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

>From cc957c23792e65f8d579ac9f79a6631e023d868b Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 1/8] [LifetimeSafety] Track origins through std::function

---
 .../LifetimeSafety/LifetimeAnnotations.h      |  4 ++
 .../LifetimeSafety/FactsGenerator.cpp         | 26 ++++++++-
 .../LifetimeSafety/LifetimeAnnotations.cpp    |  8 +++
 clang/lib/Analysis/LifetimeSafety/Origins.cpp | 15 +++--
 clang/test/Sema/Inputs/lifetime-analysis.h    | 10 ++++
 clang/test/Sema/warn-lifetime-safety.cpp      | 57 +++++++++++++++++++
 6 files changed, 109 insertions(+), 11 deletions(-)

diff --git 
a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
index aa9ae4b2a5e6a..098c15f4a7fb4 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h
@@ -80,6 +80,10 @@ bool isUniquePtrRelease(const CXXMethodDecl &MD);
 // https://en.cppreference.com/w/cpp/container#Iterator_invalidation
 bool isContainerInvalidationMethod(const CXXMethodDecl &MD);
 
+/// Returns true for standard library callable wrappers (e.g., std::function)
+/// that can propagate the stored lambda's origins.
+bool isStdCallableWrapperType(const CXXRecordDecl *RD);
+
 } // 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 82b890b57817e..9bf85f8cf0b41 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -190,9 +190,8 @@ void FactsGenerator::VisitCXXConstructExpr(const 
CXXConstructExpr *CCE) {
     return;
   }
   // For defaulted (implicit or `= default`) copy/move constructors, propagate
-  // origins directly. User-defined copy/move constructors have opaque 
semantics
-  // and fall through to `handleFunctionCall`, where [[clang::lifetimebound]] 
is
-  // needed to propagate origins.
+  // origins directly. User-defined copy/move constructors are not handled here
+  // as they have opaque semantics.
   if (CCE->getConstructor()->isCopyOrMoveConstructor() &&
       CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 &&
       hasOrigins(CCE->getType())) {
@@ -202,6 +201,16 @@ void FactsGenerator::VisitCXXConstructExpr(const 
CXXConstructExpr *CCE) {
       return;
     }
   }
+  // Standard library callable wrappers (e.g., std::function) propagate the
+  // stored lambda's origins.
+  if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
+      RD && isStdCallableWrapperType(RD) && CCE->getNumArgs() == 1) {
+    const Expr *Arg = CCE->getArg(0);
+    if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
+      flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
+      return;
+    }
+  }
   handleFunctionCall(CCE, CCE->getConstructor(),
                      {CCE->getArgs(), CCE->getNumArgs()},
                      /*IsGslConstruction=*/false);
@@ -395,6 +404,10 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
     } else
       markUseAsWrite(DRE_LHS);
   }
+  // RHS may not have tracked origins (e.g., assigning a non-lambda functor
+  // to a std::function). Skip the flow in that case.
+  if (!RHSList)
+    return;
   // Kill the old loans of the destination origin and flow the new loans
   // from the source origin.
   flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
@@ -488,6 +501,13 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const 
CXXOperatorCallExpr *OCE) {
       handleAssignment(OCE->getArg(0), OCE->getArg(1));
       return;
     }
+    // Standard library callable wrappers (e.g., std::function) can propagate
+    // the stored lambda's origins.
+    if (const auto *RD = LHSTy->getAsCXXRecordDecl();
+        RD && isStdCallableWrapperType(RD)) {
+      handleAssignment(OCE->getArg(0), OCE->getArg(1));
+      return;
+    }
     // Other tracked types: only defaulted operator= propagates origins.
     // User-defined operator= has opaque semantics, so don't handle them now.
     if (const auto *MD =
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp 
b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 4852f444a51b3..27d95821dd0b4 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -382,4 +382,12 @@ bool isContainerInvalidationMethod(const CXXMethodDecl 
&MD) {
 
   return InvalidatingMethods->contains(MD.getName());
 }
+
+bool isStdCallableWrapperType(const CXXRecordDecl *RD) {
+  if (!RD || !isInStlNamespace(RD))
+    return false;
+  StringRef Name = getName(*RD);
+  return Name == "function" || Name == "move_only_function";
+}
+
 } // namespace clang::lifetimes
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index abf9890b08522..c0336ec14db7b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -107,14 +107,13 @@ bool OriginManager::hasOrigins(QualType QT) const {
   const auto *RD = QT->getAsCXXRecordDecl();
   if (!RD)
     return false;
-  // TODO: Limit to lambdas for now. This will be extended to user-defined
-  // structs with pointer-like fields.
-  if (!RD->isLambda())
-    return false;
-  for (const auto *FD : RD->fields())
-    if (hasOrigins(FD->getType()))
-      return true;
-  return false;
+  // Standard library callable wrappers (e.g., std::function) can propagate the
+  // stored lambda's origins.
+  if (isStdCallableWrapperType(RD))
+    return true;
+  // TODO: Extend origin tracking to user-defined structs that may carry
+  // origins.
+  return RD->isLambda();
 }
 
 /// Determines if an expression has origins that need to be tracked.
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h 
b/clang/test/Sema/Inputs/lifetime-analysis.h
index d1e847d20cc50..58ba1aa0c03c3 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -268,4 +268,14 @@ struct true_type {
 template<class T> struct is_pointer : false_type {};
 template<class T> struct is_pointer<T*> : true_type {};
 template<class T> struct is_pointer<T* const> : true_type {};
+
+template<class> class function;
+template<class R, class... Args>
+class function<R(Args...)> {
+public:
+  template<class F> function(F) {}
+  template<class F> function& operator=(F) { return *this; }
+  ~function();
+};
+
 }
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 77d8e3370676d..dd2b343477ef0 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2531,3 +2531,60 @@ int *noreturn_dead_nested(bool cond, bool cond2, int 
*num) {
 }
 
 } // namespace conditional_operator_control_flow
+
+namespace callable_wrappers {
+
+std::function<void()> direct_return() {
+  int x;
+  return [&x]() { (void)x; }; // expected-warning {{address of stack memory is 
returned later}} \
+                              // expected-note {{returned here}}
+}
+
+std::function<void()> copy_function() {
+  int x;
+  std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function<void()> f2 = f;
+  return f2; // expected-note {{returned here}}
+}
+
+std::function<void()> reassign_safe_then_unsafe() {
+  static int safe = 1;
+  int local = 2;
+  std::function<void()> f = []() { (void)safe; };
+  f = [&local]() { (void)local; }; // expected-warning {{address of stack 
memory is returned later}}
+  return f; // expected-note {{returned here}}
+}
+
+std::function<void()> reassign_unsafe_then_safe() {
+  static int safe = 1;
+  int local = 2;
+  std::function<void()> f = [&local]() { (void)local; };
+  f = []() { (void)safe; };
+  return f;
+}
+
+std::function<void()> non_capturing_lambda() {
+  return []() {};
+}
+
+void free_function();
+
+std::function<void()> reassign_lambda_to_function_pointer() {
+  int local;
+  std::function<void()> f = [&local]() { (void)local; };
+  f = &free_function;
+  return f;
+}
+
+struct Functor { void operator()() const; };
+
+// FIXME: False positive.
+std::function<void()> reassign_lambda_to_functor() {
+  int local;
+  Functor c;
+  std::function<void()> f = [&local]() { (void)local; }; // expected-warning 
{{address of stack memory is returned later}}
+  f = c;
+  return f; // expected-note {{returned here}}
+}
+
+} // namespace callable_wrappers

>From 0edd7cc4f78ee8a164110499eec1e7419767051a Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Sat, 11 Apr 2026 16:52:54 -0700
Subject: [PATCH 2/8] Add a kill-only mechanism (e.g., OriginFlowFact with no
 source) to clear old loans when the RHS has no origins

