https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/142313

>From 7b929ee264356c66f415f9e039819a89b1dac06b Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <u...@google.com>
Date: Mon, 2 Jun 2025 17:53:14 +0000
Subject: [PATCH] Introduce Intra-procedural lifetime analysis in Clang

---
 .../clang/Analysis/Analyses/LifetimeSafety.h  |  13 +
 clang/lib/Analysis/CMakeLists.txt             |   1 +
 clang/lib/Analysis/LifetimeSafety.cpp         | 723 ++++++++++++++++++
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |   8 +
 .../test/Sema/lifetime-safety-testcase-gen.py |  79 ++
 .../Sema/warn-lifetime-safety-dataflow.cpp    | 362 +++++++++
 6 files changed, 1186 insertions(+)
 create mode 100644 clang/include/clang/Analysis/Analyses/LifetimeSafety.h
 create mode 100644 clang/lib/Analysis/LifetimeSafety.cpp
 create mode 100644 clang/test/Sema/lifetime-safety-testcase-gen.py
 create mode 100644 clang/test/Sema/warn-lifetime-safety-dataflow.cpp

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h 
b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
new file mode 100644
index 0000000000000..5b33d582f7278
--- /dev/null
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
@@ -0,0 +1,13 @@
+#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIME_SAFETY_H
+#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIME_SAFETY_H
+#include "clang/AST/DeclBase.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
+#include "clang/Analysis/CFG.h"
+namespace clang {
+
+void runLifetimeAnalysis(const DeclContext &dc, const CFG &cfg,
+                         AnalysisDeclContext &ac);
+
+} // namespace clang
+
+#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIME_SAFETY_H
diff --git a/clang/lib/Analysis/CMakeLists.txt 
b/clang/lib/Analysis/CMakeLists.txt
index 8cd3990db4c3e..0523d92480cb3 100644
--- a/clang/lib/Analysis/CMakeLists.txt
+++ b/clang/lib/Analysis/CMakeLists.txt
@@ -21,6 +21,7 @@ add_clang_library(clangAnalysis
   FixitUtil.cpp
   IntervalPartition.cpp
   IssueHash.cpp
+  LifetimeSafety.cpp
   LiveVariables.cpp
   MacroExpansionContext.cpp
   ObjCNoReturn.cpp
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp 
b/clang/lib/Analysis/LifetimeSafety.cpp
new file mode 100644
index 0000000000000..12584d82c5f69
--- /dev/null
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -0,0 +1,723 @@
+#include "clang/Analysis/Analyses/LifetimeSafety.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/StmtVisitor.h"
+#include "clang/AST/Type.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
+#include "clang/Analysis/CFG.h"
+#include "clang/Analysis/FlowSensitive/DataflowWorklist.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"
+#include <vector>
+
+namespace clang {
+namespace {
+
+struct Point {
+  const clang::CFGBlock *Block;
+  /// Index into Block->Elements().
+  unsigned ElementIndex;
+
+  Point(const clang::CFGBlock *B = nullptr, unsigned Idx = 0)
+      : Block(B), ElementIndex(Idx) {}
+
+  bool operator==(const Point &Other) const {
+    return Block == Other.Block && ElementIndex == Other.ElementIndex;
+  }
+};
+
+/// Represents the storage location being borrowed, e.g., a specific stack
+/// variable.
+/// TODO: Handle member accesseslike `s.y`.
+struct Path {
+  const clang::ValueDecl *D;
+
+  enum class Kind : uint8_t {
+    StackVariable,
+    Heap,            // TODO: Handle.
+    Field,           // TODO: Handle.
+    ArrayElement,    // TODO: Handle.
+    TemporaryObject, // TODO: Handle.
+    StaticOrGlobal,  // TODO: Handle.
+  };
+
+  Kind PathKind;
+
+  Path(const clang::ValueDecl *D, Kind K) : D(D), PathKind(K) {}
+};
+
+using LoanID = uint32_t;
+using OriginID = uint32_t;
+
+/// Information about a single borrow, or "Loan". A loan is created when a
+/// reference or pointer is taken.
+struct LoanInfo {
+  /// TODO: Represent opaque loans.
+  /// TODO: Represent nullptr: loans to no path. Accessing it UB! Currently it
+  /// is represented as empty LoanSet
+  LoanID ID;
+  Path SourcePath;
+  SourceLocation IssueLoc;
+
+  LoanInfo(LoanID id, Path path, SourceLocation loc)
+      : ID(id), SourcePath(path), IssueLoc(loc) {}
+};
+
+enum class OriginKind : uint8_t { Variable, ExpressionResult };
+
+/// An Origin is a symbolic identifier that represents the set of possible
+/// loans a pointer-like object could hold at any given time.
+/// TODO: Also represent Origins of complex types (fields, dependent internal
+/// types).
+struct OriginInfo {
+  OriginID ID;
+  OriginKind Kind;
+  union {
+    const clang::ValueDecl *Decl;
+    const clang::Expr *Expression;
+  };
+  OriginInfo(OriginID id, OriginKind kind, const clang::ValueDecl *D)
+      : ID(id), Kind(kind), Decl(D) {}
+  OriginInfo(OriginID id, OriginKind kind, const clang::Expr *E)
+      : ID(id), Kind(kind), Expression(E) {}
+};
+
+class LoanManager {
+public:
+  LoanManager() = default;
+
+  LoanInfo &addLoanInfo(Path path, SourceLocation loc) {
+    NextLoanIDVal++;
+    AllLoans.emplace_back(NextLoanIDVal, path, loc);
+    return AllLoans.back();
+  }
+
+  const LoanInfo *getLoanInfo(LoanID id) const {
+    if (id < AllLoans.size())
+      return &AllLoans[id];
+    return nullptr;
+  }
+  llvm::ArrayRef<LoanInfo> getLoanInfos() const { return AllLoans; }
+
+private:
+  LoanID NextLoanIDVal = 0;
+  llvm::SmallVector<LoanInfo> AllLoans;
+};
+
+class OriginManager {
+public:
+  OriginManager() = default;
+
+  OriginID getNextOriginID() { return NextOriginIDVal++; }
+  OriginInfo &addOriginInfo(OriginID id, const clang::ValueDecl *D) {
+    assert(D != nullptr);
+    AllOrigins.emplace_back(id, OriginKind::Variable, D);
+    return AllOrigins.back();
+  }
+  OriginInfo &addOriginInfo(OriginID id, const clang::Expr *E) {
+    assert(E != nullptr);
+    AllOrigins.emplace_back(id, OriginKind::ExpressionResult, E);
+    return AllOrigins.back();
+  }
+
+  OriginID getOrCreate(const Expr *E) {
+    auto It = ExprToOriginID.find(E);
+    if (It != ExprToOriginID.end())
+      return It->second;
+
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+      // Origin of DeclRefExpr is that of the declaration it refers to.
+      return getOrCreate(DRE->getDecl());
+    }
+    OriginID NewID = getNextOriginID();
+    addOriginInfo(NewID, E);
+    ExprToOriginID[E] = NewID;
+    return NewID;
+  }
+
+  const OriginInfo *getOriginInfo(OriginID id) const {
+    if (id < AllOrigins.size())
+      return &AllOrigins[id];
+    return nullptr;
+  }
+
+  llvm::ArrayRef<OriginInfo> getOriginInfos() const { return AllOrigins; }
+
+  OriginID getOrCreate(const ValueDecl *D) {
+    auto It = DeclToOriginID.find(D);
+    if (It != DeclToOriginID.end())
+      return It->second;
+    OriginID NewID = getNextOriginID();
+    addOriginInfo(NewID, D);
+    DeclToOriginID[D] = NewID;
+    return NewID;
+  }
+
+private:
+  OriginID NextOriginIDVal = 0;
+  llvm::SmallVector<OriginInfo> AllOrigins;
+  llvm::DenseMap<const clang::ValueDecl *, OriginID> DeclToOriginID;
+  llvm::DenseMap<const clang::Expr *, OriginID> ExprToOriginID;
+};
+
+/// An abstract base class for a single, atomic lifetime-relevant event.
+class Fact {
+
+public:
+  enum class Kind : uint8_t {
+    /// A new loan is issued from a borrow expression (e.g., &x).
+    Issue,
+    /// A loan expires as its underlying storage is freed (e.g., variable goes
+    /// out of scope).
+    Expire,
+    /// An origin is propagated from a source to a destination (e.g., p = q).
+    AssignOrigin,
+    /// An origin is part of a function's return value.
+    ReturnOfOrigin
+  };
+
+private:
+  Kind K;
+
+protected:
+  Point P;
+  Fact(Kind K, Point Pt) : K(K), P(Pt) {}
+
+public:
+  virtual ~Fact() = default;
+  Kind getKind() const { return K; }
+  Point getPoint() const { return P; }
+
+  template <typename T> const T *getAs() const {
+    if (T::classof(this))
+      return static_cast<const T *>(this);
+    return nullptr;
+  }
+
+  virtual void dump(llvm::raw_ostream &OS) const {
+    OS << "Fact (Kind: " << static_cast<int>(K) << ", Point: B"
+       << P.Block->getBlockID() << ":" << P.ElementIndex << ")\n";
+  }
+};
+
+class IssueFact : public Fact {
+  LoanID LID;
+  OriginID OID;
+
+public:
+  IssueFact(LoanID LID, OriginID OID, Point Pt)
+      : Fact(Kind::Issue, Pt), LID(LID), OID(OID) {}
+  LoanID getLoanID() const { return LID; }
+  OriginID getOriginID() const { return OID; }
+  static bool classof(const Fact *F) { return F->getKind() == Kind::Issue; }
+  void dump(llvm::raw_ostream &OS) const override {
+    OS << "Issue (LoanID: " << getLoanID() << ", OriginID: " << getOriginID()
+       << ")\n";
+  }
+};
+
+class ExpireFact : public Fact {
+  LoanID LID;
+
+public:
+  ExpireFact(LoanID LID, Point Pt) : Fact(Kind::Expire, Pt), LID(LID) {}
+  LoanID getLoanID() const { return LID; }
+  static bool classof(const Fact *F) { return F->getKind() == Kind::Expire; }
+  void dump(llvm::raw_ostream &OS) const override {
+    OS << "Expire (LoanID: " << getLoanID() << ")\n";
+  }
+};
+
+class AssignOriginFact : public Fact {
+  OriginID OIDDest;
+  OriginID OIDSrc;
+
+public:
+  AssignOriginFact(OriginID OIDDest, OriginID OIDSrc, Point Pt)
+      : Fact(Kind::AssignOrigin, Pt), OIDDest(OIDDest), OIDSrc(OIDSrc) {}
+  OriginID getDestOriginID() const { return OIDDest; }
+  OriginID getSrcOriginID() const { return OIDSrc; }
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::AssignOrigin;
+  }
+  void dump(llvm::raw_ostream &OS) const override {
+    OS << "AssignOrigin (DestID: " << getDestOriginID()
+       << ", SrcID: " << getSrcOriginID() << ")\n";
+  }
+};
+
+class ReturnOfOriginFact : public Fact {
+  OriginID OID;
+
+public:
+  ReturnOfOriginFact(OriginID OID, Point Pt)
+      : Fact(Kind::ReturnOfOrigin, Pt), OID(OID) {}
+  OriginID getReturnedOriginID() const { return OID; }
+  static bool classof(const Fact *F) {
+    return F->getKind() == Kind::ReturnOfOrigin;
+  }
+  void dump(llvm::raw_ostream &OS) const override {
+    OS << "ReturnOfOrigin (OriginID: " << getReturnedOriginID() << ")\n";
+  }
+};
+
+class FactManager {
+public:
+  llvm::ArrayRef<Fact *> getFacts(const CFGBlock *B) const {
+    auto It = BlockToFactsMap.find(B);
+    if (It != BlockToFactsMap.end())
+      return It->second;
+    return {};
+  }
+
+  void addFact(const CFGBlock *B, Fact *NewFact) {
+    BlockToFactsMap[B].push_back(NewFact);
+    DEBUG_WITH_TYPE("dev", NewFact->dump(llvm::dbgs()));
+  }
+
+  template <typename FactType, typename... Args>
+  FactType *createFact(Args &&...args) {
+    void *Mem = FactAllocator.Allocate<FactType>();
+    return new (Mem) FactType(std::forward<Args>(args)...);
+  }
+
+  void dump(const CFG &Cfg, AnalysisDeclContext &AC) const {
+    llvm::dbgs() << "==========================================\n";
+    llvm::dbgs() << "       Lifetime Analysis Facts:\n";
+    llvm::dbgs() << "==========================================\n";
+    if (const Decl *D = AC.getDecl()) {
+      if (const auto *ND = dyn_cast<NamedDecl>(D))
+        llvm::dbgs() << "Function: " << ND->getQualifiedNameAsString() << "\n";
+    }
+    // Print blocks in the order as they appear in code for a stable ordering.
+    ForwardDataflowWorklist worklist(Cfg, AC);
+    for (const CFGBlock *B : Cfg.const_nodes())
+      worklist.enqueueBlock(B);
+    while (const CFGBlock *B = worklist.dequeue()) {
+      llvm::dbgs() << "  Block B" << B->getBlockID() << ":\n";
+      auto It = BlockToFactsMap.find(B);
+      if (It != BlockToFactsMap.end()) {
+        for (const Fact *F : It->second) {
+          llvm::dbgs() << "    ";
+          F->dump(llvm::dbgs());
+        }
+      }
+      llvm::dbgs() << "  End of Block\n";
+    }
+  }
+
+private:
+  llvm::DenseMap<const clang::CFGBlock *, llvm::SmallVector<Fact *>>
+      BlockToFactsMap;
+  llvm::BumpPtrAllocator FactAllocator;
+};
+
+struct FactsContext {
+  FactManager Facts;
+  LoanManager Loans;
+  OriginManager Origins;
+};
+
+class FactGenerator : public ConstStmtVisitor<FactGenerator> {
+
+public:
+  FactGenerator(const CFG &Cfg, FactsContext &FactsCtx, AnalysisDeclContext 
&AC)
+      : FactsCtx(FactsCtx), Cfg(Cfg), AC(AC), CurrentBlock(nullptr) {}
+
+  void run() {
+    // Iterate through the CFG blocks in topological order to ensure that
+    // initializations and destructions are processed in the correct sequence.
+    ForwardDataflowWorklist Worklist(Cfg, AC);
+    for (const CFGBlock *B : Cfg.const_nodes())
+      Worklist.enqueueBlock(B);
+    while (const CFGBlock *Block = Worklist.dequeue()) {
+      CurrentBlock = Block;
+      for (unsigned I = 0; I < Block->size(); ++I) {
+        const CFGElement &Element = Block->Elements[I];
+        CurrentPoint = Point(Block, I);
+        if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
+          Visit(CS->getStmt());
+        else if (std::optional<CFGAutomaticObjDtor> DtorOpt =
+                     Element.getAs<CFGAutomaticObjDtor>())
+          handleDestructor(*DtorOpt);
+      }
+    }
+  }
+
+  void VisitDeclStmt(const DeclStmt *DS) {
+    for (const Decl *D : DS->decls()) {
+      if (const auto *VD = dyn_cast<VarDecl>(D)) {
+        if (hasOrigin(VD->getType())) {
+          if (const Expr *InitExpr = VD->getInit()) {
+            OriginID DestOID = FactsCtx.Origins.getOrCreate(VD);
+            OriginID SrcOID = FactsCtx.Origins.getOrCreate(InitExpr);
+            FactsCtx.Facts.addFact(CurrentBlock,
+                                   FactsCtx.Facts.createFact<AssignOriginFact>(
+                                       DestOID, SrcOID, CurrentPoint));
+          }
+        }
+      }
+    }
+  }
+
+  void VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
+    if (!hasOrigin(ICE->getType()))
+      return;
+    // An ImplicitCastExpr node itself gets an origin, which flows from the
+    // origin of its sub-expression (after stripping its own parens/casts).
+    if (ICE->getCastKind() == CK_LValueToRValue) {
+      OriginID IceOID = FactsCtx.Origins.getOrCreate(ICE);
+      OriginID SubExprOID = FactsCtx.Origins.getOrCreate(ICE->getSubExpr());
+      FactsCtx.Facts.addFact(CurrentBlock,
+                             FactsCtx.Facts.createFact<AssignOriginFact>(
+                                 IceOID, SubExprOID, CurrentPoint));
+    }
+  }
+
+  void VisitUnaryOperator(const UnaryOperator *UO) {
+    if (UO->getOpcode() == UO_AddrOf) {
+      const Expr *SubExpr = UO->getSubExpr();
+      if (const auto *DRE = dyn_cast<DeclRefExpr>(SubExpr)) {
+        if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+          // Check if it's a local variable.
+          if (VD->hasLocalStorage()) {
+            OriginID OID = FactsCtx.Origins.getOrCreate(UO);
+            Path AddrOfLocalVarPath(VD, Path::Kind::StackVariable);
+            LoanInfo &Loan = FactsCtx.Loans.addLoanInfo(AddrOfLocalVarPath,
+                                                        UO->getOperatorLoc());
+            FactsCtx.Facts.addFact(CurrentBlock,
+                                   FactsCtx.Facts.createFact<IssueFact>(
+                                       Loan.ID, OID, CurrentPoint));
+          }
+        }
+      }
+    }
+  }
+
+  void VisitReturnStmt(const ReturnStmt *RS) {
+    if (const Expr *RetExpr = RS->getRetValue()) {
+      if (hasOrigin(RetExpr->getType())) {
+        OriginID OID = FactsCtx.Origins.getOrCreate(RetExpr);
+        FactsCtx.Facts.addFact(
+            CurrentBlock,
+            FactsCtx.Facts.createFact<ReturnOfOriginFact>(OID, CurrentPoint));
+      }
+    }
+  }
+
+  void VisitBinaryOperator(const BinaryOperator *BO) {
+    if (BO->isAssignmentOp()) {
+      const Expr *LHSExpr = BO->getLHS();
+      const Expr *RHSExpr = BO->getRHS();
+
+      // We are interested in assignments like `ptr1 = ptr2` or `ptr = &var`
+      // LHS must be a pointer/reference type that can be an origin.
+      // RHS must also represent an origin (either another pointer/ref or an
+      // address-of).
+      if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
+        if (const auto *VD_LHS =
+                dyn_cast<ValueDecl>(DRE_LHS->getDecl()->getCanonicalDecl());
+            VD_LHS && hasOrigin(VD_LHS->getType())) {
+          OriginID DestOID = FactsCtx.Origins.getOrCreate(VD_LHS);
+          OriginID SrcOID = FactsCtx.Origins.getOrCreate(RHSExpr);
+          FactsCtx.Facts.addFact(CurrentBlock,
+                                 FactsCtx.Facts.createFact<AssignOriginFact>(
+                                     DestOID, SrcOID, CurrentPoint));
+        }
+      }
+    }
+  }
+
+private:
+  // Check if a type have an origin.
+  bool hasOrigin(QualType QT) { return QT->isPointerOrReferenceType(); }
+
+  void handleDestructor(const CFGAutomaticObjDtor &DtorOpt) {
+    /// TODO: Also handle trivial destructors (e.g., for `int`
+    // variables) which will never have a CFGAutomaticObjDtor node.
+    /// TODO: Handle loans to temporaries.
+    const VarDecl *DestructedVD = DtorOpt.getVarDecl();
+    if (!DestructedVD)
+      return;
+    // Iterate through all loans to see if any expire.
+    for (const LoanInfo &Loan : FactsCtx.Loans.getLoanInfos()) {
+      const Path &LoanPath = Loan.SourcePath;
+      // Check if the loan is for a stack variable and if that variable
+      // is the one being destructed.
+      if (LoanPath.PathKind == Path::Kind::StackVariable) {
+        if (LoanPath.D == DestructedVD) {
+          FactsCtx.Facts.addFact(
+              CurrentBlock,
+              FactsCtx.Facts.createFact<ExpireFact>(Loan.ID, CurrentPoint));
+        }
+      }
+    }
+  }
+
+  FactsContext &FactsCtx;
+  const CFG &Cfg;
+  AnalysisDeclContext &AC;
+  const CFGBlock *CurrentBlock;
+  Point CurrentPoint;
+};
+
+// ========================================================================= //
+//                              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.
+// TODO: Consider making this private to Lattice.
+using LoanSet = llvm::ImmutableSet<LoanID>;
+using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
+
+/// A context object to hold the factories for immutable collections, ensuring
+/// that all created states share the same underlying memory management.
+struct LifetimeFactory {
+  OriginLoanMap::Factory MapFact;
+  LoanSet::Factory SetFact;
+
+  LoanSet createLoanSet(LoanID LID) {
+    return SetFact.add(SetFact.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.SetFact.getEmptySet();
+  }
+
+  /// TODO: UpperCammelCase function names ?
+  LifetimeLattice merge(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.merge(*this, Factory);
+
+    OriginLoanMap MergedState = 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;
+      MergedState = Factory.MapFact.add(
+          MergedState, OID,
+          merge(getLoans(OID, Factory), OtherLoanSet, Factory));
+    }
+    return LifetimeLattice(MergedState);
+  }
+
+  LoanSet merge(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): This loop is a major performance bottleneck. Investigate
+      /// using a bitset to represent the set of loans for improved merge
+      /// performance.
+      Result = Factory.SetFact.add(Result, LID);
+    }
+    return Result;
+  }
+
+  void dump(llvm::raw_ostream &OS) const {
+    OS << "LifetimeLattice State:\n";
+    if (Origins.isEmpty()) {
+      OS << "  <empty>\n";
+      return;
+    }
+    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.
+  LifetimeLattice transferBlock(const CFGBlock *Block,
+                                LifetimeLattice EntryState) {
+    LifetimeLattice BlockState = EntryState;
+    llvm::ArrayRef<Fact *> Facts = AllFacts.getFacts(Block);
+
+    for (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 transferIssue(In, cast<IssueFact>(F));
+    case Fact::Kind::AssignOrigin:
+      return transferAssignOrigin(In, cast<AssignOriginFact>(F));
+    // Expire and ReturnOfOrigin facts don't modify the Origins.
+    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 transferIssue(LifetimeLattice In, const IssueFact *F) {
+    OriginID OID = F->getOriginID();
+    LoanID LID = F->getLoanID();
+    DEBUG_WITH_TYPE("transfer", llvm::dbgs() << "Issue Fact: Origin " << OID
+                                             << " gets Loan " << LID << "\n");
+    return LifetimeLattice(
+        Factory.MapFact.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 transferAssignOrigin(LifetimeLattice InState,
+                                       const AssignOriginFact *F) {
+    OriginID DestOID = F->getDestOriginID();
+    OriginID SrcOID = F->getSrcOriginID();
+    LoanSet SrcLoans = InState.getLoans(SrcOID, Factory);
+    return LifetimeLattice(
+        Factory.MapFact.add(InState.Origins, DestOID, SrcLoans));
+  }
+};
+// ========================================================================= //
+//                              Dataflow analysis
+// ========================================================================= //
+
+/// TODO: Document.
+/// TODO: Maybe use the dataflow framework!
+class LifetimeDataflow {
+  const CFG &Cfg;
+  AnalysisDeclContext &AC;
+  LifetimeFactory LAC;
+
+  Transferer Xfer;
+
+  /// TODO: doc.
+  llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockEntryStates;
+  llvm::DenseMap<const CFGBlock *, LifetimeLattice> BlockExitStates;
+
+public:
+  LifetimeDataflow(const CFG &C, FactManager &FS, AnalysisDeclContext &AC)
+      : Cfg(C), AC(AC), Xfer(FS, LAC) {}
+
+  void run() {
+    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.merge(ExitState, LAC);
+        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
+
+void runLifetimeAnalysis(const DeclContext &DC, const CFG &Cfg,
+                         AnalysisDeclContext &AC) {
+  FactsContext FactsCtx;
+  FactGenerator FactGen(Cfg, FactsCtx, AC);
+  FactGen.run();
+  DEBUG_WITH_TYPE("LifetimeFacts", FactsCtx.Facts.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, FactsCtx.Facts, AC);
+  Dataflow.run();
+  DEBUG_WITH_TYPE("LifetimeDataflow", Dataflow.dump());
+}
+} // namespace clang
\ No newline at end of file
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp 
b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index d95844cfed614..dd4bede775a40 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -29,6 +29,7 @@
 #include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
 #include "clang/Analysis/Analyses/CalledOnceCheck.h"
 #include "clang/Analysis/Analyses/Consumed.h"
+#include "clang/Analysis/Analyses/LifetimeSafety.h"
 #include "clang/Analysis/Analyses/ReachableCode.h"
 #include "clang/Analysis/Analyses/ThreadSafety.h"
 #include "clang/Analysis/Analyses/UninitializedValues.h"
@@ -49,6 +50,7 @@
 #include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Debug.h"
 #include <algorithm>
 #include <deque>
 #include <iterator>
@@ -2842,6 +2844,12 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     }
   }
 
+  DEBUG_WITH_TYPE(
+      "ExperimentalLifetimeAnalysis", if (S.getLangOpts().CPlusPlus) {
+        if (CFG *cfg = AC.getCFG()) {
+          runLifetimeAnalysis(*cast<DeclContext>(D), *cfg, AC);
+        }
+      });
   // Check for violations of "called once" parameter properties.
   if (S.getLangOpts().ObjC && !S.getLangOpts().CPlusPlus &&
       shouldAnalyzeCalledOnceParameters(Diags, D->getBeginLoc())) {
diff --git a/clang/test/Sema/lifetime-safety-testcase-gen.py 
b/clang/test/Sema/lifetime-safety-testcase-gen.py
new file mode 100644
index 0000000000000..67d19decf3005
--- /dev/null
+++ b/clang/test/Sema/lifetime-safety-testcase-gen.py
@@ -0,0 +1,79 @@
+import sys
+import argparse
+
+
+def generate_cpp_test_data(n: int) -> str:
+    """
+    Generates a C++ code snippet with a specified number of pointers in a 
cycle.
+
+    Args:
+        n: The number of MyObj variables and pointers to create.
+
+    Returns:
+        A string containing the generated C++ code.
+    """
+    if n <= 0:
+        return "// Number of variables must be positive."
+
+    # Struct definition
+    cpp_code = """struct MyObj {
+  int id;
+  ~MyObj() {}
+};
+
+"""
+
+    # Function signature
+    cpp_code += "void long_cycle(bool condition) {\n"
+
+    # Object and pointer declarations
+    for i in range(1, n + 1):
+        cpp_code += f"  MyObj v{i}{{1}};\n"
+    cpp_code += "\n"
+    for i in range(1, n + 1):
+        cpp_code += f"  MyObj* p{i} = &v{i};\n"
+
+    # While loop for the cycle
+    cpp_code += "\n  while (condition) {\n"
+    if n > 0:
+        cpp_code += f"    MyObj* temp = p1;\n"
+        for i in range(1, n):
+            cpp_code += f"    p{i} = p{i+1};\n"
+        cpp_code += f"    p{n} = temp;\n"
+    cpp_code += "  }\n"
+
+    cpp_code += "}\n"
+
+    return cpp_code
+
+
+if __name__ == "__main__":
+    # Set up the argument parser
+    parser = argparse.ArgumentParser(
+        description="Generate C++ test data with a variable number of pointers 
in a cycle."
+    )
+
+    # Add the '-n' argument
+    parser.add_argument(
+        "-n",
+        "--number",
+        type=int,
+        required=True,
+        help="The number of pointers to generate in the cycle.",
+    )
+
+    # Parse the arguments from the command line
+    args = parser.parse_args()
+
+    number_of_pointers = args.number
+
+    # Validate the input
+    if number_of_pointers <= 0:
+        print(
+            "Error: The number of pointers must be a positive integer.", 
file=sys.stderr
+        )
+        sys.exit(1)
+
+    # Generate and print the code
+    generated_code = generate_cpp_test_data(number_of_pointers)
+    print(generated_code)
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp 
b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
new file mode 100644
index 0000000000000..466bb7d9c3cd9
--- /dev/null
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -0,0 +1,362 @@
+// RUN: %clang_cc1 -mllvm 
-debug-only=ExperimentalLifetimeAnalysis,LifetimeFacts,LifetimeDataflow 
-Wreturn-stack-address-cfg %s 2>&1 | FileCheck %s
+
+struct MyObj {
+  int id;
+  ~MyObj() {} // Non-trivial destructor
+};
+
+// Simple Local Variable Address and Return
+// CHECK-LABEL: Function: return_local_addr
+MyObj* return_local_addr() {
+  MyObj x {10};
+  MyObj* p = &x;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   Issue (LoanID: [[L_X:[0-9]+]], OriginID: [[O_ADDR_X:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_ADDR_X]])
+  return p;
+// CHECK:   AssignOrigin (DestID: [[O_RET_VAL:[0-9]+]], SrcID: [[O_P]])
+// 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
+// CHECK-LABEL: Function: assign_and_return_local_addr
+// CHECK-NEXT: Block B{{[0-9]+}}:
+MyObj* assign_and_return_local_addr() {
+  MyObj y{20};
+  MyObj* ptr1 = &y;
+// CHECK: Issue (LoanID: [[L_Y:[0-9]+]], OriginID: [[O_ADDR_Y:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_PTR1:[0-9]+]], SrcID: [[O_ADDR_Y]])
+  MyObj* ptr2 = ptr1;
+// CHECK: AssignOrigin (DestID: [[O_PTR1_RVAL:[0-9]+]], SrcID: [[O_PTR1]])
+// CHECK: AssignOrigin (DestID: [[O_PTR2:[0-9]+]], SrcID: [[O_PTR1_RVAL]])
+  ptr2 = ptr1;
+// CHECK: AssignOrigin (DestID: [[O_PTR1_RVAL_2:[0-9]+]], SrcID: [[O_PTR1]])
+// CHECK: AssignOrigin (DestID: [[O_PTR2]], SrcID: [[O_PTR1_RVAL_2]])
+  ptr2 = ptr2; // Self assignment.
+// CHECK: AssignOrigin (DestID: [[O_PTR2_RVAL:[0-9]+]], SrcID: [[O_PTR2]])
+// CHECK: AssignOrigin (DestID: [[O_PTR2]], SrcID: [[O_PTR2_RVAL]])
+  return ptr2;
+// CHECK: AssignOrigin (DestID: [[O_PTR2_RVAL_2:[0-9]+]], SrcID: [[O_PTR2]])
+// 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
+// CHECK-LABEL: Function: return_int_val
+// CHECK-NEXT: Block B{{[0-9]+}}:
+int return_int_val() {
+  int x = 10;
+  return x;
+}
+// CHECK-NEXT: End of Block
+// CHECK: Dataflow results:
+
+// CHECK:  <empty>
+
+
+// Loan Expiration (Automatic Variable, C++)
+// CHECK-LABEL: Function: loan_expires_cpp
+// CHECK-NEXT: Block B{{[0-9]+}}:
+void loan_expires_cpp() {
+  MyObj obj{1};
+  MyObj* pObj = &obj;
+// CHECK: Issue (LoanID: [[L_OBJ:[0-9]+]], OriginID: [[O_ADDR_OBJ:[0-9]+]])
+// 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
+// CHECK-LABEL: Function: loan_expires_trivial
+// CHECK-NEXT: Block B{{[0-9]+}}:
+void loan_expires_trivial() {
+  int trivial_obj = 1;
+  int* pTrivialObj = &trivial_obj;
+// CHECK: Issue (LoanID: [[L_TRIVIAL_OBJ:[0-9]+]], OriginID: 
[[O_ADDR_TRIVIAL_OBJ:[0-9]+]])
+// CHECK: AssignOrigin (DestID: [[O_PTOBJ:[0-9]+]], SrcID: 
[[O_ADDR_TRIVIAL_OBJ]])
+// CHECK-NOT: Expire (LoanID: [[L_TRIVIAL_OBJ]])
+// 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
+void conditional(bool condition) {
+  int a = 5;
+  int b = 10;
+  int* p = nullptr;
+
+  if (condition)
+    p = &a;
+  // CHECK: Issue (LoanID: [[L_A:[0-9]+]], OriginID: [[O_ADDR_A:[0-9]+]])
+  // CHECK: AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_ADDR_A]])
+  else
+    p = &b;
+  // CHECK: Issue (LoanID: [[L_B:[0-9]+]], OriginID: [[O_ADDR_B:[0-9]+]])
+  // CHECK: AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_B]])
+}
+// CHECK: Dataflow results:
+
+// CHECK-DAG: Origin [[O_ADDR_A]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_ADDR_B]] contains Loan [[L_B]]
+// CHECK-DAG: Origin [[O_P]] 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
+void overwrite_origin() {
+  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]])
+  p = &s2;
+// CHECK:   Issue (LoanID: [[L_S2:[0-9]+]], OriginID: [[O_ADDR_S2:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_S2]])
+// 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
+void reassign_to_null() {
+  MyObj s1;
+  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]])
+  p = nullptr;
+// CHECK:   AssignOrigin (DestID: [[O_P]], SrcID: [[O_NULLPTR:[0-9]+]])
+// CHECK:   Expire (LoanID: [[L_S1]])
+}
+// 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
+void reassign_in_if(bool condition) {
+  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]])
+  if (condition) {
+    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]])
+  }
+// CHECK: Block B{{[0-9]+}}:
+// 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_P:[0-9]+]], SrcID: [[O_NULLPTR:[0-9]+]])
+  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_P:[0-9]+]], SrcID: 
[[O_NULLPTR:[0-9]+]])
+  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
+void nested_scopes() {
+  MyObj* p = nullptr;
+// CHECK: Block B{{[0-9]+}}:
+// CHECK:   AssignOrigin (DestID: [[O_P:[0-9]+]], SrcID: [[O_NULLPTR:[0-9]+]])
+  {
+    MyObj outer;
+    p = &outer;
+// CHECK:   Issue (LoanID: [[L_OUTER:[0-9]+]], OriginID: 
[[O_ADDR_OUTER:[0-9]+]])
+// CHECK:   AssignOrigin (DestID: [[O_P]], SrcID: [[O_ADDR_OUTER]])
+    {
+      MyObj inner;
+      p = &inner;
+// 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:   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]]

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to