https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/207520
>From 52d55b077adf8aa69536328ac5f34c058dc794d5 Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> 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 ++++ .../Analysis/Analyses/LifetimeSafety/Loans.h | 31 +++++ clang/lib/Analysis/LifetimeSafety/Dataflow.h | 8 +- clang/lib/Analysis/LifetimeSafety/Facts.cpp | 24 ++++ .../LifetimeSafety/FactsGenerator.cpp | 11 +- .../LifetimeSafety/LoanPropagation.cpp | 84 +++++++++++--- clang/lib/Analysis/LifetimeSafety/Loans.cpp | 32 +++++ .../Sema/LifetimeSafety/invalidations.cpp | 90 +++++++++++++-- .../unittests/Analysis/LifetimeSafetyTest.cpp | 109 ++++++++++++------ 9 files changed, 348 insertions(+), 65 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 8dccccc0f2257..b826e4a979db7 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 source origin are projected (their access paths are extended + /// by a path element) and then flowed into the destination origin. + /// Example: if `obj` holds loan `{x}`, `p = obj.field` projects `{x}` with `field` + /// to `{x.field}` and flows it to `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/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h index c20af392a705f..6d512fa4df1ef 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h @@ -206,6 +206,7 @@ class Loan { /// Manages the creation, storage and retrieval of loans. class LoanManager { + using ExtensionCacheKey = std::pair<LoanID, PathElement>; public: LoanManager() = default; @@ -221,6 +222,17 @@ class LoanManager { const PlaceholderBase *getOrCreatePlaceholderBase(const ParmVarDecl *PVD); const PlaceholderBase *getOrCreatePlaceholderBase(const CXXMethodDecl *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 IDs that could have been extended to produce + /// ExtendedLoanID. + llvm::SmallVector<LoanID, 2> getBaseLoans(LoanID ExtendedLoanID, + PathElement Element) const; + + + const Loan *getLoan(LoanID ID) const { assert(ID.Value < AllLoans.size()); return AllLoans[ID.Value]; @@ -234,6 +246,12 @@ 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; + /// TODO(opt): Profile and evaluate the usefullness of small buffer /// optimisation. @@ -242,4 +260,17 @@ 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 15b666fbdf7ca..ffe8bba906ad4 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -66,6 +66,30 @@ 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) { + LoanSet Loans = LPA->getLoans(getOriginID(), this); + if (Loans.isEmpty()) + OS << " has no loans"; + else { + OS << " has loans to { "; + for (LoanID LID : Loans) { + LM.getLoan(LID)->getAccessPath().dump(OS); + OS << " "; + } + OS << "}"; + } + } + 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 95872d32adfb6..d07e7857b209a 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 a67b1b3c0f826..65492625c3027 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -58,6 +58,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()) @@ -180,14 +185,28 @@ class AnalysisImpl OriginID DestOID = F.getDestOriginID(); OriginID SrcOID = F.getSrcOriginID(); + LoanSet SrcLoans = getLoans(In, SrcOID); LoanSet DestLoans = F.getKillDest() ? LoanSetFactory.getEmptySet() : getLoans(In, DestOID); - LoanSet SrcLoans = getLoans(In, SrcOID); LoanSet MergedLoans = utils::join(DestLoans, SrcLoans, LoanSetFactory); 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()); } @@ -208,31 +227,68 @@ class AnalysisImpl assert(getLoans(StartOID, StartPoint).contains(TargetLoan) && "TargetLoan must be present in the StartOID at the StartPoint"); + LoanID CurrLoanID = TargetLoan; OriginID CurrOID = StartOID; llvm::SmallVector<OriginID> OriginFlowChain; llvm::ArrayRef<const Fact *> Facts = FactMgr.getBlockContaining(StartPoint); const auto *StartIt = llvm::find(Facts, StartPoint); assert(StartIt != Facts.end()); + const CFGBlock *B = nullptr; + for (const CFGBlock *Block : Cfg) { + if (FactMgr.getFacts(Block).data() == Facts.data()) { + B = Block; + break; + } + } + assert(B && "Could not find CFGBlock containing StartPoint"); + + auto GetStateBefore = [&](const Fact *F) -> Lattice { + auto It = llvm::find(Facts, F); + assert(It != Facts.end()); + if (It == Facts.begin()) { + auto InState = getInState(B); + assert(InState); + return *InState; + } + return getState(*(It - 1)); + }; + for (const Fact *F : llvm::reverse(llvm::make_range(Facts.begin(), StartIt))) { - if (const auto *IF = F->getAs<IssueFact>()) - if (IF->getLoanID() == TargetLoan) { + if (const auto *IF = F->getAs<IssueFact>()) { + if (IF->getLoanID() == CurrLoanID) { assert(IF->getOriginID() == CurrOID); return OriginFlowChain; } + } - const auto *OFF = F->getAs<OriginFlowFact>(); - if (!OFF) - continue; - if (OFF->getDestOriginID() != CurrOID) - continue; - - const OriginID SrcOriginID = OFF->getSrcOriginID(); - if (!getLoans(SrcOriginID, OFF).contains(TargetLoan)) - continue; - OriginFlowChain.push_back(SrcOriginID); - CurrOID = SrcOriginID; + if (const auto *OFF = F->getAs<OriginFlowFact>()) { + if (OFF->getDestOriginID() == CurrOID) { + OriginID SrcOriginID = OFF->getSrcOriginID(); + if (getLoans(SrcOriginID, OFF).contains(CurrLoanID)) { + CurrOID = SrcOriginID; + OriginFlowChain.push_back(SrcOriginID); + } + } + } else if (const auto *PF = F->getAs<ProjectionFact>()) { + if (PF->getOriginID() == CurrOID) { + PathElement Element = PF->getPathElement(); + auto Candidates = + FactMgr.getLoanMgr().getBaseLoans(CurrLoanID, Element); + std::optional<LoanID> NextLoanID; + Lattice StateBefore = GetStateBefore(PF); + for (LoanID Candidate : Candidates) { + if (getLoans(StateBefore, CurrOID).contains(Candidate)) { + NextLoanID = Candidate; + break; + } + } + if (NextLoanID) { + CurrLoanID = *NextLoanID; + } + } + } } // FIXME: Ideally, this return is unreachable and should be an assert diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp index 3c90ed85831ca..9692b5ff33f48 100644 --- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp @@ -64,4 +64,36 @@ 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); + // TODO: Polish comment: Do not add interior access if the base loan path + // already contains that at the end. + if (Element.isInterior() && + !BaseLoan->getAccessPath().getElements().empty() && + BaseLoan->getAccessPath().getElements().back().isInterior()) + return ExtensionCache[Key] = const_cast<Loan *>(BaseLoan); + + AccessPath ExtendedPath(BaseLoan->getAccessPath(), Element); + return ExtensionCache[Key] = + createLoan(ExtendedPath, BaseLoan->getIssueExpr()); +} + +llvm::SmallVector<LoanID, 2> +LoanManager::getBaseLoans(LoanID ExtendedLoanID, PathElement Element) const { + llvm::SmallVector<LoanID, 2> Result; + for (const auto &Entry : ExtensionCache) { + if (Entry.second->getID() == ExtendedLoanID && + Entry.first.second == Element) { + Result.push_back(Entry.first.first); + } + } + return Result; +} } // namespace clang::lifetimes::internal diff --git a/clang/test/Sema/LifetimeSafety/invalidations.cpp b/clang/test/Sema/LifetimeSafety/invalidations.cpp index be1acc6bc7fbc..1572e78a27e14 100644 --- a/clang/test/Sema/LifetimeSafety/invalidations.cpp +++ b/clang/test/Sema/LifetimeSafety/invalidations.cpp @@ -485,13 +485,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}} + 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; @@ -894,12 +893,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}} - 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 @@ -937,3 +935,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 78b7449958140..e13b8f88cb834 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> @@ -136,11 +137,16 @@ class LifetimeTestHelper { } bool isLoanToATemporary(LoanID LID) { - return Analysis.getFactManager() - .getLoanMgr() - .getLoan(LID) - ->getAccessPath() - .getAsMaterializeTemporaryExpr() != nullptr; + const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(LID); + return L->getAccessPath().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 @@ -287,7 +293,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 +309,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 +1195,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() { _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
