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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits