[clang] [LifetimeSafety] Track origins through std::function (PR #191123)
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)
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)
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)
@@ -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)
@@ -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)
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)
@@ -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)
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)
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)
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)
@@ -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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
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)
@@ -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)
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)
@@ -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)
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)
@@ -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)
@@ -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)
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)
@@ -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)
@@ -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)
@@ -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)
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)
@@ -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)
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)
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)
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)
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)
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)
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)
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)
@@ -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