---
 .../clang/Analysis/Analyses/LifetimeSafety/Facts.h    |  9 ++++++---
 clang/lib/Analysis/LifetimeSafety/Facts.cpp           | 10 ++++++----
 clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp  | 11 ++++++++---
 clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 10 ++++++----
 clang/lib/Analysis/LifetimeSafety/Origins.cpp         | 11 ++++++++---
 clang/test/Sema/warn-lifetime-safety.cpp              |  5 ++---
 6 files changed, 36 insertions(+), 20 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 6be8f6e455bc2..265f783183553 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -130,7 +130,9 @@ class ExpireFact : public Fact {
 
 class OriginFlowFact : public Fact {
   OriginID OIDDest;
-  OriginID OIDSrc;
+  // The source origin to flow from. Absent when only clearing the 
destination's
+  // loans.
+  std::optional<OriginID> OIDSrc;
   // True if the destination origin should be killed (i.e., its current loans
   // cleared) before the source origin's loans are flowed into it.
   bool KillDest;
@@ -140,12 +142,13 @@ class OriginFlowFact : public Fact {
     return F->getKind() == Kind::OriginFlow;
   }
 
-  OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
+  OriginFlowFact(OriginID OIDDest, std::optional<OriginID> OIDSrc,
+                 bool KillDest)
       : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc),
         KillDest(KillDest) {}
 
   OriginID getDestOriginID() const { return OIDDest; }
-  OriginID getSrcOriginID() const { return OIDSrc; }
+  std::optional<OriginID> getSrcOriginID() const { return OIDSrc; }
   bool getKillDest() const { return KillDest; }
 
   void dump(llvm::raw_ostream &OS, const LoanManager &,
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 1bc0521a72359..7a04fd1dfe2f2 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -43,10 +43,12 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
   OS << "\tDest: ";
   OM.dump(getDestOriginID(), OS);
   OS << "\n";
-  OS << "\tSrc:  ";
-  OM.dump(getSrcOriginID(), OS);
-  OS << (getKillDest() ? "" : ", Merge");
-  OS << "\n";
+  if (getSrcOriginID()) {
+    OS << "\tSrc:  ";
+    OM.dump(*getSrcOriginID(), OS);
+    OS << (getKillDest() ? "" : ", Merge");
+    OS << "\n";
+  }
 }
 
 void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 9bf85f8cf0b41..f20a8493e7215 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -404,10 +404,15 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
     } else
       markUseAsWrite(DRE_LHS);
   }
-  // RHS may not have tracked origins (e.g., assigning a non-lambda functor
-  // to a std::function). Skip the flow in that case.
-  if (!RHSList)
+  if (!RHSList) {
+    // RHS has no tracked origins (e.g., assigning a non-lambda functor to a
+    // std::function). Emit a kill-only flow to clear old loans.
+    if (OriginList *LHSInner = LHSList->peelOuterOrigin())
+      CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+          LHSInner->getOuterOriginID(), /*OIDSrc=*/std::nullopt,
+          /*KillDest=*/true));
     return;
+  }
   // Kill the old loans of the destination origin and flow the new loans
   // from the source origin.
   flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp 
