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