@@ -940,16 +940,16 @@ class MallocBugVisitor final : public BugReporterVisitor {
940940 // A symbol from when the primary region should have been reallocated.
941941 SymbolRef FailedReallocSymbol;
942942
943- // A C++ destructor stack frame in which memory was released. Used for
943+ // A release function stack frame in which memory was released. Used for
944944 // miscellaneous false positive suppression.
945- const StackFrameContext *ReleaseDestructorLC ;
945+ const StackFrameContext *ReleaseFunctionLC ;
946946
947947 bool IsLeak;
948948
949949public:
950950 MallocBugVisitor (SymbolRef S, bool isLeak = false )
951951 : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr ),
952- ReleaseDestructorLC (nullptr ), IsLeak(isLeak) {}
952+ ReleaseFunctionLC (nullptr ), IsLeak(isLeak) {}
953953
954954 static void *getTag () {
955955 static int Tag = 0 ;
@@ -3653,21 +3653,25 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
36533653
36543654 const LocationContext *CurrentLC = N->getLocationContext ();
36553655
3656- // If we find an atomic fetch_add or fetch_sub within the destructor in which
3657- // the pointer was released (before the release), this is likely a destructor
3658- // of a shared pointer.
3656+ // If we find an atomic fetch_add or fetch_sub within the function in which
3657+ // the pointer was released (before the release), this is likely a release
3658+ // point of reference-counted object (like shared pointer).
3659+ //
36593660 // Because we don't model atomics, and also because we don't know that the
36603661 // original reference count is positive, we should not report use-after-frees
3661- // on objects deleted in such destructors . This can probably be improved
3662+ // on objects deleted in such functions . This can probably be improved
36623663 // through better shared pointer modeling.
3663- if (ReleaseDestructorLC && (ReleaseDestructorLC == CurrentLC ||
3664- ReleaseDestructorLC ->isParentOf (CurrentLC))) {
3664+ if (ReleaseFunctionLC && (ReleaseFunctionLC == CurrentLC ||
3665+ ReleaseFunctionLC ->isParentOf (CurrentLC))) {
36653666 if (const auto *AE = dyn_cast<AtomicExpr>(S)) {
36663667 // Check for manual use of atomic builtins.
36673668 AtomicExpr::AtomicOp Op = AE->getOp ();
36683669 if (Op == AtomicExpr::AO__c11_atomic_fetch_add ||
36693670 Op == AtomicExpr::AO__c11_atomic_fetch_sub) {
36703671 BR.markInvalid (getTag (), S);
3672+ // After report is considered invalid there is no need to proceed
3673+ // futher.
3674+ return nullptr ;
36713675 }
36723676 } else if (const auto *CE = dyn_cast<CallExpr>(S)) {
36733677 // Check for `std::atomic` and such. This covers both regular method calls
@@ -3679,6 +3683,9 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
36793683 // "__atomic_base" or something.
36803684 if (StringRef (RD->getNameAsString ()).contains (" atomic" )) {
36813685 BR.markInvalid (getTag (), S);
3686+ // After report is considered invalid there is no need to proceed
3687+ // futher.
3688+ return nullptr ;
36823689 }
36833690 }
36843691 }
@@ -3750,35 +3757,54 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
37503757 return nullptr ;
37513758 }
37523759
3753- // See if we're releasing memory while inlining a destructor
3754- // (or one of its callees). This turns on various common
3755- // false positive suppressions.
3756- bool FoundAnyDestructor = false ;
3757- for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent ()) {
3758- if (const auto *DD = dyn_cast<CXXDestructorDecl>(LC->getDecl ())) {
3759- if (isReferenceCountingPointerDestructor (DD)) {
3760- // This immediately looks like a reference-counting destructor.
3761- // We're bad at guessing the original reference count of the object,
3762- // so suppress the report for now.
3763- BR.markInvalid (getTag (), DD);
3764- } else if (!FoundAnyDestructor) {
3765- assert (!ReleaseDestructorLC &&
3766- " There can be only one release point!" );
3767- // Suspect that it's a reference counting pointer destructor.
3768- // On one of the next nodes might find out that it has atomic
3769- // reference counting operations within it (see the code above),
3770- // and if so, we'd conclude that it likely is a reference counting
3771- // pointer destructor.
3772- ReleaseDestructorLC = LC->getStackFrame ();
3773- // It is unlikely that releasing memory is delegated to a destructor
3774- // inside a destructor of a shared pointer, because it's fairly hard
3775- // to pass the information that the pointer indeed needs to be
3776- // released into it. So we're only interested in the innermost
3777- // destructor.
3778- FoundAnyDestructor = true ;
3760+ // Save the first destructor/function as release point.
3761+ assert (!ReleaseFunctionLC && " There should be only one release point" );
3762+ ReleaseFunctionLC = CurrentLC->getStackFrame ();
3763+
3764+ // See if we're releasing memory while inlining a destructor that
3765+ // decrement reference counters (or one of its callees).
3766+ // This turns on various common false positive suppressions.
3767+ for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent ()) {
3768+ if (const auto *DD = dyn_cast<CXXDestructorDecl>(LC->getDecl ())) {
3769+ if (isReferenceCountingPointerDestructor (DD)) {
3770+ // This immediately looks like a reference-counting destructor.
3771+ // We're bad at guessing the original reference count of the
3772+ // object, so suppress the report for now.
3773+ BR.markInvalid (getTag (), DD);
3774+
3775+ // After report is considered invalid there is no need to proceed
3776+ // futher.
3777+ return nullptr ;
3778+ }
3779+
3780+ // Switch suspection to outer destructor to catch patterns like:
3781+ // (note that class name is distorted to bypass
3782+ // isReferenceCountingPointerDestructor() logic)
3783+ //
3784+ // SmartPointr::~SmartPointr() {
3785+ // if (refcount.fetch_sub(1) == 1)
3786+ // release_resources();
3787+ // }
3788+ // void SmartPointr::release_resources() {
3789+ // free(buffer);
3790+ // }
3791+ //
3792+ // This way ReleaseFunctionLC will point to outermost destructor and
3793+ // it would be possible to catch wider range of FP.
3794+ //
3795+ // NOTE: it would be great to support smth like that in C, since
3796+ // currently patterns like following won't be supressed:
3797+ //
3798+ // void doFree(struct Data *data) { free(data); }
3799+ // void putData(struct Data *data)
3800+ // {
3801+ // if (refPut(data))
3802+ // doFree(data);
3803+ // }
3804+ ReleaseFunctionLC = LC->getStackFrame ();
37793805 }
37803806 }
3781- }
3807+
37823808 } else if (isRelinquished (RSCurr, RSPrev, S)) {
37833809 Msg = " Memory ownership is transferred" ;
37843810 StackHint = std::make_unique<StackHintGeneratorForSymbol>(Sym, " " );
0 commit comments