[llvm-branch-commits] [clang] [LifetimeSafety] Implement LiveOrigins analysis (PR #148976)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Jul 19 03:01:10 PDT 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/148976
>From 5eadf7b9b70e2a1f839939806de9794313b6f8f0 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 15 Jul 2025 22:19:48 +0000
Subject: [PATCH] add-liveness-finally
---
.../clang/Analysis/Analyses/LifetimeSafety.h | 5 +
clang/lib/Analysis/LifetimeSafety.cpp | 83 +++++++++++
.../unittests/Analysis/LifetimeSafetyTest.cpp | 134 ++++++++++++++++++
3 files changed, 222 insertions(+)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
index 1c00558d32f63..1b7cb82fbe891 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
@@ -34,6 +34,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.
@@ -97,6 +98,9 @@ class LifetimeSafetyAnalysis {
/// their Path.
std::vector<LoanID> getLoanIDForVar(const VarDecl *VD) const;
+ /// Returns the set of live origins at a specific program point.
+ OriginSet getLiveOriginsAtPoint(ProgramPoint PP) const;
+
/// Retrieves program points that were specially marked in the source code
/// for testing.
///
@@ -114,6 +118,7 @@ class LifetimeSafetyAnalysis {
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 815a36e13412c..a717681c4ea44 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -727,12 +727,14 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
// ========================================================================= //
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
+using OriginSet = llvm::ImmutableSet<OriginID>;
/// 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;
+ OriginSet::Factory OriginSetFactory;
/// Creates a singleton set containing only the given loan ID.
LoanSet createLoanSet(LoanID LID) {
@@ -833,6 +835,78 @@ class LoanPropagationAnalysis
}
};
+// ========================================================================= //
+// Live Origins Analysis
+// ========================================================================= //
+
+/// The dataflow lattice for origin liveness analysis.
+/// It tracks the set of origins that are live at a given program point.
+struct LivenessLattice {
+ OriginSet LiveOrigins;
+
+ LivenessLattice() : LiveOrigins(nullptr) {};
+ explicit LivenessLattice(OriginSet S) : LiveOrigins(S) {}
+
+ 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 << "LivenessLattice State:\n";
+ if (LiveOrigins.isEmpty())
+ OS << " <empty>\n";
+ for (const OriginID &OID : LiveOrigins)
+ OS << " Origin " << OID << " is live\n";
+ }
+};
+
+/// The analysis that tracks which origins are live. This is a backward
+/// analysis.
+class LiveOriginAnalysis
+ : public DataflowAnalysis<LiveOriginAnalysis, LivenessLattice,
+ Direction::Backward> {
+
+ OriginSet::Factory &SetFactory;
+
+public:
+ LiveOriginAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
+ OriginSet::Factory &SF)
+ : DataflowAnalysis(C, AC, F), SetFactory(SF) {}
+
+ using DataflowAnalysis<LiveOriginAnalysis, Lattice,
+ Direction::Backward>::transfer;
+
+ StringRef getAnalysisName() const { return "LiveOrigins"; }
+
+ Lattice getInitialState() { return Lattice(SetFactory.getEmptySet()); }
+
+ /// Merges two lattices by taking the union of the live origin sets.
+ Lattice join(Lattice L1, Lattice L2) const {
+ return Lattice(utils::join(L1.LiveOrigins, L2.LiveOrigins, SetFactory));
+ }
+
+ /// An assignment `p = q` kills the liveness of `p` and generates liveness
+ /// for `q`.
+ Lattice transfer(Lattice In, const AssignOriginFact &F) {
+ OriginSet S = SetFactory.remove(In.LiveOrigins, F.getDestOriginID());
+ S = SetFactory.add(S, F.getSrcOriginID());
+ return Lattice(S);
+ }
+
+ /// Issuing a new loan to an origin kills its liveness.
+ Lattice transfer(Lattice In, const IssueFact &F) {
+ return Lattice(SetFactory.remove(In.LiveOrigins, F.getOriginID()));
+ }
+
+ /// A return statement generates liveness for the returned origin.
+ Lattice transfer(Lattice In, const ReturnOfOriginFact &F) {
+ return Lattice(SetFactory.add(In.LiveOrigins, F.getReturnedOriginID()));
+ }
+};
+
// ========================================================================= //
// Expired Loans Analysis
// ========================================================================= //
@@ -937,6 +1011,10 @@ void LifetimeSafetyAnalysis::run() {
ExpiredLoans =
std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory);
ExpiredLoans->run();
+
+ LiveOrigins = std::make_unique<LiveOriginAnalysis>(Cfg, AC, *FactMgr,
+ Factory->OriginSetFactory);
+ LiveOrigins->run();
}
LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
@@ -969,6 +1047,11 @@ LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const {
return Result;
}
+OriginSet LifetimeSafetyAnalysis::getLiveOriginsAtPoint(ProgramPoint PP) const {
+ assert(LiveOrigins && "LiveOriginAnalysis has not been run.");
+ return LiveOrigins->getState(PP).LiveOrigins;
+}
+
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 a48fcfd9865a8..689ca38e6fb5a 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -125,6 +125,13 @@ class LifetimeTestHelper {
return Analysis.getExpiredLoansAtPoint(PP);
}
+ std::optional<OriginSet> 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();
@@ -162,6 +169,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.
///
@@ -232,6 +248,34 @@ 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(ActualLiveSetOpt->begin(),
+ ActualLiveSetOpt->end());
+
+ 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:
@@ -244,6 +288,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);
@@ -706,5 +757,88 @@ TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) {
EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires"));
}
+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 s;
+ MyObj* p = nullptr;
+ POINT(p1);
+ while(c) {
+ POINT(p2);
+ p = &s;
+ POINT(p3);
+ }
+ POINT(p4);
+ return p;
+ }
+ )");
+ EXPECT_THAT(Origins({"p"}), AreLiveAt("p4"));
+ EXPECT_THAT(Origins({"p"}), AreLiveAt("p1"));
+ EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
+ EXPECT_THAT(Origins({"p"}), AreLiveAt("p3"));
+}
+
} // anonymous namespace
} // namespace clang::lifetimes::internal
More information about the llvm-branch-commits
mailing list