[llvm-branch-commits] [clang] [LifetimeSafety] Detect dangling references to field members (PR #176805)
Utkarsh Saxena via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Jan 19 11:46:56 PST 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/176805
>From 45af9400622db8746f960b42e96c3955136db947 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Mon, 19 Jan 2026 19:28:46 +0000
Subject: [PATCH] Detect dangling references to field members
---
.../Analyses/LifetimeSafety/FactsGenerator.h | 1 +
.../LifetimeSafety/FactsGenerator.cpp | 15 ++++++++++
.../Sema/warn-lifetime-analysis-nocfg.cpp | 30 +++++++++----------
.../Sema/warn-lifetime-safety-suggestions.cpp | 6 ++--
clang/test/Sema/warn-lifetime-safety.cpp | 28 +++++++++++++++++
5 files changed, 62 insertions(+), 18 deletions(-)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
index ee2f1aae4a4c5..a47505ee9f159 100644
--- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
+++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h
@@ -37,6 +37,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
void VisitDeclRefExpr(const DeclRefExpr *DRE);
void VisitCXXConstructExpr(const CXXConstructExpr *CCE);
void VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE);
+ void VisitMemberExpr(const MemberExpr *ME);
void VisitCallExpr(const CallExpr *CE);
void VisitCXXNullPtrLiteralExpr(const CXXNullPtrLiteralExpr *N);
void VisitImplicitCastExpr(const ImplicitCastExpr *ICE);
diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
index bbab0627bdead..ccedd0522cbb3 100644
--- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
@@ -203,6 +203,21 @@ void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
}
}
+void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
+ if (isa<FieldDecl>(ME->getMemberDecl())) {
+ 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.
+ CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
+ Dst->getOuterOriginID(), Src->getOuterOriginID(),
+ /*Kill=*/true));
+ }
+}
+
static bool isStdMove(const FunctionDecl *FD) {
return FD && FD->isInStdNamespace() && FD->getIdentifier() &&
FD->getName() == "move";
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 5b6a548389bd5..cb06ec4930907 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1009,15 +1009,12 @@ void operator_star_arrow_of_iterators_false_positive_no_cfg_analysis() {
const char* q = (*v.begin()).second.data();
const std::string& r = (*v.begin()).second;
- // FIXME: Detect this using the CFG-based lifetime analysis.
- // Detect dangling references to struct field.
- // https://github.com/llvm/llvm-project/issues/176144
auto temporary = []() { return std::vector<std::pair<int, std::string>>{{1, "1"}}; };
- const char* x = temporary().begin()->second.data();
- const char* y = (*temporary().begin()).second.data();
- const std::string& z = (*temporary().begin()).second;
+ const char* x = temporary().begin()->second.data(); // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ const char* y = (*temporary().begin()).second.data(); // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ const std::string& z = (*temporary().begin()).second; // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
- use(p, q, r, x, y, z);
+ use(p, q, r, x, y, z); // cfg-note 3 {{later used here}}
}
} // namespace iterator_arrow
@@ -1057,24 +1054,27 @@ struct S {
};
struct Q {
const S* get() const [[clang::lifetimebound]];
+ ~Q();
};
std::string_view foo(std::string_view sv [[clang::lifetimebound]]);
-// FIXME: Detect this using the CFG-based lifetime analysis.
-// Detect dangling references to struct field.
-// https://github.com/llvm/llvm-project/issues/176144
void test1() {
std::string_view k1 = S().sv; // OK
- std::string_view k2 = S().s; // expected-warning {{object backing the pointer will}}
+ std::string_view k2 = S().s; // expected-warning {{object backing the pointer will}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
std::string_view k3 = Q().get()->sv; // OK
- std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}}
+ std::string_view k4 = Q().get()->s; // expected-warning {{object backing the pointer will}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
- std::string_view lb1 = foo(S().s); // expected-warning {{object backing the pointer will}}
- std::string_view lb2 = foo(Q().get()->s); // expected-warning {{object backing the pointer will}}
- use(k1, k2, k3, k4, lb1, lb2);
+ std::string_view lb1 = foo(S().s); // expected-warning {{object backing the pointer will}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+ std::string_view lb2 = foo(Q().get()->s); // expected-warning {{object backing the pointer will}} \
+ // cfg-warning {{object whose reference is captured does not live long enough}} cfg-note {{destroyed here}}
+
+ use(k1, k2, k3, k4, lb1, lb2); // cfg-note 4 {{later used here}}
}
struct Bar {};
diff --git a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
index 6e3a6f1fd9117..bb8fd3933b55d 100644
--- a/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
+++ b/clang/test/Sema/warn-lifetime-safety-suggestions.cpp
@@ -110,9 +110,9 @@ struct Container {
MyObj data;
const MyObj& getData() [[clang::lifetimebound]] { return data; }
};
-// FIXME: c.data does not forward loans
-View return_struct_field(const Container& c) {
- return c.data;
+
+View return_struct_field(const Container& c) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
+ return c.data; // expected-note {{param returned here}}
}
View return_struct_lifetimebound_getter(Container& c) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
return c.getData().getView(); // expected-note {{param returned here}}
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 13666b0402522..84a6bc16ba52a 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1492,3 +1492,31 @@ const std::string& test_return() {
return x; // expected-note {{later used here}}
}
} // namespace reference_type_decl_ref_expr
+
+namespace field_access{
+
+struct S {
+ std::string s;
+ std::string_view sv;
+};
+
+void uaf() {
+ std::string_view view;
+ {
+ S str;
+ S* p = &str; // expected-warning {{object whose reference is captured does not live long enough}}
+ view = p->s;
+ } // expected-note {{destroyed here}}
+ (void)view; // expected-note {{later used here}}
+}
+
+void not_uaf() {
+ std::string_view view;
+ {
+ S str;
+ S* p = &str;
+ view = p->sv;
+ }
+ (void)view;
+}
+} // namespace field_access
More information about the llvm-branch-commits
mailing list