[clang] [LifetimeSafety] Support field-sensitivity in lifetime tracking (PR #207520)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 27 01:56:31 PDT 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/207520
>From e1a1f8f4e481b4a90898d9ed8e3952cd53e10ac0 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Sat, 4 Jul 2026 16:25:06 +0000
Subject: [PATCH] [LifetimeSafety] Support field-sensitivity in lifetime
tracking
This patch enables field-sensitivity when tracking lifetimes of nested objects.
- FactsGenerator now generates `PathElement::getField` for `MemberExpr` accesses, mapping fields to loans.
- LoanPropagation now propagates field paths along flow facts, appending fields to base loans.
- Removes false-positive warnings in `invalidations.cpp` where modifications to one field were incorrectly reported as invalidating iterators/pointers to another field.
- Adds comprehensive unit tests checking nested field access and placeholder fields.
TAG=agy
CONV=2cfd8d00-18d7-4a03-8d78-2aba2f9a8f23
---
.../Analysis/Analyses/LifetimeSafety/Facts.h | 24 +++
.../Analyses/LifetimeSafety/LoanPropagation.h | 3 +
.../Analysis/Analyses/LifetimeSafety/Loans.h | 29 ++++
clang/lib/Analysis/LifetimeSafety/Dataflow.h | 8 +-
clang/lib/Analysis/LifetimeSafety/Facts.cpp | 26 ++--
.../LifetimeSafety/FactsGenerator.cpp | 11 +-
.../LifetimeSafety/LoanPropagation.cpp | 146 +++++++++++++-----
clang/lib/Analysis/LifetimeSafety/Loans.cpp | 20 +++
.../Sema/LifetimeSafety/invalidations.cpp | 92 +++++++++--
.../unittests/Analysis/LifetimeSafetyTest.cpp | 102 ++++++++----
10 files changed, 364 insertions(+), 97 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 94db2a7f311ae..6ff63513d21be 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -48,6 +48,11 @@ class Fact {
/// it. Otherwise, the source's loan set is merged into the destination's
/// loan set.
OriginFlow,
+ /// Loans held by the origin are projected (their access paths are
+ /// extended by a path element).
+ /// Example: if `obj` holds loan `{x}`, `p = obj.field` projects `{x}` with
+ /// `field` to `{x.field}`before flowing into `p`.
+ Projection,
/// An origin is used (eg. appears as l-value expression like DeclRefExpr).
Use,
/// An origin that is moved (e.g., passed to an rvalue reference parameter).
@@ -160,6 +165,25 @@ class OriginFlowFact : public Fact {
const LoanPropagationAnalysis *LPA = nullptr) const override;
};
+class ProjectionFact : public Fact {
+ OriginID OID;
+ PathElement Element;
+
+public:
+ static bool classof(const Fact *F) {
+ return F->getKind() == Kind::Projection;
+ }
+
+ ProjectionFact(OriginID OID, PathElement Element)
+ : Fact(Kind::Projection), OID(OID), Element(Element) {}
+
+ OriginID getOriginID() const { return OID; }
+ PathElement getPathElement() const { return Element; }
+
+ void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM,
+ const LoanPropagationAnalysis *LPA = nullptr) const override;
+};
+
/// Represents that an origin escapes the current scope through various means.
/// This is the base class for different escape scenarios.
class OriginEscapesFact : public Fact {
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
index e13442facd82d..7a968b7f11630 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LoanPropagation.h
@@ -37,6 +37,9 @@ class LoanPropagationAnalysis {
LoanSet getLoans(OriginID OID, ProgramPoint P) const;
+ void dumpLoans(OriginID OID, ProgramPoint P, llvm::raw_ostream &OS,
+ const LoanManager &LM) const;
+
/// Builds the chain of origins through which a loan has propagated.
///
/// Starting from the last fact of the block containing StartPoint, this
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
index 8137a207290d7..2e5d21d7ba372 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
@@ -208,6 +208,7 @@ class Loan {
/// Manages the creation, storage and retrieval of loans.
class LoanManager {
+ using ExtensionCacheKey = std::pair<LoanID, PathElement>;
public:
LoanManager() = default;
@@ -226,6 +227,14 @@ class LoanManager {
return createLoan(AccessPath(getOrCreatePlaceholderBase(MD)));
}
+ /// Gets or creates a loan by extending BaseLoanID with Element.
+ /// Caches the result to ensure convergence in LoanPropagation.
+ Loan *getOrCreateExtendedLoan(LoanID BaseLoanID, PathElement Element);
+
+ /// Finds the base loan ID that was extended to produce ExtendedLoanID, if
+ /// any.
+ std::optional<LoanID> getBaseLoan(LoanID ExtendedLoanID) const;
+
const Loan *getLoan(LoanID ID) const {
assert(ID.Value < AllLoans.size());
return AllLoans[ID.Value];
@@ -243,6 +252,14 @@ class LoanManager {
LoanID NextLoanID{0};
llvm::FoldingSet<PlaceholderBase> PlaceholderBases;
+ /// Cache for extended loans. Maps (BaseLoanID, PathElement) to the extended
+ /// loan. Ensures that extending the same loan with the same path element
+ /// always returns the same loan object, which is necessary for dataflow
+ /// analysis convergence.
+ llvm::DenseMap<ExtensionCacheKey, Loan *> ExtensionCache;
+
+ /// Maps an extended loan ID back to its base loan ID.
+ llvm::DenseMap<LoanID, LoanID> BaseLoansMap;
/// TODO(opt): Profile and evaluate the usefullness of small buffer
/// optimisation.
@@ -251,4 +268,16 @@ class LoanManager {
};
} // namespace clang::lifetimes::internal
+namespace llvm {
+template <> struct DenseMapInfo<clang::lifetimes::internal::PathElement> {
+ using PathElement = clang::lifetimes::internal::PathElement;
+ static unsigned getHashValue(const PathElement &Val) {
+ return llvm::hash_combine(Val.isInterior(), Val.getFieldDecl());
+ }
+ static bool isEqual(const PathElement &LHS, const PathElement &RHS) {
+ return LHS == RHS;
+ }
+};
+} // namespace llvm
+
#endif // LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H
diff --git a/clang/lib/Analysis/LifetimeSafety/Dataflow.h b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
index fc3049c8bec84..c3a60f9b881f9 100644
--- a/clang/lib/Analysis/LifetimeSafety/Dataflow.h
+++ b/clang/lib/Analysis/LifetimeSafety/Dataflow.h
@@ -60,9 +60,6 @@ class DataflowAnalysis {
using Base = DataflowAnalysis<Derived, Lattice, Dir>;
private:
- const CFG &Cfg;
- AnalysisDeclContext &AC;
-
/// The dataflow state before a basic block is processed.
llvm::DenseMap<const CFGBlock *, Lattice> InStates;
/// The dataflow state after a basic block is processed.
@@ -75,6 +72,8 @@ class DataflowAnalysis {
static constexpr bool isForward() { return Dir == Direction::Forward; }
protected:
+ const CFG &Cfg;
+ AnalysisDeclContext &AC;
FactManager &FactMgr;
explicit DataflowAnalysis(const CFG &Cfg, AnalysisDeclContext &AC,
@@ -170,6 +169,8 @@ class DataflowAnalysis {
return D->transfer(In, *F->getAs<ExpireFact>());
case Fact::Kind::OriginFlow:
return D->transfer(In, *F->getAs<OriginFlowFact>());
+ case Fact::Kind::Projection:
+ return D->transfer(In, *F->getAs<ProjectionFact>());
case Fact::Kind::MovedOrigin:
return D->transfer(In, *F->getAs<MovedOriginFact>());
case Fact::Kind::OriginEscapes:
@@ -190,6 +191,7 @@ class DataflowAnalysis {
Lattice transfer(Lattice In, const IssueFact &) { return In; }
Lattice transfer(Lattice In, const ExpireFact &) { return In; }
Lattice transfer(Lattice In, const OriginFlowFact &) { return In; }
+ Lattice transfer(Lattice In, const ProjectionFact &) { return In; }
Lattice transfer(Lattice In, const MovedOriginFact &) { return In; }
Lattice transfer(Lattice In, const OriginEscapesFact &) { return In; }
Lattice transfer(Lattice In, const UseFact &) { return In; }
diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
index ec2d42e10206a..54d5024784fc6 100644
--- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp
@@ -47,17 +47,7 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
OS << "\tDest: ";
OM.dump(getDestOriginID(), OS);
if (LPA) {
- LoanSet DestinationLoans = LPA->getLoans(getDestOriginID(), this);
- if (DestinationLoans.isEmpty())
- OS << " has no loans";
- else {
- OS << " has loans to { ";
- for (LoanID LID : DestinationLoans) {
- LM.getLoan(LID)->getAccessPath().dump(OS);
- OS << " ";
- }
- OS << "}";
- }
+ LPA->dumpLoans(getDestOriginID(), this, OS, LM);
}
OS << "\n";
OS << "\tSrc: ";
@@ -66,6 +56,20 @@ void OriginFlowFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
OS << "\n";
}
+void ProjectionFact::dump(llvm::raw_ostream &OS, const LoanManager &LM,
+ const OriginManager &OM,
+ const LoanPropagationAnalysis *LPA) const {
+ OS << "Projection: \n";
+ OS << "\tOrigin: ";
+ OM.dump(getOriginID(), OS);
+ if (LPA) {
+ LPA->dumpLoans(getOriginID(), this, OS, LM);
+ }
+ OS << "\n\tElement: ";
+ getPathElement().dump(OS);
+ OS << "\n";
+}
+
void MovedOriginFact::dump(llvm::raw_ostream &OS, const LoanManager &,
const OriginManager &OM,
const LoanPropagationAnalysis *) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index ac6267dabf48e..0b647fd4e7019 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -274,17 +274,18 @@ void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
auto *MD = ME->getMemberDecl();
- if (isa<FieldDecl>(MD) && doesDeclHaveStorage(MD)) {
+ if (auto *FD = dyn_cast<FieldDecl>(MD); FD && doesDeclHaveStorage(FD)) {
assert(ME->isGLValue() && "Field member should be GL value");
OriginList *Dst = getOriginsList(*ME);
assert(Dst && "Field member should have an origin list as it is GL value");
OriginList *Src = getOriginsList(*ME->getBase());
assert(Src && "Base expression should be a pointer/reference type");
- // The field's glvalue (outermost origin) holds the same loans as the base
- // expression.
+ // Flow loans from base to field, extending each loan's path with the field.
+ // E.g., if base has loan to `obj`, field gets loan to `obj.field`.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
- Dst->getOuterOriginID(), Src->getOuterOriginID(),
- /*Kill=*/true));
+ Dst->getOuterOriginID(), Src->getOuterOriginID(), /*KillDest=*/true));
+ CurrentBlockFacts.push_back(FactMgr.createFact<ProjectionFact>(
+ Dst->getOuterOriginID(), PathElement::getField(*FD)));
}
}
diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
index 078892bd48c10..4f622d628c19d 100644
--- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp
@@ -59,6 +59,11 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr,
CheckOrigin(OF->getSrcOriginID());
break;
}
+ case Fact::Kind::Projection: {
+ const auto *PF = F->getAs<ProjectionFact>();
+ CheckOrigin(PF->getOriginID());
+ break;
+ }
case Fact::Kind::Use:
for (const OriginList *Cur = F->getAs<UseFact>()->getUsedOrigins(); Cur;
Cur = Cur->peelOuterOrigin())
@@ -189,6 +194,21 @@ class AnalysisImpl
return setLoans(In, DestOID, MergedLoans);
}
+ /// A projection projects the loans currently held by the origin in-place.
+ Lattice transfer(Lattice In, const ProjectionFact &F) {
+ OriginID OID = F.getOriginID();
+ LoanSet Loans = getLoans(In, OID);
+ LoanSet ProjectedLoans = LoanSetFactory.getEmptySet();
+ PathElement Element = F.getPathElement();
+ for (LoanID LID : Loans) {
+ Loan *ExtendedLoan =
+ FactMgr.getLoanMgr().getOrCreateExtendedLoan(LID, Element);
+ ProjectedLoans =
+ LoanSetFactory.add(ProjectedLoans, ExtendedLoan->getID());
+ }
+ return setLoans(In, OID, ProjectedLoans);
+ }
+
Lattice transfer(Lattice In, const KillOriginFact &F) {
return setLoans(In, F.getKilledOrigin(), LoanSetFactory.getEmptySet());
}
@@ -218,11 +238,12 @@ class AnalysisImpl
EndBlock = Block;
break;
}
+ assert(EndBlock && "Could not find CFGBlock containing StartPoint");
- // Set up DFS traversal state
- // SearchState tracks which block we're in and which origin we're tracing
+ // Set up DFS traversal state.
+ // SearchState tracks which block we're in and which origin we're tracing.
// Each DFSNode maintains its own OriginFlowChain.
- using SearchState = std::pair<const CFGBlock *, OriginID>;
+ using SearchState = std::tuple<const CFGBlock *, OriginID, LoanID>;
struct DFSNode {
SearchState CurrState;
llvm::SmallVector<OriginID> OriginFlowChain;
@@ -230,30 +251,32 @@ class AnalysisImpl
llvm::SmallVector<DFSNode> PendingStates;
llvm::SmallSet<SearchState, 16> VistedStates;
- PendingStates.push_back({{EndBlock, StartOID}, {}});
+ PendingStates.push_back({{EndBlock, StartOID, TargetLoan}, {}});
// DFS loop to trace loan backwards through CFG
while (!PendingStates.empty()) {
DFSNode CurrNode = PendingStates.pop_back_val();
- auto [CurrBlock, CurrOID] = CurrNode.CurrState;
-
- // Trace origins within the current block
- const auto [BuildResult, Complete] =
- buildOriginFlowChain(CurrBlock, CurrOID, TargetLoan);
- if (!BuildResult.empty()) {
- CurrNode.OriginFlowChain.append(BuildResult);
- CurrOID = BuildResult.back();
- }
-
- // If we found the IssueFact, we're done
- if (Complete)
+ auto [CurrBlock, CurrOID, CurrLoanID] = CurrNode.CurrState;
+
+ // Trace origins within the current block.
+ BlockTraceResult TraceResult =
+ buildOriginFlowChain(CurrBlock, CurrOID, CurrLoanID);
+ if (!TraceResult.Chain.empty())
+ CurrNode.OriginFlowChain.append(TraceResult.Chain);
+ CurrOID = TraceResult.OutOID;
+ CurrLoanID = TraceResult.OutLoanID;
+
+ // If we found the IssueFact, we're done.
+ if (TraceResult.Complete)
return CurrNode.OriginFlowChain;
// Only explore predecessor blocks where the target loan is present in the
// current origin.
for (const CFGBlock *PredBlock : CurrBlock->preds()) {
- SearchState NextState = {PredBlock, CurrOID};
- if (getLoans(getOutState(PredBlock), CurrOID).contains(TargetLoan) &&
+ if (!PredBlock)
+ continue;
+ SearchState NextState = {PredBlock, CurrOID, CurrLoanID};
+ if (getLoans(getOutState(PredBlock), CurrOID).contains(CurrLoanID) &&
VistedStates.insert(NextState).second)
PendingStates.push_back({NextState, CurrNode.OriginFlowChain});
}
@@ -297,38 +320,69 @@ class AnalysisImpl
return LoanSetFactory.getEmptySet();
}
+ struct BlockTraceResult {
+ llvm::SmallVector<OriginID> Chain;
+ bool Complete;
+ OriginID OutOID;
+ LoanID OutLoanID;
+ };
+
/// Builds the chain of origins through which a loan has propagated.
///
/// This procedure operates strictly within a single Block. Starting from the
/// last fact of the Block, it traces backwards through OriginFlowFacts to
/// identify the sequence of origins through which the loan flowed.
///
- /// Returns (chain, true) if the target loan origin is found during the
- /// traversal, otherwise returns (chain, false).
- std::pair<llvm::SmallVector<OriginID>, bool>
- buildOriginFlowChain(const CFGBlock *Block, const OriginID StartOID,
- const LoanID TargetLoan) const {
+ /// Returns (chain, true, outOID, outLoanID) if the target loan origin is
+ /// found during the traversal, otherwise returns (chain, false, outOID,
+ /// outLoanID).
+ BlockTraceResult buildOriginFlowChain(const CFGBlock *Block,
+ const OriginID StartOID,
+ const LoanID StartLoanID) const {
OriginID CurrOID = StartOID;
+ LoanID CurrLoanID = StartLoanID;
llvm::SmallVector<OriginID> OriginFlowChain;
- for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) {
- if (const auto *IF = F->getAs<IssueFact>())
- if (IF->getLoanID() == TargetLoan && IF->getOriginID() == CurrOID)
- return {OriginFlowChain, true};
-
- const auto *OFF = F->getAs<OriginFlowFact>();
- if (!OFF || OFF->getDestOriginID() != CurrOID)
- continue;
-
- const OriginID SrcOriginID = OFF->getSrcOriginID();
- if (!getLoans(SrcOriginID, OFF).contains(TargetLoan))
- continue;
+ llvm::ArrayRef<const Fact *> Facts = FactMgr.getFacts(Block);
+ auto GetStateBefore = [&](const Fact *F) -> Lattice {
+ const auto *It = llvm::find(Facts, F);
+ assert(It != Facts.end());
+ if (It == Facts.begin()) {
+ auto InState = getInState(Block);
+ assert(InState);
+ return *InState;
+ }
+ return getState(*(It - 1));
+ };
- OriginFlowChain.push_back(SrcOriginID);
- CurrOID = SrcOriginID;
+ for (const Fact *F : llvm::reverse(FactMgr.getFacts(Block))) {
+ if (const auto *IF = F->getAs<IssueFact>()) {
+ // Search is complete.
+ if (IF->getLoanID() == CurrLoanID && IF->getOriginID() == CurrOID)
+ return {OriginFlowChain, true, CurrOID, CurrLoanID};
+ } else if (const auto *OFF = F->getAs<OriginFlowFact>()) {
+ // Trace the loan back to its source origin if it flowed from there.
+ if (OFF->getDestOriginID() != CurrOID)
+ continue;
+ OriginID SrcOriginID = OFF->getSrcOriginID();
+ if (!getLoans(SrcOriginID, OFF).contains(CurrLoanID))
+ continue;
+ CurrOID = SrcOriginID;
+ OriginFlowChain.push_back(SrcOriginID);
+ } else if (const auto *PF = F->getAs<ProjectionFact>()) {
+ // Step back from a projected field loan to its base loan (e.g., from
+ // 'obj.field' to 'obj').
+ if (PF->getOriginID() != CurrOID)
+ continue;
+ std::optional<LoanID> BaseLoanID =
+ FactMgr.getLoanMgr().getBaseLoan(CurrLoanID);
+ if (BaseLoanID &&
+ getLoans(GetStateBefore(PF), CurrOID).contains(*BaseLoanID))
+ CurrLoanID = *BaseLoanID;
+ }
}
- return {OriginFlowChain, false};
+ return {OriginFlowChain, false, CurrOID, CurrLoanID};
}
OriginLoanMap::Factory &OriginLoanMapFactory;
@@ -359,6 +413,22 @@ LoanSet LoanPropagationAnalysis::getLoans(OriginID OID, ProgramPoint P) const {
return PImpl->getLoans(OID, P);
}
+void LoanPropagationAnalysis::dumpLoans(OriginID OID, ProgramPoint P,
+ llvm::raw_ostream &OS,
+ const LoanManager &LM) const {
+ LoanSet Loans = getLoans(OID, P);
+ if (Loans.isEmpty())
+ OS << " has no loans";
+ else {
+ OS << " has loans to { ";
+ for (LoanID LID : Loans) {
+ LM.getLoan(LID)->getAccessPath().dump(OS);
+ OS << " ";
+ }
+ OS << "}";
+ }
+}
+
llvm::SmallVector<OriginID> LoanPropagationAnalysis::buildOriginFlowChain(
ProgramPoint StartPoint, const OriginID StartOID, const LoanID TargetLoan,
const CFG *Cfg) const {
diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
index e71842eefca6a..a9d7b92da719c 100644
--- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp
@@ -64,4 +64,24 @@ LoanManager::getOrCreatePlaceholderBase(const CXXMethodDecl *MD) {
PlaceholderBases.InsertNode(NewPB, InsertPos);
return NewPB;
}
+
+Loan *LoanManager::getOrCreateExtendedLoan(LoanID BaseLoanID,
+ PathElement Element) {
+ ExtensionCacheKey Key = {BaseLoanID, Element};
+ auto It = ExtensionCache.find(Key);
+ if (It != ExtensionCache.end())
+ return It->second;
+ const auto *BaseLoan = getLoan(BaseLoanID);
+ AccessPath ExtendedPath(BaseLoan->getAccessPath(), Element);
+ Loan *NewLoan = createLoan(ExtendedPath, BaseLoan->getIssueExpr());
+ BaseLoansMap[NewLoan->getID()] = BaseLoanID;
+ return ExtensionCache[Key] = NewLoan;
+}
+
+std::optional<LoanID> LoanManager::getBaseLoan(LoanID ExtendedLoanID) const {
+ auto It = BaseLoansMap.find(ExtendedLoanID);
+ if (It != BaseLoansMap.end())
+ return It->second;
+ return std::nullopt;
+}
} // namespace clang::lifetimes::internal
diff --git a/clang/test/Sema/LifetimeSafety/invalidations.cpp b/clang/test/Sema/LifetimeSafety/invalidations.cpp
index 127e375bc023c..86f58540d64d9 100644
--- a/clang/test/Sema/LifetimeSafety/invalidations.cpp
+++ b/clang/test/Sema/LifetimeSafety/invalidations.cpp
@@ -519,14 +519,12 @@ void ConditionalFieldInvalidatesIterator(bool flag) {
(flag ? s.strings1 : s.strings2).push_back("1");
*it;
}
-// FIXME: Requires field-sensitive AccessPaths to fix.
void Invalidate1Use2ViaRefIsOk() {
S s;
- auto it = s.strings2.begin(); // expected-warning {{local variable 's' is later invalidated}} \
- // expected-note {{result of call to 'begin' aliases the storage of local variable 's'}}
+ auto it = s.strings2.begin();
auto& strings1 = s.strings1;
- strings1.push_back("1"); // expected-note {{local variable 's' is invalidated here}}
- *it; // expected-note {{later used here}}
+ strings1.push_back("1"); // OK
+ *it;
}
void Invalidate1UseSIsOk() {
S s;
@@ -942,13 +940,11 @@ struct StringOwner {
std::string s, t;
};
-// FIXME: False-positive
void member_destructor_invalidates_pointer() {
StringOwner owner = {"42", "43"};
- const char *p = owner.s.data(); // expected-warning {{local variable 'owner' is later invalidated}} \
- // expected-note {{result of call to 'data' aliases the storage of local variable 'owner'}}
- owner.t.~basic_string(); // expected-note {{local variable 'owner' is invalidated here}}
- (void)*p; // expected-note {{later used here}}
+ const char *p = owner.s.data();
+ owner.t.~basic_string(); // OK
+ (void)*p;
}
} // namespace explicit_destructor
@@ -990,3 +986,79 @@ void invalid_after_ternary_reset(bool flag) {
}
} // namespace unique_ptr_invalidation
+
+namespace DeepFieldNesting {
+struct Level3 {
+ std::vector<std::string> vec;
+ int x;
+};
+struct Level2 {
+ Level3 inner3_1;
+ Level3 inner3_2;
+};
+struct Level1 {
+ Level2 inner2_1;
+ Level2 inner2_2;
+};
+
+// Modifying sibling at Level 3: OK
+void SiblingLevel3Ok() {
+ Level1 obj;
+ auto it = obj.inner2_1.inner3_1.vec.begin();
+ obj.inner2_1.inner3_2.vec.push_back("1");
+ *it;
+}
+
+// Modifying sibling at Level 2: OK
+void SiblingLevel2Ok() {
+ Level1 obj;
+ auto it = obj.inner2_1.inner3_1.vec.begin();
+ obj.inner2_2.inner3_1.vec.push_back("1");
+ *it;
+}
+
+// Modifying sibling non-container field at Level 3: OK
+void SiblingFieldLevel3Ok() {
+ Level1 obj;
+ auto it = obj.inner2_1.inner3_1.vec.begin();
+ obj.inner2_1.inner3_1.x = 42;
+ *it;
+}
+
+// Modifying parent structure after use: OK
+void ParentModifiedAfterUseOk() {
+ Level1 obj;
+ auto it = obj.inner2_1.inner3_1.vec.begin();
+ *it; // Use here
+ Level3 new_val;
+ obj.inner2_1.inner3_1 = new_val; // OK, because 'it' is no longer used!
+}
+
+// Pointers with sibling modification: OK
+void PointerSiblingLevel3Ok(Level1* ptr) {
+ auto it = ptr->inner2_1.inner3_1.vec.begin();
+ ptr->inner2_1.inner3_2.vec.push_back("1"); // OK
+ *it;
+}
+
+// References with sibling modification: OK
+void ReferenceSiblingLevel3Ok(Level1& ref) {
+ auto it = ref.inner2_1.inner3_1.vec.begin();
+ ref.inner2_1.inner3_2.vec.push_back("1"); // OK
+ *it;
+}
+} // namespace DeepFieldNesting
+
+namespace StructFieldDisambiguation {
+struct S {
+ std::vector<int> v;
+ int x;
+};
+
+void TestStructVsField(S& s) {
+ int* px = &s.x;
+ s.v.push_back(1); // Invalidates s.v.* (interior), but must NOT invalidate s.x
+ *px = 42; // OK
+}
+} // namespace StructFieldDisambiguation
+
diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
index 57cf7068affae..5aef3fd7d5c66 100644
--- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp
+++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp
@@ -12,6 +12,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/raw_ostream.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <optional>
@@ -143,6 +144,14 @@ class LifetimeTestHelper {
.getAsMaterializeTemporaryExpr() != nullptr;
}
+ std::string getAccessPathString(LoanID LID) {
+ const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(LID);
+ std::string S;
+ llvm::raw_string_ostream OS(S);
+ L->getAccessPath().dump(OS);
+ return S;
+ }
+
// Gets the set of loans that are live at the given program point. A loan is
// considered live at point P if there is a live origin which contains this
// loan.
@@ -287,7 +296,7 @@ class OriginsInfo {
/// variable expected to be the source of a loan.
/// \param Annotation A string identifying the program point (created with
/// POINT()) where the check should be performed.
-MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
+MATCHER_P2(HasLoansToImpl, LoanPathStrs, Annotation, "") {
const OriginInfo &Info = arg;
std::optional<OriginID> OIDOpt = Info.Helper.getOriginForDecl(Info.OriginVar);
if (!OIDOpt) {
@@ -303,36 +312,12 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
<< Annotation << "'";
return false;
}
- std::vector<LoanID> ActualLoans(ActualLoansSetOpt->begin(),
- ActualLoansSetOpt->end());
-
- std::vector<LoanID> ExpectedLoans;
- for (const auto &LoanVar : LoanVars) {
- std::vector<LoanID> ExpectedLIDs = Info.Helper.getLoansForVar(LoanVar);
- if (ExpectedLIDs.empty()) {
- *result_listener << "could not find loan for var '" << LoanVar << "'";
- return false;
- }
- ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(),
- ExpectedLIDs.end());
- }
- std::sort(ExpectedLoans.begin(), ExpectedLoans.end());
- std::sort(ActualLoans.begin(), ActualLoans.end());
- if (ExpectedLoans != ActualLoans) {
- *result_listener << "Expected: {";
- for (const auto &LoanID : ExpectedLoans) {
- *result_listener << LoanID.Value << ", ";
- }
- *result_listener << "} Actual: {";
- for (const auto &LoanID : ActualLoans) {
- *result_listener << LoanID.Value << ", ";
- }
- *result_listener << "}";
- return false;
- }
+ std::vector<std::string> ActualLoanPaths;
+ for (LoanID LID : *ActualLoansSetOpt)
+ ActualLoanPaths.push_back(Info.Helper.getAccessPathString(LID));
- return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
- ActualLoans, result_listener);
+ return ExplainMatchResult(UnorderedElementsAreArray(LoanPathStrs),
+ ActualLoanPaths, result_listener);
}
enum class LivenessKindFilter { Maybe, Must, All };
@@ -1213,6 +1198,63 @@ TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) {
EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1"));
}
+TEST_F(LifetimeAnalysisTest, NestedFieldAccess) {
+ SetupTest(R"(
+ struct Inner { int val; };
+ struct Outer { Inner f; };
+ void target() {
+ Outer o;
+ Outer *p = &o;
+ int* p1 = &o.f.val;
+ POINT(a);
+ int* p2 = &p->f.val;
+ POINT(b);
+ }
+ )");
+ EXPECT_THAT(Origin("p1"), HasLoansTo({"o.f.val"}, "a"));
+ EXPECT_THAT(Origin("p2"), HasLoansTo({"o.f.val"}, "b"));
+}
+
+TEST_F(LifetimeAnalysisTest, PlaceholderParamField) {
+ SetupTest(R"(
+ struct S { int val; };
+ void target(S* p) {
+ int* p1 = &p->val;
+ POINT(a);
+ }
+ )");
+ EXPECT_THAT(Origin("p1"), HasLoansTo({"$p.val"}, "a"));
+}
+
+TEST_F(LifetimeAnalysisTest, PlaceholderThisField) {
+ SetupTest(R"(
+ struct S {
+ int f;
+ void target() {
+ int* p1 = &f;
+ POINT(a);
+ }
+ };
+ )");
+ EXPECT_THAT(Origin("p1"), HasLoansTo({"$this.f"}, "a"));
+}
+
+TEST_F(LifetimeAnalysisTest, PlaceholderThisNestedField) {
+ SetupTest(R"(
+ struct S1 {
+ int f;
+ };
+ struct S {
+ S1 s1;
+ void target() {
+ int* p1 = &s1.f;
+ POINT(a);
+ }
+ };
+ )");
+ EXPECT_THAT(Origin("p1"), HasLoansTo({"$this.s1.f"}, "a"));
+}
+
TEST_F(LifetimeAnalysisTest, LivenessDeadPointer) {
SetupTest(R"(
void target() {
More information about the cfe-commits
mailing list