Skip to content

Commit 70b63ed

Browse files
committed
[LifetimeSafety] Add loan expiry analysis
1 parent 3e0b53f commit 70b63ed

File tree

3 files changed

+249
-5
lines changed

3 files changed

+249
-5
lines changed

clang/include/clang/Analysis/Analyses/LifetimeSafety.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ namespace internal {
3333
class Fact;
3434
class FactManager;
3535
class LoanPropagationAnalysis;
36+
class ExpiredLoansAnalysis;
3637
struct LifetimeFactory;
3738

3839
/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
@@ -53,6 +54,11 @@ template <typename Tag> struct ID {
5354
}
5455
};
5556

57+
template <typename Tag>
58+
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
59+
return OS << ID.Value;
60+
}
61+
5662
using LoanID = ID<struct LoanTag>;
5763
using OriginID = ID<struct OriginTag>;
5864

@@ -81,6 +87,9 @@ class LifetimeSafetyAnalysis {
8187
/// Returns the set of loans an origin holds at a specific program point.
8288
LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
8389

90+
/// Returns the set of loans that have expired at a specific program point.
91+
LoanSet getExpiredLoansAtPoint(ProgramPoint PP) const;
92+
8493
/// Finds the OriginID for a given declaration.
8594
/// Returns a null optional if not found.
8695
std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const;
@@ -96,6 +105,7 @@ class LifetimeSafetyAnalysis {
96105
std::unique_ptr<LifetimeFactory> Factory;
97106
std::unique_ptr<FactManager> FactMgr;
98107
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
108+
std::unique_ptr<ExpiredLoansAnalysis> ExpiredLoans;
99109
};
100110
} // namespace internal
101111
} // namespace clang::lifetimes

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@
2323
#include "llvm/Support/Debug.h"
2424
#include "llvm/Support/TimeProfiler.h"
2525
#include <cstdint>
26+
#include <memory>
2627

2728
namespace clang::lifetimes {
2829
namespace internal {
2930
namespace {
30-
template <typename Tag>
31-
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
32-
return OS << ID.Value;
33-
}
31+
// template <typename Tag>
32+
// inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
33+
// return OS << ID.Value;
34+
// }
3435
} // namespace
3536

3637
/// Represents the storage location being borrowed, e.g., a specific stack
@@ -832,6 +833,65 @@ class LoanPropagationAnalysis
832833
}
833834
};
834835

836+
// ========================================================================= //
837+
// Expired Loans Analysis
838+
// ========================================================================= //
839+
840+
/// The dataflow lattice for tracking the set of expired loans.
841+
struct ExpiredLattice {
842+
LoanSet Expired;
843+
844+
ExpiredLattice() : Expired(nullptr) {};
845+
explicit ExpiredLattice(LoanSet S) : Expired(S) {}
846+
847+
bool operator==(const ExpiredLattice &Other) const {
848+
return Expired == Other.Expired;
849+
}
850+
bool operator!=(const ExpiredLattice &Other) const {
851+
return !(*this == Other);
852+
}
853+
854+
void dump(llvm::raw_ostream &OS) const {
855+
OS << "ExpiredLattice State:\n";
856+
if (Expired.isEmpty())
857+
OS << " <empty>\n";
858+
for (const LoanID &LID : Expired)
859+
OS << " Loan " << LID << " is expired\n";
860+
}
861+
};
862+
863+
/// The analysis that tracks which loans have expired.
864+
class ExpiredLoansAnalysis
865+
: public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice,
866+
Direction::Forward> {
867+
868+
LoanSet::Factory &Factory;
869+
870+
public:
871+
ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F,
872+
LifetimeFactory &Factory)
873+
: DataflowAnalysis(C, AC, F), Factory(Factory.LoanSetFactory) {}
874+
875+
using Base::transfer;
876+
877+
StringRef getAnalysisName() const { return "ExpiredLoans"; }
878+
879+
Lattice getInitialState() { return Lattice(Factory.getEmptySet()); }
880+
881+
/// Merges two lattices by taking the union of the expired loan sets.
882+
Lattice join(Lattice L1, Lattice L2) const {
883+
return Lattice(utils::join(L1.Expired, L2.Expired, Factory));
884+
}
885+
886+
Lattice transfer(Lattice In, const ExpireFact &F) {
887+
return Lattice(Factory.add(In.Expired, F.getLoanID()));
888+
}
889+
890+
Lattice transfer(Lattice In, const IssueFact &F) {
891+
return Lattice(Factory.remove(In.Expired, F.getLoanID()));
892+
}
893+
};
894+
835895
// ========================================================================= //
836896
// TODO:
837897
// - Modify loan expiry analysis to answer `bool isExpired(Loan L, Point P)`
@@ -873,6 +933,10 @@ void LifetimeSafetyAnalysis::run() {
873933
LoanPropagation =
874934
std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
875935
LoanPropagation->run();
936+
937+
ExpiredLoans =
938+
std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory);
939+
ExpiredLoans->run();
876940
}
877941

878942
LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
@@ -881,6 +945,11 @@ LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
881945
return LoanPropagation->getLoans(OID, PP);
882946
}
883947

948+
LoanSet LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const {
949+
assert(ExpiredLoans && "ExpiredLoansAnalysis has not been run.");
950+
return ExpiredLoans->getState(PP).Expired;
951+
}
952+
884953
std::optional<OriginID>
885954
LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const {
886955
assert(FactMgr && "FactManager not initialized");

clang/unittests/Analysis/LifetimeSafetyTest.cpp

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace clang::lifetimes::internal {
2020
namespace {
2121

2222
using namespace ast_matchers;
23+
using ::testing::Not;
2324
using ::testing::UnorderedElementsAreArray;
2425

2526
// A helper class to run the full lifetime analysis on a piece of code
@@ -45,7 +46,10 @@ class LifetimeTestRunner {
4546
return;
4647
}
4748
AnalysisCtx = std::make_unique<AnalysisDeclContext>(nullptr, FD);
48-
AnalysisCtx->getCFGBuildOptions().setAllAlwaysAdd();
49+
CFG::BuildOptions &BuildOptions = AnalysisCtx->getCFGBuildOptions();
50+
BuildOptions.setAllAlwaysAdd();
51+
BuildOptions.AddImplicitDtors = true;
52+
BuildOptions.AddTemporaryDtors = true;
4953

5054
// Run the main analysis.
5155
Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx);
@@ -115,6 +119,13 @@ class LifetimeTestHelper {
115119
return Analysis.getLoansAtPoint(OID, PP);
116120
}
117121

122+
std::optional<LoanSet> getExpiredLoansAtPoint(llvm::StringRef Annotation) {
123+
ProgramPoint PP = Runner.getProgramPoint(Annotation);
124+
if (!PP)
125+
return std::nullopt;
126+
return Analysis.getExpiredLoansAtPoint(PP);
127+
}
128+
118129
private:
119130
template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
120131
auto &Ctx = Runner.getASTContext();
@@ -134,6 +145,15 @@ class LifetimeTestHelper {
134145
// GTest Matchers & Fixture
135146
// ========================================================================= //
136147

148+
// A helper class to represent the subject of a check, e.g., "the loan to 'x'".
149+
class LoanInfo {
150+
public:
151+
LoanInfo(llvm::StringRef LoanVar, LifetimeTestHelper &Helper)
152+
: LoanVar(LoanVar), Helper(Helper) {}
153+
llvm::StringRef LoanVar;
154+
LifetimeTestHelper &Helper;
155+
};
156+
137157
// It holds the name of the origin variable and a reference to the helper.
138158
class OriginInfo {
139159
public:
@@ -185,6 +205,33 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
185205
ActualLoans, result_listener);
186206
}
187207

208+
/// Matcher to verify if a loan to a specific variable has expired at a given
209+
// program point.
210+
MATCHER_P(IsExpiredAt, Annotation, "") {
211+
const LoanInfo &Info = arg;
212+
std::optional<LoanID> TargetLoanIDOpt =
213+
Info.Helper.getLoanForVar(Info.LoanVar);
214+
if (!TargetLoanIDOpt) {
215+
*result_listener << "could not find a loan for variable '"
216+
<< Info.LoanVar.str() << "'";
217+
return false;
218+
}
219+
220+
std::optional<LoanSet> ExpiredLoansSetOpt =
221+
Info.Helper.getExpiredLoansAtPoint(Annotation);
222+
if (!ExpiredLoansSetOpt) {
223+
*result_listener << "could not get a valid expired loan set at point '"
224+
<< Annotation << "'";
225+
return false;
226+
}
227+
228+
if (ExpiredLoansSetOpt->contains(*TargetLoanIDOpt))
229+
return true;
230+
231+
*result_listener << "was expected to be expired, but was not";
232+
return false;
233+
}
234+
188235
// Base test fixture to manage the runner and helper.
189236
class LifetimeAnalysisTest : public ::testing::Test {
190237
protected:
@@ -197,6 +244,10 @@ class LifetimeAnalysisTest : public ::testing::Test {
197244
return OriginInfo(OriginVar, *Helper);
198245
}
199246

247+
LoanInfo LoanTo(llvm::StringRef LoanVar) {
248+
return LoanInfo(LoanVar, *Helper);
249+
}
250+
200251
// Factory function that hides the std::vector creation.
201252
auto HasLoansTo(std::initializer_list<std::string> LoanVars,
202253
const char *Annotation) {
@@ -435,5 +486,119 @@ TEST_F(LifetimeAnalysisTest, NestedScopes) {
435486
EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_inner_scope"));
436487
}
437488

489+
// ========================================================================= //
490+
// Loan Expiration Tests
491+
// ========================================================================= //
492+
493+
TEST_F(LifetimeAnalysisTest, SimpleExpiry) {
494+
SetupTest(R"(
495+
void target() {
496+
MyObj* p = nullptr;
497+
{
498+
MyObj s;
499+
p = &s;
500+
POINT(before_expiry);
501+
} // s goes out of scope here
502+
POINT(after_expiry);
503+
}
504+
)");
505+
EXPECT_THAT(LoanTo("s"), Not(IsExpiredAt("before_expiry")));
506+
EXPECT_THAT(LoanTo("s"), IsExpiredAt("after_expiry"));
507+
}
508+
509+
TEST_F(LifetimeAnalysisTest, NestedExpiry) {
510+
SetupTest(R"(
511+
void target() {
512+
MyObj s1;
513+
MyObj* p = &s1;
514+
POINT(before_inner);
515+
{
516+
MyObj s2;
517+
p = &s2;
518+
POINT(in_inner);
519+
} // s2 expires
520+
POINT(after_inner);
521+
}
522+
)");
523+
EXPECT_THAT(LoanTo("s1"), Not(IsExpiredAt("before_inner")));
524+
EXPECT_THAT(LoanTo("s2"), Not(IsExpiredAt("in_inner")));
525+
EXPECT_THAT(LoanTo("s1"), Not(IsExpiredAt("after_inner")));
526+
EXPECT_THAT(LoanTo("s2"), IsExpiredAt("after_inner"));
527+
}
528+
529+
TEST_F(LifetimeAnalysisTest, ConditionalExpiry) {
530+
SetupTest(R"(
531+
void target(bool cond) {
532+
MyObj s1;
533+
MyObj* p = &s1;
534+
POINT(before_if);
535+
if (cond) {
536+
MyObj s2;
537+
p = &s2;
538+
POINT(then_block);
539+
} // s2 expires here
540+
POINT(after_if);
541+
}
542+
)");
543+
EXPECT_THAT(LoanTo("s1"), Not(IsExpiredAt("before_if")));
544+
EXPECT_THAT(LoanTo("s2"), Not(IsExpiredAt("then_block")));
545+
// After the if-statement, the loan to s2 (created in the 'then' branch)
546+
// will have expired.
547+
EXPECT_THAT(LoanTo("s2"), IsExpiredAt("after_if"));
548+
EXPECT_THAT(LoanTo("s1"), Not(IsExpiredAt("after_if")));
549+
}
550+
551+
TEST_F(LifetimeAnalysisTest, LoopExpiry) {
552+
SetupTest(R"(
553+
void target() {
554+
MyObj *p = nullptr;
555+
for (int i = 0; i < 2; ++i) {
556+
MyObj s;
557+
p = &s;
558+
POINT(in_loop);
559+
} // s expires here on each iteration
560+
POINT(after_loop);
561+
}
562+
)");
563+
// Inside the loop, before the scope of 's' ends, its loan is not expired.
564+
EXPECT_THAT(LoanTo("s"), Not(IsExpiredAt("in_loop")));
565+
// After the loop finishes, the loan to 's' from the last iteration has
566+
// expired.
567+
EXPECT_THAT(LoanTo("s"), IsExpiredAt("after_loop"));
568+
}
569+
570+
TEST_F(LifetimeAnalysisTest, MultipleExpiredLoans) {
571+
SetupTest(R"(
572+
void target() {
573+
MyObj *p1, *p2, *p3;
574+
{
575+
MyObj s1;
576+
p1 = &s1;
577+
POINT(p1);
578+
} // s1 expires
579+
POINT(p2);
580+
{
581+
MyObj s2;
582+
p2 = &s2;
583+
MyObj s3;
584+
p3 = &s3;
585+
POINT(p3);
586+
} // s2, s3 expire
587+
POINT(p4);
588+
}
589+
)");
590+
EXPECT_THAT(LoanTo("s1"), Not(IsExpiredAt("p1")));
591+
592+
EXPECT_THAT(LoanTo("s1"), IsExpiredAt("p2"));
593+
594+
EXPECT_THAT(LoanTo("s1"), IsExpiredAt("p3"));
595+
EXPECT_THAT(LoanTo("s2"), Not(IsExpiredAt("p3")));
596+
EXPECT_THAT(LoanTo("s3"), Not(IsExpiredAt("p3")));
597+
598+
EXPECT_THAT(LoanTo("s1"), IsExpiredAt("p4"));
599+
EXPECT_THAT(LoanTo("s2"), IsExpiredAt("p4"));
600+
EXPECT_THAT(LoanTo("s3"), IsExpiredAt("p4"));
601+
}
602+
438603
} // anonymous namespace
439604
} // namespace clang::lifetimes::internal

0 commit comments

Comments
 (0)