https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/159991
>From c859c7eda2fcf5f35ab5cf094ad55cbe8715b0fb Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <u...@google.com> Date: Sun, 21 Sep 2025 16:30:28 +0000 Subject: [PATCH] liveness-based-lifetime-policy --- .../clang/Analysis/Analyses/LifetimeSafety.h | 9 +- clang/lib/Analysis/LifetimeSafety.cpp | 391 +++++++------- clang/test/Sema/warn-lifetime-safety.cpp | 141 +++-- .../unittests/Analysis/LifetimeSafetyTest.cpp | 501 +++++++++--------- 4 files changed, 580 insertions(+), 462 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h index 512cb76cd6349..2cc3fb3d69eb4 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety.h @@ -55,6 +55,7 @@ class Fact; class FactManager; class LoanPropagationAnalysis; class ExpiredLoansAnalysis; +class LiveOriginAnalysis; struct LifetimeFactory; /// A generic, type-safe wrapper for an ID, distinguished by its `Tag` type. @@ -89,6 +90,7 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, OriginID ID) { // TODO(opt): Consider using a bitset to represent the set of loans. using LoanSet = llvm::ImmutableSet<LoanID>; using OriginSet = llvm::ImmutableSet<OriginID>; +using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>; /// A `ProgramPoint` identifies a location in the CFG by pointing to a specific /// `Fact`. identified by a lifetime-related event (`Fact`). @@ -110,8 +112,9 @@ class LifetimeSafetyAnalysis { /// Returns the set of loans an origin holds at a specific program point. LoanSet getLoansAtPoint(OriginID OID, ProgramPoint PP) const; - /// Returns the set of loans that have expired at a specific program point. - std::vector<LoanID> getExpiredLoansAtPoint(ProgramPoint PP) const; + /// TODO:Document. + std::vector<std::pair<OriginID, Confidence>> + getLiveOriginsAtPoint(ProgramPoint PP) const; /// Finds the OriginID for a given declaration. /// Returns a null optional if not found. @@ -138,7 +141,7 @@ class LifetimeSafetyAnalysis { std::unique_ptr<LifetimeFactory> Factory; std::unique_ptr<FactManager> FactMgr; std::unique_ptr<LoanPropagationAnalysis> LoanPropagation; - std::unique_ptr<ExpiredLoansAnalysis> ExpiredLoans; + std::unique_ptr<LiveOriginAnalysis> LiveOrigins; }; } // namespace internal } // namespace clang::lifetimes diff --git a/clang/lib/Analysis/LifetimeSafety.cpp b/clang/lib/Analysis/LifetimeSafety.cpp index a8bbeb6af78e2..d21de2432f071 100644 --- a/clang/lib/Analysis/LifetimeSafety.cpp +++ b/clang/lib/Analysis/LifetimeSafety.cpp @@ -21,6 +21,7 @@ #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/TimeProfiler.h" #include <cstdint> #include <memory> @@ -901,19 +902,26 @@ class DataflowAnalysis { llvm::SmallBitVector Visited(Cfg.getNumBlockIDs() + 1); while (const CFGBlock *B = W.dequeue()) { - Lattice StateIn = getInState(B); + Lattice StateIn = *getInState(B); Lattice StateOut = transferBlock(B, StateIn); OutStates[B] = StateOut; - Visited.set(B->getBlockID()); for (const CFGBlock *AdjacentB : isForward() ? B->succs() : B->preds()) { if (!AdjacentB) continue; - Lattice OldInState = getInState(AdjacentB); - Lattice NewInState = D.join(OldInState, StateOut); + Lattice OldInState; + bool SawFirstTime = false; + Lattice NewInState; + if (const Lattice *In = getInState(AdjacentB)) { + OldInState = *In; + NewInState = D.join(OldInState, StateOut); + } else { + OldInState = D.getInitialState(); + SawFirstTime = true; + NewInState = StateOut; + } // Enqueue the adjacent block if its in-state has changed or if we have // never visited it. - if (!Visited.test(AdjacentB->getBlockID()) || - NewInState != OldInState) { + if (SawFirstTime || NewInState != OldInState) { InStates[AdjacentB] = NewInState; W.enqueueBlock(AdjacentB); } @@ -924,7 +932,12 @@ class DataflowAnalysis { protected: Lattice getState(ProgramPoint P) const { return PerPointStates.lookup(P); } - Lattice getInState(const CFGBlock *B) const { return InStates.lookup(B); } + const Lattice *getInState(const CFGBlock *B) const { + auto It = InStates.find(B); + if (It != InStates.end()) + return &It->second; + return nullptr; + } Lattice getOutState(const CFGBlock *B) const { return OutStates.lookup(B); } @@ -1023,22 +1036,26 @@ static bool isSubsetOf(const llvm::ImmutableSet<T> &A, // instead of the current AVL-tree-based ImmutableMap. template <typename K, typename V, typename Joiner> static llvm::ImmutableMap<K, V> -join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B, +join(const llvm::ImmutableMap<K, V> &A, const llvm::ImmutableMap<K, V> &B, typename llvm::ImmutableMap<K, V>::Factory &F, Joiner JoinValues) { if (A.getHeight() < B.getHeight()) - std::swap(A, B); + return join(B, A, F, JoinValues); // For each element in B, join it with the corresponding element in A // (or with an empty value if it doesn't exist in A). + llvm::ImmutableMap<K, V> Res = A; for (const auto &Entry : B) { const K &Key = Entry.first; const V &ValB = Entry.second; - if (const V *ValA = A.lookup(Key)) - A = F.add(A, Key, JoinValues(*ValA, ValB)); - else - A = F.add(A, Key, ValB); + Res = F.add(Res, Key, JoinValues(A.lookup(Key), &ValB)); } - return A; + for (const auto &Entry : A) { + const K &Key = Entry.first; + const V &ValA = Entry.second; + if (!B.contains(Key)) + Res = F.add(Res, Key, JoinValues(&ValA, nullptr)); + } + return Res; } } // namespace utils @@ -1046,21 +1063,6 @@ join(llvm::ImmutableMap<K, V> A, llvm::ImmutableMap<K, V> B, // Loan Propagation Analysis // ========================================================================= // -using OriginLoanMap = llvm::ImmutableMap<OriginID, LoanSet>; -using ExpiredLoanMap = llvm::ImmutableMap<LoanID, const ExpireFact *>; - -/// An object to hold the factories for immutable collections, ensuring -/// that all created states share the same underlying memory management. -struct LifetimeFactory { - llvm::BumpPtrAllocator Allocator; - OriginLoanMap::Factory OriginMapFactory = - OriginLoanMap::Factory(Allocator, /*canonicalize=*/false); - LoanSet::Factory LoanSetFactory = - LoanSet::Factory(Allocator, /*canonicalize=*/false); - ExpiredLoanMap::Factory ExpiredLoanMapFactory = - ExpiredLoanMap::Factory(Allocator, /*canonicalize=*/false); -}; - /// Represents the dataflow lattice for loan propagation. /// /// This lattice tracks which loans each origin may hold at a given program @@ -1104,10 +1106,10 @@ class LoanPropagationAnalysis public: LoanPropagationAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - LifetimeFactory &LFactory) - : DataflowAnalysis(C, AC, F), - OriginLoanMapFactory(LFactory.OriginMapFactory), - LoanSetFactory(LFactory.LoanSetFactory) {} + OriginLoanMap::Factory &OriginLoanMapFactory, + LoanSet::Factory &LoanSetFactory) + : DataflowAnalysis(C, AC, F), OriginLoanMapFactory(OriginLoanMapFactory), + LoanSetFactory(LoanSetFactory) {} using Base::transfer; @@ -1120,8 +1122,13 @@ class LoanPropagationAnalysis Lattice join(Lattice A, Lattice B) { OriginLoanMap JoinedOrigins = utils::join(A.Origins, B.Origins, OriginLoanMapFactory, - [&](LoanSet S1, LoanSet S2) { - return utils::join(S1, S2, LoanSetFactory); + [&](const LoanSet *S1, const LoanSet *S2) { + assert((S1 || S2) && "unexpectedly merging 2 empty sets"); + if (!S1) + return *S2; + if (!S2) + return *S1; + return utils::join(*S1, *S2, LoanSetFactory); }); return Lattice(JoinedOrigins); } @@ -1171,96 +1178,145 @@ class LoanPropagationAnalysis }; // ========================================================================= // -// Expired Loans Analysis +// Live Origins Analysis // ========================================================================= // -/// The dataflow lattice for tracking the set of expired loans. -struct ExpiredLattice { - /// Map from an expired `LoanID` to the `ExpireFact` that made it expire. - ExpiredLoanMap Expired; +/// Information about why an origin is live at a program point. +struct LivenessInfo { + // TODO: Doc. + const UseFact *CausingUseFact; + // TODO: Doc. + Confidence ConfidenceLevel; + + LivenessInfo() : CausingUseFact(nullptr), ConfidenceLevel(Confidence::None) {} + LivenessInfo(const UseFact *UF, Confidence C) + : CausingUseFact(UF), ConfidenceLevel(C) {} + + bool operator==(const LivenessInfo &Other) const { + return CausingUseFact == Other.CausingUseFact && + ConfidenceLevel == Other.ConfidenceLevel; + } + bool operator!=(const LivenessInfo &Other) const { return !(*this == Other); } + + void Profile(llvm::FoldingSetNodeID &IDBuilder) const { + IDBuilder.AddPointer(CausingUseFact); + IDBuilder.Add(ConfidenceLevel); + } +}; + +using LivenessMap = llvm::ImmutableMap<OriginID, LivenessInfo>; + +/// The dataflow lattice for origin liveness analysis. +/// It tracks which origins are live, why they're live (which UseFact), +/// and the confidence level of that liveness. +struct LivenessLattice { + LivenessMap LiveOrigins; - ExpiredLattice() : Expired(nullptr) {}; - explicit ExpiredLattice(ExpiredLoanMap M) : Expired(M) {} + LivenessLattice() : LiveOrigins(nullptr) {}; - bool operator==(const ExpiredLattice &Other) const { - return Expired == Other.Expired; + explicit LivenessLattice(LivenessMap L) : LiveOrigins(L) {} + + bool operator==(const LivenessLattice &Other) const { + return LiveOrigins == Other.LiveOrigins; } - bool operator!=(const ExpiredLattice &Other) const { + + bool operator!=(const LivenessLattice &Other) const { return !(*this == Other); } void dump(llvm::raw_ostream &OS) const { - OS << "ExpiredLattice State:\n"; - if (Expired.isEmpty()) + OS << "LivenessLattice State:\n"; + if (LiveOrigins.isEmpty()) OS << " <empty>\n"; - for (const auto &[ID, _] : Expired) - OS << " Loan " << ID << " is expired\n"; + for (const auto &Entry : LiveOrigins) { + OriginID OID = Entry.first; + const LivenessInfo &Info = Entry.second; + OS << " Origin " << OID << " is "; + switch (Info.ConfidenceLevel) { + case Confidence::Definite: + OS << "definitely"; + break; + case Confidence::Maybe: + OS << "maybe"; + break; + case Confidence::None: + llvm_unreachable("liveness condidence should not be none."); + } + OS << " live at this point\n"; + } } }; -/// The analysis that tracks which loans have expired. -class ExpiredLoansAnalysis - : public DataflowAnalysis<ExpiredLoansAnalysis, ExpiredLattice, - Direction::Forward> { - - ExpiredLoanMap::Factory &Factory; +/// The analysis that tracks which origins are live, with granular information +/// about the causing use fact and confidence level. This is a backward +/// analysis. +class LiveOriginAnalysis + : public DataflowAnalysis<LiveOriginAnalysis, LivenessLattice, + Direction::Backward> { + FactManager &FactMgr; + LivenessMap::Factory &Factory; public: - ExpiredLoansAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, - LifetimeFactory &Factory) - : DataflowAnalysis(C, AC, F), Factory(Factory.ExpiredLoanMapFactory) {} - - using Base::transfer; + LiveOriginAnalysis(const CFG &C, AnalysisDeclContext &AC, FactManager &F, + LivenessMap::Factory &SF) + : DataflowAnalysis(C, AC, F), FactMgr(F), Factory(SF) {} + using DataflowAnalysis<LiveOriginAnalysis, Lattice, + Direction::Backward>::transfer; - StringRef getAnalysisName() const { return "ExpiredLoans"; } + StringRef getAnalysisName() const { return "LiveOrigins"; } Lattice getInitialState() { return Lattice(Factory.getEmptyMap()); } - /// Merges two lattices by taking the union of the two expired loans. - Lattice join(Lattice L1, Lattice L2) { - return Lattice( - utils::join(L1.Expired, L2.Expired, Factory, - // Take the last expiry fact to make this hermetic. - [](const ExpireFact *F1, const ExpireFact *F2) { - return F1->getExpiryLoc() > F2->getExpiryLoc() ? F1 : F2; - })); - } - - Lattice transfer(Lattice In, const ExpireFact &F) { - return Lattice(Factory.add(In.Expired, F.getLoanID(), &F)); - } - - // Removes the loan from the set of expired loans. - // - // When a loan is re-issued (e.g., in a loop), it is no longer considered - // expired. A loan can be in the expired set at the point of issue due to - // the dataflow state from a previous loop iteration being propagated along - // a backedge in the CFG. - // - // Note: This has a subtle false-negative though where a loan from previous - // iteration is not overwritten by a reissue. This needs careful tracking - // of loans "across iterations" which can be considered for future - // enhancements. - // - // void foo(int safe) { - // int* p = &safe; - // int* q = &safe; - // while (condition()) { - // int x = 1; - // p = &x; // A loan to 'x' is issued to 'p' in every iteration. - // if (condition()) { - // q = p; - // } - // (void)*p; // OK — 'p' points to 'x' from new iteration. - // (void)*q; // UaF - 'q' still points to 'x' from previous iteration - // // which is now destroyed. - // } - // } - Lattice transfer(Lattice In, const IssueFact &F) { - return Lattice(Factory.remove(In.Expired, F.getLoanID())); + /// Merges two lattices by combining liveness information. + /// When the same origin has different confidence levels, we take the lower + /// one. + Lattice join(Lattice L1, Lattice L2) const { + LivenessMap Merged = L1.LiveOrigins; + auto CombineConfidence = [](Confidence C1, Confidence C2) -> Confidence { + if (C1 == Confidence::Definite && C2 == Confidence::Definite) + return Confidence::Definite; + return Confidence::Maybe; + }; + auto CombineUseFact = [](const LivenessInfo &A, + const LivenessInfo &B) -> const UseFact * { + return A.ConfidenceLevel >= B.ConfidenceLevel ? A.CausingUseFact + : B.CausingUseFact; + }; + return Lattice(utils::join( + L1.LiveOrigins, L2.LiveOrigins, Factory, + [&](const LivenessInfo *L1, const LivenessInfo *L2) -> LivenessInfo { + assert((L1 || L2) && "unexpectedly merging 2 empty sets"); + if (!L1) + return LivenessInfo(L2->CausingUseFact, Confidence::Maybe); + if (!L2) + return LivenessInfo(L1->CausingUseFact, Confidence::Maybe); + return LivenessInfo( + CombineUseFact(*L1, *L2), + CombineConfidence(L1->ConfidenceLevel, L2->ConfidenceLevel)); + })); + } + + /// TODO:Document. + Lattice transfer(Lattice In, const UseFact &UF) { + OriginID OID = UF.getUsedOrigin(FactMgr.getOriginMgr()); + // Write kills liveness. + if (UF.isWritten()) + return Lattice(Factory.remove(In.LiveOrigins, OID)); + // Read makes origin live with definite confidence (dominates this point). + LivenessInfo Info(&UF, Confidence::Definite); + return Lattice(Factory.add(In.LiveOrigins, OID, Info)); + } + + /// Issuing a new loan to an origin kills its liveness. + Lattice transfer(Lattice In, const IssueFact &IF) { + return Lattice(Factory.remove(In.LiveOrigins, IF.getOriginID())); } - ExpiredLoanMap getExpiredLoans(ProgramPoint P) { return getState(P).Expired; } + Lattice transfer(Lattice In, const KillOriginFact &KF) { + return Lattice(Factory.remove(In.LiveOrigins, KF.getOriginID())); + } + + LivenessMap getLiveOrigins(ProgramPoint P) { return getState(P).LiveOrigins; } }; // ========================================================================= // @@ -1278,84 +1334,49 @@ class LifetimeChecker { private: llvm::DenseMap<LoanID, PendingWarning> FinalWarningsMap; LoanPropagationAnalysis &LoanPropagation; - ExpiredLoansAnalysis &ExpiredLoans; + LiveOriginAnalysis &LiveOrigins; FactManager &FactMgr; AnalysisDeclContext &ADC; LifetimeSafetyReporter *Reporter; public: - LifetimeChecker(LoanPropagationAnalysis &LPA, ExpiredLoansAnalysis &ELA, + LifetimeChecker(LoanPropagationAnalysis &LPA, LiveOriginAnalysis &LOA, FactManager &FM, AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter) - : LoanPropagation(LPA), ExpiredLoans(ELA), FactMgr(FM), ADC(ADC), + : LoanPropagation(LPA), LiveOrigins(LOA), FactMgr(FM), ADC(ADC), Reporter(Reporter) {} void run() { llvm::TimeTraceScope TimeProfile("LifetimeChecker"); for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) for (const Fact *F : FactMgr.getFacts(B)) - if (const auto *UF = F->getAs<UseFact>()) - checkUse(UF); + if (const auto *EF = F->getAs<ExpireFact>()) + checkExpiry(EF); issuePendingWarnings(); } - /// Checks for use-after-free errors for a given use of an Origin. - /// - /// This method is called for each 'UseFact' identified in the control flow - /// graph. It determines if the loans held by the used origin have expired - /// at the point of use. - void checkUse(const UseFact *UF) { - if (UF->isWritten()) - return; - OriginID O = UF->getUsedOrigin(FactMgr.getOriginMgr()); - - // Get the set of loans that the origin might hold at this program point. - LoanSet HeldLoans = LoanPropagation.getLoans(O, UF); - - // Get the set of all loans that have expired at this program point. - ExpiredLoanMap AllExpiredLoans = ExpiredLoans.getExpiredLoans(UF); - - // If the pointer holds no loans or no loans have expired, there's nothing - // to check. - if (HeldLoans.isEmpty() || AllExpiredLoans.isEmpty()) - return; - - // Identify loans that which have expired but are held by the pointer. Using - // them is a use-after-free. - llvm::SmallVector<LoanID> DefaultedLoans; - // A definite UaF error occurs if all loans the origin might hold have - // expired. - bool IsDefiniteError = true; - for (LoanID L : HeldLoans) { - if (AllExpiredLoans.contains(L)) - DefaultedLoans.push_back(L); - else - // If at least one loan is not expired, this use is not a definite UaF. - IsDefiniteError = false; - } - // If there are no defaulted loans, the use is safe. - if (DefaultedLoans.empty()) - return; - - // Determine the confidence level of the error (definite or maybe). - Confidence CurrentConfidence = - IsDefiniteError ? Confidence::Definite : Confidence::Maybe; - - // For each expired loan, create a pending warning. - for (LoanID DefaultedLoan : DefaultedLoans) { - // If we already have a warning for this loan with a higher or equal - // confidence, skip this one. - if (FinalWarningsMap.count(DefaultedLoan) && - CurrentConfidence <= FinalWarningsMap[DefaultedLoan].ConfidenceLevel) + void checkExpiry(const ExpireFact *EF) { + LoanID ExpiredLoan = EF->getLoanID(); + LivenessMap Origins = LiveOrigins.getLiveOrigins(EF); + Confidence CurConfidence = Confidence::None; + const UseFact *BadUse = nullptr; + for (auto &[OID, Info] : Origins) { + LoanSet HeldLoans = LoanPropagation.getLoans(OID, EF); + if (!HeldLoans.contains(ExpiredLoan)) continue; - - auto *EF = AllExpiredLoans.lookup(DefaultedLoan); - assert(EF && "Could not find ExpireFact for an expired loan."); - - FinalWarningsMap[DefaultedLoan] = {/*ExpiryLoc=*/(*EF)->getExpiryLoc(), - /*UseExpr=*/UF->getUseExpr(), - /*ConfidenceLevel=*/CurrentConfidence}; + // Loan is defaulted. + if (CurConfidence < Info.ConfidenceLevel) { + CurConfidence = Info.ConfidenceLevel; + BadUse = Info.CausingUseFact; + } } + Confidence LastConf = FinalWarningsMap.lookup(ExpiredLoan).ConfidenceLevel; + if (LastConf >= CurConfidence) + return; + // We have a use-after-free. + FinalWarningsMap[ExpiredLoan] = {/*ExpiryLoc=*/EF->getExpiryLoc(), + /*UseExpr=*/BadUse->getUseExpr(), + /*ConfidenceLevel=*/CurConfidence}; } void issuePendingWarnings() { @@ -1374,6 +1395,18 @@ class LifetimeChecker { // LifetimeSafetyAnalysis Class Implementation // ========================================================================= // +/// An object to hold the factories for immutable collections, ensuring +/// that all created states share the same underlying memory management. +struct LifetimeFactory { + llvm::BumpPtrAllocator Allocator; + OriginLoanMap::Factory OriginMapFactory = + OriginLoanMap::Factory(Allocator, /*canonicalize=*/false); + LoanSet::Factory LoanSetFactory = + LoanSet::Factory(Allocator, /*canonicalize=*/false); + LivenessMap::Factory LivenessMapFactory = + LivenessMap::Factory(Allocator, /*canonicalize=*/false); +}; + // We need this here for unique_ptr with forward declared class. LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default; @@ -1404,15 +1437,15 @@ void LifetimeSafetyAnalysis::run() { /// the analysis. /// 3. Collapse ExpireFacts belonging to same source location into a single /// Fact. - LoanPropagation = - std::make_unique<LoanPropagationAnalysis>(Cfg, AC, *FactMgr, *Factory); + LoanPropagation = std::make_unique<LoanPropagationAnalysis>( + Cfg, AC, *FactMgr, Factory->OriginMapFactory, Factory->LoanSetFactory); LoanPropagation->run(); - ExpiredLoans = - std::make_unique<ExpiredLoansAnalysis>(Cfg, AC, *FactMgr, *Factory); - ExpiredLoans->run(); + LiveOrigins = std::make_unique<LiveOriginAnalysis>( + Cfg, AC, *FactMgr, Factory->LivenessMapFactory); + LiveOrigins->run(); - LifetimeChecker Checker(*LoanPropagation, *ExpiredLoans, *FactMgr, AC, + LifetimeChecker Checker(*LoanPropagation, *LiveOrigins, *FactMgr, AC, Reporter); Checker.run(); } @@ -1423,15 +1456,6 @@ LoanSet LifetimeSafetyAnalysis::getLoansAtPoint(OriginID OID, return LoanPropagation->getLoans(OID, PP); } -std::vector<LoanID> -LifetimeSafetyAnalysis::getExpiredLoansAtPoint(ProgramPoint PP) const { - assert(ExpiredLoans && "ExpiredLoansAnalysis has not been run."); - std::vector<LoanID> Result; - for (const auto &pair : ExpiredLoans->getExpiredLoans(PP)) - Result.push_back(pair.first); - return Result; -} - std::optional<OriginID> LifetimeSafetyAnalysis::getOriginIDForDecl(const ValueDecl *D) const { assert(FactMgr && "FactManager not initialized"); @@ -1451,6 +1475,15 @@ LifetimeSafetyAnalysis::getLoanIDForVar(const VarDecl *VD) const { return Result; } +std::vector<std::pair<OriginID, Confidence>> +LifetimeSafetyAnalysis::getLiveOriginsAtPoint(ProgramPoint PP) const { + assert(LiveOrigins && "LiveOriginAnalysis has not been run."); + std::vector<std::pair<OriginID, Confidence>> Result; + for (auto &[OID, Info] : LiveOrigins->getLiveOrigins(PP)) + Result.push_back({OID, Info.ConfidenceLevel}); + return Result; +} + llvm::StringMap<ProgramPoint> LifetimeSafetyAnalysis::getTestPoints() const { assert(FactMgr && "FactManager not initialized"); llvm::StringMap<ProgramPoint> AnnotationToPointMap; diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 0cfd69b68ec55..3e09052c5f121 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -126,11 +126,15 @@ void definite_single_pointer_multiple_loans_gsl(bool cond) { v.use(); // expected-note 2 {{later used here}} } - -//===----------------------------------------------------------------------===// -// Potential (Maybe) Use-After-Free (-W...strict) -// These are cases where the pointer *may* become dangling, depending on the path taken. -//===----------------------------------------------------------------------===// +void definite_if_branch(bool cond) { + MyObj safe; + MyObj* p = &safe; + if (cond) { + MyObj temp; + p = &temp; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)*p; // expected-note {{later used here}} +} void potential_if_branch(bool cond) { MyObj safe; @@ -139,15 +143,18 @@ void potential_if_branch(bool cond) { MyObj temp; p = &temp; // expected-warning {{object whose reference is captured may not live long enough}} } // expected-note {{destroyed here}} - (void)*p; // expected-note {{later used here}} + if (!cond) + (void)*p; // expected-note {{later used here}} + else + p = &safe; } -void potential_if_branch_gsl(bool cond) { +void definite_if_branch_gsl(bool cond) { MyObj safe; View v = safe; if (cond) { MyObj temp; - v = temp; // expected-warning {{object whose reference is captured may not live long enough}} + v = temp; // expected-warning {{object whose reference is captured does not live long enough}} } // expected-note {{destroyed here}} v.use(); // expected-note {{later used here}} } @@ -159,13 +166,14 @@ void definite_potential_together(bool cond) { { MyObj s; - p_definite = &s; // expected-warning {{does not live long enough}} - if (cond) { - p_maybe = &s; // expected-warning {{may not live long enough}} - } - } // expected-note 2 {{destroyed here}} - (void)*p_definite; // expected-note {{later used here}} - (void)*p_maybe; // expected-note {{later used here}} + if (cond) + p_definite = &s; // expected-warning {{does not live long enough}} + if (cond) + p_maybe = &s; // expected-warning {{may not live long enough}} + } // expected-note 2 {{destroyed here}} + (void)*p_definite; // expected-note {{later used here}} + if (!cond) + (void)*p_maybe; // expected-note {{later used here}} } void definite_overrides_potential(bool cond) { @@ -189,10 +197,19 @@ void definite_overrides_potential(bool cond) { (void)*q; } - -//===----------------------------------------------------------------------===// -// Control Flow Tests -//===----------------------------------------------------------------------===// +void potential_due_to_conditional_killing(bool cond) { + MyObj safe; + MyObj* q; + { + MyObj s; + q = &s; // expected-warning {{may not live long enough}} + } // expected-note {{destroyed here}} + if (cond) { + // 'q' is conditionally "rescued". 'p' is not. + q = &safe; + } + (void)*q; // expected-note {{later used here}} +} void potential_for_loop_use_after_loop_body(MyObj safe) { MyObj* p = &safe; @@ -216,33 +233,33 @@ void potential_for_loop_gsl() { void potential_for_loop_use_before_loop_body(MyObj safe) { MyObj* p = &safe; for (int i = 0; i < 1; ++i) { - (void)*p; // expected-note {{later used here}} + (void)*p; MyObj s; - p = &s; // expected-warning {{may not live long enough}} + p = &s; // expected-warning {{does not live long enough}} } // expected-note {{destroyed here}} - (void)*p; + (void)*p; // expected-note {{later used here}} } -void potential_loop_with_break(bool cond) { +void definite_loop_with_break(bool cond) { MyObj safe; MyObj* p = &safe; for (int i = 0; i < 10; ++i) { if (cond) { MyObj temp; - p = &temp; // expected-warning {{may not live long enough}} + p = &temp; // expected-warning {{does not live long enough}} break; // expected-note {{destroyed here}} } } (void)*p; // expected-note {{later used here}} } -void potential_loop_with_break_gsl(bool cond) { +void definite_loop_with_break_gsl(bool cond) { MyObj safe; View v = safe; for (int i = 0; i < 10; ++i) { if (cond) { MyObj temp; - v = temp; // expected-warning {{object whose reference is captured may not live long enough}} + v = temp; // expected-warning {{object whose reference is captured does not live long enough}} break; // expected-note {{destroyed here}} } } @@ -250,37 +267,52 @@ void potential_loop_with_break_gsl(bool cond) { } void potential_multiple_expiry_of_same_loan(bool cond) { - // Choose the last expiry location for the loan. + // Choose the last expiry location for the loan (e.g., through scope-ends and break statements). MyObj safe; MyObj* p = &safe; for (int i = 0; i < 10; ++i) { MyObj unsafe; if (cond) { - p = &unsafe; // expected-warning {{may not live long enough}} - break; + p = &unsafe; // expected-warning {{does not live long enough}} + break; // expected-note {{destroyed here}} } - } // expected-note {{destroyed here}} + } (void)*p; // expected-note {{later used here}} p = &safe; for (int i = 0; i < 10; ++i) { MyObj unsafe; if (cond) { - p = &unsafe; // expected-warning {{may not live long enough}} + p = &unsafe; // expected-warning {{does not live long enough}} if (cond) - break; + break; // expected-note {{destroyed here}} } - } // expected-note {{destroyed here}} + } (void)*p; // expected-note {{later used here}} p = &safe; for (int i = 0; i < 10; ++i) { if (cond) { MyObj unsafe2; - p = &unsafe2; // expected-warning {{may not live long enough}} + p = &unsafe2; // expected-warning {{does not live long enough}} break; // expected-note {{destroyed here}} } } + + // TODO: This can be argued to be a "maybe" warning. This is because + // we only check for confidence of liveness and not the confidence of + // the loan contained in an origin. To deal with this, we can introduce + // a confidence in loan propagation analysis as well like liveness. + (void)*p; // expected-note {{later used here}} + + p = &safe; + for (int i = 0; i < 10; ++i) { + MyObj unsafe; + if (cond) + p = &unsafe; // expected-warning {{does not live long enough}} + if (cond) + break; // expected-note {{destroyed here}} + } (void)*p; // expected-note {{later used here}} } @@ -298,13 +330,14 @@ void potential_switch(int mode) { break; } } - (void)*p; // expected-note {{later used here}} + if (mode == 2) + (void)*p; // expected-note {{later used here}} } void definite_switch(int mode) { MyObj safe; MyObj* p = &safe; - // All cases are UaF --> Definite error. + // A use domintates all the loan expires --> all definite error. switch (mode) { case 1: { MyObj temp1; @@ -347,6 +380,21 @@ void definite_switch_gsl(int mode) { v.use(); // expected-note 3 {{later used here}} } +void loan_from_previous_iteration(MyObj safe, bool condition) { + MyObj* p = &safe; + MyObj* q = &safe; + + while (condition) { + MyObj x; + p = &x; // expected-warning {{may not live long enough}} + + if (condition) + q = p; + (void)*p; + (void)*q; // expected-note {{later used here}} + } // expected-note {{destroyed here}} +} + //===----------------------------------------------------------------------===// // No-Error Cases //===----------------------------------------------------------------------===// @@ -372,6 +420,19 @@ void no_error_if_dangle_then_rescue_gsl() { v.use(); // This is safe. } +void no_error_loan_from_current_iteration(bool cond) { + // See https://github.com/llvm/llvm-project/issues/156959. + MyObj b; + while (cond) { + MyObj a; + View p = b; + if (cond) { + p = a; + } + (void)p; + } +} + //===----------------------------------------------------------------------===// // Lifetimebound Attribute Tests @@ -415,9 +476,9 @@ void lifetimebound_multiple_args_potential(bool cond) { MyObj obj1; if (cond) { MyObj obj2; - v = Choose(true, - obj1, // expected-warning {{object whose reference is captured may not live long enough}} - obj2); // expected-warning {{object whose reference is captured may not live long enough}} + v = Choose(true, + obj1, // expected-warning {{object whose reference is captured does not live long enough}} + obj2); // expected-warning {{object whose reference is captured does not live long enough}} } // expected-note {{destroyed here}} } // expected-note {{destroyed here}} v.use(); // expected-note 2 {{later used here}} @@ -488,7 +549,7 @@ void lifetimebound_partial_safety(bool cond) { MyObj temp_obj; v = Choose(true, safe_obj, - temp_obj); // expected-warning {{object whose reference is captured may not live long enough}} + temp_obj); // expected-warning {{object whose reference is captured does not live long enough}} } // expected-note {{destroyed here}} v.use(); // expected-note {{later used here}} } diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 3821015f07fb1..41783f7c99352 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -126,12 +126,12 @@ class LifetimeTestHelper { return Analysis.getLoansAtPoint(OID, PP); } - std::optional<std::vector<LoanID>> - getExpiredLoansAtPoint(llvm::StringRef Annotation) { + std::optional<std::vector<std::pair<OriginID, Confidence>>> + getLiveOriginsAtPoint(llvm::StringRef Annotation) { ProgramPoint PP = Runner.getProgramPoint(Annotation); if (!PP) return std::nullopt; - return Analysis.getExpiredLoansAtPoint(PP); + return Analysis.getLiveOriginsAtPoint(PP); } private: @@ -180,6 +180,15 @@ class OriginInfo { LifetimeTestHelper &Helper; }; +// A helper class to represent a set of origins, identified by variable names. +class OriginsInfo { +public: + OriginsInfo(const std::vector<std::string> &Vars, LifetimeTestHelper &H) + : OriginVars(Vars), Helper(H) {} + std::vector<std::string> OriginVars; + LifetimeTestHelper &Helper; +}; + /// Matcher to verify the set of loans held by an origin at a specific /// program point. /// @@ -221,14 +230,15 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") { std::sort(ExpectedLoans.begin(), ExpectedLoans.end()); std::sort(ActualLoans.begin(), ActualLoans.end()); if (ExpectedLoans != ActualLoans) { - *result_listener << "Expected: "; + *result_listener << "Expected: {"; for (const auto &LoanID : ExpectedLoans) { *result_listener << LoanID.Value << ", "; } - *result_listener << "Actual: "; + *result_listener << "} Actual: {"; for (const auto &LoanID : ActualLoans) { *result_listener << LoanID.Value << ", "; } + *result_listener << "}"; return false; } @@ -236,32 +246,71 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") { ActualLoans, result_listener); } -/// Matcher to verify that the complete set of expired loans at a program point -/// matches the expected loan set. -MATCHER_P(AreExpiredAt, Annotation, "") { - const LoanSetInfo &Info = arg; - auto &Helper = Info.Helper; +enum class ConfidenceFilter { Maybe, Definite, All }; - auto ActualExpiredSetOpt = Helper.getExpiredLoansAtPoint(Annotation); - if (!ActualExpiredSetOpt) { - *result_listener << "could not get a valid expired loan set at point '" +/// Matcher to verify the complete set of live origins at a program point. +MATCHER_P2(AreLiveAtImpl, Annotation, ConfFilter, "") { + const OriginsInfo &Info = arg; + auto &Helper = Info.Helper; + auto ActualLiveSetOpt = Helper.getLiveOriginsAtPoint(Annotation); + if (!ActualLiveSetOpt) { + *result_listener << "could not get a valid live origin set at point '" << Annotation << "'"; return false; } - std::vector<LoanID> ActualExpiredLoans = *ActualExpiredSetOpt; - std::vector<LoanID> ExpectedExpiredLoans; - for (const auto &VarName : Info.LoanVars) { - auto LoanIDs = Helper.getLoansForVar(VarName); - if (LoanIDs.empty()) { - *result_listener << "could not find a loan for variable '" << VarName + std::vector<OriginID> ActualLiveOrigins; + for (const auto [OID, ActualConfidence] : ActualLiveSetOpt.value()) { + if (ConfFilter == ConfidenceFilter::All) + ActualLiveOrigins.push_back(OID); + if (ActualConfidence == Confidence::Maybe && + ConfFilter == ConfidenceFilter::Maybe) + ActualLiveOrigins.push_back(OID); + if (ActualConfidence == Confidence::Definite && + ConfFilter == ConfidenceFilter::Definite) + ActualLiveOrigins.push_back(OID); + } + + std::vector<OriginID> ExpectedLiveOrigins; + for (const auto &VarName : Info.OriginVars) { + auto OriginIDOpt = Helper.getOriginForDecl(VarName); + if (!OriginIDOpt) { + *result_listener << "could not find an origin for variable '" << VarName << "'"; return false; } - ExpectedExpiredLoans.insert(ExpectedExpiredLoans.end(), LoanIDs.begin(), - LoanIDs.end()); + ExpectedLiveOrigins.push_back(*OriginIDOpt); } - return ExplainMatchResult(UnorderedElementsAreArray(ExpectedExpiredLoans), - ActualExpiredLoans, result_listener); + std::sort(ExpectedLiveOrigins.begin(), ExpectedLiveOrigins.end()); + std::sort(ActualLiveOrigins.begin(), ActualLiveOrigins.end()); + if (ExpectedLiveOrigins != ActualLiveOrigins) { + *result_listener << "Expected: {"; + for (const auto &OriginID : ExpectedLiveOrigins) { + *result_listener << OriginID.Value << ", "; + } + *result_listener << "} Actual: {"; + for (const auto &OriginID : ActualLiveOrigins) { + *result_listener << OriginID.Value << ", "; + } + *result_listener << "}"; + return false; + } + return true; +} + +MATCHER_P(AreDefinitelyLiveAt, Annotation, "") { + return ExplainMatchResult( + AreLiveAtImpl(Annotation, ConfidenceFilter::Definite), arg, + result_listener); +} + +MATCHER_P(AreMaybeLiveAt, Annotation, "") { + return ExplainMatchResult(AreLiveAtImpl(Annotation, ConfidenceFilter::Maybe), + arg, result_listener); +} + +MATCHER_P(AreLiveAt, Annotation, "") { + return ExplainMatchResult(AreLiveAtImpl(Annotation, ConfidenceFilter::All), + arg, result_listener); } // Base test fixture to manage the runner and helper. @@ -276,6 +325,13 @@ class LifetimeAnalysisTest : public ::testing::Test { return OriginInfo(OriginVar, *Helper); } + /// Factory function that hides the std::vector creation. + OriginsInfo Origins(std::initializer_list<std::string> OriginVars) { + return OriginsInfo({OriginVars}, *Helper); + } + + OriginsInfo NoOrigins() { return Origins({}); } + /// Factory function that hides the std::vector creation. LoanSetInfo LoansTo(std::initializer_list<std::string> LoanVars) { return LoanSetInfo({LoanVars}, *Helper); @@ -428,29 +484,6 @@ TEST_F(LifetimeAnalysisTest, AssignInSwitch) { 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) { - POINT(start_loop); - MyObj inner; - p = &inner; - POINT(end_loop); - } - POINT(after_loop); - } - )"); - EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "start_loop")); - EXPECT_THAT(LoansTo({"inner"}), AreExpiredAt("start_loop")); - - EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "end_loop")); - EXPECT_THAT(NoLoans(), AreExpiredAt("end_loop")); - - EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_loop")); - EXPECT_THAT(LoansTo({"inner"}), AreExpiredAt("after_loop")); -} - TEST_F(LifetimeAnalysisTest, LoopWithBreak) { SetupTest(R"( void target(int count) { @@ -528,20 +561,16 @@ TEST_F(LifetimeAnalysisTest, PointersAndExpirationInACycle) { )"); EXPECT_THAT(Origin("p1"), HasLoansTo({"v1"}, "before_while")); EXPECT_THAT(Origin("p2"), HasLoansTo({"v2"}, "before_while")); - EXPECT_THAT(NoLoans(), AreExpiredAt("before_while")); EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "temp"}, "in_loop_before_temp")); EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "in_loop_before_temp")); - EXPECT_THAT(LoansTo({"temp"}), AreExpiredAt("in_loop_before_temp")); EXPECT_THAT(Origin("p1"), HasLoansTo({"temp"}, "in_loop_after_temp")); EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "in_loop_after_temp")); - EXPECT_THAT(NoLoans(), AreExpiredAt("in_loop_after_temp")); EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "temp"}, "after_loop")); EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "after_loop")); - EXPECT_THAT(LoansTo({"temp"}), AreExpiredAt("after_loop")); } TEST_F(LifetimeAnalysisTest, InfiniteLoopPrunesEdges) { @@ -585,178 +614,6 @@ TEST_F(LifetimeAnalysisTest, NestedScopes) { EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_inner_scope")); } -TEST_F(LifetimeAnalysisTest, SimpleExpiry) { - SetupTest(R"( - void target() { - MyObj* p = nullptr; - { - MyObj s; - p = &s; - POINT(before_expiry); - } // s goes out of scope here - POINT(after_expiry); - } - )"); - EXPECT_THAT(NoLoans(), AreExpiredAt("before_expiry")); - EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_expiry")); -} - -TEST_F(LifetimeAnalysisTest, NestedExpiry) { - SetupTest(R"( - void target() { - MyObj s1; - MyObj* p = &s1; - POINT(before_inner); - { - MyObj s2; - p = &s2; - POINT(in_inner); - } // s2 expires - POINT(after_inner); - } - )"); - EXPECT_THAT(NoLoans(), AreExpiredAt("before_inner")); - EXPECT_THAT(NoLoans(), AreExpiredAt("in_inner")); - EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("after_inner")); -} - -TEST_F(LifetimeAnalysisTest, ConditionalExpiry) { - SetupTest(R"( - void target(bool cond) { - MyObj s1; - MyObj* p = &s1; - POINT(before_if); - if (cond) { - MyObj s2; - p = &s2; - POINT(then_block); - } // s2 expires here - POINT(after_if); - } - )"); - EXPECT_THAT(NoLoans(), AreExpiredAt("before_if")); - EXPECT_THAT(NoLoans(), AreExpiredAt("then_block")); - EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("after_if")); -} - -TEST_F(LifetimeAnalysisTest, LoopExpiry) { - SetupTest(R"( - void target() { - MyObj *p = nullptr; - for (int i = 0; i < 2; ++i) { - POINT(start_loop); - MyObj s; - p = &s; - POINT(end_loop); - } // s expires here on each iteration - POINT(after_loop); - } - )"); - EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("start_loop")); - EXPECT_THAT(NoLoans(), AreExpiredAt("end_loop")); - EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_loop")); -} - -TEST_F(LifetimeAnalysisTest, MultipleExpiredLoans) { - SetupTest(R"( - void target() { - MyObj *p1, *p2, *p3; - { - MyObj s1; - p1 = &s1; - POINT(p1); - } // s1 expires - POINT(p2); - { - MyObj s2; - p2 = &s2; - MyObj s3; - p3 = &s3; - POINT(p3); - } // s2, s3 expire - POINT(p4); - } - )"); - EXPECT_THAT(NoLoans(), AreExpiredAt("p1")); - EXPECT_THAT(LoansTo({"s1"}), AreExpiredAt("p2")); - EXPECT_THAT(LoansTo({"s1"}), AreExpiredAt("p3")); - EXPECT_THAT(LoansTo({"s1", "s2", "s3"}), AreExpiredAt("p4")); -} - -TEST_F(LifetimeAnalysisTest, GotoJumpsOutOfScope) { - SetupTest(R"( - void target(bool cond) { - MyObj *p = nullptr; - { - MyObj s; - p = &s; - POINT(before_goto); - if (cond) { - goto end; - } - } // `s` expires here on the path that doesn't jump - POINT(after_scope); - end: - POINT(after_goto); - } - )"); - EXPECT_THAT(NoLoans(), AreExpiredAt("before_goto")); - EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_scope")); - EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_goto")); -} - -TEST_F(LifetimeAnalysisTest, ContinueInLoop) { - SetupTest(R"( - void target(int count) { - MyObj *p = nullptr; - MyObj outer; - p = &outer; - POINT(before_loop); - - for (int i = 0; i < count; ++i) { - if (i % 2 == 0) { - MyObj s_even; - p = &s_even; - POINT(in_even_iter); - continue; - } - MyObj s_odd; - p = &s_odd; - POINT(in_odd_iter); - } - POINT(after_loop); - } - )"); - EXPECT_THAT(NoLoans(), AreExpiredAt("before_loop")); - EXPECT_THAT(LoansTo({"s_odd"}), AreExpiredAt("in_even_iter")); - EXPECT_THAT(LoansTo({"s_even"}), AreExpiredAt("in_odd_iter")); - EXPECT_THAT(LoansTo({"s_even", "s_odd"}), AreExpiredAt("after_loop")); -} - -TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) { - SetupTest(R"( - void target() { - MyObj* p = nullptr; - { - MyObj s1; - p = &s1; - POINT(p_has_s1); - { - MyObj s2; - p = &s2; - POINT(p_has_s2); - } - POINT(p_after_s2_expires); - } // s1 expires here. - POINT(p_after_s1_expires); - } - )"); - EXPECT_THAT(NoLoans(), AreExpiredAt("p_has_s1")); - EXPECT_THAT(NoLoans(), AreExpiredAt("p_has_s2")); - EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("p_after_s2_expires")); - EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires")); -} - TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) { SetupTest(R"( void target() { @@ -880,23 +737,6 @@ TEST_F(LifetimeAnalysisTest, GslPointerPropagation) { EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p3")); } -TEST_F(LifetimeAnalysisTest, GslPointerLoanExpiration) { - SetupTest(R"( - void target() { - View x; - { - MyObj a; - x = a; - POINT(before_expiry); - } // `a` is destroyed here. - POINT(after_expiry); - } - )"); - - EXPECT_THAT(NoLoans(), AreExpiredAt("before_expiry")); - EXPECT_THAT(LoansTo({"a"}), AreExpiredAt("after_expiry")); -} - TEST_F(LifetimeAnalysisTest, GslPointerReassignment) { SetupTest(R"( void target() { @@ -916,7 +756,6 @@ TEST_F(LifetimeAnalysisTest, GslPointerReassignment) { EXPECT_THAT(Origin("v"), HasLoansTo({"safe"}, "p1")); EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p2")); EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p3")); - EXPECT_THAT(LoansTo({"unsafe"}), AreExpiredAt("p3")); } TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) { @@ -1174,5 +1013,187 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) { )"); EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1")); } + +TEST_F(LifetimeAnalysisTest, LivenessDeadPointer) { + SetupTest(R"( + void target() { + POINT(p2); + MyObj s; + MyObj* p = &s; + POINT(p1); + } + )"); + EXPECT_THAT(NoOrigins(), AreLiveAt("p1")); + EXPECT_THAT(NoOrigins(), AreLiveAt("p2")); +} + +TEST_F(LifetimeAnalysisTest, LivenessSimpleReturn) { + SetupTest(R"( + MyObj* target() { + MyObj s; + MyObj* p = &s; + POINT(p1); + return p; + } + )"); + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, LivenessKilledByReassignment) { + SetupTest(R"( + MyObj* target() { + MyObj s1, s2; + MyObj* p = &s1; + POINT(p1); + p = &s2; + POINT(p2); + return p; + } + )"); + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p2")); + EXPECT_THAT(NoOrigins(), AreLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, LivenessAcrossBranches) { + SetupTest(R"( + MyObj* target(bool c) { + MyObj x, y; + MyObj* p = nullptr; + POINT(p1); + if (c) { + p = &x; + POINT(p2); + } else { + p = &y; + POINT(p3); + } + return p; + } + )"); + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p2")); + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p3")); + // Before the `if`, the value of `p` (`nullptr`) is always overwritten before. + EXPECT_THAT(NoOrigins(), AreLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, LivenessInLoop) { + SetupTest(R"( + MyObj* target(bool c) { + MyObj s1, s2; + MyObj* p = &s1; + MyObj* q = &s2; + POINT(p1); + while(c) { + POINT(p2); + + p = q; + POINT(p3); + } + POINT(p4); + return p; + } + )"); + + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p4")); + EXPECT_THAT(NoOrigins(), AreMaybeLiveAt("p4")); + + EXPECT_THAT(Origins({"p", "q"}), AreMaybeLiveAt("p3")); + + EXPECT_THAT(Origins({"q"}), AreDefinitelyLiveAt("p2")); + EXPECT_THAT(NoOrigins(), AreMaybeLiveAt("p2")); + + EXPECT_THAT(Origins({"p", "q"}), AreMaybeLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf) { + // See https://github.com/llvm/llvm-project/issues/156959. + SetupTest(R"( + void target(bool cond) { + MyObj b; + while (cond) { + POINT(p1); + + MyObj a; + View p = b; + + POINT(p2); + + if (cond) { + POINT(p3); + p = a; + } + POINT(p4); + (void)p; + POINT(p5); + } + } + )"); + EXPECT_THAT(NoOrigins(), AreLiveAt("p5")); + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p4")); + EXPECT_THAT(NoOrigins(), AreLiveAt("p3")); + EXPECT_THAT(Origins({"p"}), AreMaybeLiveAt("p2")); + EXPECT_THAT(NoOrigins(), AreLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf2) { + SetupTest(R"( + void target(MyObj safe, bool condition) { + MyObj* p = &safe; + MyObj* q = &safe; + POINT(p6); + + while (condition) { + POINT(p5); + MyObj x; + p = &x; + + POINT(p4); + + if (condition) { + q = p; + POINT(p3); + } + + POINT(p2); + (void)*p; + (void)*q; + POINT(p1); + } + } + )"); + EXPECT_THAT(Origins({"q"}), AreMaybeLiveAt("p1")); + EXPECT_THAT(NoOrigins(), AreDefinitelyLiveAt("p1")); + + EXPECT_THAT(Origins({"p", "q"}), AreDefinitelyLiveAt("p2")); + + EXPECT_THAT(Origins({"p", "q"}), AreDefinitelyLiveAt("p3")); + + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p4")); + EXPECT_THAT(Origins({"q"}), AreMaybeLiveAt("p4")); + + EXPECT_THAT(Origins({"q"}), AreMaybeLiveAt("p5")); + EXPECT_THAT(NoOrigins(), AreDefinitelyLiveAt("p5")); + + EXPECT_THAT(Origins({"q"}), AreMaybeLiveAt("p6")); + EXPECT_THAT(NoOrigins(), AreDefinitelyLiveAt("p6")); +} + +TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) { + SetupTest(R"( + void target(MyObj safe) { + MyObj* p = &safe; + for (int i = 0; i < 1; ++i) { + MyObj s; + p = &s; + POINT(p2); + } + POINT(p1); + (void)*p; + } + )"); + EXPECT_THAT(Origins({"p"}), AreDefinitelyLiveAt("p1")); + EXPECT_THAT(Origins({"p"}), AreMaybeLiveAt("p2")); +} + } // anonymous namespace } // namespace clang::lifetimes::internal _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits