Skip to content

Commit 5337efc

Browse files
committed
[analyzer] MallocChecker: Suppress false positives in shared pointers.
Throw away MallocChecker warnings that occur after releasing a pointer within a destructor (or its callees) after performing C11 atomic fetch_add or fetch_sub within that destructor (or its callees). This is an indication that the destructor's class is likely a reference-counting pointer. The analyzer is not able to understand that the original reference count is usually large enough to avoid most use-after-frees. Even when the smart pointer is a local variable, we still have these false positives that this patch suppresses, because the analyzer doesn't currently support atomics well enough. Differential Revision: https://reviews.llvm.org/D43791 llvm-svn: 326249
1 parent dff5a44 commit 5337efc

File tree

2 files changed

+130
-7
lines changed

2 files changed

+130
-7
lines changed

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -446,15 +446,24 @@ class MallocChecker : public Checker<check::DeadSymbols,
446446
// A symbol from when the primary region should have been reallocated.
447447
SymbolRef FailedReallocSymbol;
448448

449+
// A C++ destructor stack frame in which memory was released. Used for
450+
// miscellaneous false positive suppression.
451+
const StackFrameContext *ReleaseDestructorLC;
452+
449453
bool IsLeak;
450454

451455
public:
452456
MallocBugVisitor(SymbolRef S, bool isLeak = false)
453-
: Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), IsLeak(isLeak) {}
457+
: Sym(S), Mode(Normal), FailedReallocSymbol(nullptr),
458+
ReleaseDestructorLC(nullptr), IsLeak(isLeak) {}
459+
460+
static void *getTag() {
461+
static int Tag = 0;
462+
return &Tag;
463+
}
454464

455465
void Profile(llvm::FoldingSetNodeID &ID) const override {
456-
static int X = 0;
457-
ID.AddPointer(&X);
466+
ID.AddPointer(getTag());
458467
ID.AddPointer(Sym);
459468
}
460469

