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
