Skip to content

Commit 51b0b24

Browse files
committed
[clang-tidy] add 'isMoveConstructibleInBoundCXXRecordDecl' matcher to properly handle private move constructors in pass-by-value check
1 parent 15b485a commit 51b0b24

File tree

3 files changed

+107
-22
lines changed

3 files changed

+107
-22
lines changed

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

Lines changed: 42 additions & 22 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,29 +45,35 @@ 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;
44-
}
45-
} // namespace
46-
47-
static bool isFriendOfClass(const CXXRecordDecl *Class,
48-
const CXXRecordDecl *Friend) {
49-
return llvm::any_of(
50-
Class->friends(), [Friend](FriendDecl *FriendDecl) -> bool {
51-
if (TypeSourceInfo *FriendTypeSource = FriendDecl->getFriendType()) {
52-
const QualType FriendType = FriendTypeSource->getType();
53-
return FriendType->getAsCXXRecordDecl() == Friend;
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;
5472
}
55-
return false;
73+
return true;
5674
});
5775
}
76+
} // namespace
5877

5978
static TypeMatcher notTemplateSpecConstRefType() {
6079
return lValueReferenceType(
@@ -238,8 +257,9 @@ void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
238257
.bind("Param"))))),
239258
hasDeclaration(cxxConstructorDecl(
240259
isCopyConstructor(), unless(isDeleted()),
241-
hasDeclContext(
242-
cxxRecordDecl(isMoveConstructible())))))))
260+
hasDeclContext(cxxRecordDecl(
261+
isMoveConstructibleInBoundCXXRecordDecl(
262+
"outer"))))))))
243263
.bind("Initializer")))
244264
.bind("Ctor")),
245265
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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,64 @@ 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+
// CHECK-FIXES: X1(const ProtectedMovable &M) : M(M) {}
287+
ProtectedMovable M;
288+
};
289+
290+
struct X2 {
291+
X2(const PrivateMovable &M) : M(M) {}
292+
// CHECK-FIXES: X2(const PrivateMovable &M) : M(M) {}
293+
PrivateMovable M;
294+
};
295+
296+
struct X3 {
297+
X3(const InheritedProtectedMovable &M) : M(M) {}
298+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
299+
// CHECK-FIXES: X3(InheritedProtectedMovable M) : M(std::move(M)) {}
300+
InheritedProtectedMovable M;
301+
};
302+
303+
struct X4 {
304+
X4(const InheritedPrivateMovable &M) : M(M) {}
305+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
306+
// CHECK-FIXES: X4(InheritedPrivateMovable M) : M(std::move(M)) {}
307+
InheritedPrivateMovable M;
308+
};
309+
310+
struct X5 {
311+
X5(const PrivateMovable &M) : M(M) {}
312+
// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
313+
// CHECK-FIXES: X5(PrivateMovable M) : M(std::move(M)) {}
314+
PrivateMovable M;
315+
};

0 commit comments

Comments
 (0)