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