[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-14 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 closed https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-14 Thread Zhijie Wang via cfe-commits

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

>From 253e52cec163bea19c391a4122cab168826079d6 Mon Sep 17 00:00:00 2001
From: Zhijie Wang 
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 01/12] [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 cae56ddd3d7c3..038e3e36c6d56 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);
@@ -400,6 +409,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);
@@ -493,6 +506,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" ||

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-14 Thread Utkarsh Saxena via cfe-commits

usx95 wrote:

Happy to land this once the conflicts have been resolved.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-14 Thread Utkarsh Saxena via cfe-commits


@@ -67,6 +67,7 @@ struct ReturnThisPointer {
 //--- test_source.cpp
 
 #include "test_header.h"
+#include "Inputs/lifetime-analysis.h"

usx95 wrote:

Should be fine. I already have a pending patch which needed to do this.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-13 Thread Zhijie Wang via cfe-commits


@@ -67,6 +67,7 @@ struct ReturnThisPointer {
 //--- test_source.cpp
 
 #include "test_header.h"
+#include "Inputs/lifetime-analysis.h"

aeft wrote:

@usx95 Is it safe to include `lifetime-analysis.h` here? Do you have any 
suggestion? I include it because I need to reuse std::function definition.

Unlike other tests, this file has a fixit RUN line that might modify the 
source. I guess this why this has specific test_header.h/test_source.cpp

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 edited https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-13 Thread Utkarsh Saxena via cfe-commits


@@ -268,4 +268,14 @@ struct true_type {
 template struct is_pointer : false_type {};
 template struct is_pointer : true_type {};
 template struct is_pointer : true_type {};
+
+template class function;
+template
+class function {
+public:
+  template function(F) {}
+  template function& operator=(F) { return *this; }

usx95 wrote:

> move assign has FN. This seems an existing limitation. e.g., 
> https://godbolt.org/z/rETxncchf

LG.  In a separate PR, we can special case `std::move` to propagate inner 
origin list. (looks like a use-case for lifetimebound(2)).

Filed https://github.com/llvm/llvm-project/issues/191954

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-13 Thread Utkarsh Saxena via cfe-commits

https://github.com/usx95 approved this pull request.

Thanks for adding more tests. This looks great to me.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-13 Thread Zhijie Wang via cfe-commits

https://github.com/aeft edited https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-13 Thread Zhijie Wang via cfe-commits

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

>From a1778114f5e7214562e5c7832f255f968c9fda99 Mon Sep 17 00:00:00 2001
From: Zhijie Wang 
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 01/12] [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 2a2ef88987286..8f86f35ca2f34 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);
@@ -400,6 +409,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);
@@ -493,6 +506,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" ||

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Utkarsh Saxena via cfe-commits


@@ -2531,3 +2531,98 @@ int *noreturn_dead_nested(bool cond, bool cond2, int 
*num) {
 }
 
 } // namespace conditional_operator_control_flow
+

usx95 wrote:

Can you also add a test for 

- Lifetime Suggestions

```cpp
std::function direct_return(const int& x) { // suggest lifetime bound 
here.
  return [&]() { (void)x; };
}
```

- Similarly detect UAF due to the inference of the above.
- Noescape: Same as above but `x` annotated as `noescape`. Should warn that 
noescape escapes.
- Dangling field: `std::function` as a field which is assigned a lambda 
capturing a local.
- Invalidation: function captures a reference to data inside a vector. the 
function is called after vector is modifed.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Utkarsh Saxena via cfe-commits


@@ -2531,3 +2531,98 @@ int *noreturn_dead_nested(bool cond, bool cond2, int 
*num) {
 }
 
 } // namespace conditional_operator_control_flow
