[llvm-branch-commits] [clang] liveness-based-lifetime-policy (PR #159991)

Utkarsh Saxena via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Sun Sep 21 11:52:10 PDT 2025


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

>From 071c85e4011b0a07bdd39990410dfa2c721d8b42 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sun, 21 Sep 2025 16:30:28 +0000
Subject: [PATCH] liveness-based-lifetime-policy

---
 .../clang/Analysis/Analyses/LifetimeSafety.h  |   9 +-
 clang/lib/Analysis/LifetimeSafety.cpp         | 365 ++++++++++--------
 .../unittests/Analysis/LifetimeSafetyTest.cpp | 135 +++++++
 3 files changed, 346 insertions(+), 163 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
index 512cb76cd6349..2cc3fb3d69eb4 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
@@ -55,6 +55,7 @@ class Fact;
 class FactManager;
 class LoanPropagationAnalysis;
 class ExpiredLoansAnalysis;
+class LiveOriginAnalysis;
 struct LifetimeFactory;
 
 /// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
@@ -89,6 +90,7 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) {
 // TODO(opt): Consider using a bitset to represent the set of loans.
 using LoanSet = llvm::ImmutableSet<LoanID>;
 using OriginSet = llvm::ImmutableSet<OriginID>;
+using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
 
 /// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
 /// `Fact`. identified by a lifetime-related event (`Fact`).
@@ -110,8 +112,9 @@ class LifetimeSafetyAnalysis {
   /// Returns the set of loans an origin holds at a specific program point.
   LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
 
-  /// Returns the set of loans that have expired at a specific program point.
-  std::vector<LoanID> getExpiredLoansAtPoint(ProgramPoint PP) const;
+  /// TODO:Document.
+  std::vector<std::pair<OriginID, Confidence>>
+  getLiveOriginsAtPoint(ProgramPoint PP) const;
 
   /// Finds the OriginID for a given declaration.
   /// Returns a null optional if not found.
@@ -138,7 +141,7 @@ class LifetimeSafetyAnalysis {
   std::unique_ptr<LifetimeFactory> Factory;
   std::unique_ptr<FactManager> FactMgr;
   std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
-  std::unique_ptr<ExpiredLoansAnalysis> ExpiredLoans;
+  std::unique_ptr<LiveOriginAnalysis> LiveOrigins;
 };
 } // namespace internal
 } // namespace clang::lifetimes
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index f63e95189e8f3..34458d1f5dddf 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -21,6 +21,7 @@
 #include "llvm/ADT/SmallBitVector.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/TimeProfiler.h"
 #include <cstdint>
 #include <memory>
@@ -1048,17 +1049,6 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
 //                          Loan Propagation Analysis
 // ========================================================================= //
 
-using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
-using ExpiredLoanMap = llvm::ImmutableMap<LoanID, const ExpireFact *>;
-
-/// An object to hold the factories for immutable collections, ensuring
-/// that all created states share the same underlying memory management.
-struct LifetimeFactory {
-  OriginLoanMap::Factory OriginMapFactory;
-  LoanSet::Factory LoanSetFactory;
-  ExpiredLoanMap::Factory ExpiredLoanMapFactory;
-};
-
 /// Represents the dataflow lattice for loan propagation.
 ///
 /// This lattice tracks which loans each origin may hold at a given program
@@ -1102,10 +1092,10 @@ class LoanPropagationAnalysis
 
 public:
   LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
-                          LifetimeFactory &LFactory)
-      : DataflowAnalysis(C, AC, F),
-        OriginLoanMapFactory(LFactory.OriginMapFactory),
-        LoanSetFactory(LFactory.LoanSetFactory) {}
+                          OriginLoanMap::Factory &OriginLoanMapFactory,
+                          LoanSet::Factory &LoanSetFactory)
+      : DataflowAnalysis(C, AC, F), OriginLoanMapFactory(OriginLoanMapFactory),
+        LoanSetFactory(LoanSetFactory) {}
 
   using Base::transfer;
 
@@ -1169,96 +1159,177 @@ class LoanPropagationAnalysis
 };
 
 // ========================================================================= //
-//                         Expired Loans Analysis
+//                         Live Origins Analysis
 // ========================================================================= //
 
-/// The dataflow lattice for tracking the set of expired loans.
-struct ExpiredLattice {
-  /// Map from an expired `LoanID` to the `ExpireFact` that made it expire.
-  ExpiredLoanMap Expired;
+/// Information about why an origin is live at a program point.
+struct LivenessInfo {
+  // TODO: Doc.
+  const UseFact *CausingUseFact;
+  // TODO: Doc.
+  Confidence ConfidenceLevel;
+
+  LivenessInfo() : CausingUseFact(nullptr), ConfidenceLevel(Confidence::None) {}
+  LivenessInfo(const UseFact *UF, Confidence C)
+      : CausingUseFact(UF), ConfidenceLevel(C) {}
 
-  ExpiredLattice() : Expired(nullptr) {};
-  explicit ExpiredLattice(ExpiredLoanMap M) : Expired(M) {}
+  bool operator==(const LivenessInfo &Other) const {
+    return CausingUseFact == Other.CausingUseFact &&
+           ConfidenceLevel == Other.ConfidenceLevel;
+  }
+  bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); }
 
-  bool operator==(const ExpiredLattice &Other) const {
-    return Expired == Other.Expired;
+  void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
+    IDBuilder.AddPointer(CausingUseFact);
+    IDBuilder.Add(ConfidenceLevel);
   }
-  bool operator!=(const ExpiredLattice &Other) const {
+};
+
+using LivenessMap = llvm::ImmutableMap<OriginID, LivenessInfo>;
+
+/// The dataflow lattice for origin liveness analysis.
+/// It tracks which origins are live, why they're live (which UseFact),
+/// and the confidence level of that liveness.
+struct LivenessLattice {
+  LivenessMap LiveOrigins;
+  LivenessLattice() : LiveOrigins(nullptr) {};
+  explicit LivenessLattice(LivenessMap L) : LiveOrigins(L) {}
+  bool operator==(const LivenessLattice &Other) const {
+    return LiveOrigins == Other.LiveOrigins;
+  }
+  bool operator!=(const LivenessLattice &Other) const {
     return !(*this == Other);
   }
-
   void dump(llvm::raw_ostream &OS) const {
-    OS << "ExpiredLattice State:\n";
-    if (Expired.isEmpty())
+    OS << "LivenessLattice State:\n";
+    if (LiveOrigins.isEmpty())
       OS << "  <empty>\n";
-    for (const auto &[ID, _] : Expired)
-      OS << "  Loan " << ID << " is expired\n";
+    for (const auto &Entry : LiveOrigins) {
+      OriginID OID = Entry.first;
+      const LivenessInfo &Info = Entry.second;
+      OS << "  Origin " << OID << " is ";
+      switch (Info.ConfidenceLevel) {
+      case Confidence::Definite:
+        OS << "definitely";
+        break;
+      case Confidence::Maybe:
+        OS << "maybe";
+        break;
+      case Confidence::None:
+        llvm_unreachable("liveness condidence should not be none.");
+      }
+      OS << " live at this point\n";
+    }
   }
 };
 
-/// The analysis that tracks which loans have expired.
-class ExpiredLoansAnalysis
-    : public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice,
-                              Direction::Forward> {
-
-  ExpiredLoanMap::Factory &Factory;
+/// The analysis that tracks which origins are live, with granular information
+/// about the causing use fact and confidence level. This is a backward
+/// analysis.
+class LiveOriginAnalysis
+    : public DataflowAnalysis<LiveOriginAnalysis, LivenessLattice,
+                              Direction::Backward> {
+  FactManager &FactMgr;
+  LivenessMap::Factory &Factory;
 
 public:
-  ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
-                       LifetimeFactory &Factory)
-      : DataflowAnalysis(C, AC, F), Factory(Factory.ExpiredLoanMapFactory) {}
+  LiveOriginAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
+                     LivenessMap::Factory &SF)
+      : DataflowAnalysis(C, AC, F), FactMgr(F), Factory(SF) {}
+  using DataflowAnalysis<LiveOriginAnalysis, Lattice,
+                         Direction::Backward>::transfer;
 