b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index e437fb7d41268..a53e8d0419282 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -55,7 +55,8 @@ static llvm::BitVector computePersistentOrigins(const 
FactManager &FactMgr,
       case Fact::Kind::OriginFlow: {
         const auto *OF = F->getAs<OriginFlowFact>();
         CheckOrigin(OF->getDestOriginID());
-        CheckOrigin(OF->getSrcOriginID());
+        if (OF->getSrcOriginID())
+          CheckOrigin(*OF->getSrcOriginID());
         break;
       }
       case Fact::Kind::Use:
@@ -168,14 +169,15 @@ class AnalysisImpl
 
   /// A flow from source to destination. If `KillDest` is true, this replaces
   /// the destination's loans with the source's. Otherwise, the source's loans
-  /// are merged into the destination's.
+  /// are merged into the destination's. If the source is nullopt, this is a
+  /// kill-only flow that clears the destination without adding new loans.
   Lattice transfer(Lattice In, const OriginFlowFact &F) {
     OriginID DestOID = F.getDestOriginID();
-    OriginID SrcOID = F.getSrcOriginID();
 
     LoanSet DestLoans =
         F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
-    LoanSet SrcLoans = getLoans(In, SrcOID);
+    LoanSet SrcLoans = F.getSrcOriginID() ? getLoans(In, *F.getSrcOriginID())
+                                          : LoanSetFactory.getEmptySet();
     LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
 
     return setLoans(In, DestOID, MergedLoans);
diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp 
b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
index c0336ec14db7b..b75ec256e65c1 100644
--- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp
@@ -111,9 +111,14 @@ bool OriginManager::hasOrigins(QualType QT) const {
   // stored lambda's origins.
   if (isStdCallableWrapperType(RD))
     return true;
-  // TODO: Extend origin tracking to user-defined structs that may carry
-  // origins.
-  return RD->isLambda();
+  // TODO: Limit to lambdas for now. This will be extended to user-defined
+  // structs with pointer-like fields.
+  if (!RD->isLambda())
+    return false;
+  for (const auto *FD : RD->fields())
+    if (hasOrigins(FD->getType()))
+      return true;
+  return false;
 }
 
 /// Determines if an expression has origins that need to be tracked.
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index dd2b343477ef0..634ab9a2af217 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2578,13 +2578,12 @@ std::function<void()> 
reassign_lambda_to_function_pointer() {
 
 struct Functor { void operator()() const; };
 
-// FIXME: False positive.
 std::function<void()> reassign_lambda_to_functor() {
   int local;
   Functor c;
-  std::function<void()> f = [&local]() { (void)local; }; // expected-warning 
{{address of stack memory is returned later}}
+  std::function<void()> f = [&local]() { (void)local; };
   f = c;
-  return f; // expected-note {{returned here}}
+  return f;
 }
 
 } // namespace callable_wrappers

>From 60f86172f10849bbfbb11d3b9ba561e19d167c55 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Sun, 12 Apr 2026 14:49:47 -0700
Subject: [PATCH 3/8] Add a separate KillOriginFact

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h  | 28 +++++++++++++++----
 clang/lib/Analysis/LifetimeSafety/Dataflow.h  |  3 ++
 clang/lib/Analysis/LifetimeSafety/Facts.cpp   | 17 +++++++----
 .../LifetimeSafety/FactsGenerator.cpp         | 12 ++++----
 .../Analysis/LifetimeSafety/LiveOrigins.cpp   |  4 +++
 .../LifetimeSafety/LoanPropagation.cpp        | 17 +++++++----
 6 files changed, 57 insertions(+), 24 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 265f783183553..d375e94ea9771 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,6 +55,9 @@ class Fact {
     OriginEscapes,
     /// An origin is invalidated (e.g. vector resized).
     InvalidateOrigin,
+    /// All loans are cleared from an origin (e.g., assigning a callable 
without
+    /// tracked origins to std::function).
+    KillOrigin,
   };
 
 private:
@@ -130,9 +133,7 @@ class ExpireFact : public Fact {
 
 class OriginFlowFact : public Fact {
   OriginID OIDDest;
-  // The source origin to flow from. Absent when only clearing the 
destination's
-  // loans.
-  std::optional<OriginID> OIDSrc;
+  OriginID OIDSrc;
   // True if the destination origin should be killed (i.e., its current loans
   // cleared) before the source origin's loans are flowed into it.
   bool KillDest;
@@ -142,13 +143,12 @@ class OriginFlowFact : public Fact {
     return F->getKind() == Kind::OriginFlow;
   }
 
-  OriginFlowFact(OriginID OIDDest, std::optional<OriginID> OIDSrc,
-                 bool KillDest)
+  OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest)
       : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc),
         KillDest(KillDest) {}
 
   OriginID getDestOriginID() const { return OIDDest; }
-  std::optional<OriginID> getSrcOriginID() const { return OIDSrc; }
+  OriginID getSrcOriginID() const { return OIDSrc; }
   bool getKillDest() const { return KillDest; }
 
   void dump(llvm::raw_ostream &OS, const LoanManager &,
@@ -319,6 +319,22 @@ class TestPointFact : public Fact {
             const OriginManager &) const override;
 };
 
+class KillOriginFact : public Fact {
+  OriginID OID;
+
+public:
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::KillOrigin;
+  }
+
+  KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {}
+
+  OriginID getKilledOrigin() const { return OID; }
+
+  void dump(llvm::raw_ostream &OS, const LoanManager &,
+            const OriginManager &OM) const override;
+};
+
 class FactManager {
 public:
   FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) : OriginMgr(AC) {
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h 
b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index 0f64ac8a36ef7..fc3049c8bec84 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -180,6 +180,8 @@ class DataflowAnalysis {
       return D->transfer(In, *F->getAs<TestPointFact>());
     case Fact::Kind::InvalidateOrigin:
       return D->transfer(In, *F->getAs<InvalidateOriginFact>());
+    case Fact::Kind::KillOrigin:
+      return D->transfer(In, *F->getAs<KillOriginFact>());
     }
     llvm_unreachable("Unknown fact kind");
   }
@@ -193,6 +195,7 @@ class DataflowAnalysis {
   Lattice transfer(Lattice In, const UseFact &) { return In; }
   Lattice transfer(Lattice In, const TestPointFact &) { return In; }
   Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; }
+  Lattice transfer(Lattice In, const KillOriginFact &) { return In; }
 };
 } // namespace clang::lifetimes::internal
 #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp 
