Skip to content

Commit 5de443a

Browse files
negativvbvictor
andauthored
[clang-tidy] Make copy-and-swap idiom more general for bugprone-unhandled-self-assignment (#147066)
This change enhances the `bugprone-unhandled-self-assignment` checker by adding an additional matcher that generalizes the copy-and-swap idiom pattern detection. # What Changed Added a new matcher that checks for: - An instance of the current class being created in operator= (regardless of constructor arguments) - That instance being passed to a `swap` function call # Problem Solved This fix reduces false positives in PMR-like scenarios where "extended" constructors are used (typically taking an additional allocator argument). The checker now properly recognizes copy-and-swap implementations that use extended copy/move constructors instead of flagging them as unhandled self-assignment cases. Fixes #146324 --------- Co-authored-by: Baranov Victor <[email protected]>
1 parent ffdada1 commit 5de443a

File tree

3 files changed

+241
-8
lines changed

3 files changed

+241
-8
lines changed

clang-tools-extra/clang-tidy/bugprone/UnhandledSelfAssignmentCheck.cpp

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,28 @@ void UnhandledSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) {
6969
cxxMethodDecl(unless(hasDescendant(cxxMemberCallExpr(callee(cxxMethodDecl(
7070
hasName("operator="), ofClass(equalsBoundNode("class"))))))));
7171

72+
// Checking that some kind of constructor is called and followed by a `swap`:
73+
// T& operator=(const T& other) {
74+
// T tmp{this->internal_data(), some, other, args};
75+
// swap(tmp);
76+
// return *this;
77+
// }
78+
const auto HasCopyAndSwap = cxxMethodDecl(
79+
ofClass(cxxRecordDecl()),
80+
hasBody(compoundStmt(
81+
hasDescendant(
82+
varDecl(hasType(cxxRecordDecl(equalsBoundNode("class"))))
83+
.bind("tmp_var")),
84+
hasDescendant(stmt(anyOf(
85+
cxxMemberCallExpr(hasArgument(
86+
0, declRefExpr(to(varDecl(equalsBoundNode("tmp_var")))))),
87+
callExpr(
88+
callee(functionDecl(hasName("swap"))), argumentCountIs(2),
89+
hasAnyArgument(
90+
declRefExpr(to(varDecl(equalsBoundNode("tmp_var"))))),
91+
hasAnyArgument(unaryOperator(has(cxxThisExpr()),
92+
hasOperatorName("*"))))))))));
93+
7294
DeclarationMatcher AdditionalMatcher = cxxMethodDecl();
7395
if (WarnOnlyIfThisHasSuspiciousField) {
7496
// Matcher for standard smart pointers.
@@ -89,14 +111,14 @@ void UnhandledSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) {
89111
hasType(arrayType())))))));
90112
}
91113

92-
Finder->addMatcher(cxxMethodDecl(ofClass(cxxRecordDecl().bind("class")),
93-
isCopyAssignmentOperator(), IsUserDefined,
94-
HasReferenceParam, HasNoSelfCheck,
95-
unless(HasNonTemplateSelfCopy),
96-
unless(HasTemplateSelfCopy),
97-
HasNoNestedSelfAssign, AdditionalMatcher)
98-
.bind("copyAssignmentOperator"),
99-
this);
114+
Finder->addMatcher(
115+
cxxMethodDecl(
116+
ofClass(cxxRecordDecl().bind("class")), isCopyAssignmentOperator(),
117+
IsUserDefined, HasReferenceParam, HasNoSelfCheck,
118+
unless(HasNonTemplateSelfCopy), unless(HasTemplateSelfCopy),
119+
unless(HasCopyAndSwap), HasNoNestedSelfAssign, AdditionalMatcher)
120+
.bind("copyAssignmentOperator"),
121+
this);
100122
}
101123

