https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/199600

>From e0272b27bd0b55a1819dea89e7c96f412dee381e 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..9841429d5cf52 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 {{address 
of stack memory is returned later}}
   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 {{address of stack memory is returned 
later}}
+    int* a;
+    a = std::move(f);
+    return a; // expected-note {{returned here}}
+  }
+
+  int* return_moved_pointer2() {
+    int x;
+    int* f = &x;         // expected-warning {{address of stack memory is 
returned later}}
+    return std::move(f); // expected-note {{returned here}}
+  }
+
+  View return_moved_view() {
+    MyObj o;
+    View v(o); // expected-warning {{address of stack memory is returned 
later}}
+    View v2 = std::move(v);
+    return v2; // expected-note {{returned here}}
+  }
+
+  int* return_forwarded_pointer() {
+    int x;
+    int* f = &x;                  // expected-warning {{address of stack 
memory is returned later}}
+    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

Reply via email to