@@ -940,16 +940,16 @@ class MallocBugVisitor final : public BugReporterVisitor {
940
940
// A symbol from when the primary region should have been reallocated.
941
941
SymbolRef FailedReallocSymbol;
942
942
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
944
944
// miscellaneous false positive suppression.
945
- const StackFrameContext *ReleaseDestructorLC ;
945
+ const StackFrameContext *ReleaseFunctionLC ;
946
946
947
947
bool IsLeak;
948
948
949
949
public:
950
950
MallocBugVisitor (SymbolRef S, bool isLeak = false )
951
951
: Sym(S), Mode(Normal), FailedReallocSymbol(nullptr ),
952
- ReleaseDestructorLC (nullptr ), IsLeak(isLeak) {}
952
+ ReleaseFunctionLC (nullptr ), IsLeak(isLeak) {}
953
953
954
954
static void *getTag () {
955
955
static int Tag = 0 ;
@@ -3653,21 +3653,25 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
3653
3653
3654
3654
const LocationContext *CurrentLC = N->getLocationContext ();
3655
3655
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
+ //
3659
3660
// Because we don't model atomics, and also because we don't know that the
3660
3661
// 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
3662
3663
// through better shared pointer modeling.
3663
- if (ReleaseDestructorLC && (ReleaseDestructorLC == CurrentLC ||
3664
- ReleaseDestructorLC ->isParentOf (CurrentLC))) {
3664
+ if (ReleaseFunctionLC && (ReleaseFunctionLC == CurrentLC ||
3665
+ ReleaseFunctionLC ->isParentOf (CurrentLC))) {
3665
3666
if (const auto *AE = dyn_cast<AtomicExpr>(S)) {
3666
3667
// Check for manual use of atomic builtins.
3667
3668
AtomicExpr::AtomicOp Op = AE->getOp ();
3668
3669
if (Op == AtomicExpr::AO__c11_atomic_fetch_add ||
3669
3670
Op == AtomicExpr::AO__c11_atomic_fetch_sub) {
3670
3671
BR.markInvalid (getTag (), S);
3672
+ // After report is considered invalid there is no need to proceed
3673
+ // futher.
3674
+ return nullptr ;
3671
3675
}
3672
3676
} else if (const auto *CE = dyn_cast<CallExpr>(S)) {
3673
3677
// Check for `std::atomic` and such. This covers both regular method calls
@@ -3679,6 +3683,9 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
3679
3683
// "__atomic_base" or something.
3680
3684
if (StringRef (RD->getNameAsString ()).contains (" atomic" )) {
3681
3685
BR.markInvalid (getTag (), S);
3686
+ // After report is considered invalid there is no need to proceed
3687
+ // futher.
3688
+ return nullptr ;
3682
3689
}
3683
3690
}
3684
3691
}
@@ -3750,35 +3757,54 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N,
3750
3757
return nullptr ;
3751
3758
}
3752
3759
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 ();
3779
3805
}
3780
3806
}
3781
- }
3807
+
3782
3808
} else if (isRelinquished (RSCurr, RSPrev, S)) {
3783
3809
Msg = " Memory ownership is transferred" ;
3784
3810
StackHint = std::make_unique<StackHintGeneratorForSymbol>(Sym, " " );
0 commit comments