https://github.com/benedekaibas created 
https://github.com/llvm/llvm-project/pull/212254

During the development of the lifetime checkers I had many important test cases 
saved locally and were never imported to the test files of the checkers. This 
PR adds those missing test cases to the test files. By adding these test cases 
it helps tracking the state of the lifetime checkers and what improvements they 
need in order to have better coverage and reduce the false positive rate.

*AI policy*: During the development I have consulted with claude sonnet 5 to 
list me test case scenarios it thinks are useful and then reviewed its feedback 
and implemented/wrote those test cases by myself. Since some of the test cases 
are edge cases I wanted to make sure the implementation is on the right track.

>From 1025b0c91d2bf411a5fa27357ee61cd2e6c7b31e Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Fri, 24 Jul 2026 16:53:31 +0200
Subject: [PATCH 1/7] [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 = &num;
+    int num = 5; // expected-note {{'num' initialized to 5}}
+    ptr = &num; // 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 = &num;
   }
   // 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}}

>From 415befa8f27112f0152f0adf4340d1cd6fd1a321 Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Sun, 26 Jul 2026 18:36:34 +0200
Subject: [PATCH 2/7] 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 = &num;
+    int num = 5; // expected-note {{'num' initialized to 5}}
+    ptr = &num; // expected-note  {{Value assigned to 'ptr'}}
   }
   // expected-note@-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 = &num;
   }
   // expected-note@-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@-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@-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@-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@-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@-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@-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@-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@-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@-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@-1 {{'tmp_buffer.buffer[1]' is destroyed here}}

>From c6b5fb0873b33217fcf485be0f4288a7e26ca84c Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Sun, 26 Jul 2026 18:53:46 +0200
Subject: [PATCH 3/7] 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 <[email protected]>
Date: Sun, 26 Jul 2026 23:46:45 +0200
Subject: [PATCH 4/7] 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 <[email protected]>
Date: Sun, 26 Jul 2026 23:58:48 +0200
Subject: [PATCH 5/7] 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@-1 {{Use of 'tmp_buffer.buffer[1]' after its lifetime 
ended}}
   // expected-note@-2    {{Use of 'tmp_buffer.buffer[1]' after its lifetime 
ended}}
 }
+

>From fc6cf144525b0ae5be9d15b739c89710153f6c96 Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Mon, 27 Jul 2026 01:47:57 +0200
Subject: [PATCH 6/7] [analyzer] Improve UseAfterLifetimeEnd checker's
 diagnostic with descriptive names and value tracking

---
 .../Checkers/UseAfterLifetimeEnd.cpp          | 12 ++-
 clang/test/Analysis/lifetime-bound.cpp        | 94 +++++++++++++------
 2 files changed, 70 insertions(+), 36 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp 
b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
index 6a2933900c01f..0f320eb910930 100644
--- a/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
@@ -1,5 +1,6 @@
 #include "LifetimeModeling.h"
 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
 #include "clang/StaticAnalyzer/Core/Checker.h"
 
 using namespace clang;
