https://github.com/suoyuan666 updated https://github.com/llvm/llvm-project/pull/204592
>From 66720433b6f0629878dc24f2bd3443b2ddaea33e Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Wed, 17 Jun 2026 18:04:06 +0800 Subject: [PATCH 01/19] [LifetimeSafety] Add multi-block support to buildOriginFlowChain After introducing `buildOriginFlowChain` to use-after-scope diagnostics, it should support multi-block analysis. This also allows it to be reused by other diagnostics. In some loops, UseFact may appear before OriginFlowFact: ```cpp void for_loop_use_before_loop_body(MyObj safe) { MyObj* p = &safe; for (int i = 0; i < 1; ++i) { (void)*p; MyObj s; p = &s; } (void)*p; } ``` So, I remove the `StartPoint` parameter to correctly find the `OriginID`. Signed-off-by: Yuan Suo <[email protected]> --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 1 + .../Analyses/LifetimeSafety/LoanPropagation.h | 7 +- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 6 +- clang/lib/Analysis/LifetimeSafety/Facts.cpp | 15 ++- .../LifetimeSafety/LoanPropagation.cpp | 99 ++++++++++++++----- clang/lib/Sema/SemaLifetimeSafety.h | 4 +- clang/test/Sema/LifetimeSafety/safety-c.c | 4 +- clang/test/Sema/LifetimeSafety/safety.cpp | 24 +++-- .../unittests/Analysis/LifetimeSafetyTest.cpp | 3 +- 9 files changed, 119 insertions(+), 44 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 88b509e1b94df..e0ddfe95b00b2 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -374,6 +374,7 @@ class FactManager { /// Retrieves all the facts in the block containing Program Point P. /// \note This is intended for testing only. llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) const; + std::optional<size_t> getBlockID(ProgramPoint P) const; unsigned getNumFacts() const { return NextFactID.Value; } diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h index 724c6eee7d3c2..42d7df0838f35 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h @@ -16,6 +16,7 @@ #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" +#include "clang/Analysis/Analyses/PostOrderCFGView.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "llvm/ADT/ImmutableMap.h" @@ -46,10 +47,12 @@ class LoanPropagationAnalysis { /// where the loan was originally issued. llvm::SmallVector<OriginID> buildOriginFlowChain(ProgramPoint StartPoint, const OriginID StartOID, - const LoanID TargetLoan) const; + const LoanID TargetLoan, + const PostOrderCFGView *POV) const; llvm::SmallVector<OriginID> - buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan) const; + buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan, + const PostOrderCFGView *POV) const; private: class Impl; diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index c258a1dc3596c..17c0c1e4478ea 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -68,6 +68,7 @@ class LifetimeChecker { LifetimeSafetySemaHelper *SemaHelper; ASTContext &AST; const Decl *FD; + const PostOrderCFGView *POV; static SourceLocation GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) { @@ -90,7 +91,8 @@ class LifetimeChecker { LifetimeSafetySemaHelper *SemaHelper) : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans), LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper), - AST(ADC.getASTContext()), FD(ADC.getDecl()) { + AST(ADC.getASTContext()), FD(ADC.getDecl()), + POV(ADC.getAnalysis<PostOrderCFGView>()) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs<ExpireFact>()) @@ -270,7 +272,7 @@ class LifetimeChecker { // Scope-based expiry (use-after-scope). SemaHelper->reportUseAfterScope( IssueExpr, UF->getUseExpr(), MovedExpr, ExpiryLoc, - getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID))); + getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID, POV))); } else if (const auto *OEF = CausingFact.dyn_cast<const OriginEscapesFact *>()) { diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 3d7fbcdacc830..823f1f686f904 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -145,12 +145,17 @@ void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const { llvm::ArrayRef<const Fact *> FactManager::getBlockContaining(ProgramPoint P) const { - for (const auto &BlockToFactsVec : BlockToFacts) { - for (const Fact *F : BlockToFactsVec) - if (F == P) - return BlockToFactsVec; - } + std::optional<size_t> BlockIndex = getBlockID(P); + if (BlockIndex) + return BlockToFacts[BlockIndex.value()]; return {}; } +std::optional<size_t> FactManager::getBlockID(ProgramPoint P) const { + for (size_t i = 0; i < BlockToFacts.size(); ++i) + for (const Fact *F : BlockToFacts[i]) + if (F == P) + return i; + return std::nullopt; +} } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index a67b1b3c0f826..8e2d9a6577427 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -130,6 +130,11 @@ struct Lattice { } }; +struct BuildOriginFlowChainResult { + const llvm::SmallVector<OriginID> OriginFlowChain; + const bool Complete; +}; + class AnalysisImpl : public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Forward> { public: @@ -204,22 +209,72 @@ class AnalysisImpl llvm::SmallVector<OriginID> buildOriginFlowChain(ProgramPoint StartPoint, const OriginID StartOID, - const LoanID TargetLoan) const { + const LoanID TargetLoan, + const PostOrderCFGView *POV) const { assert(getLoans(StartOID, StartPoint).contains(TargetLoan) && "TargetLoan must be present in the StartOID at the StartPoint"); + std::optional<OriginID> FinalOID; + llvm::DenseMap<OriginID, OriginID> VistedOriginIDs; + + const auto OriginFlowChainFilter = [&VistedOriginIDs](OriginID FinalOID) { + llvm::SmallVector<OriginID> OriginFlowChain; + while (true) { + OriginFlowChain.push_back(FinalOID); + const auto NextOriginID = VistedOriginIDs.find(FinalOID); + if (NextOriginID == VistedOriginIDs.end()) + break; + FinalOID = NextOriginID->second; + } + return OriginFlowChain; + }; + + const auto InsertVistedOriginIDs = + [&VistedOriginIDs, &FinalOID](llvm::ArrayRef<OriginID> OriginFlowChain, + OriginID &StartOID) { + if (!VistedOriginIDs.empty()) + VistedOriginIDs.insert({OriginFlowChain[0], StartOID}); + + for (size_t i = 0; i < OriginFlowChain.size() - 1; ++i) + VistedOriginIDs.insert( + {OriginFlowChain[i + 1], OriginFlowChain[i]}); + + StartOID = OriginFlowChain.back(); + FinalOID = StartOID; + }; + + std::optional<size_t> BlockID = FactMgr.getBlockID(StartPoint); + assert(BlockID.has_value()); + const auto StartIt = llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { + return Block->getBlockID() == BlockID; + }); + + OriginID CurrOID = StartOID; + for (const CFGBlock *B : + llvm::reverse(llvm::make_range(POV->begin(), StartIt + 1))) { + BuildOriginFlowChainResult BuildResult = + buildOriginFlowChain(B, CurrOID, TargetLoan); + if (!BuildResult.OriginFlowChain.empty()) + InsertVistedOriginIDs(BuildResult.OriginFlowChain, CurrOID); + if (BuildResult.Complete) + return OriginFlowChainFilter(*FinalOID); + } + + llvm_unreachable( + "buildOriginFlowChain should return at BuildResult.Complete"); + } + + BuildOriginFlowChainResult + buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID, + const LoanID TargetLoan) const { OriginID CurrOID = StartOID; llvm::SmallVector<OriginID> OriginFlowChain; - llvm::ArrayRef<const Fact *> Facts = FactMgr.getBlockContaining(StartPoint); - const auto *StartIt = llvm::find(Facts, StartPoint); - assert(StartIt != Facts.end()); - for (const Fact *F : - llvm::reverse(llvm::make_range(Facts.begin(), StartIt))) { + for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) { if (const auto *IF = F->getAs<IssueFact>()) if (IF->getLoanID() == TargetLoan) { assert(IF->getOriginID() == CurrOID); - return OriginFlowChain; + return {OriginFlowChain, true}; } const auto *OFF = F->getAs<OriginFlowFact>(); @@ -235,20 +290,17 @@ class AnalysisImpl CurrOID = SrcOriginID; } - // FIXME: Ideally, this return is unreachable and should be an assert - // because we expect to always finish at an IssueFact. But since current - // traversal is limited to a single CFG block, multi-block OriginFlowChain - // construction might miss the IssueFact. We should add llvm_unreachable - // here once multi-block support is implemented. - return {}; + return {OriginFlowChain, false}; } llvm::SmallVector<OriginID> - buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan) const { + buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan, + const PostOrderCFGView *POV) const { for (const OriginList *Cur = UF->getUsedOrigins(); Cur; Cur = Cur->peelOuterOrigin()) if (getLoans(Cur->getOuterOriginID(), UF).contains(TargetLoan)) - return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan); + return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan, + POV); return {}; } @@ -303,16 +355,15 @@ LoanSet LoanPropagationAnalysis::getLoans(OriginID OID, ProgramPoint P) const { return PImpl->getLoans(OID, P); } -llvm::SmallVector<OriginID> -LoanPropagationAnalysis::buildOriginFlowChain(ProgramPoint StartPoint, - const OriginID StartOID, - const LoanID TargetLoan) const { - return PImpl->buildOriginFlowChain(StartPoint, StartOID, TargetLoan); +llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain( + ProgramPoint StartPoint, const OriginID StartOID, const LoanID TargetLoan, + const PostOrderCFGView *POV) const { + return PImpl->buildOriginFlowChain(StartPoint, StartOID, TargetLoan, POV); } -llvm::SmallVector<OriginID> -LoanPropagationAnalysis::buildOriginFlowChain(const UseFact *UF, - const LoanID TargetLoan) const { - return PImpl->buildOriginFlowChain(UF, TargetLoan); +llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain( + const UseFact *UF, const LoanID TargetLoan, + const PostOrderCFGView *POV) const { + return PImpl->buildOriginFlowChain(UF, TargetLoan, POV); } } // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 1047aecf863fb..a1313816aae3a 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -566,10 +566,10 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { if (OriginExprChain.empty()) return; - const Expr *LastExpr = OriginExprChain.back(); + const Expr *LastExpr = OriginExprChain.front(); std::string IssueStr = getDiagSubjectDescription(LastExpr); - for (const Expr *CurrExpr : reverse(OriginExprChain.drop_back())) { + for (const Expr *CurrExpr : OriginExprChain.drop_front()) { if (!shouldShowInAliasChain(CurrExpr, LastExpr)) continue; S.Diag(CurrExpr->getBeginLoc(), diff --git a/clang/test/Sema/LifetimeSafety/safety-c.c b/clang/test/Sema/LifetimeSafety/safety-c.c index e9443899c9935..42adbbb3f0628 100644 --- a/clang/test/Sema/LifetimeSafety/safety-c.c +++ b/clang/test/Sema/LifetimeSafety/safety-c.c @@ -93,7 +93,9 @@ void conditional_operator_lifetimebound(int cond) { int *p; { int a, b; - p = identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} + p = identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} \ + // expected-note {{result of call to 'identity' aliases the storage of local variable 'a'}} \ + // expected-note {{result of call to 'identity' aliases the storage of local variable 'b'}} : &b); // expected-warning {{local variable 'b' does not live long enough}} } // expected-note {{local variable 'a' is destroyed here}} \ // expected-note {{local variable 'b' is destroyed here}} diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index abd3d9c61b784..0a6bea96af45d 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -448,7 +448,7 @@ void loan_from_previous_iteration(MyObj safe, bool condition) { p = &x; // expected-warning {{does not live long enough}} if (condition) - q = p; + q = p; // expected-note {{local variable 'p' aliases the storage of local variable 'x'}} (void)*p; (void)*q; // expected-note {{later used here}} } // expected-note {{local variable 'x' is destroyed here}} @@ -846,7 +846,8 @@ void lifetimebound_multiple_args_potential(bool cond) { MyObj obj1; if (cond) { MyObj obj2; - v = Choose(true, + v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj1'}} \ + // expected-note {{result of call to 'Choose' aliases the storage of local variable 'obj2'}} obj1, // expected-warning {{local variable 'obj1' does not live long enough}} obj2); // expected-warning {{local variable 'obj2' does not live long enough}} } // expected-note {{local variable 'obj2' is destroyed here}} @@ -940,7 +941,7 @@ void lifetimebound_partial_safety(bool cond) { if (cond) { MyObj temp_obj; - v = Choose(true, + v = Choose(true, // expected-note {{result of call to 'Choose' aliases the storage of local variable 'temp_obj'}} safe_obj, temp_obj); // expected-warning {{local variable 'temp_obj' does not live long enough}} } // expected-note {{local variable 'temp_obj' is destroyed here}} @@ -1223,7 +1224,9 @@ void conditional_operator_lifetimebound(bool cond) { MyObj* p; { MyObj a, b; - p = Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} + p = Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'a'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b'}} : &b); // expected-warning {{local variable 'b' does not live long enough}} } // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}} (void)*p; // expected-note 2 {{later used here}} @@ -1243,9 +1246,15 @@ void conditional_operator_lifetimebound_nested_deep(bool cond) { MyObj* p; { MyObj a, b, c, d; - p = Identity(cond ? Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} + p = Identity(cond ? Identity(cond ? &a // expected-warning {{local variable 'a' does not live long enough}} \ + // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'a'}} \ + // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'b'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'c'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'd'}} : &b) // expected-warning {{local variable 'b' does not live long enough}} - : Identity(cond ? &c // expected-warning {{local variable 'c' does not live long enough}} + : Identity(cond ? &c // expected-warning {{local variable 'c' does not live long enough}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'c'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'd'}} : &d)); // expected-warning {{local variable 'd' does not live long enough}} } // expected-note {{local variable 'a' is destroyed here}} expected-note {{local variable 'd' is destroyed here}} expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'c' is destroyed here}} (void)*p; // expected-note 4 {{later used here}} @@ -1539,7 +1548,8 @@ void range_based_for_use_after_scope() { View v; { MyObjStorage s; - for (const MyObj &o : s) { // expected-warning {{local variable 's' does not live long enough}} + for (const MyObj &o : s) { // expected-warning {{local variable 's' does not live long enough}} \ + // expected-note {{local variable '__range2' aliases the storage of local variable 's'}} v = o; } } // expected-note {{local variable 's' is destroyed here}} diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 78b7449958140..8d5ed9c88dcff 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -215,7 +215,8 @@ class LifetimeTestHelper { for (LoanID LID : EndLoanIDs) { llvm::SmallVector<OriginID> OriginFlowChain = Runner.getAnalysis().getLoanPropagation().buildOriginFlowChain( - getProgramPoint(Annotation), *StartOriginID, LID); + getProgramPoint(Annotation), *StartOriginID, LID, + Runner.getAnalysisContext().getAnalysis<PostOrderCFGView>()); if (!OriginFlowChain.empty()) return OriginFlowChain; } >From 2b0858c475a458ec73ff8593ce519f8e727eee98 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Fri, 19 Jun 2026 15:25:42 +0800 Subject: [PATCH 02/19] Add return after llvm_unreachable and clean up code Signed-off-by: Yuan Suo <[email protected]> --- .../LifetimeSafety/LoanPropagation.cpp | 104 +++++++----------- clang/lib/Sema/SemaLifetimeSafety.h | 4 +- 2 files changed, 42 insertions(+), 66 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 8e2d9a6577427..59081522b40f7 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -214,35 +214,7 @@ class AnalysisImpl assert(getLoans(StartOID, StartPoint).contains(TargetLoan) && "TargetLoan must be present in the StartOID at the StartPoint"); - std::optional<OriginID> FinalOID; - llvm::DenseMap<OriginID, OriginID> VistedOriginIDs; - - const auto OriginFlowChainFilter = [&VistedOriginIDs](OriginID FinalOID) { - llvm::SmallVector<OriginID> OriginFlowChain; - while (true) { - OriginFlowChain.push_back(FinalOID); - const auto NextOriginID = VistedOriginIDs.find(FinalOID); - if (NextOriginID == VistedOriginIDs.end()) - break; - FinalOID = NextOriginID->second; - } - return OriginFlowChain; - }; - - const auto InsertVistedOriginIDs = - [&VistedOriginIDs, &FinalOID](llvm::ArrayRef<OriginID> OriginFlowChain, - OriginID &StartOID) { - if (!VistedOriginIDs.empty()) - VistedOriginIDs.insert({OriginFlowChain[0], StartOID}); - - for (size_t i = 0; i < OriginFlowChain.size() - 1; ++i) - VistedOriginIDs.insert( - {OriginFlowChain[i + 1], OriginFlowChain[i]}); - - StartOID = OriginFlowChain.back(); - FinalOID = StartOID; - }; - + llvm::SmallVector<OriginID> OriginFlowChain; std::optional<size_t> BlockID = FactMgr.getBlockID(StartPoint); assert(BlockID.has_value()); const auto StartIt = llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { @@ -252,45 +224,20 @@ class AnalysisImpl OriginID CurrOID = StartOID; for (const CFGBlock *B : llvm::reverse(llvm::make_range(POV->begin(), StartIt + 1))) { - BuildOriginFlowChainResult BuildResult = - buildOriginFlowChain(B, CurrOID, TargetLoan); - if (!BuildResult.OriginFlowChain.empty()) - InsertVistedOriginIDs(BuildResult.OriginFlowChain, CurrOID); - if (BuildResult.Complete) - return OriginFlowChainFilter(*FinalOID); - } - - llvm_unreachable( - "buildOriginFlowChain should return at BuildResult.Complete"); - } - - BuildOriginFlowChainResult - buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID, - const LoanID TargetLoan) const { - OriginID CurrOID = StartOID; - llvm::SmallVector<OriginID> OriginFlowChain; + auto [OFChain, Complete] = buildOriginFlowChain(B, CurrOID, TargetLoan); - for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) { - if (const auto *IF = F->getAs<IssueFact>()) - if (IF->getLoanID() == TargetLoan) { - assert(IF->getOriginID() == CurrOID); - return {OriginFlowChain, true}; - } - - const auto *OFF = F->getAs<OriginFlowFact>(); - if (!OFF) - continue; - if (OFF->getDestOriginID() != CurrOID) - continue; + if (!OFChain.empty()) { + OriginFlowChain.append(OFChain.begin(), OFChain.end()); + CurrOID = OFChain.back(); + } - const OriginID SrcOriginID = OFF->getSrcOriginID(); - if (!getLoans(SrcOriginID, OFF).contains(TargetLoan)) - continue; - OriginFlowChain.push_back(SrcOriginID); - CurrOID = SrcOriginID; + if (Complete) + return OriginFlowChain; } - return {OriginFlowChain, false}; + llvm_unreachable( + "buildOriginFlowChain should return at BuildResult.Complete"); + return OriginFlowChain; } llvm::SmallVector<OriginID> @@ -327,6 +274,35 @@ class AnalysisImpl return LoanSetFactory.getEmptySet(); } + BuildOriginFlowChainResult + buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID, + const LoanID TargetLoan) const { + OriginID CurrOID = StartOID; + llvm::SmallVector<OriginID> OriginFlowChain; + + for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) { + if (const auto *IF = F->getAs<IssueFact>()) + if (IF->getLoanID() == TargetLoan) { + assert(IF->getOriginID() == CurrOID); + return {OriginFlowChain, true}; + } + + const auto *OFF = F->getAs<OriginFlowFact>(); + if (!OFF) + continue; + if (OFF->getDestOriginID() != CurrOID) + continue; + + const OriginID SrcOriginID = OFF->getSrcOriginID(); + if (!getLoans(SrcOriginID, OFF).contains(TargetLoan)) + continue; + OriginFlowChain.push_back(SrcOriginID); + CurrOID = SrcOriginID; + } + + return {OriginFlowChain, false}; + } + OriginLoanMap::Factory &OriginLoanMapFactory; LoanSet::Factory &LoanSetFactory; /// Boolean vector indexed by origin ID. If true, the origin appears in diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index a1313816aae3a..4199e5dec1787 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -566,10 +566,10 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { if (OriginExprChain.empty()) return; - const Expr *LastExpr = OriginExprChain.front(); + const Expr *LastExpr = OriginExprChain.back(); std::string IssueStr = getDiagSubjectDescription(LastExpr); - for (const Expr *CurrExpr : OriginExprChain.drop_front()) { + for (const Expr *CurrExpr : llvm::reverse(OriginExprChain.drop_back())) { if (!shouldShowInAliasChain(CurrExpr, LastExpr)) continue; S.Diag(CurrExpr->getBeginLoc(), >From bad3f3aa0ffcf6af363b164671c4c88441f06a82 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Fri, 19 Jun 2026 15:49:18 +0800 Subject: [PATCH 03/19] Fix issues from previous commit Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 9 ++------- clang/lib/Sema/SemaLifetimeSafety.h | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 59081522b40f7..d8b41f478b878 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -130,11 +130,6 @@ struct Lattice { } }; -struct BuildOriginFlowChainResult { - const llvm::SmallVector<OriginID> OriginFlowChain; - const bool Complete; -}; - class AnalysisImpl : public DataflowAnalysis<AnalysisImpl, Lattice, Direction::Forward> { public: @@ -237,7 +232,7 @@ class AnalysisImpl llvm_unreachable( "buildOriginFlowChain should return at BuildResult.Complete"); - return OriginFlowChain; + return {}; } llvm::SmallVector<OriginID> @@ -274,7 +269,7 @@ class AnalysisImpl return LoanSetFactory.getEmptySet(); } - BuildOriginFlowChainResult + std::pair<llvm::SmallVector<OriginID>, bool> buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID, const LoanID TargetLoan) const { OriginID CurrOID = StartOID; diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 4199e5dec1787..1047aecf863fb 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -569,7 +569,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { const Expr *LastExpr = OriginExprChain.back(); std::string IssueStr = getDiagSubjectDescription(LastExpr); - for (const Expr *CurrExpr : llvm::reverse(OriginExprChain.drop_back())) { + for (const Expr *CurrExpr : reverse(OriginExprChain.drop_back())) { if (!shouldShowInAliasChain(CurrExpr, LastExpr)) continue; S.Diag(CurrExpr->getBeginLoc(), >From 8c84420389bfd0ad9ae33d3ee19160fc9f45f4f9 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Fri, 19 Jun 2026 15:52:35 +0800 Subject: [PATCH 04/19] Reanme StartIt to EndBlockIt Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index d8b41f478b878..3424bbc80a14f 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -212,13 +212,13 @@ class AnalysisImpl llvm::SmallVector<OriginID> OriginFlowChain; std::optional<size_t> BlockID = FactMgr.getBlockID(StartPoint); assert(BlockID.has_value()); - const auto StartIt = llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { + const auto EndBlockIt = llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { return Block->getBlockID() == BlockID; }); OriginID CurrOID = StartOID; for (const CFGBlock *B : - llvm::reverse(llvm::make_range(POV->begin(), StartIt + 1))) { + llvm::reverse(llvm::make_range(POV->begin(), EndBlockIt + 1))) { auto [OFChain, Complete] = buildOriginFlowChain(B, CurrOID, TargetLoan); if (!OFChain.empty()) { >From 3487b3e8e338aec539bc8587f1e80d98b5085a64 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Fri, 19 Jun 2026 15:55:02 +0800 Subject: [PATCH 05/19] Fix clang-format error Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 3424bbc80a14f..f6d620d6520ca 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -212,9 +212,10 @@ class AnalysisImpl llvm::SmallVector<OriginID> OriginFlowChain; std::optional<size_t> BlockID = FactMgr.getBlockID(StartPoint); assert(BlockID.has_value()); - const auto EndBlockIt = llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { - return Block->getBlockID() == BlockID; - }); + const auto EndBlockIt = + llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { + return Block->getBlockID() == BlockID; + }); OriginID CurrOID = StartOID; for (const CFGBlock *B : >From b42b7243152617a32bd6942f95441a3791c6544d Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Sat, 20 Jun 2026 15:29:12 +0800 Subject: [PATCH 06/19] Remove return after llvm_unreachable add llvm_unreachable in getBlockID Also add a unit test for buildOriginFlowChain Signed-off-by: Yuan Suo <[email protected]> --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 2 +- clang/lib/Analysis/LifetimeSafety/Facts.cpp | 9 ++-- .../LifetimeSafety/LoanPropagation.cpp | 6 +-- .../unittests/Analysis/LifetimeSafetyTest.cpp | 44 +++++++++++++++++++ 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index e0ddfe95b00b2..e1380a6cf195c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -374,7 +374,7 @@ class FactManager { /// Retrieves all the facts in the block containing Program Point P. /// \note This is intended for testing only. llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) const; - std::optional<size_t> getBlockID(ProgramPoint P) const; + size_t getBlockID(ProgramPoint P) const; unsigned getNumFacts() const { return NextFactID.Value; } diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 823f1f686f904..08e7c6cd62345 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -145,17 +145,14 @@ void FactManager::dump(const CFG &Cfg, AnalysisDeclContext &AC) const { llvm::ArrayRef<const Fact *> FactManager::getBlockContaining(ProgramPoint P) const { - std::optional<size_t> BlockIndex = getBlockID(P); - if (BlockIndex) - return BlockToFacts[BlockIndex.value()]; - return {}; + return BlockToFacts[getBlockID(P)]; } -std::optional<size_t> FactManager::getBlockID(ProgramPoint P) const { +size_t FactManager::getBlockID(ProgramPoint P) const { for (size_t i = 0; i < BlockToFacts.size(); ++i) for (const Fact *F : BlockToFacts[i]) if (F == P) return i; - return std::nullopt; + llvm_unreachable("Failed to find BlockID for given ProgramPoint"); } } // namespace clang::lifetimes::internal diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index f6d620d6520ca..385bf5f8a192f 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -210,8 +210,7 @@ class AnalysisImpl "TargetLoan must be present in the StartOID at the StartPoint"); llvm::SmallVector<OriginID> OriginFlowChain; - std::optional<size_t> BlockID = FactMgr.getBlockID(StartPoint); - assert(BlockID.has_value()); + size_t BlockID = FactMgr.getBlockID(StartPoint); const auto EndBlockIt = llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { return Block->getBlockID() == BlockID; @@ -232,8 +231,7 @@ class AnalysisImpl } llvm_unreachable( - "buildOriginFlowChain should return at BuildResult.Complete"); - return {}; + "buildOriginFlowChain did not reach IssueFact for TargetLoan"); } llvm::SmallVector<OriginID> diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 8d5ed9c88dcff..153225dcdbf15 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -2078,5 +2078,49 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithLifetimeBound) { EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("result"))); EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("a")))); } + +TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainInMultiBlock) { + SetupTest(R"( + void target(bool c1, bool c2) { + int *s; + int *a, *b, *c; + + { + int tgta, tgtb, tgtc; + a = &tgta; + b = &tgtb; + c = &tgtc; + } + + if (c1) { + s = c2 ? a : b; + } else { + s = c; + } + + POINT(after_nested_merge); + (void)*s; + } + )"); + + llvm::SmallVector<OriginID> ChainForTgtA = + Helper->buildOriginFlowChainInOneBlock("s", "tgta", "after_nested_merge"); + llvm::SmallVector<OriginID> ChainForTgtB = + Helper->buildOriginFlowChainInOneBlock("s", "tgtb", "after_nested_merge"); + llvm::SmallVector<OriginID> ChainForTgtC = + Helper->buildOriginFlowChainInOneBlock("s", "tgtc", "after_nested_merge"); + + EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("a"))); + EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("b")))); + EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("c")))); + + EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("b"))); + EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("a")))); + EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("c")))); + + EXPECT_THAT(ChainForTgtC, Contains(*Helper->getOriginForDecl("c"))); + EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("b")))); + EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("a")))); +} } // anonymous namespace } // namespace clang::lifetimes::internal >From 62d8318e42ae7f36f57354711fa2eaecadf9acd4 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Sat, 20 Jun 2026 16:17:23 +0800 Subject: [PATCH 07/19] Add Decl->isImplicit() in shouldShowInAliasChain Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Sema/SemaLifetimeSafety.h | 15 ++++++++++++++- clang/test/Sema/LifetimeSafety/safety.cpp | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 1047aecf863fb..88c0d81979437 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -550,11 +550,24 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { return "expression"; } + bool isInValidExpr(const Expr *E) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { + return !DRE->getDecl()->isImplicit(); + } + + if (const auto *CE = dyn_cast<CallExpr>(E)) { + if (const auto *FE = CE->getDirectCallee()) + return !FE->isImplicit(); + } + + return false; + } + bool shouldShowInAliasChain(const Expr *CurrExpr, const Expr *LastExpr) { CurrExpr = CurrExpr->IgnoreImpCasts(); LastExpr = LastExpr->IgnoreImpCasts(); - if (!isa<CallExpr, DeclRefExpr>(CurrExpr)) + if (!isInValidExpr(CurrExpr)) return false; // Source ranges can be used to filter out many implicit expressions, // because operations between class objects often involve numerous implicit diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index 0a6bea96af45d..82d443b179bd3 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -1549,7 +1549,7 @@ void range_based_for_use_after_scope() { { MyObjStorage s; for (const MyObj &o : s) { // expected-warning {{local variable 's' does not live long enough}} \ - // expected-note {{local variable '__range2' aliases the storage of local variable 's'}} + // expected-note {{result of call to 'begin' aliases the storage of local variable 's'}} v = o; } } // expected-note {{local variable 's' is destroyed here}} >From ca7a6e9dae2e4cc3e968345cba177fa93eaf0676 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Sat, 20 Jun 2026 16:55:16 +0800 Subject: [PATCH 08/19] Fix inconsistent semantics in isInValidExpr Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Sema/SemaLifetimeSafety.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 88c0d81979437..9f5c9592a2e6e 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -552,22 +552,22 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { bool isInValidExpr(const Expr *E) { if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { - return !DRE->getDecl()->isImplicit(); + return DRE->getDecl()->isImplicit(); } if (const auto *CE = dyn_cast<CallExpr>(E)) { if (const auto *FE = CE->getDirectCallee()) - return !FE->isImplicit(); + return FE->isImplicit(); } - return false; + return true; } bool shouldShowInAliasChain(const Expr *CurrExpr, const Expr *LastExpr) { CurrExpr = CurrExpr->IgnoreImpCasts(); LastExpr = LastExpr->IgnoreImpCasts(); - if (!isInValidExpr(CurrExpr)) + if (isInValidExpr(CurrExpr)) return false; // Source ranges can be used to filter out many implicit expressions, // because operations between class objects often involve numerous implicit >From b2331e72324e74e46b71ddb6104709cb2d7ba260 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Sun, 21 Jun 2026 12:55:16 +0800 Subject: [PATCH 09/19] isInValidExpr -> isInvalidExpr Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Sema/SemaLifetimeSafety.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 9f5c9592a2e6e..eea106911ae0a 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -550,7 +550,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { return "expression"; } - bool isInValidExpr(const Expr *E) { + bool isInvalidExpr(const Expr *E) { if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { return DRE->getDecl()->isImplicit(); } @@ -567,7 +567,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { CurrExpr = CurrExpr->IgnoreImpCasts(); LastExpr = LastExpr->IgnoreImpCasts(); - if (isInValidExpr(CurrExpr)) + if (isInvalidExpr(CurrExpr)) return false; // Source ranges can be used to filter out many implicit expressions, // because operations between class objects often involve numerous implicit >From b2dd596b44dbf69bd2a304797c67dcc124b0adce Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Sun, 21 Jun 2026 13:51:26 +0800 Subject: [PATCH 10/19] Using Block->preds() instead of PostOrderCFGView Signed-off-by: Yuan Suo <[email protected]> --- .../Analyses/LifetimeSafety/LoanPropagation.h | 17 ++- clang/lib/Analysis/LifetimeSafety/Checker.cpp | 7 +- .../LifetimeSafety/LoanPropagation.cpp | 98 +++++++++++----- clang/lib/Sema/SemaLifetimeSafety.h | 4 +- .../unittests/Analysis/LifetimeSafetyTest.cpp | 107 +++++++++--------- 5 files changed, 134 insertions(+), 99 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h index 42d7df0838f35..97d529e91a9ee 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h @@ -16,7 +16,6 @@ #define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOAN_PROPAGATION_H #include "clang/Analysis/Analyses/LifetimeSafety/Facts.h" -#include "clang/Analysis/Analyses/PostOrderCFGView.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "llvm/ADT/ImmutableMap.h" @@ -45,14 +44,14 @@ class LoanPropagationAnalysis { /// this function traces backwards through OriginFlowFacts to identify the /// sequence of origins through which the loan flowed, ending at the origin /// where the loan was originally issued. - llvm::SmallVector<OriginID> - buildOriginFlowChain(ProgramPoint StartPoint, const OriginID StartOID, - const LoanID TargetLoan, - const PostOrderCFGView *POV) const; - - llvm::SmallVector<OriginID> - buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan, - const PostOrderCFGView *POV) const; + llvm::SmallVector<OriginID> buildOriginFlowChain(ProgramPoint StartPoint, + const OriginID StartOID, + const LoanID TargetLoan, + const CFG *Cfg) const; + + llvm::SmallVector<OriginID> buildOriginFlowChain(const UseFact *UF, + const LoanID TargetLoan, + const CFG *Cfg) const; private: class Impl; diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp b/clang/lib/Analysis/LifetimeSafety/Checker.cpp index 17c0c1e4478ea..0288e794998a6 100644 --- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp @@ -67,8 +67,8 @@ class LifetimeChecker { FactManager &FactMgr; LifetimeSafetySemaHelper *SemaHelper; ASTContext &AST; + const CFG *Cfg; const Decl *FD; - const PostOrderCFGView *POV; static SourceLocation GetFactLoc(llvm::PointerUnion<const UseFact *, const OriginEscapesFact *> F) { @@ -91,8 +91,7 @@ class LifetimeChecker { LifetimeSafetySemaHelper *SemaHelper) : LoanPropagation(LoanPropagation), MovedLoans(MovedLoans), LiveOrigins(LiveOrigins), FactMgr(FM), SemaHelper(SemaHelper), - AST(ADC.getASTContext()), FD(ADC.getDecl()), - POV(ADC.getAnalysis<PostOrderCFGView>()) { + AST(ADC.getASTContext()), Cfg(ADC.getCFG()), FD(ADC.getDecl()) { for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) if (const auto *EF = F->getAs<ExpireFact>()) @@ -272,7 +271,7 @@ class LifetimeChecker { // Scope-based expiry (use-after-scope). SemaHelper->reportUseAfterScope( IssueExpr, UF->getUseExpr(), MovedExpr, ExpiryLoc, - getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID, POV))); + getExprChain(LoanPropagation.buildOriginFlowChain(UF, LID, Cfg))); } else if (const auto *OEF = CausingFact.dyn_cast<const OriginEscapesFact *>()) { diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 385bf5f8a192f..a77dbf3cf3b58 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -18,6 +18,7 @@ #include "clang/Analysis/CFG.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/BitVector.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/raw_ostream.h" @@ -202,46 +203,86 @@ class AnalysisImpl return getLoans(getState(P), OID); } - llvm::SmallVector<OriginID> - buildOriginFlowChain(ProgramPoint StartPoint, const OriginID StartOID, - const LoanID TargetLoan, - const PostOrderCFGView *POV) const { + llvm::SmallVector<OriginID> buildOriginFlowChain(ProgramPoint StartPoint, + const OriginID StartOID, + const LoanID TargetLoan, + const CFG *Cfg) const { assert(getLoans(StartOID, StartPoint).contains(TargetLoan) && "TargetLoan must be present in the StartOID at the StartPoint"); - llvm::SmallVector<OriginID> OriginFlowChain; - size_t BlockID = FactMgr.getBlockID(StartPoint); - const auto EndBlockIt = - llvm::find_if(*POV, [&BlockID](const CFGBlock *Block) { - return Block->getBlockID() == BlockID; - }); + std::optional<OriginID> FinalOID; + llvm::DenseMap<OriginID, OriginID> VistedOriginIDs; + + const auto OriginFlowChainFilter = [&VistedOriginIDs](OriginID FinalOID) { + llvm::SmallVector<OriginID> OriginFlowChain; + while (true) { + OriginFlowChain.push_back(FinalOID); + const auto NextOriginID = VistedOriginIDs.find(FinalOID); + if (NextOriginID == VistedOriginIDs.end()) + break; + FinalOID = NextOriginID->second; + } + return OriginFlowChain; + }; - OriginID CurrOID = StartOID; - for (const CFGBlock *B : - llvm::reverse(llvm::make_range(POV->begin(), EndBlockIt + 1))) { - auto [OFChain, Complete] = buildOriginFlowChain(B, CurrOID, TargetLoan); + const auto InsertVistedOriginIDs = + [&VistedOriginIDs, &FinalOID](llvm::ArrayRef<OriginID> OriginFlowChain, + OriginID &StartOID) { + if (!VistedOriginIDs.empty()) + VistedOriginIDs.insert({OriginFlowChain[0], StartOID}); - if (!OFChain.empty()) { - OriginFlowChain.append(OFChain.begin(), OFChain.end()); - CurrOID = OFChain.back(); + for (size_t i = 0; i < OriginFlowChain.size() - 1; ++i) + VistedOriginIDs.insert( + {OriginFlowChain[i + 1], OriginFlowChain[i]}); + + StartOID = OriginFlowChain.back(); + FinalOID = StartOID; + }; + + const CFGBlock *EndBlock = nullptr; + size_t BlockID = FactMgr.getBlockID(StartPoint); + for (const CFGBlock *Block : *Cfg) + if (Block->getBlockID() == BlockID) { + EndBlock = Block; + break; } + using SearchContext = std::pair<const CFGBlock *, OriginID>; + std::queue<SearchContext> PendingContext; + llvm::SmallSet<SearchContext, 32> VistedContext; + PendingContext.push({EndBlock, StartOID}); + + while (!PendingContext.empty()) { + auto [CurrBlock, CurrOID] = PendingContext.front(); + PendingContext.pop(); + + const auto [BuildResult, Complete] = + buildOriginFlowChain(CurrBlock, CurrOID, TargetLoan); + if (!BuildResult.empty()) + InsertVistedOriginIDs(BuildResult, CurrOID); + if (Complete) - return OriginFlowChain; + return OriginFlowChainFilter(*FinalOID); + + for (const CFGBlock *Block : CurrBlock->preds()) { + SearchContext Context = {Block, CurrOID}; + if (Block && VistedContext.insert(Context).second) + PendingContext.push(Context); + } } llvm_unreachable( "buildOriginFlowChain did not reach IssueFact for TargetLoan"); } - llvm::SmallVector<OriginID> - buildOriginFlowChain(const UseFact *UF, const LoanID TargetLoan, - const PostOrderCFGView *POV) const { + llvm::SmallVector<OriginID> buildOriginFlowChain(const UseFact *UF, + const LoanID TargetLoan, + const CFG *Cfg) const { for (const OriginList *Cur = UF->getUsedOrigins(); Cur; Cur = Cur->peelOuterOrigin()) if (getLoans(Cur->getOuterOriginID(), UF).contains(TargetLoan)) return buildOriginFlowChain(UF, Cur->getOuterOriginID(), TargetLoan, - POV); + Cfg); return {}; } @@ -276,10 +317,8 @@ class AnalysisImpl for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) { if (const auto *IF = F->getAs<IssueFact>()) - if (IF->getLoanID() == TargetLoan) { - assert(IF->getOriginID() == CurrOID); + if (IF->getLoanID() == TargetLoan && IF->getOriginID() == CurrOID) return {OriginFlowChain, true}; - } const auto *OFF = F->getAs<OriginFlowFact>(); if (!OFF) @@ -327,13 +366,12 @@ LoanSet LoanPropagationAnalysis::getLoans(OriginID OID, ProgramPoint P) const { llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain( ProgramPoint StartPoint, const OriginID StartOID, const LoanID TargetLoan, - const PostOrderCFGView *POV) const { - return PImpl->buildOriginFlowChain(StartPoint, StartOID, TargetLoan, POV); + const CFG *Cfg) const { + return PImpl->buildOriginFlowChain(StartPoint, StartOID, TargetLoan, Cfg); } llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain( - const UseFact *UF, const LoanID TargetLoan, - const PostOrderCFGView *POV) const { - return PImpl->buildOriginFlowChain(UF, TargetLoan, POV); + const UseFact *UF, const LoanID TargetLoan, const CFG *Cfg) const { + return PImpl->buildOriginFlowChain(UF, TargetLoan, Cfg); } } // namespace clang::lifetimes::internal diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index eea106911ae0a..6baaa320cfd6f 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -579,10 +579,10 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { if (OriginExprChain.empty()) return; - const Expr *LastExpr = OriginExprChain.back(); + const Expr *LastExpr = OriginExprChain.front(); std::string IssueStr = getDiagSubjectDescription(LastExpr); - for (const Expr *CurrExpr : reverse(OriginExprChain.drop_back())) { + for (const Expr *CurrExpr : OriginExprChain.drop_front()) { if (!shouldShowInAliasChain(CurrExpr, LastExpr)) continue; S.Diag(CurrExpr->getBeginLoc(), diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 153225dcdbf15..d79161ae1db54 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -206,9 +206,8 @@ class LifetimeTestHelper { } llvm::SmallVector<OriginID> - buildOriginFlowChainInOneBlock(llvm::StringRef StartOriginVar, - llvm::StringRef EndLoanVar, - llvm::StringRef Annotation) { + buildOriginFlowChain(llvm::StringRef StartOriginVar, + llvm::StringRef EndLoanVar, llvm::StringRef Annotation) { std::optional<OriginID> StartOriginID = getOriginForDecl(StartOriginVar); std::vector<LoanID> EndLoanIDs = getLoansForVar(EndLoanVar); @@ -216,7 +215,7 @@ class LifetimeTestHelper { llvm::SmallVector<OriginID> OriginFlowChain = Runner.getAnalysis().getLoanPropagation().buildOriginFlowChain( getProgramPoint(Annotation), *StartOriginID, LID, - Runner.getAnalysisContext().getAnalysis<PostOrderCFGView>()); + Runner.getAnalysisContext().getCFG()); if (!OriginFlowChain.empty()) return OriginFlowChain; } @@ -1976,6 +1975,50 @@ TEST_F(LifetimeAnalysisTest, LambdaInitCaptureViewByValue) { // Tests for buildOriginFlowChain // ========================================================================= // +TEST_F(LifetimeAnalysisTest, BuildOriginFlowChain) { + SetupTest(R"( + void target(bool c1, bool c2) { + int *s; + int *a, *b, *c; + + { + int tgta, tgtb, tgtc; + a = &tgta; + b = &tgtb; + c = &tgtc; + } + + if (c1) { + s = c2 ? a : b; + } else { + s = c; + } + + POINT(after_nested_merge); + (void)*s; + } + )"); + + llvm::SmallVector<OriginID> ChainForTgtA = + Helper->buildOriginFlowChain("s", "tgta", "after_nested_merge"); + llvm::SmallVector<OriginID> ChainForTgtB = + Helper->buildOriginFlowChain("s", "tgtb", "after_nested_merge"); + llvm::SmallVector<OriginID> ChainForTgtC = + Helper->buildOriginFlowChain("s", "tgtc", "after_nested_merge"); + + EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("a"))); + EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("b")))); + EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("c")))); + + EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("a")))); + EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("b"))); + EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("c")))); + + EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("a")))); + EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("b")))); + EXPECT_THAT(ChainForTgtC, Contains(*Helper->getOriginForDecl("c"))); +} + TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithErrorTargetLoan) { SetupTest(R"( void target() { @@ -1987,7 +2030,7 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithErrorTargetLoan) { )"); #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST - EXPECT_DEATH(Helper->buildOriginFlowChainInOneBlock("s", "a", "after_use"), + EXPECT_DEATH(Helper->buildOriginFlowChain("s", "a", "after_use"), "TargetLoan must be present in the StartOID at the StartPoint"); #endif } @@ -2006,7 +2049,7 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithSelfAssignment) { )"); const llvm::SmallVector<OriginID> OriginFlowChain = - Helper->buildOriginFlowChainInOneBlock("s", "tgt", "after_use"); + Helper->buildOriginFlowChain("s", "tgt", "after_use"); EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a"))); } @@ -2023,7 +2066,7 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithMultiAssignInSameStmt) { )"); const llvm::SmallVector<OriginID> OriginFlowChain = - Helper->buildOriginFlowChainInOneBlock("s", "tgt", "after_use"); + Helper->buildOriginFlowChain("s", "tgt", "after_use"); EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a"))); EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("b"))); @@ -2044,7 +2087,7 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithOverwritingAssignments) { )"); const llvm::SmallVector<OriginID> OriginFlowChain = - Helper->buildOriginFlowChainInOneBlock("s", "tgt1", "after_use"); + Helper->buildOriginFlowChain("s", "tgt1", "after_use"); EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a"))); EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("b"))); @@ -2066,9 +2109,9 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithLifetimeBound) { )"); llvm::SmallVector<OriginID> ChainForTgtA = - Helper->buildOriginFlowChainInOneBlock("s", "tgta", "after_use"); + Helper->buildOriginFlowChain("s", "tgta", "after_use"); llvm::SmallVector<OriginID> ChainForTgtB = - Helper->buildOriginFlowChainInOneBlock("s", "tgtb", "after_use"); + Helper->buildOriginFlowChain("s", "tgtb", "after_use"); EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("a"))); EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("result"))); @@ -2078,49 +2121,5 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithLifetimeBound) { EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("result"))); EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("a")))); } - -TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainInMultiBlock) { - SetupTest(R"( - void target(bool c1, bool c2) { - int *s; - int *a, *b, *c; - - { - int tgta, tgtb, tgtc; - a = &tgta; - b = &tgtb; - c = &tgtc; - } - - if (c1) { - s = c2 ? a : b; - } else { - s = c; - } - - POINT(after_nested_merge); - (void)*s; - } - )"); - - llvm::SmallVector<OriginID> ChainForTgtA = - Helper->buildOriginFlowChainInOneBlock("s", "tgta", "after_nested_merge"); - llvm::SmallVector<OriginID> ChainForTgtB = - Helper->buildOriginFlowChainInOneBlock("s", "tgtb", "after_nested_merge"); - llvm::SmallVector<OriginID> ChainForTgtC = - Helper->buildOriginFlowChainInOneBlock("s", "tgtc", "after_nested_merge"); - - EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("a"))); - EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("b")))); - EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("c")))); - - EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("b"))); - EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("a")))); - EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("c")))); - - EXPECT_THAT(ChainForTgtC, Contains(*Helper->getOriginForDecl("c"))); - EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("b")))); - EXPECT_THAT(ChainForTgtC, Not(Contains(*Helper->getOriginForDecl("a")))); -} } // anonymous namespace } // namespace clang::lifetimes::internal >From c9046561524beda497c09b35975e141e7f95eac4 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Sun, 21 Jun 2026 15:02:59 +0800 Subject: [PATCH 11/19] Add a fallback in isInvalidExpr(CallExpr) Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Sema/SemaLifetimeSafety.h | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 6baaa320cfd6f..4130e72b8b88a 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -558,6 +558,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { if (const auto *CE = dyn_cast<CallExpr>(E)) { if (const auto *FE = CE->getDirectCallee()) return FE->isImplicit(); + return false; } return true; >From 119a023286e909a70bc798b76afe4cb24faa413f Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Mon, 22 Jun 2026 15:58:15 +0800 Subject: [PATCH 12/19] Add debug output ========================================== Lifetime Analysis buildOriginFlow ========================================== StartOriginID: 3, TargetLoanID: 6 CurrBlockID: 3, StartOriginID: 3 Find OriginID: 29 Find OriginID: 9 EndOriginID: 9 CurrBlockID: 6, StartOriginID: 3 EndOriginID: 3 Signed-off-by: Yuan Suo <[email protected]> --- .../LifetimeSafety/LoanPropagation.cpp | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index a77dbf3cf3b58..27f5ecf65ceb9 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -210,6 +210,18 @@ class AnalysisImpl assert(getLoans(StartOID, StartPoint).contains(TargetLoan) && "TargetLoan must be present in the StartOID at the StartPoint"); + DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", + llvm::dbgs() + << "==========================================\n"); + DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", + llvm::dbgs() << " Lifetime Analysis buildOriginFlow\n"); + DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", + llvm::dbgs() + << "==========================================\n"); + DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", + llvm::dbgs() << "StartOriginID: " << StartOID + << ", TargetLoanID: " << TargetLoan << "\n\n"); + std::optional<OriginID> FinalOID; llvm::DenseMap<OriginID, OriginID> VistedOriginIDs; @@ -256,6 +268,10 @@ class AnalysisImpl auto [CurrBlock, CurrOID] = PendingContext.front(); PendingContext.pop(); + DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", + llvm::dbgs() << "CurrBlockID: " << CurrBlock->getBlockID() + << ", StartOriginID: " << CurrOID << "\n"); + const auto [BuildResult, Complete] = buildOriginFlowChain(CurrBlock, CurrOID, TargetLoan); if (!BuildResult.empty()) @@ -264,6 +280,9 @@ class AnalysisImpl if (Complete) return OriginFlowChainFilter(*FinalOID); + DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", + llvm::dbgs() << "EndOriginID: " << CurrOID << "\n"); + for (const CFGBlock *Block : CurrBlock->preds()) { SearchContext Context = {Block, CurrOID}; if (Block && VistedContext.insert(Context).second) @@ -329,6 +348,10 @@ class AnalysisImpl const OriginID SrcOriginID = OFF->getSrcOriginID(); if (!getLoans(SrcOriginID, OFF).contains(TargetLoan)) continue; + + DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", + llvm::dbgs() + << "\tFind OriginID: " << SrcOriginID << "\n"); OriginFlowChain.push_back(SrcOriginID); CurrOID = SrcOriginID; } >From 66398f953fbb06025db9ab01f6073c061cbc5b96 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Tue, 23 Jun 2026 21:16:20 +0800 Subject: [PATCH 13/19] Simplify buildOriginFlowChain and add some test case Signed-off-by: Yuan Suo <[email protected]> --- .../Analyses/LifetimeSafety/LoanPropagation.h | 2 +- .../LifetimeSafety/LoanPropagation.cpp | 58 +++++-------------- clang/lib/Sema/SemaLifetimeSafety.h | 4 +- clang/test/Sema/LifetimeSafety/safety.cpp | 42 ++++++++++++++ 4 files changed, 59 insertions(+), 47 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h index 97d529e91a9ee..2c3357125810c 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h @@ -40,7 +40,7 @@ class LoanPropagationAnalysis { /// Builds the chain of origins through which a loan has propagated. /// - /// Starting from StartPoint where StartOID currently holds TargetLoan, + /// Starting from the last fact of the block containg StartPoint, /// this function traces backwards through OriginFlowFacts to identify the /// sequence of origins through which the loan flowed, ending at the origin /// where the loan was originally issued. diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 27f5ecf65ceb9..2d9acbd822f16 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -222,35 +222,6 @@ class AnalysisImpl llvm::dbgs() << "StartOriginID: " << StartOID << ", TargetLoanID: " << TargetLoan << "\n\n"); - std::optional<OriginID> FinalOID; - llvm::DenseMap<OriginID, OriginID> VistedOriginIDs; - - const auto OriginFlowChainFilter = [&VistedOriginIDs](OriginID FinalOID) { - llvm::SmallVector<OriginID> OriginFlowChain; - while (true) { - OriginFlowChain.push_back(FinalOID); - const auto NextOriginID = VistedOriginIDs.find(FinalOID); - if (NextOriginID == VistedOriginIDs.end()) - break; - FinalOID = NextOriginID->second; - } - return OriginFlowChain; - }; - - const auto InsertVistedOriginIDs = - [&VistedOriginIDs, &FinalOID](llvm::ArrayRef<OriginID> OriginFlowChain, - OriginID &StartOID) { - if (!VistedOriginIDs.empty()) - VistedOriginIDs.insert({OriginFlowChain[0], StartOID}); - - for (size_t i = 0; i < OriginFlowChain.size() - 1; ++i) - VistedOriginIDs.insert( - {OriginFlowChain[i + 1], OriginFlowChain[i]}); - - StartOID = OriginFlowChain.back(); - FinalOID = StartOID; - }; - const CFGBlock *EndBlock = nullptr; size_t BlockID = FactMgr.getBlockID(StartPoint); for (const CFGBlock *Block : *Cfg) @@ -259,14 +230,14 @@ class AnalysisImpl break; } - using SearchContext = std::pair<const CFGBlock *, OriginID>; - std::queue<SearchContext> PendingContext; - llvm::SmallSet<SearchContext, 32> VistedContext; - PendingContext.push({EndBlock, StartOID}); + OriginID CurrOID = StartOID; + llvm::SmallVector<OriginID> OriginFlowChain; + std::queue<const CFGBlock *> PendingState; + PendingState.push(EndBlock); - while (!PendingContext.empty()) { - auto [CurrBlock, CurrOID] = PendingContext.front(); - PendingContext.pop(); + while (!PendingState.empty()) { + const CFGBlock *CurrBlock = PendingState.front(); + PendingState.pop(); DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", llvm::dbgs() << "CurrBlockID: " << CurrBlock->getBlockID() @@ -274,20 +245,19 @@ class AnalysisImpl const auto [BuildResult, Complete] = buildOriginFlowChain(CurrBlock, CurrOID, TargetLoan); - if (!BuildResult.empty()) - InsertVistedOriginIDs(BuildResult, CurrOID); + if (!BuildResult.empty()) { + OriginFlowChain.append(BuildResult); + CurrOID = BuildResult.back(); + } if (Complete) - return OriginFlowChainFilter(*FinalOID); + return OriginFlowChain; DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", llvm::dbgs() << "EndOriginID: " << CurrOID << "\n"); - for (const CFGBlock *Block : CurrBlock->preds()) { - SearchContext Context = {Block, CurrOID}; - if (Block && VistedContext.insert(Context).second) - PendingContext.push(Context); - } + for (const CFGBlock *Block : CurrBlock->preds()) + PendingState.push(Block); } llvm_unreachable( diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 4130e72b8b88a..ff127baa5549d 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -580,10 +580,10 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { if (OriginExprChain.empty()) return; - const Expr *LastExpr = OriginExprChain.front(); + const Expr *LastExpr = OriginExprChain.back(); std::string IssueStr = getDiagSubjectDescription(LastExpr); - for (const Expr *CurrExpr : OriginExprChain.drop_front()) { + for (const Expr *CurrExpr : reverse(OriginExprChain.drop_back())) { if (!shouldShowInAliasChain(CurrExpr, LastExpr)) continue; S.Diag(CurrExpr->getBeginLoc(), diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index 82d443b179bd3..fa2779a9b8811 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -3905,3 +3905,45 @@ struct [[gsl::Pointer()]] PtrWithInt { int x; }; PtrWithInt f() { return PtrWithInt{10}; } + +//===----------------------------------------------------------------------===// +// buildOriginFlowChain +//===----------------------------------------------------------------------===// + +void used_variable_reassigned() { + View p, q, r; + { + MyObj a; + p = a; // expected-warning {{local variable 'a' does not live long enough}} + q = p; // expected-note {{local variable 'p' aliases the storage of local variable 'a'}} + r = q; // expected-note {{local variable 'q' aliases the storage of local variable 'a'}} + } // expected-note {{destroyed here}} + r.use(); // expected-note {{later used here}} + + MyObj b; + r = b; + r.use(); +} + +void multi_reassigned(bool condition) { + MyObj v1, v2, v3; + View p1, p2, p3, p4; + { + MyObj v4; + + p1 = v1; + p2 = v2; + p3 = v3; + p4 = v4; // expected-warning {{local variable 'v4' does not live long enough}} + + while (condition) { + View temp = p1; + p1 = p2; // expected-note {{local variable 'p2' aliases the storage of local variable 'v4'}} + p2 = p3; // expected-note {{local variable 'p3' aliases the storage of local variable 'v4'}} + p3 = p4; // expected-note {{local variable 'p4' aliases the storage of local variable 'v4'}} + p4 = temp; + } + } // expected-note {{destroyed here}} + + p1.use(); // expected-note {{later used here}} +} >From 1b3bda5fd643f2dd2d9d54998edd2b9c324ffd4e Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Tue, 23 Jun 2026 21:48:00 +0800 Subject: [PATCH 14/19] rebase Signed-off-by: Yuan Suo <[email protected]> --- clang/test/Sema/LifetimeSafety/safety.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index fa2779a9b8811..d1f8ffa6c430e 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -236,7 +236,7 @@ void overrides_potential(bool cond) { { MyObj s; q = &s; // expected-warning {{does not live long enough}} - p = q; + p = q; // expected-note {{local variable 'q' aliases the storage of local variable 's'}} } // expected-note {{local variable 's' is destroyed here}} if (cond) { @@ -1236,8 +1236,11 @@ void conditional_operator_lifetimebound_nested(bool cond) { MyObj* p; { MyObj a, b; - p = Identity(cond ? Identity(&a) // expected-warning {{local variable 'a' does not live long enough}} - : Identity(&b)); // expected-warning {{local variable 'b' does not live long enough}} + p = Identity(cond ? Identity(&a) // expected-warning {{local variable 'a' does not live long enough}} \ + // expected-note 2 {{result of call to 'Identity' aliases the storage of local variable 'a'}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b'}} + : Identity(&b)); // expected-warning {{local variable 'b' does not live long enough}} \ + // expected-note {{result of call to 'Identity' aliases the storage of local variable 'b'}} } // expected-note {{local variable 'b' is destroyed here}} expected-note {{local variable 'a' is destroyed here}} (void)*p; // expected-note 2 {{later used here}} } @@ -2637,8 +2640,7 @@ void chained_defaulted_assignment_propagation() { std::string str{"abc"}; S a = getS(str); // expected-warning {{local variable 'str' does not live long enough}} \ // expected-note {{result of call to 'getS' aliases the storage of local variable 'str'}} - c = b = a; // expected-note {{local variable 'a' aliases the storage of local variable 'str'}}\ - // expected-note {{expression aliases the storage of local variable 'str'}} + c = b = a; // expected-note {{local variable 'a' aliases the storage of local variable 'str'}} } // expected-note {{local variable 'str' is destroyed here}} use(c); // expected-note {{later used here}} } >From e023dc361b76c9c29b2283f3931a1f9e7bbb4ebb Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Tue, 23 Jun 2026 22:15:43 +0800 Subject: [PATCH 15/19] Add a test case for function pointer Signed-off-by: Yuan Suo <[email protected]> --- clang/test/Sema/LifetimeSafety/safety.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index d1f8ffa6c430e..0ba3ff527122c 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -855,6 +855,18 @@ void lifetimebound_multiple_args_potential(bool cond) { v.use(); // expected-note 2 {{later used here}} } +// FIXME: Detect this. +void func_pointer() { + View p; + View (*func_ptr)(View v [[clang::lifetimebound]]); + { + MyObj s; + View a = Identity(s); + p = func_ptr(a); + } + p.use(); +} + View SelectFirst(View a [[clang::lifetimebound]], View b); void lifetimebound_mixed_args() { View v; >From 0dce435796c83d96d5849523067592dbece26943 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Tue, 23 Jun 2026 22:22:16 +0800 Subject: [PATCH 16/19] Remove unused include Signed-off-by: Yuan Suo <[email protected]> --- clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 2d9acbd822f16..212c98720165f 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -18,7 +18,6 @@ #include "clang/Analysis/CFG.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/BitVector.h" -#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/raw_ostream.h" >From 153274f8cea0599eea52a6b52e496e85abc0c931 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Thu, 25 Jun 2026 15:42:56 +0800 Subject: [PATCH 17/19] Imporve unit test case Signed-off-by: Yuan Suo <[email protected]> --- clang/unittests/Analysis/LifetimeSafetyTest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index d79161ae1db54..57cf7068affae 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -1996,6 +1996,8 @@ TEST_F(LifetimeAnalysisTest, BuildOriginFlowChain) { POINT(after_nested_merge); (void)*s; + int reset; + s = &reset; } )"); >From d7840bf01da2ea4ae46029f1bb96786777f3671f Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Thu, 25 Jun 2026 15:43:57 +0800 Subject: [PATCH 18/19] PendingState -> PendingBlocks Signed-off-by: Yuan Suo <[email protected]> --- .../lib/Analysis/LifetimeSafety/LoanPropagation.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 212c98720165f..8bde1f9c84ef8 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -231,12 +231,12 @@ class AnalysisImpl OriginID CurrOID = StartOID; llvm::SmallVector<OriginID> OriginFlowChain; - std::queue<const CFGBlock *> PendingState; - PendingState.push(EndBlock); + std::queue<const CFGBlock *> PendingBlocks; + PendingBlocks.push(EndBlock); - while (!PendingState.empty()) { - const CFGBlock *CurrBlock = PendingState.front(); - PendingState.pop(); + while (!PendingBlocks.empty()) { + const CFGBlock *CurrBlock = PendingBlocks.front(); + PendingBlocks.pop(); DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", llvm::dbgs() << "CurrBlockID: " << CurrBlock->getBlockID() @@ -256,7 +256,7 @@ class AnalysisImpl llvm::dbgs() << "EndOriginID: " << CurrOID << "\n"); for (const CFGBlock *Block : CurrBlock->preds()) - PendingState.push(Block); + PendingBlocks.push(Block); } llvm_unreachable( >From e852599474fe7a9b6ad12872544f36a8056fb455 Mon Sep 17 00:00:00 2001 From: Yuan Suo <[email protected]> Date: Thu, 25 Jun 2026 15:50:31 +0800 Subject: [PATCH 19/19] Merge first debug statement Signed-off-by: Yuan Suo <[email protected]> --- .../Analysis/LifetimeSafety/LoanPropagation.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index 8bde1f9c84ef8..c75a1a0216183 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -211,15 +211,11 @@ class AnalysisImpl DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", llvm::dbgs() - << "==========================================\n"); - DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", - llvm::dbgs() << " Lifetime Analysis buildOriginFlow\n"); - DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", - llvm::dbgs() - << "==========================================\n"); - DEBUG_WITH_TYPE("LifetimeBuildOriginFlow", - llvm::dbgs() << "StartOriginID: " << StartOID - << ", TargetLoanID: " << TargetLoan << "\n\n"); + << "==========================================\n" + << " Lifetime Analysis buildOriginFlow\n" + << "==========================================\n" + << "StartOriginID: " << StartOID + << ", TargetLoanID: " << TargetLoan << "\n\n"); const CFGBlock *EndBlock = nullptr; size_t BlockID = FactMgr.getBlockID(StartPoint); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
