[clang] e1d7480 - [analyzer] Implemented the DanglingPtrDeref checker to detect use-after-scope lifetime errors (#209278)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 14 08:03:47 PDT 2026
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 at -1 {{'num' is destroyed here}}
+ *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() {
+ int *ptr_one = nullptr;
+ int *ptr_two = nullptr;
+ {
+ int n = 1;
+ int m = 2;
+ ptr_one = &n;
+ ptr_two = &m;
+ }
+ // expected-note at -1 {{'n' is destroyed here}}
+ // expected-note at -2 {{'m' is destroyed here}}
+ *ptr_one = 6;
+ *ptr_two = 7;
+ // expected-warning at -2 {{Use of 'n' after its lifetime ended}}
+ // expected-note at -3 {{Use of 'n' after its lifetime ended}}
+ // expected-warning at -3 {{Use of 'm' after its lifetime ended}}
+ // expected-note at -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 at -1 {{'num' is destroyed here}}
+ 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;
+}
+
+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 at +3 {{Loop condition is true. Entering loop body}}
+ // expected-note at +2 {{Assuming 'i' is >= 10}}
+ // expected-note at +1 {{Loop condition is false. Execution continues on line}}
+ for (int i = 0; i < 10; ++i) {
+ ptr = &i;
+ escape(ptr);
+ }
+ // expected-note at -1 {{'i' is destroyed here}}
+ *ptr = 6;
+ // expected-warning at -1 {{Use of 'i' after its lifetime ended}}
+ // expected-note at -2 {{Use of 'i' after its lifetime ended}}
+}
More information about the cfe-commits
mailing list