@@ -8,7 +9,7 @@ using namespace ento;
 namespace {
 class UseAfterLifetimeEnd : public Checker<check::EndFunction> {
 public:
-  void reportDanglingSource(const MemRegion *Source, ExplodedNode *N,
+  void reportDanglingSource(const MemRegion *Source, SVal Val, ExplodedNode *N,
                             CheckerContext &C) const;
   void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
   const BugType BugMsg{this, "UseAfterLifetimeEnd", "LifetimeBound"};
@@ -38,18 +39,19 @@ void UseAfterLifetimeEnd::checkEndFunction(const ReturnStmt 
*RS,
   if (ExplodedNode *N =
           C.generateNonFatalErrorNode(State, C.getPredecessor())) {
     for (const MemRegion *R : RetValRegion)
-      reportDanglingSource(R, N, C);
+      reportDanglingSource(R, RetVal, N, C);
   }
 }
 
 void UseAfterLifetimeEnd::reportDanglingSource(const MemRegion *Source,
-                                               ExplodedNode *N,
+                                               SVal RetVal, ExplodedNode *N,
                                                CheckerContext &C) const {
   auto BR = std::make_unique<PathSensitiveBugReport>(
       BugMsg,
-      (llvm::Twine("Returning value bound to '") + Source->getString() +
-       "' that will go out of scope"),
+      (llvm::Twine("Returning value bound to ") +
+       lifetime_modeling::getRegionName(Source) + " that will go out of 
scope"),
       N);
+  bugreporter::trackStoredValue(RetVal, Source, *BR);
   C.emitReport(std::move(BR));
 }
 
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
index 4920a3c928fb8..f9c3ddf524ac0 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -1,8 +1,7 @@
 // RUN: %clang_analyze_cc1 
-analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugLifetimeModeling
 \
-// RUN:   -analyzer-config cfg-lifetime=true -verify %s
+// RUN:   -analyzer-config cfg-lifetime=true -analyzer-output=text -verify %s
 // RUN: %clang_analyze_cc1 
-analyzer-checker=core,alpha.cplusplus.UseAfterLifetimeEnd,debug.DebugLifetimeModeling
 \
-// RUN:   -analyzer-config c++-container-inlining=false -analyzer-config 
cfg-lifetime=true -verify %s
-
+// RUN:   -analyzer-config c++-container-inlining=false -analyzer-config 
cfg-lifetime=true -analyzer-output=text -verify %s
 struct A {};
 
 void clang_analyzer_dumpLifetimeOriginsOf(int*);
@@ -21,7 +20,9 @@ void caller() {
   int v = 0;
   X obj;
   int &r = obj.choose(v);
-  clang_analyzer_dumpLifetimeOriginsOf(r); // expected-warning {{Origin &v 
bound to v}}
+  clang_analyzer_dumpLifetimeOriginsOf(r);
+  // expected-warning@-1 {{Origin &v bound to v}}
+  // expected-note@-2    {{Origin &v bound to v}}
 }
 
 // Obj ref type function return annotated case.
@@ -34,7 +35,9 @@ void caller_two() {
   // Return statement is annotated case.
   Y y;
   A &f = y.getA();
-  clang_analyzer_dumpLifetimeOriginsOf(f); // expected-warning {{Origin &y.a 
bound to y}}
+  clang_analyzer_dumpLifetimeOriginsOf(f);
+  // expected-warning@-1 {{Origin &y.a bound to y}}
+  // expected-note@-2    {{Origin &y.a bound to y}}
 }
 
 // Obj ptr type function return annotated case.
@@ -46,7 +49,9 @@ struct Z {
 void caller_three() {
   Z z;
   A *func = z.getA();
-  clang_analyzer_dumpLifetimeOriginsOf(func); // expected-warning {{Origin 
&z.a bound to z}}
+  clang_analyzer_dumpLifetimeOriginsOf(func);
+  // expected-warning@-1 {{Origin &z.a bound to z}}
+  // expected-note@-2    {{Origin &z.a bound to z}}
 }
 
 // Free function with annotated param and ref return.
@@ -55,7 +60,9 @@ int &foo(int &num [[clang::lifetimebound]]) { return num; }
 void caller_four() {
   int num = 5;
   int &s = foo(num);
-  clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &num 
bound to num}}
+  clang_analyzer_dumpLifetimeOriginsOf(s);
+  // expected-warning@-1 {{Origin &num bound to num}}
+  // expected-note@-2    {{Origin &num bound to num}}
 }
 
 // Free function with annotated param and ptr return.
@@ -66,7 +73,9 @@ void caller_five() {
   int *n_ptr = &n;
   int *s = boo(n_ptr);
 
-  clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &n 
bound to n}}
+  clang_analyzer_dumpLifetimeOriginsOf(s);
+  // expected-warning@-1 {{Origin &n bound to n}}
+  // expected-note@-2  {{Origin &n bound to n}}
 }
 
 // Free function with both annotated and non-annotated parameters.
