[clang] [LifetimeSafety] Fix handling of reference-type DeclRefExpr (PR #176728)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Jan 19 04:51:31 PST 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/176728
>From eeda4d31711bf81c01f4c41a86bf8ad4625722ba Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 19 Jan 2026 10:46:44 +0000
Subject: [PATCH] track use of dangling references
---
.../LifetimeSafety/FactsGenerator.cpp | 7 ++--
clang/test/Sema/Inputs/lifetime-analysis.h | 3 +-
.../Sema/warn-lifetime-analysis-nocfg.cpp | 18 ++++++---
clang/test/Sema/warn-lifetime-safety.cpp | 39 +++++++++++++++++++
4 files changed, 58 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index 54e19b58bd7d5..5d1afd15c795d 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -589,9 +589,10 @@ void FactsGenerator::handleUse(const DeclRefExpr *DRE) {
OriginList *List = getOriginsList(*DRE);
if (!List)
return;
- // Remove the outer layer of origin which borrows from the decl directly. This
- // is a use of the underlying decl.
- List = getRValueOrigins(DRE, List);
+ // Remove the outer layer of origin which borrows from the decl directly
+ // (e.g., when this is not a reference). This is a use of the underlying decl.
+ if (!DRE->getDecl()->getType()->isReferenceType())
+ List = getRValueOrigins(DRE, List);
// Skip if there is no inner origin (e.g., when it is not a pointer type).
if (!List)
return;
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 918547dfcec51..b47fe78bdf0fb 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -49,7 +49,8 @@ struct vector {
template<typename InputIterator>
vector(InputIterator first, InputIterator __last);
- T &at(int n);
+ T & at(int n) &;
+ T && at(int n) &&;
void push_back(const T&);
void push_back(T&&);
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 441f9fc602916..77fb39ebb10f3 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -280,13 +280,21 @@ std::string_view danglingRefToOptionalFromTemp4() {
}
void danglingReferenceFromTempOwner() {
+ int &&r = *std::optional<int>(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
// FIXME: Detect this using the CFG-based lifetime analysis.
// https://github.com/llvm/llvm-project/issues/175893
- int &&r = *std::optional<int>(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
int &&r2 = *std::optional<int>(5); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+
+ // FIXME: Detect this using the CFG-based lifetime analysis.
+ // https://github.com/llvm/llvm-project/issues/175893
int &&r3 = std::optional<int>(5).value(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
- int &r4 = std::vector<int>().at(3); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
- use(r, r2, r3, r4);
+
+ const int &r4 = std::vector<int>().at(3); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ int &&r5 = std::vector<int>().at(3); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ use(r, r2, r3, r4, r5); // cfg-note 3 {{later used here}}
std::string_view sv = *getTempOptStr(); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
// cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
@@ -299,8 +307,8 @@ std::optional<std::vector<int>> getTempOptVec();
void testLoops() {
for (auto i : getTempVec()) // ok
;
- // FIXME: Detect this using the CFG-based lifetime analysis.
- for (auto i : *getTempOptVec()) // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+ for (auto i : *getTempOptVec()) // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}} cfg-note {{later used here}}
;
}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 24ac72e7aad4d..13666b0402522 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1453,3 +1453,42 @@ void bar() {
(void)x; // expected-note {{used here}}
}
}
+
+namespace reference_type_decl_ref_expr {
+struct S {
+ S();
+ ~S();
+ const std::string& x() const [[clang::lifetimebound]];
+};
+
+const std::string& identity(const std::string& in [[clang::lifetimebound]]);
+const S& identity(const S& in [[clang::lifetimebound]]);
+
+void test_temporary() {
+ const std::string& x = S().x(); // expected-warning {{object whose reference is captured does not live long enough}} expected-note {{destroyed here}}
+ (void)x; // expected-note {{later used here}}
+
+ const std::string& y = identity(S().x()); // expected-warning {{object whose reference is captured does not live long enough}} expected-note {{destroyed here}}
+ (void)y; // expected-note {{later used here}}
+
+ std::string_view z;
+ {
+ S s;
+ const std::string& zz = s.x(); // expected-warning {{object whose reference is captured does not live long enough}}
+ z = zz;
+ } // expected-note {{destroyed here}}
+ (void)z; // expected-note {{later used here}}
+}
+
+void test_lifetime_extension_ok() {
+ const S& x = S();
+ (void)x;
+ const S& y = identity(S()); // expected-warning {{object whose reference is captured does not live long enough}} expected-note {{destroyed here}}
+ (void)y; // expected-note {{later used here}}
+}
+
+const std::string& test_return() {
+ const std::string& x = S().x(); // expected-warning {{object whose reference is captured does not live long enough}} expected-note {{destroyed here}}
+ return x; // expected-note {{later used here}}
+}
+} // namespace reference_type_decl_ref_expr
More information about the cfe-commits
mailing list