1 | //===--- MoveConstArgCheck.cpp - clang-tidy -----------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "MoveConstArgCheck.h" |
10 | |
11 | #include "clang/Lex/Lexer.h" |
12 | |
13 | using namespace clang::ast_matchers; |
14 | |
15 | namespace clang::tidy::performance { |
16 | |
17 | static void replaceCallWithArg(const CallExpr *Call, DiagnosticBuilder &Diag, |
18 | const SourceManager &SM, |
19 | const LangOptions &LangOpts) { |
20 | const Expr *Arg = Call->getArg(Arg: 0); |
21 | |
22 | CharSourceRange BeforeArgumentsRange = Lexer::makeFileCharRange( |
23 | Range: CharSourceRange::getCharRange(Call->getBeginLoc(), Arg->getBeginLoc()), |
24 | SM, LangOpts); |
25 | CharSourceRange AfterArgumentsRange = Lexer::makeFileCharRange( |
26 | Range: CharSourceRange::getCharRange(B: Call->getEndLoc(), |
27 | E: Call->getEndLoc().getLocWithOffset(Offset: 1)), |
28 | SM, LangOpts); |
29 | |
30 | if (BeforeArgumentsRange.isValid() && AfterArgumentsRange.isValid()) { |
31 | Diag << FixItHint::CreateRemoval(RemoveRange: BeforeArgumentsRange) |
32 | << FixItHint::CreateRemoval(RemoveRange: AfterArgumentsRange); |
33 | } |
34 | } |
35 | |
36 | void MoveConstArgCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
37 | Options.store(Options&: Opts, LocalName: "CheckTriviallyCopyableMove" , Value: CheckTriviallyCopyableMove); |
38 | Options.store(Options&: Opts, LocalName: "CheckMoveToConstRef" , Value: CheckMoveToConstRef); |
39 | } |
40 | |
41 | void MoveConstArgCheck::registerMatchers(MatchFinder *Finder) { |
42 | auto MoveCallMatcher = |
43 | callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "::std::move" ))), argumentCountIs(N: 1), |
44 | unless(isInTemplateInstantiation())) |
45 | .bind(ID: "call-move" ); |
46 | |
47 | // Match ternary expressions where either branch contains std::move |
48 | auto TernaryWithMoveMatcher = |
49 | conditionalOperator(hasDescendant(MoveCallMatcher)); |
50 | |
51 | Finder->addMatcher( |
52 | NodeMatch: expr(anyOf( |
53 | castExpr(hasSourceExpression(InnerMatcher: MoveCallMatcher)), |
54 | cxxConstructExpr(hasDeclaration(InnerMatcher: cxxConstructorDecl(anyOf( |
55 | isCopyConstructor(), isMoveConstructor()))), |
56 | hasArgument(N: 0, InnerMatcher: MoveCallMatcher)))), |
57 | Action: this); |
58 | |
59 | auto ConstTypeParmMatcher = |
60 | qualType(references(InnerMatcher: isConstQualified())).bind(ID: "invocation-parm-type" ); |
61 | auto RValueTypeParmMatcher = |
62 | qualType(rValueReferenceType()).bind(ID: "invocation-parm-type" ); |
63 | // Matches respective ParmVarDecl for a CallExpr or CXXConstructExpr. |
64 | auto ArgumentWithParamMatcher = forEachArgumentWithParam( |
65 | ArgMatcher: anyOf(MoveCallMatcher, TernaryWithMoveMatcher), |
66 | ParamMatcher: parmVarDecl( |
67 | anyOf(hasType(InnerMatcher: ConstTypeParmMatcher), hasType(InnerMatcher: RValueTypeParmMatcher))) |
68 | .bind(ID: "invocation-parm" )); |
69 | // Matches respective types of arguments for a CallExpr or CXXConstructExpr |
70 | // and it works on calls through function pointers as well. |
71 | auto ArgumentWithParamTypeMatcher = forEachArgumentWithParamType( |
72 | ArgMatcher: anyOf(MoveCallMatcher, TernaryWithMoveMatcher), |
73 | ParamMatcher: anyOf(ConstTypeParmMatcher, RValueTypeParmMatcher)); |
74 | |
75 | Finder->addMatcher( |
76 | NodeMatch: invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher)) |
77 | .bind(ID: "receiving-expr" ), |
78 | Action: this); |
79 | } |
80 | |
81 | static bool isRValueReferenceParam(const Expr *Invocation, |
82 | const QualType *InvocationParmType, |
83 | const Expr *Arg) { |
84 | if (Invocation && (*InvocationParmType)->isRValueReferenceType() && |
85 | Arg->isLValue()) { |
86 | if (!Invocation->getType()->isRecordType()) |
87 | return true; |
88 | if (const auto *ConstructCallExpr = |
89 | dyn_cast<CXXConstructExpr>(Val: Invocation)) { |
90 | if (const auto *ConstructorDecl = ConstructCallExpr->getConstructor()) { |
91 | if (!ConstructorDecl->isCopyOrMoveConstructor() && |
92 | !ConstructorDecl->isDefaultConstructor()) |
93 | return true; |
94 | } |
95 | } |
96 | } |
97 | return false; |
98 | } |
99 | |
100 | void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) { |
101 | const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>(ID: "call-move" ); |
102 | const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>(ID: "receiving-expr" ); |
103 | const auto *InvocationParm = |
104 | Result.Nodes.getNodeAs<ParmVarDecl>(ID: "invocation-parm" ); |
105 | const auto *InvocationParmType = |
106 | Result.Nodes.getNodeAs<QualType>(ID: "invocation-parm-type" ); |
107 | |
108 | // Skipping matchers which have been matched. |
109 | if (!ReceivingExpr && AlreadyCheckedMoves.contains(V: CallMove)) |
110 | return; |
111 | |
112 | if (ReceivingExpr) |
113 | AlreadyCheckedMoves.insert(V: CallMove); |
114 | |
115 | const Expr *Arg = CallMove->getArg(Arg: 0); |
116 | const QualType ArgType = Arg->getType().getCanonicalType(); |
117 | SourceManager &SM = Result.Context->getSourceManager(); |
118 | |
119 | CharSourceRange MoveRange = |
120 | CharSourceRange::getCharRange(CallMove->getSourceRange()); |
121 | CharSourceRange FileMoveRange = |
122 | Lexer::makeFileCharRange(Range: MoveRange, SM, LangOpts: getLangOpts()); |
123 | if (!FileMoveRange.isValid()) |
124 | return; |
125 | |
126 | bool IsConstArg = ArgType.isConstQualified(); |
127 | bool IsTriviallyCopyable = ArgType.isTriviallyCopyableType(Context: *Result.Context); |
128 | |
129 | if (IsConstArg || IsTriviallyCopyable) { |
130 | if (const CXXRecordDecl *R = ArgType->getAsCXXRecordDecl()) { |
131 | // According to [expr.prim.lambda]p3, "whether the closure type is |
132 | // trivially copyable" property can be changed by the implementation of |
133 | // the language, so we shouldn't rely on it when issuing diagnostics. |
134 | if (R->isLambda()) |
135 | return; |
136 | // Don't warn when the type is not copyable. |
137 | for (const auto *Ctor : R->ctors()) { |
138 | if (Ctor->isCopyConstructor() && Ctor->isDeleted()) |
139 | return; |
140 | } |
141 | } |
142 | |
143 | if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove) |
144 | return; |
145 | |
146 | bool IsVariable = isa<DeclRefExpr>(Val: Arg); |
147 | // std::move shouldn't be removed when an lvalue wrapped by std::move is |
148 | // passed to the function with an rvalue reference parameter. |
149 | bool IsRVRefParam = |
150 | isRValueReferenceParam(Invocation: ReceivingExpr, InvocationParmType, Arg); |
151 | const auto *Var = |
152 | IsVariable ? dyn_cast<DeclRefExpr>(Val: Arg)->getDecl() : nullptr; |
153 | |
154 | { |
155 | auto Diag = diag(Loc: FileMoveRange.getBegin(), |
156 | Description: "std::move of the %select{|const }0" |
157 | "%select{expression|variable %5}1 " |
158 | "%select{|of the trivially-copyable type %6 }2" |
159 | "has no effect%select{; remove std::move()|}3" |
160 | "%select{| or make the variable non-const}4" ) |
161 | << IsConstArg << IsVariable << IsTriviallyCopyable |
162 | << IsRVRefParam |
163 | << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var |
164 | << Arg->getType(); |
165 | if (!IsRVRefParam) |
166 | replaceCallWithArg(CallMove, Diag, SM, getLangOpts()); |
167 | } |
168 | if (IsRVRefParam) { |
169 | // Generate notes for an invocation with an rvalue reference parameter. |
170 | const auto *ReceivingCallExpr = dyn_cast<CallExpr>(Val: ReceivingExpr); |
171 | const auto *ReceivingConstructExpr = |
172 | dyn_cast<CXXConstructExpr>(Val: ReceivingExpr); |
173 | // Skipping the invocation which is a template instantiation. |
174 | if ((!ReceivingCallExpr || !ReceivingCallExpr->getDirectCallee() || |
175 | ReceivingCallExpr->getDirectCallee()->isTemplateInstantiation()) && |
176 | (!ReceivingConstructExpr || |
177 | !ReceivingConstructExpr->getConstructor() || |
178 | ReceivingConstructExpr->getConstructor()->isTemplateInstantiation())) |
179 | return; |
180 | |
181 | const NamedDecl *FunctionName = nullptr; |
182 | FunctionName = |
183 | ReceivingCallExpr |
184 | ? ReceivingCallExpr->getDirectCallee()->getUnderlyingDecl() |
185 | : ReceivingConstructExpr->getConstructor()->getUnderlyingDecl(); |
186 | |
187 | QualType NoRefType = (*InvocationParmType)->getPointeeType(); |
188 | PrintingPolicy PolicyWithSuppressedTag(getLangOpts()); |
189 | PolicyWithSuppressedTag.SuppressTagKeyword = true; |
190 | PolicyWithSuppressedTag.SuppressUnwrittenScope = true; |
191 | std::string ExpectParmTypeName = |
192 | NoRefType.getAsString(Policy: PolicyWithSuppressedTag); |
193 | if (!NoRefType->isPointerType()) { |
194 | NoRefType.addConst(); |
195 | ExpectParmTypeName = |
196 | NoRefType.getAsString(Policy: PolicyWithSuppressedTag) + " &" ; |
197 | } |
198 | |
199 | diag(InvocationParm->getLocation(), |
200 | "consider changing the %ordinal0 parameter of %1 from %2 to '%3'" , |
201 | DiagnosticIDs::Note) |
202 | << (InvocationParm->getFunctionScopeIndex() + 1) << FunctionName |
203 | << *InvocationParmType << ExpectParmTypeName; |
204 | } |
205 | } else if (ReceivingExpr && CheckMoveToConstRef) { |
206 | if ((*InvocationParmType)->isRValueReferenceType()) |
207 | return; |
208 | |
209 | { |
210 | auto Diag = diag(Loc: FileMoveRange.getBegin(), |
211 | Description: "passing result of std::move() as a const reference " |
212 | "argument; no move will actually happen" ); |
213 | |
214 | replaceCallWithArg(CallMove, Diag, SM, getLangOpts()); |
215 | } |
216 | |
217 | if (const CXXRecordDecl *RecordDecl = ArgType->getAsCXXRecordDecl(); |
218 | RecordDecl && RecordDecl->hasDefinition() && |
219 | !(RecordDecl->hasMoveConstructor() && |
220 | RecordDecl->hasMoveAssignment())) { |
221 | const bool MissingMoveAssignment = !RecordDecl->hasMoveAssignment(); |
222 | const bool MissingMoveConstructor = !RecordDecl->hasMoveConstructor(); |
223 | const bool MissingBoth = MissingMoveAssignment && MissingMoveConstructor; |
224 | |
225 | diag(RecordDecl->getLocation(), |
226 | "%0 is not move " |
227 | "%select{|assignable}1%select{|/}2%select{|constructible}3" , |
228 | DiagnosticIDs::Note) |
229 | << RecordDecl << MissingMoveAssignment << MissingBoth |
230 | << MissingMoveConstructor; |
231 | } |
232 | } |
233 | } |
234 | |
235 | } // namespace clang::tidy::performance |
236 | |