-  using Base::transfer;
-
-  StringRef getAnalysisName() const { return "ExpiredLoans"; }
+  StringRef getAnalysisName() const { return "LiveOrigins"; }
 
   Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); }
 
-  /// Merges two lattices by taking the union of the two expired loans.
-  Lattice join(Lattice L1, Lattice L2) {
-    return Lattice(
-        utils::join(L1.Expired, L2.Expired, Factory,
-                    // Take the last expiry fact to make this hermetic.
-                    [](const ExpireFact *F1, const ExpireFact *F2) {
-                      return F1->getExpiryLoc() > F2->getExpiryLoc() ? F1 : F2;
-                    }));
-  }
-
-  Lattice transfer(Lattice In, const ExpireFact &F) {
-    return Lattice(Factory.add(In.Expired, F.getLoanID(), &F));
-  }
-
-  // Removes the loan from the set of expired loans.
-  //
-  // When a loan is re-issued (e.g., in a loop), it is no longer considered
-  // expired. A loan can be in the expired set at the point of issue due to
-  // the dataflow state from a previous loop iteration being propagated along
-  // a backedge in the CFG.
-  //
-  // Note: This has a subtle false-negative though where a loan from previous
-  // iteration is not overwritten by a reissue. This needs careful tracking
-  // of loans "across iterations" which can be considered for future
-  // enhancements.
-  //
-  //    void foo(int safe) {
-  //      int* p = &safe;
-  //      int* q = &safe;
-  //      while (condition()) {
-  //        int x = 1;
-  //        p = &x;    // A loan to 'x' is issued to 'p' in every iteration.
-  //        if (condition()) {
-  //          q = p;
-  //        }
-  //        (void)*p; // OK  — 'p' points to 'x' from new iteration.
-  //        (void)*q; // UaF - 'q' still points to 'x' from previous iteration
-  //                  // which is now destroyed.
-  //      }
-  // }
-  Lattice transfer(Lattice In, const IssueFact &F) {
-    return Lattice(Factory.remove(In.Expired, F.getLoanID()));
+  /// Merges two lattices by combining liveness information.
+  /// When the same origin has different confidence levels, we take the lower
+  /// one.
+  Lattice join(Lattice L1, Lattice L2) const {
+    LivenessMap Merged = L1.LiveOrigins;
+    for (const auto &Entry : L2.LiveOrigins) {
+      OriginID OID = Entry.first;
+      const LivenessInfo &Info2 = Entry.second;
+
+      if (auto *Info1 = L1.LiveOrigins.lookup(OID)) {
+        // Both lattices have this origin - merge the confidence
+        Confidence MergedConfidence;
+        if (Info1->ConfidenceLevel == Confidence::Definite &&
+            Info2.ConfidenceLevel == Confidence::Definite) {
+          MergedConfidence = Confidence::Definite;
+        } else if (Info1->ConfidenceLevel != Confidence::None &&
+                   Info2.ConfidenceLevel != Confidence::None) {
+          MergedConfidence = Confidence::Maybe;
+        } else {
+          // One is None, result is Maybe if the other isn't None
+          MergedConfidence = (Info1->ConfidenceLevel != Confidence::None ||
+                              Info2.ConfidenceLevel != Confidence::None)
+                                 ? Confidence::Maybe
+                                 : Confidence::None;
+        }
+
+        // Prefer the use fact from the higher confidence path
+        const UseFact *PreferredUse =
+            (Info1->ConfidenceLevel >= Info2.ConfidenceLevel)
+                ? Info1->CausingUseFact
+                : Info2.CausingUseFact;
+
+        Merged = Factory.add(Merged, OID,
+                             LivenessInfo(PreferredUse, MergedConfidence));
+      } else {
+        // Only L2 has this origin - add it with Maybe confidence
+        // (since it's not definite if it's only on one path)
+        Confidence AdjustedConfidence =
+            (Info2.ConfidenceLevel == Confidence::Definite)
+                ? Confidence::Maybe
+                : Info2.ConfidenceLevel;
+        Merged =
+            Factory.add(Merged, OID,
+                        LivenessInfo(Info2.CausingUseFact, AdjustedConfidence));
+      }
+    }
+
+    // Handle origins that are only in L1
+    for (const auto &Entry : L1.LiveOrigins) {
+      OriginID OID = Entry.first;
+      if (!L2.LiveOrigins.lookup(OID)) {
+        // Only L1 has this origin - add it with Maybe confidence
+        const LivenessInfo &Info1 = Entry.second;
+        Confidence AdjustedConfidence =
+            (Info1.ConfidenceLevel == Confidence::Definite)
+                ? Confidence::Maybe
+                : Info1.ConfidenceLevel;
+        Merged =
+            Factory.add(Merged, OID,
+                        LivenessInfo(Info1.CausingUseFact, AdjustedConfidence));
+      }
+    }
+
+    return Lattice(Merged);
+  }
+
+  /// TODO:Document.
+  Lattice transfer(Lattice In, const UseFact &UF) {
+    OriginID OID = UF.getUsedOrigin(FactMgr.getOriginMgr());
+    // Write kills liveness.
+    if (UF.isWritten())
+      return Lattice(Factory.remove(In.LiveOrigins, OID));
+    // Read makes origin live with definite confidence (dominates this point).
+    LivenessInfo Info(&UF, Confidence::Definite);
+    return Lattice(Factory.add(In.LiveOrigins, OID, Info));
   }
 
-  ExpiredLoanMap getExpiredLoans(ProgramPoint P) { return getState(P).Expired; }
+  /// Issuing a new loan to an origin kills its liveness.
+  Lattice transfer(Lattice In, const IssueFact &IF) {
+    return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID()));
+  }
+
+  Lattice transfer(Lattice In, const KillOriginFact &KF) {
+    return Lattice(Factory.remove(In.LiveOrigins, KF.getOriginID()));
+  }
+
+  LivenessMap getLiveOrigins(ProgramPoint P) { return getState(P).LiveOrigins; }
 };
 
 // ========================================================================= //