+
+namespace callable_wrappers {
+
+std::function direct_return() {
+  int x;
+  return [&x]() { (void)x; }; // expected-warning {{address of stack memory is 
returned later}} \
+  // expected-note {{returned here}}
+}
+
+std::function copy_function() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function f2 = f;
+  return f2; // expected-note {{returned here}}
+}
+
+std::function copy_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function 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 move_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // Should warn.
+  std::function f2 = []() {};
+  f2 = std::move(f);
+  return f2;
+}
+
+std::function reassign_safe_then_unsafe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = []() { (void)safe; };
+  f = [&local]() { (void)local; }; // expected-warning {{address of stack 
memory is returned later}}
+  return f; // expected-note {{returned here}}
+}
+
+std::function reassign_unsafe_then_safe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = [&local]() { (void)local; };
+  f = []() { (void)safe; };
+  return f;
+}
+
+std::function non_capturing_lambda() {
+  return []() {};
+}
+
+void free_function();
+
+std::function reassign_lambda_to_function_pointer() {
+  int local;
+  std::function f = [&local]() { (void)local; };
+  f = &free_function;
+  return f;
+}
+
+struct Functor { void operator()() const; };
+
+std::function reassign_lambda_to_functor() {
+  int local;
+  Functor c;
+  std::function f = [&local]() { (void)local; };
+  f = c;
+  return f;
+}
+
+} // namespace callable_wrappers
+
+namespace GH126600 {
+// https://github.com/llvm/llvm-project/issues/126600

usx95 wrote:

nit: remove the link as the namespace is sufficient to document this.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Utkarsh Saxena via cfe-commits


@@ -2531,3 +2531,98 @@ int *noreturn_dead_nested(bool cond, bool cond2, int 
*num) {
 }
 
 } // namespace conditional_operator_control_flow
+
+namespace callable_wrappers {
+
+std::function direct_return() {

usx95 wrote:

nit: add a test like this which does not capture any local.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits


@@ -2531,3 +2531,90 @@ int *noreturn_dead_nested(bool cond, bool cond2, int 
*num) {
 }
 
 } // namespace conditional_operator_control_flow
+
+namespace callable_wrappers {
+
+std::function direct_return() {
+  int x;
+  return [&x]() { (void)x; }; // expected-warning {{address of stack memory is 
returned later}} \
+  // expected-note {{returned here}}
+}
+
+std::function copy_function() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function f2 = f;
+  return f2; // expected-note {{returned here}}
+}
+
+std::function copy_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function 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 move_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // Should warn.
+  std::function f2 = []() {};
+  f2 = std::move(f);
+  return f2;
+}
+
+std::function reassign_safe_then_unsafe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = []() { (void)safe; };
+  f = [&local]() { (void)local; }; // expected-warning {{address of stack 
memory is returned later}}
+  return f; // expected-note {{returned here}}
+}
+
+std::function reassign_unsafe_then_safe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = [&local]() { (void)local; };
+  f = []() { (void)safe; };
+  return f;
+}
+
+std::function non_capturing_lambda() {
+  return []() {};
+}
+
+void free_function();
+
+std::function reassign_lambda_to_function_pointer() {
+  int local;
+  std::function f = [&local]() { (void)local; };
+  f = &free_function;
+  return f;
+}
+
+struct Functor { void operator()() const; };
+
+std::function reassign_lambda_to_functor() {
+  int local;
+  Functor c;
+  std::function f = [&local]() { (void)local; };
+  f = c;
+  return f;
+}
+
+struct [[gsl::Pointer]] function_ref {
+  template 
+  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
+  void (*ref)();
+};
+
+void assign_non_capturing_to_function_ref(function_ref &r) {

aeft wrote:

Done

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits

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 
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 1/9] [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" || N

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Utkarsh Saxena via cfe-commits


@@ -2531,3 +2531,90 @@ int *noreturn_dead_nested(bool cond, bool cond2, int 
*num) {
 }
 
 } // namespace conditional_operator_control_flow
