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
15namespace clang::tidy::performance {
16
17static 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
36void 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
41void 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
81static 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
100void 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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.cpp