https://github.com/NagyDonat created https://github.com/llvm/llvm-project/pull/147080
This commit converts MallocChecker to the new checker family framework that was introduced in the recent commit 6833076a5d9f5719539a24e900037da5a3979289 -- and gets rid of some awkward unintended interactions between the checker frontends. From c0e669a4f31702a871fce4c8c3805b322c331afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Don=C3=A1t=20Nagy?= <donat.n...@ericsson.com> Date: Wed, 2 Jul 2025 15:09:42 +0200 Subject: [PATCH] [analyzer] Connversion to CheckerFamily: MallocChecker This commit converts MallocChecker to the new checker family framework that was introduced in the recent commit 6833076a5d9f5719539a24e900037da5a3979289 -- and gets rid of some awkward unintended interactions between the checker frontends. --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 414 ++++++++---------- clang/test/Analysis/new.cpp | 40 +- .../Analysis/test-member-invalidation.cpp | 47 ++ 3 files changed, 227 insertions(+), 274 deletions(-) create mode 100644 clang/test/Analysis/test-member-invalidation.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index a33e61fabc2c1..9e7540eecc8ee 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -333,11 +333,55 @@ template <typename T> static bool isStandardNewDelete(const T &FD) { return isStandardDelete(FD) || isStandardNew(FD); } +namespace { + //===----------------------------------------------------------------------===// -// Definition of the MallocChecker class. +// Utility classes that provide access to the bug types and can model that some +// of the bug types are shared by multiple checker frontends. //===----------------------------------------------------------------------===// -namespace { +#define BUGTYPE_PROVIDER(NAME, DEF) \ + struct NAME : virtual public CheckerFrontend { \ + BugType NAME##Bug{this, DEF, categories::MemoryError}; \ + }; + +BUGTYPE_PROVIDER(DoubleFree, "Double free") +// TODO: Remove DoubleDelete as a separate bug type and when it would be +// emitted, emit DoubleFree reports instead. (Note that DoubleFree is already +// used for all allocation families, not just malloc/free.) +BUGTYPE_PROVIDER(DoubleDelete, "Double delete") + +struct Leak : virtual public CheckerFrontend { + // Leaks should not be reported if they are post-dominated by a sink: + // (1) Sinks are higher importance bugs. + // (2) NoReturnFunctionChecker uses sink nodes to represent paths ending + // with __noreturn functions such as assert() or exit(). We choose not + // to report leaks on such paths. + BugType LeakBug{this, "Memory leak", categories::MemoryError, + /*SuppressOnSink=*/true}; +}; + +BUGTYPE_PROVIDER(UseFree, "Use-after-free") +BUGTYPE_PROVIDER(BadFree, "Bad free") +BUGTYPE_PROVIDER(FreeAlloca, "Free 'alloca()'") +BUGTYPE_PROVIDER(MismatchedDealloc, "Bad deallocator") +BUGTYPE_PROVIDER(OffsetFree, "Offset free") +BUGTYPE_PROVIDER(UseZeroAllocated, "Use of zero allocated") + +template <typename... BT_PROVIDERS> +struct DynMemFrontend : virtual public CheckerFrontend, public BT_PROVIDERS... { + template <typename T> const T *getAs() const { + if constexpr (std::is_same_v<T, CheckerFrontend>) + return static_cast<const T *>(this); + if constexpr ((std::is_same_v<T, BT_PROVIDERS> || ...)) + return static_cast<const T *>(this); + return nullptr; + } +}; + +//===----------------------------------------------------------------------===// +// Definition of the MallocChecker class. +//===----------------------------------------------------------------------===// class MallocChecker : public Checker<check::DeadSymbols, check::PointerEscape, @@ -355,26 +399,29 @@ class MallocChecker bool ShouldRegisterNoOwnershipChangeVisitor = false; - /// Many checkers are essentially built into this one, so enabling them will - /// make MallocChecker perform additional modeling and reporting. - enum CheckKind { - /// When a subchecker is enabled but MallocChecker isn't, model memory - /// management but do not emit warnings emitted with MallocChecker only - /// enabled. - CK_MallocChecker, - CK_NewDeleteChecker, - CK_NewDeleteLeaksChecker, - CK_MismatchedDeallocatorChecker, - CK_InnerPointerChecker, - CK_TaintedAllocChecker, - CK_NumCheckKinds - }; + // This checker family implements many bug types and frontends, and several + // bug types are shared between multiple frontends, so most of the frontends + // are declared with the helper class DynMemFrontend. + // FIXME: There is no clear reason for separating NewDelete vs NewDeleteLeaks + // while e.g. MallocChecker covers both non-leak and leak bugs together. It + // would be nice to redraw the boundaries between the frontends in a more + // logical way. + DynMemFrontend<DoubleFree, Leak, UseFree, BadFree, FreeAlloca, OffsetFree, + UseZeroAllocated> + MallocChecker; + DynMemFrontend<DoubleFree, DoubleDelete, UseFree, BadFree, OffsetFree, + UseZeroAllocated> + NewDeleteChecker; + DynMemFrontend<Leak> NewDeleteLeaksChecker; + DynMemFrontend<FreeAlloca, MismatchedDealloc> MismatchedDeallocatorChecker; + DynMemFrontend<UseFree> InnerPointerChecker; + // This last frontend is associated with a single bug type which is not used + // elsewhere and has a different bug category, so it's declared separately. + CheckerFrontendWithBugType TaintedAllocChecker{"Tainted Memory Allocation", + categories::TaintedData}; using LeakInfo = std::pair<const ExplodedNode *, const MemRegion *>; - bool ChecksEnabled[CK_NumCheckKinds] = {false}; - CheckerNameRef CheckNames[CK_NumCheckKinds]; - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; bool evalCall(const CallEvent &Call, CheckerContext &C) const; @@ -402,16 +449,19 @@ class MallocChecker const char *NL, const char *Sep) const override; private: - mutable std::unique_ptr<BugType> BT_DoubleFree[CK_NumCheckKinds]; - mutable std::unique_ptr<BugType> BT_DoubleDelete; - mutable std::unique_ptr<BugType> BT_Leak[CK_NumCheckKinds]; - mutable std::unique_ptr<BugType> BT_UseFree[CK_NumCheckKinds]; - mutable std::unique_ptr<BugType> BT_BadFree[CK_NumCheckKinds]; - mutable std::unique_ptr<BugType> BT_FreeAlloca[CK_NumCheckKinds]; - mutable std::unique_ptr<BugType> BT_MismatchedDealloc; - mutable std::unique_ptr<BugType> BT_OffsetFree[CK_NumCheckKinds]; - mutable std::unique_ptr<BugType> BT_UseZerroAllocated[CK_NumCheckKinds]; - mutable std::unique_ptr<BugType> BT_TaintedAlloc; + /// Helper method to handle the cases where there is no associated frontend + /// (just exit early) or the associated frontend is disabled (sink the + /// execution path and and then exit early). Intended to be called as + /// if (handleNullOrDisabled(Frontend, C)) + /// return; + static bool handleNullOrDisabled(const CheckerFrontend *F, + CheckerContext &C) { + if (F && F->isEnabled()) + return false; + if (F) + C.addSink(); + return true; + } #define CHECK_FN(NAME) \ void NAME(ProgramStateRef State, const CallEvent &Call, CheckerContext &C) \ @@ -439,7 +489,7 @@ class MallocChecker CheckerContext &C, bool ShouldFreeOnFail) const; using CheckFn = - std::function<void(const MallocChecker *, ProgramStateRef State, + std::function<void(const class MallocChecker *, ProgramStateRef State, const CallEvent &Call, CheckerContext &C)>; const CallDescriptionMap<CheckFn> PreFnMap{ @@ -773,14 +823,16 @@ class MallocChecker void checkEscapeOnReturn(const ReturnStmt *S, CheckerContext &C) const; ///@{ - /// Tells if a given family/call/symbol is tracked by the current checker. - /// Sets CheckKind to the kind of the checker responsible for this - /// family/call/symbol. - std::optional<CheckKind> getCheckIfTracked(AllocationFamily Family, - bool IsALeakCheck = false) const; - - std::optional<CheckKind> getCheckIfTracked(CheckerContext &C, SymbolRef Sym, - bool IsALeakCheck = false) const; + /// Returns a pointer to the checker frontend corresponding to the given + /// family or symbol. The template argument T may be either CheckerFamily or + /// a BUGTYPE_PROVIDER class; in the latter case the query is restricted to + /// frontends that descend from that PROVIDER class (i.e. can emit that bug + /// type). Note that this may return a frontend which is disabled. + template <class T> + const T *getRelevantFrontendAs(AllocationFamily Family) const; + + template <class T> + const T *getRelevantFrontendAs(CheckerContext &C, SymbolRef Sym) const; ///@} static bool SummarizeValue(raw_ostream &os, SVal V); static bool SummarizeRegion(ProgramStateRef State, raw_ostream &os, @@ -1558,7 +1610,7 @@ void MallocChecker::checkOwnershipAttr(ProgramStateRef State, if (!FD) return; if (ShouldIncludeOwnershipAnnotatedFunctions || - ChecksEnabled[CK_MismatchedDeallocatorChecker]) { + MismatchedDeallocatorChecker.isEnabled()) { // Check all the attributes, if there are any. // There can be multiple of these attributes. if (FD->hasAttrs()) @@ -1883,11 +1935,8 @@ void MallocChecker::reportTaintBug(StringRef Msg, ProgramStateRef State, llvm::ArrayRef<SymbolRef> TaintedSyms, AllocationFamily Family) const { if (ExplodedNode *N = C.generateNonFatalErrorNode(State, this)) { - if (!BT_TaintedAlloc) - BT_TaintedAlloc.reset(new BugType(CheckNames[CK_TaintedAllocChecker], - "Tainted Memory Allocation", - categories::TaintedData)); - auto R = std::make_unique<PathSensitiveBugReport>(*BT_TaintedAlloc, Msg, N); + auto R = + std::make_unique<PathSensitiveBugReport>(TaintedAllocChecker, Msg, N); for (const auto *TaintedSym : TaintedSyms) { R->markInteresting(TaintedSym); } @@ -1898,7 +1947,7 @@ void MallocChecker::reportTaintBug(StringRef Msg, ProgramStateRef State, void MallocChecker::checkTaintedness(CheckerContext &C, const CallEvent &Call, const SVal SizeSVal, ProgramStateRef State, AllocationFamily Family) const { - if (!ChecksEnabled[CK_TaintedAllocChecker]) + if (!TaintedAllocChecker.isEnabled()) return; std::vector<SymbolRef> TaintedSyms = taint::getTaintedSymbols(State, SizeSVal); @@ -2348,53 +2397,47 @@ MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr, RefState::getReleased(Family, ParentExpr)); } -std::optional<MallocChecker::CheckKind> -MallocChecker::getCheckIfTracked(AllocationFamily Family, - bool IsALeakCheck) const { +template <class T> +const T *MallocChecker::getRelevantFrontendAs(AllocationFamily Family) const { switch (Family.Kind) { case AF_Malloc: case AF_Alloca: case AF_Custom: - case AF_IfNameIndex: { - if (ChecksEnabled[CK_MallocChecker]) - return CK_MallocChecker; - return std::nullopt; - } + case AF_IfNameIndex: + return MallocChecker.getAs<T>(); case AF_CXXNew: case AF_CXXNewArray: { - if (IsALeakCheck) { - if (ChecksEnabled[CK_NewDeleteLeaksChecker]) - return CK_NewDeleteLeaksChecker; + const T *ND = NewDeleteChecker.getAs<T>(); + const T *NDL = NewDeleteLeaksChecker.getAs<T>(); + // Bugs corresponding to C++ new/delete allocations are split between these + // two frontends. + if constexpr (std::is_same_v<T, CheckerFrontend>) { + assert(ND && NDL && "Casting to CheckerFrontend always succeeds"); + // Prefer NewDelete unless it's disabled and NewDeleteLeaks is enabled. + return (!ND->isEnabled() && NDL->isEnabled()) ? NDL : ND; } - else { - if (ChecksEnabled[CK_NewDeleteChecker]) - return CK_NewDeleteChecker; - } - return std::nullopt; - } - case AF_InnerBuffer: { - if (ChecksEnabled[CK_InnerPointerChecker]) - return CK_InnerPointerChecker; - return std::nullopt; + assert(!(ND && NDL) && + "NewDelete and NewDeleteLeaks must not share a bug type"); + return ND ? ND : NDL; } - case AF_None: { + case AF_InnerBuffer: + return InnerPointerChecker.getAs<T>(); + case AF_None: assert(false && "no family"); - return std::nullopt; - } + return nullptr; } assert(false && "unhandled family"); - return std::nullopt; + return nullptr; } - -std::optional<MallocChecker::CheckKind> -MallocChecker::getCheckIfTracked(CheckerContext &C, SymbolRef Sym, - bool IsALeakCheck) const { +template <class T> +const T *MallocChecker::getRelevantFrontendAs(CheckerContext &C, + SymbolRef Sym) const { if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym)) - return CK_MallocChecker; + return MallocChecker.getAs<T>(); const RefState *RS = C.getState()->get<RegionState>(Sym); assert(RS); - return getCheckIfTracked(RS->getAllocationFamily(), IsALeakCheck); + return getRelevantFrontendAs<T>(RS->getAllocationFamily()); } bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) { @@ -2490,21 +2533,11 @@ void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr, AllocationFamily Family) const { - - if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { - C.addSink(); - return; - } - - std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); - if (!CheckKind) + const BadFree *Frontend = getRelevantFrontendAs<BadFree>(Family); + if (handleNullOrDisabled(Frontend, C)) return; if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_BadFree[*CheckKind]) - BT_BadFree[*CheckKind].reset(new BugType( - CheckNames[*CheckKind], "Bad free", categories::MemoryError)); - SmallString<100> buf; llvm::raw_svector_ostream os(buf); @@ -2526,7 +2559,7 @@ void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, printExpectedAllocName(os, Family); - auto R = std::make_unique<PathSensitiveBugReport>(*BT_BadFree[*CheckKind], + auto R = std::make_unique<PathSensitiveBugReport>(Frontend->BadFreeBug, os.str(), N); R->markInteresting(MR); R->addRange(Range); @@ -2536,25 +2569,20 @@ void MallocChecker::HandleNonHeapDealloc(CheckerContext &C, SVal ArgVal, void MallocChecker::HandleFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const { + const FreeAlloca *Frontend; - std::optional<MallocChecker::CheckKind> CheckKind; - - if (ChecksEnabled[CK_MallocChecker]) - CheckKind = CK_MallocChecker; - else if (ChecksEnabled[CK_MismatchedDeallocatorChecker]) - CheckKind = CK_MismatchedDeallocatorChecker; + if (MallocChecker.isEnabled()) + Frontend = &MallocChecker; + else if (MismatchedDeallocatorChecker.isEnabled()) + Frontend = &MismatchedDeallocatorChecker; else { C.addSink(); return; } if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_FreeAlloca[*CheckKind]) - BT_FreeAlloca[*CheckKind].reset(new BugType( - CheckNames[*CheckKind], "Free 'alloca()'", categories::MemoryError)); - auto R = std::make_unique<PathSensitiveBugReport>( - *BT_FreeAlloca[*CheckKind], + Frontend->FreeAllocaBug, "Memory allocated by 'alloca()' should not be deallocated", N); R->markInteresting(ArgVal.getAsRegion()); R->addRange(Range); @@ -2567,18 +2595,12 @@ void MallocChecker::HandleMismatchedDealloc(CheckerContext &C, const Expr *DeallocExpr, const RefState *RS, SymbolRef Sym, bool OwnershipTransferred) const { - - if (!ChecksEnabled[CK_MismatchedDeallocatorChecker]) { + if (!MismatchedDeallocatorChecker.isEnabled()) { C.addSink(); return; } if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_MismatchedDealloc) - BT_MismatchedDealloc.reset( - new BugType(CheckNames[CK_MismatchedDeallocatorChecker], - "Bad deallocator", categories::MemoryError)); - SmallString<100> buf; llvm::raw_svector_ostream os(buf); @@ -2612,8 +2634,8 @@ void MallocChecker::HandleMismatchedDealloc(CheckerContext &C, printOwnershipTakesList(os, C, DeallocExpr); } - auto R = std::make_unique<PathSensitiveBugReport>(*BT_MismatchedDealloc, - os.str(), N); + auto R = std::make_unique<PathSensitiveBugReport>( + MismatchedDeallocatorChecker.MismatchedDeallocBug, os.str(), N); R->markInteresting(Sym); R->addRange(Range); R->addVisitor<MallocBugVisitor>(Sym); @@ -2625,24 +2647,14 @@ void MallocChecker::HandleOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr, AllocationFamily Family, const Expr *AllocExpr) const { - - if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { - C.addSink(); - return; - } - - std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); - if (!CheckKind) + const OffsetFree *Frontend = getRelevantFrontendAs<OffsetFree>(Family); + if (handleNullOrDisabled(Frontend, C)) return; ExplodedNode *N = C.generateErrorNode(); if (!N) return; - if (!BT_OffsetFree[*CheckKind]) - BT_OffsetFree[*CheckKind].reset(new BugType( - CheckNames[*CheckKind], "Offset free", categories::MemoryError)); - SmallString<100> buf; llvm::raw_svector_ostream os(buf); SmallString<20> AllocNameBuf; @@ -2672,7 +2684,7 @@ void MallocChecker::HandleOffsetFree(CheckerContext &C, SVal ArgVal, else os << "allocated memory"; - auto R = std::make_unique<PathSensitiveBugReport>(*BT_OffsetFree[*CheckKind], + auto R = std::make_unique<PathSensitiveBugReport>(Frontend->OffsetFreeBug, os.str(), N); R->markInteresting(MR->getBaseRegion()); R->addRange(Range); @@ -2681,27 +2693,16 @@ void MallocChecker::HandleOffsetFree(CheckerContext &C, SVal ArgVal, void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const { - - if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker] && - !ChecksEnabled[CK_InnerPointerChecker]) { - C.addSink(); - return; - } - - std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - if (!CheckKind) + const UseFree *Frontend = getRelevantFrontendAs<UseFree>(C, Sym); + if (handleNullOrDisabled(Frontend, C)) return; if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_UseFree[*CheckKind]) - BT_UseFree[*CheckKind].reset(new BugType( - CheckNames[*CheckKind], "Use-after-free", categories::MemoryError)); - AllocationFamily AF = C.getState()->get<RegionState>(Sym)->getAllocationFamily(); auto R = std::make_unique<PathSensitiveBugReport>( - *BT_UseFree[*CheckKind], + Frontend->UseFreeBug, AF.Kind == AF_InnerBuffer ? "Inner pointer of container used after re/deallocation" : "Use of memory after it is freed", @@ -2721,23 +2722,13 @@ void MallocChecker::HandleUseAfterFree(CheckerContext &C, SourceRange Range, void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range, bool Released, SymbolRef Sym, SymbolRef PrevSym) const { - - if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { - C.addSink(); - return; - } - - std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - if (!CheckKind) + const DoubleFree *Frontend = getRelevantFrontendAs<DoubleFree>(C, Sym); + if (handleNullOrDisabled(Frontend, C)) return; if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_DoubleFree[*CheckKind]) - BT_DoubleFree[*CheckKind].reset(new BugType( - CheckNames[*CheckKind], "Double free", categories::MemoryError)); - auto R = std::make_unique<PathSensitiveBugReport>( - *BT_DoubleFree[*CheckKind], + Frontend->DoubleFreeBug, (Released ? "Attempt to free released memory" : "Attempt to free non-owned memory"), N); @@ -2751,24 +2742,14 @@ void MallocChecker::HandleDoubleFree(CheckerContext &C, SourceRange Range, } void MallocChecker::HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const { - - if (!ChecksEnabled[CK_NewDeleteChecker]) { - C.addSink(); - return; - } - - std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - if (!CheckKind) + const DoubleDelete *Frontend = getRelevantFrontendAs<DoubleDelete>(C, Sym); + if (handleNullOrDisabled(Frontend, C)) return; if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_DoubleDelete) - BT_DoubleDelete.reset(new BugType(CheckNames[CK_NewDeleteChecker], - "Double delete", - categories::MemoryError)); auto R = std::make_unique<PathSensitiveBugReport>( - *BT_DoubleDelete, "Attempt to delete released memory", N); + Frontend->DoubleDeleteBug, "Attempt to delete released memory", N); R->markInteresting(Sym); R->addVisitor<MallocBugVisitor>(Sym); @@ -2778,26 +2759,15 @@ void MallocChecker::HandleDoubleDelete(CheckerContext &C, SymbolRef Sym) const { void MallocChecker::HandleUseZeroAlloc(CheckerContext &C, SourceRange Range, SymbolRef Sym) const { - - if (!ChecksEnabled[CK_MallocChecker] && !ChecksEnabled[CK_NewDeleteChecker]) { - C.addSink(); - return; - } - - std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); - - if (!CheckKind) + const UseZeroAllocated *Frontend = + getRelevantFrontendAs<UseZeroAllocated>(C, Sym); + if (handleNullOrDisabled(Frontend, C)) return; if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_UseZerroAllocated[*CheckKind]) - BT_UseZerroAllocated[*CheckKind].reset( - new BugType(CheckNames[*CheckKind], "Use of zero allocated", - categories::MemoryError)); - auto R = std::make_unique<PathSensitiveBugReport>( - *BT_UseZerroAllocated[*CheckKind], - "Use of memory allocated with size zero", N); + Frontend->UseZeroAllocatedBug, "Use of memory allocated with size zero", + N); R->addRange(Range); if (Sym) { @@ -2812,20 +2782,11 @@ void MallocChecker::HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *FreeExpr, AllocationFamily Family) const { - if (!ChecksEnabled[CK_MallocChecker]) { - C.addSink(); - return; - } - - std::optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(Family); - if (!CheckKind) + const BadFree *Frontend = getRelevantFrontendAs<BadFree>(Family); + if (handleNullOrDisabled(Frontend, C)) return; if (ExplodedNode *N = C.generateErrorNode()) { - if (!BT_BadFree[*CheckKind]) - BT_BadFree[*CheckKind].reset(new BugType( - CheckNames[*CheckKind], "Bad free", categories::MemoryError)); - SmallString<100> Buf; llvm::raw_svector_ostream Os(Buf); @@ -2839,7 +2800,7 @@ void MallocChecker::HandleFunctionPtrFree(CheckerContext &C, SVal ArgVal, Os << " is a function pointer"; - auto R = std::make_unique<PathSensitiveBugReport>(*BT_BadFree[*CheckKind], + auto R = std::make_unique<PathSensitiveBugReport>(Frontend->BadFreeBug, Os.str(), N); R->markInteresting(MR); R->addRange(Range); @@ -3016,10 +2977,7 @@ MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const { - - if (!ChecksEnabled[CK_MallocChecker] && - !ChecksEnabled[CK_NewDeleteLeaksChecker]) - return; + assert(N && "HandleLeak is only called with a non-null node"); const RefState *RS = C.getState()->get<RegionState>(Sym); assert(RS && "cannot leak an untracked symbol"); @@ -3028,24 +2986,10 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N, if (Family.Kind == AF_Alloca) return; - std::optional<MallocChecker::CheckKind> CheckKind = - getCheckIfTracked(Family, true); - - if (!CheckKind) + const Leak *Frontend = getRelevantFrontendAs<Leak>(Family); + if (handleNullOrDisabled(Frontend, C)) return; - assert(N); - if (!BT_Leak[*CheckKind]) { - // Leaks should not be reported if they are post-dominated by a sink: - // (1) Sinks are higher importance bugs. - // (2) NoReturnFunctionChecker uses sink nodes to represent paths ending - // with __noreturn functions such as assert() or exit(). We choose not - // to report leaks on such paths. - BT_Leak[*CheckKind].reset(new BugType(CheckNames[*CheckKind], "Memory leak", - categories::MemoryError, - /*SuppressOnSink=*/true)); - } - // Most bug reports are cached at the location where they occurred. // With leaks, we want to unique them by the location where they were // allocated, and only report a single path. @@ -3070,7 +3014,7 @@ void MallocChecker::HandleLeak(SymbolRef Sym, ExplodedNode *N, } auto R = std::make_unique<PathSensitiveBugReport>( - *BT_Leak[*CheckKind], os.str(), N, LocUsedForUniqueing, + Frontend->LeakBug, os.str(), N, LocUsedForUniqueing, AllocNode->getLocationContext()->getDecl()); R->markInteresting(Sym); R->addVisitor<MallocBugVisitor>(Sym, true); @@ -3150,7 +3094,7 @@ void MallocChecker::checkPreCall(const CallEvent &Call, if (const auto *DC = dyn_cast<CXXDeallocatorCall>(&Call)) { const CXXDeleteExpr *DE = DC->getOriginExpr(); - if (!ChecksEnabled[CK_NewDeleteChecker]) + if (!NewDeleteChecker.isEnabled()) if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol()) checkUseAfterFree(Sym, C, DE->getArgument()); @@ -3187,7 +3131,7 @@ void MallocChecker::checkPreCall(const CallEvent &Call, if (!FD) return; - if (ChecksEnabled[CK_MallocChecker] && isFreeingCall(Call)) + if (MallocChecker.isEnabled() && isFreeingCall(Call)) return; } @@ -3902,16 +3846,15 @@ void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State, for (auto [Sym, Data] : RS) { const RefState *RefS = State->get<RegionState>(Sym); AllocationFamily Family = RefS->getAllocationFamily(); - std::optional<MallocChecker::CheckKind> CheckKind = - getCheckIfTracked(Family); - if (!CheckKind) - CheckKind = getCheckIfTracked(Family, true); + + const CheckerFrontend *Frontend = + getRelevantFrontendAs<CheckerFrontend>(Family); Sym->dumpToStream(Out); Out << " : "; Data.dump(Out); - if (CheckKind) - Out << " (" << CheckNames[*CheckKind] << ")"; + if (Frontend) + Out << " (" << Frontend->getName() << ")"; Out << NL; } } @@ -3933,36 +3876,31 @@ markReleased(ProgramStateRef State, SymbolRef Sym, const Expr *Origin) { // Intended to be used in InnerPointerChecker to register the part of // MallocChecker connected to it. -void ento::registerInnerPointerCheckerAux(CheckerManager &mgr) { - MallocChecker *checker = mgr.getChecker<MallocChecker>(); - checker->ChecksEnabled[MallocChecker::CK_InnerPointerChecker] = true; - checker->CheckNames[MallocChecker::CK_InnerPointerChecker] = - mgr.getCurrentCheckerName(); +void ento::registerInnerPointerCheckerAux(CheckerManager &Mgr) { + Mgr.getChecker<MallocChecker>()->InnerPointerChecker.enable(Mgr); } -void ento::registerDynamicMemoryModeling(CheckerManager &mgr) { - auto *checker = mgr.registerChecker<MallocChecker>(); - checker->ShouldIncludeOwnershipAnnotatedFunctions = - mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "Optimistic"); - checker->ShouldRegisterNoOwnershipChangeVisitor = - mgr.getAnalyzerOptions().getCheckerBooleanOption( - checker, "AddNoOwnershipChangeNotes"); +void ento::registerDynamicMemoryModeling(CheckerManager &Mgr) { + auto *Chk = Mgr.registerChecker<MallocChecker>(); + Chk->ShouldIncludeOwnershipAnnotatedFunctions = + Mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "Optimistic"); + Chk->ShouldRegisterNoOwnershipChangeVisitor = + Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Chk, "AddNoOwnershipChangeNotes"); } bool ento::shouldRegisterDynamicMemoryModeling(const CheckerManager &mgr) { return true; } -#define REGISTER_CHECKER(name) \ - void ento::register##name(CheckerManager &mgr) { \ - MallocChecker *checker = mgr.getChecker<MallocChecker>(); \ - checker->ChecksEnabled[MallocChecker::CK_##name] = true; \ - checker->CheckNames[MallocChecker::CK_##name] = \ - mgr.getCurrentCheckerName(); \ +#define REGISTER_CHECKER(NAME) \ + void ento::register##NAME(CheckerManager &Mgr) { \ + Mgr.getChecker<MallocChecker>()->NAME.enable(Mgr); \ } \ \ - bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } + bool ento::shouldRegister##NAME(const CheckerManager &) { return true; } +// TODO: NewDelete and NewDeleteLeaks shouldn't be registered when not in C++. REGISTER_CHECKER(MallocChecker) REGISTER_CHECKER(NewDeleteChecker) REGISTER_CHECKER(NewDeleteLeaksChecker) diff --git a/clang/test/Analysis/new.cpp b/clang/test/Analysis/new.cpp index 3542b5c594b50..8439a4e55d812 100644 --- a/clang/test/Analysis/new.cpp +++ b/clang/test/Analysis/new.cpp @@ -327,39 +327,7 @@ void testArrayDestr() { clang_analyzer_warnIfReached(); // no-warning } -// Invalidate Region even in case of default destructor -class InvalidateDestTest { -public: - int x; - int *y; - ~InvalidateDestTest(); -}; - -int test_member_invalidation() { - - //test invalidation of member variable - InvalidateDestTest *test = new InvalidateDestTest(); - test->x = 5; - int *k = &(test->x); - clang_analyzer_eval(*k == 5); // expected-warning{{TRUE}} - delete test; - clang_analyzer_eval(*k == 5); // expected-warning{{UNKNOWN}} - - //test invalidation of member pointer - int localVar = 5; - test = new InvalidateDestTest(); - test->y = &localVar; - delete test; - clang_analyzer_eval(localVar == 5); // expected-warning{{UNKNOWN}} - - // Test aray elements are invalidated. - int Var1 = 5; - int Var2 = 5; - InvalidateDestTest *a = new InvalidateDestTest[2]; - a[0].y = &Var1; - a[1].y = &Var2; - delete[] a; - clang_analyzer_eval(Var1 == 5); // expected-warning{{UNKNOWN}} - clang_analyzer_eval(Var2 == 5); // expected-warning{{UNKNOWN}} - return 0; -} +// See also test-member-invalidation.cpp which validates that calling an +// unknown destructor invalidates the members of an object. This behavior +// cannot be tested in this file because here `MallocChecker.cpp` sinks +// execution paths that refer to members of a deleted object. diff --git a/clang/test/Analysis/test-member-invalidation.cpp b/clang/test/Analysis/test-member-invalidation.cpp new file mode 100644 index 0000000000000..cbff3986325df --- /dev/null +++ b/clang/test/Analysis/test-member-invalidation.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -std=c++11 -verify=expected,nosink -analyzer-config eagerly-assume=false %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc,debug.ExprInspection -std=c++11 -verify=expected,sink -analyzer-config eagerly-assume=false %s + +// This test validates that calling an unknown destructor invalidates the +// members of an object. This was originally a part of the test file `new.cpp`, +// but was split off into a separate file because the checker family +// implemented in `MallocChecker.cpp` (which is activated via unix.Malloc in +// `new.cpp` sinks all execution paths that refer to members of a deleted object. + +void clang_analyzer_eval(bool); + +// Invalidate Region even in case of default destructor +class InvalidateDestTest { +public: + int x; + int *y; + ~InvalidateDestTest(); +}; + +int test_member_invalidation() { + + //test invalidation of member variable + InvalidateDestTest *test = new InvalidateDestTest(); + test->x = 5; + int *k = &(test->x); + clang_analyzer_eval(*k == 5); // expected-warning{{TRUE}} + delete test; + clang_analyzer_eval(*k == 5); // nosink-warning{{UNKNOWN}} + + //test invalidation of member pointer + int localVar = 5; + test = new InvalidateDestTest(); + test->y = &localVar; + delete test; + clang_analyzer_eval(localVar == 5); // nosink-warning{{UNKNOWN}} + + // Test aray elements are invalidated. + int Var1 = 5; + int Var2 = 5; + InvalidateDestTest *a = new InvalidateDestTest[2]; + a[0].y = &Var1; + a[1].y = &Var2; + delete[] a; + clang_analyzer_eval(Var1 == 5); // nosink-warning{{UNKNOWN}} + clang_analyzer_eval(Var2 == 5); // nosink-warning{{UNKNOWN}} + return 0; +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits