Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
^^^^^^^^^^^^^^

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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){};
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -540,6 +663,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.

Expand Down