[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
Sun Mar 29 00:02:25 PDT 2026


https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/184295

>From c92d6cf5aecd8ddfa243b40326ce52b9ac0e747e Mon Sep 17 00:00:00 2001
From: Zhijie Wang <yesterda9 at gmail.com>
Date: Sun, 29 Mar 2026 00:02:05 -0700
Subject: [PATCH] [LifetimeSafety] Detect use of a reference type as a use of
 underlying origin

---
 .../Analysis/Analyses/LifetimeSafety/Facts.h   |  3 +++
 .../Analyses/LifetimeSafety/FactsGenerator.h   |  2 +-
 .../Analysis/LifetimeSafety/FactsGenerator.cpp | 16 ++++++++++++----
 .../Analysis/LifetimeSafety/LiveOrigins.cpp    | 18 +++++++++++++++---
 .../warn-lifetime-safety-invalidations.cpp     | 15 ++++++++++-----
 clang/test/Sema/warn-lifetime-safety.cpp       | 12 ++++++++++++
 6 files changed, 53 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
index 0f848abd913d3..7d95d9a01b1db 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Facts.h
@@ -238,6 +238,7 @@ class UseFact : public Fact {
   // 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;
+  bool IsReferenceWrite = false;
 
 public:
   static bool classof(const Fact *F) { return F->getKind() == Kind::Use; }
@@ -248,7 +249,9 @@ class UseFact : public Fact {
   const OriginList *getUsedOrigins() const { return OList; }
   const Expr *getUseExpr() const { return UseExpr; }
   void markAsWritten() { IsWritten = true; }
+  void markAsReferenceWrite() { IsReferenceWrite = true; }
   bool isWritten() const { return IsWritten; }
+  bool isReferenceWrite() const { return IsReferenceWrite; }
 
   void dump(llvm::raw_ostream &OS, const LoanManager &,
             const OriginManager &OM) const override;
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index dfcbdc7d73007..60b79168ca9b5 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -111,7 +111,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
   // (e.g. on the left-hand side of an assignment in the case of a DeclRefExpr).
   void handleUse(const Expr *E);
 
-  void markUseAsWrite(const DeclRefExpr *DRE);
+  void markUseAsWrite(const DeclRefExpr *DRE, bool IsReferenceWrite);
 
   bool escapesViaReturn(OriginID OID) const;
 
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 80a73a2bf687e..16f435c3a4301 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -366,8 +366,12 @@ 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();
+    bool IsRef = QT->isReferenceType();
+    if (!IsRef || hasOrigins(QT->getPointeeType()))
+      markUseAsWrite(DRE_LHS, IsRef);
+  }
   // Kill the old loans of the destination origin and flow the new loans
   // from the source origin.
   flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
@@ -753,9 +757,13 @@ void FactsGenerator::handleUse(const Expr *E) {
   }
 }
 
-void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
-  if (UseFacts.contains(DRE))
+void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE,
+                                    bool IsReferenceWrite) {
+  if (UseFacts.contains(DRE)) {
     UseFacts[DRE]->markAsWritten();
+    if (IsReferenceWrite)
+      UseFacts[DRE]->markAsReferenceWrite();
+  }
 }
 
 // Creates an IssueFact for a new placeholder loan for each pointer or reference
diff --git a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
index bc7494360624e..e6bbcb616e193 100644
--- a/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LiveOrigins.cpp
@@ -126,11 +126,23 @@ class AnalysisImpl
 
   /// A read operation makes the origin live with definite confidence, as it
   /// dominates this program point. A write operation kills the liveness of
-  /// the origin since it overwrites the value.
+  /// the origin since it overwrites the value. For reference writes, the outer
+  /// origin (reference binding) is kept live and only inner origins are killed.
   Lattice transfer(Lattice In, const UseFact &UF) {
     Lattice Out = In;
-    for (const OriginList *Cur = UF.getUsedOrigins(); Cur;
-         Cur = Cur->peelOuterOrigin()) {
+    const OriginList *Cur = UF.getUsedOrigins();
+
+    // Writing through a reference (e.g., `int*& ref; ref = &y;`) is a use of
+    // the reference itself. Keep the outer origin (reference binding) live and
+    // kill only inner origins.
+    if (UF.isReferenceWrite()) {
+      assert(Cur);
+      Out = Lattice(Factory.add(Out.LiveOrigins, Cur->getOuterOriginID(),
+                                LivenessInfo(&UF, LivenessKind::Must)));
+      Cur = Cur->peelOuterOrigin();
+    }
+
+    for (; Cur; Cur = Cur->peelOuterOrigin()) {
       OriginID OID = Cur->getOuterOriginID();
       // Write kills liveness.
       if (UF.isWritten()) {
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