diff --git a/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp index 1f432c4ccc5f0..69f02ee37bba5 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp @@ -69,6 +69,22 @@ 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(unless(hasAncestor(classTemplateDecl())))), + hasDescendant( + stmt(hasDescendant( + varDecl(hasType(cxxRecordDecl(equalsBoundNode("class")))) + .bind("tmp_var")), + hasDescendant(callExpr(callee(functionDecl(hasName("swap"))), + hasAnyArgument(declRefExpr(to(varDecl( + equalsBoundNode("tmp_var")))))))))); + DeclarationMatcher AdditionalMatcher = cxxMethodDecl(); if (WarnOnlyIfThisHasSuspiciousField) { // Matcher for standard smart pointers. @@ -89,14 +105,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 e021d6350694e..aab36269413d6 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -189,6 +189,12 @@ Changes in existing checks calls of ``std::string`` constructor with char pointer, start position and length parameters. +- Improved :doc:`bugprone-unhandled-self-assignment + ` check by adding + an additional matcher that generalizes the copy-and-swap idiom pattern + detection. The checker now properly recognizes copy-and-swap implementations + that use "extended" copy/move constructors. + - Improved :doc:`bugprone-unchecked-optional-access ` fixing false positives from smart pointer accessors repeated in checking ``has_value`` 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 8610393449f97..f2f61062f883f 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 auto_ptr { }; +namespace pmr { + template + class allocator {}; +} + +struct allocator_arg_t {} allocator_arg; + } // namespace std void assert(int expression){}; @@ -540,6 +547,89 @@ class NotACopyAssignmentOperator { 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.