+
+namespace callable_wrappers {
+
+std::function direct_return() {
+  int x;
+  return [&x]() { (void)x; }; // expected-warning {{address of stack memory is 
returned later}} \
+  // expected-note {{returned here}}
+}
+
+std::function copy_function() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function f2 = f;
+  return f2; // expected-note {{returned here}}
+}
+
+std::function copy_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function 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 move_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // Should warn.
+  std::function f2 = []() {};
+  f2 = std::move(f);
+  return f2;
+}
+
+std::function reassign_safe_then_unsafe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = []() { (void)safe; };
+  f = [&local]() { (void)local; }; // expected-warning {{address of stack 
memory is returned later}}
+  return f; // expected-note {{returned here}}
+}
+
+std::function reassign_unsafe_then_safe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = [&local]() { (void)local; };
+  f = []() { (void)safe; };
+  return f;
+}
+
+std::function non_capturing_lambda() {
+  return []() {};
+}
+
+void free_function();
+
+std::function reassign_lambda_to_function_pointer() {
+  int local;
+  std::function f = [&local]() { (void)local; };
+  f = &free_function;
+  return f;
+}
+
+struct Functor { void operator()() const; };
+
+std::function reassign_lambda_to_functor() {
+  int local;
+  Functor c;
+  std::function f = [&local]() { (void)local; };
+  f = c;
+  return f;
+}
+
+struct [[gsl::Pointer]] function_ref {
+  template 
+  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
+  void (*ref)();
+};
+
+void assign_non_capturing_to_function_ref(function_ref &r) {

usx95 wrote:

Yes.

Can you enclose this in a namespace GH126600

Please also add a FIXME. This looks like we need annotate inner callable 
instead of the outer reference. Also a case for lifetimebound(2) or similar 
solution for inner lifetimes.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits


@@ -2531,3 +2531,90 @@ int *noreturn_dead_nested(bool cond, bool cond2, int 
*num) {
 }
 
 } // namespace conditional_operator_control_flow
+
+namespace callable_wrappers {
+
+std::function direct_return() {
+  int x;
+  return [&x]() { (void)x; }; // expected-warning {{address of stack memory is 
returned later}} \
+  // expected-note {{returned here}}
+}
+
+std::function copy_function() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function f2 = f;
+  return f2; // expected-note {{returned here}}
+}
+
+std::function copy_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
+  std::function 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 move_assign() {
+  int x;
+  std::function f = [&x]() { (void)x; }; // Should warn.
+  std::function f2 = []() {};
+  f2 = std::move(f);
+  return f2;
+}
+
+std::function reassign_safe_then_unsafe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = []() { (void)safe; };
+  f = [&local]() { (void)local; }; // expected-warning {{address of stack 
memory is returned later}}
+  return f; // expected-note {{returned here}}
+}
+
+std::function reassign_unsafe_then_safe() {
+  static int safe = 1;
+  int local = 2;
+  std::function f = [&local]() { (void)local; };
+  f = []() { (void)safe; };
+  return f;
+}
+
+std::function non_capturing_lambda() {
+  return []() {};
+}
+
+void free_function();
+
+std::function reassign_lambda_to_function_pointer() {
+  int local;
+  std::function f = [&local]() { (void)local; };
+  f = &free_function;
+  return f;
+}
+
+struct Functor { void operator()() const; };
+
+std::function reassign_lambda_to_functor() {
+  int local;
+  Functor c;
+  std::function f = [&local]() { (void)local; };
+  f = c;
+  return f;
+}
+
+struct [[gsl::Pointer]] function_ref {
+  template 
+  function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {}
+  void (*ref)();
+};
+
+void assign_non_capturing_to_function_ref(function_ref &r) {

aeft wrote:

@usx95 Is this test you were asking for in 
https://github.com/llvm/llvm-project/issues/126600#issuecomment-4216573661?

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits

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 
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" || N

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits


@@ -316,6 +319,22 @@ class TestPointFact : public Fact {
 const OriginManager &) const override;
 };
 
+class KillOriginFact : public Fact {

aeft wrote:

Done. I omitted it before because it overlaps with the comment for 
`Kind::KillOrigin`.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits

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 
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 1/7] [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" || N

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Utkarsh Saxena via cfe-commits


@@ -316,6 +319,22 @@ class TestPointFact : public Fact {
 const OriginManager &) const override;
 };
 
