Skip to content

Commit 55c972e

Browse files
committed
[LifetimeSafety] Revamp test suite using unittests
1 parent 4f11ea0 commit 55c972e

File tree

5 files changed

+628
-53
lines changed

5 files changed

+628
-53
lines changed

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

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,82 @@
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+
using ProgramPoint = std::pair<const CFGBlock *, const Fact *>;
62+
63+
/// Running the lifetime safety analysis and querying its results. It
64+
/// encapsulates the various dataflow analyses.
65+
class LifetimeSafetyAnalysis {
66+
public:
67+
LifetimeSafetyAnalysis(AnalysisDeclContext &AC);
68+
~LifetimeSafetyAnalysis();
69+
70+
void run();
71+
72+
/// Returns the set of loans an origin holds at a specific program point.
73+
LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
74+
75+
/// Finds the OriginID for a given declaration.
76+
/// Returns a null optional if not found.
77+
std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const;
78+
79+
/// Finds the LoanID for a loan created on a specific variable.
80+
/// Returns a null optional if not found.
81+
std::optional<LoanID> getLoanIDForVar(const VarDecl *VD) const;
82+
83+
llvm::StringMap<ProgramPoint> getTestPoints() const;
84+
85+
private:
86+
AnalysisDeclContext &AC;
87+
std::unique_ptr<LifetimeFactory> Factory;
88+
std::unique_ptr<FactManager> FactMgr;
89+
std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
90+
};
91+
} // namespace internal
92+
93+
/// The main entry point for the analysis.
94+
void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC);
95+
96+
} // namespace clang::lifetimes
2997

3098
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 128 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;
@@ -630,6 +661,8 @@ class DataflowAnalysis {
630661
return D->transfer(In, *F->getAs<AssignOriginFact>());
631662
case Fact::Kind::ReturnOfOrigin:
632663
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
664+
case Fact::Kind::TestPoint:
665+
return D->transfer(In, *F->getAs<TestPointFact>());
633666
}
634667
llvm_unreachable("Unknown fact kind");
635668
}
@@ -639,14 +672,16 @@ class DataflowAnalysis {
639672
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
640673
Lattice transfer(Lattice In, const AssignOriginFact &) { return In; }
641674
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
675+
Lattice transfer(Lattice In, const TestPointFact &) { return In; }
642676
};
643677

644678
namespace utils {
645679

646680
/// Computes the union of two ImmutableSets.
647681
template <typename T>
648-
llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, llvm::ImmutableSet<T> B,
649-
typename llvm::ImmutableSet<T>::Factory &F) {
682+
static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
683+
llvm::ImmutableSet<T> B,
684+
typename llvm::ImmutableSet<T>::Factory &F) {
650685
if (A.getHeight() < B.getHeight())
651686
std::swap(A, B);
652687
for (const T &E : B)
@@ -659,7 +694,7 @@ llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, llvm::ImmutableSet<T> B,
659694
// efficient merge could be implemented using a Patricia Trie or HAMT
660695
// instead of the current AVL-tree-based ImmutableMap.
661696
template <typename K, typename V, typename Joiner>
662-
llvm::ImmutableMap<K, V>
697+
static llvm::ImmutableMap<K, V>
663698
join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
664699
typename llvm::ImmutableMap<K, V>::Factory &F, Joiner joinValues) {
665700
if (A.getHeight() < B.getHeight())
@@ -683,10 +718,6 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
683718
// Loan Propagation Analysis
684719
// ========================================================================= //
685720

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

692723
/// An object to hold the factories for immutable collections, ensuring
@@ -800,17 +831,27 @@ class LoanPropagationAnalysis
800831
// - Modify origin liveness analysis to answer `bool isLive(Origin O, Point P)`
801832
// - Using the above three to perform the final error reporting.
802833
// ========================================================================= //
803-
} // anonymous namespace
804834

805-
void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
806-
AnalysisDeclContext &AC) {
835+
// ========================================================================= //
836+
// LifetimeSafetyAnalysis Class Implementation
837+
// ========================================================================= //
838+
839+
LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
840+
841+
LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC)
842+
: AC(AC), Factory(std::make_unique<LifetimeFactory>()),
843+
FactMgr(std::make_unique<FactManager>()) {}
844+
845+
void LifetimeSafetyAnalysis::run() {
807846
llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
847+
848+
const CFG &Cfg = *AC.getCFG();
808849
DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
809850
/*ShowColors=*/true));
810-
FactManager FactMgr;
811-
FactGenerator FactGen(FactMgr, AC);
851+
852+
FactGenerator FactGen(*FactMgr, AC);
812853
FactGen.run();
813-
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
854+
DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC));
814855

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

clang/unittests/Analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_clang_unittest(ClangAnalysisTests
44
CloneDetectionTest.cpp
55
ExprMutationAnalyzerTest.cpp
66
IntervalPartitionTest.cpp
7+
LifetimeSafetyTest.cpp
78
MacroExpansionContextTest.cpp
89
UnsafeBufferUsageTest.cpp
910
CLANG_LIBS

0 commit comments

Comments
 (0)