b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index 7a04fd1dfe2f2..3d7fbcdacc830 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -43,12 +43,10 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
   OS << "\tDest: ";
   OM.dump(getDestOriginID(), OS);
   OS << "\n";
-  if (getSrcOriginID()) {
-    OS << "\tSrc:  ";
-    OM.dump(*getSrcOriginID(), OS);
-    OS << (getKillDest() ? "" : ", Merge");
-    OS << "\n";
-  }
+  OS << "\tSrc:  ";
+  OM.dump(getSrcOriginID(), OS);
+  OS << (getKillDest() ? "" : ", Merge");
+  OS << "\n";
 }
 
 void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
@@ -105,6 +103,13 @@ void TestPointFact::dump(llvm::raw_ostream &OS, const 
LoanManager &,
   OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
 }
 
+void KillOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
+                          const OriginManager &OM) const {
+  OS << "KillOrigin (";
+  OM.dump(getKilledOrigin(), OS);
+  OS << ")\n";
+}
+
 llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const {
   llvm::StringMap<ProgramPoint> AnnotationToPointMap;
   for (const auto &BlockFacts : BlockToFacts) {
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp 
b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index f20a8493e7215..eb5f1e9ccc045 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -405,12 +405,12 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
       markUseAsWrite(DRE_LHS);
   }
   if (!RHSList) {
-    // RHS has no tracked origins (e.g., assigning a non-lambda functor to a
-    // std::function). Emit a kill-only flow to clear old loans.
-    if (OriginList *LHSInner = LHSList->peelOuterOrigin())
-      CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
-          LHSInner->getOuterOriginID(), /*OIDSrc=*/std::nullopt,
-          /*KillDest=*/true));
+    // RHS has no tracked origins (e.g., assigning a callable without origins
+    // to std::function). Clear loans of the destination.
+    for (OriginList *LHSInner = LHSList->peelOuterOrigin(); LHSInner;
+         LHSInner = LHSInner->peelOuterOrigin())
+      CurrentBlockFacts.push_back(
+          FactMgr.createFact<KillOriginFact>(LHSInner->getOuterOriginID()));
     return;
   }
   // Kill the old loans of the destination origin and flow the new loans
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp 
b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index bc7494360624e..cfbcacf04b1b0 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -166,6 +166,10 @@ class AnalysisImpl
     return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID()));
   }
 
