[clang] [analyzer] Implemented the DanglingPtrDeref checker to detect use-after-scope lifetime errors (PR #206460)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 29 03:59:13 PDT 2026
https://github.com/benedekaibas created https://github.com/llvm/llvm-project/pull/206460
None
>From d1c7eb6cfd432345ee492fd3d25d14a02305d4e1 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 1/4] Start implementing the reportDanglingPtrDeref checker.
---
.../include/clang/StaticAnalyzer/Checkers/Checkers.td | 11 +++++++++++
clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt | 1 +
2 files changed, 12 insertions(+)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index b565481d28fdb..631a273bb4f18 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -788,9 +788,20 @@ def SmartPtrChecker: Checker<"SmartPtr">,
Dependencies<[SmartPtrModeling]>,
Documentation<HasDocumentation>;
+def LifetimeModeling : Checker<"LifetimeModeling">,
+ HelpText<"Model [[clang::lifetimebound]] annotations for lifetime analysis">,
+ Documentation<NotDocumented>,
+ Hidden;
+
def UseAfterLifetimeEnd : Checker<"UseAfterLifetimeEnd">,
HelpText<"Check for uses of references or pointers that "
"outlive their bound object">,
+ 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 46c0c36fda736..b05a51c46485c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -92,6 +92,7 @@ add_clang_library(clangStaticAnalyzerCheckers
PointerSubChecker.cpp
PthreadLockChecker.cpp
PutenvStackArrayChecker.cpp
+ ReportDanglingPtrDeref.cpp
RetainCountChecker/RetainCountChecker.cpp
RetainCountChecker/RetainCountDiagnostics.cpp
ReturnPointerRangeChecker.cpp
>From 5e7f0fb5a14052b220fe75106aba33eb5f64bd24 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 2/4] Implemented the Modeling checker.
---
.../Checkers/LifetimeModeling.h | 18 ++++
.../StaticAnalyzer/Checkers/CMakeLists.txt | 1 +
.../Checkers/LifetimeModeling.cpp | 101 ++++++++++++++++++
3 files changed, 120 insertions(+)
create mode 100644 clang/include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h
create mode 100644 clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
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
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index b05a51c46485c..4ec6e368c8fc3 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -55,6 +55,7 @@ add_clang_library(clangStaticAnalyzerCheckers
IteratorModeling.cpp
IteratorRangeChecker.cpp
IvarInvalidationChecker.cpp
+ LifetimeModeling.cpp
LLVMConventionsChecker.cpp
LocalizationChecker.cpp
MacOSKeychainAPIChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
new file mode 100644
index 0000000000000..65aa071a3284a
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -0,0 +1,101 @@
+#include "clang/StaticAnalyzer/Checkers/LifetimeModeling.h"
+#include "clang/AST/Attr.h"
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+using namespace lifetimemodeling;
+
+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::LifetimeEnd> {
+public:
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
+};
+
+} // namespace
+
+std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef State,
+ SVal Val) {
+ std::vector<const MemRegion *> StoreRegion;
+ if (const auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
+ for (const MemRegion *Region : *SourceSet)
+ StoreRegion.push_back(Region);
+ return StoreRegion;
+ }
+ return StoreRegion;
+}
+
+static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
+ const MemRegion *Source) {
+ LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
+ const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
+
+ LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
+ Set = F.add(Set, Source);
+ State = State->set<LifetimeBoundMap>(RetVal, Set);
+ return State;
+}
+
+void LifetimeModeling::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
+ if (!FC)
+ return;
+
+ const FunctionDecl *FD = FC->getDecl();
+ if (!FD)
+ return;
+
+ SVal RetVal = Call.getReturnValue();
+
+ for (const ParmVarDecl *PVD : FD->parameters()) {
+ if (PVD->hasAttr<LifetimeBoundAttr>()) {
+ unsigned Idx = PVD->getFunctionScopeIndex();
+ SVal Arg = Call.getArgSVal(Idx);
+ if (const MemRegion *ArgValRegion = Arg.getAsRegion())
+ State = bindValues(State, RetVal, ArgValRegion);
+ }
+ }
+
+ if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+ if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
+ if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion())
+ State = bindValues(State, RetVal, AttrRegion);
+ }
+ }
+ 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 ento::registerLifetimeModeling(CheckerManager &Mgr) {
+ Mgr.registerChecker<LifetimeModeling>();
+}
+
+bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) {
+ return true;
+}
>From 187fcbf113977244fb14ec79ccc81c9893f44073 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 3/4] Add isDeallocated API to the modeling checker.
---
.../include/clang/StaticAnalyzer/Checkers/LifetimeModeling.h | 1 +
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 5 ++++-
2 files changed, 5 insertions(+), 1 deletion(-)
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
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 65aa071a3284a..1355c4218f921 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -31,11 +31,14 @@ std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef State,
if (const auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet)
StoreRegion.push_back(Region);
- return StoreRegion;
}
return StoreRegion;
}
+bool isDeallocated(ProgramStateRef State, const MemRegion *Region) {
+ return State->contains<DeallocatedSourceSet>(Region);
+}
+
static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
const MemRegion *Source) {
LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
>From 98aaa83b3029da553dfe751488597e97367c17ea 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 4/4] Added ReportDanglingPtrDeref checker.
---
.../Checkers/LifetimeModeling.cpp | 10 +++-
.../Checkers/ReportDanglingPtrDeref.cpp | 46 +++++++++++++++++++
2 files changed, 54 insertions(+), 2 deletions(-)
create mode 100644 clang/lib/StaticAnalyzer/Checkers/ReportDanglingPtrDeref.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 1355c4218f921..ea9949d9696ba 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -8,7 +8,6 @@
using namespace clang;
using namespace ento;
-using namespace lifetimemodeling;
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
@@ -24,9 +23,12 @@ class LifetimeModeling : public Checker<check::PostCall, check::LifetimeEnd> {
};
} // namespace
-
+namespace clang {
+namespace ento {
+namespace lifetimemodeling {
std::vector<const MemRegion *> getLifetimeSourceSet(ProgramStateRef State,
SVal Val) {
+
std::vector<const MemRegion *> StoreRegion;
if (const auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet)
@@ -39,6 +41,10 @@ bool isDeallocated(ProgramStateRef State, const MemRegion *Region) {
return State->contains<DeallocatedSourceSet>(Region);
}
+} // namespace lifetimemodeling
+} // namespace ento
+} // namespace clang
+
static ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
const MemRegion *Source) {
LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
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;
+}
More information about the cfe-commits
mailing list