[clang] 54b5068 - Revert "[LifetimeSafety] Revamp test suite using unittests (#149158)"
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 22 05:11:06 PDT 2025
Author: Utkarsh Saxena
Date: 2025-07-22T12:10:47Z
New Revision: 54b50681ca0fd1c0c6ddb059c88981a45e2f1b19
URL: https://github.com/llvm/llvm-project/commit/54b50681ca0fd1c0c6ddb059c88981a45e2f1b19
DIFF: https://github.com/llvm/llvm-project/commit/54b50681ca0fd1c0c6ddb059c88981a45e2f1b19.diff
LOG: Revert "[LifetimeSafety] Revamp test suite using unittests (#149158)"
This reverts commit 688ea048affe8e79221ea1a8c376bcf20ef8f3bb.
Added:
Modified:
clang/include/clang/Analysis/Analyses/LifetimeSafety.h
clang/lib/Analysis/LifetimeSafety.cpp
clang/lib/Sema/AnalysisBasedWarnings.cpp
clang/test/Sema/warn-lifetime-safety-dataflow.cpp
clang/unittests/Analysis/CMakeLists.txt
Removed:
clang/unittests/Analysis/LifetimeSafetyTest.cpp
################################################################################
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
index beeb0aaba5d0d..9998702a41cab 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h
@@ -17,96 +17,14 @@
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
+#include "clang/AST/DeclBase.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Analysis/CFG.h"
-#include "llvm/ADT/ImmutableSet.h"
-#include "llvm/ADT/StringMap.h"
-#include <memory>
+namespace clang {
-namespace clang::lifetimes {
+void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
+ AnalysisDeclContext &AC);
-/// The main entry point for the analysis.
-void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC);
-
-namespace internal {
-// Forward declarations of internal types.
-class Fact;
-class FactManager;
-class LoanPropagationAnalysis;
-struct LifetimeFactory;
-
-/// 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>;
-
-/// A `ProgramPoint` identifies a location in the CFG by pointing to a specific
-/// `Fact`. identified by a lifetime-related event (`Fact`).
-///
-/// A `ProgramPoint` has "after" semantics: it represents the location
-/// immediately after its corresponding `Fact`.
-using ProgramPoint = 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's for the loan created with the specific variable as
- /// their Path.
- std::vector<LoanID> getLoanIDForVar(const VarDecl *VD) const;
-
- /// Retrieves program points that were specially marked in the source code
- /// for testing.
- ///
- /// The analysis recognizes special function calls of the form
- /// `void("__lifetime_test_point_<name>")` as test points. This method returns
- /// a map from the annotation string (<name>) to the corresponding
- /// `ProgramPoint`. This allows test harnesses to query the analysis state at
- /// user-defined locations in the code.
- /// \note This is intended for testing only.
- 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
-} // namespace clang::lifetimes
+} // namespace clang
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_H
diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp
index ae6ec9f76cbf6..a95db6d8013bd 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -24,14 +24,8 @@
#include "llvm/Support/TimeProfiler.h"
#include <cstdint>
-namespace clang::lifetimes {
-namespace internal {
+namespace clang {
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.
@@ -42,6 +36,32 @@ 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 {
@@ -203,9 +223,7 @@ 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,
- /// A marker for a specific point in the code, for testing.
- TestPoint,
+ ReturnOfOrigin
};
private:
@@ -292,24 +310,6 @@ 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 {
- StringRef Annotation;
-
-public:
- static bool classof(const Fact *F) { return F->getKind() == Kind::TestPoint; }
-
- explicit TestPointFact(StringRef Annotation)
- : Fact(Kind::TestPoint), Annotation(Annotation) {}
-
- StringRef 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,7 +363,6 @@ class FactManager {
};
class FactGenerator : public ConstStmtVisitor<FactGenerator> {
- using Base = ConstStmtVisitor<FactGenerator>;
public:
FactGenerator(FactManager &FactMgr, AnalysisDeclContext &AC)
@@ -459,15 +458,6 @@ 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(); }
@@ -501,27 +491,6 @@ 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)) {
- StringRef Annotation = LiteralValue.drop_front(Prefix.length());
- CurrentBlockFacts.push_back(
- FactMgr.createFact<TestPointFact>(Annotation));
- return true;
- }
- }
- return false;
- }
-
FactManager &FactMgr;
AnalysisDeclContext &AC;
llvm::SmallVector<Fact *> CurrentBlockFacts;
@@ -668,8 +637,6 @@ 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");
}
@@ -679,16 +646,14 @@ 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>
-static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
- llvm::ImmutableSet<T> B,
- typename llvm::ImmutableSet<T>::Factory &F) {
+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)
@@ -701,7 +666,7 @@ static llvm::ImmutableSet<T> join(llvm::ImmutableSet<T> A,
// 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>
-static llvm::ImmutableMap<K, V>
+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())
@@ -725,6 +690,10 @@ 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
@@ -838,28 +807,17 @@ class LoanPropagationAnalysis
// - Modify origin liveness analysis to answer `bool isLive(Origin O, Point P)`
// - Using the above three to perform the final error reporting.
// ========================================================================= //
+} // anonymous namespace
-// ========================================================================= //
-// LifetimeSafetyAnalysis Class Implementation
-// ========================================================================= //
-
-// We need this here for unique_ptr with forward declared class.
-LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
-
-LifetimeSafetyAnalysis::LifetimeSafetyAnalysis(AnalysisDeclContext &AC)
- : AC(AC), Factory(std::make_unique<LifetimeFactory>()),
- FactMgr(std::make_unique<FactManager>()) {}
-
-void LifetimeSafetyAnalysis::run() {
+void runLifetimeSafetyAnalysis(const DeclContext &DC, const CFG &Cfg,
+ AnalysisDeclContext &AC) {
llvm::TimeTraceScope TimeProfile("LifetimeSafetyAnalysis");
-
- const CFG &Cfg = *AC.getCFG();
DEBUG_WITH_TYPE("PrintCFG", Cfg.dump(AC.getASTContext().getLangOpts(),
/*ShowColors=*/true));
-
- FactGenerator FactGen(*FactMgr, AC);
+ FactManager FactMgr;
+ 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.
@@ -870,56 +828,9 @@ void LifetimeSafetyAnalysis::run() {
/// 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.
- LoanPropagation =
- std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory);
- LoanPropagation->run();
-}
-
-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::vector<LoanID>
-LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const {
- assert(FactMgr && "FactManager not initialized");
- std::vector<LoanID> Result;
- for (const Loan &L : FactMgr->getLoanMgr().getLoans())
- if (L.Path.D == VD)
- Result.push_back(L.ID);
- return Result;
-}
-
-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>()) {
- StringRef PointName = TPF->getAnnotation();
- assert(AnnotationToPointMap.find(PointName) ==
- AnnotationToPointMap.end() &&
- "more than one test points with the same name");
- AnnotationToPointMap[PointName] = F;
- }
- }
- }
- return AnnotationToPointMap;
-}
-} // namespace internal
-
-void runLifetimeSafetyAnalysis(AnalysisDeclContext &AC) {
- internal::LifetimeSafetyAnalysis Analysis(AC);
- Analysis.run();
+ LifetimeFactory Factory;
+ LoanPropagationAnalysis LoanPropagation(Cfg, AC, FactMgr, Factory);
+ LoanPropagation.run();
+ DEBUG_WITH_TYPE("LifetimeLoanPropagation", LoanPropagation.dump());
}
-} // namespace clang::lifetimes
+} // namespace clang
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index d4d640bf988b1..d1400cbfc884d 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3029,8 +3029,8 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
// TODO: Enable lifetime safety analysis for other languages once it is
// stable.
if (EnableLifetimeSafetyAnalysis && S.getLangOpts().CPlusPlus) {
- if (AC.getCFG())
- lifetimes::runLifetimeSafetyAnalysis(AC);
+ if (CFG *cfg = AC.getCFG())
+ runLifetimeSafetyAnalysis(*cast<DeclContext>(D), *cfg, AC);
}
// Check for violations of "called once" parameter properties.
if (S.getLangOpts().ObjC && !S.getLangOpts().CPlusPlus &&
diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
index 0eb3bda918f82..0e98904ade86a 100644
--- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -mllvm -debug-only=LifetimeFacts -Wexperimental-lifetime-safety %s 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -mllvm -debug-only=LifetimeFacts,LifetimeLoanPropagation -Wexperimental-lifetime-safety %s 2>&1 | FileCheck %s
// REQUIRES: asserts
struct MyObj {
@@ -19,6 +19,10 @@ MyObj* return_local_addr() {
// CHECK: ReturnOfOrigin (OriginID: [[O_RET_VAL]])
// CHECK: Expire (LoanID: [[L_X]])
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_ADDR_X]] contains Loan [[L_X]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_X]]
+// CHECK-DAG: Origin [[O_RET_VAL]] contains Loan [[L_X]]
// Pointer Assignment and Return
@@ -43,6 +47,15 @@ MyObj* assign_and_return_local_addr() {
// CHECK: ReturnOfOrigin (OriginID: [[O_PTR2_RVAL_2]])
// CHECK: Expire (LoanID: [[L_Y]])
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_ADDR_Y]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR1]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR1_RVAL]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR1_RVAL_2]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2_RVAL]] contains Loan [[L_Y]]
+// CHECK-DAG: Origin [[O_PTR2_RVAL_2]] contains Loan [[L_Y]]
+
// Return of Non-Pointer Type
// CHECK-LABEL: Function: return_int_val
@@ -52,6 +65,8 @@ int return_int_val() {
return x;
}
// CHECK-NEXT: End of Block
+// CHECK: LoanPropagation results:
+// CHECK: <empty>
// Loan Expiration (Automatic Variable, C++)
@@ -64,6 +79,9 @@ void loan_expires_cpp() {
// CHECK: AssignOrigin (DestID: [[O_POBJ:[0-9]+]], SrcID: [[O_ADDR_OBJ]])
// CHECK: Expire (LoanID: [[L_OBJ]])
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_ADDR_OBJ]] contains Loan [[L_OBJ]]
+// CHECK-DAG: Origin [[O_POBJ]] contains Loan [[L_OBJ]]
// FIXME: No expire for Trivial Destructors
@@ -78,6 +96,10 @@ void loan_expires_trivial() {
// CHECK-NEXT: End of Block
// FIXME: Add check for Expire once trivial destructors are handled for expiration.
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_ADDR_TRIVIAL_OBJ]] contains Loan [[L_TRIVIAL_OBJ]]
+// CHECK-DAG: Origin [[O_PTOBJ]] contains Loan [[L_TRIVIAL_OBJ]]
+
// CHECK-LABEL: Function: conditional
void conditional(bool condition) {
@@ -97,6 +119,13 @@ void conditional(bool condition) {
// CHECK: AssignOrigin (DestID: [[O_P_RVAL:[0-9]+]], SrcID: [[O_P]])
// CHECK: AssignOrigin (DestID: [[O_Q:[0-9]+]], SrcID: [[O_P_RVAL]])
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_ADDR_A]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_ADDR_B]] contains Loan [[L_B]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_B]]
+// CHECK-DAG: Origin [[O_Q]] contains Loan [[L_A]]
+// CHECK-DAG: Origin [[O_Q]] contains Loan [[L_B]]
// CHECK-LABEL: Function: pointers_in_a_cycle
@@ -132,6 +161,25 @@ void pointers_in_a_cycle(bool condition) {
// CHECK: AssignOrigin (DestID: [[O_P3]], SrcID: [[O_TEMP_RVAL]])
}
}
+// At the end of the analysis, the origins for the pointers involved in the cycle
+// (p1, p2, p3, temp) should all contain the loans from v1, v2, and v3 at the fixed point.
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P1]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P2]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_P3]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_TEMP]] contains Loan [[L_V3]]
+// CHECK-DAG: Origin [[O_ADDR_V1]] contains Loan [[L_V1]]
+// CHECK-DAG: Origin [[O_ADDR_V2]] contains Loan [[L_V2]]
+// CHECK-DAG: Origin [[O_ADDR_V3]] contains Loan [[L_V3]]
+
// CHECK-LABEL: Function: overwrite_origin
void overwrite_origin() {
@@ -147,6 +195,10 @@ void overwrite_origin() {
// CHECK: Expire (LoanID: [[L_S2]])
// CHECK: Expire (LoanID: [[L_S1]])
}
+// CHECK: LoanPropagation results:
+// CHECK: Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-NOT: Origin [[O_P]] contains Loan [[L_S1]]
+
// CHECK-LABEL: Function: reassign_to_null
void reassign_to_null() {
@@ -161,6 +213,8 @@ void reassign_to_null() {
}
// FIXME: Have a better representation for nullptr than just an empty origin.
// It should be a separate loan and origin kind.
+// CHECK: LoanPropagation results:
+// CHECK: Origin [[O_P]] contains no loans
// CHECK-LABEL: Function: reassign_in_if
@@ -181,6 +235,11 @@ void reassign_in_if(bool condition) {
// CHECK: Expire (LoanID: [[L_S2]])
// CHECK: Expire (LoanID: [[L_S1]])
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
// CHECK-LABEL: Function: assign_in_switch
@@ -217,6 +276,14 @@ void assign_in_switch(int mode) {
// CHECK-DAG: Expire (LoanID: [[L_S2]])
// CHECK-DAG: Expire (LoanID: [[L_S1]])
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S3]]
+// CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_ADDR_S3]] contains Loan [[L_S3]]
+
// CHECK-LABEL: Function: loan_in_loop
void loan_in_loop(bool condition) {
@@ -232,6 +299,10 @@ void loan_in_loop(bool condition) {
// CHECK: Expire (LoanID: [[L_INNER]])
}
}
+// CHECK: LoanPropagation results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_INNER]]
+// CHECK-DAG: Origin [[O_ADDR_INNER]] contains Loan [[L_INNER]]
+
// CHECK-LABEL: Function: loop_with_break
void loop_with_break(int count) {
@@ -255,6 +326,13 @@ void loop_with_break(int count) {
// CHECK: Expire (LoanID: [[L_S1]])
}
+// CHECK-LABEL: LoanPropagation results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_S2]]
+// CHECK-DAG: Origin [[O_ADDR_S1]] contains Loan [[L_S1]]
+// CHECK-DAG: Origin [[O_ADDR_S2]] contains Loan [[L_S2]]
+
+
// CHECK-LABEL: Function: nested_scopes
void nested_scopes() {
MyObj* p = nullptr;
@@ -277,6 +355,13 @@ void nested_scopes() {
// CHECK: Expire (LoanID: [[L_OUTER]])
}
+// CHECK-LABEL: LoanPropagation results:
+// CHECK-DAG: Origin [[O_P]] contains Loan [[L_INNER]]
+// CHECK-DAG: Origin [[O_ADDR_INNER]] contains Loan [[L_INNER]]
+// CHECK-DAG: Origin [[O_ADDR_OUTER]] contains Loan [[L_OUTER]]
+// CHECK-NOT: Origin [[O_P]] contains Loan [[L_OUTER]]
+
+
// CHECK-LABEL: Function: pointer_indirection
void pointer_indirection() {
int a;
diff --git a/clang/unittests/Analysis/CMakeLists.txt b/clang/unittests/Analysis/CMakeLists.txt
index 52e7d2854633d..059a74843155c 100644
--- a/clang/unittests/Analysis/CMakeLists.txt
+++ b/clang/unittests/Analysis/CMakeLists.txt
@@ -4,7 +4,6 @@ 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
deleted file mode 100644
index af4d63a38211e..0000000000000
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ /dev/null
@@ -1,439 +0,0 @@
-//===- 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"
-#include <optional>
-#include <vector>
-
-namespace clang::lifetimes::internal {
-namespace {
-
-using namespace ast_matchers;
-using ::testing::UnorderedElementsAreArray;
-
-// 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;
- }
- 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()) {}
-
- std::optional<OriginID> getOriginForDecl(llvm::StringRef VarName) {
- auto *VD = findDecl<ValueDecl>(VarName);
- if (!VD)
- return std::nullopt;
- auto OID = Analysis.getOriginIDForDecl(VD);
- if (!OID)
- ADD_FAILURE() << "Origin for '" << VarName << "' not found.";
- return OID;
- }
-
- std::optional<LoanID> getLoanForVar(llvm::StringRef VarName) {
- auto *VD = findDecl<VarDecl>(VarName);
- if (!VD)
- return std::nullopt;
- std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD);
- if (LID.empty()) {
- ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
- return std::nullopt;
- }
- // TODO: Support retrieving more than one loans to a var.
- if (LID.size() > 1) {
- ADD_FAILURE() << "More than 1 loans found for '" << VarName;
- return std::nullopt;
- }
- return LID[0];
- }
-
- std::optional<LoanSet> getLoansAtPoint(OriginID OID,
- llvm::StringRef Annotation) {
- ProgramPoint PP = Runner.getProgramPoint(Annotation);
- if (!PP)
- return std::nullopt;
- 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 to verify the set of loans held by an origin at a specific
-/// program point.
-///
-/// This matcher is intended to be used with an \c OriginInfo object.
-///
-/// \param LoanVars A vector of strings, where each string is the name of a
-/// variable expected to be the source of a loan.
-/// \param Annotation A string identifying the program point (created with
-/// POINT()) where the check should be performed.
-MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
- const OriginInfo &Info = arg;
- std::optional<OriginID> OIDOpt = Info.Helper.getOriginForDecl(Info.OriginVar);
- if (!OIDOpt) {
- *result_listener << "could not find origin for '" << Info.OriginVar.str()
- << "'";
- return false;
- }
-
- std::optional<LoanSet> ActualLoansSetOpt =
- Info.Helper.getLoansAtPoint(*OIDOpt, Annotation);
- if (!ActualLoansSetOpt) {
- *result_listener << "could not get a valid loan set at point '"
- << Annotation << "'";
- return false;
- }
- std::vector<LoanID> ActualLoans(ActualLoansSetOpt->begin(),
- ActualLoansSetOpt->end());
-
- std::vector<LoanID> ExpectedLoans;
- for (const auto &LoanVar : LoanVars) {
- std::optional<LoanID> ExpectedLIDOpt = Info.Helper.getLoanForVar(LoanVar);
- if (!ExpectedLIDOpt) {
- *result_listener << "could not find loan for var '" << LoanVar << "'";
- return false;
- }
- ExpectedLoans.push_back(*ExpectedLIDOpt);
- }
-
- return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
- ActualLoans, result_listener);
-}
-
-// Base test fixture to manage the runner and helper.
-class LifetimeAnalysisTest : public ::testing::Test {
-protected:
- void SetupTest(llvm::StringRef Code) {
- Runner = std::make_unique<LifetimeTestRunner>(Code);
- Helper = std::make_unique<LifetimeTestHelper>(*Runner);
- }
-
- OriginInfo Origin(llvm::StringRef OriginVar) {
- return OriginInfo(OriginVar, *Helper);
- }
-
- // Factory function that hides the std::vector creation.
- auto HasLoansTo(std::initializer_list<std::string> LoanVars,
- const char *Annotation) {
- return HasLoansToImpl(std::vector<std::string>(LoanVars), Annotation);
- }
-
- std::unique_ptr<LifetimeTestRunner> Runner;
- std::unique_ptr<LifetimeTestHelper> Helper;
-};
-
-// ========================================================================= //
-// TESTS
-// ========================================================================= //
-
-TEST_F(LifetimeAnalysisTest, SimpleLoanAndOrigin) {
- SetupTest(R"(
- void target() {
- int x;
- int* p = &x;
- POINT(p1);
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"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"), HasLoansTo({"s1"}, "after_s1"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"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"), HasLoansTo({"a"}, "after_then"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"b"}, "after_else"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"a", "b"}, "after_if"));
-}
-
-TEST_F(LifetimeAnalysisTest, PointerChain) {
- SetupTest(R"(
- void target() {
- MyObj y;
- MyObj* ptr1 = &y;
- POINT(p1);
-
- MyObj* ptr2 = ptr1;
- POINT(p2);
-
- ptr2 = ptr1;
- POINT(p3);
-
- ptr2 = ptr2; // Self assignment
- POINT(p4);
- }
- )");
- EXPECT_THAT(Origin("ptr1"), HasLoansTo({"y"}, "p1"));
- EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p2"));
- EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p3"));
- EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p4"));
-}
-
-TEST_F(LifetimeAnalysisTest, ReassignToNull) {
- SetupTest(R"(
- void target() {
- MyObj s1;
- MyObj* p = &s1;
- POINT(before_null);
- p = nullptr;
- POINT(after_null);
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_null"));
- // After assigning to null, the origin for `p` should have no loans.
- EXPECT_THAT(Origin("p"), HasLoansTo({}, "after_null"));
-}
-
-TEST_F(LifetimeAnalysisTest, ReassignInIf) {
- SetupTest(R"(
- void target(bool condition) {
- MyObj s1, s2;
- MyObj* p = &s1;
- POINT(before_if);
- if (condition) {
- p = &s2;
- POINT(after_reassign);
- }
- POINT(after_if);
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_if"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_reassign"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_if"));
-}
-
-TEST_F(LifetimeAnalysisTest, AssignInSwitch) {
- SetupTest(R"(
- void target(int mode) {
- MyObj s1, s2, s3;
- MyObj* p = nullptr;
- switch (mode) {
- case 1:
- p = &s1;
- POINT(case1);
- break;
- case 2:
- p = &s2;
- POINT(case2);
- break;
- default:
- p = &s3;
- POINT(case3);
- break;
- }
- POINT(after_switch);
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "case1"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "case2"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"s3"}, "case3"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2", "s3"}, "after_switch"));
-}
-
-TEST_F(LifetimeAnalysisTest, LoanInLoop) {
- SetupTest(R"(
- void target(bool condition) {
- MyObj* p = nullptr;
- while (condition) {
- MyObj inner;
- p = &inner;
- POINT(in_loop);
- }
- POINT(after_loop);
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "in_loop"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_loop"));
-}
-
-TEST_F(LifetimeAnalysisTest, LoopWithBreak) {
- SetupTest(R"(
- void target(int count) {
- MyObj s1;
- MyObj s2;
- MyObj* p = &s1;
- POINT(before_loop);
- for (int i = 0; i < count; ++i) {
- if (i == 5) {
- p = &s2;
- POINT(inside_if);
- break;
- }
- POINT(after_if);
- }
- POINT(after_loop);
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_loop"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "inside_if"));
- // At the join point after if, s2 cannot make it to p without the if.
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_if"));
- // At the join point after the loop, p could hold a loan to s1 (if the loop
- // completed normally) or to s2 (if the loop was broken).
- EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_loop"));
-}
-
-TEST_F(LifetimeAnalysisTest, PointersInACycle) {
- SetupTest(R"(
- void target(bool condition) {
- MyObj v1, v2, v3;
- MyObj *p1 = &v1, *p2 = &v2, *p3 = &v3;
-
- POINT(before_while);
- while (condition) {
- MyObj* temp = p1;
- p1 = p2;
- p2 = p3;
- p3 = temp;
- }
- POINT(after_loop);
- }
- )");
- EXPECT_THAT(Origin("p1"), HasLoansTo({"v1"}, "before_while"));
- EXPECT_THAT(Origin("p2"), HasLoansTo({"v2"}, "before_while"));
- EXPECT_THAT(Origin("p3"), HasLoansTo({"v3"}, "before_while"));
-
- // At the fixed point after the loop, all pointers could point to any of
- // the three variables.
- EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
- EXPECT_THAT(Origin("p2"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
- EXPECT_THAT(Origin("p3"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
- EXPECT_THAT(Origin("temp"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
-}
-
-TEST_F(LifetimeAnalysisTest, NestedScopes) {
- SetupTest(R"(
- void target() {
- MyObj* p = nullptr;
- {
- MyObj outer;
- p = &outer;
- POINT(before_inner_scope);
- {
- MyObj inner;
- p = &inner;
- POINT(inside_inner_scope);
- } // inner expires
- POINT(after_inner_scope);
- } // outer expires
- }
- )");
- EXPECT_THAT(Origin("p"), HasLoansTo({"outer"}, "before_inner_scope"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "inside_inner_scope"));
- EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_inner_scope"));
-}
-
-} // anonymous namespace
-} // namespace clang::lifetimes::internal
More information about the cfe-commits
mailing list