@@ -77,12 +86,12 @@ void caller_six() {
   int odd = 55;
   int &s = fn(even, odd);
 
-  clang_analyzer_dumpLifetimeOriginsOf(s); // expected-warning {{Origin &odd 
bound to odd}}
+  clang_analyzer_dumpLifetimeOriginsOf(s);
+  // expected-warning@-1 {{Origin &odd bound to odd}}
+  // expected-note@-2    {{Origin &odd bound to odd}}
 }
 
-
-
-// These are the cases when the result of function calls are SymbolRefs.
+// Test cases for testing when the result of function calls are SymbolRefs.
 
 // Function returns ptr and has an annotated parameter.
 int *foo(int *n [[clang::lifetimebound]]);
@@ -92,7 +101,9 @@ void caller_seven() {
   int *y_ptr = &y;
   auto *bind = foo(y_ptr);
 
-  clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin 
&SymRegion{{.*}} bound to y}}
+  clang_analyzer_dumpLifetimeOriginsOf(bind);
+  // expected-warning-re@-1 {{Origin &SymRegion{{.*}} bound to y}}
+  // expected-note-re@-2    {{Origin &SymRegion{{.*}} bound to y}} 
 }
 
 // Function returns a reference and has an annotated parameter.
@@ -102,7 +113,9 @@ void caller_eight() {
   int f = 15;
   auto &bind = func(f);
 
-  clang_analyzer_dumpLifetimeOriginsOf(bind); // expected-warning-re {{Origin 
&SymRegion{{.*}} bound to f}}
+  clang_analyzer_dumpLifetimeOriginsOf(bind);
+  // expected-warning-re@-1 {{Origin &SymRegion{{.*}} bound to f}}
+  // expected-note-re@-2    {{Origin &SymRegion{{.*}} bound to f}}
 }
 
 // Function returns a reference and has two annotated parameters.
@@ -113,7 +126,9 @@ void caller_nine() {
   int second_num = 2;
   int &numbers = f(first_num, second_num);
 
-  clang_analyzer_dumpLifetimeOriginsOf(numbers); // expected-warning-re 
{{Origin &SymRegion{{.*}} bound to first_num, second_num}}
+  clang_analyzer_dumpLifetimeOriginsOf(numbers);
+  // expected-warning-re@-1 {{Origin &SymRegion{{.*}} bound to first_num, 
second_num}}
+  // expected-note-re@-2    {{Origin &SymRegion{{.*}} bound to first_num, 
second_num}}
 }
 
 struct View {
@@ -139,16 +154,19 @@ int *test_func(int *p [[clang::lifetimebound]]);
 
 
 int *direct_return() {
-  int i = 5;
+  int i = 5; //expected-note {{'i' initialized here}}
   return test_func(&i);
   // expected-warning@-1 {{Returning value bound to 'i' that will go out of 
scope}}
   // expected-warning@-2 {{address of stack memory associated with local 
