[clang-tools-extra] [clang-tidy] properly handle private move constructors in `modernize-pass-by-value` check (PR #141304)
Baranov Victor via cfe-commits
cfe-commits at lists.llvm.org
Fri May 23 16:54:24 PDT 2025
https://github.com/vbvictor created https://github.com/llvm/llvm-project/pull/141304
Fixed false positives when class passed by const-reference had a private move constructor, which could not be used for a fix-it.
>From fea5b6609391e6dc9d33777640ff98df1b54fecf Mon Sep 17 00:00:00 2001
From: Baranov Victor <bar.victor.2002 at gmail.com>
Date: Sat, 17 May 2025 18:40:54 +0300
Subject: [PATCH 1/4] [clang-tidy] Add isFriendOfClass helper function for
PassByValueCheck
---
.../clang-tidy/modernize/PassByValueCheck.cpp | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
index 35f90fb8da15b..dd276b9e9be32 100644
--- a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
@@ -44,6 +44,18 @@ AST_MATCHER(CXXRecordDecl, isMoveConstructible) {
}
} // namespace
+static bool isFriendOfClass(const CXXRecordDecl *Class,
+ const CXXRecordDecl *Friend) {
+ return llvm::any_of(
+ Class->friends(), [Friend](FriendDecl *FriendDecl) -> bool {
+ if (TypeSourceInfo *FriendTypeSource = FriendDecl->getFriendType()) {
+ const QualType FriendType = FriendTypeSource->getType();
+ return FriendType->getAsCXXRecordDecl() == Friend;
+ }
+ return false;
+ });
+}
+
static TypeMatcher notTemplateSpecConstRefType() {
return lValueReferenceType(
pointee(unless(elaboratedType(namesType(templateSpecializationType()))),
>From 8ab2714171e15bf089b3e2a317f1ffeeac2534c5 Mon Sep 17 00:00:00 2001
From: Baranov Victor <bar.victor.2002 at gmail.com>
Date: Sat, 17 May 2025 19:03:46 +0300
Subject: [PATCH 2/4] [clang-tidy] Bind outer class in constructor matcher
---
clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
index dd276b9e9be32..454cf8e5be637 100644
--- a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
@@ -214,6 +214,7 @@ void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
traverse(
TK_AsIs,
cxxConstructorDecl(
+ ofClass(cxxRecordDecl().bind("outer")),
forEachConstructorInitializer(
cxxCtorInitializer(
unless(isBaseInitializer()),
>From 8a688dff0ba82b57ed21d843adef8673d1faa0a1 Mon Sep 17 00:00:00 2001
From: Baranov Victor <bar.victor.2002 at gmail.com>
Date: Fri, 23 May 2025 18:10:09 +0300
Subject: [PATCH 3/4] [clang-tidy] add
'isMoveConstructibleInBoundCXXRecordDecl' matcher to properly handle private
move constructors in `pass-by-value` check
---
.../clang-tidy/modernize/PassByValueCheck.cpp | 64 ++++++++++++-------
clang-tools-extra/docs/ReleaseNotes.rst | 4 ++
.../checkers/modernize/pass-by-value.cpp | 61 ++++++++++++++++++
3 files changed, 107 insertions(+), 22 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
index 454cf8e5be637..343cc966b7e09 100644
--- a/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/PassByValueCheck.cpp
@@ -20,8 +20,21 @@ using namespace llvm;
namespace clang::tidy::modernize {
+static bool isFirstFriendOfSecond(const CXXRecordDecl *Friend,
+ const CXXRecordDecl *Class) {
+ return llvm::any_of(
+ Class->friends(), [Friend](FriendDecl *FriendDecl) -> bool {
+ if (TypeSourceInfo *FriendTypeSource = FriendDecl->getFriendType()) {
+ const QualType FriendType = FriendTypeSource->getType();
+ return FriendType->getAsCXXRecordDecl() == Friend;
+ }
+ return false;
+ });
+}
+
namespace {
-/// Matches move-constructible classes.
+/// Matches move-constructible classes whose constructor can be called inside
+/// a CXXRecordDecl with a bound ID.
///
/// Given
/// \code
@@ -32,29 +45,35 @@ namespace {
/// Bar(Bar &&) = deleted;
/// int a;
/// };
+///
+/// class Buz {
+/// Buz(Buz &&);
+/// int a;
+/// friend class Outer;
+/// };
+///
+/// class Outer {
+/// };
/// \endcode
-/// recordDecl(isMoveConstructible())
-/// matches "Foo".
-AST_MATCHER(CXXRecordDecl, isMoveConstructible) {
- for (const CXXConstructorDecl *Ctor : Node.ctors()) {
- if (Ctor->isMoveConstructor() && !Ctor->isDeleted())
- return true;
- }
- return false;
-}
-} // namespace
-
-static bool isFriendOfClass(const CXXRecordDecl *Class,
- const CXXRecordDecl *Friend) {
- return llvm::any_of(
- Class->friends(), [Friend](FriendDecl *FriendDecl) -> bool {
- if (TypeSourceInfo *FriendTypeSource = FriendDecl->getFriendType()) {
- const QualType FriendType = FriendTypeSource->getType();
- return FriendType->getAsCXXRecordDecl() == Friend;
+/// recordDecl(isMoveConstructibleInBoundCXXRecordDecl("Outer"))
+/// matches "Foo", "Buz".
+AST_MATCHER_P(CXXRecordDecl, isMoveConstructibleInBoundCXXRecordDecl, StringRef,
+ RecordDeclID) {
+ return Builder->removeBindings(
+ [this,
+ &Node](const ast_matchers::internal::BoundNodesMap &Nodes) -> bool {
+ const auto *BoundClass =
+ Nodes.getNode(this->RecordDeclID).get<CXXRecordDecl>();
+ for (const CXXConstructorDecl *Ctor : Node.ctors()) {
+ if (Ctor->isMoveConstructor() && !Ctor->isDeleted() &&
+ (Ctor->getAccess() == AS_public ||
+ (BoundClass && isFirstFriendOfSecond(BoundClass, &Node))))
+ return false;
}
- return false;
+ return true;
});
}
+} // namespace
static TypeMatcher notTemplateSpecConstRefType() {
return lValueReferenceType(
@@ -238,8 +257,9 @@ void PassByValueCheck::registerMatchers(MatchFinder *Finder) {
.bind("Param"))))),
hasDeclaration(cxxConstructorDecl(
isCopyConstructor(), unless(isDeleted()),
- hasDeclContext(
- cxxRecordDecl(isMoveConstructible())))))))
+ hasDeclContext(cxxRecordDecl(
+ isMoveConstructibleInBoundCXXRecordDecl(
+ "outer"))))))))
.bind("Initializer")))
.bind("Ctor")),
this);
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 8032f0d23f494..75f3471070646 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -203,6 +203,10 @@ Changes in existing checks
excluding variables with ``thread_local`` storage class specifier from being
matched.
+- Improved :doc:`modernize-pass-by-value
+ <clang-tidy/checks/modernize/pass-by-value>` check by fixing false positives
+ when class passed by const-reference had a private move constructor.
+
- Improved :doc:`modernize-use-default-member-init
<clang-tidy/checks/modernize/use-default-member-init>` check by matching
``constexpr`` and ``static``` values on member initialization and by detecting
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp
index be33988607b27..72f8d41ea6550 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp
@@ -252,3 +252,64 @@ struct W3 {
W3(W1 &&, Movable &&M);
Movable M;
};
+
+struct ProtectedMovable {
+ ProtectedMovable() = default;
+ ProtectedMovable(const ProtectedMovable &) {}
+protected:
+ ProtectedMovable(ProtectedMovable &&) {}
+};
+
+struct PrivateMovable {
+ PrivateMovable() = default;
+ PrivateMovable(const PrivateMovable &) {}
+private:
+ PrivateMovable(PrivateMovable &&) {}
+
+ friend struct X5;
+};
+
+struct InheritedProtectedMovable : ProtectedMovable {
+ InheritedProtectedMovable() = default;
+ InheritedProtectedMovable(const InheritedProtectedMovable &) {}
+ InheritedProtectedMovable(InheritedProtectedMovable &&) {}
+};
+
+struct InheritedPrivateMovable : PrivateMovable {
+ InheritedPrivateMovable() = default;
+ InheritedPrivateMovable(const InheritedPrivateMovable &) {}
+ InheritedPrivateMovable(InheritedPrivateMovable &&) {}
+};
+
+struct X1 {
+ X1(const ProtectedMovable &M) : M(M) {}
+ // CHECK-FIXES: X1(const ProtectedMovable &M) : M(M) {}
+ ProtectedMovable M;
+};
+
+struct X2 {
+ X2(const PrivateMovable &M) : M(M) {}
+ // CHECK-FIXES: X2(const PrivateMovable &M) : M(M) {}
+ PrivateMovable M;
+};
+
+struct X3 {
+ X3(const InheritedProtectedMovable &M) : M(M) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
+ // CHECK-FIXES: X3(InheritedProtectedMovable M) : M(std::move(M)) {}
+ InheritedProtectedMovable M;
+};
+
+struct X4 {
+ X4(const InheritedPrivateMovable &M) : M(M) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
+ // CHECK-FIXES: X4(InheritedPrivateMovable M) : M(std::move(M)) {}
+ InheritedPrivateMovable M;
+};
+
+struct X5 {
+ X5(const PrivateMovable &M) : M(M) {}
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: pass by value and use std::move
+ // CHECK-FIXES: X5(PrivateMovable M) : M(std::move(M)) {}
+ PrivateMovable M;
+};
>From 7c65dec5912dceaeedb8bad9075ded96b99cd4ca Mon Sep 17 00:00:00 2001
From: Baranov Victor <bar.victor.2002 at gmail.com>
Date: Sat, 24 May 2025 02:52:30 +0300
Subject: [PATCH 4/4] deleted unused check-fixes in tests
---
.../test/clang-tidy/checkers/modernize/pass-by-value.cpp | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp
index 72f8d41ea6550..7538862dd83f8 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/pass-by-value.cpp
@@ -283,13 +283,11 @@ struct InheritedPrivateMovable : PrivateMovable {
struct X1 {
X1(const ProtectedMovable &M) : M(M) {}
- // CHECK-FIXES: X1(const ProtectedMovable &M) : M(M) {}
ProtectedMovable M;
};
struct X2 {
X2(const PrivateMovable &M) : M(M) {}
- // CHECK-FIXES: X2(const PrivateMovable &M) : M(M) {}
PrivateMovable M;
};
More information about the cfe-commits
mailing list