[clang] [analyzer][NFC] Add test cases for the lifetime test suite (PR #212254)

Benedek Kaibas via cfe-commits cfe-commits at lists.llvm.org
Mon Jul 27 06:44:49 PDT 2026


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 <kaibas01 at allegheny.edu>
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 = #
+    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/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 = #
+    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/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 <kaibas01 at allegheny.edu>
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 <kaibas01 at allegheny.edu>
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 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 fc6cf144525b0ae5be9d15b739c89710153f6c96 Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
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 at -1 {{Origin &v bound to v}}
+  // expected-note at -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 at -1 {{Origin &y.a bound to y}}
+  // expected-note at -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 at -1 {{Origin &z.a bound to z}}
+  // expected-note at -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 at -1 {{Origin &num bound to num}}
+  // expected-note at -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 at -1 {{Origin &n bound to n}}
+  // expected-note at -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 at -1 {{Origin &odd bound to odd}}
+  // expected-note at -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 at -1 {{Origin &SymRegion{{.*}} bound to y}}
+  // expected-note-re at -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 at -1 {{Origin &SymRegion{{.*}} bound to f}}
+  // expected-note-re at -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 at -1 {{Origin &SymRegion{{.*}} bound to first_num, second_num}}
+  // expected-note-re at -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 at -1 {{Returning value bound to 'i' that will go out of scope}}
   // expected-warning at -2 {{address of stack memory associated with local variable 'i' returned}}
+  // expected-note at -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 at -1 {{Returning value bound to 'y' that will go out of scope}}
+  // expected-note at -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 at -1 {{'x' initialized here}}
+  // expected-note at -2 {{'y' initialized here}}
   return multi_param_test_ref(x, y);
   // expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
-  // expected-warning at -2 {{Returning value bound to 'y' that will go out of scope}}
-  // expected-warning at -3 {{reference to stack memory associated with local variable 'x' returned}}
-  // expected-warning at -4 {{reference to stack memory associated with local variable 'y' returned}}
+  // expected-note at -2    {{Returning value bound to 'x' that will go out of scope}}
+  // expected-warning at -3 {{Returning value bound to 'y' that will go out of scope}}
+  // expected-note at -4    {{Returning value bound to 'y' that will go out of scope}}
+  // expected-warning at -5 {{reference to stack memory associated with local variable 'x' returned}}
+  // expected-warning at -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 at -1 {{Returning value bound to 'x' that will go out of scope}}
-  // expected-warning at -2 {{reference to stack memory associated with local variable 'x' returned}}
+  // expected-note at -2    {{Returning value bound to 'x' that will go out of scope}}
+  // expected-warning at -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 at -1 {{'x' initialized here}}
+  // expected-note at -2 {{'y' initialized here}}
   int *x_ptr = &x;
   int *y_ptr = &y;
   return multi_param_test_ptr(x_ptr, y_ptr);
   // expected-warning at -1 {{Returning value bound to 'x' that will go out of scope}}
-  // expected-warning at -2 {{Returning value bound to 'y' that will go out of scope}}
+  // expected-note at -2    {{Returning value bound to 'x' that will go out of scope}}
+  // expected-warning at -3 {{Returning value bound to 'y' that will go out of scope}}
+  // expected-note at -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 at -1 {{Returning value bound to 'x' that will go out of scope}}
+  // expected-note at -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 at -1 {{Returning value bound to 's' that will go out of scope}}
   // expected-warning at -2 {{Address of stack memory associated with local variable 's' returned}}
-  // expected-warning at -3 {{address of stack memory associated with local variable 's' returned}}
+  // expected-note at -3    {{Address of stack memory associated with local variable 's' returned to caller}}
+  // expected-warning at -4 {{address of stack memory associated with local variable 's' returned}}
 }
 
 int *danglingParam(S param) {
   return param.get();
   // expected-warning at -1 {{Returning value bound to 'param' that will go out of scope}}
-  // expected-warning at -2 {{Address of stack memory associated with local variable 'param' returned}}
-  // expected-warning at -3 {{address of stack memory associated with parameter 'param' returned}}
+  // expected-note at -2    {{Returning value bound to 'param' that will go out of scope}}
+  // expected-warning at -3 {{Address of stack memory associated with local variable 'param' returned}}
+  // expected-note at -4    {{Address of stack memory associated with local variable 'param' returned to caller}}
+  // expected-warning at -5 {{address of stack memory associated with parameter 'param' returned}}
 }
 

>From 96b882b24767af5107a76f722018d0a4f7c1a09c Mon Sep 17 00:00:00 2001
From: benedekaibas <kaibas01 at allegheny.edu>
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 at -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 at -1 {{Use of 'local' after its lifetime ended}}
+  // expected-note at -2    {{Use of 'local' after its lifetime ended}}
+  // expected-note at -9    {{'local' initialized to 5}}
+  // expected-note at -8    {{'b' initialized to the value of 'a'}}
+  // expected-note at -8    {{'c' initialized to the value of 'b'}}
+  // expected-note at -8    {{The value of 'c' is assigned to 'ptr'}}
+  // expected-note at -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 at -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 at -1 {{Returning value bound to 'pair' that will go out of scope}}
+  // expected-note at -2    {{Returning value bound to 'pair' that will go out of scope}}
+  // expected-warning at -3 {{Address of stack memory associated with local variable 'pair' returned to caller}}
+  // expected-note at -4    {{Address of stack memory associated with local variable 'pair' returned to caller}}
+  // expected-warning at -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 at -1 {{Returning value bound to 'derived' that will go out of scope}}
+  // expected-note at -2    {{Returning value bound to 'derived' that will go out of scope}}
+  // expected-warning at -3 {{Address of stack memory associated with local variable 'derived' returned to caller}}
+  // expected-note at -4    {{Address of stack memory associated with local variable 'derived' returned to caller}}
+  // expected-warning at -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 at -1 {{Returning value bound to 'outer' that will go out of scope}}
+  // expected-note at -2    {{Returning value bound to 'outer' that will go out of scope}} 
+  // expected-warning at -3 {{Address of stack memory associated with local variable 'outer' returned to caller}}
+  // expected-note at -4    {{Address of stack memory associated with local variable 'outer' returned to caller}}
+  // expected-warning at -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 at -1 {{Returning value bound to 'buf' that will go out of scope}}
+  // expected-note at -2    {{Returning value bound to 'buf' that will go out of scope}}
+  // expected-warning at -3 {{Address of stack memory associated with local variable 'buf' returned to caller}}
+  // expected-note at -4    {{Address of stack memory associated with local variable 'buf' returned to caller}}
+  // expected-warning at -5 {{address of stack memory associated with local variable 'buf' returned}}
+}
+



More information about the cfe-commits mailing list