Skip to content

Commit 1b945d0

Browse files
committed
[LifetimeSafety] Revamp test suite using unittests
1 parent 3e67c98 commit 1b945d0

File tree

6 files changed

+640
-139
lines changed

6 files changed

+640
-139
lines changed

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

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,87 @@
1717
//===----------------------------------------------------------------------===//
1818
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
1919
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
20-
#include "clang/AST/DeclBase.h"
2120
#include "clang/Analysis/AnalysisDeclContext.h"
2221
#include "clang/Analysis/CFG.h"
23-
namespace clang {
22+
#include "llvm/ADT/ImmutableSet.h"
23+
#include "llvm/ADT/StringMap.h"
24+
#include <memory>
2425

25-
void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
26-
AnalysisDeclContext &AC);
26+
namespace clang::lifetimes {
27+
namespace internal {
28+
// Forward declarations of internal types.
29+
class Fact;
30+
class FactManager;
31+
class LoanPropagationAnalysis;
32+
struct LifetimeFactory;
2733

28-
} // namespace clang
34+
/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
35+
/// Used for giving ID to loans and origins.
36+
template <typename Tag> struct ID {
37+
uint32_t Value = 0;
38+
39+
bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; }
40+
bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); }
41+
bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; }
42+
ID<Tag> operator++(int) {
43+
ID<Tag> Tmp = *this;
44+
++Value;
45+
return Tmp;
46+
}
47+
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
48+
IDBuilder.AddInteger(Value);
49+
}
50+
};
51+
52+
using LoanID = ID<struct LoanTag>;
53+
using OriginID = ID<struct OriginTag>;
54+
55+
// Using LLVM's immutable collections is efficient for dataflow analysis
56+
// as it avoids deep copies during state transitions.
57+
// TODO(opt): Consider using a bitset to represent the set of loans.
58+
using LoanSet = llvm::ImmutableSet<LoanID>;
59+
using OriginSet = llvm::ImmutableSet<OriginID>;
60+
61+
/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
62+
/// `Fact`. identified by a lifetime-related event (`Fact`).
63+
///
64+
/// A `ProgramPoint` has "after" semantics: it represents the location
65+
/// immediately after its corresponding `Fact`.
66+
using ProgramPoint = const Fact *;
67+
68+
/// Running the lifetime safety analysis and querying its results. It
69+
/// encapsulates the various dataflow analyses.
70+
class LifetimeSafetyAnalysis {
71+
public:
72+
LifetimeSafetyAnalysis(AnalysisDeclContext &AC);
73+
~LifetimeSafetyAnalysis();
74+
75+
void run();
76+
77+
/// Returns the set of loans an origin holds at a specific program point.
78+
LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
79+
80+
/// Finds the OriginID for a given declaration.
81+
/// Returns a null optional if not found.
82+
std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const;
83+
84+
/// Finds the LoanID's for the loan created with the specific variable as
85+
/// their Path.
86+
std::vector<LoanID> getLoanIDForVar(const VarDecl *VD) const;
87+
88+
llvm::StringMap<ProgramPoint> getTestPoints() const;
89+
90+
private:
91+
AnalysisDeclContext &AC;
92+
std::unique_ptr<LifetimeFactory> Factory;
93+
std::unique_ptr<FactManager> FactMgr;
94+
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
95+
};
96+
} // namespace internal
97+
98+
/// The main entry point for the analysis.
99+
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC);
100+
101+
} // namespace clang::lifetimes
29102

30103
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 127 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@
2424
#include "llvm/Support/TimeProfiler.h"
2525
#include <cstdint>
2626

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

3036
/// Represents the storage location being borrowed, e.g., a specific stack
3137
/// variable.
@@ -36,32 +42,6 @@ struct AccessPath {
3642
AccessPath(const clang::ValueDecl *D) : D(D) {}
3743
};
3844

39-
/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
40-
/// Used for giving ID to loans and origins.
41-
template <typename Tag> struct ID {
42-
uint32_t Value = 0;
43-
44-
bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; }
45-
bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); }
46-
bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; }
47-
ID<Tag> operator++(int) {
48-
ID<Tag> Tmp = *this;
49-
++Value;
50-
return Tmp;
51-
}
52-
void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
53-
IDBuilder.AddInteger(Value);
54-
}
55-
};
56-
57-
template <typename Tag>
58-
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
59-
return OS << ID.Value;
60-
}
61-
62-
using LoanID = ID<struct LoanTag>;
63-
using OriginID = ID<struct OriginTag>;
64-
6545
/// Information about a single borrow, or "Loan". A loan is created when a
6646
/// reference or pointer is created.
6747
struct Loan {
@@ -223,7 +203,9 @@ class Fact {
223203
/// An origin is propagated from a source to a destination (e.g., p = q).
224204
AssignOrigin,
225205
/// An origin escapes the function by flowing into the return value.
226-
ReturnOfOrigin
206+
ReturnOfOrigin,
207+
/// A marker for a specific point in the code, for testing.
208+
TestPoint,
227209
};
228210

229211
private:
@@ -310,6 +292,24 @@ class ReturnOfOriginFact : public Fact {
310292
}
311293
};
312294