102124
void UnhandledSelfAssignmentCheck::check(

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ Changes in existing checks
114114
<clang-tidy/checks/bugprone/infinite-loop>` check by adding detection for
115115
variables introduced by structured bindings.
116116

117+
- Improved :doc:`bugprone-unhandled-self-assignment
118+
<clang-tidy/checks/bugprone/unhandled-self-assignment>` check by adding
119+
an additional matcher that generalizes the copy-and-swap idiom pattern
120+
detection.
121+
117122
Removed checks
118123
^^^^^^^^^^^^^^
119124

clang-tools-extra/test/clang-tidy/checkers/bugprone/unhandled-self-assignment.cpp

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ template <class T>
2828
class auto_ptr {
2929
};
3030

31+
namespace pmr {
32+
template <typename TYPE = void>
33+
class allocator {};
34+
}
35+
36+
struct allocator_arg_t {} allocator_arg;
37+
3138
} // namespace std
3239

3340
void assert(int expression){};
@@ -229,6 +236,122 @@ bool operator!=(Foo2 &, Foo2 &) {
229236
};
230237
}
231238

239+
// Missing call to `swap` function
240+
class AllocatorAwareClassNoSwap {
241+
// pointer member to trigger bugprone-unhandled-self-assignment
242+
void *foo = nullptr;
243+
244+
public:
245+
using allocator_type = std::pmr::allocator<>;
246+
247+
AllocatorAwareClassNoSwap(const AllocatorAwareClassNoSwap& other) {
248+
}
249+
250+
AllocatorAwareClassNoSwap(const AllocatorAwareClassNoSwap& other, const allocator_type& alloc) {
251+
}
252+
253+
AllocatorAwareClassNoSwap& operator=(const AllocatorAwareClassNoSwap& other) {
254+
// CHECK-MESSAGES: [[@LINE-1]]:32: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
255+
AllocatorAwareClassNoSwap tmp(other, get_allocator());
256+
return *this;
257+
}
258+
259+
allocator_type get_allocator() const {
260+
return allocator_type();
261+
}
262+
};
263+
264+
// "Wrong" type is passed to member `swap` function
265+
class AllocatorAwareClassSwapWrongArgType {
266+
// pointer member to trigger bugprone-unhandled-self-assignment
267+
void *foo = nullptr;
268+
269+
public:
270+
using allocator_type = std::pmr::allocator<>;
271+
272+
AllocatorAwareClassSwapWrongArgType(const AllocatorAwareClassSwapWrongArgType& other) {
273+
}
274+
275+
AllocatorAwareClassSwapWrongArgType(const AllocatorAwareClassSwapWrongArgType& other, const allocator_type& alloc) {
276+
}
277+
278+
AllocatorAwareClassSwapWrongArgType& operator=(const AllocatorAwareClassSwapWrongArgType& other) {
279+
// CHECK-MESSAGES: [[@LINE-1]]:42: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
280+
int tmp = 0;
281+
swap(tmp);
282+
283+
return *this;
284+
}
285+
286+
void swap(int&) noexcept {
287+
}
288+
289+
allocator_type get_allocator() const {
290+
return allocator_type();
291+
}
292+
};
293+
294+
295+
// Making ADL `swap` call but with wrong argument type
296+
class AllocatorAwareClassAdlSwapWrongArgType {
297+
// pointer member to trigger bugprone-unhandled-self-assignment
298+
void *foo = nullptr;
299+
300+
public:
301+
using allocator_type = std::pmr::allocator<>;
302+
303+
AllocatorAwareClassAdlSwapWrongArgType(const AllocatorAwareClassAdlSwapWrongArgType& other) {
304+
}
305+
306+
AllocatorAwareClassAdlSwapWrongArgType(const AllocatorAwareClassAdlSwapWrongArgType& other, const allocator_type& alloc) {
307+
}
308+
309+
AllocatorAwareClassAdlSwapWrongArgType& operator=(const AllocatorAwareClassAdlSwapWrongArgType& other) {
310+
// CHECK-MESSAGES: [[@LINE-1]]:45: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
311+
int tmp = 0;
312+
swap(*this, tmp);
313+
314+
return *this;
315+
}
316+
317+
allocator_type get_allocator() const {
318+
return allocator_type();
319+
}
320+
321+
friend void swap(AllocatorAwareClassAdlSwapWrongArgType&, int&) {
322+
}
323+
};
324+
325+
// `this` isn't passed to `swap`
326+
class AllocatorAwareClassAdlSwapWrongArgs {
327+
// pointer member to trigger bugprone-unhandled-self-assignment
328+
void *foo = nullptr;
329+
330+
public:
331+
using allocator_type = std::pmr::allocator<>;
332+
333+
AllocatorAwareClassAdlSwapWrongArgs(const AllocatorAwareClassAdlSwapWrongArgs& other) {
334+
}
335+
336+
AllocatorAwareClassAdlSwapWrongArgs(const AllocatorAwareClassAdlSwapWrongArgs& other, const allocator_type& alloc) {
337+
}
338+
339+
AllocatorAwareClassAdlSwapWrongArgs& operator=(const AllocatorAwareClassAdlSwapWrongArgs& other) {
340+
// CHECK-MESSAGES: [[@LINE-1]]:42: warning: operator=() does not handle self-assignment properly [bugprone-unhandled-self-assignment]
341+
AllocatorAwareClassAdlSwapWrongArgs tmp1(other, get_allocator());
342+
AllocatorAwareClassAdlSwapWrongArgs tmp2(*this, get_allocator());
343+
swap(tmp1, tmp2);
344+
return *this;
345+
}
346+
347+
allocator_type get_allocator() const {
348+
return allocator_type();
349+
}
350+
351+
friend void swap(AllocatorAwareClassAdlSwapWrongArgs&, AllocatorAwareClassAdlSwapWrongArgs&) {
352+
}
353+
};
354+
232355
///////////////////////////////////////////////////////////////////
233356
/// Test cases correctly ignored by the check.
234357

@@ -540,6 +663,89 @@ class NotACopyAssignmentOperator {
540663
Uy *getUy() const { return Ptr2; }
541664
};
542665

666+
// Support "extended" copy/move constructors
667+
class AllocatorAwareClass {
668+
// pointer member to trigger bugprone-unhandled-self-assignment
669+
void *foo = nullptr;
670+
671+
public:
672+
using allocator_type = std::pmr::allocator<>;
673+
674+
AllocatorAwareClass(const AllocatorAwareClass& other) {
675+
}
676+
677+
AllocatorAwareClass(const AllocatorAwareClass& other, const allocator_type& alloc) {
678+
}
679+
680+
AllocatorAwareClass& operator=(const AllocatorAwareClass& other) {
681+
AllocatorAwareClass tmp(other, get_allocator());
682+
swap(tmp);
683+
return *this;
684+
}
685+
686+
void swap(AllocatorAwareClass& other) noexcept {
687+
}
688+
689+
allocator_type get_allocator() const {
690+
return allocator_type();
691+
}
692+
};
693+
694+
// Support "extended" copy/move constructors + std::swap
695+
class AllocatorAwareClassStdSwap {
696+
// pointer member to trigger bugprone-unhandled-self-assignment
697+
void *foo = nullptr;
698+
699+
public:
700+
using allocator_type = std::pmr::allocator<>;
701+
702+
AllocatorAwareClassStdSwap(const AllocatorAwareClassStdSwap& other) {
703+
}
704+
705+
AllocatorAwareClassStdSwap(const AllocatorAwareClassStdSwap& other, const allocator_type& alloc) {
706+
}
707+
708+
AllocatorAwareClassStdSwap& operator=(const AllocatorAwareClassStdSwap& other) {
709+
using std::swap;
710+
711+
AllocatorAwareClassStdSwap tmp(other, get_allocator());
712+
swap(*this, tmp);
713+
return *this;
714+
}
715+
716+
allocator_type get_allocator() const {
717+
return allocator_type();
718+
}
719+
};
720+
721+
// Support "extended" copy/move constructors + ADL swap
722+
class AllocatorAwareClassAdlSwap {
723+
// pointer member to trigger bugprone-unhandled-self-assignment
724+
void *foo = nullptr;
725+
726+
public:
727+
using allocator_type = std::pmr::allocator<>;
728+
729+
AllocatorAwareClassAdlSwap(const AllocatorAwareClassAdlSwap& other) {
730+
}
731+
732+
AllocatorAwareClassAdlSwap(const AllocatorAwareClassAdlSwap& other, const allocator_type& alloc) {
733+
}
734+
735+
AllocatorAwareClassAdlSwap& operator=(const AllocatorAwareClassAdlSwap& other) {
736+
AllocatorAwareClassAdlSwap tmp(other, get_allocator());
737+
swap(*this, tmp);
738+
return *this;
739+
}
740+
741+
allocator_type get_allocator() const {
742+
return allocator_type();
743+
}
744+
745+
friend void swap(AllocatorAwareClassAdlSwap&, AllocatorAwareClassAdlSwap&) {
746+
}
747+
};
748+
543749
///////////////////////////////////////////////////////////////////
544750
/// Test cases which should be caught by the check.
545751

0 commit comments

Comments
 (0)