+  Lattice transfer(Lattice In, const KillOriginFact &F) {
+    return Lattice(Factory.remove(In.LiveOrigins, F.getKilledOrigin()));
+  }
+
   Lattice transfer(Lattice In, const ExpireFact &F) {
     if (auto OID = F.getOriginID())
       return Lattice(Factory.remove(In.LiveOrigins, *OID));
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp 
b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index a53e8d0419282..adbc0458516e1 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -55,8 +55,7 @@ static llvm::BitVector computePersistentOrigins(const 
FactManager &FactMgr,
       case Fact::Kind::OriginFlow: {
         const auto *OF = F->getAs<OriginFlowFact>();
         CheckOrigin(OF->getDestOriginID());
-        if (OF->getSrcOriginID())
-          CheckOrigin(*OF->getSrcOriginID());
+        CheckOrigin(OF->getSrcOriginID());
         break;
       }
       case Fact::Kind::Use:
@@ -64,6 +63,9 @@ static llvm::BitVector computePersistentOrigins(const 
FactManager &FactMgr,
              Cur = Cur->peelOuterOrigin())
           CheckOrigin(Cur->getOuterOriginID());
         break;
+      case Fact::Kind::KillOrigin:
+        CheckOrigin(F->getAs<KillOriginFact>()->getKilledOrigin());
+        break;
       case Fact::Kind::MovedOrigin:
       case Fact::Kind::OriginEscapes:
       case Fact::Kind::Expire:
@@ -169,20 +171,23 @@ class AnalysisImpl
 
   /// A flow from source to destination. If `KillDest` is true, this replaces
   /// the destination's loans with the source's. Otherwise, the source's loans
-  /// are merged into the destination's. If the source is nullopt, this is a
-  /// kill-only flow that clears the destination without adding new loans.
+  /// are merged into the destination's.
   Lattice transfer(Lattice In, const OriginFlowFact &F) {
     OriginID DestOID = F.getDestOriginID();
+    OriginID SrcOID = F.getSrcOriginID();
 
     LoanSet DestLoans =
         F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID);
-    LoanSet SrcLoans = F.getSrcOriginID() ? getLoans(In, *F.getSrcOriginID())
-                                          : LoanSetFactory.getEmptySet();
+    LoanSet SrcLoans = getLoans(In, SrcOID);
     LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory);
 
     return setLoans(In, DestOID, MergedLoans);
   }
 
+  Lattice transfer(Lattice In, const KillOriginFact &F) {
+    return setLoans(In, F.getKilledOrigin(), LoanSetFactory.getEmptySet());
+  }
+
   Lattice transfer(Lattice In, const ExpireFact &F) {
     if (auto OID = F.getOriginID())
       return setLoans(In, *OID, LoanSetFactory.getEmptySet());

>From 1501529412eb4171e5ab98835e4e8c5224bdb7c2 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Sun, 12 Apr 2026 17:29:22 -0700
Subject: [PATCH 4/8] Add copy/move assign tests

---
 clang/test/Sema/Inputs/lifetime-analysis.h |  4 ++++
 clang/test/Sema/warn-lifetime-safety.cpp   | 19 +++++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h 
b/clang/test/Sema/Inputs/lifetime-analysis.h
index 58ba1aa0c03c3..2b904f88bc475 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -274,7 +274,11 @@ template<class R, class... Args>
 class function<R(Args...)> {
 public:
   template<class F> function(F) {}
+  function(const function&) {}
+  function(function&&) {}
   template<class F> function& operator=(F) { return *this; }
+  function& operator=(const function&) { return *this; }
+  function& operator=(function&&) { return *this; }
   ~function();
 };
 
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 634ab9a2af217..6fef2317bf078 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2547,6 +2547,25 @@ std::function<void()> copy_function() {
   return f2; // expected-note {{returned here}}
 }
 
+std::function<void()> copy_assign() {
+  int x;
+  std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function<void()> f2 = []() {};
+  f2 = f;
+  return f2; // 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()> f2 = []() {};
+  f2 = std::move(f);
+  return f2;
+}
+
 std::function<void()> reassign_safe_then_unsafe() {
   static int safe = 1;
   int local = 2;

>From 18c7a354c00fd71da967aad1076c8aff759933e0 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Sun, 12 Apr 2026 18:22:02 -0700
Subject: [PATCH 5/8] Add test function_ref_from_non_capturing_lambda

---
 clang/test/Sema/warn-lifetime-safety.cpp | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 6fef2317bf078..6c436ad4d576f 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2605,4 +2605,15 @@ std::function<void()> reassign_lambda_to_functor() {
   return f;
 }
 
+struct [[gsl::Pointer]] function_ref {
+  template <typename Callable>
+  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
+  void (*ref)();
+};
+
+function_ref function_ref_from_non_capturing_lambda() {
+  return []() {}; // expected-warning {{address of stack memory is returned 
later}} \
+                  // expected-note {{returned here}}
+}
+
 } // namespace callable_wrappers