@@ -1276,84 +1347,50 @@ class LifetimeChecker {
 private:
   llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap;
   LoanPropagationAnalysis &LoanPropagation;
-  ExpiredLoansAnalysis &ExpiredLoans;
+  LiveOriginAnalysis &LiveOrigins;
   FactManager &FactMgr;
   AnalysisDeclContext &ADC;
   LifetimeSafetyReporter *Reporter;
 
 public:
-  LifetimeChecker(LoanPropagationAnalysis &LPA, ExpiredLoansAnalysis &ELA,
+  LifetimeChecker(LoanPropagationAnalysis &LPA, LiveOriginAnalysis &LOA,
                   FactManager &FM, AnalysisDeclContext &ADC,
                   LifetimeSafetyReporter *Reporter)
-      : LoanPropagation(LPA), ExpiredLoans(ELA), FactMgr(FM), ADC(ADC),
+      : LoanPropagation(LPA), LiveOrigins(LOA), FactMgr(FM), ADC(ADC),
         Reporter(Reporter) {}
 
   void run() {
     llvm::TimeTraceScope TimeProfile("LifetimeChecker");
     for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>())
       for (const Fact *F : FactMgr.getFacts(B))
-        if (const auto *UF = F->getAs<UseFact>())
-          checkUse(UF);
+        if (const auto *EF = F->getAs<ExpireFact>())
+          checkExpiry(EF);
     issuePendingWarnings();
   }
 
-  /// Checks for use-after-free errors for a given use of an Origin.
-  ///
-  /// This method is called for each 'UseFact' identified in the control flow
-  /// graph. It determines if the loans held by the used origin have expired
-  /// at the point of use.
-  void checkUse(const UseFact *UF) {
-    if (UF->isWritten())
-      return;
-    OriginID O = UF->getUsedOrigin(FactMgr.getOriginMgr());
-
-    // Get the set of loans that the origin might hold at this program point.
-    LoanSet HeldLoans = LoanPropagation.getLoans(O, UF);
-
-    // Get the set of all loans that have expired at this program point.
-    ExpiredLoanMap AllExpiredLoans = ExpiredLoans.getExpiredLoans(UF);
-
-    // If the pointer holds no loans or no loans have expired, there's nothing
-    // to check.
-    if (HeldLoans.isEmpty() || AllExpiredLoans.isEmpty())
-      return;
-
-    // Identify loans that which have expired but are held by the pointer. Using
-    // them is a use-after-free.
-    llvm::SmallVector<LoanID> DefaultedLoans;
-    // A definite UaF error occurs if all loans the origin might hold have
-    // expired.
-    bool IsDefiniteError = true;
-    for (LoanID L : HeldLoans) {
-      if (AllExpiredLoans.contains(L))
-        DefaultedLoans.push_back(L);
-      else
-        // If at least one loan is not expired, this use is not a definite UaF.
-        IsDefiniteError = false;
-    }
-    // If there are no defaulted loans, the use is safe.
-    if (DefaultedLoans.empty())
-      return;
-
-    // Determine the confidence level of the error (definite or maybe).
-    Confidence CurrentConfidence =
-        IsDefiniteError ? Confidence::Definite : Confidence::Maybe;
-
-    // For each expired loan, create a pending warning.
-    for (LoanID DefaultedLoan : DefaultedLoans) {
-      // If we already have a warning for this loan with a higher or equal
-      // confidence, skip this one.
-      if (FinalWarningsMap.count(DefaultedLoan) &&
-          CurrentConfidence <= FinalWarningsMap[DefaultedLoan].ConfidenceLevel)
+  void checkExpiry(const ExpireFact *EF) {
+    LoanID ExpiredLoan = EF->getLoanID();
+    LivenessMap Origins = LiveOrigins.getLiveOrigins(EF);
+    Confidence CurConfidence = Confidence::None;
+    const UseFact *BadUse = nullptr;
+    for (auto &[OID, Info] : Origins) {
+      if (Info.ConfidenceLevel != Confidence::Definite)
         continue;
-
-      auto *EF = AllExpiredLoans.lookup(DefaultedLoan);
-      assert(EF && "Could not find ExpireFact for an expired loan.");
-
-      FinalWarningsMap[DefaultedLoan] = {/*ExpiryLoc=*/(*EF)->getExpiryLoc(),
-                                         /*UseExpr=*/UF->getUseExpr(),
-                                         /*ConfidenceLevel=*/CurrentConfidence};
+      LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF);
+      if (!HeldLoans.contains(ExpiredLoan))
+        continue;
+      // Loan is defaulted.
+      if (CurConfidence < Info.ConfidenceLevel) {
+        CurConfidence = Info.ConfidenceLevel;
+        BadUse = Info.CausingUseFact;
+      }
     }
+    if (CurConfidence == Confidence::None)
+      return;
+    // We have a use-after-free.
+    FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(),
+                                     /*UseExpr=*/BadUse->getUseExpr(),
+                                     /*ConfidenceLevel=*/CurConfidence};
   }
 
   void issuePendingWarnings() {
@@ -1372,6 +1409,14 @@ class LifetimeChecker {
 //                  LifetimeSafetyAnalysis Class Implementation
 // ========================================================================= //
 
+/// An object to hold the factories for immutable collections, ensuring
+/// that all created states share the same underlying memory management.
+struct LifetimeFactory {
+  OriginLoanMap::Factory OriginMapFactory;
+  LoanSet::Factory LoanSetFactory;
+  LivenessMap::Factory LivenessMapFactory;
+};
+
 // We need this here for unique_ptr with forward declared class.
 LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
 
@@ -1402,15 +1447,15 @@ void LifetimeSafetyAnalysis::run() {
   ///    the analysis.
   /// 3. Collapse ExpireFacts belonging to same source location into a single
   ///    Fact.
-  LoanPropagation =
-      std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
+  LoanPropagation = std::make_unique<LoanPropagationAnalysis>(
+      Cfg, AC, *FactMgr, Factory->OriginMapFactory, Factory->LoanSetFactory);
   LoanPropagation->run();
 
-  ExpiredLoans =
-      std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory);
-  ExpiredLoans->run();
+  LiveOrigins = std::make_unique<LiveOriginAnalysis>(
+      Cfg, AC, *FactMgr, Factory->LivenessMapFactory);
+  LiveOrigins->run();
 
-  LifetimeChecker Checker(*LoanPropagation, *ExpiredLoans, *FactMgr, AC,
+  LifetimeChecker Checker(*LoanPropagation, *LiveOrigins, *FactMgr, AC,
                           Reporter);
   Checker.run();
 }
@@ -1421,15 +1466,6 @@ LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
   return LoanPropagation->getLoans(OID, PP);
 }
 
-std::vector<LoanID>
-LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const {
-  assert(ExpiredLoans && "ExpiredLoansAnalysis has not been run.");
-  std::vector<LoanID> Result;
-  for (const auto &pair : ExpiredLoans->getExpiredLoans(PP))
-    Result.push_back(pair.first);
-  return Result;
-}
-
 std::optional<OriginID>
 LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const {
   assert(FactMgr && "FactManager not initialized");
@@ -1449,6 +1485,15 @@ LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const {
   return Result;
 }
 
+std::vector<std::pair<OriginID, Confidence>>
+LifetimeSafetyAnalysis::getLiveOriginsAtPoint(ProgramPoint PP) const {
+  assert(LiveOrigins && "LiveOriginAnalysis has not been run.");
+  std::vector<std::pair<OriginID, Confidence>> Result;
+  for (auto &[OID, Info] : LiveOrigins->getLiveOrigins(PP))
+    Result.push_back({OID, Info.ConfidenceLevel});
+  return Result;
+}
+
 llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const {
   assert(FactMgr && "FactManager not initialized");
   llvm::StringMap<ProgramPoint> AnnotationToPointMap;
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 3821015f07fb1..b227ddb288df8 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -134,6 +134,14 @@ class LifetimeTestHelper {
     return Analysis.getExpiredLoansAtPoint(PP);
   }
 
+  std::optional<std::vector<OriginID>>
+  getLiveOriginsAtPoint(llvm::StringRef Annotation) {
+    ProgramPoint PP = Runner.getProgramPoint(Annotation);
+    if (!PP)
+      return std::nullopt;
+    return Analysis.getLiveOriginsAtPoint(PP);
+  }
+
 private:
   template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
     auto &Ctx = Runner.getASTContext();
@@ -180,6 +188,15 @@ class OriginInfo {
   LifetimeTestHelper &Helper;
 };
 
+// A helper class to represent a set of origins, identified by variable names.
+class OriginsInfo {
+public:
+  OriginsInfo(const std::vector<std::string> &Vars, LifetimeTestHelper &H)
+      : OriginVars(Vars), Helper(H) {}
+  std::vector<std::string> OriginVars;
+  LifetimeTestHelper &Helper;
+};
+
 /// Matcher to verify the set of loans held by an origin at a specific
 /// program point.
 ///
@@ -264,6 +281,31 @@ MATCHER_P(AreExpiredAt, Annotation, "") {
                             ActualExpiredLoans, result_listener);
 }
 
+/// Matcher to verify the complete set of live origins at a program point.
+MATCHER_P(AreLiveAt, Annotation, "") {
+  const OriginsInfo &Info = arg;
+  auto &Helper = Info.Helper;
+  auto ActualLiveSetOpt = Helper.getLiveOriginsAtPoint(Annotation);
+  if (!ActualLiveSetOpt) {
+    *result_listener << "could not get a valid live origin set at point '"
+                     << Annotation << "'";
+    return false;
+  }
+  std::vector<OriginID> ActualLiveOrigins = std::move(ActualLiveSetOpt.value());
+  std::vector<OriginID> ExpectedLiveOrigins;
+  for (const auto &VarName : Info.OriginVars) {
+    auto OriginIDOpt = Helper.getOriginForDecl(VarName);
+    if (!OriginIDOpt) {
+      *result_listener << "could not find an origin for variable '" << VarName
+                       << "'";
+      return false;
+    }
+    ExpectedLiveOrigins.push_back(*OriginIDOpt);
+  }
+  return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLiveOrigins),
+                            ActualLiveOrigins, result_listener);
+}
+
 // Base test fixture to manage the runner and helper.
 class LifetimeAnalysisTest : public ::testing::Test {
 protected:
@@ -276,6 +318,13 @@ class LifetimeAnalysisTest : public ::testing::Test {
     return OriginInfo(OriginVar, *Helper);
   }
 
+  /// Factory function that hides the std::vector creation.
+  OriginsInfo Origins(std::initializer_list<std::string> OriginVars) {
+    return OriginsInfo({OriginVars}, *Helper);
+  }
+
+  OriginsInfo NoOrigins() { return Origins({}); }
+
   /// Factory function that hides the std::vector creation.
   LoanSetInfo LoansTo(std::initializer_list<std::string> LoanVars) {
     return LoanSetInfo({LoanVars}, *Helper);
@@ -1174,5 +1223,91 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) {
   )");
   EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1"));
 }
+
+TEST_F(LifetimeAnalysisTest, LivenessDeadPointer) {
+  SetupTest(R"(
+    void target() {
+      POINT(p2);
+      MyObj s;
+      MyObj* p = &s;
+      POINT(p1);
+    }
+  )");
+  EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+  EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessSimpleReturn) {
+  SetupTest(R"(
+    MyObj* target() {
+      MyObj s;
+      MyObj* p = &s;
+      POINT(p1);
+      return p;
+    }
+  )");
+  EXPECT_THAT(Origins({"p"}), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessKilledByReassignment) {
+  SetupTest(R"(
+    MyObj* target() {
+      MyObj s1, s2;
+      MyObj* p = &s1;
+      POINT(p1);
+      p = &s2;
+      POINT(p2);
+      return p;
+    }
+  )");
+  EXPECT_THAT(Origins({"p"}), AreLiveAt("p2"));
+  EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessAcrossBranches) {
+  SetupTest(R"(
+    MyObj* target(bool c) {
+      MyObj x, y;
+      MyObj* p = nullptr;
+      POINT(p1);
+      if (c) {
+        p = &x;
+        POINT(p2);
+      } else {
+        p = &y;
+        POINT(p3);
+      }
+      return p;
+    }
+  )");
+  EXPECT_THAT(Origins({"p"}), AreLiveAt("p2"));
+  EXPECT_THAT(Origins({"p"}), AreLiveAt("p3"));
+  // Before the `if`, the value of `p` (`nullptr`) is always overwritten before
+  EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, LivenessInLoop) {
+  SetupTest(R"(
+    MyObj* target(bool c) {
+      MyObj s1, s2;
+      MyObj* p = &s1;
+      MyObj* q = &s2;
+      POINT(p1);
+      while(c) {
+        POINT(p2);
+        p = q;
+        POINT(p3);
+      }
+      POINT(p4);
+      return p;
+    }
+  )");
+
+  EXPECT_THAT(Origins({"p"}), AreLiveAt("p4"));
+  EXPECT_THAT(Origins({"p", "q"}), AreLiveAt("p3"));
+  EXPECT_THAT(Origins({"q"}), AreLiveAt("p2"));
+  EXPECT_THAT(Origins({"p", "q"}), AreLiveAt("p1"));
+}
+
 } // anonymous namespace
 } // namespace clang::lifetimes::internal



More information about the llvm-branch-commits mailing list