Skip to content

Commit 51f178d

Browse files
authored
[analyzer] MallocChecker: Recognize std::atomics in smart pointer suppression. (#90918)
Fixes #90498. Same as 5337efc for atomic builtins, but for `std::atomic` this time. This is useful because even though the actual builtin atomic is still there, it may be buried beyond the inlining depth limit. Also add one popular custom smart pointer class name to the name-based heuristics, which isn't necessary to fix the bug but arguably a good idea regardless.
1 parent 73a0144 commit 51f178d

File tree

3 files changed

+130
-12
lines changed

3 files changed

+130
-12
lines changed

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3451,7 +3451,7 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) {
34513451
if (N.contains_insensitive("ptr") || N.contains_insensitive("pointer")) {
34523452
if (N.contains_insensitive("ref") || N.contains_insensitive("cnt") ||
34533453
N.contains_insensitive("intrusive") ||
3454-
N.contains_insensitive("shared")) {
3454+
N.contains_insensitive("shared") || N.ends_with_insensitive("rc")) {
34553455
return true;
34563456
}
34573457
}
@@ -3483,13 +3483,24 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
34833483
// original reference count is positive, we should not report use-after-frees
34843484
// on objects deleted in such destructors. This can probably be improved
34853485
// through better shared pointer modeling.
3486-
if (ReleaseDestructorLC) {
3486+
if (ReleaseDestructorLC && (ReleaseDestructorLC == CurrentLC ||
3487+
ReleaseDestructorLC->isParentOf(CurrentLC))) {
34873488
if (const auto *AE = dyn_cast<AtomicExpr>(S)) {
3489+
// Check for manual use of atomic builtins.
34883490
AtomicExpr::AtomicOp Op = AE->getOp();
34893491
if (Op == AtomicExpr::AO__c11_atomic_fetch_add ||
34903492
Op == AtomicExpr::AO__c11_atomic_fetch_sub) {
3491-
if (ReleaseDestructorLC == CurrentLC ||
3492-
ReleaseDestructorLC->isParentOf(CurrentLC)) {
3493+
BR.markInvalid(getTag(), S);
3494+
}
3495+
} else if (const auto *CE = dyn_cast<CallExpr>(S)) {
3496+
// Check for `std::atomic` and such. This covers both regular method calls
3497+
// and operator calls.
3498+
if (const auto *MD =
3499+
dyn_cast_or_null<CXXMethodDecl>(CE->getDirectCallee())) {
3500+
const CXXRecordDecl *RD = MD->getParent();
3501+
// A bit wobbly with ".contains()" because it may be like
3502+
// "__atomic_base" or something.
3503+
if (StringRef(RD->getNameAsString()).contains("atomic")) {
34933504
BR.markInvalid(getTag(), S);
34943505
}
34953506
}

clang/test/Analysis/Inputs/system-header-simulator-cxx.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,13 @@ template<
12601260
iterator end() const { return iterator(val + 1); }
12611261
};
12621262

1263+
template <typename T>
1264+
class atomic {
1265+
public:
1266+
T operator++();
1267+
T operator--();
1268+
};
1269+
12631270
namespace execution {
12641271
class sequenced_policy {};
12651272
}

clang/test/Analysis/NewDelete-atomics.cpp

Lines changed: 108 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ typedef enum memory_order {
2020
memory_order_seq_cst = __ATOMIC_SEQ_CST
2121
} memory_order;
2222

23-
class Obj {
23+
class RawObj {
2424
int RefCnt;
2525

2626
public:
@@ -37,11 +37,27 @@ class Obj {
3737
void foo();
3838
};
3939

40+
class StdAtomicObj {
41+
std::atomic<int> RefCnt;
42+
43+
public:
44+
int incRef() {
45+
return ++RefCnt;
46+
}
47+
48+
int decRef() {
49+
return --RefCnt;
50+
}
51+
52+
void foo();
53+
};
54+
55+
template <typename T>
4056
class IntrusivePtr {
41-
Obj *Ptr;
57+
T *Ptr;
4258

4359
public:
44-
IntrusivePtr(Obj *Ptr) : Ptr(Ptr) {
60+
IntrusivePtr(T *Ptr) : Ptr(Ptr) {
4561
Ptr->incRef();
4662
}
4763

@@ -55,22 +71,106 @@ class IntrusivePtr {
5571
delete Ptr;
5672
}
5773

58-
Obj *getPtr() const { return Ptr; } // no-warning
74+
T *getPtr() const { return Ptr; } // no-warning
75+
};
76+
77+
// Also IntrusivePtr but let's dodge name-based heuristics.
78+
template <typename T>
79+
class DifferentlyNamed {
80+
T *Ptr;
81+
82+
public:
83+
DifferentlyNamed(T *Ptr) : Ptr(Ptr) {
84+
Ptr->incRef();
85+
}
86+
87+
DifferentlyNamed(const DifferentlyNamed &Other) : Ptr(Other.Ptr) {
88+
Ptr->incRef();
89+
}
90+
91+
~DifferentlyNamed() {
92+
// We should not take the path on which the object is deleted.
93+
if (Ptr->decRef() == 1)
94+
delete Ptr;
95+
}
96+
97+
T *getPtr() const { return Ptr; } // no-warning
5998
};
6099

61100
void testDestroyLocalRefPtr() {
62-
IntrusivePtr p1(new Obj());
101+
IntrusivePtr<RawObj> p1(new RawObj());
102+
{
103+
IntrusivePtr<RawObj> p2(p1);
104+
}
105+
106+
// p1 still maintains ownership. The object is not deleted.
107+
p1.getPtr()->foo(); // no-warning
108+
}
109+
110+
void testDestroySymbolicRefPtr(const IntrusivePtr<RawObj> &p1) {
111+
{
112+
IntrusivePtr<RawObj> p2(p1);
113+
}
114+
115+
// p1 still maintains ownership. The object is not deleted.
116+
p1.getPtr()->foo(); // no-warning
117+
}
118+
119+
void testDestroyLocalRefPtrWithAtomics() {
120+
IntrusivePtr<StdAtomicObj> p1(new StdAtomicObj());
121+
{
122+
IntrusivePtr<StdAtomicObj> p2(p1);
123+
}
124+
125+
// p1 still maintains ownership. The object is not deleted.
126+
p1.getPtr()->foo(); // no-warning
127+
}
128+
129+
130+
void testDestroyLocalRefPtrWithAtomics(const IntrusivePtr<StdAtomicObj> &p1) {
63131
{
64-
IntrusivePtr p2(p1);
132+
IntrusivePtr<StdAtomicObj> p2(p1);
65133
}
66134

67135
// p1 still maintains ownership. The object is not deleted.
68136
p1.getPtr()->foo(); // no-warning
69137
}
70138

71-
void testDestroySymbolicRefPtr(const IntrusivePtr &p1) {
139+
void testDestroyLocalRefPtrDifferentlyNamed() {
140+
DifferentlyNamed<RawObj> p1(new RawObj());
141+
{
142+
DifferentlyNamed<RawObj> p2(p1);
143+
}
144+
145+
// p1 still maintains ownership. The object is not deleted.
146+
p1.getPtr()->foo(); // no-warning
147+
}
148+
149+
void testDestroySymbolicRefPtrDifferentlyNamed(
150+
const DifferentlyNamed<RawObj> &p1) {
151+
{
152+
DifferentlyNamed<RawObj> p2(p1);
153+
}
154+
155+
// p1 still maintains ownership. The object is not deleted.
156+
p1.getPtr()->foo(); // no-warning
157+
}
158+
159+
void testDestroyLocalRefPtrWithAtomicsDifferentlyNamed() {
160+
DifferentlyNamed<StdAtomicObj> p1(new StdAtomicObj());
161+
{
162+
DifferentlyNamed<StdAtomicObj> p2(p1);
163+
}
164+
165+
// p1 still maintains ownership. The object is not deleted.
166+
p1.getPtr()->foo(); // no-warning
167+
}
168+
169+
170+
void testDestroyLocalRefPtrWithAtomicsDifferentlyNamed(
171+
const DifferentlyNamed<StdAtomicObj> &p1) {
72172
{
73-
IntrusivePtr p2(p1);
173+
DifferentlyNamed<StdAtomicObj> p2(p1);
74174
}
75175

76176
// p1 still maintains ownership. The object is not deleted.

0 commit comments

Comments
 (0)