[clang] [LifetimeSafety] Fix false negative for GSL Owner type with arrow operator (PR #184725)
Zhijie Wang via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 5 12:04:20 PST 2026
https://github.com/aeft updated https://github.com/llvm/llvm-project/pull/184725
>From 8f6bd5019fdc151da437f6c6c3be6c4c05e0719d Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Wed, 4 Mar 2026 17:50:08 -0800
Subject: [PATCH 1/3] [LifetimeSafety] Fix false negative for GSL Owner type
with arrow operator
---
.../LifetimeSafety/LifetimeAnnotations.cpp | 7 +++-
clang/test/Sema/Inputs/lifetime-analysis.h | 2 +
.../Sema/warn-lifetime-analysis-nocfg.cpp | 40 +++++++++++++++++++
clang/test/Sema/warn-lifetime-safety.cpp | 29 ++++++++++++++
4 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 0d3da898137a6..1be35cd698669 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -152,8 +152,11 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
return false;
if (isPointerLikeType(Callee->getReturnType())) {
- if (!Callee->getIdentifier())
- return false;
+ if (!Callee->getIdentifier()) {
+ return Callee->getParent()->hasAttr<OwnerAttr>() &&
+ Callee->getOverloadedOperator() ==
+ OverloadedOperatorKind::OO_Arrow;
+ }
return IteratorMembers.contains(Callee->getName()) ||
InnerPointerGetters.contains(Callee->getName()) ||
ContainerFindFns.contains(Callee->getName());
diff --git a/clang/test/Sema/Inputs/lifetime-analysis.h b/clang/test/Sema/Inputs/lifetime-analysis.h
index 1b07f4f13467f..85b5a5fe5e07f 100644
--- a/clang/test/Sema/Inputs/lifetime-analysis.h
+++ b/clang/test/Sema/Inputs/lifetime-analysis.h
@@ -188,6 +188,7 @@ struct unique_ptr {
~unique_ptr();
T* release();
T &operator*();
+ T *operator->();
T *get() const;
};
@@ -204,6 +205,7 @@ struct optional {
template<typename U>
optional(optional<U>&& __t);
+ T *operator->();
T &operator*() &;
T &&operator*() &&;
T &value() &;
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 3305e9e270d86..534f075a6a8b9 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1287,3 +1287,43 @@ void test() {
const auto ptrTSC = StringTemplateSpecC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
}
} // namespace GH175391
+
+namespace owner_arrow {
+struct TypeParamType {
+ std::string_view name() [[clang::lifetimebound]];
+};
+
+void test_optional_arrow_lifetimebound() {
+ std::string_view a;
+ a = std::optional<TypeParamType>()->name(); // expected-warning {{object backing the pointer 'a' 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(a); // cfg-note {{later used here}}
+}
+
+void test_optional_arrow_data() {
+ const char* p = std::optional<std::string>()->data(); // 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(p); // cfg-note {{later used here}}
+}
+
+void test_optional_arrow_non_temporary() {
+ std::optional<std::string> opt;
+ const char* p = opt->data();
+ use(p);
+}
+
+void test_unique_ptr_arrow_data() {
+ const char* p = std::unique_ptr<std::string>()->data(); // 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(p); // cfg-note {{later used here}}
+}
+
+void test_unique_ptr_arrow_non_temporary() {
+ std::unique_ptr<std::string> up;
+ const char* p = up->data();
+ use(p);
+}
+} // namespace owner_arrow
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 097f3279d8e54..45cf26528d689 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1746,3 +1746,32 @@ View test3(std::string a) {
return b; // expected-note {{returned here}}
}
} // namespace non_trivial_views
+
+namespace OwnerArrowOperator {
+void test_optional_arrow() {
+ const char* p;
+ {
+ std::optional<std::string> opt;
+ p = opt->data(); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+
+void test_optional_arrow_lifetimebound() {
+ View v;
+ {
+ std::optional<MyObj> opt;
+ v = opt->getView(); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ v.use(); // expected-note {{later used here}}
+}
+
+void test_unique_ptr_arrow() {
+ const char* p;
+ {
+ std::unique_ptr<std::string> up;
+ p = up->data(); // expected-warning {{object whose reference is captured does not live long enough}}
+ } // expected-note {{destroyed here}}
+ (void)*p; // expected-note {{later used here}}
+}
+} // namespace OwnerArrowOperator
>From 8951733d6f23cdc206c6a6f677f21ef305b21738 Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Thu, 5 Mar 2026 10:29:57 -0800
Subject: [PATCH 2/3] fix false positive for sema analysis
---
.../LifetimeSafety/LifetimeAnnotations.cpp | 14 +++++++++++---
clang/test/Sema/warn-lifetime-analysis-nocfg.cpp | 16 ++++++++++++++++
clang/test/Sema/warn-lifetime-safety.cpp | 9 +++++++++
3 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 1be35cd698669..2eb22f768cad9 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -153,9 +153,17 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
if (isPointerLikeType(Callee->getReturnType())) {
if (!Callee->getIdentifier()) {
- return Callee->getParent()->hasAttr<OwnerAttr>() &&
- Callee->getOverloadedOperator() ==
- OverloadedOperatorKind::OO_Arrow;
+ // e.g., std::optional<T>::operator->() returns T*.
+ if (Callee->getParent()->hasAttr<OwnerAttr>() &&
+ Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Arrow) {
+ if (RunningUnderLifetimeSafety)
+ return true;
+ // For Sema analysis, don't track operator-> when the pointee is a GSL
+ // Pointer (e.g., optional<string_view>), as Sema can't distinguish the
+ // Pointer object's lifetime from the data it observes.
+ return !isGslPointerType(Callee->getReturnType()->getPointeeType());
+ }
+ return false;
}
return IteratorMembers.contains(Callee->getName()) ||
InnerPointerGetters.contains(Callee->getName()) ||
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 534f075a6a8b9..315c7ce0964d3 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1326,4 +1326,20 @@ void test_unique_ptr_arrow_non_temporary() {
const char* p = up->data();
use(p);
}
+
+void test_optional_view_arrow_data() {
+ const char* p;
+ p = std::optional<std::string_view>()->data();
+ (void)*p;
+}
+
+void test_optional_view_arrow() {
+ // FIXME: Sema analysis doesn't warn here because we suppress operator->
+ // tracking when the pointee is a GSL Pointer to avoid false positives (see
+ // test_optional_view_arrow_data). The CFG-based lifetime safety analysis
+ // handles this correctly.
+ std::string_view* p = std::optional<std::string_view>().operator->(); // cfg-warning {{object whose reference is captured does not live long enough}} \
+ // cfg-note {{destroyed here}}
+ (void)*p; // cfg-note {{later used here}}
+}
} // namespace owner_arrow
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index 45cf26528d689..a75c70aa3674a 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -1774,4 +1774,13 @@ void test_unique_ptr_arrow() {
} // expected-note {{destroyed here}}
(void)*p; // expected-note {{later used here}}
}
+
+void test_optional_view_arrow() {
+ const char* p;
+ {
+ std::optional<std::string_view> opt;
+ p = opt->data();
+ }
+ (void)*p;
+}
} // namespace OwnerArrowOperator
>From bb4bbcdcd9d32f91c0afc6be7477736920e3a7be Mon Sep 17 00:00:00 2001
From: Alex Wang <yesterda9 at gmail.com>
Date: Thu, 5 Mar 2026 12:04:07 -0800
Subject: [PATCH 3/3] remove handling for sema analysis
---
.../LifetimeSafety/LifetimeAnnotations.cpp | 15 ++---
.../Sema/warn-lifetime-analysis-nocfg.cpp | 58 +------------------
2 files changed, 6 insertions(+), 67 deletions(-)
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index 2eb22f768cad9..6cbb03720ed06 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -154,16 +154,11 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
if (isPointerLikeType(Callee->getReturnType())) {
if (!Callee->getIdentifier()) {
// e.g., std::optional<T>::operator->() returns T*.
- if (Callee->getParent()->hasAttr<OwnerAttr>() &&
- Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Arrow) {
- if (RunningUnderLifetimeSafety)
- return true;
- // For Sema analysis, don't track operator-> when the pointee is a GSL
- // Pointer (e.g., optional<string_view>), as Sema can't distinguish the
- // Pointer object's lifetime from the data it observes.
- return !isGslPointerType(Callee->getReturnType()->getPointeeType());
- }
- return false;
+ return RunningUnderLifetimeSafety
+ ? Callee->getParent()->hasAttr<OwnerAttr>() &&
+ Callee->getOverloadedOperator() ==
+ OverloadedOperatorKind::OO_Arrow
+ : false;
}
return IteratorMembers.contains(Callee->getName()) ||
InnerPointerGetters.contains(Callee->getName()) ||
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 315c7ce0964d3..65d323b27c977 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -1286,60 +1286,4 @@ void test() {
const auto ptrTSB = StringTemplateSpecB<char>().data(); // Definition-only attribute // expected-warning {{temporary whose address is used}}
const auto ptrTSC = StringTemplateSpecC<char>().data(); // Both have attribute // expected-warning {{temporary whose address is used}}
}
-} // namespace GH175391
-
-namespace owner_arrow {
-struct TypeParamType {
- std::string_view name() [[clang::lifetimebound]];
-};
-
-void test_optional_arrow_lifetimebound() {
- std::string_view a;
- a = std::optional<TypeParamType>()->name(); // expected-warning {{object backing the pointer 'a' 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(a); // cfg-note {{later used here}}
-}
-
-void test_optional_arrow_data() {
- const char* p = std::optional<std::string>()->data(); // 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(p); // cfg-note {{later used here}}
-}
-
-void test_optional_arrow_non_temporary() {
- std::optional<std::string> opt;
- const char* p = opt->data();
- use(p);
-}
-
-void test_unique_ptr_arrow_data() {
- const char* p = std::unique_ptr<std::string>()->data(); // 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(p); // cfg-note {{later used here}}
-}
-
-void test_unique_ptr_arrow_non_temporary() {
- std::unique_ptr<std::string> up;
- const char* p = up->data();
- use(p);
-}
-
-void test_optional_view_arrow_data() {
- const char* p;
- p = std::optional<std::string_view>()->data();
- (void)*p;
-}
-
-void test_optional_view_arrow() {
- // FIXME: Sema analysis doesn't warn here because we suppress operator->
- // tracking when the pointee is a GSL Pointer to avoid false positives (see
- // test_optional_view_arrow_data). The CFG-based lifetime safety analysis
- // handles this correctly.
- std::string_view* p = std::optional<std::string_view>().operator->(); // cfg-warning {{object whose reference is captured does not live long enough}} \
- // cfg-note {{destroyed here}}
- (void)*p; // cfg-note {{later used here}}
-}
-} // namespace owner_arrow
+} // namespace GH175391
\ No newline at end of file
More information about the cfe-commits
mailing list