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 | Finder->addMatcher(NodeMatch: MoveCallMatcher, Action: this); |
48 | |
49 | auto ConstTypeParmMatcher = |
50 | qualType(references(InnerMatcher: isConstQualified())).bind(ID: "invocation-parm-type" ); |
51 | auto RValueTypeParmMatcher = |
52 | qualType(rValueReferenceType()).bind(ID: "invocation-parm-type" ); |
53 | // Matches respective ParmVarDecl for a CallExpr or CXXConstructExpr. |
54 | auto ArgumentWithParamMatcher = forEachArgumentWithParam( |
55 | ArgMatcher: MoveCallMatcher, ParamMatcher: parmVarDecl(anyOf(hasType(InnerMatcher: ConstTypeParmMatcher), |
56 | hasType(InnerMatcher: RValueTypeParmMatcher))) |
57 | .bind(ID: "invocation-parm" )); |
58 | // Matches respective types of arguments for a CallExpr or CXXConstructExpr |
59 | // and it works on calls through function pointers as well. |
60 | auto ArgumentWithParamTypeMatcher = forEachArgumentWithParamType( |
61 | ArgMatcher: MoveCallMatcher, ParamMatcher: anyOf(ConstTypeParmMatcher, RValueTypeParmMatcher)); |
62 | |
63 | Finder->addMatcher( |
64 | NodeMatch: invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher)) |
65 | .bind(ID: "receiving-expr" ), |
66 | Action: this); |
67 | } |
68 | |
69 | bool IsRValueReferenceParam(const Expr *Invocation, |
70 | const QualType *InvocationParmType, |
71 | const Expr *Arg) { |
72 | if (Invocation && (*InvocationParmType)->isRValueReferenceType() && |
73 | Arg->isLValue()) { |
74 | if (!Invocation->getType()->isRecordType()) |
75 | return true; |
76 | if (const auto *ConstructCallExpr = |
77 | dyn_cast<CXXConstructExpr>(Val: Invocation)) { |
78 | if (const auto *ConstructorDecl = ConstructCallExpr->getConstructor()) { |
79 | if (!ConstructorDecl->isCopyOrMoveConstructor() && |
80 | !ConstructorDecl->isDefaultConstructor()) |
81 | return true; |
82 | } |
83 | } |
84 | } |
85 | return false; |
86 | } |
87 | |
88 | void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) { |
89 | const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>(ID: "call-move" ); |
90 | const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>(ID: "receiving-expr" ); |
91 | const auto *InvocationParm = |
92 | Result.Nodes.getNodeAs<ParmVarDecl>(ID: "invocation-parm" ); |
93 | const auto *InvocationParmType = |
94 | Result.Nodes.getNodeAs<QualType>(ID: "invocation-parm-type" ); |
95 | |
96 | // Skipping matchers which have been matched. |
97 | if (!ReceivingExpr && AlreadyCheckedMoves.contains(V: CallMove)) |
98 | return; |
99 | |
100 | if (ReceivingExpr) |
101 | AlreadyCheckedMoves.insert(V: CallMove); |
102 | |
103 | const Expr *Arg = CallMove->getArg(Arg: 0); |
104 | const QualType ArgType = Arg->getType().getCanonicalType(); |
105 | SourceManager &SM = Result.Context->getSourceManager(); |
106 | |
107 | CharSourceRange MoveRange = |
108 | CharSourceRange::getCharRange(CallMove->getSourceRange()); |
109 | CharSourceRange FileMoveRange = |
110 | Lexer::makeFileCharRange(Range: MoveRange, SM, LangOpts: getLangOpts()); |
111 | if (!FileMoveRange.isValid()) |
112 | return; |
113 | |
114 | bool IsConstArg = ArgType.isConstQualified(); |
115 | bool IsTriviallyCopyable = ArgType.isTriviallyCopyableType(Context: *Result.Context); |
116 | |
117 | if (IsConstArg || IsTriviallyCopyable) { |
118 | if (const CXXRecordDecl *R = ArgType->getAsCXXRecordDecl()) { |
119 | // According to [expr.prim.lambda]p3, "whether the closure type is |
120 | // trivially copyable" property can be changed by the implementation of |
121 | // the language, so we shouldn't rely on it when issuing diagnostics. |
122 | if (R->isLambda()) |
123 | return; |
124 | // Don't warn when the type is not copyable. |
125 | for (const auto *Ctor : R->ctors()) { |
126 | if (Ctor->isCopyConstructor() && Ctor->isDeleted()) |
127 | return; |
128 | } |
129 | } |
130 | |
131 | if (!IsConstArg && IsTriviallyCopyable && !CheckTriviallyCopyableMove) |
132 | return; |
133 | |
134 | bool IsVariable = isa<DeclRefExpr>(Val: Arg); |
135 | // std::move shouldn't be removed when an lvalue wrapped by std::move is |
136 | // passed to the function with an rvalue reference parameter. |
137 | bool IsRVRefParam = |
138 | IsRValueReferenceParam(Invocation: ReceivingExpr, InvocationParmType, Arg); |
139 | const auto *Var = |
140 | IsVariable ? dyn_cast<DeclRefExpr>(Val: Arg)->getDecl() : nullptr; |
141 | |
142 | { |
143 | auto Diag = diag(Loc: FileMoveRange.getBegin(), |
144 | Description: "std::move of the %select{|const }0" |
145 | "%select{expression|variable %5}1 " |
146 | "%select{|of the trivially-copyable type %6 }2" |
147 | "has no effect%select{; remove std::move()|}3" |
148 | "%select{| or make the variable non-const}4" ) |
149 | << IsConstArg << IsVariable << IsTriviallyCopyable |
150 | << IsRVRefParam |
151 | << (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var |
152 | << Arg->getType(); |
153 | if (!IsRVRefParam) |
154 | replaceCallWithArg(CallMove, Diag, SM, getLangOpts()); |
155 | } |
156 | if (IsRVRefParam) { |
157 | // Generate notes for an invocation with an rvalue reference parameter. |
158 | const auto *ReceivingCallExpr = dyn_cast<CallExpr>(Val: ReceivingExpr); |
159 | const auto *ReceivingConstructExpr = |
160 | dyn_cast<CXXConstructExpr>(Val: ReceivingExpr); |
161 | // Skipping the invocation which is a template instantiation. |
162 | if ((!ReceivingCallExpr || !ReceivingCallExpr->getDirectCallee() || |
163 | ReceivingCallExpr->getDirectCallee()->isTemplateInstantiation()) && |
164 | (!ReceivingConstructExpr || |
165 | !ReceivingConstructExpr->getConstructor() || |
166 | ReceivingConstructExpr->getConstructor()->isTemplateInstantiation())) |
167 | return; |
168 | |
169 | const NamedDecl *FunctionName = nullptr; |
170 | FunctionName = |
171 | ReceivingCallExpr |
172 | ? ReceivingCallExpr->getDirectCallee()->getUnderlyingDecl() |
173 | : ReceivingConstructExpr->getConstructor()->getUnderlyingDecl(); |
174 | |
175 | QualType NoRefType = (*InvocationParmType)->getPointeeType(); |
176 | PrintingPolicy PolicyWithSuppressedTag(getLangOpts()); |
177 | PolicyWithSuppressedTag.SuppressTagKeyword = true; |
178 | PolicyWithSuppressedTag.SuppressUnwrittenScope = true; |
179 | std::string ExpectParmTypeName = |
180 | NoRefType.getAsString(Policy: PolicyWithSuppressedTag); |
181 | if (!NoRefType->isPointerType()) { |
182 | NoRefType.addConst(); |
183 | ExpectParmTypeName = |
184 | NoRefType.getAsString(Policy: PolicyWithSuppressedTag) + " &" ; |
185 | } |
186 | |
187 | diag(InvocationParm->getLocation(), |
188 | "consider changing the %ordinal0 parameter of %1 from %2 to '%3'" , |
189 | DiagnosticIDs::Note) |
190 | << (InvocationParm->getFunctionScopeIndex() + 1) << FunctionName |
191 | << *InvocationParmType << ExpectParmTypeName; |
192 | } |
193 | } else if (ReceivingExpr && CheckMoveToConstRef) { |
194 | if ((*InvocationParmType)->isRValueReferenceType()) |
195 | return; |
196 | |
197 | { |
198 | auto Diag = diag(Loc: FileMoveRange.getBegin(), |
199 | Description: "passing result of std::move() as a const reference " |
200 | "argument; no move will actually happen" ); |
201 | |
202 | replaceCallWithArg(CallMove, Diag, SM, getLangOpts()); |
203 | } |
204 | |
205 | if (const CXXRecordDecl *RecordDecl = ArgType->getAsCXXRecordDecl(); |
206 | RecordDecl && !(RecordDecl->hasMoveConstructor() && |
207 | RecordDecl->hasMoveAssignment())) { |
208 | const bool MissingMoveAssignment = !RecordDecl->hasMoveAssignment(); |
209 | const bool MissingMoveConstructor = !RecordDecl->hasMoveConstructor(); |
210 | const bool MissingBoth = MissingMoveAssignment && MissingMoveConstructor; |
211 | |
212 | diag(RecordDecl->getLocation(), |
213 | "%0 is not move " |
214 | "%select{|assignable}1%select{|/}2%select{|constructible}3" , |
215 | DiagnosticIDs::Note) |
216 | << RecordDecl << MissingMoveAssignment << MissingBoth |
217 | << MissingMoveConstructor; |
218 | } |
219 | } |
220 | } |
221 | |
222 | } // namespace clang::tidy::performance |
223 | |