variable 'i' returned}}
+  // expected-note@-3    {{Returning value bound to 'i' that will go out of 
scope}}
 }
 
 int *variable_return() {
-  int y = 5;
+  int y = 5; // expected-note {{'y' initialized here}}
   int *p = test_func(&y);
-  return p; // expected-warning {{Returning value bound to 'y' that will go 
out of scope}}
+  return p;
+  // expected-warning@-1 {{Returning value bound to 'y' that will go out of 
scope}}
+  // expected-note@-2    {{Returning value bound to 'y' that will go out of 
scope}}
 }
 
 int *borrow_from_caller(int *b [[clang::lifetimebound]]) {
@@ -173,11 +191,15 @@ int &multi_param_test_ref(int &a 
[[clang::lifetimebound]], int &b [[clang::lifet
 // Return value bound to annotated parameters (two dangling sources).
 int &dangling_sources_ref() {
   int x = 1, y = 2;
+  // expected-note@-1 {{'x' initialized here}}
+  // expected-note@-2 {{'y' initialized here}}
   return multi_param_test_ref(x, y);
   // expected-warning@-1 {{Returning value bound to 'x' that will go out of 
scope}}
-  // expected-warning@-2 {{Returning value bound to 'y' that will go out of 
scope}}
-  // expected-warning@-3 {{reference to stack memory associated with local 
variable 'x' returned}}
-  // expected-warning@-4 {{reference to stack memory associated with local 
variable 'y' returned}}
+  // expected-note@-2    {{Returning value bound to 'x' that will go out of 
scope}}
+  // expected-warning@-3 {{Returning value bound to 'y' that will go out of 
scope}}
+  // expected-note@-4    {{Returning value bound to 'y' that will go out of 
scope}}
+  // expected-warning@-5 {{reference to stack memory associated with local 
variable 'x' returned}}
+  // expected-warning@-6 {{reference to stack memory associated with local 
variable 'y' returned}}
 }
 
 // Return value bound to annotated parameters (no dangling sources).
@@ -187,10 +209,11 @@ int &no_dangling_sources_ref(int &a 
[[clang::lifetimebound]], int &b [[clang::li
 
 // Return value bound to annotated parameters (one dangling source).
 int &one_dangling_source_ref(int &a [[clang::lifetimebound]]) {
-  int x = 1;
+  int x = 1; // expected-note {{'x' initialized here}}
   return multi_param_test_ref(a, x);
   // expected-warning@-1 {{Returning value bound to 'x' that will go out of 
scope}}
-  // expected-warning@-2 {{reference to stack memory associated with local 
variable 'x' returned}}
+  // expected-note@-2    {{Returning value bound to 'x' that will go out of 
scope}}
+  // expected-warning@-3 {{reference to stack memory associated with local 
variable 'x' returned}}
 }
 
 int *multi_param_test_ptr(int *a [[clang::lifetimebound]], int *b 
[[clang::lifetimebound]]);
@@ -198,11 +221,15 @@ int *multi_param_test_ptr(int *a 
[[clang::lifetimebound]], int *b [[clang::lifet
 // Return value bound to annotated parameters (two dangling sources).
 int *dangling_sources_ptr() {
   int x = 1, y = 2;
+  // expected-note@-1 {{'x' initialized here}}
+  // expected-note@-2 {{'y' initialized here}}
   int *x_ptr = &x;
   int *y_ptr = &y;
   return multi_param_test_ptr(x_ptr, y_ptr);
   // expected-warning@-1 {{Returning value bound to 'x' that will go out of 
scope}}
-  // expected-warning@-2 {{Returning value bound to 'y' that will go out of 
scope}}
+  // expected-note@-2    {{Returning value bound to 'x' that will go out of 
scope}}
+  // expected-warning@-3 {{Returning value bound to 'y' that will go out of 
scope}}
+  // expected-note@-4    {{Returning value bound to 'y' that will go out of 
scope}}
 }
 
 // Return value bound to annotated parameters (no dangling sources).
@@ -212,9 +239,11 @@ int *no_dangling_sources_ptr(int *a 
[[clang::lifetimebound]], int *b [[clang::li
 
 // Return value bound to annotated parameters (one dangling source).
 int *one_dangling_source_ptr(int *a [[clang::lifetimebound]]) {
-  int x = 1;
+  int x = 1; // expected-note {{'x' initialized here}} 
   int *x_ptr = &x;
-  return multi_param_test_ptr(a, x_ptr); // expected-warning {{Returning value 
bound to 'x' that will go out of scope}}
+  return multi_param_test_ptr(a, x_ptr);
+  // expected-warning@-1 {{Returning value bound to 'x' that will go out of 
scope}}
+  // expected-note@-2    {{Returning value bound to 'x' that will go out of 
scope}}
 }
 
 struct S {
@@ -235,17 +264,20 @@ void outer() {
 }
 
 int *danglingLocal() {
-  S s;
-  return s.get();
+  S s; // expected-note {{'s' initialized here}}
+  return s.get(); // expected-note {{Returning value bound to 's' that will go 
out of scope}}
   // expected-warning@-1 {{Returning value bound to 's' that will go out of 