295+
/// A dummy-fact used to mark a specific point in the code for testing.
296+
/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
297+
class TestPointFact : public Fact {
298+
std::string Annotation;
299+
300+
public:
301+
static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; }
302+
303+
explicit TestPointFact(std::string Annotation)
304+
: Fact(Kind::TestPoint), Annotation(std::move(Annotation)) {}
305+
306+
const std::string &getAnnotation() const { return Annotation; }
307+
308+
void dump(llvm::raw_ostream &OS) const override {
309+
OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
310+
}
311+
};
312+
313313
class FactManager {
314314
public:
315315
llvm::ArrayRef<const Fact *> getFacts(const CFGBlock *B) const {
@@ -363,6 +363,7 @@ class FactManager {
363363
};
364364

365365
class FactGenerator : public ConstStmtVisitor<FactGenerator> {
366+
using Base = ConstStmtVisitor<FactGenerator>;
366367

367368
public:
368369
FactGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
@@ -458,6 +459,15 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
458459
}
459460
}
460461

462+
void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) {
463+
// Check if this is a test point marker. If so, we are done with this
464+
// expression.
465+
if (VisitTestPoint(FCE))
466+
return;
467+
// Visit as normal otherwise.
468+
Base::VisitCXXFunctionalCastExpr(FCE);
469+
}
470+
461471
private:
462472
// Check if a type has an origin.
463473
bool hasOrigin(QualType QT) { return QT->isPointerOrReferenceType(); }
@@ -491,6 +501,27 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
491501
}
492502
}
493503

504+
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
505+
/// If so, creates a `TestPointFact` and returns true.
506+
bool VisitTestPoint(const CXXFunctionalCastExpr *FCE) {
507+
if (!FCE->getType()->isVoidType())
508+
return false;
509+
510+
const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
511+
if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) {
512+
llvm::StringRef LiteralValue = SL->getString();
513+
const std::string Prefix = "__lifetime_test_point_";
514+
515+
if (LiteralValue.starts_with(Prefix)) {
516+
std::string Annotation = LiteralValue.drop_front(Prefix.length()).str();
517+
CurrentBlockFacts.push_back(
518+
FactMgr.createFact<TestPointFact>(Annotation));
519+
return true;
520+
}
521+
}
522+
return false;
523+
}
524+
494525
FactManager &FactMgr;
495526
AnalysisDeclContext &AC;
496527
llvm::SmallVector<Fact *> CurrentBlockFacts;
@@ -637,6 +668,8 @@ class DataflowAnalysis {
637668
return D->transfer(In, *F->getAs<AssignOriginFact>());
638669
case Fact::Kind::ReturnOfOrigin:
639670
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
671+
case Fact::Kind::TestPoint:
672+
return D->transfer(In, *F->getAs<TestPointFact>());
640673
}
641674
llvm_unreachable("Unknown fact kind");
642675
}
@@ -646,14 +679,16 @@ class DataflowAnalysis {
646679
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
647680
Lattice transfer(Lattice In, const AssignOriginFact &) { return In; }
648681
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
682+
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
649683
};
650684

651685
namespace utils {
652686

653687
/// Computes the union of two ImmutableSets.
654688
template <typename T>
655-
llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, llvm::ImmutableSet<T> B,
656-
typename llvm::ImmutableSet<T>::Factory &F) {
689+
static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
690+
llvm::ImmutableSet<T> B,
691+
typename llvm::ImmutableSet<T>::Factory &F) {
657692
if (A.getHeight() < B.getHeight())
658693
std::swap(A, B);
659694
for (const T &E : B)
@@ -666,7 +701,7 @@ llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, llvm::ImmutableSet<T> B,
666701
// efficient merge could be implemented using a Patricia Trie or HAMT
667702
// instead of the current AVL-tree-based ImmutableMap.
668703
template <typename K, typename V, typename Joiner>
669-
llvm::ImmutableMap<K, V>
704+
static llvm::ImmutableMap<K, V>
670705
join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
671706
typename llvm::ImmutableMap<K, V>::Factory &F, Joiner joinValues) {
672707
if (A.getHeight() < B.getHeight())
@@ -690,10 +725,6 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
690725
// Loan Propagation Analysis
691726
// ========================================================================= //
692727

693-
// Using LLVM's immutable collections is efficient for dataflow analysis
694-
// as it avoids deep copies during state transitions.
695-
// TODO(opt): Consider using a bitset to represent the set of loans.
696-
using LoanSet = llvm::ImmutableSet<LoanID>;
697728
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
698729

699730
/// An object to hold the factories for immutable collections, ensuring
@@ -807,17 +838,27 @@ class LoanPropagationAnalysis
807838
// - Modify origin liveness analysis to answer `bool isLive(Origin O, Point P)`
808839
// - Using the above three to perform the final error reporting.
809840
// ========================================================================= //
810-
} // anonymous namespace
811841

812-
void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
813-
AnalysisDeclContext &AC) {
842+
// ========================================================================= //
843+
// LifetimeSafetyAnalysis Class Implementation
844+
// ========================================================================= //
845+
846+
LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
847+
848+
LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC)
849+
: AC(AC), Factory(std::make_unique<LifetimeFactory>()),
850+
FactMgr(std::make_unique<FactManager>()) {}
851+
852+
void LifetimeSafetyAnalysis::run() {
814853
llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
854+
855+
const CFG &Cfg = *AC.getCFG();
815856
DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
816857
/*ShowColors=*/true));
817-
FactManager FactMgr;
818-
FactGenerator FactGen(FactMgr, AC);
858+
859+
FactGenerator FactGen(*FactMgr, AC);
819860
FactGen.run();
820-
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
861+
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC));
821862

822863
/// TODO(opt): Consider optimizing individual blocks before running the
823864
/// dataflow analysis.
@@ -828,9 +869,49 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
828869
/// blocks; only Decls are visible. Therefore, loans in a block that
829870
/// never reach an Origin associated with a Decl can be safely dropped by
830871
/// the analysis.
831-
LifetimeFactory Factory;
832-
LoanPropagationAnalysis LoanPropagation(Cfg, AC, FactMgr, Factory);
833-
LoanPropagation.run();
834-
DEBUG_WITH_TYPE("LifetimeLoanPropagation", LoanPropagation.dump());
872+
LoanPropagation =
873+
std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
874+
LoanPropagation->run();
875+
}
876+
877+
LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
878+
ProgramPoint PP) const {
879+
assert(LoanPropagation && "Analysis has not been run.");
880+
return LoanPropagation->getLoans(OID, PP);
881+
}
882+
883+
std::optional<OriginID>
884+
LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const {
885+
assert(FactMgr && "FactManager not initialized");
886+
// This assumes the OriginManager's `get` can find an existing origin.
887+
// We might need a `find` method on OriginManager to avoid `getOrCreate` logic
888+
// in a const-query context if that becomes an issue.
889+
return FactMgr->getOriginMgr().get(*D);
890+
}
891+
892+
std::vector<LoanID>
893+
LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const {
894+
assert(FactMgr && "FactManager not initialized");
895+
std::vector<LoanID> Result;
896+
for (const Loan &L : FactMgr->getLoanMgr().getLoans())
897+
if (L.Path.D == VD)
898+
Result.push_back(L.ID);
899+
return Result;
900+
}
901+
902+
llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const {
903+
assert(FactMgr && "FactManager not initialized");
904+
llvm::StringMap<ProgramPoint> AnnotationToPointMap;
905+
for (const CFGBlock *Block : *AC.getCFG())
906+
for (const Fact *F : FactMgr->getFacts(Block))
907+
if (const auto *TPF = F->getAs<TestPointFact>())
908+
AnnotationToPointMap[TPF->getAnnotation()] = F;
909+
return AnnotationToPointMap;
910+
}
911+
} // namespace internal
912+
913+
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC) {
914+
internal::LifetimeSafetyAnalysis Analysis(AC);
915+
Analysis.run();
835916
}
836-
} // namespace clang
917+
} // namespace clang::lifetimes

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3030,8 +3030,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
30303030
// TODO: Enable lifetime safety analysis for other languages once it is
30313031
// stable.
30323032
if (EnableLifetimeSafetyAnalysis && S.getLangOpts().CPlusPlus) {
3033-
if (CFG *cfg = AC.getCFG())
3034-
runLifetimeSafetyAnalysis(*cast<DeclContext>(D), *cfg, AC);
3033+
if (AC.getCFG())
3034+
lifetimes::runLifetimeSafetyAnalysis(AC);
30353035
}
30363036
// Check for violations of "called once" parameter properties.
30373037
if (S.getLangOpts().ObjC && !S.getLangOpts().CPlusPlus &&

0 commit comments

Comments
 (0)