Author: Benedek Kaibas Date: 2026-07-14T16:03:42+01:00 New Revision: e1d7480aabee9ea5b06be8f7902ff2ab23f50647
URL: https://github.com/llvm/llvm-project/commit/e1d7480aabee9ea5b06be8f7902ff2ab23f50647 DIFF: https://github.com/llvm/llvm-project/commit/e1d7480aabee9ea5b06be8f7902ff2ab23f50647.diff LOG: [analyzer] Implemented the DanglingPtrDeref checker to detect use-after-scope lifetime errors (#209278) Added: clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp clang/test/Analysis/dangling-ptr-deref.cpp Modified: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h Removed: ################################################################################ diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 0f8b3bb1e1d18..32506b33ceb7c 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -806,6 +806,11 @@ def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">, Dependencies<[LifetimeModeling]>, Documentation<NotDocumented>; +def DanglingPtrDeref : Checker<"DanglingPtrDeref">, + HelpText<"Check for dereferences of a dangling pointer">, + Dependencies<[LifetimeModeling]>, + Documentation<NotDocumented>; + } // end: "alpha.cplusplus" //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 8f61b7d88e8d2..4ec6e368c8fc3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -93,6 +93,7 @@ add_clang_library(clangStaticAnalyzerCheckers PointerSubChecker.cpp PthreadLockChecker.cpp PutenvStackArrayChecker.cpp + ReportDanglingPtrDeref.cpp RetainCountChecker/RetainCountChecker.cpp RetainCountChecker/RetainCountDiagnostics.cpp ReturnPointerRangeChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp index a2f58e0710801..42219edc5cceb 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp @@ -14,14 +14,20 @@ using namespace ento; REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *) REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet) +REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *) + namespace { -class LifetimeModeling : public Checker<check::PostCall, check::DeadSymbols> { +class LifetimeModeling + : public Checker<check::PostCall, check::DeadSymbols, + check::PreStmt<DeclStmt>, check::LifetimeEnd> { public: void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const override; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const; + void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; }; } // namespace @@ -55,6 +61,11 @@ std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn( return Regions; } +bool lifetime_modeling::isDeallocated(ProgramStateRef State, + const MemRegion *Region) { + return State->contains<DeallocatedSourceSet>(Region); +} + static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal, const MemRegion *Source) { LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>(); @@ -97,10 +108,35 @@ void LifetimeModeling::checkPostCall(const CallEvent &Call, C.addTransition(State); } +void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + SVal SourceVal = State->getLValue(VD, C.getStackFrame()); + if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) { + State = State->add<DeallocatedSourceSet>(SourceValRegion); + C.addTransition(State); + } +} + +void LifetimeModeling::checkPreStmt(const DeclStmt *DS, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + for (const auto *I : DS->decls()) { + if (const VarDecl *VD = dyn_cast<VarDecl>(I)) { + SVal Val = State->getLValue(VD, C.getStackFrame()); + if (const MemRegion *ValRegion = Val.getAsRegion()) + State = State->remove<DeallocatedSourceSet>(ValRegion); + } + } + C.addTransition(State); +} + void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { ProgramStateRef State = C.getState(); LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>(); + DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>(); for (SVal Val : llvm::make_first_range(LBMap)) { if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(R)) @@ -112,6 +148,11 @@ void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper, State = State->remove<LifetimeBoundMap>(Val); } + + for (const MemRegion *Region : Sources) { + if (!SymReaper.isLiveRegion(Region)) + State = State->remove<DeallocatedSourceSet>(Region); + } C.addTransition(State); } diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h index e13942eb1856b..cea2799db4b1d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h @@ -13,6 +13,8 @@ std::vector<const MemRegion *> getDanglingRegionsAfterReturn(SVal Source, ProgramStateRef State, CheckerContext &C); +/// Returns true if the underlying MemRegion is deallocated. +bool isDeallocated(ProgramStateRef State, const MemRegion *Region); } // namespace clang::ento::lifetime_modeling #endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H diff --git a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp new file mode 100644 index 0000000000000..781045a4d0654 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp @@ -0,0 +1,95 @@ +#include "LifetimeModeling.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { +class DanglingPtrDeref : public Checker<check::Location> { +public: + void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const; + void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N, + CheckerContext &C) const; + const BugType BugMsg{this, "ReportDanglingPtrDeref", "LifetimeBound"}; +}; + +class DanglingPtrDerefBRVisitor : public BugReporterVisitor { + const MemRegion *SourceRegion; + +public: + explicit DanglingPtrDerefBRVisitor(const MemRegion *Source) + : SourceRegion(Source) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.AddPointer(SourceRegion); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; +}; + +} // namespace + +void DanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + if (const MemRegion *LocRegion = Loc.getAsRegion()) { + if (lifetime_modeling::isDeallocated(State, LocRegion)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) + reportUseAfterScope(LocRegion, N, C); + } + } +} + +void DanglingPtrDeref::reportUseAfterScope(const MemRegion *Region, + ExplodedNode *N, + CheckerContext &C) const { + auto BR = std::make_unique<PathSensitiveBugReport>( + BugMsg, + (llvm::Twine("Use of '") + Region->getString() + + "' after its lifetime ended."), + N); + BR->addVisitor<DanglingPtrDerefBRVisitor>(Region); + C.emitReport(std::move(BR)); +} + +PathDiagnosticPieceRef +DanglingPtrDerefBRVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + using lifetime_modeling::isDeallocated; + const ExplodedNode *Pred = N->getFirstPred(); + if (!Pred) + return nullptr; + + if (!isDeallocated(N->getState(), SourceRegion) || + isDeallocated(Pred->getState(), SourceRegion)) + return nullptr; + + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + PathDiagnosticLocation Pos = PathDiagnosticLocation::createEnd( + S, BRC.getSourceManager(), N->getStackFrame()); + return std::make_shared<PathDiagnosticEventPiece>( + Pos, + (llvm::Twine("'") + SourceRegion->getString() + "' is destroyed here") + .str(), + true); +} + +void ento::registerDanglingPtrDeref(CheckerManager &Mgr) { + Mgr.registerChecker<DanglingPtrDeref>(); +} + +bool ento::shouldRegisterDanglingPtrDeref(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp new file mode 100644 index 0000000000000..ad6a9aceb5981 --- /dev/null +++ b/clang/test/Analysis/dangling-ptr-deref.cpp @@ -0,0 +1,86 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.DanglingPtrDeref \ +// RUN: -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s + +void test_case_one() { + int *ptr = nullptr; + { + int num = 5; + ptr = # + } + // expected-note@-1 {{'num' is destroyed here}} + *ptr = 6; + // expected-warning@-1 {{Use of 'num' after its lifetime ended}} + // expected-note@-2 {{Use of 'num' after its lifetime ended}} +} + +void test_case_two() { + int *ptr_one = nullptr; + int *ptr_two = nullptr; + { + int n = 1; + int m = 2; + ptr_one = &n; + ptr_two = &m; + } + // expected-note@-1 {{'n' is destroyed here}} + // expected-note@-2 {{'m' is destroyed here}} + *ptr_one = 6; + *ptr_two = 7; + // expected-warning@-2 {{Use of 'n' after its lifetime ended}} + // expected-note@-3 {{Use of 'n' after its lifetime ended}} + // expected-warning@-3 {{Use of 'm' after its lifetime ended}} + // expected-note@-4 {{Use of 'm' after its lifetime ended}} +} + +void escape(int *ptr); + +void test_case_three() { + int num = 5; + int *ptr = # + { + *ptr = 6; // no-warning + } +} + +void test_case_four() { + int *ptr = nullptr; + { + int num = 5; + ptr = # + } + // expected-note@-1 {{'num' is destroyed here}} + int i = *ptr; + // expected-warning@-1 {{Use of 'num' after its lifetime ended}} + // expected-note@-2 {{Use of 'num' after its lifetime ended}} + i += i; +} + +void test_case_five() { + int *ptr = nullptr; + for(int i = 0; i < 10; ++i) { + ptr = &i; + } + escape(ptr); // no-warning +} + +void test_case_six() { + for(int i = 0; i < 10; ++i) { + int *ptr = &i; + escape(ptr); // no-warning + } +} + +void test_case_seven() { + int *ptr = nullptr; + // expected-note@+3 {{Loop condition is true. Entering loop body}} + // expected-note@+2 {{Assuming 'i' is >= 10}} + // expected-note@+1 {{Loop condition is false. Execution continues on line}} + for (int i = 0; i < 10; ++i) { + ptr = &i; + escape(ptr); + } + // expected-note@-1 {{'i' is destroyed here}} + *ptr = 6; + // expected-warning@-1 {{Use of 'i' after its lifetime ended}} + // expected-note@-2 {{Use of 'i' after its lifetime ended}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
