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 = &num;
+  }
+  // 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 = &num;
+  {
+    *ptr = 6; // no-warning
+  }
+}
+
+void test_case_four() {
+  int *ptr = nullptr;
+  {
+    int num = 5;
+    ptr = &num;
+  }
+  // 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

Reply via email to