https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/199600
>From f7274dbaf3e8f563e6b6890b16bd2bf75d19cc39 Mon Sep 17 00:00:00 2001 From: Zhijie Wang <[email protected]> Date: Mon, 25 May 2026 21:21:35 -0700 Subject: [PATCH] [LifetimeSafety] Propagate inner origins through std::move and related casts --- .../LifetimeSafety/LifetimeAnnotations.h | 5 +++ .../LifetimeSafety/FactsGenerator.cpp | 12 +++++- .../LifetimeSafety/LifetimeAnnotations.cpp | 15 +++++++ clang/test/Sema/Inputs/lifetime-analysis.h | 3 ++ clang/test/Sema/warn-lifetime-safety.cpp | 42 ++++++++++++++++--- 5 files changed, 70 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index a97df7a08dfeb..47fcd5dbfd569 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -106,6 +106,11 @@ bool destructsFirstArg(const FunctionDecl &FD); /// that can propagate the stored lambda's origins. bool isStdCallableWrapperType(const CXXRecordDecl *RD); +/// Returns true for std reference-cast builtins (e.g., std::move). Their result +/// refers to the same object as the argument, so all origins propagate from +/// argument to result. +bool isStdReferenceCast(const FunctionDecl *FD); + } // namespace clang::lifetimes #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMEANNOTATIONS_H diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 9038f56689779..323802c6a88d5 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -886,6 +886,14 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, handleImplicitObjectFieldUses(Call, FD); if (!CallList) return; + if (isStdReferenceCast(FD)) { + assert(Args.size() == 1 && + "std reference cast builtins take exactly one argument"); + // std reference-cast functions like std::move return a result that refers + // to the same object as the argument, so propagate the full origins. + flow(CallList, getOriginsList(*Args[0]), /*Kill=*/true); + return; + } auto IsArgLifetimeBound = [FD, &Args](unsigned I) -> bool { const ParmVarDecl *PVD = nullptr; if (const auto *Method = dyn_cast<CXXMethodDecl>(FD); @@ -954,7 +962,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, } } else if (shouldTrackPointerImplicitObjectArg(I)) { assert(ArgList->getLength() >= 2 && - "Object arg of pointer type should have atleast two origins"); + "Object arg of pointer type should have at least two origins"); // See through the GSLPointer reference to see the pointer's value. CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( CallList->getOuterOriginID(), @@ -963,7 +971,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, } else if (IsArgLifetimeBound(I)) { // Lifetimebound on a non-GSL-ctor function means the returned // pointer/reference itself must not outlive the arguments. This - // only constraints the top-level origin. + // only constrains the top-level origin. CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc)); KillSrc = false; diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 2f26e77d5a0eb..6a52616c5d590 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -460,4 +460,19 @@ bool isStdCallableWrapperType(const CXXRecordDecl *RD) { return Name == "function" || Name == "move_only_function"; } +bool isStdReferenceCast(const FunctionDecl *FD) { + if (!FD) + return false; + switch (FD->getBuiltinID()) { + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + case Builtin::BIforward: + case Builtin::BIforward_like: + case Builtin::BIas_const: + return true; + default: + return false; + } +} + } // namespace clang::lifetimes diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h index 2ae6ed38714b7..4d727ae9499d6 100644 --- a/clang/test/Sema/Inputs/lifetime-analysis.h +++ b/clang/test/Sema/Inputs/lifetime-analysis.h @@ -37,6 +37,9 @@ ForwardIt1 search( ForwardIt1 first, ForwardIt1 last, template<typename T> typename remove_reference<T>::type &&move(T &&t) noexcept; +template<typename T> +T &&forward(typename remove_reference<T>::type &t) noexcept; + template <typename C> auto data(const C &c) -> decltype(c.data()); diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index d5f558c7918b3..ae9d5d08e358b 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -3249,15 +3249,12 @@ std::function<void()> chained_copy_assign() { return f3; // expected-note {{returned here}} } -// FIXME: False negative. std::move's lifetimebound handling in -// `handleFunctionCall` only flows the outermost origin, missing inner origins -// that carry the lambda's loans. std::function<void()> move_assign() { int x; - std::function<void()> f = [&x]() { (void)x; }; // Should warn. + 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); - return f2; + return f2; // expected-note {{returned here}} } std::function<void()> reassign_safe_then_unsafe() { @@ -3376,3 +3373,38 @@ void deref_use_after_scope() { } } // namespace GH188832 + +namespace GH191954 { + int* return_moved_pointer() { + int x; + int* f = &x; // expected-warning {{stack memory associated with local variable 'x' is returned}} + int* a; + a = std::move(f); + 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}} + } + + 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); + 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}} + } + + int g; + int* return_moved_pointer_to_global() { + int* f = &g; + return std::move(f); + } +} // namespace GH191954 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
