Skip to content

[LifetimeSafety] Implement LiveOrigins analysis #148976

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: users/usx95/07-14-users_usx95_lifetime-safety-add-loan-expiry
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions clang/include/clang/Analysis/Analyses/LifetimeSafety.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
///
Expand All @@ -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
Expand Down
83 changes: 83 additions & 0 deletions clang/lib/Analysis/LifetimeSafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -721,12 +721,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) {
Expand Down Expand Up @@ -827,6 +829,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
// ========================================================================= //
Expand Down Expand Up @@ -957,6 +1031,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,
Expand Down Expand Up @@ -989,6 +1067,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;
Expand Down
136 changes: 136 additions & 0 deletions clang/unittests/Analysis/LifetimeSafetyTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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:
Expand All @@ -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);
Expand Down Expand Up @@ -706,5 +757,90 @@ 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 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
Loading