clang-tools 19.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(MoveCallMatcher, this);
48
49 auto ConstTypeParmMatcher =
50 qualType(references(isConstQualified())).bind("invocation-parm-type");
51 auto RValueTypeParmMatcher =
52 qualType(rValueReferenceType()).bind("invocation-parm-type");
53 // Matches respective ParmVarDecl for a CallExpr or CXXConstructExpr.
54 auto ArgumentWithParamMatcher = forEachArgumentWithParam(
55 MoveCallMatcher, parmVarDecl(anyOf(hasType(ConstTypeParmMatcher),
56 hasType(RValueTypeParmMatcher)))
57 .bind("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 MoveCallMatcher, anyOf(ConstTypeParmMatcher, RValueTypeParmMatcher));
62
63 Finder->addMatcher(
64 invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher))
65 .bind("receiving-expr"),
66 this);
67}
68
69bool 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>(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
88void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) {
89 const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
90 const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr");
91 const auto *InvocationParm =
92 Result.Nodes.getNodeAs<ParmVarDecl>("invocation-parm");
93 const auto *InvocationParmType =
94 Result.Nodes.getNodeAs<QualType>("invocation-parm-type");
95
96 // Skipping matchers which have been matched.
97 if (!ReceivingExpr && AlreadyCheckedMoves.contains(CallMove))
98 return;
99
100 if (ReceivingExpr)
101 AlreadyCheckedMoves.insert(CallMove);
102
103 const Expr *Arg = CallMove->getArg(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(MoveRange, SM, getLangOpts());
111 if (!FileMoveRange.isValid())
112 return;
113
114 bool IsConstArg = ArgType.isConstQualified();
115 bool IsTriviallyCopyable = ArgType.isTriviallyCopyableType(*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>(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(ReceivingExpr, InvocationParmType, Arg);
139 const auto *Var =
140 IsVariable ? dyn_cast<DeclRefExpr>(Arg)->getDecl() : nullptr;
141
142 {
143 auto Diag = diag(FileMoveRange.getBegin(),
144 "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>(ReceivingExpr);
159 const auto *ReceivingConstructExpr =
160 dyn_cast<CXXConstructExpr>(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(PolicyWithSuppressedTag);
181 if (!NoRefType->isPointerType()) {
182 NoRefType.addConst();
183 ExpectParmTypeName =
184 NoRefType.getAsString(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(FileMoveRange.getBegin(),
199 "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
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