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