[clang] [analyzer] Implemented the DanglingPtrDeref checker to detect use-after-scope lifetime errors (PR #209278)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 13 14:47:53 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/209278
>From f296e044437ae6cbb0ba0d3e33c4a00ac1b82295 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas.benedek02 at gmail.com>
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 <kaibas.benedek02 at gmail.com>
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 <kaibas.benedek02 at gmail.com>
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 <kaibas.benedek02 at gmail.com>
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 <kaibas.benedek02 at gmail.com>
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 <kaibas01 at allegheny.edu>
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 at -1 {{Use of 'num' after its lifetime ended}}
+ // expected-note at -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 at -2 {{Use of 'n' after its lifetime ended}}
+ // expected-warning at -2 {{Use of 'm' after its lifetime ended}}
+ // expected-note at -4 {{Use of 'n' after its lifetime ended}}
+ // expected-note at -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 at -1 {{Use of 'num' after its lifetime ended}}
+ // expected-note at -2 {{Use of 'num' after its lifetime ended}}
i += i;
}
>From 3b3e42522257fb11bab1dd4c92d757fa91a83231 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
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 <kaibas01 at allegheny.edu>
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 <kaibas01 at allegheny.edu>
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 <kaibas01 at allegheny.edu>
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 at -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 at -1 {{Use of 'i' after its lifetime ended}}
+ // expected-note at -2 {{Use of 'i' after its lifetime ended}}
+ // expected-note at -7 {{Loop condition is true. Entering loop body}}
+ // expected-note at -8 {{Assuming 'i' is >= 10}}
+ // expected-note at -9 {{Loop condition is false. Execution continues on line 71}}
+}
>From 503a3b00288a15e2da3fb39e2d910026a62a6b33 Mon Sep 17 00:00:00 2001
From: Benedek Kaibas <82393336+benedekaibas at users.noreply.github.com>
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 <benicsbalazs at gmail.com>
---
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 at -2 {{Use of 'n' after its lifetime ended}}
// expected-warning at -2 {{Use of 'm' after its lifetime ended}}
- // expected-note at -4 {{Use of 'n' after its lifetime ended}}
- // expected-note at -4 {{Use of 'm' after its lifetime ended}}
+ // expected-note at -4 {{Use of 'n' after its lifetime ended}}
+ // expected-note at -4 {{Use of 'm' after its lifetime ended}}
}
void escape(int *ptr);
More information about the cfe-commits
mailing list