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

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

Reply via email to