@@ -2822,6 +2831,32 @@ static SymbolRef findFailedReallocSymbol(ProgramStateRef currState,
28222831
std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode(
28232832
const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
28242833
BugReport &BR) {
2834+
const Stmt *S = PathDiagnosticLocation::getStmt(N);
2835+
if (!S)
2836+
return nullptr;
2837+
2838+
const LocationContext *CurrentLC = N->getLocationContext();
2839+
2840+
// If we find an atomic fetch_add or fetch_sub within the destructor in which
2841+
// the pointer was released (before the release), this is likely a destructor
2842+
// of a shared pointer.
2843+
// Because we don't model atomics, and also because we don't know that the
2844+
// original reference count is positive, we should not report use-after-frees
2845+
// on objects deleted in such destructors. This can probably be improved
2846+
// through better shared pointer modeling.
2847+
if (ReleaseDestructorLC) {
2848+
if (const auto *AE = dyn_cast<AtomicExpr>(S)) {
2849+
AtomicExpr::AtomicOp Op = AE->getOp();
2850+
if (Op == AtomicExpr::AO__c11_atomic_fetch_add ||
2851+
Op == AtomicExpr::AO__c11_atomic_fetch_sub) {
2852+
if (ReleaseDestructorLC == CurrentLC ||
2853+
ReleaseDestructorLC->isParentOf(CurrentLC)) {
2854+
BR.markInvalid(getTag(), S);
2855+
}
2856+
}
2857+
}
2858+
}
2859+
28252860
ProgramStateRef state = N->getState();
28262861
ProgramStateRef statePrev = PrevN->getState();
28272862

@@ -2830,10 +2865,6 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode(
28302865
if (!RS)
28312866
return nullptr;
28322867

2833-
const Stmt *S = PathDiagnosticLocation::getStmt(N);
2834-
if (!S)
2835-
return nullptr;
2836-
28372868
// FIXME: We will eventually need to handle non-statement-based events
28382869
// (__attribute__((cleanup))).
28392870

@@ -2849,6 +2880,24 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode(
28492880
Msg = "Memory is released";
28502881
StackHint = new StackHintGeneratorForSymbol(Sym,
28512882
"Returning; memory was released");
2883+
2884+
// See if we're releasing memory while inlining a destructor (or one of
2885+
// its callees). If so, enable the atomic-related suppression within that
2886+
// destructor (and all of its callees), which would kick in while visiting
2887+
// other nodes (the visit order is from the bug to the graph root).
2888+
for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent()) {
2889+
if (isa<CXXDestructorDecl>(LC->getDecl())) {
2890+
assert(!ReleaseDestructorLC &&
2891+
"There can be only one release point!");
2892+
ReleaseDestructorLC = LC->getCurrentStackFrame();
2893+
// It is unlikely that releasing memory is delegated to a destructor
2894+
// inside a destructor of a shared pointer, because it's fairly hard
2895+
// to pass the information that the pointer indeed needs to be
2896+
// released into it. So we're only interested in the innermost
2897+
// destructor.
2898+
break;
2899+
}
2900+
}
28522901
} else if (isRelinquished(RS, RSPrev, S)) {
28532902
Msg = "Memory ownership is transferred";
28542903
StackHint = new StackHintGeneratorForSymbol(Sym, "");
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.NewDelete -std=c++11 -verify %s
2+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.NewDeleteLeaks -DLEAKS -std=c++11 -verify %s
3+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.NewDelete -std=c++11 -DTEST_INLINABLE_ALLOCATORS -verify %s
4+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.NewDeleteLeaks -DLEAKS -std=c++11 -DTEST_INLINABLE_ALLOCATORS -verify %s
5+
6+
// expected-no-diagnostics
7+
8+
#include "Inputs/system-header-simulator-cxx.h"
9+
10+
typedef enum memory_order {
11+
memory_order_relaxed = __ATOMIC_RELAXED,
12+
memory_order_consume = __ATOMIC_CONSUME,
13+
memory_order_acquire = __ATOMIC_ACQUIRE,
14+
memory_order_release = __ATOMIC_RELEASE,
15+
memory_order_acq_rel = __ATOMIC_ACQ_REL,
16+
memory_order_seq_cst = __ATOMIC_SEQ_CST
17+
} memory_order;
18+
19+
class Obj {
20+
int RefCnt;
21+
22+
public:
23+
int incRef() {
24+
return __c11_atomic_fetch_add((volatile _Atomic(int) *)&RefCnt, 1,
25+
memory_order_relaxed);
26+
}
27+
28+
int decRef() {
29+
return __c11_atomic_fetch_sub((volatile _Atomic(int) *)&RefCnt, 1,
30+
memory_order_relaxed);
31+
}
32+
33+
void foo();
34+
};
35+
36+
class IntrusivePtr {
37+
Obj *Ptr;
38+
39+
public:
40+
IntrusivePtr(Obj *Ptr) : Ptr(Ptr) {
41+
Ptr->incRef();
42+
}
43+
44+
IntrusivePtr(const IntrusivePtr &Other) : Ptr(Other.Ptr) {
45+
Ptr->incRef();
46+
}
47+
48+
~IntrusivePtr() {
49+
// We should not take the path on which the object is deleted.
50+
if (Ptr->decRef() == 1)
51+
delete Ptr;
52+
}
53+
54+
Obj *getPtr() const { return Ptr; }
55+
};
56+
57+
void testDestroyLocalRefPtr() {
58+
IntrusivePtr p1(new Obj());
59+
{
60+
IntrusivePtr p2(p1);
61+
}
62+
63+
// p1 still maintains ownership. The object is not deleted.
64+
p1.getPtr()->foo(); // no-warning
65+
}
66+
67+
void testDestroySymbolicRefPtr(const IntrusivePtr &p1) {
68+
{
69+
IntrusivePtr p2(p1);
70+
}
71+
72+
// p1 still maintains ownership. The object is not deleted.
73+
p1.getPtr()->foo(); // no-warning
74+
}

0 commit comments

Comments
 (0)