Author: Zhijie Wang Date: 2026-04-14T15:01:35+05:30 New Revision: 1b804e20b9d258c8f0b454d43013cf2300f0df26
URL: https://github.com/llvm/llvm-project/commit/1b804e20b9d258c8f0b454d43013cf2300f0df26 DIFF: https://github.com/llvm/llvm-project/commit/1b804e20b9d258c8f0b454d43013cf2300f0df26.diff LOG: [LifetimeSafety] Track origins through std::function (#191123) 1. Recognizes `std::function` and `std::move_only_function` as types that can carry origins from a wrapped lambda's captures, propagating origins through both construction and assignment. 2. Adds a kill-only mechanism (i.e., a new `KillOriginFact`) to clear old loans when the RHS has no origins. Fixes #186009 Added: Modified: clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h clang/lib/Analysis/LifetimeSafety/Dataflow.h clang/lib/Analysis/LifetimeSafety/Facts.cpp clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp clang/lib/Analysis/LifetimeSafety/Origins.cpp clang/test/Sema/Inputs/lifetime-analysis.h clang/test/Sema/warn-lifetime-safety-dangling-field.cpp clang/test/Sema/warn-lifetime-safety-invalidations.cpp clang/test/Sema/warn-lifetime-safety-noescape.cpp clang/test/Sema/warn-lifetime-safety-suggestions.cpp clang/test/Sema/warn-lifetime-safety.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 6be8f6e455bc2..b70fecd5ab1d1 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -55,6 +55,8 @@ class Fact { OriginEscapes, /// An origin is invalidated (e.g. vector resized). InvalidateOrigin, + /// All loans of an origin are cleared. + KillOrigin, }; private: @@ -316,6 +318,24 @@ class TestPointFact : public Fact { const OriginManager &) const override; }; +/// All loans are cleared from an origin (e.g., assigning a callable without +/// tracked origins to std::function). +class KillOriginFact : public Fact { + OriginID OID; + +public: + static bool classof(const Fact *F) { + return F->getKind() == Kind::KillOrigin; + } + + KillOriginFact(OriginID OID) : Fact(Kind::KillOrigin), OID(OID) {} + + OriginID getKilledOrigin() const { return OID; } + + void dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const override; +}; + class FactManager { public: FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) : OriginMgr(AC) { 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/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h index 0f64ac8a36ef7..fc3049c8bec84 100644 --- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h +++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h @@ -180,6 +180,8 @@ class DataflowAnalysis { return D->transfer(In, *F->getAs<TestPointFact>()); case Fact::Kind::InvalidateOrigin: return D->transfer(In, *F->getAs<InvalidateOriginFact>()); + case Fact::Kind::KillOrigin: + return D->transfer(In, *F->getAs<KillOriginFact>()); } llvm_unreachable("Unknown fact kind"); } @@ -193,6 +195,7 @@ class DataflowAnalysis { Lattice transfer(Lattice In, const UseFact &) { return In; } Lattice transfer(Lattice In, const TestPointFact &) { return In; } Lattice transfer(Lattice In, const InvalidateOriginFact &) { return In; } + Lattice transfer(Lattice In, const KillOriginFact &) { return In; } }; } // namespace clang::lifetimes::internal #endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_DATAFLOW_H diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 1bc0521a72359..3d7fbcdacc830 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -103,6 +103,13 @@ void TestPointFact::dump(llvm::raw_ostream &OS, const LoanManager &, OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n"; } +void KillOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, + const OriginManager &OM) const { + OS << "KillOrigin ("; + OM.dump(getKilledOrigin(), OS); + OS << ")\n"; +} + llvm::StringMap<ProgramPoint> FactManager::getTestPoints() const { llvm::StringMap<ProgramPoint> AnnotationToPointMap; for (const auto &BlockFacts : BlockToFacts) { diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index cae56ddd3d7c3..fcc6035c3c1c8 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,15 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr, } else markUseAsWrite(DRE_LHS); } + if (!RHSList) { + // RHS has no tracked origins (e.g., assigning a callable without origins + // to std::function). Clear loans of the destination. + for (OriginList *LHSInner = LHSList->peelOuterOrigin(); LHSInner; + LHSInner = LHSInner->peelOuterOrigin()) + CurrentBlockFacts.push_back( + FactMgr.createFact<KillOriginFact>(LHSInner->getOuterOriginID())); + 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 +511,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/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp index bc7494360624e..cfbcacf04b1b0 100644 --- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp @@ -166,6 +166,10 @@ class AnalysisImpl return Lattice(Factory.remove(In.LiveOrigins, OF.getDestOriginID())); } + Lattice transfer(Lattice In, const KillOriginFact &F) { + return Lattice(Factory.remove(In.LiveOrigins, F.getKilledOrigin())); + } + Lattice transfer(Lattice In, const ExpireFact &F) { if (auto OID = F.getOriginID()) return Lattice(Factory.remove(In.LiveOrigins, *OID)); diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index e437fb7d41268..adbc0458516e1 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -63,6 +63,9 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr, Cur = Cur->peelOuterOrigin()) CheckOrigin(Cur->getOuterOriginID()); break; + case Fact::Kind::KillOrigin: + CheckOrigin(F->getAs<KillOriginFact>()->getKilledOrigin()); + break; case Fact::Kind::MovedOrigin: case Fact::Kind::OriginEscapes: case Fact::Kind::Expire: @@ -181,6 +184,10 @@ class AnalysisImpl return setLoans(In, DestOID, MergedLoans); } + Lattice transfer(Lattice In, const KillOriginFact &F) { + return setLoans(In, F.getKilledOrigin(), LoanSetFactory.getEmptySet()); + } + Lattice transfer(Lattice In, const ExpireFact &F) { if (auto OID = F.getOriginID()) return setLoans(In, *OID, LoanSetFactory.getEmptySet()); diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index fdd2671dee2e0..033cbdd75352c 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -107,6 +107,10 @@ bool OriginManager::hasOrigins(QualType QT) const { const auto *RD = QT->getAsCXXRecordDecl(); if (!RD) return false; + // Standard library callable wrappers (e.g., std::function) can propagate the + // stored lambda's origins. + if (isStdCallableWrapperType(RD)) + return true; // TODO: Limit to lambdas for now. This will be extended to user-defined // structs with pointer-like fields. if (!RD->isLambda()) diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h index d1e847d20cc50..2b904f88bc475 100644 --- a/clang/test/Sema/Inputs/lifetime-analysis.h +++ b/clang/test/Sema/Inputs/lifetime-analysis.h @@ -268,4 +268,18 @@ 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) {} + function(const function&) {} + function(function&&) {} + template<class F> function& operator=(F) { return *this; } + function& operator=(const function&) { return *this; } + function& operator=(function&&) { return *this; } + ~function(); +}; + } diff --git a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp index 79b0183ed91ec..2afcd4a3dd97b 100644 --- a/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp +++ b/clang/test/Sema/warn-lifetime-safety-dangling-field.cpp @@ -196,3 +196,16 @@ struct DerivedWithCtor : BaseWithPointer { } }; } // namespace DanglingPointerFieldInBaseClass + +namespace callable_wrappers { + +struct HasCallback { + std::function<void()> callback; // expected-note {{this field dangles}} + + void set_callback() { + int local; + callback = [&local]() { (void)local; }; // expected-warning {{address of stack memory escapes to a field}} + } +}; + +} // namespace callable_wrappers diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp index 5a77ac97d16b1..973e095fb68b4 100644 --- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp +++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp @@ -527,3 +527,15 @@ struct S { } }; } // namespace method_call_uses_field_invalidation + +namespace callable_wrappers { + +void function_captured_ref_invalidated() { + std::vector<int> v; + v.push_back(1); + std::function<void()> f = [&r = v[0]]() { (void)r; }; // expected-warning {{object whose reference is captured is later invalidated}} + v.push_back(2); // expected-note {{invalidated here}} + (void)f; // expected-note {{later used here}} +} + +} // namespace callable_wrappers diff --git a/clang/test/Sema/warn-lifetime-safety-noescape.cpp b/clang/test/Sema/warn-lifetime-safety-noescape.cpp index f233ec546faa5..4bb57e6b9df95 100644 --- a/clang/test/Sema/warn-lifetime-safety-noescape.cpp +++ b/clang/test/Sema/warn-lifetime-safety-noescape.cpp @@ -191,3 +191,11 @@ MyObj& return_ref_from_noescape_ptr( int* return_spaced_brackets(int* p [ [clang::noescape] /*some comment*/ ]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} return p; // expected-note {{returned here}} } + +namespace callable_wrappers { + +std::function<void()> escape_noescape_via_function(int &x [[clang::noescape]]) { // expected-warning {{parameter is marked [[clang::noescape]] but escapes}} + return [&]() { (void)x; }; // expected-note {{returned here}} +} + +} // namespace callable_wrappers diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp index b3b13038dc344..d2cf1c175eb57 100644 --- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp +++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp @@ -1,8 +1,8 @@ // RUN: rm -rf %t // RUN: split-file %s %t -// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -verify %t/test_source.cpp -// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -fixit %t/test_source.cpp -// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%t -Werror=lifetime-safety-suggestions %t/test_source.cpp +// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -verify %t/test_source.cpp +// RUN: %clang_cc1 -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wlifetime-safety -Wno-dangling -I%t -I%S -fixit %t/test_source.cpp +// RUN: %clang_cc1 -fsyntax-only -flifetime-safety-inference -fexperimental-lifetime-safety-tu-analysis -Wlifetime-safety-suggestions -Wno-dangling -I%t -I%S -Werror=lifetime-safety-suggestions %t/test_source.cpp View definition_before_header(View a); @@ -67,6 +67,7 @@ struct ReturnThisPointer { //--- test_source.cpp #include "test_header.h" +#include "Inputs/lifetime-analysis.h" View definition_before_header(View a) { return a; // expected-note {{param returned here}} @@ -529,3 +530,20 @@ CaptureRefToBaseView test_ref_to_base_view() { return x; // expected-note {{returned here}} } } // namespace capturing_constructor + +namespace callable_wrappers { + +std::function<void()> return_lambda_capturing_param(int &x) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}} + return [&]() { (void)x; }; // expected-note {{param returned here}} +} + +void uaf_via_inferred_lifetimebound() { + std::function<void()> f = []() {}; + { + int local; + f = return_lambda_capturing_param(local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)f; // expected-note {{later used here}} +} + +} // namespace callable_wrappers diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index f87b5cbdd0230..c083c30f5855d 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -2619,3 +2619,110 @@ struct Y : X { } }; } // namespace base_class_fields + +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()> copy_assign() { + int x; + std::function<void()> f = [&x]() { (void)x; }; // expected-warning {{address of stack memory is returned later}} + std::function<void()> 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<void()> move_assign() { + int x; + std::function<void()> f = [&x]() { (void)x; }; // Should warn. + std::function<void()> f2 = []() {}; + f2 = std::move(f); + return f2; +} + +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; }; + +std::function<void()> reassign_lambda_to_functor() { + int local; + Functor c; + std::function<void()> f = [&local]() { (void)local; }; + f = c; + return f; +} + +std::function<void()> capture_lifetimebound_param(int &x [[clang::lifetimebound]]) { + return [&]() { (void)x; }; +} + +void uaf_via_lifetimebound() { + std::function<void()> f = []() {}; + { + int local; + f = capture_lifetimebound_param(local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)f; // expected-note {{later used here}} +} + +} // namespace callable_wrappers + +namespace GH126600 { +struct [[gsl::Pointer]] function_ref { + template <typename Callable> + function_ref(Callable &&callable [[clang::lifetimebound]]) : ref(callable) {} + void (*ref)(); +}; + +// FIXME: The lifetimebound annotation tracks the outer callable object's +// storage rather than what the callable captures. A mechanism like +// lifetimebound(2) could enable tracking inner lifetimes, which would +// avoid this warning for non-capturing lambdas. +void assign_non_capturing_to_function_ref(function_ref &r) { + r = []() {}; // expected-warning {{object whose reference is captured does not live long enough}} \ + // expected-note {{destroyed here}} + (void)r; // expected-note {{later used here}} +} + +} // namespace GH126600 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
