1 //===--- MoveConstArgCheck.cpp - clang-tidy -----------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "MoveConstArgCheck.h"
11 #include "clang/Lex/Lexer.h"
13 using namespace clang::ast_matchers
;
15 namespace clang::tidy::performance
{
17 static void replaceCallWithArg(const CallExpr
*Call
, DiagnosticBuilder
&Diag
,
18 const SourceManager
&SM
,
19 const LangOptions
&LangOpts
) {
20 const Expr
*Arg
= Call
->getArg(0);
22 CharSourceRange BeforeArgumentsRange
= Lexer::makeFileCharRange(
23 CharSourceRange::getCharRange(Call
->getBeginLoc(), Arg
->getBeginLoc()),
25 CharSourceRange AfterArgumentsRange
= Lexer::makeFileCharRange(
26 CharSourceRange::getCharRange(Call
->getEndLoc(),
27 Call
->getEndLoc().getLocWithOffset(1)),
30 if (BeforeArgumentsRange
.isValid() && AfterArgumentsRange
.isValid()) {
31 Diag
<< FixItHint::CreateRemoval(BeforeArgumentsRange
)
32 << FixItHint::CreateRemoval(AfterArgumentsRange
);
36 void MoveConstArgCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
37 Options
.store(Opts
, "CheckTriviallyCopyableMove", CheckTriviallyCopyableMove
);
38 Options
.store(Opts
, "CheckMoveToConstRef", CheckMoveToConstRef
);
41 void MoveConstArgCheck::registerMatchers(MatchFinder
*Finder
) {
42 auto MoveCallMatcher
=
43 callExpr(callee(functionDecl(hasName("::std::move"))), argumentCountIs(1),
44 unless(isInTemplateInstantiation()))
49 castExpr(hasSourceExpression(MoveCallMatcher
)),
50 cxxConstructExpr(hasDeclaration(cxxConstructorDecl(anyOf(
51 isCopyConstructor(), isMoveConstructor()))),
52 hasArgument(0, MoveCallMatcher
)))),
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
));
70 invocation(anyOf(ArgumentWithParamMatcher
, ArgumentWithParamTypeMatcher
))
71 .bind("receiving-expr"),
75 bool IsRValueReferenceParam(const Expr
*Invocation
,
76 const QualType
*InvocationParmType
,
78 if (Invocation
&& (*InvocationParmType
)->isRValueReferenceType() &&
80 if (!Invocation
->getType()->isRecordType())
82 if (const auto *ConstructCallExpr
=
83 dyn_cast
<CXXConstructExpr
>(Invocation
)) {
84 if (const auto *ConstructorDecl
= ConstructCallExpr
->getConstructor()) {
85 if (!ConstructorDecl
->isCopyOrMoveConstructor() &&
86 !ConstructorDecl
->isDefaultConstructor())
94 void 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");
102 // Skipping matchers which have been matched.
103 if (!ReceivingExpr
&& AlreadyCheckedMoves
.contains(CallMove
))
107 AlreadyCheckedMoves
.insert(CallMove
);
109 const Expr
*Arg
= CallMove
->getArg(0);
110 const QualType ArgType
= Arg
->getType().getCanonicalType();
111 SourceManager
&SM
= Result
.Context
->getSourceManager();
113 CharSourceRange MoveRange
=
114 CharSourceRange::getCharRange(CallMove
->getSourceRange());
115 CharSourceRange FileMoveRange
=
116 Lexer::makeFileCharRange(MoveRange
, SM
, getLangOpts());
117 if (!FileMoveRange
.isValid())
120 bool IsConstArg
= ArgType
.isConstQualified();
121 bool IsTriviallyCopyable
= ArgType
.isTriviallyCopyableType(*Result
.Context
);
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.
130 // Don't warn when the type is not copyable.
131 for (const auto *Ctor
: R
->ctors()) {
132 if (Ctor
->isCopyConstructor() && Ctor
->isDeleted())
137 if (!IsConstArg
&& IsTriviallyCopyable
&& !CheckTriviallyCopyableMove
)
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.
144 IsRValueReferenceParam(ReceivingExpr
, InvocationParmType
, Arg
);
146 IsVariable
? dyn_cast
<DeclRefExpr
>(Arg
)->getDecl() : nullptr;
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
157 << (IsConstArg
&& IsVariable
&& !IsTriviallyCopyable
) << Var
160 replaceCallWithArg(CallMove
, Diag
, SM
, getLangOpts());
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()))
175 const NamedDecl
*FunctionName
= nullptr;
178 ? ReceivingCallExpr
->getDirectCallee()->getUnderlyingDecl()
179 : ReceivingConstructExpr
->getConstructor()->getUnderlyingDecl();
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();
190 NoRefType
.getAsString(PolicyWithSuppressedTag
) + " &";
193 diag(InvocationParm
->getLocation(),
194 "consider changing the %ordinal0 parameter of %1 from %2 to '%3'",
196 << (InvocationParm
->getFunctionScopeIndex() + 1) << FunctionName
197 << *InvocationParmType
<< ExpectParmTypeName
;
199 } else if (ReceivingExpr
&& CheckMoveToConstRef
) {
200 if ((*InvocationParmType
)->isRValueReferenceType())
204 auto Diag
= diag(FileMoveRange
.getBegin(),
205 "passing result of std::move() as a const reference "
206 "argument; no move will actually happen");
208 replaceCallWithArg(CallMove
, Diag
, SM
, getLangOpts());
211 if (const CXXRecordDecl
*RecordDecl
= ArgType
->getAsCXXRecordDecl();
212 RecordDecl
&& RecordDecl
->hasDefinition() &&
213 !(RecordDecl
->hasMoveConstructor() &&
214 RecordDecl
->hasMoveAssignment())) {
215 const bool MissingMoveAssignment
= !RecordDecl
->hasMoveAssignment();
216 const bool MissingMoveConstructor
= !RecordDecl
->hasMoveConstructor();
217 const bool MissingBoth
= MissingMoveAssignment
&& MissingMoveConstructor
;
219 diag(RecordDecl
->getLocation(),
221 "%select{|assignable}1%select{|/}2%select{|constructible}3",
223 << RecordDecl
<< MissingMoveAssignment
<< MissingBoth
224 << MissingMoveConstructor
;
229 } // namespace clang::tidy::performance