https://github.com/benedekaibas created https://github.com/llvm/llvm-project/pull/211818
Improve dangling value tracking in the `DanglingPtrDeref` checker by adding `trackExpressionValue`. The report with this change now tracks the dangling value and shows where the value originated from. Currently the checker only points at the destruction and use sites which does not explain the full picture for the user. >From 28b8e2424a5e3c947765457f2de5f2bf937d100b Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Thu, 23 Jul 2026 15:01:21 +0200 Subject: [PATCH 1/3] [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@-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@-1 {{'tmp_buffer.buffer[0]' is destroyed here}} + char c = *p; + // expected-warning@-1 {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}} + // expected-note@-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@-1 {{'tmp_buffer.buffer[0]' is destroyed here}} + opaque(p); + // expected-warning@-1 {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}} + // expected-note@-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@-1 {{'local_arr[1]' is destroyed here}} + *ptr = 7; + // expected-warning@-1 {{Use of 'local_arr[1]' after its lifetime ended}} + // expected-note@-2 {{Use of 'local_arr[1]' after its lifetime ended}} +} >From 2cdca2d740aca62e6b947b6afffb22e1ba3b7f57 Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Thu, 23 Jul 2026 23:55:54 +0200 Subject: [PATCH 2/3] 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@-1 {{'tmp_buffer.buffer[0]' is destroyed here}} - char c = *p; + return *p; // expected-warning@-1 {{Use of 'tmp_buffer.buffer[0]' after its lifetime ended}} // expected-note@-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@-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@-1 {{Use of 'local_arr[1]' after its lifetime ended}} // expected-note@-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@-1 {{'tmp_buffer.buffer[3]' is destroyed here}} + return *p; + // expected-warning@-1 {{Use of 'tmp_buffer.buffer[3]' after its lifetime ended}} + // expected-note@-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@-1 {{'s.x' is destroyed here}} + return *p; + // expected-warning@-1 {{Use of 's.x' after its lifetime ended}} + // expected-note@-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@-1 {{'arr[2].x' is destroyed here}} + return *p; + // expected-warning@-1 {{Use of 'arr[2].x' after its lifetime ended}} + // expected-note@-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@-1 {{'o.inner.x' is destroyed here}} + return *p; + // expected-warning@-1 {{Use of 'o.inner.x' after its lifetime ended}} + // expected-note@-2 {{Use of 'o.inner.x' after its lifetime ended}} +} >From 5a751fe8482780d9949544ac962fdd4877f7fa1c Mon Sep 17 00:00:00 2001 From: benedekaibas <[email protected]> Date: Fri, 24 Jul 2026 16:53:31 +0200 Subject: [PATCH 3/3] [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 d1b9f0fea4044..16550bb7007e1 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 654b23d0fea64..890ab279a54aa 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@-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@-1 {{'n' is destroyed here}} // expected-note@-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@-1 {{'num' is destroyed here}} @@ -75,8 +75,8 @@ void test_case_seven() { // 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; + 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@-1 {{'i' is destroyed here}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
