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 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" || 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 >From 0edd7cc4f78ee8a164110499eec1e7419767051a Mon Sep 17 00:00:00 2001 From: Zhijie Wang <[email protected]> Date: Sat, 11 Apr 2026 16:52:54 -0700 Subject: [PATCH 2/4] Add a kill-only mechanism (e.g., OriginFlowFact with no source) to clear old loans when the RHS has no origins --- .../clang/Analysis/Analyses/LifetimeSafety/Facts.h | 9 ++++++--- clang/lib/Analysis/LifetimeSafety/Facts.cpp | 10 ++++++---- clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp | 11 ++++++++--- clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 10 ++++++---- clang/lib/Analysis/LifetimeSafety/Origins.cpp | 11 ++++++++--- clang/test/Sema/warn-lifetime-safety.cpp | 5 ++--- 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 6be8f6e455bc2..265f783183553 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -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 + // loans. + std::optional<OriginID> OIDSrc; // True if the destination origin should be killed (i.e., its current loans // cleared) before the source origin's loans are flowed into it. bool KillDest; @@ -140,12 +142,13 @@ class OriginFlowFact : public Fact { return F->getKind() == Kind::OriginFlow; } - OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest) + OriginFlowFact(OriginID OIDDest, std::optional<OriginID> OIDSrc, + bool KillDest) : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc), KillDest(KillDest) {} OriginID getDestOriginID() const { return OIDDest; } - OriginID getSrcOriginID() const { return OIDSrc; } + std::optional<OriginID> getSrcOriginID() const { return OIDSrc; } bool getKillDest() const { return KillDest; } void dump(llvm::raw_ostream &OS, const LoanManager &, diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 1bc0521a72359..7a04fd1dfe2f2 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -43,10 +43,12 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &, OS << "\tDest: "; OM.dump(getDestOriginID(), OS); OS << "\n"; - OS << "\tSrc: "; - OM.dump(getSrcOriginID(), OS); - OS << (getKillDest() ? "" : ", Merge"); - OS << "\n"; + if (getSrcOriginID()) { + OS << "\tSrc: "; + OM.dump(*getSrcOriginID(), OS); + OS << (getKillDest() ? "" : ", Merge"); + OS << "\n"; + } } void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 9bf85f8cf0b41..f20a8493e7215 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -404,10 +404,15 @@ 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) + if (!RHSList) { + // RHS has no tracked origins (e.g., assigning a non-lambda functor to a + // std::function). Emit a kill-only flow to clear old loans. + if (OriginList *LHSInner = LHSList->peelOuterOrigin()) + CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( + LHSInner->getOuterOriginID(), /*OIDSrc=*/std::nullopt, + /*KillDest=*/true)); return; + } // Kill the old loans of the destination origin and flow the new loans // from the source origin. flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true); diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index e437fb7d41268..a53e8d0419282 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -55,7 +55,8 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr, case Fact::Kind::OriginFlow: { const auto *OF = F->getAs<OriginFlowFact>(); CheckOrigin(OF->getDestOriginID()); - CheckOrigin(OF->getSrcOriginID()); + if (OF->getSrcOriginID()) + CheckOrigin(*OF->getSrcOriginID()); break; } case Fact::Kind::Use: @@ -168,14 +169,15 @@ class AnalysisImpl /// A flow from source to destination. If `KillDest` is true, this replaces /// the destination's loans with the source's. Otherwise, the source's loans - /// are merged into the destination's. + /// are merged into the destination's. If the source is nullopt, this is a + /// kill-only flow that clears the destination without adding new loans. Lattice transfer(Lattice In, const OriginFlowFact &F) { OriginID DestOID = F.getDestOriginID(); - OriginID SrcOID = F.getSrcOriginID(); LoanSet DestLoans = F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID); - LoanSet SrcLoans = getLoans(In, SrcOID); + LoanSet SrcLoans = F.getSrcOriginID() ? getLoans(In, *F.getSrcOriginID()) + : LoanSetFactory.getEmptySet(); LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory); return setLoans(In, DestOID, MergedLoans); diff --git a/clang/lib/Analysis/LifetimeSafety/Origins.cpp b/clang/lib/Analysis/LifetimeSafety/Origins.cpp index c0336ec14db7b..b75ec256e65c1 100644 --- a/clang/lib/Analysis/LifetimeSafety/Origins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Origins.cpp @@ -111,9 +111,14 @@ bool OriginManager::hasOrigins(QualType QT) const { // stored lambda's origins. if (isStdCallableWrapperType(RD)) return true; - // TODO: Extend origin tracking to user-defined structs that may carry - // origins. - return RD->isLambda(); + // 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; } /// Determines if an expression has origins that need to be tracked. diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index dd2b343477ef0..634ab9a2af217 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -2578,13 +2578,12 @@ std::function<void()> reassign_lambda_to_function_pointer() { 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}} + std::function<void()> f = [&local]() { (void)local; }; f = c; - return f; // expected-note {{returned here}} + return f; } } // namespace callable_wrappers >From 60f86172f10849bbfbb11d3b9ba561e19d167c55 Mon Sep 17 00:00:00 2001 From: Zhijie Wang <[email protected]> Date: Sun, 12 Apr 2026 14:49:47 -0700 Subject: [PATCH 3/4] Add a separate KillOriginFact --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 28 +++++++++++++++---- clang/lib/Analysis/LifetimeSafety/Dataflow.h | 3 ++ clang/lib/Analysis/LifetimeSafety/Facts.cpp | 17 +++++++---- .../LifetimeSafety/FactsGenerator.cpp | 12 ++++---- .../Analysis/LifetimeSafety/LiveOrigins.cpp | 4 +++ .../LifetimeSafety/LoanPropagation.cpp | 17 +++++++---- 6 files changed, 57 insertions(+), 24 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 265f783183553..d375e94ea9771 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -55,6 +55,9 @@ class Fact { OriginEscapes, /// An origin is invalidated (e.g. vector resized). InvalidateOrigin, + /// All loans are cleared from an origin (e.g., assigning a callable without + /// tracked origins to std::function). + KillOrigin, }; private: @@ -130,9 +133,7 @@ class ExpireFact : public Fact { class OriginFlowFact : public Fact { OriginID OIDDest; - // The source origin to flow from. Absent when only clearing the destination's - // loans. - std::optional<OriginID> OIDSrc; + OriginID OIDSrc; // True if the destination origin should be killed (i.e., its current loans // cleared) before the source origin's loans are flowed into it. bool KillDest; @@ -142,13 +143,12 @@ class OriginFlowFact : public Fact { return F->getKind() == Kind::OriginFlow; } - OriginFlowFact(OriginID OIDDest, std::optional<OriginID> OIDSrc, - bool KillDest) + OriginFlowFact(OriginID OIDDest, OriginID OIDSrc, bool KillDest) : Fact(Kind::OriginFlow), OIDDest(OIDDest), OIDSrc(OIDSrc), KillDest(KillDest) {} OriginID getDestOriginID() const { return OIDDest; } - std::optional<OriginID> getSrcOriginID() const { return OIDSrc; } + OriginID getSrcOriginID() const { return OIDSrc; } bool getKillDest() const { return KillDest; } void dump(llvm::raw_ostream &OS, const LoanManager &, @@ -319,6 +319,22 @@ class TestPointFact : public Fact { const OriginManager &) const override; }; +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/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 7a04fd1dfe2f2..3d7fbcdacc830 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -43,12 +43,10 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &, OS << "\tDest: "; OM.dump(getDestOriginID(), OS); OS << "\n"; - if (getSrcOriginID()) { - OS << "\tSrc: "; - OM.dump(*getSrcOriginID(), OS); - OS << (getKillDest() ? "" : ", Merge"); - OS << "\n"; - } + OS << "\tSrc: "; + OM.dump(getSrcOriginID(), OS); + OS << (getKillDest() ? "" : ", Merge"); + OS << "\n"; } void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &, @@ -105,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 f20a8493e7215..eb5f1e9ccc045 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -405,12 +405,12 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr, markUseAsWrite(DRE_LHS); } if (!RHSList) { - // RHS has no tracked origins (e.g., assigning a non-lambda functor to a - // std::function). Emit a kill-only flow to clear old loans. - if (OriginList *LHSInner = LHSList->peelOuterOrigin()) - CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>( - LHSInner->getOuterOriginID(), /*OIDSrc=*/std::nullopt, - /*KillDest=*/true)); + // 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 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 a53e8d0419282..adbc0458516e1 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -55,8 +55,7 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr, case Fact::Kind::OriginFlow: { const auto *OF = F->getAs<OriginFlowFact>(); CheckOrigin(OF->getDestOriginID()); - if (OF->getSrcOriginID()) - CheckOrigin(*OF->getSrcOriginID()); + CheckOrigin(OF->getSrcOriginID()); break; } case Fact::Kind::Use: @@ -64,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: @@ -169,20 +171,23 @@ class AnalysisImpl /// A flow from source to destination. If `KillDest` is true, this replaces /// the destination's loans with the source's. Otherwise, the source's loans - /// are merged into the destination's. If the source is nullopt, this is a - /// kill-only flow that clears the destination without adding new loans. + /// are merged into the destination's. Lattice transfer(Lattice In, const OriginFlowFact &F) { OriginID DestOID = F.getDestOriginID(); + OriginID SrcOID = F.getSrcOriginID(); LoanSet DestLoans = F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID); - LoanSet SrcLoans = F.getSrcOriginID() ? getLoans(In, *F.getSrcOriginID()) - : LoanSetFactory.getEmptySet(); + LoanSet SrcLoans = getLoans(In, SrcOID); LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory); 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()); >From 1501529412eb4171e5ab98835e4e8c5224bdb7c2 Mon Sep 17 00:00:00 2001 From: Zhijie Wang <[email protected]> Date: Sun, 12 Apr 2026 17:29:22 -0700 Subject: [PATCH 4/4] Add copy/move assign tests --- clang/test/Sema/Inputs/lifetime-analysis.h | 4 ++++ clang/test/Sema/warn-lifetime-safety.cpp | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h index 58ba1aa0c03c3..2b904f88bc475 100644 --- a/clang/test/Sema/Inputs/lifetime-analysis.h +++ b/clang/test/Sema/Inputs/lifetime-analysis.h @@ -274,7 +274,11 @@ 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.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 634ab9a2af217..6fef2317bf078 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -2547,6 +2547,25 @@ std::function<void()> copy_function() { 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; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
