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