>From a321deb9c5b2e825b5c51e6be6bc8232847a8cc0 Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Sun, 12 Apr 2026 20:59:02 -0700
Subject: [PATCH 6/8] add test

---
 clang/test/Sema/warn-lifetime-safety.cpp | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index 6c436ad4d576f..a29c688ddd0a2 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2616,4 +2616,9 @@ function_ref function_ref_from_non_capturing_lambda() {
                   // expected-note {{returned here}}
 }
 
+void assign_non_capturing_to_function_ref(function_ref &r) {
+  r = []() {};
+  (void)r;
+}
+
 } // namespace callable_wrappers

>From 99774ba859d6e1b965ee05f8522e63129fc0d04e Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Sun, 12 Apr 2026 21:19:04 -0700
Subject: [PATCH 7/8] update doc

---
 clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index d375e94ea9771..b70fecd5ab1d1 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -55,8 +55,7 @@ class Fact {
     OriginEscapes,
     /// An origin is invalidated (e.g. vector resized).
     InvalidateOrigin,
-    /// All loans are cleared from an origin (e.g., assigning a callable 
without
-    /// tracked origins to std::function).
+    /// All loans of an origin are cleared.
     KillOrigin,
   };
 
@@ -319,6 +318,8 @@ class TestPointFact : public Fact {
             const OriginManager &) const override;
 };
 
+/// All loans are cleared from an origin (e.g., assigning a callable without
+/// tracked origins to std::function).
 class KillOriginFact : public Fact {
   OriginID OID;
 

>From 320972424f0ae99fa2fd17c62b46eab38070d91a Mon Sep 17 00:00:00 2001
From: Zhijie Wang <[email protected]>
Date: Sun, 12 Apr 2026 21:33:31 -0700
Subject: [PATCH 8/8] update test

---
 clang/test/Sema/warn-lifetime-safety.cpp | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/clang/test/Sema/warn-lifetime-safety.cpp 
b/clang/test/Sema/warn-lifetime-safety.cpp
index a29c688ddd0a2..8a701f7860593 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -2611,14 +2611,10 @@ struct [[gsl::Pointer]] function_ref {
   void (*ref)();
 };
 
-function_ref function_ref_from_non_capturing_lambda() {
-  return []() {}; // expected-warning {{address of stack memory is returned 
later}} \
-                  // expected-note {{returned here}}
-}
-
 void assign_non_capturing_to_function_ref(function_ref &r) {
-  r = []() {};
-  (void)r;
+  r = []() {}; // expected-warning {{object whose reference is captured does 
not live long enough}} \
+               // expected-note {{destroyed here}}
+  (void)r; // expected-note {{later used here}}
 }
 
 } // namespace callable_wrappers

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to