[llvm-branch-commits] [clang] [LifetimeSafety] Implement dataflow analysis for loan propagation (PR #147295)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jul 7 07:58:28 PDT 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/147295
>From e870b040c4ef29b7ca2e50c1fc0ab5a2446f5cf6 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 6 Jul 2025 19:12:55 +0000
Subject: [PATCH] [LifetimeSafety] Propagate loans using dataflow analysis
---
clang/lib/Analysis/LifetimeSafety.cpp | 258 +++++++++++++++++-
.../Sema/warn-lifetime-safety-dataflow.cpp | 186 +++++++++++++
2 files changed, 443 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index 2c2309de90e26..e881e592ef59f 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -13,7 +13,10 @@
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Analysis/CFG.h"
+#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/ImmutableMap.h"
+#include "llvm/ADT/ImmutableSet.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Debug.h"
@@ -482,7 +485,247 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
};
// ========================================================================= //
-// TODO: Run dataflow analysis to propagate loans, analyse and error reporting.
+// The Dataflow Lattice
+// ========================================================================= //
+
+// Using LLVM's immutable collections is efficient for dataflow analysis
+// as it avoids deep copies during state transitions.
+// TODO(opt): Consider using a bitset to represent the set of loans.
+using LoanSet = llvm::ImmutableSet<LoanID>;
+using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
+
+/// An object to hold the factories for immutable collections, ensuring
+/// that all created states share the same underlying memory management.
+struct LifetimeFactory {
+ OriginLoanMap::Factory OriginMapFact;
+ LoanSet::Factory LoanSetFact;
+
+ LoanSet createLoanSet(LoanID LID) {
+ return LoanSetFact.add(LoanSetFact.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 {
+ /// The map from an origin to the set of loans it contains.
+ /// 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;
+
+ bool operator==(const LifetimeLattice &Other) const {
+ return Origins == Other.Origins;
+ }
+ bool operator!=(const LifetimeLattice &Other) const {
+ return !(*this == Other);
+ }
+
+ LoanSet getLoans(OriginID OID, LifetimeFactory &Factory) const {
+ if (auto *Loans = Origins.lookup(OID))
+ return *Loans;
+ return Factory.LoanSetFact.getEmptySet();
+ }
+
+ /// 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.
+ 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.OriginMapFact.add(
+ JoinedState, OID,
+ join(getLoans(OID, Factory), 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())
+ OS << " <empty>\n";
+ for (const auto &Entry : Origins) {
+ if (Entry.second.isEmpty())
+ OS << " Origin " << Entry.first << " contains no loans\n";
+ for (const LoanID &LID : Entry.second)
+ OS << " Origin " << Entry.first << " contains Loan " << LID << "\n";
+ }
+ }
+};
+
+// ========================================================================= //
+// The Transfer Function
+// ========================================================================= //
+class Transferer {
+ FactManager &AllFacts;
+ 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);
+ }
+ return BlockState;
+ }
+
+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");
+ }
+
+ /// A new loan is issued to the origin. Old loans are erased.
+ LifetimeLattice transfer(LifetimeLattice In, const IssueFact &F) {
+ OriginID OID = F.getOriginID();
+ LoanID LID = F.getLoanID();
+ return LifetimeLattice(
+ Factory.OriginMapFact.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) {
+ OriginID DestOID = F.getDestOriginID();
+ OriginID SrcOID = F.getSrcOriginID();
+ LoanSet SrcLoans = InState.getLoans(SrcOID, Factory);
+ return LifetimeLattice(
+ Factory.OriginMapFact.add(InState.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 {
+ auto It = BlockEntryStates.find(B);
+ if (It != BlockEntryStates.end()) {
+ return It->second;
+ }
+ return LifetimeLattice{};
+ }
+
+ LifetimeLattice getExitState(const CFGBlock *B) const {
+ auto It = BlockExitStates.find(B);
+ if (It != BlockExitStates.end()) {
+ return It->second;
+ }
+ return LifetimeLattice{};
+ }
+};
+
+// ========================================================================= //
+// TODO: Analysing dataflow results and error reporting.
// ========================================================================= //
} // anonymous namespace
@@ -495,5 +738,18 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
FactGenerator FactGen(FactMgr, AC);
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());
}
} // namespace clang
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 4ca094a4393b0..8f4547e77681a 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -18,6 +18,10 @@ MyObj* return_local_addr() {
// CHECK: ReturnOfOrigin (OriginID: [[O_RET_VAL]])
// CHECK: Expire (LoanID: [[L_X]])
}
+// CHECK: Dataflow 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]]
// Pointer Assignment and Return
@@ -42,6 +46,14 @@ MyObj* assign_and_return_local_addr() {
// CHECK: ReturnOfOrigin (OriginID: [[O_PTR2_RVAL_2]])
// CHECK: Expire (LoanID: [[L_Y]])
}
+// CHECK: Dataflow 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]]
+// CHECK-DAG: Origin [[O_PTR1_RVAL]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR1_RVAL_2]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2_RVAL]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2_RVAL_2]] contains Loan [[L_Y]]
// Return of Non-Pointer Type
@@ -52,6 +64,8 @@ int return_int_val() {
return x;
}
// CHECK-NEXT: End of Block
+// CHECK: Dataflow results:
+// CHECK: <empty>
// Loan Expiration (Automatic Variable, C++)
@@ -64,6 +78,9 @@ void loan_expires_cpp() {
// CHECK: AssignOrigin (DestID: [[O_POBJ:[0-9]+]], SrcID: [[O_ADDR_OBJ]])
// CHECK: Expire (LoanID: [[L_OBJ]])
}
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_ADDR_OBJ]] contains Loan [[L_OBJ]]
+// CHECK-DAG: Origin [[O_POBJ]] contains Loan [[L_OBJ]]
// FIXME: No expire for Trivial Destructors
@@ -78,6 +95,9 @@ 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-DAG: Origin [[O_ADDR_TRIVIAL_OBJ]] contains Loan [[L_TRIVIAL_OBJ]]
+// CHECK-DAG: Origin [[O_PTOBJ]] contains Loan [[L_TRIVIAL_OBJ]]
// CHECK-LABEL: Function: conditional
@@ -98,6 +118,66 @@ 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-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]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_B]]
+// CHECK-DAG: Origin [[O_Q]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_Q]] contains Loan [[L_B]]
+
+
+// CHECK-LABEL: Function: pointers_in_a_cycle
+void pointers_in_a_cycle(bool condition) {
+ MyObj v1{1};
+ MyObj v2{1};
+ MyObj v3{1};
+
+ MyObj* p1 = &v1;
+ MyObj* p2 = &v2;
+ MyObj* p3 = &v3;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue (LoanID: [[L_V1:[0-9]+]], OriginID: [[O_ADDR_V1:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P1:[0-9]+]], SrcID: [[O_ADDR_V1]])
+// CHECK: Issue (LoanID: [[L_V2:[0-9]+]], OriginID: [[O_ADDR_V2:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P2:[0-9]+]], SrcID: [[O_ADDR_V2]])
+// CHECK: Issue (LoanID: [[L_V3:[0-9]+]], OriginID: [[O_ADDR_V3:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P3:[0-9]+]], SrcID: [[O_ADDR_V3]])
+
+ while (condition) {
+ MyObj* temp = p1;
+ p1 = p2;
+ p2 = p3;
+ p3 = temp;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: AssignOrigin (DestID: [[O_P1_RVAL:[0-9]+]], SrcID: [[O_P1]])
+// CHECK: AssignOrigin (DestID: [[O_TEMP:[0-9]+]], SrcID: [[O_P1_RVAL]])
+// CHECK: AssignOrigin (DestID: [[O_P2_RVAL:[0-9]+]], SrcID: [[O_P2]])
+// CHECK: AssignOrigin (DestID: [[O_P1]], SrcID: [[O_P2_RVAL]])
+// CHECK: AssignOrigin (DestID: [[O_P3_RVAL:[0-9]+]], SrcID: [[O_P3]])
+// CHECK: AssignOrigin (DestID: [[O_P2]], SrcID: [[O_P3_RVAL]])
+// CHECK: AssignOrigin (DestID: [[O_TEMP_RVAL:[0-9]+]], SrcID: [[O_TEMP]])
+// CHECK: AssignOrigin (DestID: [[O_P3]], SrcID: [[O_TEMP_RVAL]])
+ }
+}
+// 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-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]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_ADDR_V1]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_ADDR_V2]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_ADDR_V3]] contains Loan [[L_V3]]
// CHECK-LABEL: Function: overwrite_origin
@@ -114,6 +194,9 @@ void overwrite_origin() {
// CHECK: Expire (LoanID: [[L_S2]])
// CHECK: Expire (LoanID: [[L_S1]])
}
+// CHECK: Dataflow results:
+// CHECK: Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-NOT: Origin [[O_P]] contains Loan [[L_S1]]
// CHECK-LABEL: Function: reassign_to_null
@@ -129,6 +212,8 @@ 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: Origin [[O_P]] contains no loans
// CHECK-LABEL: Function: reassign_in_if
@@ -149,6 +234,102 @@ void reassign_in_if(bool condition) {
// CHECK: Expire (LoanID: [[L_S2]])
// CHECK: Expire (LoanID: [[L_S1]])
}
+// CHECK: Dataflow 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]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
+
+
+// CHECK-LABEL: Function: assign_in_switch
+void assign_in_switch(int mode) {
+ MyObj s1;
+ MyObj s2;
+ MyObj s3;
+ MyObj* p = nullptr;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: AssignOrigin (DestID: [[O_NULLPTR_CAST:[0-9]+]], SrcID: [[O_NULLPTR:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_NULLPTR_CAST]])
+ switch (mode) {
+ case 1:
+ p = &s1;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue (LoanID: [[L_S1:[0-9]+]], OriginID: [[O_ADDR_S1:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S1]])
+ break;
+ case 2:
+ p = &s2;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue (LoanID: [[L_S2:[0-9]+]], OriginID: [[O_ADDR_S2:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S2]])
+ break;
+ default:
+ p = &s3;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue (LoanID: [[L_S3:[0-9]+]], OriginID: [[O_ADDR_S3:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S3]])
+ break;
+ }
+// CHECK: Block B{{[0-9]+}}:
+// CHECK-DAG: Expire (LoanID: [[L_S3]])
+// CHECK-DAG: Expire (LoanID: [[L_S2]])
+// CHECK-DAG: Expire (LoanID: [[L_S1]])
+}
+// CHECK: Dataflow 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]]
+// CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_ADDR_S3]] contains Loan [[L_S3]]
+
+
+// CHECK-LABEL: Function: loan_in_loop
+void loan_in_loop(bool condition) {
+ MyObj* p = nullptr;
+ // CHECK: AssignOrigin (DestID: [[O_NULLPTR_CAST:[0-9]+]], SrcID: [[O_NULLPTR:[0-9]+]])
+ // CHECK: AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_NULLPTR_CAST]])
+ while (condition) {
+ MyObj inner;
+ p = &inner;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue (LoanID: [[L_INNER:[0-9]+]], OriginID: [[O_ADDR_INNER:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_INNER]])
+// CHECK: Expire (LoanID: [[L_INNER]])
+ }
+}
+// CHECK: Dataflow results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_INNER]]
+// CHECK-DAG: Origin [[O_ADDR_INNER]] contains Loan [[L_INNER]]
+
+
+// CHECK-LABEL: Function: loop_with_break
+void loop_with_break(int count) {
+ MyObj s1;
+ MyObj s2;
+ MyObj* p = &s1;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue (LoanID: [[L_S1:[0-9]+]], OriginID: [[O_ADDR_S1:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_ADDR_S1]])
+ for (int i = 0; i < count; ++i) {
+ if (i == 5) {
+ p = &s2;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Issue (LoanID: [[L_S2:[0-9]+]], OriginID: [[O_ADDR_S2:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S2]])
+ break;
+ }
+ }
+// CHECK: Block B{{[0-9]+}}:
+// CHECK: Expire (LoanID: [[L_S2]])
+// CHECK: Expire (LoanID: [[L_S1]])
+}
+
+// CHECK-LABEL: Dataflow 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]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
// CHECK-LABEL: Function: nested_scopes
@@ -173,6 +354,11 @@ void nested_scopes() {
// CHECK: Expire (LoanID: [[L_OUTER]])
}
+// CHECK-LABEL: Dataflow 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]]
+
// CHECK-LABEL: Function: pointer_indirection
void pointer_indirection() {
More information about the llvm-branch-commits
mailing list