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

None

>From 189fb5a7e21b099d5497debf47962483eafee37d 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         | 17 +++++---
 .../LifetimeSafety/LifetimeAnnotations.cpp    | 15 +++++++
 clang/test/Sema/Inputs/lifetime-analysis.h    |  3 ++
 clang/test/Sema/warn-lifetime-safety.cpp      | 42 ++++++++++++++++---
 5 files changed, 72 insertions(+), 10 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..b79442836c461 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -961,11 +961,18 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
           ArgList->peelOuterOrigin()->getOuterOriginID(), KillSrc));
       KillSrc = false;
     } 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.
-      CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
-          CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc));
+      if (isStdReferenceCast(FD)) {
+        // e.g., std::move(p): the result refers to the same object as p, so
+        // flow inner origins too.
+        flow(CallList, ArgList, KillSrc);
+      } else {
+        // Lifetimebound on a non-GSL-ctor function means the returned
+        // pointer/reference itself must not outlive the arguments. This
+        // 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 0619fda738fa4..486ac29a6b818 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