Skip to content

Commit 6631665

Browse files
authored
[clang-tidy] properly handle private move constructors in modernize-pass-by-value check (#141304)
Fixed false positives when class passed by const-reference had a private move constructor, which could not be used for a fix-it. Closes #140236.
1 parent 56679a8 commit 6631665

File tree

3 files changed

+107
-11
lines changed

3 files changed

+107
-11
lines changed

clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,21 @@ using namespace llvm;
2020

2121
namespace clang::tidy::modernize {
2222

23+
static bool isFirstFriendOfSecond(const CXXRecordDecl *Friend,
24+
const CXXRecordDecl *Class) {
25+
return llvm::any_of(
26+
Class->friends(), [Friend](FriendDecl *FriendDecl) -> bool {
27+
if (TypeSourceInfo *FriendTypeSource = FriendDecl->getFriendType()) {
28+
const QualType FriendType = FriendTypeSource->getType();
29+
return FriendType->getAsCXXRecordDecl() == Friend;
30+
}
31+
return false;
32+
});
33+
}
34+
2335
namespace {
24-
/// Matches move-constructible classes.
36+
/// Matches move-constructible classes whose constructor can be called inside
37+
/// a CXXRecordDecl with a bound ID.
2538
///
2639
/// Given
2740
/// \code
@@ -32,15 +45,33 @@ namespace {
3245
/// Bar(Bar &&) = deleted;
3346
/// int a;
3447
/// };
48+
///
49+
/// class Buz {
50+
/// Buz(Buz &&);
51+
/// int a;
52+
/// friend class Outer;
53+
/// };
54+
///
55+
/// class Outer {
56+
/// };
3557
/// \endcode
36-
/// recordDecl(isMoveConstructible())
37-
/// matches "Foo".
38-
AST_MATCHER(CXXRecordDecl, isMoveConstructible) {
39-
for (const CXXConstructorDecl *Ctor : Node.ctors()) {
40-
if (Ctor->isMoveConstructor() && !Ctor->isDeleted())
41-
return true;
42-
}
43-
return false;
58+
/// recordDecl(isMoveConstructibleInBoundCXXRecordDecl("Outer"))
59+
/// matches "Foo", "Buz".
60+
AST_MATCHER_P(CXXRecordDecl, isMoveConstructibleInBoundCXXRecordDecl, StringRef,
61+
RecordDeclID) {
62+
return Builder->removeBindings(
63+
[this,
64+
&Node](const ast_matchers::internal::BoundNodesMap &Nodes) -> bool {
65+
const auto *BoundClass =
66+
Nodes.getNode(this->RecordDeclID).get<CXXRecordDecl>();
67+
for (const CXXConstructorDecl *Ctor : Node.ctors()) {
68+
if (Ctor->isMoveConstructor() && !Ctor->isDeleted() &&
69+
(Ctor->getAccess() == AS_public ||
70+
(BoundClass && isFirstFriendOfSecond(BoundClass, &Node))))
71+
return false;
72+
}
73+
return true;
74+
});
4475
}
4576
} // namespace
4677

@@ -202,6 +233,7 @@ void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
202233
traverse(
203234
TK_AsIs,
204235
cxxConstructorDecl(
236+
ofClass(cxxRecordDecl().bind("outer")),
205237
forEachConstructorInitializer(
206238
cxxCtorInitializer(
207239
unless(isBaseInitializer()),
@@ -225,8 +257,9 @@ void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
225257
.bind("Param"))))),
226258
hasDeclaration(cxxConstructorDecl(
227259
isCopyConstructor(), unless(isDeleted()),
228-
hasDeclContext(
229-
cxxRecordDecl(isMoveConstructible())))))))
260+
hasDeclContext(cxxRecordDecl(
261+
isMoveConstructibleInBoundCXXRecordDecl(
262+
"outer"))))))))
230263
.bind("Initializer")))
231264
.bind("Ctor")),
232265
this);

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,10 @@ Changes in existing checks
281281
excluding variables with ``thread_local`` storage class specifier from being
282282
matched.
283283

284+
- Improved :doc:`modernize-pass-by-value
285+
<clang-tidy/checks/modernize/pass-by-value>` check by fixing false positives
286+
when class passed by const-reference had a private move constructor.
287+
284288
- Improved :doc:`modernize-type-traits
285289
<clang-tidy/checks/modernize/type-traits>` check by detecting more type traits.
286290

clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,62 @@ struct W3 {
252252
W3(W1 &&, Movable &&M);
253253
Movable M;
254254
};
255+
256+
struct ProtectedMovable {
257+
ProtectedMovable() = default;
258+
ProtectedMovable(const ProtectedMovable &) {}
259+
protected:
260+
ProtectedMovable(ProtectedMovable &&) {}
261+
};
262+
263+
struct PrivateMovable {
264+
PrivateMovable() = default;
265+
PrivateMovable(const PrivateMovable &) {}
266+
private:
267+
PrivateMovable(PrivateMovable &&) {}
268+
269+
friend struct X5;
270+
};
271+
272+
struct InheritedProtectedMovable : ProtectedMovable {
273+
InheritedProtectedMovable() = default;
274+
InheritedProtectedMovable(const InheritedProtectedMovable &) {}
275+
InheritedProtectedMovable(InheritedProtectedMovable &&) {}
276+
};
277+
278+
struct InheritedPrivateMovable : PrivateMovable {
279+
InheritedPrivateMovable() = default;
280+
InheritedPrivateMovable(const InheritedPrivateMovable &) {}
281+
InheritedPrivateMovable(InheritedPrivateMovable &&) {}
282+
};
283+
284+
struct X1 {
285+
X1(const ProtectedMovable &M) : M(M) {}
286+
ProtectedMovable M;
287+
};
288+
289+
struct X2 {
290+
X2(const PrivateMovable &M) : M(M) {}
291+
PrivateMovable M;
292+
};
293+
294+
struct X3 {
295+
X3(const InheritedProtectedMovable &M) : M(M) {}
296+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
297+
// CHECK-FIXES: X3(InheritedProtectedMovable M) : M(std::move(M)) {}
298+
InheritedProtectedMovable M;
299+
};
300+
301+
struct X4 {
302+
X4(const InheritedPrivateMovable &M) : M(M) {}
303+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
304+
// CHECK-FIXES: X4(InheritedPrivateMovable M) : M(std::move(M)) {}
305+
InheritedPrivateMovable M;
306+
};
307+
308+
struct X5 {
309+
X5(const PrivateMovable &M) : M(M) {}
310+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
311+
// CHECK-FIXES: X5(PrivateMovable M) : M(std::move(M)) {}
312+
PrivateMovable M;
313+
};

0 commit comments

Comments
 (0)