[clang] [analyzer] Match dangling subobjects by their base region in DanglingPtrDeref (PR #211552)

Benedek Kaibas via cfe-commits cfe-commits at lists.llvm.org
Thu Jul 23 14:56:13 PDT 2026


https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/211552

>From 28b8e2424a5e3c947765457f2de5f2bf937d100b Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Thu, 23 Jul 2026 15:01:21 +0200
Subject: [PATCH 1/2] [analyzer] Match dangling subobjects by their base region
 in DanglingPtrDeref

---
 .../Checkers/DanglingPtrDeref.cpp             | 14 +++--
 .../Checkers/LifetimeModeling.cpp             |  2 +-
 clang/test/Analysis/dangling-ptr-deref.cpp    | 53 +++++++++++++++++++
 3 files changed, 65 insertions(+), 4 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
index ff2087e1db933..41fcc68f8f109 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
@@ -66,13 +66,21 @@ void DanglingPtrDeref::checkPostCall(const CallEvent &Call,
   }
 }
 
+static std::string getRegionName(const MemRegion *Reg) {
+  // FIXME: Once the checker supports heap allocation, more region kinds
+  // should be handled to produce the correct descriptive name.
+  if (const std::string RegName = Reg->getDescriptiveName(); !RegName.empty())
+    return RegName;
+  return "the region";
+}
+
 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."),
+      (llvm::Twine("Use of ") + getRegionName(Region) +
+       " after its lifetime ended."),
       N);
   BR->addVisitor<DanglingPtrDerefBRVisitor>(Region);
   C.emitReport(std::move(BR));
@@ -99,7 +107,7 @@ DanglingPtrDerefBRVisitor::VisitNode(const ExplodedNode *N,
       S, BRC.getSourceManager(), N->getStackFrame());
   return std::make_shared<PathDiagnosticEventPiece>(
       Pos,
-      (llvm::Twine("'") + SourceRegion->getString() + "' is destroyed here")
+      (llvm::Twine() + getRegionName(SourceRegion) + " is destroyed here")
           .str(),
       true);
 }
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 2b6f5dae4243f..ef0b1cb264b18 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -73,7 +73,7 @@ std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn(
 
 bool lifetime_modeling::isDeallocated(ProgramStateRef State,
                                       const MemRegion *Region) {
-  return State->contains<DeallocatedSourceSet>(Region);
+  return State->contains<DeallocatedSourceSet>(Region->getBaseRegion());
 }
 
 static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp
index e661eff9ccbb6..9deda4b2502a4 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -112,3 +112,56 @@ void inlined_callee_single_report() {
   // expected-note at -1 {{Calling 'deref_param'}}
   (void)r;
 }
+
+struct MyBuffer {
+  char buffer[8];
+};
+
+void member_subregion_dangling_deref() {
+  const char *p = nullptr;
+  {
+    struct MyBuffer tmp_buffer = {};
+    p = tmp_buffer.buffer;
+  }
+  // expected-note at -1 {{'tmp_buffer.buffer[0]' is destroyed here}}
+  char c = *p;
+  // expected-warning at -1 {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}}
+  // expected-note at -2    {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}}
+  (void)c;
+}
+
+void opaque(const char *);
+
+void passing_dangling_to_call() {
+  const char *p = nullptr;
+  {
+    struct MyBuffer tmp_buffer = {};
+    p = tmp_buffer.buffer;
+  }
+  // expected-note at -1 {{'tmp_buffer.buffer[0]' is destroyed here}}
+  opaque(p);
+  // expected-warning at -1 {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}}
+  // expected-note at -2    {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}}
+}
+
+void member_subregion_alive_deref() {
+  {
+    struct MyBuffer tmp_buffer = {};
+    const char *p = tmp_buffer.buffer;
+    opaque(p); //   no-warning
+    char c = *p; // no-warning
+    (void)c;
+  }
+}
+
+void arr_elem_subreg_dangling_deref() {
+  int *ptr = nullptr;
+  {
+    int local_arr[5];
+    ptr = &local_arr[1];
+  }
+  // expected-note at -1 {{'local_arr[1]' is destroyed here}}
+  *ptr = 7;
+  // expected-warning at -1 {{Use of 'local_arr[1]' after its lifetime ended}}
+  // expected-note at -2    {{Use of 'local_arr[1]' after its lifetime ended}}
+}

>From 2cdca2d740aca62e6b947b6afffb22e1ba3b7f57 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Thu, 23 Jul 2026 23:55:54 +0200
Subject: [PATCH 2/2] Resolve nits.

---
 .../Checkers/DanglingPtrDeref.cpp             |  3 +-
 clang/test/Analysis/dangling-ptr-deref.cpp    | 61 +++++++++++++++++--
 2 files changed, 56 insertions(+), 8 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
index 41fcc68f8f109..d1b9f0fea4044 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
@@ -107,8 +107,7 @@ DanglingPtrDerefBRVisitor::VisitNode(const ExplodedNode *N,
       S, BRC.getSourceManager(), N->getStackFrame());
   return std::make_shared<PathDiagnosticEventPiece>(
       Pos,
-      (llvm::Twine() + getRegionName(SourceRegion) + " is destroyed here")
-          .str(),
+      (getRegionName(SourceRegion) + llvm::Twine(" is destroyed here")).str(),
       true);
 }
 
diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp
index 9deda4b2502a4..654b23d0fea64 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -116,18 +116,20 @@ void inlined_callee_single_report() {
 struct MyBuffer {
   char buffer[8];
 };
+struct MyStruct { int x; };
+struct Inner { int x; };
+struct Outer { struct Inner inner; };
 
-void member_subregion_dangling_deref() {
+char member_subregion_dangling_deref() {
   const char *p = nullptr;
   {
     struct MyBuffer tmp_buffer = {};
     p = tmp_buffer.buffer;
   }
   // expected-note at -1 {{'tmp_buffer.buffer[0]' is destroyed here}}
-  char c = *p;
+  return *p; 
   // expected-warning at -1 {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}}
   // expected-note at -2    {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}}
-  (void)c;
 }
 
 void opaque(const char *);
@@ -144,13 +146,12 @@ void passing_dangling_to_call() {
   // expected-note at -2    {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}}
 }
 
-void member_subregion_alive_deref() {
+char member_subregion_alive_deref() {
   {
     struct MyBuffer tmp_buffer = {};
     const char *p = tmp_buffer.buffer;
     opaque(p); //   no-warning
-    char c = *p; // no-warning
-    (void)c;
+    return *p; //   no-warning
   }
 }
 
@@ -165,3 +166,51 @@ void arr_elem_subreg_dangling_deref() {
   // expected-warning at -1 {{Use of 'local_arr[1]' after its lifetime ended}}
   // expected-note at -2    {{Use of 'local_arr[1]' after its lifetime ended}}
 }
+
+char member_array_elem__dangling_deref() {
+  const char *p = nullptr;
+  {
+    struct MyBuffer tmp_buffer = {};
+    p = tmp_buffer.buffer + 3;
+  }
+  // expected-note at -1 {{'tmp_buffer.buffer[3]' is destroyed here}}
+  return *p;
+  // expected-warning at -1 {{Use of 'tmp_buffer.buffer[3]' after its lifetime ended}}
+  // expected-note at -2    {{Use of 'tmp_buffer.buffer[3]' after its lifetime ended}}
+}
+
+int struct_field_dangling_deref() {
+  int *p = nullptr;
+  {
+    struct MyStruct s = {};
+    p = &s.x;
+  }
+  // expected-note at -1 {{'s.x' is destroyed here}}
+  return *p;
+  // expected-warning at -1 {{Use of 's.x' after its lifetime ended}}
+  // expected-note at -2    {{Use of 's.x' after its lifetime ended}}
+}
+
+int struct_array_element_dangling_deref() {
+  int *p = nullptr;
+  {
+    struct MyStruct arr[4] = {};
+    p = &arr[2].x;
+  }
+  // expected-note at -1 {{'arr[2].x' is destroyed here}}
+  return *p;
+  // expected-warning at -1 {{Use of 'arr[2].x' after its lifetime ended}}
+  // expected-note at -2    {{Use of 'arr[2].x' after its lifetime ended}}
+}
+
+int nested_field_dangling_deref() {
+  int *p = nullptr;
+  {
+    struct Outer o = {};
+    p = &o.inner.x;
+  }
+  // expected-note at -1 {{'o.inner.x' is destroyed here}}
+  return *p;
+  // expected-warning at -1 {{Use of 'o.inner.x' after its lifetime ended}}
+  // expected-note at -2    {{Use of 'o.inner.x' after its lifetime ended}}
+}



More information about the cfe-commits mailing list