https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/184295
>From fe7ea93422db01f15d310941cc2e859f1748562f Mon Sep 17 00:00:00 2001 From: Zhijie Wang <[email protected]> Date: Mon, 30 Mar 2026 09:54:06 -0700 Subject: [PATCH] [LifetimeSafety] Detect use of a reference type as a use of underlying origin --- .../Analysis/Analyses/LifetimeSafety/Facts.h | 24 ++++++++++++++++--- clang/lib/Analysis/LifetimeSafety/Facts.cpp | 14 +++++------ .../LifetimeSafety/FactsGenerator.cpp | 23 ++++++++++++++++-- .../Analysis/LifetimeSafety/LiveOrigins.cpp | 6 ++--- .../LifetimeSafety/LoanPropagation.cpp | 5 ++-- .../warn-lifetime-safety-invalidations.cpp | 15 ++++++++---- clang/test/Sema/warn-lifetime-safety.cpp | 12 ++++++++++ 7 files changed, 75 insertions(+), 24 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h index 0f848abd913d3..a986f37e67c05 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h @@ -234,7 +234,7 @@ class GlobalEscapeFact : public OriginEscapesFact { class UseFact : public Fact { const Expr *UseExpr; - const OriginList *OList; + std::variant<const OriginList *, OriginID> Origins; // True if this use is a write operation (e.g., left-hand side of assignment). // Write operations are exempted from use-after-free checks. bool IsWritten = false; @@ -243,13 +243,31 @@ class UseFact : public Fact { static bool classof(const Fact *F) { return F->getKind() == Kind::Use; } UseFact(const Expr *UseExpr, const OriginList *OList) - : Fact(Kind::Use), UseExpr(UseExpr), OList(OList) {} + : Fact(Kind::Use), UseExpr(UseExpr), Origins(OList) {} + UseFact(const Expr *UseExpr, OriginID OID) + : Fact(Kind::Use), UseExpr(UseExpr), Origins(OID) {} - const OriginList *getUsedOrigins() const { return OList; } const Expr *getUseExpr() const { return UseExpr; } void markAsWritten() { IsWritten = true; } bool isWritten() const { return IsWritten; } + void setOrigins(OriginID OID) { Origins = OID; } + + const OriginList *getOriginList() const { + if (auto *OList = std::get_if<const OriginList *>(&Origins)) + return *OList; + return nullptr; + } + + template <typename Fn> void forEachOrigin(Fn &&F) const { + if (const OriginList *OList = getOriginList()) { + for (auto *Cur = OList; Cur; Cur = Cur->peelOuterOrigin()) + F(Cur->getOuterOriginID()); + } else { + F(std::get<OriginID>(Origins)); + } + } + void dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const override; }; diff --git a/clang/lib/Analysis/LifetimeSafety/Facts.cpp b/clang/lib/Analysis/LifetimeSafety/Facts.cpp index 1bc0521a72359..d1dd396bdbe10 100644 --- a/clang/lib/Analysis/LifetimeSafety/Facts.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Facts.cpp @@ -80,14 +80,14 @@ void GlobalEscapeFact::dump(llvm::raw_ostream &OS, const LoanManager &, void UseFact::dump(llvm::raw_ostream &OS, const LoanManager &, const OriginManager &OM) const { OS << "Use ("; - size_t NumUsedOrigins = getUsedOrigins()->getLength(); - size_t I = 0; - for (const OriginList *Cur = getUsedOrigins(); Cur; - Cur = Cur->peelOuterOrigin(), ++I) { - OM.dump(Cur->getOuterOriginID(), OS); - if (I < NumUsedOrigins - 1) + bool First = true; + forEachOrigin([&](OriginID OID) { + if (!First) OS << ", "; - } + else + First = false; + OM.dump(OID, OS); + }); OS << ", " << (isWritten() ? "Write" : "Read") << ")\n"; } diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 80a73a2bf687e..bf3589314d7f7 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -366,8 +366,27 @@ void FactsGenerator::handleAssignment(const Expr *LHSExpr, // assigned. RHSList = getRValueOrigins(RHSExpr, RHSList); - if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) - markUseAsWrite(DRE_LHS); + if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) { + QualType QT = DRE_LHS->getDecl()->getType(); + if (QT->isReferenceType() && hasOrigins(QT->getPointeeType())) { + // Writing through a reference uses the binding but overwrites the + // pointee. Model this as a Read of the outer origin (keeping the binding + // live) and a Write of the inner origins (killing the pointee's + // liveness). + if (UseFact *UF = UseFacts.lookup(DRE_LHS)) { + const OriginList *FullList = UF->getOriginList(); + assert(FullList); + UF->setOrigins(FullList->getOuterOriginID()); + if (const OriginList *InnerList = FullList->peelOuterOrigin()) { + UseFact *WriteUF = FactMgr.createFact<UseFact>(DRE_LHS, InnerList); + WriteUF->markAsWritten(); + CurrentBlockFacts.push_back(WriteUF); + } + } + } else if (!QT->isReferenceType()) { + markUseAsWrite(DRE_LHS); + } + } // Kill the old loans of the destination origin and flow the new loans // from the source origin. flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true); diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp index bc7494360624e..de4d7fa833ddf 100644 --- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp @@ -129,9 +129,7 @@ class AnalysisImpl /// the origin since it overwrites the value. Lattice transfer(Lattice In, const UseFact &UF) { Lattice Out = In; - for (const OriginList *Cur = UF.getUsedOrigins(); Cur; - Cur = Cur->peelOuterOrigin()) { - OriginID OID = Cur->getOuterOriginID(); + UF.forEachOrigin([&](OriginID OID) { // Write kills liveness. if (UF.isWritten()) { Out = Lattice(Factory.remove(Out.LiveOrigins, OID)); @@ -141,7 +139,7 @@ class AnalysisImpl Out = Lattice(Factory.add(Out.LiveOrigins, OID, LivenessInfo(&UF, LivenessKind::Must))); } - } + }); return Out; } diff --git a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp index e437fb7d41268..8960479edb17b 100644 --- a/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LoanPropagation.cpp @@ -59,9 +59,8 @@ static llvm::BitVector computePersistentOrigins(const FactManager &FactMgr, break; } case Fact::Kind::Use: - for (const OriginList *Cur = F->getAs<UseFact>()->getUsedOrigins(); Cur; - Cur = Cur->peelOuterOrigin()) - CheckOrigin(Cur->getOuterOriginID()); + F->getAs<UseFact>()->forEachOrigin( + [&](OriginID OID) { CheckOrigin(OID); }); break; case Fact::Kind::MovedOrigin: case Fact::Kind::OriginEscapes: diff --git a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp index 486edd7a1a023..c7f920c03736a 100644 --- a/clang/test/Sema/warn-lifetime-safety-invalidations.cpp +++ b/clang/test/Sema/warn-lifetime-safety-invalidations.cpp @@ -259,14 +259,19 @@ namespace ElementReferences { void ReferenceToVectorElement() { std::vector<int> v = {1, 2, 3}; - int& ref = v[0]; - v.push_back(4); - // FIXME: Detect this as a use of 'ref'. - // https://github.com/llvm/llvm-project/issues/180187 - ref = 10; + int& ref = v[0]; // expected-warning {{object whose reference is captured is later invalidated}} + v.push_back(4); // expected-note {{invalidated here}} + ref = 10; // expected-note {{later used here}} (void)ref; } +void PointerRefToVectorElement() { + std::vector<int*> v = {nullptr, nullptr}; + int*& ref = v[0]; // expected-warning {{object whose reference is captured is later invalidated}} + v.push_back(nullptr); // expected-note {{invalidated here}} + ref = nullptr; // expected-note {{later used here}} +} + void PointerToVectorElement() { std::vector<int> v = {1, 2, 3}; int* ptr = &v[0]; // expected-warning {{object whose reference is captured is later invalidated}} diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 76d43445f8636..866542303414a 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -752,6 +752,18 @@ void no_error_if_dangle_then_rescue_gsl() { v.use(); // This is safe. } +void no_error_if_dangle_then_rescue_via_ref() { + MyObj safe; + MyObj* p; + MyObj*& ref = p; + { + MyObj temp; + ref = &temp; // p temporarily points to temp via ref. + } + ref = &safe; // p is "rescued" via ref before use. + (void)*ref; // This is safe. +} + void no_error_loan_from_current_iteration(bool cond) { // See https://github.com/llvm/llvm-project/issues/156959. MyObj b; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
