[clang] [LifetimeSafety] Make the dataflow analysis generic (PR #148222)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 14 12:49:30 PDT 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/148222
>From 927f92a13bfe02ca3e458723a0e74fe0b7f53d18 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Fri, 11 Jul 2025 11:11:47 +0000
Subject: [PATCH 1/2] [LifetimeSafety] Add expired loans analysis
---
clang/lib/Analysis/LifetimeSafety.cpp | 363 ++++++++++++++------------
1 file changed, 203 insertions(+), 160 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index bf67bea6c9933..fe6ccf2a33c49 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -496,7 +496,108 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
};
// ========================================================================= //
-// The Dataflow Lattice
+// Generic Dataflow Framework
+// ========================================================================= //
+/// 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.
+/// - `Lattice getInitialState()`
+/// - `Lattice join(Lattice, Lattice)`
+/// - `Lattice transfer(Lattice, const FactType&)` for relevant fact types.
+///
+/// \tparam Derived The CRTP derived class that implements the specific
+/// analysis.
+/// \tparam LatticeType The lattice type used by the analysis.
+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);
+ ForwardDataflowWorklist Worklist(Cfg, AC);
+ const CFGBlock *Entry = &Cfg.getEntry();
+ BlockEntryStates[Entry] = d.getInitialState();
+ Worklist.enqueueBlock(Entry);
+
+ while (const CFGBlock *B = Worklist.dequeue()) {
+ Lattice EntryState = getEntryState(B);
+ Lattice ExitState = transferBlock(B, EntryState);
+ BlockExitStates[B] = ExitState;
+
+ for (const CFGBlock *Successor : B->succs()) {
+ auto SuccIt = BlockEntryStates.find(Successor);
+ Lattice OldSuccEntryState = (SuccIt != BlockEntryStates.end())
+ ? SuccIt->second
+ : d.getInitialState();
+ Lattice NewSuccEntryState = d.join(OldSuccEntryState, ExitState);
+
+ if (SuccIt == BlockEntryStates.end() ||
+ 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);
+ }
+
+private:
+ 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; }
+};
+
+// ========================================================================= //
+// Loan Propagation Analysis
// ========================================================================= //
// Using LLVM's immutable collections is efficient for dataflow analysis
@@ -538,51 +639,6 @@ struct LifetimeLattice {
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";
if (Origins.isEmpty())
@@ -596,144 +652,128 @@ struct LifetimeLattice {
}
};
-// ========================================================================= //
-// The Transfer Function
-// ========================================================================= //
-class Transferer {
- FactManager &AllFacts;
+class LoanPropagationAnalysis
+ : public DataflowAnalysis<LoanPropagationAnalysis, LifetimeLattice> {
+
LifetimeFactory &Factory;
public:
- explicit Transferer(FactManager &F, LifetimeFactory &Factory)
- : AllFacts(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);
-
- for (const Fact *F : Facts) {
- BlockState = transferFact(BlockState, F);
+ LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
+ LifetimeFactory &Factory)
+ : DataflowAnalysis(C, AC, F), Factory(Factory) {}
+
+ // Make the base class's transfer overloads visible.
+ using DataflowAnalysis<LoanPropagationAnalysis, Lattice>::transfer;
+
+ Lattice getInitialState() { return Lattice{}; }
+
+ Lattice join(Lattice L1, Lattice L2) {
+ /// Merge the smaller map into the larger one ensuring we iterate over the
+ /// smaller map.
+ if (L1.Origins.getHeight() < L2.Origins.getHeight())
+ std::swap(L1, L2);
+
+ OriginLoanMap JoinedState = L1.Origins;
+ // For each origin in the other map, union its loan set with ours.
+ for (const auto &Entry : L2.Origins) {
+ OriginID OID = Entry.first;
+ LoanSet OtherLoanSet = Entry.second;
+ JoinedState = Factory.OriginMapFactory.add(
+ JoinedState, OID, join(getLoans(L1, OID), OtherLoanSet));
}
- return BlockState;
+ return Lattice(JoinedState);
}
-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 S1, LoanSet S2) {
+ if (S1.getHeight() < S2.getHeight())
+ std::swap(S1, S2);
+ for (LoanID L : S2)
+ S1 = Factory.LoanSetFact.add(S1, L);
+ return S1;
}
- /// A new loan is issued to the origin. Old loans are erased.
- LifetimeLattice transfer(LifetimeLattice In, const IssueFact &F) {
+ // Overloads for specific fact types this transferer cares about.
+ Lattice transfer(Lattice In, const IssueFact &F) {
OriginID OID = F.getOriginID();
LoanID LID = F.getLoanID();
return LifetimeLattice(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);
+ LoanSet SrcLoans = getLoans(In, SrcOID);
return LifetimeLattice(
- Factory.OriginMapFactory.add(InState.Origins, DestOID, SrcLoans));
+ Factory.OriginMapFactory.add(In.Origins, DestOID, SrcLoans));
+ }
+
+private:
+ LoanSet getLoans(Lattice L, OriginID OID) {
+ if (auto *Loans = L.Origins.lookup(OID))
+ return *Loans;
+ return Factory.LoanSetFact.getEmptySet();
}
};
// ========================================================================= //
-// Dataflow analysis
+// Expired Loans 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;
+/// The lattice for tracking expired loans. It is a set of loan IDs.
+struct ExpiredLattice {
+ LoanSet Expired;
+
+ ExpiredLattice() : Expired(nullptr) {};
+ explicit ExpiredLattice(LoanSet S) : Expired(S) {}
+
+ bool operator==(const ExpiredLattice &Other) const {
+ return Expired == Other.Expired;
+ }
+ bool operator!=(const ExpiredLattice &Other) const {
+ return !(*this == Other);
+ }
+
+ void dump(llvm::raw_ostream &OS) const {
+ OS << "ExpiredLattice State:\n";
+ if (Expired.isEmpty())
+ OS << " <empty>\n";
+ for (const LoanID &LID : Expired)
+ OS << " Loan " << LID << " is expired\n";
+ }
+};
- Transferer Xfer;
+/// Transfer function for the expired loans analysis.
+class ExpiredLoansAnalysis
+ : public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice> {
- /// 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;
+ LoanSet::Factory &SetFactory;
public:
- LifetimeDataflow(const CFG &C, FactManager &FS, AnalysisDeclContext &AC)
- : Cfg(C), AC(AC), Xfer(FS, LifetimeFact) {}
+ ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
+ LoanSet::Factory &SF)
+ : DataflowAnalysis(C, AC, F), SetFactory(SF) {}
- 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;
+ using DataflowAnalysis<ExpiredLoansAnalysis, Lattice>::transfer;
- 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);
- }
- }
- }
- }
+ Lattice getInitialState() { return Lattice(SetFactory.getEmptySet()); }
- void dump() const {
- llvm::dbgs() << "==========================================\n";
- llvm::dbgs() << " Dataflow results:\n";
- llvm::dbgs() << "==========================================\n";
- const CFGBlock &B = Cfg.getExit();
- getExitState(&B).dump(llvm::dbgs());
+ Lattice join(Lattice L1, Lattice L2) const {
+ LoanSet JoinedSet = L1.Expired;
+ for (LoanID LID : L2.Expired)
+ JoinedSet = SetFactory.add(JoinedSet, LID);
+ return Lattice(JoinedSet);
}
- LifetimeLattice getEntryState(const CFGBlock *B) const {
- return BlockEntryStates.lookup(B);
+ // Overloads for specific fact types this transferer cares about.
+ Lattice transfer(Lattice In, const ExpireFact &F) {
+ return Lattice(SetFactory.add(In.Expired, F.getLoanID()));
}
- LifetimeLattice getExitState(const CFGBlock *B) const {
- return BlockExitStates.lookup(B);
+ Lattice transfer(Lattice In, const IssueFact &F) {
+ return Lattice(SetFactory.remove(In.Expired, F.getLoanID()));
}
};
-
-// ========================================================================= //
-// TODO: Analysing dataflow results and error reporting.
-// ========================================================================= //
} // anonymous namespace
void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
@@ -746,17 +786,20 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
FactGen.run();
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
- /// TODO(opt): Consider optimizing individual blocks before running the
- /// dataflow analysis.
- /// 1. Expression Origins: These are assigned once and read at most once,
- /// forming simple chains. These chains can be compressed into a single
- /// assignment.
- /// 2. Block-Local Loans: Origins of expressions are never read by other
- /// 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());
+ // Run Loan Propagation Analysis
+ LifetimeFactory LifetimeFact;
+ LoanPropagationAnalysis LoanPropagation(Cfg, AC, FactMgr, LifetimeFact);
+ LoanPropagation.run();
+ DEBUG_WITH_TYPE(
+ "LifetimeDataflow",
+ LoanPropagation.getExitState(&Cfg.getExit()).dump(llvm::dbgs()));
+
+ // Run Expired Loans Analysis
+ ExpiredLoansAnalysis ExpiredAnalysis(Cfg, AC, FactMgr,
+ LifetimeFact.LoanSetFact);
+ ExpiredAnalysis.run();
+ DEBUG_WITH_TYPE(
+ "ExpiredLoans",
+ ExpiredAnalysis.getExitState(&Cfg.getExit()).dump(llvm::dbgs()));
}
} // namespace clang
>From 702d2559f7f7fafbbc4deefe86d8cea2b9460fea Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 14 Jul 2025 18:51:17 +0000
Subject: [PATCH 2/2] bring back comments
---
clang/lib/Analysis/LifetimeSafety.cpp | 179 ++++++++----------
.../Sema/warn-lifetime-safety-dataflow.cpp | 30 +--
2 files changed, 93 insertions(+), 116 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index fe6ccf2a33c49..c1bd4e82f4327 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -503,13 +503,18 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
///
/// The derived class is expected to provide:
/// - A `Lattice` type.
-/// - `Lattice getInitialState()`
-/// - `Lattice join(Lattice, Lattice)`
-/// - `Lattice transfer(Lattice, const FactType&)` for relevant fact types.
+/// - `const char *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 lattice type 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;
@@ -530,10 +535,12 @@ template <typename Derived, typename LatticeType> class DataflowAnalysis {
public:
void run() {
- Derived &d = static_cast<Derived &>(*this);
+ Derived &D = static_cast<Derived &>(*this);
+ llvm::TimeTraceScope Time(D.getAnalysisName());
+
ForwardDataflowWorklist Worklist(Cfg, AC);
const CFGBlock *Entry = &Cfg.getEntry();
- BlockEntryStates[Entry] = d.getInitialState();
+ BlockEntryStates[Entry] = D.getInitialState();
Worklist.enqueueBlock(Entry);
while (const CFGBlock *B = Worklist.dequeue()) {
@@ -545,9 +552,11 @@ template <typename Derived, typename LatticeType> class DataflowAnalysis {
auto SuccIt = BlockEntryStates.find(Successor);
Lattice OldSuccEntryState = (SuccIt != BlockEntryStates.end())
? SuccIt->second
- : d.getInitialState();
- Lattice NewSuccEntryState = d.join(OldSuccEntryState, ExitState);
-
+ : D.getInitialState();
+ Lattice NewSuccEntryState = D.join(OldSuccEntryState, ExitState);
+ // 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;
@@ -565,7 +574,20 @@ template <typename Derived, typename LatticeType> class DataflowAnalysis {
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)) {
@@ -618,10 +640,10 @@ struct LifetimeFactory {
}
};
-/// LifetimeLattice represents the state of our analysis at a given program
-/// point. It is an immutable object, and all operations produce a new
+/// LoanPropagationLattice 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 {
+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.
@@ -629,13 +651,13 @@ struct LifetimeLattice {
/// 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);
}
@@ -653,7 +675,7 @@ struct LifetimeLattice {
};
class LoanPropagationAnalysis
- : public DataflowAnalysis<LoanPropagationAnalysis, LifetimeLattice> {
+ : public DataflowAnalysis<LoanPropagationAnalysis, LoanPropagationLattice> {
LifetimeFactory &Factory;
@@ -662,49 +684,58 @@ class LoanPropagationAnalysis
LifetimeFactory &Factory)
: DataflowAnalysis(C, AC, F), Factory(Factory) {}
- // Make the base class's transfer overloads visible.
using DataflowAnalysis<LoanPropagationAnalysis, Lattice>::transfer;
+ const char *getAnalysisName() const { return "LoanPropagation"; }
+
Lattice getInitialState() { return Lattice{}; }
- Lattice join(Lattice L1, Lattice L2) {
+ /// 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.
+ Lattice join(Lattice A, Lattice B) {
/// Merge the smaller map into the larger one ensuring we iterate over the
/// smaller map.
- if (L1.Origins.getHeight() < L2.Origins.getHeight())
- std::swap(L1, L2);
+ if (A.Origins.getHeight() < B.Origins.getHeight())
+ std::swap(A, B);
- OriginLoanMap JoinedState = L1.Origins;
+ OriginLoanMap JoinedState = A.Origins;
// For each origin in the other map, union its loan set with ours.
- for (const auto &Entry : L2.Origins) {
+ for (const auto &Entry : B.Origins) {
OriginID OID = Entry.first;
LoanSet OtherLoanSet = Entry.second;
JoinedState = Factory.OriginMapFactory.add(
- JoinedState, OID, join(getLoans(L1, OID), OtherLoanSet));
+ JoinedState, OID, join(getLoans(A, OID), OtherLoanSet));
}
return Lattice(JoinedState);
}
- LoanSet join(LoanSet S1, LoanSet S2) {
- if (S1.getHeight() < S2.getHeight())
- std::swap(S1, S2);
- for (LoanID L : S2)
- S1 = Factory.LoanSetFact.add(S1, L);
- return S1;
+ LoanSet join(LoanSet A, LoanSet B) {
+ if (A.getHeight() < B.getHeight())
+ std::swap(A, B);
+ for (LoanID L : B)
+ A = Factory.LoanSetFact.add(A, L);
+ return A;
}
- // Overloads for specific fact types this transferer cares about.
+ /// A new loan is issued to the origin. Old loans are erased.
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.
Lattice transfer(Lattice In, const AssignOriginFact &F) {
OriginID DestOID = F.getDestOriginID();
OriginID SrcOID = F.getSrcOriginID();
LoanSet SrcLoans = getLoans(In, SrcOID);
- return LifetimeLattice(
+ return LoanPropagationLattice(
Factory.OriginMapFactory.add(In.Origins, DestOID, SrcLoans));
}
@@ -715,65 +746,13 @@ class LoanPropagationAnalysis
return Factory.LoanSetFact.getEmptySet();
}
};
-
// ========================================================================= //
-// Expired Loans Analysis
+// 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.
// ========================================================================= //
-
-/// The lattice for tracking expired loans. It is a set of loan IDs.
-struct ExpiredLattice {
- LoanSet Expired;
-
- ExpiredLattice() : Expired(nullptr) {};
- explicit ExpiredLattice(LoanSet S) : Expired(S) {}
-
- bool operator==(const ExpiredLattice &Other) const {
- return Expired == Other.Expired;
- }
- bool operator!=(const ExpiredLattice &Other) const {
- return !(*this == Other);
- }
-
- void dump(llvm::raw_ostream &OS) const {
- OS << "ExpiredLattice State:\n";
- if (Expired.isEmpty())
- OS << " <empty>\n";
- for (const LoanID &LID : Expired)
- OS << " Loan " << LID << " is expired\n";
- }
-};
-
-/// Transfer function for the expired loans analysis.
-class ExpiredLoansAnalysis
- : public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice> {
-
- LoanSet::Factory &SetFactory;
-
-public:
- ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
- LoanSet::Factory &SF)
- : DataflowAnalysis(C, AC, F), SetFactory(SF) {}
-
- using DataflowAnalysis<ExpiredLoansAnalysis, Lattice>::transfer;
-
- Lattice getInitialState() { return Lattice(SetFactory.getEmptySet()); }
-
- Lattice join(Lattice L1, Lattice L2) const {
- LoanSet JoinedSet = L1.Expired;
- for (LoanID LID : L2.Expired)
- JoinedSet = SetFactory.add(JoinedSet, LID);
- return Lattice(JoinedSet);
- }
-
- // Overloads for specific fact types this transferer cares about.
- Lattice transfer(Lattice In, const ExpireFact &F) {
- return Lattice(SetFactory.add(In.Expired, F.getLoanID()));
- }
-
- Lattice transfer(Lattice In, const IssueFact &F) {
- return Lattice(SetFactory.remove(In.Expired, F.getLoanID()));
- }
-};
} // anonymous namespace
void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
@@ -786,20 +765,18 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
FactGen.run();
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
- // Run Loan Propagation Analysis
+ /// TODO(opt): Consider optimizing individual blocks before running the
+ /// dataflow analysis.
+ /// 1. Expression Origins: These are assigned once and read at most once,
+ /// forming simple chains. These chains can be compressed into a single
+ /// assignment.
+ /// 2. Block-Local Loans: Origins of expressions are never read by other
+ /// 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.
LifetimeFactory LifetimeFact;
LoanPropagationAnalysis LoanPropagation(Cfg, AC, FactMgr, LifetimeFact);
LoanPropagation.run();
- DEBUG_WITH_TYPE(
- "LifetimeDataflow",
- LoanPropagation.getExitState(&Cfg.getExit()).dump(llvm::dbgs()));
-
- // Run Expired Loans Analysis
- ExpiredLoansAnalysis ExpiredAnalysis(Cfg, AC, FactMgr,
- LifetimeFact.LoanSetFact);
- ExpiredAnalysis.run();
- DEBUG_WITH_TYPE(
- "ExpiredLoans",
- ExpiredAnalysis.getExitState(&Cfg.getExit()).dump(llvm::dbgs()));
+ 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]]
More information about the cfe-commits
mailing list