[llvm-branch-commits] [clang] [LifetimeSafety] Introduce a liveness-based lifetime policy (PR #159991)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Sep 23 04:58:25 PDT 2025
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/159991
>From f49485d0faf49de565a1db8cde342b9ba68a9d6c Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at 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 | 386 +++++++-------
clang/test/Sema/warn-lifetime-safety.cpp | 141 +++--
.../unittests/Analysis/LifetimeSafetyTest.cpp | 501 +++++++++---------
4 files changed, 577 insertions(+), 460 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 4068df31f1147..6381f50fa97f9 100644
--- a/clang/lib/Analysis/LifetimeSafety.cpp
+++ b/clang/lib/Analysis/LifetimeSafety.cpp
@@ -22,6 +22,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>
@@ -875,19 +876,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);
}
@@ -898,7 +906,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); }
@@ -997,22 +1010,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
@@ -1020,19 +1037,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{Allocator, /*canonicalize=*/false};
- LoanSet::Factory LoanSetFactory{Allocator, /*canonicalize=*/false};
- ExpiredLoanMap::Factory ExpiredLoanMapFactory{Allocator,
- /*canonicalize=*/false};
-};
-
/// Represents the dataflow lattice for loan propagation.
///
/// This lattice tracks which loans each origin may hold at a given program
@@ -1076,10 +1080,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;
@@ -1092,8 +1096,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);
}
@@ -1143,96 +1152,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) {}
- ExpiredLattice() : Expired(nullptr) {};
- explicit ExpiredLattice(ExpiredLoanMap M) : Expired(M) {}
+ 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>;
- bool operator==(const ExpiredLattice &Other) const {
- return Expired == Other.Expired;
+/// 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;
+
+ LivenessLattice() : LiveOrigins(nullptr) {};
+
+ 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) {}
+ 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;
- using Base::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; }
};
// ========================================================================= //
@@ -1250,84 +1308,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() {
@@ -1346,6 +1369,15 @@ 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{Allocator, /*canonicalize=*/false};
+ LoanSet::Factory LoanSetFactory{Allocator, /*canonicalize=*/false};
+ LivenessMap::Factory LivenessMapFactory{Allocator, /*canonicalize=*/false};
+};
+
// We need this here for unique_ptr with forward declared class.
LifetimeSafetyAnalysis::~LifetimeSafetyAnalysis() = default;
@@ -1376,15 +1408,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();
}
@@ -1395,15 +1427,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");
@@ -1423,6 +1446,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
More information about the llvm-branch-commits
mailing list