Skip to content

Commit a7f8aea

Browse files
committed
[clang-tidy] Fix wrong FixIt in performance-move-const-arg
There are incorrect Fixit and missing warnings: case : A trivially-copyable object wrapped by std::move is passed to the function with rvalue reference parameters. Removing std::move will cause compilation errors. ``` void showInt(int&&) {} void testInt() { int a = 10; // expect: warning + nofix showInt(std::move(a)); // showInt(a) <--- wrong fix } struct Tmp {}; void showTmp(Tmp&&) {} void testTmp() { Tmp t; // expect: warning + nofix showTmp(std::move(t)); // showTmp(t) <--- wrong fix } ``` Reviewed By: aaron.ballman, Quuxplusone Differential Revision: https://reviews.llvm.org/D107450
1 parent 9006bf4 commit a7f8aea

File tree

4 files changed

+205
-13
lines changed

4 files changed

+205
-13
lines changed

clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.cpp

Lines changed: 106 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,62 @@ void MoveConstArgCheck::registerMatchers(MatchFinder *Finder) {
4747

4848
Finder->addMatcher(MoveCallMatcher, this);
4949

50+
auto ConstTypeParmMatcher =
51+
qualType(references(isConstQualified())).bind("invocation-parm-type");
52+
auto RValueTypeParmMatcher =
53+
qualType(rValueReferenceType()).bind("invocation-parm-type");
54+
// Matches respective ParmVarDecl for a CallExpr or CXXConstructExpr.
55+
auto ArgumentWithParamMatcher = forEachArgumentWithParam(
56+
MoveCallMatcher, parmVarDecl(anyOf(hasType(ConstTypeParmMatcher),
57+
hasType(RValueTypeParmMatcher)))
58+
.bind("invocation-parm"));
59+
// Matches respective types of arguments for a CallExpr or CXXConstructExpr
60+
// and it works on calls through function pointers as well.
61+
auto ArgumentWithParamTypeMatcher = forEachArgumentWithParamType(
62+
MoveCallMatcher, anyOf(ConstTypeParmMatcher, RValueTypeParmMatcher));
63+
5064
Finder->addMatcher(
51-
invocation(forEachArgumentWithParam(
52-
MoveCallMatcher,
53-
parmVarDecl(hasType(references(isConstQualified())))))
65+
invocation(anyOf(ArgumentWithParamMatcher, ArgumentWithParamTypeMatcher))
5466
.bind("receiving-expr"),
5567
this);
5668
}
5769

