[clang] [LifetimeSafety] Detect use of a reference type as a use of underlying origin (PR #184295)
Zhijie Wang via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 30 09:54:25 PDT 2026
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 <yesterda9 at gmail.com>
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;
More information about the cfe-commits
mailing list