aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp38
-rw-r--r--clang-tools-extra/docs/ReleaseNotes.rst5
-rw-r--r--clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-self-assignment.cpp206
3 files changed, 241 insertions, 8 deletions
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp
index 1f432c4..c4c4267 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp
@@ -69,6 +69,28 @@ void UnhandledSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) {
cxxMethodDecl(unless(hasDescendant(cxxMemberCallExpr(callee(cxxMethodDecl(
hasName("operator="), ofClass(equalsBoundNode("class"))))))));
+ // Checking that some kind of constructor is called and followed by a `swap`:
+ // T& operator=(const T& other) {
+ // T tmp{this->internal_data(), some, other, args};
+ // swap(tmp);
+ // return *this;
+ // }
+ const auto HasCopyAndSwap = cxxMethodDecl(
+ ofClass(cxxRecordDecl()),
+ hasBody(compoundStmt(
+ hasDescendant(
+ varDecl(hasType(cxxRecordDecl(equalsBoundNode("class"))))
+ .bind("tmp_var")),
+ hasDescendant(stmt(anyOf(
+ cxxMemberCallExpr(hasArgument(
+ 0, declRefExpr(to(varDecl(equalsBoundNode("tmp_var")))))),
+ callExpr(
+ callee(functionDecl(hasName("swap"))), argumentCountIs(2),
+ hasAnyArgument(
+ declRefExpr(to(varDecl(equalsBoundNode("tmp_var"))))),
+ hasAnyArgument(unaryOperator(has(cxxThisExpr()),
+ hasOperatorName("*"))))))))));
+
DeclarationMatcher AdditionalMatcher = cxxMethodDecl();
if (WarnOnlyIfThisHasSuspiciousField) {
// Matcher for standard smart pointers.
@@ -89,14 +111,14 @@ void UnhandledSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) {
hasType(arrayType())))))));
}
- Finder->addMatcher(cxxMethodDecl(ofClass(cxxRecordDecl().bind("class")),
- isCopyAssignmentOperator(), IsUserDefined,
- HasReferenceParam, HasNoSelfCheck,
- unless(HasNonTemplateSelfCopy),
- unless(HasTemplateSelfCopy),
- HasNoNestedSelfAssign, AdditionalMatcher)
- .bind("copyAssignmentOperator"),
- this);
+ Finder->addMatcher(
+ cxxMethodDecl(
+ ofClass(cxxRecordDecl().bind("class")), isCopyAssignmentOperator(),
+ IsUserDefined, HasReferenceParam, HasNoSelfCheck,
+ unless(HasNonTemplateSelfCopy), unless(HasTemplateSelfCopy),
+ unless(HasCopyAndSwap), HasNoNestedSelfAssign, AdditionalMatcher)
+ .bind("copyAssignmentOperator"),
+ this);
}
void UnhandledSelfAssignmentCheck::check(
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index bccb0ca..bd2d6eb 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -114,6 +114,11 @@ Changes in existing checks
<clang-tidy/checks/bugprone/infinite-loop>` check by adding detection for
variables introduced by structured bindings.
+- Improved :doc:`bugprone-unhandled-self-assignment
+ <clang-tidy/checks/bugprone/unhandled-self-assignment>` check by adding
+ an additional matcher that generalizes the copy-and-swap idiom pattern
+ detection.
+
Removed checks
^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-self-assignment.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-self-assignment.cpp
index 8610393..c2a8ddc 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-self-assignment.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-self-assignment.cpp
@@ -28,6 +28,13 @@ template <class T>
class auto_ptr {
};
+namespace pmr {
+ template <typename TYPE = void>
+ class allocator {};
+}
+
+struct allocator_arg_t {} allocator_arg;
+
} // namespace std
void assert(int expression){};
@@ -229,6 +236,122 @@ bool operator!=(Foo2 &, Foo2 &) {
};
}
+// Missing call to `swap` function
+class AllocatorAwareClassNoSwap {
+ // pointer member to trigger bugprone-unhandled-self-assignment
+ void *foo = nullptr;
+
+ public:
+ using allocator_type = std::pmr::allocator<>;
+
+ AllocatorAwareClassNoSwap(const AllocatorAwareClassNoSwap& other) {
+ }
+
+ AllocatorAwareClassNoSwap(const AllocatorAwareClassNoSwap& other, const allocator_type& alloc) {
+ }
+
+ AllocatorAwareClassNoSwap& operator=(const AllocatorAwareClassNoSwap& other) {
+ // CHECK-MESSAGES: [[@LINE-1]]:32: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
+ AllocatorAwareClassNoSwap tmp(other, get_allocator());
+ return *this;
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_type();
+ }
+};
+
+// "Wrong" type is passed to member `swap` function
+class AllocatorAwareClassSwapWrongArgType {
+ // pointer member to trigger bugprone-unhandled-self-assignment
+ void *foo = nullptr;
+
+ public:
+ using allocator_type = std::pmr::allocator<>;
+
+ AllocatorAwareClassSwapWrongArgType(const AllocatorAwareClassSwapWrongArgType& other) {
+ }
+
+ AllocatorAwareClassSwapWrongArgType(const AllocatorAwareClassSwapWrongArgType& other, const allocator_type& alloc) {
+ }
+
+ AllocatorAwareClassSwapWrongArgType& operator=(const AllocatorAwareClassSwapWrongArgType& other) {
+ // CHECK-MESSAGES: [[@LINE-1]]:42: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
+ int tmp = 0;
+ swap(tmp);
+
+ return *this;
+ }
+
+ void swap(int&) noexcept {
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_type();
+ }
+};
+
+
+// Making ADL `swap` call but with wrong argument type
+class AllocatorAwareClassAdlSwapWrongArgType {
+ // pointer member to trigger bugprone-unhandled-self-assignment
+ void *foo = nullptr;
+
+ public:
+ using allocator_type = std::pmr::allocator<>;
+
+ AllocatorAwareClassAdlSwapWrongArgType(const AllocatorAwareClassAdlSwapWrongArgType& other) {
+ }
+
+ AllocatorAwareClassAdlSwapWrongArgType(const AllocatorAwareClassAdlSwapWrongArgType& other, const allocator_type& alloc) {
+ }
+
+ AllocatorAwareClassAdlSwapWrongArgType& operator=(const AllocatorAwareClassAdlSwapWrongArgType& other) {
+ // CHECK-MESSAGES: [[@LINE-1]]:45: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
+ int tmp = 0;
+ swap(*this, tmp);
+
+ return *this;
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_type();
+ }
+
+ friend void swap(AllocatorAwareClassAdlSwapWrongArgType&, int&) {
+ }
+};
+
+// `this` isn't passed to `swap`
+class AllocatorAwareClassAdlSwapWrongArgs {
+ // pointer member to trigger bugprone-unhandled-self-assignment
+ void *foo = nullptr;
+
+ public:
+ using allocator_type = std::pmr::allocator<>;
+
+ AllocatorAwareClassAdlSwapWrongArgs(const AllocatorAwareClassAdlSwapWrongArgs& other) {
+ }
+
+ AllocatorAwareClassAdlSwapWrongArgs(const AllocatorAwareClassAdlSwapWrongArgs& other, const allocator_type& alloc) {
+ }
+
+ AllocatorAwareClassAdlSwapWrongArgs& operator=(const AllocatorAwareClassAdlSwapWrongArgs& other) {
+ // CHECK-MESSAGES: [[@LINE-1]]:42: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
+ AllocatorAwareClassAdlSwapWrongArgs tmp1(other, get_allocator());
+ AllocatorAwareClassAdlSwapWrongArgs tmp2(*this, get_allocator());
+ swap(tmp1, tmp2);
+ return *this;
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_type();
+ }
+
+ friend void swap(AllocatorAwareClassAdlSwapWrongArgs&, AllocatorAwareClassAdlSwapWrongArgs&) {
+ }
+};
+
///////////////////////////////////////////////////////////////////
/// Test cases correctly ignored by the check.
@@ -540,6 +663,89 @@ public:
Uy *getUy() const { return Ptr2; }
};
+// Support "extended" copy/move constructors
+class AllocatorAwareClass {
+ // pointer member to trigger bugprone-unhandled-self-assignment
+ void *foo = nullptr;
+
+ public:
+ using allocator_type = std::pmr::allocator<>;
+
+ AllocatorAwareClass(const AllocatorAwareClass& other) {
+ }
+
+ AllocatorAwareClass(const AllocatorAwareClass& other, const allocator_type& alloc) {
+ }
+
+ AllocatorAwareClass& operator=(const AllocatorAwareClass& other) {
+ AllocatorAwareClass tmp(other, get_allocator());
+ swap(tmp);
+ return *this;
+ }
+
+ void swap(AllocatorAwareClass& other) noexcept {
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_type();
+ }
+};
+
+// Support "extended" copy/move constructors + std::swap
+class AllocatorAwareClassStdSwap {
+ // pointer member to trigger bugprone-unhandled-self-assignment
+ void *foo = nullptr;
+
+ public:
+ using allocator_type = std::pmr::allocator<>;
+
+ AllocatorAwareClassStdSwap(const AllocatorAwareClassStdSwap& other) {
+ }
+
+ AllocatorAwareClassStdSwap(const AllocatorAwareClassStdSwap& other, const allocator_type& alloc) {
+ }
+
+ AllocatorAwareClassStdSwap& operator=(const AllocatorAwareClassStdSwap& other) {
+ using std::swap;
+
+ AllocatorAwareClassStdSwap tmp(other, get_allocator());
+ swap(*this, tmp);
+ return *this;
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_type();
+ }
+};
+
+// Support "extended" copy/move constructors + ADL swap
+class AllocatorAwareClassAdlSwap {
+ // pointer member to trigger bugprone-unhandled-self-assignment
+ void *foo = nullptr;
+
+ public:
+ using allocator_type = std::pmr::allocator<>;
+
+ AllocatorAwareClassAdlSwap(const AllocatorAwareClassAdlSwap& other) {
+ }
+
+ AllocatorAwareClassAdlSwap(const AllocatorAwareClassAdlSwap& other, const allocator_type& alloc) {
+ }
+
+ AllocatorAwareClassAdlSwap& operator=(const AllocatorAwareClassAdlSwap& other) {
+ AllocatorAwareClassAdlSwap tmp(other, get_allocator());
+ swap(*this, tmp);
+ return *this;
+ }
+
+ allocator_type get_allocator() const {
+ return allocator_type();
+ }
+
+ friend void swap(AllocatorAwareClassAdlSwap&, AllocatorAwareClassAdlSwap&) {
+ }
+};
+
///////////////////////////////////////////////////////////////////
/// Test cases which should be caught by the check.