https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/148222
>From 328e89349a0e71f2d2e8a83d1b63baf46c129b50 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Fri, 11 Jul 2025 11:11:47 +0000 Subject: [PATCH 1/3] [LifetimeSafety] Add expired loans analysis --- clang/lib/Analysis/LifetimeSafety.cpp | 398 ++++++++++-------- .../Sema/warn-lifetime-safety-dataflow.cpp | 30 +- 2 files changed, 237 insertions(+), 191 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp index bf67bea6c9933..70ecd79e48809 100644 --- a/clang/lib/Analysis/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -496,7 +496,166 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> { }; // ========================================================================= // -// The Dataflow Lattice +// Generic Dataflow Analysis +// ========================================================================= // +/// A generic, policy-based driver for forward dataflow analyses. It combines +/// the dataflow runner and the transferer logic into a single class hierarchy. +/// +/// The derived class is expected to provide: +/// - A `Lattice` type. +/// - `StringRef getAnalysisName() const` +/// - `Lattice getInitialState();` The initial state at the function entry. +/// - `Lattice join(Lattice, Lattice);` Merges states from multiple CFG paths. +/// - `Lattice transfer(Lattice, const FactType&);` Defines how a single +/// lifetime-relevant `Fact` transforms the lattice state. Only overloads +/// for facts relevant to the analysis need to be implemented. +/// +/// \tparam Derived The CRTP derived class that implements the specific +/// analysis. +/// \tparam LatticeType The dataflow lattice used by the analysis. +/// TODO: Maybe use the dataflow framework! The framework might need changes +/// to support the current comparison done at block-entry. +template <typename Derived, typename LatticeType> class DataflowAnalysis { +public: + using Lattice = LatticeType; + +private: + const CFG &Cfg; + AnalysisDeclContext &AC; + + llvm::DenseMap<const CFGBlock *, Lattice> BlockEntryStates; + llvm::DenseMap<const CFGBlock *, Lattice> BlockExitStates; + +protected: + FactManager &AllFacts; + + explicit DataflowAnalysis(const CFG &C, AnalysisDeclContext &AC, + FactManager &F) + : Cfg(C), AC(AC), AllFacts(F) {} + +public: + void run() { + Derived &D = static_cast<Derived &>(*this); + llvm::TimeTraceScope Time(D.getAnalysisName()); + + ForwardDataflowWorklist Worklist(Cfg, AC); + const CFGBlock *Entry = &Cfg.getEntry(); + BlockEntryStates[Entry] = D.getInitialState(); + Worklist.enqueueBlock(Entry); + llvm::SmallBitVector Visited; + Visited.resize(Cfg.getNumBlockIDs() + 1); + + while (const CFGBlock *B = Worklist.dequeue()) { + Lattice EntryState = getEntryState(B); + Lattice ExitState = transferBlock(B, EntryState); + BlockExitStates[B] = ExitState; + Visited.set(B->getBlockID()); + + for (const CFGBlock *Successor : B->succs()) { + Lattice OldSuccEntryState = getEntryState(Successor); + Lattice NewSuccEntryState = D.join(OldSuccEntryState, ExitState); + + // Enqueue the successor if its entry state has changed or if we have + // never visited it. + if (!Visited.test(Successor->getBlockID()) || + NewSuccEntryState != OldSuccEntryState) { + BlockEntryStates[Successor] = NewSuccEntryState; + Worklist.enqueueBlock(Successor); + } + } + } + } + + Lattice getEntryState(const CFGBlock *B) const { + return BlockEntryStates.lookup(B); + } + + Lattice getExitState(const CFGBlock *B) const { + return BlockExitStates.lookup(B); + } + + void dump() const { + const Derived *D = static_cast<const Derived *>(this); + llvm::dbgs() << "==========================================\n"; + llvm::dbgs() << D->getAnalysisName() << " results:\n"; + llvm::dbgs() << "==========================================\n"; + const CFGBlock &B = Cfg.getExit(); + getExitState(&B).dump(llvm::dbgs()); + } + +private: + /// Computes the exit state of a block by applying all its facts sequentially + /// to a given entry state. + /// TODO: We might need to store intermediate states per-fact in the block for + /// later analysis. + Lattice transferBlock(const CFGBlock *Block, Lattice EntryState) { + Lattice BlockState = EntryState; + for (const Fact *F : AllFacts.getFacts(Block)) { + BlockState = transferFact(BlockState, F); + } + return BlockState; + } + + Lattice transferFact(Lattice In, const Fact *F) { + Derived *d = static_cast<Derived *>(this); + switch (F->getKind()) { + case Fact::Kind::Issue: + return d->transfer(In, *F->getAs<IssueFact>()); + case Fact::Kind::Expire: + return d->transfer(In, *F->getAs<ExpireFact>()); + case Fact::Kind::AssignOrigin: + return d->transfer(In, *F->getAs<AssignOriginFact>()); + case Fact::Kind::ReturnOfOrigin: + return d->transfer(In, *F->getAs<ReturnOfOriginFact>()); + } + llvm_unreachable("Unknown fact kind"); + } + +public: + Lattice transfer(Lattice In, const IssueFact &) { return In; } + Lattice transfer(Lattice In, const ExpireFact &) { return In; } + Lattice transfer(Lattice In, const AssignOriginFact &) { return In; } + Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; } +}; + +namespace utils { + +/// Computes the union of two ImmutableSets. +template <typename T> +llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, llvm::ImmutableSet<T> B, + typename llvm::ImmutableSet<T>::Factory &F) { + if (A.getHeight() < B.getHeight()) + std::swap(A, B); + for (const T &E : B) + A = F.add(A, E); + return A; +} + +/// Computes the key-wise union of two ImmutableMaps. +// TODO(opt): This key-wise join is a performance bottleneck. A more +// efficient merge could be implemented using a Patricia Trie or HAMT +// instead of the current AVL-tree-based ImmutableMap. +template <typename K, typename V, typename Joiner> +llvm::ImmutableMap<K, V> +join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B, + typename llvm::ImmutableMap<K, V>::Factory &F, Joiner joinValues) { + if (A.getHeight() < B.getHeight()) + std::swap(A, B); + + // For each element in B, join it with the corresponding element in A + // (or with an empty value if it doesn't exist in A). + for (const auto &Entry : B) { + const K &Key = Entry.first; + const V &ValB = Entry.second; + const V &ValA = *A.lookup(Key); + A = F.add(A, Key, joinValues(ValA, ValB)); + } + return A; +} +} // namespace utils + +// ========================================================================= // +// Loan Propagation Analysis // ========================================================================= // // Using LLVM's immutable collections is efficient for dataflow analysis @@ -509,82 +668,37 @@ using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>; /// that all created states share the same underlying memory management. struct LifetimeFactory { OriginLoanMap::Factory OriginMapFactory; - LoanSet::Factory LoanSetFact; + LoanSet::Factory LoanSetFactory; /// Creates a singleton set containing only the given loan ID. LoanSet createLoanSet(LoanID LID) { - return LoanSetFact.add(LoanSetFact.getEmptySet(), LID); + return LoanSetFactory.add(LoanSetFactory.getEmptySet(), LID); } }; -/// LifetimeLattice represents the state of our analysis at a given program -/// point. It is an immutable object, and all operations produce a new -/// instance rather than modifying the existing one. -struct LifetimeLattice { +/// Represents the dataflow lattice for loan propagation. +/// +/// This lattice tracks which loans each origin may hold at a given program +/// point.The lattice has a finite height: An origin's loan set is bounded by +/// the total number of loans in the function. +/// TODO(opt): To reduce the lattice size, propagate origins of declarations, +/// not expressions, because expressions are not visible across blocks. +struct LoanPropagationLattice { /// The map from an origin to the set of loans it contains. - /// The lattice has a finite height: An origin's loan set is bounded by the - /// total number of loans in the function. - /// TODO(opt): To reduce the lattice size, propagate origins of declarations, - /// not expressions, because expressions are not visible across blocks. OriginLoanMap Origins = OriginLoanMap(nullptr); - explicit LifetimeLattice(const OriginLoanMap &S) : Origins(S) {} - LifetimeLattice() = default; + explicit LoanPropagationLattice(const OriginLoanMap &S) : Origins(S) {} + LoanPropagationLattice() = default; - bool operator==(const LifetimeLattice &Other) const { + bool operator==(const LoanPropagationLattice &Other) const { return Origins == Other.Origins; } - bool operator!=(const LifetimeLattice &Other) const { + bool operator!=(const LoanPropagationLattice &Other) const { return !(*this == Other); } - LoanSet getLoans(OriginID OID) const { - if (auto *Loans = Origins.lookup(OID)) - return *Loans; - return LoanSet(nullptr); - } - - /// Computes the union of two lattices by performing a key-wise join of - /// their OriginLoanMaps. - // TODO(opt): This key-wise join is a performance bottleneck. A more - // efficient merge could be implemented using a Patricia Trie or HAMT - // instead of the current AVL-tree-based ImmutableMap. - // TODO(opt): Keep the state small by removing origins which become dead. - LifetimeLattice join(const LifetimeLattice &Other, - LifetimeFactory &Factory) const { - /// Merge the smaller map into the larger one ensuring we iterate over the - /// smaller map. - if (Origins.getHeight() < Other.Origins.getHeight()) - return Other.join(*this, Factory); - - OriginLoanMap JoinedState = Origins; - // For each origin in the other map, union its loan set with ours. - for (const auto &Entry : Other.Origins) { - OriginID OID = Entry.first; - LoanSet OtherLoanSet = Entry.second; - JoinedState = Factory.OriginMapFactory.add( - JoinedState, OID, join(getLoans(OID), OtherLoanSet, Factory)); - } - return LifetimeLattice(JoinedState); - } - - LoanSet join(LoanSet a, LoanSet b, LifetimeFactory &Factory) const { - /// Merge the smaller set into the larger one ensuring we iterate over the - /// smaller set. - if (a.getHeight() < b.getHeight()) - std::swap(a, b); - LoanSet Result = a; - for (LoanID LID : b) { - /// TODO(opt): Profiling shows that this loop is a major performance - /// bottleneck. Investigate using a BitVector to represent the set of - /// loans for improved join performance. - Result = Factory.LoanSetFact.add(Result, LID); - } - return Result; - } - void dump(llvm::raw_ostream &OS) const { - OS << "LifetimeLattice State:\n"; + OS << "LoanPropagationLattice State:\n"; if (Origins.isEmpty()) OS << " <empty>\n"; for (const auto &Entry : Origins) { @@ -596,143 +710,74 @@ struct LifetimeLattice { } }; -// ========================================================================= // -// The Transfer Function -// ========================================================================= // -class Transferer { - FactManager &AllFacts; +/// The analysis that tracks which loans belong to which origins. +class LoanPropagationAnalysis + : public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice> { + LifetimeFactory &Factory; public: - explicit Transferer(FactManager &F, LifetimeFactory &Factory) - : AllFacts(F), Factory(Factory) {} + LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, + LifetimeFactory &Factory) + : DataflowAnalysis(C, AC, F), Factory(Factory) {} - /// Computes the exit state of a block by applying all its facts sequentially - /// to a given entry state. - /// TODO: We might need to store intermediate states per-fact in the block for - /// later analysis. - LifetimeLattice transferBlock(const CFGBlock *Block, - LifetimeLattice EntryState) { - LifetimeLattice BlockState = EntryState; - llvm::ArrayRef<const Fact *> Facts = AllFacts.getFacts(Block); + using DataflowAnalysis<LoanPropagationAnalysis, Lattice>::transfer; - for (const Fact *F : Facts) { - BlockState = transferFact(BlockState, F); - } - return BlockState; + const char *getAnalysisName() const { return "Loan Propagation"; } + + Lattice getInitialState() { return Lattice{}; } + + /// Merges two lattices by taking the union of loans for each origin. + // TODO(opt): Keep the state small by removing origins which become dead. + Lattice join(Lattice A, Lattice B) { + OriginLoanMap JoinedOrigins = + utils::join(A.Origins, B.Origins, Factory.OriginMapFactory, + [this](LoanSet S1, LoanSet S2) { + return utils::join(S1, S2, Factory.LoanSetFactory); + }); + return Lattice(JoinedOrigins); } -private: - LifetimeLattice transferFact(LifetimeLattice In, const Fact *F) { - switch (F->getKind()) { - case Fact::Kind::Issue: - return transfer(In, *F->getAs<IssueFact>()); - case Fact::Kind::AssignOrigin: - return transfer(In, *F->getAs<AssignOriginFact>()); - // Expire and ReturnOfOrigin facts don't modify the Origins and the State. - case Fact::Kind::Expire: - case Fact::Kind::ReturnOfOrigin: - return In; - } - llvm_unreachable("Unknown fact kind"); + LoanSet join(LoanSet A, LoanSet B) { + if (A.getHeight() < B.getHeight()) + std::swap(A, B); + for (LoanID L : B) + A = Factory.LoanSetFactory.add(A, L); + return A; } /// A new loan is issued to the origin. Old loans are erased. - LifetimeLattice transfer(LifetimeLattice In, const IssueFact &F) { + Lattice transfer(Lattice In, const IssueFact &F) { OriginID OID = F.getOriginID(); LoanID LID = F.getLoanID(); - return LifetimeLattice(Factory.OriginMapFactory.add( + return LoanPropagationLattice(Factory.OriginMapFactory.add( In.Origins, OID, Factory.createLoanSet(LID))); } /// The destination origin's loan set is replaced by the source's. /// This implicitly "resets" the old loans of the destination. - LifetimeLattice transfer(LifetimeLattice InState, const AssignOriginFact &F) { + Lattice transfer(Lattice In, const AssignOriginFact &F) { OriginID DestOID = F.getDestOriginID(); OriginID SrcOID = F.getSrcOriginID(); - LoanSet SrcLoans = InState.getLoans(SrcOID); - return LifetimeLattice( - Factory.OriginMapFactory.add(InState.Origins, DestOID, SrcLoans)); + LoanSet SrcLoans = getLoans(In, SrcOID); + return LoanPropagationLattice( + Factory.OriginMapFactory.add(In.Origins, DestOID, SrcLoans)); } -}; -// ========================================================================= // -// Dataflow analysis -// ========================================================================= // - -/// Drives the intra-procedural dataflow analysis. -/// -/// Orchestrates the analysis by iterating over the CFG using a worklist -/// algorithm. It computes a fixed point by propagating the LifetimeLattice -/// state through each block until the state no longer changes. -/// TODO: Maybe use the dataflow framework! The framework might need changes -/// to support the current comparison done at block-entry. -class LifetimeDataflow { - const CFG &Cfg; - AnalysisDeclContext &AC; - LifetimeFactory LifetimeFact; - - Transferer Xfer; - - /// Stores the merged analysis state at the entry of each CFG block. - llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockEntryStates; - /// Stores the analysis state at the exit of each CFG block, after the - /// transfer function has been applied. - llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockExitStates; - -public: - LifetimeDataflow(const CFG &C, FactManager &FS, AnalysisDeclContext &AC) - : Cfg(C), AC(AC), Xfer(FS, LifetimeFact) {} - - void run() { - llvm::TimeTraceScope TimeProfile("Lifetime Dataflow"); - ForwardDataflowWorklist Worklist(Cfg, AC); - const CFGBlock *Entry = &Cfg.getEntry(); - BlockEntryStates[Entry] = LifetimeLattice{}; - Worklist.enqueueBlock(Entry); - while (const CFGBlock *B = Worklist.dequeue()) { - LifetimeLattice EntryState = getEntryState(B); - LifetimeLattice ExitState = Xfer.transferBlock(B, EntryState); - BlockExitStates[B] = ExitState; - - for (const CFGBlock *Successor : B->succs()) { - auto SuccIt = BlockEntryStates.find(Successor); - LifetimeLattice OldSuccEntryState = (SuccIt != BlockEntryStates.end()) - ? SuccIt->second - : LifetimeLattice{}; - LifetimeLattice NewSuccEntryState = - OldSuccEntryState.join(ExitState, LifetimeFact); - // Enqueue the successor if its entry state has changed. - // TODO(opt): Consider changing 'join' to report a change if != - // comparison is found expensive. - if (SuccIt == BlockEntryStates.end() || - NewSuccEntryState != OldSuccEntryState) { - BlockEntryStates[Successor] = NewSuccEntryState; - Worklist.enqueueBlock(Successor); - } - } - } - } - - void dump() const { - llvm::dbgs() << "==========================================\n"; - llvm::dbgs() << " Dataflow results:\n"; - llvm::dbgs() << "==========================================\n"; - const CFGBlock &B = Cfg.getExit(); - getExitState(&B).dump(llvm::dbgs()); - } - - LifetimeLattice getEntryState(const CFGBlock *B) const { - return BlockEntryStates.lookup(B); - } - - LifetimeLattice getExitState(const CFGBlock *B) const { - return BlockExitStates.lookup(B); +private: + LoanSet getLoans(Lattice L, OriginID OID) { + if (auto *Loans = L.Origins.lookup(OID)) + return *Loans; + return Factory.LoanSetFactory.getEmptySet(); } }; // ========================================================================= // -// TODO: Analysing dataflow results and error reporting. +// TODO: +// - Modifying loan propagation to answer `LoanSet getLoans(Origin O, Point P)` +// - Adding loan expiry analysis to answer `bool isExpired(Loan L, Point P)` +// - Adding origin liveness analysis to answer `bool isLive(Origin O, Point P)` +// - Using the above three to perform the final error reporting. // ========================================================================= // } // anonymous namespace @@ -755,8 +800,9 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg, /// blocks; only Decls are visible. Therefore, loans in a block that /// never reach an Origin associated with a Decl can be safely dropped by /// the analysis. - LifetimeDataflow Dataflow(Cfg, FactMgr, AC); - Dataflow.run(); - DEBUG_WITH_TYPE("LifetimeDataflow", Dataflow.dump()); + LifetimeFactory Factory; + LoanPropagationAnalysis LoanPropagation(Cfg, AC, FactMgr, Factory); + LoanPropagation.run(); + DEBUG_WITH_TYPE("LifetimeLoanPropagation", LoanPropagation.dump()); } } // namespace clang diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp index 38dfdb98f08fc..0e98904ade86a 100644 --- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp +++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -mllvm -debug-only=LifetimeFacts,LifetimeDataflow -Wexperimental-lifetime-safety %s 2>&1 | FileCheck %s +// RUN: %clang_cc1 -mllvm -debug-only=LifetimeFacts,LifetimeLoanPropagation -Wexperimental-lifetime-safety %s 2>&1 | FileCheck %s // REQUIRES: asserts struct MyObj { @@ -19,7 +19,7 @@ MyObj* return_local_addr() { // CHECK: ReturnOfOrigin (OriginID: [[O_RET_VAL]]) // CHECK: Expire (LoanID: [[L_X]]) } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_ADDR_X]] contains Loan [[L_X]] // CHECK-DAG: Origin [[O_P]] contains Loan [[L_X]] // CHECK-DAG: Origin [[O_RET_VAL]] contains Loan [[L_X]] @@ -47,7 +47,7 @@ MyObj* assign_and_return_local_addr() { // CHECK: ReturnOfOrigin (OriginID: [[O_PTR2_RVAL_2]]) // CHECK: Expire (LoanID: [[L_Y]]) } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_ADDR_Y]] contains Loan [[L_Y]] // CHECK-DAG: Origin [[O_PTR1]] contains Loan [[L_Y]] // CHECK-DAG: Origin [[O_PTR2]] contains Loan [[L_Y]] @@ -65,7 +65,7 @@ int return_int_val() { return x; } // CHECK-NEXT: End of Block -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK: <empty> @@ -79,7 +79,7 @@ void loan_expires_cpp() { // CHECK: AssignOrigin (DestID: [[O_POBJ:[0-9]+]], SrcID: [[O_ADDR_OBJ]]) // CHECK: Expire (LoanID: [[L_OBJ]]) } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_ADDR_OBJ]] contains Loan [[L_OBJ]] // CHECK-DAG: Origin [[O_POBJ]] contains Loan [[L_OBJ]] @@ -96,7 +96,7 @@ void loan_expires_trivial() { // CHECK-NEXT: End of Block // FIXME: Add check for Expire once trivial destructors are handled for expiration. } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_ADDR_TRIVIAL_OBJ]] contains Loan [[L_TRIVIAL_OBJ]] // CHECK-DAG: Origin [[O_PTOBJ]] contains Loan [[L_TRIVIAL_OBJ]] @@ -119,7 +119,7 @@ void conditional(bool condition) { // CHECK: AssignOrigin (DestID: [[O_P_RVAL:[0-9]+]], SrcID: [[O_P]]) // CHECK: AssignOrigin (DestID: [[O_Q:[0-9]+]], SrcID: [[O_P_RVAL]]) } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_ADDR_A]] contains Loan [[L_A]] // CHECK-DAG: Origin [[O_ADDR_B]] contains Loan [[L_B]] // CHECK-DAG: Origin [[O_P]] contains Loan [[L_A]] @@ -163,7 +163,7 @@ void pointers_in_a_cycle(bool condition) { } // At the end of the analysis, the origins for the pointers involved in the cycle // (p1, p2, p3, temp) should all contain the loans from v1, v2, and v3 at the fixed point. -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V1]] // CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V2]] // CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V3]] @@ -195,7 +195,7 @@ void overwrite_origin() { // CHECK: Expire (LoanID: [[L_S2]]) // CHECK: Expire (LoanID: [[L_S1]]) } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK: Origin [[O_P]] contains Loan [[L_S2]] // CHECK-NOT: Origin [[O_P]] contains Loan [[L_S1]] @@ -213,7 +213,7 @@ void reassign_to_null() { } // FIXME: Have a better representation for nullptr than just an empty origin. // It should be a separate loan and origin kind. -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK: Origin [[O_P]] contains no loans @@ -235,7 +235,7 @@ void reassign_in_if(bool condition) { // CHECK: Expire (LoanID: [[L_S2]]) // CHECK: Expire (LoanID: [[L_S1]]) } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]] // CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]] // CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]] @@ -276,7 +276,7 @@ void assign_in_switch(int mode) { // CHECK-DAG: Expire (LoanID: [[L_S2]]) // CHECK-DAG: Expire (LoanID: [[L_S1]]) } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]] // CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]] // CHECK-DAG: Origin [[O_P]] contains Loan [[L_S3]] @@ -299,7 +299,7 @@ void loan_in_loop(bool condition) { // CHECK: Expire (LoanID: [[L_INNER]]) } } -// CHECK: Dataflow results: +// CHECK: LoanPropagation results: // CHECK-DAG: Origin [[O_P]] contains Loan [[L_INNER]] // CHECK-DAG: Origin [[O_ADDR_INNER]] contains Loan [[L_INNER]] @@ -326,7 +326,7 @@ void loop_with_break(int count) { // CHECK: Expire (LoanID: [[L_S1]]) } -// CHECK-LABEL: Dataflow results: +// CHECK-LABEL: LoanPropagation results: // CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]] // CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]] // CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]] @@ -355,7 +355,7 @@ void nested_scopes() { // CHECK: Expire (LoanID: [[L_OUTER]]) } -// CHECK-LABEL: Dataflow results: +// CHECK-LABEL: LoanPropagation results: // CHECK-DAG: Origin [[O_P]] contains Loan [[L_INNER]] // CHECK-DAG: Origin [[O_ADDR_INNER]] contains Loan [[L_INNER]] // CHECK-DAG: Origin [[O_ADDR_OUTER]] contains Loan [[L_OUTER]] >From b63cb4b11e825ecf037f454460dccb5d0d271068 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Tue, 15 Jul 2025 09:12:09 +0000 Subject: [PATCH 2/3] remove unused member function --- clang/lib/Analysis/LifetimeSafety.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp index 70ecd79e48809..dfc015e1e0ff8 100644 --- a/clang/lib/Analysis/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -738,14 +738,6 @@ class LoanPropagationAnalysis return Lattice(JoinedOrigins); } - LoanSet join(LoanSet A, LoanSet B) { - if (A.getHeight() < B.getHeight()) - std::swap(A, B); - for (LoanID L : B) - A = Factory.LoanSetFactory.add(A, L); - return A; - } - /// A new loan is issued to the origin. Old loans are erased. Lattice transfer(Lattice In, const IssueFact &F) { OriginID OID = F.getOriginID(); >From 7b26a72ba6ed5b7dd25c552b0983c85d32e38c8f Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Tue, 15 Jul 2025 09:21:58 +0000 Subject: [PATCH 3/3] fix tests --- clang/lib/Analysis/LifetimeSafety.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp index dfc015e1e0ff8..0e013ec23e776 100644 --- a/clang/lib/Analysis/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -18,6 +18,7 @@ #include "llvm/ADT/ImmutableMap.h" #include "llvm/ADT/ImmutableSet.h" #include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" #include "llvm/Support/TimeProfiler.h" @@ -647,8 +648,10 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B, for (const auto &Entry : B) { const K &Key = Entry.first; const V &ValB = Entry.second; - const V &ValA = *A.lookup(Key); - A = F.add(A, Key, joinValues(ValA, ValB)); + if (const V *ValA = A.lookup(Key)) + A = F.add(A, Key, joinValues(*ValA, ValB)); + else + A = F.add(A, Key, ValB); } return A; } @@ -723,7 +726,7 @@ class LoanPropagationAnalysis using DataflowAnalysis<LoanPropagationAnalysis, Lattice>::transfer; - const char *getAnalysisName() const { return "Loan Propagation"; } + StringRef getAnalysisName() const { return "LoanPropagation"; } Lattice getInitialState() { return Lattice{}; } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits