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/2] [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/2] 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

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

Reply via email to