https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/209278
>From f296e044437ae6cbb0ba0d3e33c4a00ac1b82295 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 29 Jun 2026 10:19:38 +0200 Subject: [PATCH 01/11] Start implementing the reportDanglingPtrDeref checker. --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td | 5 +++++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 0f8b3bb1e1d18..d3b042a7d510f 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 ReportDanglingPtrDeref : Checker<"ReportDanglingPtrDeref">, + 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 >From 4d1e43906b26f74ef91d263b7252ef44e26c5fae Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 26 Jun 2026 01:09:44 +0200 Subject: [PATCH 02/11] Implemented the Modeling checker. --- .../StaticAnalyzer/Checkers/LifetimeModeling.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h new file mode 100644 index 0000000000000..671d5ce1ec5da --- /dev/null +++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h @@ -0,0 +1,18 @@ +#ifndef LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H +#define LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H + +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include <vector> + +namespace clang { +namespace ento { +namespace lifetimemodeling { + +std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal); + +} // namespace lifetimemodeling +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H >From 830b218c18f2edc518b41d593667bd78c88ffbc7 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 29 Jun 2026 10:54:21 +0200 Subject: [PATCH 03/11] Add isDeallocated API to the modeling checker. --- clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h index 671d5ce1ec5da..e9eb4979e05d2 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h +++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h @@ -10,6 +10,7 @@ namespace ento { namespace lifetimemodeling { std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal); +bool isDeallocated(ProgramStateRef, const MemRegion *); } // namespace lifetimemodeling } // namespace ento >From 6e92cd9ca5424b44e5095a0713fe88f60000f69a Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 29 Jun 2026 12:57:19 +0200 Subject: [PATCH 04/11] Added ReportDanglingPtrDeref checker. --- .../Checkers/ReportDanglingPtrDeref.cpp | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp new file mode 100644 index 0000000000000..24620d369b80d --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp @@ -0,0 +1,46 @@ +#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" + +using namespace clang; +using namespace ento; + +namespace { +class ReportDanglingPtrDeref : 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"}; +}; +} // namespace + +void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + if (const MemRegion *LocRegion = Loc.getAsRegion()) { + if (lifetimemodeling::isDeallocated(State, LocRegion)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) + reportUseAfterScope(LocRegion, N, C); + } + } +} + +void ReportDanglingPtrDeref::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.") + .str(), + N); + C.emitReport(std::move(BR)); +} + +void ento::registerReportDanglingPtrDeref(CheckerManager &Mgr) { + Mgr.registerChecker<ReportDanglingPtrDeref>(); +} + +bool ento::shouldRegisterReportDanglingPtrDeref(const CheckerManager &Mgr) { + return true; +} >From 21d1ba323d183bc0c982f3c5c642acf6da89afe7 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 29 Jun 2026 17:47:21 +0200 Subject: [PATCH 05/11] Implemented reportDanglingPtrDeref checker and added test suites for the checker. --- .../Checkers/ReportDanglingPtrDeref.cpp | 17 ++++--- clang/test/Analysis/dangling-ptr-deref.cpp | 45 +++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 clang/test/Analysis/dangling-ptr-deref.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp index 24620d369b80d..309468b1736f6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp @@ -1,8 +1,8 @@ -#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" using namespace clang; using namespace ento; @@ -10,13 +10,16 @@ using namespace ento; namespace { class ReportDanglingPtrDeref : 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; + 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"}; }; } // namespace -void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, CheckerContext &C) const { +void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const { ProgramStateRef State = C.getState(); if (const MemRegion *LocRegion = Loc.getAsRegion()) { @@ -27,7 +30,9 @@ void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, } } -void ReportDanglingPtrDeref::reportUseAfterScope(const MemRegion *Region, ExplodedNode *N, CheckerContext &C) const { +void ReportDanglingPtrDeref::reportUseAfterScope(const MemRegion *Region, + ExplodedNode *N, + CheckerContext &C) const { auto BR = std::make_unique<PathSensitiveBugReport>( BugMsg, (llvm::Twine("Use of '") + Region->getString() + diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp new file mode 100644 index 0000000000000..05755f09c0e57 --- /dev/null +++ b/clang/test/Analysis/dangling-ptr-deref.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.ReportDanglingPtrDeref \ +// RUN: -analyzer-config cfg-lifetime=true -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.ReportDanglingPtrDeref \ +// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s + +void test_case_one() { + int *ptr = nullptr; + { + int num = 5; + ptr = # + } + *ptr = 6; // expected-warning {{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; + } + *ptr_one = 6; // expected-warning {{Use of 'n' after its lifetime ended}} + *ptr_two = 7; // expected-warning {{Use of 'm' after its lifetime ended}} +} + +void test_case_three() { + int num = 5; + int *ptr = # + { + *ptr = 6; // no-warning + } +} + +void test_case_four() { + int *ptr = nullptr; + { + int num = 5; + ptr = # + } + int i = *ptr; // expected-warning {{Use of 'num' after its lifetime ended}} + i += i; +} + >From e2b903a11cfec1406594f707aa3043bb95b87ad3 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Thu, 9 Jul 2026 12:59:16 +0200 Subject: [PATCH 06/11] [analyzer] Implemented VisitNode for BugReporterVisitor. --- .../Checkers/ReportDanglingPtrDeref.cpp | 49 ++++++++++++++++++- clang/test/Analysis/dangling-ptr-deref.cpp | 20 +++++--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp index 309468b1736f6..52438dbd2a330 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp @@ -1,6 +1,7 @@ #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Checkers/LifetimeModeling.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" @@ -10,12 +11,33 @@ using namespace ento; namespace { class ReportDanglingPtrDeref : public Checker<check::Location> { public: + class ReportDanglingPtrDerefBRVisitor : public BugReporterVisitor { + SVal BoundRegion; + const MemRegion *SourceRegion; + + public: + ReportDanglingPtrDerefBRVisitor(SVal Region, const MemRegion *Source) + : BoundRegion(Region), SourceRegion(Source) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + BoundRegion.Profile(ID); + SourceRegion->Profile(ID); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + }; + 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"}; }; + } // namespace void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, @@ -23,8 +45,9 @@ void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, ProgramStateRef State = C.getState(); if (const MemRegion *LocRegion = Loc.getAsRegion()) { - if (lifetimemodeling::isDeallocated(State, LocRegion)) { - if (ExplodedNode *N = C.generateNonFatalErrorNode()) + if (lifetime_modeling::isDeallocated(State, LocRegion)) { + if (ExplodedNode *N = + C.generateNonFatalErrorNode(C.getState(), C.getPredecessor())) reportUseAfterScope(LocRegion, N, C); } } @@ -42,6 +65,28 @@ void ReportDanglingPtrDeref::reportUseAfterScope(const MemRegion *Region, C.emitReport(std::move(BR)); } +PathDiagnosticPieceRef +ReportDanglingPtrDeref::ReportDanglingPtrDerefBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + if (!lifetime_modeling::isBoundToLifetimeSourceSet(N->getState(), + BoundRegion) || + lifetime_modeling::isBoundToLifetimeSourceSet( + N->getFirstPred()->getState(), BoundRegion)) + return nullptr; + + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getStackFrame()); + return std::make_shared<PathDiagnosticEventPiece>( + Pos, + (llvm::Twine("'") + SourceRegion->getString() + "' is destroyed here") + .str(), + true); +} + void ento::registerReportDanglingPtrDeref(CheckerManager &Mgr) { Mgr.registerChecker<ReportDanglingPtrDeref>(); } diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp index 05755f09c0e57..2cbbadcd24313 100644 --- a/clang/test/Analysis/dangling-ptr-deref.cpp +++ b/clang/test/Analysis/dangling-ptr-deref.cpp @@ -1,7 +1,7 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.ReportDanglingPtrDeref \ -// RUN: -analyzer-config cfg-lifetime=true -verify %s +// RUN: -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.ReportDanglingPtrDeref \ -// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -verify %s +// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s void test_case_one() { int *ptr = nullptr; @@ -9,7 +9,9 @@ void test_case_one() { int num = 5; ptr = # } - *ptr = 6; // expected-warning {{Use of 'num' after its lifetime ended}} + *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() { @@ -21,8 +23,12 @@ void test_case_two() { ptr_one = &n; ptr_two = &m; } - *ptr_one = 6; // expected-warning {{Use of 'n' after its lifetime ended}} - *ptr_two = 7; // expected-warning {{Use of 'm' after its lifetime ended}} + *ptr_one = 6; + *ptr_two = 7; + // expected-warning@-2 {{Use of 'n' after its lifetime ended}} + // expected-warning@-2 {{Use of 'm' after its lifetime ended}} + // expected-note@-4 {{Use of 'n' after its lifetime ended}} + // expected-note@-4 {{Use of 'm' after its lifetime ended}} } void test_case_three() { @@ -39,7 +45,9 @@ void test_case_four() { int num = 5; ptr = # } - int i = *ptr; // expected-warning {{Use of 'num' after its lifetime ended}} + 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; } >From 3b3e42522257fb11bab1dd4c92d757fa91a83231 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Thu, 9 Jul 2026 13:11:43 +0200 Subject: [PATCH 07/11] [analyzer] Updated the modeling checker. --- .../StaticAnalyzer/Checkers/LifetimeModeling.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h index e9eb4979e05d2..b39735c09d63a 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h +++ b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h @@ -5,15 +5,16 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include <vector> -namespace clang { -namespace ento { -namespace lifetimemodeling { - +namespace clang::ento::lifetime_modeling { +// FIXME: Once #205951 lands this function might needs to be removed. std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal); + +/// Returns true if the SVal key is present in the map. +bool isBoundToLifetimeSourceSet(ProgramStateRef State, SVal Val); + +// FIXME: Once PR #205951 lands this function might needs to be removed. bool isDeallocated(ProgramStateRef, const MemRegion *); -} // namespace lifetimemodeling -} // namespace ento -} // namespace clang +} // namespace clang::ento::lifetime_modeling #endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H >From bfa4b69e071512f8d132c6832e2097111c96b050 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Sun, 12 Jul 2026 22:48:24 +0200 Subject: [PATCH 08/11] Resolved nits. --- .../Checkers/ReportDanglingPtrDeref.cpp | 67 +++++++++---------- clang/test/Analysis/dangling-ptr-deref.cpp | 2 - 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp index 52438dbd2a330..e25e49be7f9ef 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp @@ -7,37 +7,38 @@ using namespace clang; using namespace ento; +using lifetime_modeling::isBoundToLifetimeSourceSet; namespace { class ReportDanglingPtrDeref : public Checker<check::Location> { public: - class ReportDanglingPtrDerefBRVisitor : public BugReporterVisitor { - SVal BoundRegion; - const MemRegion *SourceRegion; - - public: - ReportDanglingPtrDerefBRVisitor(SVal Region, const MemRegion *Source) - : BoundRegion(Region), SourceRegion(Source) {} - - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - BoundRegion.Profile(ID); - SourceRegion->Profile(ID); - } - - PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - PathSensitiveBugReport &BR) override; - }; - void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, CheckerContext &C) const; - void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N, + void reportUseAfterScope(const MemRegion *Region, SVal Val, ExplodedNode *N, CheckerContext &C) const; const BugType BugMsg{this, "ReportDanglingPtrDeref", "LifetimeBound"}; }; +class ReportDanglingPtrDerefBRVisitor : public BugReporterVisitor { + SVal BoundRegion; + const MemRegion *SourceRegion; + +public: + ReportDanglingPtrDerefBRVisitor(SVal Region, const MemRegion *Source) + : BoundRegion(Region), SourceRegion(Source) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + BoundRegion.Profile(ID); + SourceRegion->Profile(ID); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; +}; + } // namespace void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, @@ -46,33 +47,31 @@ void ReportDanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S, if (const MemRegion *LocRegion = Loc.getAsRegion()) { if (lifetime_modeling::isDeallocated(State, LocRegion)) { - if (ExplodedNode *N = - C.generateNonFatalErrorNode(C.getState(), C.getPredecessor())) - reportUseAfterScope(LocRegion, N, C); + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) + reportUseAfterScope(LocRegion, Loc, N, C); } } } void ReportDanglingPtrDeref::reportUseAfterScope(const MemRegion *Region, - ExplodedNode *N, + SVal Val, ExplodedNode *N, CheckerContext &C) const { auto BR = std::make_unique<PathSensitiveBugReport>( BugMsg, (llvm::Twine("Use of '") + Region->getString() + - "' after its lifetime ended.") - .str(), + "' after its lifetime ended."), N); + BR->addVisitor( + std::make_unique<ReportDanglingPtrDerefBRVisitor>(Val, Region)); C.emitReport(std::move(BR)); } PathDiagnosticPieceRef -ReportDanglingPtrDeref::ReportDanglingPtrDerefBRVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, - PathSensitiveBugReport &BR) { - if (!lifetime_modeling::isBoundToLifetimeSourceSet(N->getState(), - BoundRegion) || - lifetime_modeling::isBoundToLifetimeSourceSet( - N->getFirstPred()->getState(), BoundRegion)) +ReportDanglingPtrDerefBRVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + if (!isBoundToLifetimeSourceSet(N->getState(), BoundRegion) || + isBoundToLifetimeSourceSet(N->getFirstPred()->getState(), BoundRegion)) return nullptr; const Stmt *S = N->getStmtForDiagnostics(); diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp index 2cbbadcd24313..e00307afc4dab 100644 --- a/clang/test/Analysis/dangling-ptr-deref.cpp +++ b/clang/test/Analysis/dangling-ptr-deref.cpp @@ -1,7 +1,5 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.ReportDanglingPtrDeref \ // RUN: -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.cplusplus.ReportDanglingPtrDeref \ -// RUN: -analyzer-config c++-container-inlining=false -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s void test_case_one() { int *ptr = nullptr; >From d16ccde645e5c679e0451bdaec388af9a1d0ee1b Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Sun, 12 Jul 2026 23:51:30 +0200 Subject: [PATCH 09/11] Check if the root node has pred and apply nits. --- .../Checkers/ReportDanglingPtrDeref.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp index e25e49be7f9ef..932f8b6fceade 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp @@ -7,7 +7,6 @@ using namespace clang; using namespace ento; -using lifetime_modeling::isBoundToLifetimeSourceSet; namespace { class ReportDanglingPtrDeref : public Checker<check::Location> { @@ -61,8 +60,7 @@ void ReportDanglingPtrDeref::reportUseAfterScope(const MemRegion *Region, (llvm::Twine("Use of '") + Region->getString() + "' after its lifetime ended."), N); - BR->addVisitor( - std::make_unique<ReportDanglingPtrDerefBRVisitor>(Val, Region)); + BR->addVisitor<ReportDanglingPtrDerefBRVisitor>(Val, Region); C.emitReport(std::move(BR)); } @@ -70,8 +68,13 @@ PathDiagnosticPieceRef ReportDanglingPtrDerefBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { + using lifetime_modeling::isBoundToLifetimeSourceSet; + const ExplodedNode *Pred = N->getFirstPred(); + if (!Pred) + return nullptr; + if (!isBoundToLifetimeSourceSet(N->getState(), BoundRegion) || - isBoundToLifetimeSourceSet(N->getFirstPred()->getState(), BoundRegion)) + isBoundToLifetimeSourceSet(Pred->getState(), BoundRegion)) return nullptr; const Stmt *S = N->getStmtForDiagnostics(); >From 3fe52b0eb4a0bb6ea2e0fe8fd9503f8fc102b72d Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Mon, 13 Jul 2026 21:23:55 +0200 Subject: [PATCH 10/11] Implemented checkPreStmt and refactored reporting checkers. --- .../Checkers/LifetimeModeling.h | 20 -------- .../Checkers/LifetimeModeling.cpp | 50 ++++++++++++++++++- .../Checkers/LifetimeModeling.h | 5 ++ .../Checkers/ReportDanglingPtrDeref.cpp | 2 +- clang/test/Analysis/dangling-ptr-deref.cpp | 30 +++++++++++ 5 files changed, 85 insertions(+), 22 deletions(-) delete mode 100644 clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h diff --git a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h deleted file mode 100644 index b39735c09d63a..0000000000000 --- a/clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H -#define LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H - -#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" -#include <vector> - -namespace clang::ento::lifetime_modeling { -// FIXME: Once #205951 lands this function might needs to be removed. -std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef, SVal); - -/// Returns true if the SVal key is present in the map. -bool isBoundToLifetimeSourceSet(ProgramStateRef State, SVal Val); - -// FIXME: Once PR #205951 lands this function might needs to be removed. -bool isDeallocated(ProgramStateRef, const MemRegion *); - -} // namespace clang::ento::lifetime_modeling - -#endif // LLVM_CLANG_INCLUDE_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp index a2f58e0710801..4abb7d8dc2d2c 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,16 @@ std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn( return Regions; } +bool lifetime_modeling::isDeallocated(ProgramStateRef State, + const MemRegion *Region) { + return State->contains<DeallocatedSourceSet>(Region); +} + +bool lifetime_modeling::isBoundToLifetimeSourceSet(ProgramStateRef State, + SVal Val) { + return State->get<LifetimeBoundMap>(Val) != nullptr; +} + static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal, const MemRegion *Source) { LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>(); @@ -97,10 +113,37 @@ void LifetimeModeling::checkPostCall(const CallEvent &Call, C.addTransition(State); } +void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (!VD) + return; + + 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 +155,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..f34a0c70b237d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h +++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h @@ -13,6 +13,11 @@ std::vector<const MemRegion *> getDanglingRegionsAfterReturn(SVal Source, ProgramStateRef State, CheckerContext &C); +/// Returns true if the SVal key is present in the map. +bool isBoundToLifetimeSourceSet(ProgramStateRef State, SVal Val); + +/// 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 index 932f8b6fceade..3de5ff4f853e7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp @@ -1,5 +1,5 @@ +#include "LifetimeModeling.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp index e00307afc4dab..99d5394d492f7 100644 --- a/clang/test/Analysis/dangling-ptr-deref.cpp +++ b/clang/test/Analysis/dangling-ptr-deref.cpp @@ -29,6 +29,8 @@ void test_case_two() { // expected-note@-4 {{Use of 'm' after its lifetime ended}} } +void escape(int *ptr); + void test_case_three() { int num = 5; int *ptr = # @@ -49,3 +51,31 @@ void test_case_four() { 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; + for (int i = 0; i < 10; ++i) { + ptr = &i; + escape(ptr); + } + *ptr = 6; + // expected-warning@-1 {{Use of 'i' after its lifetime ended}} + // expected-note@-2 {{Use of 'i' after its lifetime ended}} + // expected-note@-7 {{Loop condition is true. Entering loop body}} + // expected-note@-8 {{Assuming 'i' is >= 10}} + // expected-note@-9 {{Loop condition is false. Execution continues on line 71}} +} >From 503a3b00288a15e2da3fb39e2d910026a62a6b33 Mon Sep 17 00:00:00 2001 From: Benedek Kaibas <[email protected]> Date: Mon, 13 Jul 2026 23:47:39 +0200 Subject: [PATCH 11/11] Update clang/test/Analysis/dangling-ptr-deref.cpp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Balázs Benics <[email protected]> --- clang/test/Analysis/dangling-ptr-deref.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp index 99d5394d492f7..b2960a27e1421 100644 --- a/clang/test/Analysis/dangling-ptr-deref.cpp +++ b/clang/test/Analysis/dangling-ptr-deref.cpp @@ -25,8 +25,8 @@ void test_case_two() { *ptr_two = 7; // expected-warning@-2 {{Use of 'n' after its lifetime ended}} // expected-warning@-2 {{Use of 'm' after its lifetime ended}} - // expected-note@-4 {{Use of 'n' after its lifetime ended}} - // expected-note@-4 {{Use of 'm' after its lifetime ended}} + // expected-note@-4 {{Use of 'n' after its lifetime ended}} + // expected-note@-4 {{Use of 'm' after its lifetime ended}} } void escape(int *ptr); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