+class KillOriginFact : public Fact {

usx95 wrote:

Please add a comment to describe this.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits

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 
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 1/5] [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" || N

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits


@@ -268,4 +268,14 @@ struct true_type {
 template struct is_pointer : false_type {};
 template struct is_pointer : true_type {};
 template struct is_pointer : true_type {};
+
+template class function;
+template
+class function {
+public:
+  template function(F) {}
+  template function& operator=(F) { return *this; }

aeft wrote:

Added both tests.

```cpp
std::function copy_assign() {
  int x;
  std::function f = [&x]() { (void)x; }; // expected-warning {{address 
of stack memory is returned later}}
  std::function 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 move_assign() {
  int x;
  std::function f = [&x]() { (void)x; }; // Should warn.
  std::function f2 = []() {};
  f2 = std::move(f);
  return f2;
}
```

move assign has FN. This seems an existing limitation. e.g., 
https://godbolt.org/z/rETxncchf
```cpp
int* test2() {
  int x;
  int* f = &x; // Should warn.
  int* a;
  a = std::move(f);
  return a;
}
```

cc @usx95 

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits


@@ -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

aeft wrote:

Done

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Zhijie Wang via cfe-commits

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 
Date: Thu, 9 Apr 2026 00:02:22 -0700
Subject: [PATCH 1/4] [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" || N

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Utkarsh Saxena via cfe-commits


@@ -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

usx95 wrote:

Having a separate `KillOriginFact` makes sense to me and sounds clearer than 
trying to fit this in existing flow fact.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Gábor Horváth via cfe-commits


@@ -268,4 +268,14 @@ struct true_type {
 template struct is_pointer : false_type {};
 template struct is_pointer : true_type {};
 template struct is_pointer : true_type {};
+
+template class function;
+template
+class function {
+public:
+  template function(F) {}
+  template function& operator=(F) { return *this; }

Xazax-hun wrote:

The actual `std::function` has more `operator=` overloads. I think at minimum 
we should test both copy and move assignments. 

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Gábor Horváth via cfe-commits


@@ -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

Xazax-hun wrote:

Hmm, we probably have a lot of flow facts and making the Src optional will 
increase the size of this type. I wonder if it would be more efficient to have 
a new OriginKillFact instead. @usx95 what do you think?

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Gábor Horváth via cfe-commits

https://github.com/Xazax-hun edited 
https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Gábor Horváth via cfe-commits


@@ -395,6 +404,15 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
 } else
   markUseAsWrite(DRE_LHS);
   }
+  if (!RHSList) {

Xazax-hun wrote:

Interestingly, `std::function` used to have an `assign` method that got removed 
later. Maybe it is not that important to handle it given that it was removed.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-12 Thread Gábor Horváth via cfe-commits

https://github.com/Xazax-hun commented:

I have one major question inline, whether we want to have a new kind of fact or 
modify the existing flow fact. Otherwise, the overall direction looks good to 
me. 

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-11 Thread Zhijie Wang via cfe-commits

aeft wrote:

@usx95 @Xazax-hun Added a kill-only mechanism and the FP is addressed. Could 
you please have a look when you have a chance?

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-11 Thread Zhijie Wang via cfe-commits

https://github.com/aeft edited https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-11 Thread Zhijie Wang via cfe-commits

https://github.com/aeft edited https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-11 Thread Zhijie Wang via cfe-commits

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 
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" || N

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-11 Thread Zhijie Wang via cfe-commits

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

[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-09 Thread Gábor Horváth via cfe-commits

Xazax-hun wrote:

> Add a kill-only mechanism (e.g., OriginFlowFact with no source) to clear old 
> loans when the RHS has no origins.


I'd prefer this solution but wait for @usx95 to chime in. 

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [LifetimeSafety] Track origins through std::function (PR #191123)

2026-04-09 Thread Zhijie Wang via cfe-commits


@@ -373,27 +382,20 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr,
 
   if (const auto *DRE_LHS = dyn_cast(LHSExpr))
 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;

aeft wrote:

Yes. Please see my comment for this pr. I have discussed this.

https://github.com/llvm/llvm-project/pull/191123
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits