[clang] [LifetimeSafety] Revamp test suite using unittests (PR #149158)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 16 12:42:30 PDT 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/149158
>From e5fefc05dcef6fd80fb459cca0d68a4d14c66564 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Wed, 16 Jul 2025 18:22:39 +0000
Subject: [PATCH] [LifetimeSafety] Revamp test suite using unittests
---
.../clang/Analysis/Analyses/LifetimeSafety.h | 77 +++++-
clang/lib/Analysis/LifetimeSafety.cpp | 201 +++++++++++----
clang/lib/Sema/AnalysisBasedWarnings.cpp | 4 +-
clang/unittests/Analysis/CMakeLists.txt | 1 +
.../unittests/Analysis/LifetimeSafetyTest.cpp | 231 ++++++++++++++++++
5 files changed, 455 insertions(+), 59 deletions(-)
create mode 100644 clang/unittests/Analysis/LifetimeSafetyTest.cpp
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
index 9998702a41cab..4d45284da646c 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
@@ -20,11 +20,80 @@
#include "clang/AST/DeclBase.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Analysis/CFG.h"
-namespace clang {
+#include "llvm/ADT/ImmutableSet.h"
+#include "llvm/ADT/StringMap.h"
+#include <memory>
-void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
- AnalysisDeclContext &AC);
+namespace clang::lifetimes {
+namespace internal {
+// Forward declarations of internal types.
+class Fact;
+class FactManager;
+class LoanPropagationAnalysis;
+struct LifetimeFactory;
-} // namespace clang
+/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
+/// Used for giving ID to loans and origins.
+template <typename Tag> struct ID {
+ uint32_t Value = 0;
+
+ bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; }
+ bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); }
+ bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; }
+ ID<Tag> operator++(int) {
+ ID<Tag> Tmp = *this;
+ ++Value;
+ return Tmp;
+ }
+ void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
+ IDBuilder.AddInteger(Value);
+ }
+};
+
+using LoanID = ID<struct LoanTag>;
+using OriginID = ID<struct OriginTag>;
+
+// Using LLVM's immutable collections is efficient for dataflow analysis
+// as it avoids deep copies during state transitions.
+// TODO(opt): Consider using a bitset to represent the set of loans.
+using LoanSet = llvm::ImmutableSet<LoanID>;
+using OriginSet = llvm::ImmutableSet<OriginID>;
+
+using ProgramPoint = std::pair<const CFGBlock *, const Fact *>;
+
+/// Running the lifetime safety analysis and querying its results. It
+/// encapsulates the various dataflow analyses.
+class LifetimeSafetyAnalysis {
+public:
+ LifetimeSafetyAnalysis(AnalysisDeclContext &AC);
+ ~LifetimeSafetyAnalysis();
+
+ void run();
+
+ /// Returns the set of loans an origin holds at a specific program point.
+ LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const;
+
+ /// Finds the OriginID for a given declaration.
+ /// Returns a null optional if not found.
+ std::optional<OriginID> getOriginIDForDecl(const ValueDecl *D) const;
+
+ /// Finds the LoanID for a loan created on a specific variable.
+ /// Returns a null optional if not found.
+ std::optional<LoanID> getLoanIDForVar(const VarDecl *VD) const;
+
+ llvm::StringMap<ProgramPoint> getTestPoints() const;
+
+private:
+ AnalysisDeclContext &AC;
+ std::unique_ptr<LifetimeFactory> Factory;
+ std::unique_ptr<FactManager> FactMgr;
+ std::unique_ptr<LoanPropagationAnalysis> LoanPropagation;
+};
+} // namespace internal
+
+/// The main entry point for the analysis.
+void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC);
+
+} // namespace clang::lifetimes
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index e3a03cf93880e..50b47e1431723 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -24,8 +24,14 @@
#include "llvm/Support/TimeProfiler.h"
#include <cstdint>
-namespace clang {
+namespace clang::lifetimes {
+namespace internal {
namespace {
+template <typename Tag>
+inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
+ return OS << ID.Value;
+}
+} // namespace
/// Represents the storage location being borrowed, e.g., a specific stack
/// variable.
@@ -36,32 +42,6 @@ struct AccessPath {
AccessPath(const clang::ValueDecl *D) : D(D) {}
};
-/// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type.
-/// Used for giving ID to loans and origins.
-template <typename Tag> struct ID {
- uint32_t Value = 0;
-
- bool operator==(const ID<Tag> &Other) const { return Value == Other.Value; }
- bool operator!=(const ID<Tag> &Other) const { return !(*this == Other); }
- bool operator<(const ID<Tag> &Other) const { return Value < Other.Value; }
- ID<Tag> operator++(int) {
- ID<Tag> Tmp = *this;
- ++Value;
- return Tmp;
- }
- void Profile(llvm::FoldingSetNodeID &IDBuilder) const {
- IDBuilder.AddInteger(Value);
- }
-};
-
-template <typename Tag>
-inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ID<Tag> ID) {
- return OS << ID.Value;
-}
-
-using LoanID = ID<struct LoanTag>;
-using OriginID = ID<struct OriginTag>;
-
/// Information about a single borrow, or "Loan". A loan is created when a
/// reference or pointer is created.
struct Loan {
@@ -223,7 +203,9 @@ class Fact {
/// An origin is propagated from a source to a destination (e.g., p = q).
AssignOrigin,
/// An origin escapes the function by flowing into the return value.
- ReturnOfOrigin
+ ReturnOfOrigin,
+ /// A marker for a specific point in the code, for testing.
+ TestPoint,
};
private:
@@ -310,6 +292,24 @@ class ReturnOfOriginFact : public Fact {
}
};
+/// A dummy-fact used to mark a specific point in the code for testing.
+/// It is generated by recognizing a `void("__lifetime_test_point_...")` cast.
+class TestPointFact : public Fact {
+ std::string Annotation;
+
+public:
+ static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; }
+
+ explicit TestPointFact(std::string Annotation)
+ : Fact(Kind::TestPoint), Annotation(std::move(Annotation)) {}
+
+ const std::string &getAnnotation() const { return Annotation; }
+
+ void dump(llvm::raw_ostream &OS) const override {
+ OS << "TestPoint (Annotation: \"" << getAnnotation() << "\")\n";
+ }
+};
+
class FactManager {
public:
llvm::ArrayRef<const Fact *> getFacts(const CFGBlock *B) const {
@@ -363,6 +363,7 @@ class FactManager {
};
class FactGenerator : public ConstStmtVisitor<FactGenerator> {
+ using Base = ConstStmtVisitor<FactGenerator>;
public:
FactGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
@@ -458,6 +459,15 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
}
}
+ void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *FCE) {
+ // Check if this is a test point marker. If so, we are done with this
+ // expression.
+ if (VisitTestPoint(FCE))
+ return;
+ // Visit as normal otherwise.
+ Base::VisitCXXFunctionalCastExpr(FCE);
+ }
+
private:
// Check if a type has an origin.
bool hasOrigin(QualType QT) { return QT->isPointerOrReferenceType(); }
@@ -491,6 +501,27 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
}
}
+ /// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
+ /// If so, creates a `TestPointFact` and returns true.
+ bool VisitTestPoint(const CXXFunctionalCastExpr *FCE) {
+ if (!FCE->getType()->isVoidType())
+ return false;
+
+ const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
+ if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) {
+ llvm::StringRef LiteralValue = SL->getString();
+ const std::string Prefix = "__lifetime_test_point_";
+
+ if (LiteralValue.starts_with(Prefix)) {
+ std::string Annotation = LiteralValue.drop_front(Prefix.length()).str();
+ CurrentBlockFacts.push_back(
+ FactMgr.createFact<TestPointFact>(Annotation));
+ return true;
+ }
+ }
+ return false;
+ }
+
FactManager &FactMgr;
AnalysisDeclContext &AC;
llvm::SmallVector<Fact *> CurrentBlockFacts;
@@ -500,6 +531,9 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
// Generic Dataflow Analysis
// ========================================================================= //
+// DO NOT SUBMIT: TODO: Document notion of before or after in the analyses.
+using ProgramPoint = std::pair<const CFGBlock *, const Fact *>;
+
enum class Direction { Forward, Backward };
/// A generic, policy-based driver for dataflow analyses. It combines
@@ -532,6 +566,7 @@ class DataflowAnalysis {
llvm::DenseMap<const CFGBlock *, Lattice> InStates;
llvm::DenseMap<const CFGBlock *, Lattice> OutStates;
+ llvm::DenseMap<ProgramPoint, Lattice> PerPointStates;
static constexpr bool isForward() { return Dir == Direction::Forward; }
@@ -577,6 +612,8 @@ class DataflowAnalysis {
}
}
+ Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); }
+
Lattice getInState(const CFGBlock *B) const { return InStates.lookup(B); }
Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); }
@@ -590,18 +627,22 @@ class DataflowAnalysis {
getOutState(&B).dump(llvm::dbgs());
}
+private:
/// Computes the state at one end of a block by applying all its facts
/// sequentially to a given state from the other end.
- /// TODO: We might need to store intermediate states per-fact in the block for
- /// later analysis.
Lattice transferBlock(const CFGBlock *Block, Lattice State) {
auto Facts = AllFacts.getFacts(Block);
- if constexpr (isForward())
- for (const Fact *F : Facts)
+ if constexpr (isForward()) {
+ for (const Fact *F : Facts) {
State = transferFact(State, F);
- else
- for (const Fact *F : llvm::reverse(Facts))
+ PerPointStates[{Block, F}] = State;
+ }
+ } else {
+ for (const Fact *F : llvm::reverse(Facts)) {
State = transferFact(State, F);
+ PerPointStates[{Block, F}] = State;
+ }
+ }
return State;
}
@@ -617,6 +658,8 @@ class DataflowAnalysis {
return D->transfer(In, *F->getAs<AssignOriginFact>());
case Fact::Kind::ReturnOfOrigin:
return D->transfer(In, *F->getAs<ReturnOfOriginFact>());
+ case Fact::Kind::TestPoint:
+ return D->transfer(In, *F->getAs<TestPointFact>());
}
llvm_unreachable("Unknown fact kind");
}
@@ -626,14 +669,16 @@ class DataflowAnalysis {
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
Lattice transfer(Lattice In, const AssignOriginFact &) { return In; }
Lattice transfer(Lattice In, const ReturnOfOriginFact &) { return In; }
+ Lattice transfer(Lattice In, const TestPointFact &) { return In; }
};
namespace utils {
/// Computes the union of two ImmutableSets.
template <typename T>
-llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, llvm::ImmutableSet<T> B,
- typename llvm::ImmutableSet<T>::Factory &F) {
+static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
+ llvm::ImmutableSet<T> B,
+ typename llvm::ImmutableSet<T>::Factory &F) {
if (A.getHeight() < B.getHeight())
std::swap(A, B);
for (const T &E : B)
@@ -646,7 +691,7 @@ llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A, llvm::ImmutableSet<T> B,
// efficient merge could be implemented using a Patricia Trie or HAMT
// instead of the current AVL-tree-based ImmutableMap.
template <typename K, typename V, typename Joiner>
-llvm::ImmutableMap<K, V>
+static llvm::ImmutableMap<K, V>
join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
typename llvm::ImmutableMap<K, V>::Factory &F, Joiner joinValues) {
if (A.getHeight() < B.getHeight())
@@ -670,10 +715,6 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B,
// Loan Propagation Analysis
// ========================================================================= //
-// Using LLVM's immutable collections is efficient for dataflow analysis
-// as it avoids deep copies during state transitions.
-// TODO(opt): Consider using a bitset to represent the set of loans.
-using LoanSet = llvm::ImmutableSet<LoanID>;
using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>;
/// An object to hold the factories for immutable collections, ensuring
@@ -769,6 +810,10 @@ class LoanPropagationAnalysis
Factory.OriginMapFactory.add(In.Origins, DestOID, SrcLoans));
}
+ LoanSet getLoans(OriginID OID, ProgramPoint P) {
+ return getLoans(getState(P), OID);
+ }
+
private:
LoanSet getLoans(Lattice L, OriginID OID) {
if (auto *Loans = L.Origins.lookup(OID))
@@ -779,22 +824,31 @@ class LoanPropagationAnalysis
// ========================================================================= //
// TODO:
-// - Modifying loan propagation to answer `LoanSet getLoans(Origin O, Point P)`
// - Modify loan expiry analysis to answer `bool isExpired(Loan L, Point P)`
// - Modify origin liveness analysis to answer `bool isLive(Origin O, Point P)`
// - Using the above three to perform the final error reporting.
// ========================================================================= //
-} // anonymous namespace
-void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
- AnalysisDeclContext &AC) {
+// ========================================================================= //
+// LifetimeSafetyAnalysis Class Implementation
+// ========================================================================= //
+
+LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
+
+LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC)
+ : AC(AC), Factory(std::make_unique<LifetimeFactory>()),
+ FactMgr(std::make_unique<FactManager>()) {}
+
+void LifetimeSafetyAnalysis::run() {
llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
+
+ const CFG &Cfg = *AC.getCFG();
DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
/*ShowColors=*/true));
- FactManager FactMgr;
- FactGenerator FactGen(FactMgr, AC);
+
+ FactGenerator FactGen(*FactMgr, AC);
FactGen.run();
- DEBUG_WITH_TYPE("LifetimeFacts", FactMgr.dump(Cfg, AC));
+ DEBUG_WITH_TYPE("LifetimeFacts", FactMgr->dump(Cfg, AC));
/// TODO(opt): Consider optimizing individual blocks before running the
/// dataflow analysis.
@@ -805,9 +859,50 @@ void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
/// blocks; only Decls are visible. Therefore, loans in a block that
/// never reach an Origin associated with a Decl can be safely dropped by
/// the analysis.
- LifetimeFactory Factory;
- LoanPropagationAnalysis LoanPropagation(Cfg, AC, FactMgr, Factory);
- LoanPropagation.run();
- DEBUG_WITH_TYPE("LifetimeLoanPropagation", LoanPropagation.dump());
+ LoanPropagation =
+ std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
+ LoanPropagation->run();
+ DEBUG_WITH_TYPE("LifetimeLoanPropagation", LoanPropagation->dump());
+}
+
+LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID,
+ ProgramPoint PP) const {
+ assert(LoanPropagation && "Analysis has not been run.");
+ return LoanPropagation->getLoans(OID, PP);
+}
+
+std::optional<OriginID>
+LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const {
+ assert(FactMgr && "FactManager not initialized");
+ // This assumes the OriginManager's `get` can find an existing origin.
+ // We might need a `find` method on OriginManager to avoid `getOrCreate` logic
+ // in a const-query context if that becomes an issue.
+ return FactMgr->getOriginMgr().get(*D);
+}
+
+std::optional<LoanID>
+LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const {
+ assert(FactMgr && "FactManager not initialized");
+ for (const Loan &L : FactMgr->getLoanMgr().getLoans()) {
+ if (L.Path.D == VD)
+ return L.ID;
+ }
+ return std::nullopt;
+}
+
+llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const {
+ assert(FactMgr && "FactManager not initialized");
+ llvm::StringMap<ProgramPoint> AnnotationToPointMap;
+ for (const CFGBlock *Block : *AC.getCFG())
+ for (const Fact *F : FactMgr->getFacts(Block))
+ if (const auto *TPF = F->getAs<TestPointFact>())
+ AnnotationToPointMap[TPF->getAnnotation()] = {Block, F};
+ return AnnotationToPointMap;
+}
+} // namespace internal
+
+void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC) {
+ internal::LifetimeSafetyAnalysis Analysis(AC);
+ Analysis.run();
}
-} // namespace clang
+} // namespace clang::lifetimes
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 5eba024e83634..89c5a3596f584 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3030,8 +3030,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
// TODO: Enable lifetime safety analysis for other languages once it is
// stable.
if (EnableLifetimeSafetyAnalysis && S.getLangOpts().CPlusPlus) {
- if (CFG *cfg = AC.getCFG())
- runLifetimeSafetyAnalysis(*cast<DeclContext>(D), *cfg, AC);
+ if (AC.getCFG())
+ lifetimes::runLifetimeSafetyAnalysis(AC);
}
// Check for violations of "called once" parameter properties.
if (S.getLangOpts().ObjC && !S.getLangOpts().CPlusPlus &&
diff --git a/clang/unittests/Analysis/CMakeLists.txt b/clang/unittests/Analysis/CMakeLists.txt
index 059a74843155c..52e7d2854633d 100644
--- a/clang/unittests/Analysis/CMakeLists.txt
+++ b/clang/unittests/Analysis/CMakeLists.txt
@@ -4,6 +4,7 @@ add_clang_unittest(ClangAnalysisTests
CloneDetectionTest.cpp
ExprMutationAnalyzerTest.cpp
IntervalPartitionTest.cpp
+ LifetimeSafetyTest.cpp
MacroExpansionContextTest.cpp
UnsafeBufferUsageTest.cpp
CLANG_LIBS
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
new file mode 100644
index 0000000000000..b01f0d5a7d44c
--- /dev/null
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -0,0 +1,231 @@
+//===- LifetimeSafetyTest.cpp - Lifetime Safety Tests -*---------- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Analyses/LifetimeSafety.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Testing/TestAST.h"
+#include "llvm/ADT/StringMap.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang::lifetimes::internal {
+namespace {
+
+using namespace ast_matchers;
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::Not;
+
+// A helper class to run the full lifetime analysis on a piece of code
+// and provide an interface for querying the results.
+class LifetimeTestRunner {
+public:
+ LifetimeTestRunner(llvm::StringRef Code) {
+ std::string FullCode = R"(
+ #define POINT(name) void("__lifetime_test_point_" #name)
+ struct MyObj { ~MyObj() {} int i; };
+ )";
+ FullCode += Code.str();
+
+ TestAST = std::make_unique<clang::TestAST>(FullCode);
+ ASTCtx = &TestAST->context();
+
+ // Find the target function using AST matchers.
+ auto MatchResult =
+ match(functionDecl(hasName("target")).bind("target"), *ASTCtx);
+ auto *FD = selectFirst<FunctionDecl>("target", MatchResult);
+ if (!FD) {
+ ADD_FAILURE() << "Test case must have a function named 'target'";
+ return;
+ }
+ AnalysisCtx = std::make_unique<AnalysisDeclContext>(nullptr, FD);
+ AnalysisCtx->getCFGBuildOptions().setAllAlwaysAdd();
+
+ // Run the main analysis.
+ Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx);
+ Analysis->run();
+
+ AnnotationToPointMap = Analysis->getTestPoints();
+ }
+
+ LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; }
+ ASTContext &getASTContext() { return *ASTCtx; }
+
+ ProgramPoint getProgramPoint(llvm::StringRef Annotation) {
+ auto It = AnnotationToPointMap.find(Annotation);
+ if (It == AnnotationToPointMap.end()) {
+ ADD_FAILURE() << "Annotation '" << Annotation << "' not found.";
+ return {nullptr, nullptr};
+ }
+ return It->second;
+ }
+
+private:
+ std::unique_ptr<TestAST> TestAST;
+ ASTContext *ASTCtx = nullptr;
+ std::unique_ptr<AnalysisDeclContext> AnalysisCtx;
+ std::unique_ptr<LifetimeSafetyAnalysis> Analysis;
+ llvm::StringMap<ProgramPoint> AnnotationToPointMap;
+};
+
+// A convenience wrapper that uses the LifetimeSafetyAnalysis public API.
+class LifetimeTestHelper {
+public:
+ LifetimeTestHelper(LifetimeTestRunner &Runner)
+ : Runner(Runner), Analysis(Runner.getAnalysis()) {}
+
+ OriginID getOriginForDecl(llvm::StringRef VarName) {
+ auto *VD = findDecl<ValueDecl>(VarName);
+ if (!VD)
+ return OriginID();
+ auto OID = Analysis.getOriginIDForDecl(VD);
+ if (!OID)
+ ADD_FAILURE() << "Origin for '" << VarName << "' not found.";
+ return OID.value_or(OriginID());
+ }
+
+ LoanID getLoanForVar(llvm::StringRef VarName) {
+ auto *VD = findDecl<VarDecl>(VarName);
+ if (!VD)
+ return LoanID();
+ auto LID = Analysis.getLoanIDForVar(VD);
+ if (!LID)
+ ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
+ return LID.value_or(LoanID());
+ }
+
+ LoanSet getLoansAtPoint(OriginID OID, llvm::StringRef Annotation) {
+ ProgramPoint PP = Runner.getProgramPoint(Annotation);
+ if (!PP.first)
+ return LoanSet{nullptr};
+ return Analysis.getLoansAtPoint(OID, PP);
+ }
+
+private:
+ template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
+ auto &Ctx = Runner.getASTContext();
+ auto Results = match(valueDecl(hasName(Name)).bind("v"), Ctx);
+ if (Results.empty()) {
+ ADD_FAILURE() << "Declaration '" << Name << "' not found in AST.";
+ return nullptr;
+ }
+ return const_cast<DeclT *>(selectFirst<DeclT>("v", Results));
+ }
+
+ LifetimeTestRunner &Runner;
+ LifetimeSafetyAnalysis &Analysis;
+};
+
+// ========================================================================= //
+// GTest Matchers & Fixture
+// ========================================================================= //
+
+// It holds the name of the origin variable and a reference to the helper.
+class OriginInfo {
+public:
+ OriginInfo(llvm::StringRef OriginVar, LifetimeTestHelper &Helper)
+ : OriginVar(OriginVar), Helper(Helper) {}
+ llvm::StringRef OriginVar;
+ LifetimeTestHelper &Helper;
+};
+
+/// Matcher that checks if an origin (identified by its variable name)
+/// contains a loan to a specific variable at a given program point (identified
+/// by an annotation).
+MATCHER_P2(ContainsLoanTo, LoanVar, Annotation, "") {
+ const OriginInfo &Info = arg;
+ OriginID OID = Info.Helper.getOriginForDecl(Info.OriginVar);
+ LoanID LID = Info.Helper.getLoanForVar(LoanVar);
+ LoanSet Loans = Info.Helper.getLoansAtPoint(OID, Annotation);
+
+ if (Loans.contains(LID))
+ return true;
+ *result_listener << "at point '" << static_cast<std::string>(Annotation)
+ << "', origin for '" << Info.OriginVar.str()
+ << "' does not contain a loan to '"
+ << static_cast<std::string>(LoanVar) << "'";
+ return false;
+}
+
+// Base test fixture to manage the runner and helper.
+class LifetimeAnalysisTest : public ::testing::Test {
+protected:
+ // Let subclasses provide the code to the runner.
+ void SetupTest(llvm::StringRef Code) {
+ Runner = std::make_unique<LifetimeTestRunner>(Code);
+ Helper = std::make_unique<LifetimeTestHelper>(*Runner);
+ }
+
+ // Wrapper to make tests more readable, using the fixture's helper.
+ OriginInfo Origin(llvm::StringRef OriginVar) {
+ return OriginInfo(OriginVar, *Helper);
+ }
+
+ std::unique_ptr<LifetimeTestRunner> Runner;
+ std::unique_ptr<LifetimeTestHelper> Helper;
+};
+
+// ========================================================================= //
+// TEST CASES
+// ========================================================================= //
+
+TEST_F(LifetimeAnalysisTest, SimpleLoanAndOrigin) {
+ SetupTest(R"(
+ void target() {
+ int x;
+ int* p = &x;
+ POINT(p1);
+ }
+ )");
+ EXPECT_THAT(Origin("p"), ContainsLoanTo("x", "p1"));
+}
+
+TEST_F(LifetimeAnalysisTest, OverwriteOrigin) {
+ SetupTest(R"(
+ void target() {
+ MyObj s1, s2;
+
+ MyObj* p = &s1;
+ POINT(after_s1);
+
+ p = &s2;
+ POINT(after_s2);
+ }
+ )");
+ EXPECT_THAT(Origin("p"), ContainsLoanTo("s1", "after_s1"));
+ EXPECT_THAT(Origin("p"), Not(ContainsLoanTo("s2", "after_s1")));
+
+ EXPECT_THAT(Origin("p"), Not(ContainsLoanTo("s1", "after_s2")));
+ EXPECT_THAT(Origin("p"), ContainsLoanTo("s2", "after_s2"));
+}
+
+TEST_F(LifetimeAnalysisTest, ConditionalLoan) {
+ SetupTest(R"(
+ void target(bool cond) {
+ int a, b;
+ int *p = nullptr;
+ if (cond) {
+ p = &a;
+ POINT(after_then);
+ } else {
+ p = &b;
+ POINT(after_else);
+ }
+ POINT(after_if);
+ }
+ )");
+ EXPECT_THAT(Origin("p"), AllOf(ContainsLoanTo("a", "after_then"),
+ Not(ContainsLoanTo("b", "after_then"))));
+ EXPECT_THAT(Origin("p"), AllOf(Not(ContainsLoanTo("a", "after_else")),
+ ContainsLoanTo("b", "after_else")));
+ EXPECT_THAT(Origin("p"), AllOf(ContainsLoanTo("a", "after_if"),
+ ContainsLoanTo("b", "after_if")));
+}
+} // anonymous namespace
+} // namespace clang::lifetimes::internal
More information about the cfe-commits
mailing list