scope}}
   // expected-warning@-2 {{Address of stack memory associated with local 
variable 's' returned}}
-  // expected-warning@-3 {{address of stack memory associated with local 
variable 's' returned}}
+  // expected-note@-3    {{Address of stack memory associated with local 
variable 's' returned to caller}}
+  // expected-warning@-4 {{address of stack memory associated with local 
variable 's' returned}}
 }
 
 int *danglingParam(S param) {
   return param.get();
   // expected-warning@-1 {{Returning value bound to 'param' that will go out 
of scope}}
-  // expected-warning@-2 {{Address of stack memory associated with local 
variable 'param' returned}}
-  // expected-warning@-3 {{address of stack memory associated with parameter 
'param' returned}}
+  // expected-note@-2    {{Returning value bound to 'param' that will go out 
of scope}}
+  // expected-warning@-3 {{Address of stack memory associated with local 
variable 'param' returned}}
+  // expected-note@-4    {{Address of stack memory associated with local 
variable 'param' returned to caller}}
+  // expected-warning@-5 {{address of stack memory associated with parameter 
'param' returned}}
 }
 

>From 96b882b24767af5107a76f722018d0a4f7c1a09c Mon Sep 17 00:00:00 2001
From: benedekaibas <[email protected]>
Date: Mon, 27 Jul 2026 15:39:09 +0200
Subject: [PATCH 7/7] [analyzer][NFC] Add test cases for the lifetime test
 suite

---
 clang/test/Analysis/dangling-ptr-deref.cpp | 18 +++++
 clang/test/Analysis/lifetime-bound.cpp     | 79 ++++++++++++++++++++++
 2 files changed, 97 insertions(+)

diff --git a/clang/test/Analysis/dangling-ptr-deref.cpp 
b/clang/test/Analysis/dangling-ptr-deref.cpp
index f210195762770..bdae67bb91539 100644
--- a/clang/test/Analysis/dangling-ptr-deref.cpp
+++ b/clang/test/Analysis/dangling-ptr-deref.cpp
@@ -272,3 +272,21 @@ char member_subregion_dangling_deref_increment() {
   // expected-note@-2    {{Use of 'tmp_buffer.buffer[1]' after its lifetime 
ended}}
 }
 
+void chain() {
+  int *ptr = nullptr;
+  {
+    int local = 5;
+    int *a = &local; // expected-note {{'a' initialized here}}
+    int *b = a;
+    int *c = b;
+    ptr = c;
+  }
+  *ptr = 6;
+  // expected-warning@-1 {{Use of 'local' after its lifetime ended}}
+  // expected-note@-2    {{Use of 'local' after its lifetime ended}}
+  // expected-note@-9    {{'local' initialized to 5}}
+  // expected-note@-8    {{'b' initialized to the value of 'a'}}
+  // expected-note@-8    {{'c' initialized to the value of 'b'}}
+  // expected-note@-8    {{The value of 'c' is assigned to 'ptr'}}
+  // expected-note@-8    {{'local' is destroyed here}}
+}
diff --git a/clang/test/Analysis/lifetime-bound.cpp 
b/clang/test/Analysis/lifetime-bound.cpp
index f9c3ddf524ac0..170f8e989e054 100644
--- a/clang/test/Analysis/lifetime-bound.cpp
+++ b/clang/test/Analysis/lifetime-bound.cpp
@@ -4,6 +4,31 @@
 // RUN:   -analyzer-config c++-container-inlining=false -analyzer-config 