70+
bool IsRValueReferenceParam(const Expr *Invocation,
71+
const QualType &InvocationParmType,
72+
const Expr *Arg) {
73+
if (Invocation && InvocationParmType->isRValueReferenceType() &&
74+
Arg->isLValue()) {
75+
if (!Invocation->getType()->isRecordType())
76+
return true;
77+
else {
78+
if (const auto *ConstructCallExpr =
79+
dyn_cast<CXXConstructExpr>(Invocation)) {
80+
if (const auto *ConstructorDecl = ConstructCallExpr->getConstructor()) {
81+
if (!ConstructorDecl->isCopyOrMoveConstructor() &&
82+
!ConstructorDecl->isDefaultConstructor())
83+
return true;
84+
}
85+
}
86+
}
87+
}
88+
return false;
89+
}
90+
5891
void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) {
5992
const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
6093
const auto *ReceivingExpr = Result.Nodes.getNodeAs<Expr>("receiving-expr");
94+
const auto *InvocationParm =
95+
Result.Nodes.getNodeAs<ParmVarDecl>("invocation-parm");
96+
const auto *InvocationParmType =
97+
Result.Nodes.getNodeAs<QualType>("invocation-parm-type");
98+
99+
// Skipping matchers which have been matched.
100+
if (!ReceivingExpr && AlreadyCheckedMoves.contains(CallMove))
101+
return;
102+
103+
if (ReceivingExpr)
104+
AlreadyCheckedMoves.insert(CallMove);
105+
61106
const Expr *Arg = CallMove->getArg(0);
62107
SourceManager &SM = Result.Context->getSourceManager();
63108

@@ -90,20 +135,68 @@ void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) {
90135
return;
91136

92137
bool IsVariable = isa<DeclRefExpr>(Arg);
138+
// std::move shouldn't be removed when an lvalue wrapped by std::move is
139+
// passed to the function with an rvalue reference parameter.
140+
bool IsRVRefParam =
141+
IsRValueReferenceParam(ReceivingExpr, *InvocationParmType, Arg);
93142
const auto *Var =
94143
IsVariable ? dyn_cast<DeclRefExpr>(Arg)->getDecl() : nullptr;
95-
auto Diag = diag(FileMoveRange.getBegin(),
96-
"std::move of the %select{|const }0"
97-
"%select{expression|variable %4}1 "
98-
"%select{|of the trivially-copyable type %5 }2"
99-
"has no effect; remove std::move()"
100-
"%select{| or make the variable non-const}3")
101-
<< IsConstArg << IsVariable << IsTriviallyCopyable
102-
<< (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var
103-
<< Arg->getType();
104144

105-
replaceCallWithArg(CallMove, Diag, SM, getLangOpts());
145+
{
146+
auto Diag = diag(FileMoveRange.getBegin(),
147+
"std::move of the %select{|const }0"
148+
"%select{expression|variable %5}1 "
149+
"%select{|of the trivially-copyable type %6 }2"
150+
"has no effect%select{; remove std::move()|}3"
151+
"%select{| or make the variable non-const}4")
152+
<< IsConstArg << IsVariable << IsTriviallyCopyable
153+
<< IsRVRefParam
154+
<< (IsConstArg && IsVariable && !IsTriviallyCopyable) << Var
155+
<< Arg->getType();
156+
if (!IsRVRefParam)
157+
replaceCallWithArg(CallMove, Diag, SM, getLangOpts());
158+
}
159+
if (IsRVRefParam) {
160+
// Generate notes for an invocation with an rvalue reference parameter.
161+
const auto *ReceivingCallExpr = dyn_cast<CallExpr>(ReceivingExpr);
162+
const auto *ReceivingConstructExpr =
163+
dyn_cast<CXXConstructExpr>(ReceivingExpr);
164+
// Skipping the invocation which is a template instantiation.
165+
if ((!ReceivingCallExpr || !ReceivingCallExpr->getDirectCallee() ||
166+
ReceivingCallExpr->getDirectCallee()->isTemplateInstantiation()) &&
167+
(!ReceivingConstructExpr ||
168+
!ReceivingConstructExpr->getConstructor() ||
169+
ReceivingConstructExpr->getConstructor()->isTemplateInstantiation()))
170+
return;
171+
172+
const NamedDecl *FunctionName = nullptr;
173+
FunctionName =
174+
ReceivingCallExpr
175+
? ReceivingCallExpr->getDirectCallee()->getUnderlyingDecl()
176+
: ReceivingConstructExpr->getConstructor()->getUnderlyingDecl();
177+
178+
QualType NoRefType = (*InvocationParmType)->getPointeeType();
179+
PrintingPolicy PolicyWithSuppressedTag(getLangOpts());
180+
PolicyWithSuppressedTag.SuppressTagKeyword = true;
181+
PolicyWithSuppressedTag.SuppressUnwrittenScope = true;
182+
std::string ExpectParmTypeName =
183+
NoRefType.getAsString(PolicyWithSuppressedTag);
184+
if (!NoRefType->isPointerType()) {
185+
NoRefType.addConst();
186+
ExpectParmTypeName =
187+
NoRefType.getAsString(PolicyWithSuppressedTag) + " &";
188+
}
189+
190+
diag(InvocationParm->getLocation(),
191+
"consider changing the %ordinal0 parameter of %1 from %2 to '%3'",
192+
DiagnosticIDs::Note)
193+
<< (InvocationParm->getFunctionScopeIndex() + 1) << FunctionName
194+
<< *InvocationParmType << ExpectParmTypeName;
195+
}
106196
} else if (ReceivingExpr) {
197+
if ((*InvocationParmType)->isRValueReferenceType())
198+
return;
199+
107200
auto Diag = diag(FileMoveRange.getBegin(),
108201
"passing result of std::move() as a const reference "
109202
"argument; no move will actually happen");

clang-tools-extra/clang-tidy/performance/MoveConstArgCheck.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MOVECONSTANTARGUMENTCHECK_H
1111

1212
#include "../ClangTidyCheck.h"
13+
#include "llvm/ADT/DenseSet.h"
1314

1415
namespace clang {
1516
namespace tidy {
@@ -36,6 +37,7 @@ class MoveConstArgCheck : public ClangTidyCheck {
3637

3738
private:
3839
const bool CheckTriviallyCopyableMove;
40+
llvm::DenseSet<const CallExpr *> AlreadyCheckedMoves;
3941
};
4042

4143
} // namespace performance

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ Changes in existing checks
175175
option to control whether to warn on narrowing integer to floating-point
176176
conversions.
177177

178+
- Improved :doc:`performance-move-const-arg` check.
179+
180+
Removed a wrong FixIt for trivially copyable objects wrapped by ``std::move()`` and passed to an rvalue reference parameter. Removal of ``std::move()`` would break the code.
178181

179182
Removed checks
180183
^^^^^^^^^^^^^^

clang-tools-extra/test/clang-tidy/checkers/performance-move-const-arg.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,97 @@ void lambda2() {
246246
};
247247
f(MoveSemantics());
248248
}
249+
250+
void showInt(int &&v);
251+
void showInt(int v1, int &&v2);
252+
void showPointer(const char *&&s);
253+
void showPointer2(const char *const &&s);
254+
void showTriviallyCopyable(TriviallyCopyable &&obj);
255+
void showTriviallyCopyablePointer(const TriviallyCopyable *&&obj);
256+
void testFunctions() {
257+
int a = 10;
258+
showInt(std::move(a));
259+
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: std::move of the variable 'a' of the trivially-copyable type 'int' has no effect [performance-move-const-arg]
260+
// CHECK-MESSAGES: :[[@LINE-10]]:20: note: consider changing the 1st parameter of 'showInt' from 'int &&' to 'const int &'
261+
showInt(int());
262+
showInt(a, std::move(a));
263+
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: std::move of the variable 'a' of the trivially-copyable type 'int' has no effect [performance-move-const-arg]
264+
// CHECK-MESSAGES: :[[@LINE-13]]:28: note: consider changing the 2nd parameter of 'showInt' from 'int &&' to 'const int &'
265+
const char* s = "";
266+
showPointer(std::move(s));
267+
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: std::move of the variable 's' of the trivially-copyable type 'const char *' has no effect [performance-move-const-arg]
268+
// CHECK-MESSAGES: :[[@LINE-16]]:32: note: consider changing the 1st parameter of 'showPointer' from 'const char *&&' to 'const char *'
269+
showPointer2(std::move(s));
270+
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: std::move of the variable 's' of the trivially-copyable type 'const char *' has no effect [performance-move-const-arg]
271+
// CHECK-MESSAGES: :[[@LINE-18]]:39: note: consider changing the 1st parameter of 'showPointer2' from 'const char *const &&' to 'const char *const'
272+
TriviallyCopyable *obj = new TriviallyCopyable();
273+
showTriviallyCopyable(std::move(*obj));
274+
// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: std::move of the expression of the trivially-copyable type 'TriviallyCopyable' has no effect [performance-move-const-arg]
275+
// CHECK-MESSAGES: :[[@LINE-21]]:48: note: consider changing the 1st parameter of 'showTriviallyCopyable' from 'TriviallyCopyable &&' to 'const TriviallyCopyable &'
276+
showTriviallyCopyablePointer(std::move(obj));
277+
// CHECK-MESSAGES: :[[@LINE-1]]:32: warning: std::move of the variable 'obj' of the trivially-copyable type 'TriviallyCopyable *' has no effect [performance-move-const-arg]
278+
// CHECK-MESSAGES: :[[@LINE-23]]:62: note: consider changing the 1st parameter of 'showTriviallyCopyablePointer' from 'const TriviallyCopyable *&&' to 'const TriviallyCopyable *'
279+
}
280+
template <class T>
281+
void forwardToShowInt(T && t) {
282+
showInt(static_cast<T &&>(t));
283+
}
284+
void testTemplate() {
285+
int a = 10;
286+
forwardToShowInt(std::move(a));
287+
// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: std::move of the variable 'a' of the trivially-copyable type 'int' has no effect [performance-move-const-arg]
288+
}
289+
290+
struct Tmp {
291+
Tmp();
292+
Tmp(int &&a);
293+
Tmp(int v1, int &&a);
294+
Tmp(const char *&&s);
295+
Tmp(TriviallyCopyable&& obj);
296+
Tmp(const TriviallyCopyable *&&obj);
297+
void showTmp(TriviallyCopyable&& t);
298+
static void showTmpStatic(TriviallyCopyable&& t);
299+
};
300+
void testMethods() {
301+
Tmp t;
302+
int a = 10;
303+
Tmp t1(std::move(a));
304+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: std::move of the variable 'a' of the trivially-copyable type 'int' has no effect [performance-move-const-arg]
305+
// CHECK-MESSAGES: :[[@LINE-13]]:13: note: consider changing the 1st parameter of 'Tmp' from 'int &&' to 'const int &'
306+
Tmp t2(a, std::move(a));
307+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: std::move of the variable 'a' of the trivially-copyable type 'int' has no effect [performance-move-const-arg]
308+
// CHECK-MESSAGES: :[[@LINE-15]]:21: note: consider changing the 2nd parameter of 'Tmp' from 'int &&' to 'const int &'
309+
const char* s = "";
310+
Tmp t3(std::move(s));
311+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: std::move of the variable 's' of the trivially-copyable type 'const char *' has no effect [performance-move-const-arg]
312+
// CHECK-MESSAGES: :[[@LINE-18]]:21: note: consider changing the 1st parameter of 'Tmp' from 'const char *&&' to 'const char *'
313+
TriviallyCopyable *obj = new TriviallyCopyable();
314+
Tmp t4(std::move(*obj));
315+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: std::move of the expression of the trivially-copyable type 'TriviallyCopyable' has no effect [performance-move-const-arg]
316+
// CHECK-MESSAGES: :[[@LINE-21]]:27: note: consider changing the 1st parameter of 'Tmp' from 'TriviallyCopyable &&' to 'const TriviallyCopyable &'
317+
Tmp t5(std::move(obj));
318+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: std::move of the variable 'obj' of the trivially-copyable type 'TriviallyCopyable *' has no effect [performance-move-const-arg]
319+
// CHECK-MESSAGES: :[[@LINE-23]]:34: note: consider changing the 1st parameter of 'Tmp' from 'const TriviallyCopyable *&&' to 'const TriviallyCopyable *'
320+
t.showTmp(std::move(*obj));
321+
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: std::move of the expression of the trivially-copyable type 'TriviallyCopyable' has no effect [performance-move-const-arg]
322+
// CHECK-MESSAGES: :[[@LINE-25]]:36: note: consider changing the 1st parameter of 'showTmp' from 'TriviallyCopyable &&' to 'const TriviallyCopyable &'
323+
Tmp::showTmpStatic(std::move(*obj));
324+
// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: std::move of the expression of the trivially-copyable type 'TriviallyCopyable' has no effect [performance-move-const-arg]
325+
// CHECK-MESSAGES: :[[@LINE-27]]:49: note: consider changing the 1st parameter of 'showTmpStatic' from 'TriviallyCopyable &&' to 'const TriviallyCopyable &'
326+
}
327+
328+
void showA(A &&v) {}
329+
void testA() {
330+
A a;
331+
showA(std::move(a));
332+
}
333+
334+
void testFuncPointer() {
335+
int a = 10;
336+
void (*choice)(int, int &&);
337+
choice = showInt;
338+
choice(std::move(a), std::move(a));
339+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: std::move of the variable 'a' of the trivially-copyable type 'int' has no effect; remove std::move() [performance-move-const-arg]
340+
// CHECK-FIXES: choice(a, std::move(a));
341+
// CHECK-MESSAGES: :[[@LINE-3]]:24: warning: std::move of the variable 'a' of the trivially-copyable type 'int' has no effect [performance-move-const-arg]
342+
}

0 commit comments

Comments
 (0)