[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