cfg-lifetime=true -analyzer-output=text -verify %s
 struct A {};
 
+struct Pair {
+  int a;
+  int b;
+};
+
+struct Base {
+  int x;
+};
+
+struct Derived : Base {
+  int y;
+};
+
+struct Inner {
+  int val;
+};
+
+struct Outer {
+  Inner inner;
+};
+
+struct Buffer {
+  int arr[4];
+};
+
 void clang_analyzer_dumpLifetimeOriginsOf(int*);
 void clang_analyzer_dumpLifetimeOriginsOf(int&);
 void clang_analyzer_dumpLifetimeOriginsOf(A*);
@@ -281,3 +306,57 @@ int *danglingParam(S param) {
   // expected-warning@-5 {{address of stack memory associated with parameter 
'param' returned}}
 }
 
+int *getFieldPtr(Pair &p [[clang::lifetimebound]]) { return &p.a; }
+
+int *field_subobject_dangling() {
+  Pair pair{3, 5}; // expected-note {{'pair' initialized here}}
+  return getFieldPtr(pair);
+  // expected-warning@-1 {{Returning value bound to 'pair' that will go out of 
scope}}
+  // expected-note@-2    {{Returning value bound to 'pair' that will go out of 
scope}}
+  // expected-warning@-3 {{Address of stack memory associated with local 
variable 'pair' returned to caller}}
+  // expected-note@-4    {{Address of stack memory associated with local 
variable 'pair' returned to caller}}
+  // expected-warning@-5    {{address of stack memory associated with local 
variable 'pair' returned}}
+}
+
+int *getBasePtr(Derived &d [[clang::lifetimebound]]) {
+  return &static_cast<Base &>(d).x;
+}
+
+int *base_subobject_dangling() {
+  Derived derived{}; // expected-note {{'derived' initialized here}}
+  return getBasePtr(derived);
+  // expected-warning@-1 {{Returning value bound to 'derived' that will go out 
of scope}}
+  // expected-note@-2    {{Returning value bound to 'derived' that will go out 
of scope}}
+  // expected-warning@-3 {{Address of stack memory associated with local 
variable 'derived' returned to caller}}
+  // expected-note@-4    {{Address of stack memory associated with local 
variable 'derived' returned to caller}}
+  // expected-warning@-5    {{address of stack memory associated with local 
variable 'derived' returned}}
+}
+
+int *getNestedFieldPtr(Outer &o [[clang::lifetimebound]]) {
+  return &o.inner.val;
+}
+
+int *nested_subobject_dangling() {
+  Outer outer{}; // expected-note {{'outer' initialized here}}
+  return getNestedFieldPtr(outer);
+  // expected-warning@-1 {{Returning value bound to 'outer' that will go out 
of scope}}
+  // expected-note@-2    {{Returning value bound to 'outer' that will go out 
of scope}} 
+  // expected-warning@-3 {{Address of stack memory associated with local 
variable 'outer' returned to caller}}
+  // expected-note@-4    {{Address of stack memory associated with local 
variable 'outer' returned to caller}}
+  // expected-warning@-5 {{address of stack memory associated with local 
variable 'outer' returned}}
+}
+
+int *getArrayElementPtr(Buffer &b [[clang::lifetimebound]]) {
+  return &b.arr[0];
+}
+
+int *array_member_subobject_dangling() {
+  Buffer buf{}; // expected-note {{'buf' initialized here}}
+  return getArrayElementPtr(buf);
+  // expected-warning@-1 {{Returning value bound to 'buf' that will go out of 
scope}}
+  // expected-note@-2    {{Returning value bound to 'buf' that will go out of 
scope}}
+  // expected-warning@-3 {{Address of stack memory associated with local 
variable 'buf' returned to caller}}
+  // expected-note@-4    {{Address of stack memory associated with local 
variable 'buf' returned to caller}}
+  // expected-warning@-5 {{address of stack memory associated with local 
variable 'buf' returned}}
+}
+

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to