https://github.com/Xazax-hun created https://github.com/llvm/llvm-project/pull/226395
reportUseAfterReturn did not receive the aliasing chain, so a returned dangling value was reported without saying which calls or variables carried the borrow, unlike use-after-scope. Build the chain from the escaping origin with a new buildOriginFlowChain overload for escape facts. Like the UseFact overload, it drops the casts that just load the returned variable, which would otherwise repeat the "returned here" note. Assisted by: Opus 5.5 From 4be6faba889dd4f6f541c160fc2d0647a2450405 Mon Sep 17 00:00:00 2001 From: Gabor Horvath <[email protected]> Date: Fri, 25 Sep 2026 09:23:32 +0100 Subject: [PATCH] [LifetimeSafety] Report the alias chain for returned stack memory reportUseAfterReturn did not receive the aliasing chain, so a returned dangling value was reported without saying which calls or variables carried the borrow, unlike use-after-scope. Build the chain from the escaping origin with a new buildOriginFlowChain overload for escape facts. Like the UseFact overload, it drops the casts that just load the returned variable, which would otherwise repeat the "returned here" note. Assisted by: Opus 5.5 --- .../Analyses/LifetimeSafety/LifetimeSafety.h | 7 +- .../Analyses/LifetimeSafety/LoanPropagation.h | 6 ++ clang/lib/Analysis/LifetimeSafety/Checker.cpp | 8 +- .../LifetimeSafety/LoanPropagation.cpp | 23 ++++- clang/lib/Sema/SemaLifetimeSafety.h | 6 +- .../LifetimeSafety/annotation-suggestions.cpp | 2 +- .../explicit-object-param-no-crash.cpp | 4 +- clang/test/Sema/LifetimeSafety/nocfg.cpp | 48 +++++----- clang/test/Sema/LifetimeSafety/safety.cpp | 90 +++++++++---------- 9 files changed, 111 insertions(+), 83 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h index 18e5e8473e4144..6854902059e501 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h @@ -74,11 +74,12 @@ class LifetimeSafetySemaHelper { SourceLocation FreeLoc, llvm::ArrayRef<const Expr *> ExprChain) {} - // TODO: Pass the expiry location and aliasing chain like - // reportUseAfterScope. + // TODO: Report where the object was destroyed when that happens before the + // return (inner scopes, temporaries). virtual void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr, - const Expr *MovedExpr) {} + const Expr *MovedExpr, + llvm::ArrayRef<const Expr *> ExprChain) {} virtual void reportDanglingField(const Expr *IssueExpr, const FieldDecl *Field, diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h index d46712ce2b1a56..de7eca39228ca3 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h @@ -57,6 +57,12 @@ class LoanPropagationAnalysis { const LoanID TargetLoan, const CFG *Cfg) const; + /// Like the above, starting from the origin \p OEF lets escape. Empty if it + /// does not hold \p TargetLoan. + llvm::SmallVector<OriginID> buildOriginFlowChain(const OriginEscapesFact *OEF, + const LoanID TargetLoan, + const CFG *Cfg) const; + private: class Impl; std::unique_ptr<Impl> PImpl; diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index c4cc872dcd568d..30ae9961781c89 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -330,11 +330,13 @@ class LifetimeChecker { // FIXME: Diagnose invalidated return escapes separately. } else llvm_unreachable("Unhandled OriginEscapesFact type"); - } else if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF)) + } else if (const auto *RetEscape = dyn_cast<ReturnEscapeFact>(OEF)) { // Return stack address. SemaHelper->reportUseAfterReturn( - IssueExpr, RetEscape->getReturnExpr(), MovedExpr); - else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF)) { + IssueExpr, RetEscape->getReturnExpr(), MovedExpr, + getExprChain( + LoanPropagation.buildOriginFlowChain(OEF, LID, Cfg))); + } else if (const auto *FieldEscape = dyn_cast<FieldEscapeFact>(OEF)) { // Dangling field. bool IsCapturedByLambda = FactMgr.isFieldCapturedByLambda(FieldEscape->getFieldDecl()); diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 80ba7d08a3103b..df66f029a5400d 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -230,18 +230,27 @@ class AnalysisImpl for (const OriginList *Cur = UF->getUsedOrigins(); Cur; Cur = Cur->peelOuterOrigin()) if (getLoans(Cur->getOuterOriginID(), UF).contains(TargetLoan)) - return dropLoadsInUse( + return dropLeadingLoads( buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan, Cfg)); return {}; } + llvm::SmallVector<OriginID> buildOriginFlowChain(const OriginEscapesFact *OEF, + const LoanID TargetLoan, + const CFG *Cfg) const { + OriginID OID = OEF->getEscapedOriginID(); + if (!getLoans(OID, OEF).contains(TargetLoan)) + return {}; + return dropLeadingLoads(buildOriginFlowChain(OEF, OID, TargetLoan, Cfg)); + } + private: /// An expression's origin only receives loans from its subexpressions, so - /// until the chain reaches a declaration it is inside the use expression. - /// Casts there just load the used variable, so drop them. + /// until the chain reaches a declaration it is inside the expression the + /// chain starts from. Casts there just load its variable, so drop them. llvm::SmallVector<OriginID> - dropLoadsInUse(llvm::SmallVector<OriginID> Chain) const { + dropLeadingLoads(llvm::SmallVector<OriginID> Chain) const { const OriginManager &OM = FactMgr.getOriginMgr(); auto FirstDecl = llvm::find_if( Chain, [&](OriginID OID) { return !OM.getOrigin(OID).getExpr(); }); @@ -346,4 +355,10 @@ llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain( const UseFact *UF, const LoanID TargetLoan, const CFG *Cfg) const { return PImpl->buildOriginFlowChain(UF, TargetLoan, Cfg); } +llvm::SmallVector<OriginID> +LoanPropagationAnalysis::buildOriginFlowChain(const OriginEscapesFact *OEF, + const LoanID TargetLoan, + const CFG *Cfg) const { + return PImpl->buildOriginFlowChain(OEF, TargetLoan, Cfg); +} } // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 620032c27f955e..e16aea60d23195 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -158,7 +158,8 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { } void reportUseAfterReturn(const Expr *IssueExpr, const Expr *ReturnExpr, - const Expr *MovedExpr) override { + const Expr *MovedExpr, + llvm::ArrayRef<const Expr *> ExprChain) override { unsigned DiagID = MovedExpr ? diag::warn_lifetime_safety_return_stack_addr_moved : diag::warn_lifetime_safety_return_stack_addr; @@ -169,6 +170,9 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { if (MovedExpr) S.Diag(MovedExpr->getExprLoc(), diag::note_lifetime_safety_moved_here) << MovedExpr->getSourceRange(); + + reportAliasingChain(ExprChain); + S.Diag(ReturnExpr->getExprLoc(), diag::note_lifetime_safety_returned_here) << ReturnExpr->getSourceRange(); } diff --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp index 22fe5e6bddfb59..4deb6d1f8688d6 100644 --- a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp +++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp @@ -550,7 +550,7 @@ struct CaptureViewToView { CaptureViewToView test_view_to_view() { MyObj obj; View v(obj); // expected-warning {{stack memory associated with local variable 'obj' is returned}} - CaptureViewToView x(v); + CaptureViewToView x(v); // expected-note {{local variable 'v' aliases the storage of local variable 'obj'}} return x; // expected-note {{returned here}} } diff --git a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp index 71c86b9f793fb6..5fb42cd7c9258e 100644 --- a/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp +++ b/clang/test/Sema/LifetimeSafety/explicit-object-param-no-crash.cpp @@ -75,13 +75,13 @@ const int *object_arg_is_not_moved(Holder &&h [[clang::lifetimebound]]) { } const int *t1(Holder h) { - const int *ptr = h.borrow(); // expected-warning {{stack memory associated with parameter 'h' is returned}} + const int *ptr = h.borrow(); // expected-warning {{stack memory associated with parameter 'h' is returned}} expected-note {{result of call to 'borrow' aliases the storage of parameter 'h' because the implicit object parameter is marked as lifetimebound}} static_cast<Holder &&>(h).consume(); return ptr; // expected-note {{returned here}} } const int *t2(Holder h) { - const int *ptr = h.borrow(); // expected-warning {{stack memory associated with parameter 'h' is returned}} + const int *ptr = h.borrow(); // expected-warning {{stack memory associated with parameter 'h' is returned}} expected-note {{result of call to 'borrow' aliases the storage of parameter 'h' because the implicit object parameter is marked as lifetimebound}} std::move(h).consume(); return ptr; // expected-note {{returned here}} } diff --git a/clang/test/Sema/LifetimeSafety/nocfg.cpp b/clang/test/Sema/LifetimeSafety/nocfg.cpp index 7f4a58c1836dd9..6986d1d61e8978 100644 --- a/clang/test/Sema/LifetimeSafety/nocfg.cpp +++ b/clang/test/Sema/LifetimeSafety/nocfg.cpp @@ -179,7 +179,7 @@ struct LifetimeBoundCtor { }; auto lifetimebound_make_unique_single_param() { - return std::make_unique<LifetimeBoundCtor>(MyIntOwner{}); // tu-warning {{stack memory associated with temporary object is returned}} tu-note {{returned here}} + return std::make_unique<LifetimeBoundCtor>(MyIntOwner{}); // tu-warning {{stack memory associated with temporary object is returned}} tu-note {{returned here}} tu-note {{result of call to 'make_unique<LifetimeBoundCtor, MyIntOwner>' aliases the storage of temporary object because parameter 'args' is inferred as lifetimebound}} } @@ -255,14 +255,14 @@ std::string_view containerWithAnnotatedElements() { use(c2); std::vector<std::string> local; - return local.at(0); // expected-warning {{address of stack memory associated with local variable}} \ + return local.at(0); // expected-warning {{address of stack memory associated with local variable}} cfg-note {{result of call to 'at' aliases the storage of local variable 'local' because the implicit object parameter is inferred as lifetimebound}} \ // cfg-warning {{stack memory associated with local variable 'local' is returned}} cfg-note {{returned here}} } std::string_view localUniquePtr(int i) { std::unique_ptr<std::string> c1; if (i) - return *c1; // expected-warning {{address of stack memory associated with local variable}} \ + return *c1; // expected-warning {{address of stack memory associated with local variable}} cfg-note {{result of call to 'operator*' aliases the storage of local variable 'c1' because the implicit object parameter is inferred as lifetimebound}} \ // cfg-warning {{stack memory associated with local variable 'c1' is returned}} cfg-note {{returned here}} std::unique_ptr<std::string_view> c2; return *c2; // expect no-warning. @@ -271,7 +271,7 @@ std::string_view localUniquePtr(int i) { std::string_view localOptional(int i) { std::optional<std::string> o; if (i) - return o.value(); // expected-warning {{address of stack memory associated with local variable}} \ + return o.value(); // expected-warning {{address of stack memory associated with local variable}} cfg-note {{result of call to 'value' aliases the storage of local variable 'o' because the implicit object parameter is inferred as lifetimebound}} \ // cfg-warning {{stack memory associated with local variable 'o' is returned}} cfg-note {{returned here}} std::optional<std::string_view> abc; return abc.value(); // expect no warning @@ -295,14 +295,14 @@ int *danglingUniquePtrFromTemp2() { } const int& danglingRefToOptionalFromTemp3() { - return std::optional<int>().value(); // expected-warning {{returning reference to local temporary object}} \ + return std::optional<int>().value(); // expected-warning {{returning reference to local temporary object}} cfg-note {{result of call to 'value' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \ // cfg-warning {{stack memory associated with temporary object is returned}} cfg-note {{returned here}} } std::optional<std::string> getTempOptStr(); std::string_view danglingRefToOptionalFromTemp4() { - return getTempOptStr().value(); // expected-warning {{returning address of local temporary object}} \ + return getTempOptStr().value(); // expected-warning {{returning address of local temporary object}} cfg-note {{result of call to 'value' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} \ // cfg-warning {{stack memory associated with temporary object is returned}} cfg-note {{returned here}} } @@ -355,7 +355,7 @@ int &usedToBeFalsePositive(std::vector<int> &v) { int &doNotFollowReferencesForLocalOwner() { // Warning caught by CFG analysis. std::unique_ptr<int> localOwner; - int &p = *localOwner // cfg-warning {{stack memory associated with local variable 'localOwner' is returned}} + int &p = *localOwner // cfg-warning {{stack memory associated with local variable 'localOwner' is returned}} cfg-note {{result of call to 'get' aliases the storage of local variable 'localOwner' because the implicit object parameter is inferred as lifetimebound}} .get(); return p; // cfg-note {{returned here}} } @@ -456,11 +456,11 @@ std::vector<std::string_view> GetTemporaryView(); std::string_view test_str_local() { std::vector<std::string> v; - return *std::find(v.begin(), // cfg-warning {{stack memory associated with local variable 'v' is returned}} cfg-note {{returned here}} + return *std::find(v.begin(), // cfg-warning {{stack memory associated with local variable 'v' is returned}} cfg-note {{returned here}} cfg-note {{result of call to 'begin' aliases the storage of local variable 'v' because the implicit object parameter is inferred as lifetimebound}} cfg-note {{result of call to 'find<__gnu_cxx::basic_iterator<std::basic_string<char>>, char[3]>' aliases the storage of local variable 'v' because parameter 'first' is inferred as lifetimebound}} cfg-note {{result of call to 'operator*' aliases the storage of local variable 'v' because the implicit object parameter is inferred as lifetimebound}} v.end(), "42"); } std::string_view test_str_temporary() { - return *std::find(GetTemporaryString().begin(), // cfg-warning {{stack memory associated with temporary object is returned}} cfg-note {{returned here}} + return *std::find(GetTemporaryString().begin(), // cfg-warning {{stack memory associated with temporary object is returned}} cfg-note {{returned here}} cfg-note {{result of call to 'begin' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} cfg-note {{result of call to 'find<__gnu_cxx::basic_iterator<std::basic_string<char>>, char[3]>' aliases the storage of temporary object because parameter 'first' is inferred as lifetimebound}} cfg-note {{result of call to 'operator*' aliases the storage of temporary object because the implicit object parameter is inferred as lifetimebound}} GetTemporaryString().end(), "42"); } std::string_view test_view() { @@ -594,10 +594,10 @@ struct FooView { }; FooView test3(int i, std::optional<Foo> a) { if (i) - return *a; // expected-warning {{address of stack memory}} \ + return *a; // expected-warning {{address of stack memory}} cfg-note {{result of call to 'operator*' aliases the storage of parameter 'a' because the implicit object parameter is inferred as lifetimebound}} \ // cfg-warning {{stack memory associated with parameter 'a' is returned}} \ // cfg-note {{returned here}} - return a.value(); // expected-warning {{address of stack memory}} \ + return a.value(); // expected-warning {{address of stack memory}} cfg-note {{result of call to 'value' aliases the storage of parameter 'a' because the implicit object parameter is inferred as lifetimebound}} \ // cfg-warning {{stack memory associated with parameter 'a' is returned}} \ // cfg-note {{returned here}} } @@ -658,7 +658,7 @@ std::string_view test2() { // We expect dangling issues as the conversion operator is marked as lifetimebound。 std::string_view bad = StatusOr<Wrapper2<std::string_view>>().value(); // expected-warning {{temporary whose address is used as value of}} - return k.value(); // expected-warning {{address of stack memory associated}} \ + return k.value(); // expected-warning {{address of stack memory associated}} cfg-note {{result of call to 'value' aliases the storage of local variable 'k' because the implicit object parameter is marked as lifetimebound}} \ // cfg-warning {{stack memory associated with local variable 'k' is returned}} cfg-note {{returned here}} } } // namespace GH108272 @@ -810,7 +810,7 @@ std::vector<int*> test8(StatusOr<std::vector<int*>> aa) { // Pointer<Pointer> from Owner<Owner<Pointer>> Span<int*> test9(StatusOr<std::vector<int*>> aa) { - return aa.valueLB(); // expected-warning {{address of stack memory associated}} \ + return aa.valueLB(); // expected-warning {{address of stack memory associated}} cfg-note {{result of call to 'valueLB' aliases the storage of parameter 'aa' because the implicit object parameter is marked as lifetimebound}} \ // cfg-warning {{stack memory associated with parameter 'aa' is returned}} cfg-note {{returned here}} return aa.valueNoLB(); // OK. } @@ -819,7 +819,7 @@ Span<int*> test9(StatusOr<std::vector<int*>> aa) { // Pointer<Owner>> from Owner<Owner> Span<std::string> test10(StatusOr<std::vector<std::string>> aa) { - return aa.valueLB(); // expected-warning {{address of stack memory}} \ + return aa.valueLB(); // expected-warning {{address of stack memory}} cfg-note {{result of call to 'valueLB' aliases the storage of parameter 'aa' because the implicit object parameter is marked as lifetimebound}} \ // cfg-warning {{stack memory associated with parameter 'aa' is returned}} cfg-note {{returned here}} return aa.valueNoLB(); // OK. } @@ -877,7 +877,7 @@ std::string_view test1_1() { // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ // cfg-note {{result of call to 'Ref' aliases the storage of temporary object because parameter 'abc' is marked as lifetimebound}} use(t1); // cfg-note {{later used here}} - return Ref(std::string()); // expected-warning {{returning address}} \ + return Ref(std::string()); // expected-warning {{returning address}} cfg-note {{result of call to 'Ref' aliases the storage of temporary object because parameter 'abc' is marked as lifetimebound}} \ // cfg-warning {{stack memory associated with temporary object is returned}} cfg-note {{returned here}} } @@ -931,7 +931,7 @@ std::string_view test2_1(Foo<std::string> r1, Foo<std::string_view> r2) { // cfg-warning {{temporary object does not live long enough}} cfg-note {{destroyed here}} \ // cfg-note {{result of call to 'get' aliases the storage of temporary object because the implicit object parameter is marked as lifetimebound}} use(t1); // cfg-note {{later used here}} - return r1.get(); // expected-warning {{address of stack}} \ + return r1.get(); // expected-warning {{address of stack}} cfg-note {{result of call to 'get' aliases the storage of parameter 'r1' because the implicit object parameter is marked as lifetimebound}} \ // cfg-warning {{stack memory associated with parameter 'r1' is returned}} cfg-note {{returned here}} } std::string_view test2_2(Foo<std::string> r1, Foo<std::string_view> r2) { @@ -999,14 +999,14 @@ void test4() { namespace range_based_for_loop_variables { std::string_view test_view_loop_var(std::vector<std::string> strings) { - for (std::string_view s : strings) { // cfg-warning {{stack memory associated with parameter 'strings' is returned}} + for (std::string_view s : strings) { // cfg-warning {{stack memory associated with parameter 'strings' is returned}} cfg-note {{result of call to 'begin' aliases the storage of parameter 'strings'}} return s; //cfg-note {{returned here}} } return ""; } const char* test_view_loop_var_with_data(std::vector<std::string> strings) { - for (std::string_view s : strings) { // cfg-warning {{stack memory associated with parameter 'strings' is returned}} + for (std::string_view s : strings) { // cfg-warning {{stack memory associated with parameter 'strings' is returned}} cfg-note {{result of call to 'begin' aliases the storage of parameter 'strings'}} return s.data(); //cfg-note {{returned here}} } return ""; @@ -1020,15 +1020,15 @@ std::string_view test_no_error_for_views(std::vector<std::string_view> views) { } std::string_view test_string_ref_var(std::vector<std::string> strings) { - for (const std::string& s : strings) { // cfg-warning {{stack memory associated with parameter 'strings' is returned}} - return s; //cfg-note {{returned here}} + for (const std::string& s : strings) { // cfg-warning {{stack memory associated with parameter 'strings' is returned}} cfg-note {{result of call to 'begin' aliases the storage of parameter 'strings'}} + return s; //cfg-note {{returned here}} cfg-note {{result of call to 'operator basic_string_view' aliases the storage of parameter 'strings'}} } return ""; } std::string_view test_opt_strings(std::optional<std::vector<std::string>> strings_or) { - for (const std::string& s : *strings_or) { // cfg-warning {{stack memory associated with parameter 'strings_or' is returned}} - return s; //cfg-note {{returned here}} + for (const std::string& s : *strings_or) { // cfg-warning {{stack memory associated with parameter 'strings_or' is returned}} cfg-note {{result of call to 'operator*' aliases the storage of parameter 'strings_or' because the implicit object parameter is inferred as lifetimebound}} cfg-note {{result of call to 'begin' aliases the storage of parameter 'strings_or'}} + return s; //cfg-note {{returned here}} cfg-note {{result of call to 'operator basic_string_view' aliases the storage of parameter 'strings_or'}} } return ""; } @@ -1037,7 +1037,7 @@ std::string_view test_opt_strings(std::optional<std::vector<std::string>> string namespace iterator_arrow { std::string_view test() { std::vector<std::string> strings; - return strings.begin()->data(); // cfg-warning {{stack memory associated with local variable 'strings' is returned}} cfg-note {{returned here}} + return strings.begin()->data(); // cfg-warning {{stack memory associated with local variable 'strings' is returned}} cfg-note {{returned here}} cfg-note {{result of call to 'begin' aliases the storage of local variable 'strings' because the implicit object parameter is inferred as lifetimebound}} cfg-note {{result of call to 'operator->' aliases the storage of local variable 'strings' because the implicit object parameter is inferred as lifetimebound}} cfg-note {{result of call to 'data' aliases the storage of local variable 'strings' because the implicit object parameter is inferred as lifetimebound}} } void operator_star_arrow_reference() { @@ -1185,7 +1185,7 @@ struct StatusOr { const char* foo() { StatusOr<std::string> s; - return s->data(); // expected-warning {{address of stack memory associated with local variable}} \ + return s->data(); // expected-warning {{address of stack memory associated with local variable}} cfg-note {{result of call to 'operator->' aliases the storage of local variable 's' because the implicit object parameter is marked as lifetimebound}} \ // cfg-warning {{stack memory associated with local variable 's' is returned}} cfg-note {{returned here}} StatusOr<std::string_view> s2; diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index bf49e86577678d..c7700a387440e6 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -544,7 +544,7 @@ View reassign_safe_to_local(const MyObj& safe) { View pointer_chain_to_local() { MyObj local; View p1 = local; // expected-warning {{stack memory associated with local variable 'local' is returned}} - View p2 = p1; + View p2 = p1; // expected-note {{local variable 'p1' aliases the storage of local variable 'local'}} return p2; // expected-note {{returned here}} } @@ -679,7 +679,7 @@ struct PtrHolder { int* const& test_ref_to_ptr() { PtrHolder a; - int *const &ref = a.getRef(); // expected-warning {{stack memory associated with local variable 'a' is returned}} + int *const &ref = a.getRef(); // expected-warning {{stack memory associated with local variable 'a' is returned}} expected-note {{result of call to 'getRef' aliases the storage of local variable 'a' because the implicit object parameter is marked as lifetimebound}} return ref; // expected-note {{returned here}} } int* const test_ref_to_ptr_no_error() { @@ -1631,10 +1631,10 @@ void range_based_for_use_after_scope() { View range_based_for_use_after_return() { MyObjStorage s; - for (const MyObj &o : s) { // expected-warning {{stack memory associated with local variable 's' is returned}} + for (const MyObj &o : s) { // expected-warning {{stack memory associated with local variable 's' is returned}} expected-note {{local variable '__range1' aliases the storage of local variable 's'}} return o; // expected-note {{returned here}} } - return *s.begin(); // expected-warning {{stack memory associated with local variable 's' is returned}} + return *s.begin(); // expected-warning {{stack memory associated with local variable 's' is returned}} expected-note {{result of call to 'begin' aliases the storage of local variable 's' because the implicit object parameter is marked as lifetimebound}} // expected-note@-1 {{returned here}} } @@ -1751,7 +1751,7 @@ T&& MaxT(T&& a [[clang::lifetimebound]], T&& b [[clang::lifetimebound]]); const MyObj& call_max_with_obj() { MyObj oa, ob; - return MaxT(oa, // expected-warning {{stack memory associated with local variable 'oa' is returned}} + return MaxT(oa, // expected-warning {{stack memory associated with local variable 'oa' is returned}} expected-note {{result of call to 'MaxT<MyObj &>' aliases the storage of local variable 'oa' because parameter 'a' is marked as lifetimebound}} expected-note {{result of call to 'MaxT<MyObj &>' aliases the storage of local variable 'ob' because parameter 'b' is marked as lifetimebound}} // expected-note@-1 2 {{returned here}} ob); // expected-warning {{stack memory associated with local variable 'ob' is returned}} @@ -1759,7 +1759,7 @@ const MyObj& call_max_with_obj() { MyObj* call_max_with_obj_error() { MyObj oa, ob; - return &MaxT(oa, // expected-warning {{stack memory associated with local variable 'oa' is returned}} + return &MaxT(oa, // expected-warning {{stack memory associated with local variable 'oa' is returned}} expected-note {{result of call to 'MaxT<MyObj &>' aliases the storage of local variable 'oa' because parameter 'a' is marked as lifetimebound}} expected-note {{result of call to 'MaxT<MyObj &>' aliases the storage of local variable 'ob' because parameter 'b' is marked as lifetimebound}} // expected-note@-1 2 {{returned here}} ob); // expected-warning {{stack memory associated with local variable 'ob' is returned}} } @@ -1768,7 +1768,7 @@ const MyObj* call_max_with_ref_obj_error() { MyObj oa, ob; const MyObj& refa = oa; // expected-warning {{stack memory associated with local variable 'oa' is returned}} const MyObj& refb = ob; // expected-warning {{stack memory associated with local variable 'ob' is returned}} - return &MaxT(refa, refb); // expected-note 2 {{returned here}} + return &MaxT(refa, refb); // expected-note 2 {{returned here}} expected-note {{result of call to 'MaxT<const MyObj &>' aliases the storage of local variable 'oa'}} expected-note {{result of call to 'MaxT<const MyObj &>' aliases the storage of local variable 'ob'}} } const MyObj& call_max_with_ref_obj_return_ref_error() { MyObj oa, ob; @@ -1788,7 +1788,7 @@ const MyObj& call_max_with_ref_obj_no_error(const MyObj& a, const MyObj& b) { const View& call_max_with_view_with_error() { View va, vb; - return MaxT(va, // expected-warning {{stack memory associated with local variable 'va' is returned}} + return MaxT(va, // expected-warning {{stack memory associated with local variable 'va' is returned}} expected-note {{result of call to 'MaxT<View &>' aliases the storage of local variable 'va' because parameter 'a' is marked as lifetimebound}} expected-note {{result of call to 'MaxT<View &>' aliases the storage of local variable 'vb' because parameter 'b' is marked as lifetimebound}} // expected-note@-1 2 {{returned here}} vb); // expected-warning {{stack memory associated with local variable 'vb' is returned}} } @@ -1797,7 +1797,7 @@ struct [[gsl::Pointer]] NonTrivialPointer { ~NonTrivialPointer(); }; const NonTrivialPointer& call_max_with_non_trivial_view_with_error() { NonTrivialPointer va, vb; - return MaxT(va, // expected-warning {{stack memory associated with local variable 'va' is returned}} + return MaxT(va, // expected-warning {{stack memory associated with local variable 'va' is returned}} expected-note {{result of call to 'MaxT<MaxFnLifetimeBound::NonTrivialPointer &>' aliases the storage of local variable 'va' because parameter 'a' is marked as lifetimebound}} expected-note {{result of call to 'MaxT<MaxFnLifetimeBound::NonTrivialPointer &>' aliases the storage of local variable 'vb' because parameter 'b' is marked as lifetimebound}} // expected-note@-1 2 {{returned here}} vb); // expected-warning {{stack memory associated with local variable 'vb' is returned}} } @@ -1964,7 +1964,7 @@ const MyObj& testDeref(MyObj obj) { } const MyObj* testDerefAddr(MyObj obj) { View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}} - return &*v; // expected-note {{returned here}} + return &*v; // expected-note {{returned here}} expected-note {{result of call to 'operator*' aliases the storage of parameter 'obj'}} } const MyObj* testData(MyObj obj) { View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}} @@ -1972,12 +1972,12 @@ const MyObj* testData(MyObj obj) { } const int* testLifetimeboundAccessorOfMyObj(MyObj obj) { View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}} - const MyObj* ptr = v.data(); + const MyObj* ptr = v.data(); // expected-note {{local variable 'v' aliases the storage of parameter 'obj'}} expected-note {{result of call to 'data' aliases the storage of parameter 'obj' because the implicit object parameter is inferred as lifetimebound}} return ptr->getData(); // expected-note {{returned here}} } const int* testLifetimeboundAccessorOfMyObjThroughDeref(MyObj obj) { View v = obj; // expected-warning {{stack memory associated with parameter 'obj' is returned}} - return v->getData(); // expected-note {{returned here}} + return v->getData(); // expected-note {{returned here}} expected-note {{result of call to 'operator->' aliases the storage of parameter 'obj'}} } } // namespace DereferenceViews @@ -2000,17 +2000,17 @@ It end() const [[clang::lifetimebound]]; MyObj Global; const MyObj& ContainerMyObjReturnRef(Container<MyObj> c) { - for (const MyObj& x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}} + for (const MyObj& x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}} expected-note {{local variable '__range1' aliases the storage of parameter 'c'}} return x; // expected-note {{returned here}} } return Global; } View ContainerMyObjReturnView(Container<MyObj> c) { - for (const MyObj& x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}} + for (const MyObj& x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}} expected-note {{local variable '__range1' aliases the storage of parameter 'c'}} return x; // expected-note {{returned here}} } - for (View x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}} + for (View x : c) { // expected-warning {{stack memory associated with parameter 'c' is returned}} expected-note {{local variable '__range1' aliases the storage of parameter 'c'}} return x; // expected-note {{returned here}} } return Global; @@ -2066,7 +2066,7 @@ void test_lifetime_extension_ok() { } const std::string& test_return() { - const std::string& x = S().x(); // expected-warning {{stack memory associated with temporary object is returned}} + const std::string& x = S().x(); // expected-warning {{stack memory associated with temporary object is returned}} expected-note {{result of call to 'x' aliases the storage of temporary object because the implicit object parameter is marked as lifetimebound}} return x; // expected-note {{returned here}} } } // namespace reference_type_decl_ref_expr @@ -2145,7 +2145,7 @@ struct RefMember { std::string_view refMemberReturnView1(RefMember a) { return a.str_ref; } std::string_view refMemberReturnView2(RefMember a) { return *a.str_ptr; } -std::string_view refMemberReturnView3(RefMember a) { return a.str; } // expected-warning {{stack memory associated with parameter 'a' is returned}} expected-note {{returned here}} +std::string_view refMemberReturnView3(RefMember a) { return a.str; } // expected-warning {{stack memory associated with parameter 'a' is returned}} expected-note {{returned here}} expected-note {{result of call to 'operator basic_string_view' aliases the storage of parameter 'a' because the implicit object parameter is inferred as lifetimebound}} std::string& refMemberReturnRef1(RefMember a) { return a.str_ref; } std::string& refMemberReturnRef2(RefMember a) { return *a.str_ptr; } std::string& refMemberReturnRef3(RefMember a) { return a.str; } // expected-warning {{stack memory associated with parameter 'a' is returned}} expected-note {{returned here}} @@ -2347,7 +2347,7 @@ auto capture_int_by_value() { auto capture_view_by_value() { MyObj obj; View v(obj); // expected-warning {{stack memory associated with local variable 'obj' is returned}} - auto lambda = [v]() { return v; }; + auto lambda = [v]() { return v; }; // expected-note {{local variable 'v' aliases the storage of local variable 'obj'}} return lambda; // expected-note {{returned here}} } @@ -2377,35 +2377,35 @@ auto capture_multiple() { auto capture_raw_pointer_by_value() { int x; int* p = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}} - auto lambda = [p]() { return p; }; + auto lambda = [p]() { return p; }; // expected-note {{local variable 'p' aliases the storage of local variable 'x'}} return lambda; // expected-note {{returned here}} } auto capture_raw_pointer_init_capture() { int x; int* p = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}} - auto lambda = [q = p]() { return q; }; + auto lambda = [q = p]() { return q; }; // expected-note {{local variable 'p' aliases the storage of local variable 'x'}} return lambda; // expected-note {{returned here}} } auto capture_view_init_capture() { MyObj obj; View v(obj); // expected-warning {{stack memory associated with local variable 'obj' is returned}} - auto lambda = [w = v]() { return w; }; + auto lambda = [w = v]() { return w; }; // expected-note {{local variable 'v' aliases the storage of local variable 'obj'}} return lambda; // expected-note {{returned here}} } auto capture_lambda() { int x; auto inner = [&x]() { return x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}} - auto outer = [inner]() { return inner(); }; + auto outer = [inner]() { return inner(); }; // expected-note {{local variable 'inner' aliases the storage of local variable 'x'}} return outer; // expected-note {{returned here}} } auto return_copied_lambda() { int local = 1; auto lambda = [&local]() { return local; }; // expected-warning {{stack memory associated with local variable 'local' is returned}} - auto lambda_copy = lambda; + auto lambda_copy = lambda; // expected-note {{local variable 'lambda' aliases the storage of local variable 'local'}} return lambda_copy; // expected-note {{returned here}} } @@ -2427,7 +2427,7 @@ auto implicit_ref_capture_multiple() { auto implicit_value_capture() { MyObj obj; View v(obj); // expected-warning {{stack memory associated with local variable 'obj' is returned}} - auto lambda = [=]() { return v; }; + auto lambda = [=]() { return v; }; // expected-note {{local variable 'v' aliases the storage of local variable 'obj'}} return lambda; // expected-note {{returned here}} } @@ -2467,7 +2467,7 @@ auto capture_multilevel_pointer() { int *p = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}} int **q = &p; // expected-warning {{stack memory associated with local variable 'p' is returned}} int ***r = &q; // expected-warning {{stack memory associated with local variable 'q' is returned}} - auto lambda = [=]() { return *p + **q + ***r; }; + auto lambda = [=]() { return *p + **q + ***r; }; // expected-note {{local variable 'p' aliases the storage of local variable 'x'}} expected-note {{local variable 'r' aliases the storage of local variable 'q'}} expected-note {{local variable 'q' aliases the storage of local variable 'p'}} return lambda; // expected-note 3 {{returned here}} } } // namespace lambda_captures @@ -2785,8 +2785,8 @@ void same_scope() { S copy_propagation() { std::string str{"abc"}; - S a = getS(str); // expected-warning {{stack memory associated with local variable 'str' is returned}} - S b = a; + S a = getS(str); // expected-warning {{stack memory associated with local variable 'str' is returned}} expected-note {{result of call to 'getS' aliases the storage of local variable 'str' because parameter 's' is marked as lifetimebound}} + S b = a; // expected-note {{local variable 'a' aliases the storage of local variable 'str'}} return b; // expected-note {{returned here}} } @@ -2833,7 +2833,7 @@ S getS2(const std::string &a [[clang::lifetimebound]], const std::string &b [[cl S multiple_lifetimebound_params() { std::string str{"abc"}; - S s = getS2(str, std::string("temp")); // expected-warning {{stack memory associated with local variable 'str' is returned}} \ + S s = getS2(str, std::string("temp")); // expected-warning {{stack memory associated with local variable 'str' is returned}} expected-note {{result of call to 'getS2' aliases the storage of local variable 'str' because parameter 'a' is marked as lifetimebound}} expected-note {{result of call to 'getS2' aliases the storage of temporary object because parameter 'b' is marked as lifetimebound}} \ // expected-warning {{stack memory associated with temporary object is returned}} return s; // expected-note 2 {{returned here}} } @@ -2951,8 +2951,8 @@ DefaultedOuter getDefaultedOuter(const std::string &s [[clang::lifetimebound]]); // pattern does not fit the ownership model this analysis supports. DefaultedOuter nested_defaulted_outer_with_user_defined_inner() { std::string str{"abc"}; - DefaultedOuter o = getDefaultedOuter(str); // expected-warning {{stack memory associated with local variable 'str' is returned}} - DefaultedOuter copy = o; + DefaultedOuter o = getDefaultedOuter(str); // expected-warning {{stack memory associated with local variable 'str' is returned}} expected-note {{result of call to 'getDefaultedOuter' aliases the storage of local variable 'str' because parameter 's' is marked as lifetimebound}} + DefaultedOuter copy = o; // expected-note {{local variable 'o' aliases the storage of local variable 'str'}} return copy; // expected-note {{returned here}} } @@ -2999,8 +2999,8 @@ void owner_return_unique_ptr_s() { std::string_view return_dangling_view_through_owner() { std::string local; auto ups = getUniqueS(local); - S* s = ups.get(); // expected-warning {{stack memory associated with local variable 'ups' is returned}} - std::string_view sv = s->getData(); + S* s = ups.get(); // expected-warning {{stack memory associated with local variable 'ups' is returned}} expected-note {{result of call to 'get' aliases the storage of local variable 'ups' because the implicit object parameter is inferred as lifetimebound}} + std::string_view sv = s->getData(); // expected-note {{local variable 's' aliases the storage of local variable 'ups'}} expected-note {{result of call to 'getData' aliases the storage of local variable 'ups' because the implicit object parameter is marked as lifetimebound}} return sv; // expected-note {{returned here}} } @@ -3112,12 +3112,12 @@ int *constexpr_dead_nested(int *num) { int *constexpr_live_false(int *num) { int local = 0; - return kFalse ? num : f(&local); // expected-warning {{stack memory associated with local variable 'local' is returned}} // expected-note {{returned here}} + return kFalse ? num : f(&local); // expected-warning {{stack memory associated with local variable 'local' is returned}} // expected-note {{returned here}} expected-note {{result of call to 'f' aliases the storage of local variable 'local' because parameter 'p' is marked as lifetimebound}} } int *constexpr_live_nested(int *num) { int local = 0; - return kTrue ? (kFalse ? num : f(&local)) : num; // expected-warning {{stack memory associated with local variable 'local' is returned}} // expected-note {{returned here}} + return kTrue ? (kFalse ? num : f(&local)) : num; // expected-warning {{stack memory associated with local variable 'local' is returned}} // expected-note {{returned here}} expected-note {{result of call to 'f' aliases the storage of local variable 'local' because parameter 'p' is marked as lifetimebound}} } int *noreturn_dead_false(bool cond, int *num) { int local = 0; @@ -3734,7 +3734,7 @@ std::function<void()> direct_return() { std::function<void()> copy_function() { int x; std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}} - std::function<void()> f2 = f; + std::function<void()> f2 = f; // expected-note {{local variable 'f' aliases the storage of local variable 'x'}} return f2; // expected-note {{returned here}} } @@ -3742,7 +3742,7 @@ std::function<void()> copy_assign() { int x; std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}} std::function<void()> f2 = []() {}; - f2 = f; + f2 = f; // expected-note {{local variable 'f' aliases the storage of local variable 'x'}} return f2; // expected-note {{returned here}} } @@ -3751,7 +3751,7 @@ std::function<void()> chained_copy_assign() { std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}} std::function<void()> f2 = []() {}; std::function<void()> f3 = []() {}; - f3 = f2 = f; + f3 = f2 = f; // expected-note {{local variable 'f' aliases the storage of local variable 'x'}} expected-note {{result of call to 'operator=' aliases the storage of local variable 'x'}} return f3; // expected-note {{returned here}} } @@ -3759,7 +3759,7 @@ std::function<void()> move_assign() { int x; std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{stack memory associated with local variable 'x' is returned}} std::function<void()> f2 = []() {}; - f2 = std::move(f); + f2 = std::move(f); // expected-note {{result of call to 'move<std::function<void ()> &>' aliases the storage of local variable 'x'}} return f2; // expected-note {{returned here}} } @@ -3854,19 +3854,19 @@ struct [[gsl::Owner]] optional : __optional_storage_base<T> { const MyObj& return_optional_deref() { optional<MyObj> opt; - return *opt; // expected-warning {{stack memory associated with local variable 'opt' is returned}} \ + return *opt; // expected-warning {{stack memory associated with local variable 'opt' is returned}} expected-note {{result of call to 'operator*' aliases the storage of local variable 'opt' because the implicit object parameter is inferred as lifetimebound}} \ // expected-note {{returned here}} } const MyObj& return_optional_value() { optional<MyObj> opt; - return opt.value(); // expected-warning {{stack memory associated with local variable 'opt' is returned}} \ + return opt.value(); // expected-warning {{stack memory associated with local variable 'opt' is returned}} expected-note {{result of call to 'value' aliases the storage of local variable 'opt' because the implicit object parameter is inferred as lifetimebound}} \ // expected-note {{returned here}} } const int* return_optional_arrow() { optional<MyObj> opt; - return &opt->id; // expected-warning {{stack memory associated with local variable 'opt' is returned}} \ + return &opt->id; // expected-warning {{stack memory associated with local variable 'opt' is returned}} expected-note {{result of call to 'operator->' aliases the storage of local variable 'opt' because the implicit object parameter is inferred as lifetimebound}} \ // expected-note {{returned here}} } @@ -3887,27 +3887,27 @@ namespace GH191954 { int x; int* f = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}} int* a; - a = std::move(f); + a = std::move(f); // expected-note {{result of call to 'move<int *&>' aliases the storage of local variable 'x'}} return a; // expected-note {{returned here}} } int* return_moved_pointer2() { int x; int* f = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}} - return std::move(f); // expected-note {{returned here}} + return std::move(f); // expected-note {{returned here}} expected-note {{result of call to 'move<int *&>' aliases the storage of local variable 'x'}} } View return_moved_view() { MyObj o; View v(o); // expected-warning {{stack memory associated with local variable 'o' is returned}} - View v2 = std::move(v); + View v2 = std::move(v); // expected-note {{result of call to 'move<View &>' aliases the storage of local variable 'o'}} return v2; // expected-note {{returned here}} } int* return_forwarded_pointer() { int x; int* f = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}} - return std::forward<int*>(f); // expected-note {{returned here}} + return std::forward<int*>(f); // expected-note {{returned here}} expected-note {{result of call to 'forward<int *>' aliases the storage of local variable 'x'}} } int g; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
