[clang] [analyzer] Improve dangling value tracking in DanglingPtrDeref (PR #211818)
Benedek Kaibas via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 27 03:34:02 PDT 2026
https://github.com/benedekaibas updated https://github.com/llvm/llvm-project/pull/211818
>From 1025b0c91d2bf411a5fa27357ee61cd2e6c7b31e Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Fri, 24 Jul 2026 16:53:31 +0200
Subject: [PATCH 1/6] [analyzer] Improve dangling value tracking in
DanglingPtrDeref
---
.../Checkers/DanglingPtrDeref.cpp | 11 ++++++-----
clang/test/Analysis/dangling-ptr-deref.cpp | 18 +++++++++---------
2 files changed, 15 insertions(+), 14 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
index 9b4b1056c8ee6..51a1cb891f6cc 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
@@ -15,8 +15,8 @@ class DanglingPtrDeref : public Checker<check::Location, check::PostCall> {
void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
CheckerContext &C) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N,
- CheckerContext &C) const;
+ void reportUseAfterScope(const MemRegion *Region, const Stmt *S,
+ ExplodedNode *N, CheckerContext &C) const;
const BugType BugMsg{this, "ReportDanglingPtrDeref", "LifetimeBound"};
};
@@ -45,7 +45,7 @@ void DanglingPtrDeref::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
if (const MemRegion *LocRegion = Loc.getAsRegion()) {
if (lifetime_modeling::isDeallocated(State, LocRegion)) {
if (ExplodedNode *N = C.generateNonFatalErrorNode(State))
- reportUseAfterScope(LocRegion, N, C);
+ reportUseAfterScope(LocRegion, S, N, C);
}
}
}
@@ -62,7 +62,7 @@ void DanglingPtrDeref::checkPostCall(const CallEvent &Call,
if (const MemRegion *ArgRegion = Call.getArgSVal(Idx).getAsRegion())
if (lifetime_modeling::isDeallocated(State, ArgRegion))
if (ExplodedNode *N = C.generateNonFatalErrorNode())
- reportUseAfterScope(ArgRegion, N, C);
+ reportUseAfterScope(ArgRegion, Call.getArgExpr(Idx), N, C);
}
}
@@ -75,7 +75,7 @@ static std::string getRegionName(const MemRegion *Reg) {
}
void DanglingPtrDeref::reportUseAfterScope(const MemRegion *Region,
- ExplodedNode *N,
+ const Stmt *S, ExplodedNode *N,
CheckerContext &C) const {
auto BR = std::make_unique<PathSensitiveBugReport>(
BugMsg,
@@ -83,6 +83,7 @@ void DanglingPtrDeref::reportUseAfterScope(const MemRegion *Region,
" after its lifetime ended."),
N);
BR->addVisitor<DanglingPtrDerefBRVisitor>(Region);
+ bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *BR);
C.emitReport(std::move(BR));
}
diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp
index fa9dae41421bc..652accd1f359a 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -4,8 +4,8 @@
void test_case_one() {
int *ptr = nullptr;
{
- int num = 5;
- ptr = #
+ int num = 5; // expected-note {{'num' initialized to 5}}
+ ptr = # // expected-note {{Value assigned to 'ptr'}}
}
// expected-note at -1 {{'num' is destroyed here}}
*ptr = 6;
@@ -17,10 +17,10 @@ void test_case_two() {
int *ptr_one = nullptr;
int *ptr_two = nullptr;
{
- int n = 1;
- int m = 2;
- ptr_one = &n;
- ptr_two = &m;
+ int n = 1; // expected-note {{'n' initialized to 1}}
+ int m = 2; // expected-note {{'m' initialized to 2}}
+ ptr_one = &n; // expected-note {{Value assigned to 'ptr_one'}}
+ ptr_two = &m; // expected-note {{Value assigned to 'ptr_two'}}
}
// expected-note at -1 {{'n' is destroyed here}}
// expected-note at -2 {{'m' is destroyed here}}
@@ -45,7 +45,7 @@ void test_case_three() {
void test_case_four() {
int *ptr = nullptr;
{
- int num = 5;
+ int num = 5; // expected-note {{'num' initialized to 5}}
ptr = #
}
// expected-note at -1 {{'num' is destroyed here}}
@@ -75,8 +75,8 @@ void test_case_seven() {
// 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;
+ for (int i = 0; i < 10; ++i) { // expected-note {{'i' initialized to 0}}
+ ptr = &i; // expected-note {{Value assigned to 'ptr'}}
escape(ptr);
}
// expected-note at -1 {{'i' is destroyed here}}
>From 415befa8f27112f0152f0adf4340d1cd6fd1a321 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sun, 26 Jul 2026 18:36:34 +0200
Subject: [PATCH 2/6] Add notes to the test suite.
---
clang/test/Analysis/dangling-ptr-deref.cpp | 28 +++++++++++-----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp
index 652accd1f359a..d7cdae75a1d95 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -88,8 +88,8 @@ void test_case_seven() {
void passing_dangling_ptr_to_opaque_func() {
int *ptr = nullptr;
{
- int num = 5;
- ptr = #
+ int num = 5; // expected-note {{'num' initialized to 5}}
+ ptr = # // expected-note {{Value assigned to 'ptr'}}
}
// expected-note at -1 {{'num' is destroyed here}}
escape(ptr);
@@ -104,7 +104,7 @@ int deref_param(int *p) { return *p; }
void inlined_callee_single_report() {
int *ptr = nullptr;
{
- int num = 5;
+ int num = 5; // expected-note {{'num' initialized to 5}}
ptr = #
}
// expected-note at -1 {{'num' is destroyed here}}
@@ -131,7 +131,7 @@ struct A {
char member_subregion_dangling_deref() {
const char *p = nullptr;
{
- MyBuffer tmp_buffer = {};
+ MyBuffer tmp_buffer = {}; // expected-note {{Initializing to 0}}
p = tmp_buffer.buffer;
}
// expected-note at -1 {{'tmp_buffer.buffer[0]' is destroyed here}}
@@ -147,7 +147,7 @@ void passing_dangling_to_call() {
const char *p = nullptr;
{
MyBuffer tmp_buffer = {};
- p = tmp_buffer.buffer;
+ p = tmp_buffer.buffer; // expected-note {{Value assigned to 'p'}}
}
// expected-note at -1 {{'tmp_buffer.buffer[0]' is destroyed here}}
opaque(p);
@@ -178,8 +178,8 @@ char member_subregion_alive_deref_pp() {
void arr_elem_subreg_dangling_deref() {
int *ptr = nullptr;
{
- int local_arr[4];
- ptr = &local_arr[1];
+ int local_arr[4]; // expected-note {{'local_arr' declared without an initial value}}
+ ptr = &local_arr[1]; // expected-note {{Value assigned to 'ptr'}}
}
// expected-note at -1 {{'local_arr[1]' is destroyed here}}
*ptr = 7;
@@ -190,7 +190,7 @@ void arr_elem_subreg_dangling_deref() {
char member_array_elem_dangling_deref() {
const char *p = nullptr;
{
- MyBuffer tmp_buffer = {};
+ MyBuffer tmp_buffer = {}; // expected-note {{Initializing to 0}}
p = tmp_buffer.buffer + 3;
}
// expected-note at -1 {{'tmp_buffer.buffer[3]' is destroyed here}}
@@ -202,7 +202,7 @@ char member_array_elem_dangling_deref() {
char member_array_out_of_bounds_dangling_deref() {
const char *p = nullptr;
{
- MyBuffer tmp_buffer = {};
+ MyBuffer tmp_buffer = {}; // expected-note {{Initializing to 0}}
p = tmp_buffer.buffer + 10;
}
// expected-note at -1 {{'tmp_buffer.buffer[10]' is destroyed here}}
@@ -214,7 +214,7 @@ char member_array_out_of_bounds_dangling_deref() {
int struct_field_dangling_deref() {
int *p = nullptr;
{
- MyStruct s = {};
+ MyStruct s = {}; // expected-note {{'s.x' initialized to 0}}
p = &s.x;
}
// expected-note at -1 {{'s.x' is destroyed here}}
@@ -226,7 +226,7 @@ int struct_field_dangling_deref() {
int struct_array_element_dangling_deref() {
int *p = nullptr;
{
- MyStruct arr[4] = {};
+ MyStruct arr[4] = {}; // expected-note {{field 'x' initialized to 0}}
p = &arr[2].x;
}
// expected-note at -1 {{'arr[2].x' is destroyed here}}
@@ -238,7 +238,7 @@ int struct_array_element_dangling_deref() {
int nested_field_dangling_deref() {
int *p = nullptr;
{
- Outer o = {};
+ Outer o = {}; // expected-note {{'o.inner.x' initialized to 0}}
p = &o.inner.x;
}
// expected-note at -1 {{'o.inner.x' is destroyed here}}
@@ -250,7 +250,7 @@ int nested_field_dangling_deref() {
int nested_type_field_dangling_deref() {
int *p = nullptr;
{
- A a = {};
+ A a = {}; // expected-note {{'a.b.x' initialized to 0}}
p = &a.b.x;
}
// expected-note at -1 {{'a.b.x' is destroyed here}}
@@ -262,7 +262,7 @@ int nested_type_field_dangling_deref() {
char member_subregion_dangling_deref_increment() {
const char *p = nullptr;
{
- MyBuffer tmp_buffer = {};
+ MyBuffer tmp_buffer = {}; // expected-note {{Initializing to 0}}
p = tmp_buffer.buffer;
}
// expected-note at -1 {{'tmp_buffer.buffer[1]' is destroyed here}}
>From c6b5fb0873b33217fcf485be0f4288a7e26ca84c Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sun, 26 Jul 2026 18:53:46 +0200
Subject: [PATCH 3/6] Remove struct keyword.
---
clang/test/Analysis/dangling-ptr-deref.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp
index d7cdae75a1d95..a4d919c168e3b 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -120,7 +120,7 @@ struct MyStruct { int x; };
struct Inner { int x; };
-struct Outer { struct Inner inner; };
+struct Outer { Inner inner; };
struct A {
struct B { int x; };
>From ba419426dfb0cbfeacc2f239a4314a461cd1c5cb Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sun, 26 Jul 2026 23:46:45 +0200
Subject: [PATCH 4/6] Move getRegionName into the modeling checker.
---
.../StaticAnalyzer/Checkers/DanglingPtrDeref.cpp | 14 ++++----------
.../StaticAnalyzer/Checkers/LifetimeModeling.cpp | 8 ++++++++
.../lib/StaticAnalyzer/Checkers/LifetimeModeling.h | 4 ++++
3 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
index 51a1cb891f6cc..3daad5f131ae6 100644
--- a/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/DanglingPtrDeref.cpp
@@ -66,20 +66,12 @@ 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;
- llvm_unreachable("unhandled region");
-}
-
void DanglingPtrDeref::reportUseAfterScope(const MemRegion *Region,
const Stmt *S, ExplodedNode *N,
CheckerContext &C) const {
auto BR = std::make_unique<PathSensitiveBugReport>(
BugMsg,
- (llvm::Twine("Use of ") + getRegionName(Region) +
+ (llvm::Twine("Use of ") + lifetime_modeling::getRegionName(Region) +
" after its lifetime ended."),
N);
BR->addVisitor<DanglingPtrDerefBRVisitor>(Region);
@@ -108,7 +100,9 @@ DanglingPtrDerefBRVisitor::VisitNode(const ExplodedNode *N,
S, BRC.getSourceManager(), N->getStackFrame());
return std::make_shared<PathDiagnosticEventPiece>(
Pos,
- (getRegionName(SourceRegion) + llvm::Twine(" is destroyed here")).str(),
+ (lifetime_modeling::getRegionName(SourceRegion) +
+ llvm::Twine(" is destroyed here"))
+ .str(),
true);
}
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 765a95c008053..df8a554e43157 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -87,6 +87,14 @@ static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
return State;
}
+std::string lifetime_modeling::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;
+ llvm_unreachable("unhandled region");
+}
+
void LifetimeModeling::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
index cea2799db4b1d..1e2ef8f7810ac 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.h
@@ -15,6 +15,10 @@ getDanglingRegionsAfterReturn(SVal Source, ProgramStateRef State,
/// Returns true if the underlying MemRegion is deallocated.
bool isDeallocated(ProgramStateRef State, const MemRegion *Region);
+
+/// Returns the descriptive name of the memory region or a placeholder if a
+/// descriptive name cannot be constructed for it.
+std::string getRegionName(const MemRegion *Reg);
} // namespace clang::ento::lifetime_modeling
#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_LIFETIMEMODELING_H
>From ee2d0ffdcc3b787accfd6cc058eb8cf7d58e61fe Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Sun, 26 Jul 2026 23:58:48 +0200
Subject: [PATCH 5/6] Update dangling-ptr-deref for tracking notes.
---
clang/test/Analysis/dangling-ptr-deref.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp b/clang/test/Analysis/dangling-ptr-deref.cpp
index a4d919c168e3b..f210195762770 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -271,3 +271,4 @@ char member_subregion_dangling_deref_increment() {
// expected-warning at -1 {{Use of 'tmp_buffer.buffer[1]' after its lifetime ended}}
// expected-note at -2 {{Use of 'tmp_buffer.buffer[1]' after its lifetime ended}}
}
+
>From 8920f2b67d05c32aead6f606fdd2f0baeb2e16e2 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
Date: Mon, 27 Jul 2026 12:33:35 +0200
Subject: [PATCH 6/6] Remove llvm_unreachable since it is incorrect.
---
clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index df8a554e43157..818a22342c12a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -90,9 +90,9 @@ static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
std::string lifetime_modeling::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())
+ if (const std::string RegName = Reg->getDescriptiveName(); !RegName.empty())
return RegName;
- llvm_unreachable("unhandled region");
+ return "this region";
}
void LifetimeModeling::checkPostCall(const CallEvent &Call,
More information about the cfe-commits
mailing list