Author: Benedek Kaibas
Date: 2026-07-30T17:20:54+02:00
New Revision: e696fb5963f9c9f1e50d1da1bab6430c58ac3da1

URL: 
https://github.com/llvm/llvm-project/commit/e696fb5963f9c9f1e50d1da1bab6430c58ac3da1
DIFF: 
https://github.com/llvm/llvm-project/commit/e696fb5963f9c9f1e50d1da1bab6430c58ac3da1.diff

LOG:  [analyzer] Improve UseAfterLifetimeEnd checker's diagnostic with 
descriptive names and value tracking (#212158)

Currently the `UseAfterLifetimeEnd` checker used `getString()` for
constructing error message. However, `getString()` is a debug only
stringification and should not be used for emitting reports to the
users. That is why I have changed it to `getDescriptiveName()` and also
implemented the `getRegionName`(#211552) function to return the region's
descriptive name. The `getRegionName()` function got also moved to the
modeling checker since both of the reporting checkers consume it
(#211818). This PR also uses the `trackStoredValue()` for value tracking
path notes, so the report points at where the value's source came from.

Added: 
    

Modified: 
    clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
    clang/lib/StaticAnalyzer/Checkers/UseAfterLifetimeEnd.cpp
    clang/test/Analysis/lifetime-bound.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp 
b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
index 9e99f81985fb3..4b6d76a09575a 100644
--- a/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/LifetimeModeling.cpp
@@ -250,8 +250,9 @@ void DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf(
 
     llvm::SmallString<128> Str;
     llvm::raw_svector_ostream OS(Str);
-    OS << " Origin " << ArgSVal << " bound to ";
-    llvm::interleaveComma(RegionNames, OS);
+    OS << " Origin '" << ArgSVal << "' bound to ";
+    llvm::interleaveComma(RegionNames, OS,
+                          [&](StringRef Name) { OS << "'" << Name << "'"; });
     C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), 
N));
   }
 }

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..5ed7ea365b236 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,19 @@ 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}}
 }
-


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

Reply via email to