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