[clang] [LifetimeSafety] Track transparent member functions for "all" GSL pointers (PR #177660)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Wed Jan 28 09:36:16 PST 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/177660
>From c21aed2a73bcdea727bd168a0e0f74ad7670d76d Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Fri, 23 Jan 2026 15:58:55 +0000
Subject: [PATCH] Transparent functions for all gsl::Pointers
---
clang/include/clang/Basic/AttrDocs.td | 40 ++++++++++
.../LifetimeSafety/LifetimeAnnotations.cpp | 51 ++++++++-----
clang/test/Sema/warn-lifetime-safety.cpp | 75 +++++++++++++++++++
3 files changed, 147 insertions(+), 19 deletions(-)
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 5b97a91af0adc..b3dcd4410de95 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7679,6 +7679,46 @@ When the Owner's lifetime ends, it will consider the Pointer to be dangling.
P.getInt(); // P is dangling
}
+**Transparent Member Functions**
+
+The analysis automatically tracks certain member functions of ``[[gsl::Pointer]]`` types
+that provide transparent access to the pointed-to object. These include:
+
+* Dereference operators: ``operator*``, ``operator->``
+* Data access methods: ``data()``, ``c_str()``, ``get()``
+* Iterator methods: ``begin()``, ``end()``, ``rbegin()``, ``rend()``, ``cbegin()``, ``cend()``, ``crbegin()``, ``crend()``
+
+When these methods return pointers, view types, or references, the analysis treats them as
+transparently borrowing from the same object that the pointer itself borrows from,
+enabling detection of use-after-free through these access patterns:
+
+.. code-block:: c++
+
+ // For example, .data() here returns a borrow to 's' instead of 'v'.
+ const char* f() {
+ std::string s = "hello";
+ std::string_view v = s; // warning: address of stack memory returned
+ return v.data(); // note: returned here
+ }
+
+ const MyObj& g(MyObj obj) {
+ View v = obj; // warning: address of stack memory returned
+ return *v; // note: returned here
+ }
+
+This tracking also applies to range-based for loops, where the ``begin()`` and ``end()``
+iterators are used to access elements:
+
+.. code-block:: c++
+
+ std::string_view f(std::vector<std::string> vec) {
+ for (const std::string& s : vec) { // warning: address of stack memory returned
+ return s; // note: returned here
+ }
+ }
+
+**Container Template Specialization**
+
If a template class is annotated with ``[[gsl::Owner]]``, and the first
instantiated template argument is a pointer type (raw pointer, or ``[[gsl::Pointer]]``),
the analysis will consider the instantiated class as a container of the pointer.
diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
index dd925d2b8fe6e..be33caf327802 100644
--- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
@@ -13,6 +13,7 @@
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
+#include "llvm/ADT/StringSet.h"
namespace clang::lifetimes {
@@ -107,6 +108,10 @@ bool isPointerLikeType(QualType QT) {
return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
}
+static bool isReferenceOrPointerLikeType(QualType QT) {
+ return QT->isReferenceType() || isPointerLikeType(QT);
+}
+
bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
bool RunningUnderLifetimeSafety) {
if (!Callee)
@@ -115,35 +120,43 @@ bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee,
if (isGslPointerType(Conv->getConversionType()) &&
Callee->getParent()->hasAttr<OwnerAttr>())
return true;
- if (!isInStlNamespace(Callee->getParent()))
- return false;
if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
!isGslOwnerType(Callee->getFunctionObjectParameterType()))
return false;
- // Track dereference operator for GSL pointers in STL. Only do so for lifetime
- // safety analysis and not for Sema's statement-local analysis as it starts
- // to have false-positives.
+ // Begin and end iterators.
+ static const llvm::StringSet<> IteratorMembers = {
+ "begin", "end", "rbegin", "rend", "cbegin", "cend", "crbegin", "crend"};
+ static const llvm::StringSet<> InnerPointerGetters = {
+ // Inner pointer getters.
+ "c_str", "data", "get"};
+ static const llvm::StringSet<> ContainerFindFns = {
+ // Map and set types.
+ "find", "equal_range", "lower_bound", "upper_bound"};
+ // Track dereference operator and transparent functions like begin(), get(),
+ // etc. for all GSL pointers. Only do so for lifetime safety analysis and not
+ // for Sema's statement-local analysis as it starts to have false-positives.
if (RunningUnderLifetimeSafety &&
isGslPointerType(Callee->getFunctionObjectParameterType()) &&
- (Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Star ||
- Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Arrow))
- return true;
+ isReferenceOrPointerLikeType(Callee->getReturnType())) {
+ if (Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Star ||
+ Callee->getOverloadedOperator() == OverloadedOperatorKind::OO_Arrow)
+ return true;
+ if (Callee->getIdentifier() &&
+ (IteratorMembers.contains(Callee->getName()) ||
+ InnerPointerGetters.contains(Callee->getName())))
+ return true;
+ }
+
+ if (!isInStlNamespace(Callee->getParent()))
+ return false;
if (isPointerLikeType(Callee->getReturnType())) {
if (!Callee->getIdentifier())
return false;
- return llvm::StringSwitch<bool>(Callee->getName())
- .Cases(
- {// Begin and end iterators.
- "begin", "end", "rbegin", "rend", "cbegin", "cend", "crbegin",
- "crend",
- // Inner pointer getters.
- "c_str", "data", "get",
- // Map and set types.
- "find", "equal_range", "lower_bound", "upper_bound"},
- true)
- .Default(false);
+ return IteratorMembers.contains(Callee->getName()) ||
+ InnerPointerGetters.contains(Callee->getName()) ||
+ ContainerFindFns.contains(Callee->getName());
}
if (Callee->getReturnType()->isReferenceType()) {
if (!Callee->getIdentifier()) {
diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp
index d6064dea9e545..c80556715fedf 100644
--- a/clang/test/Sema/warn-lifetime-safety.cpp
+++ b/clang/test/Sema/warn-lifetime-safety.cpp
@@ -14,6 +14,7 @@ struct [[gsl::Owner]] MyObj {
MyObj operator+(MyObj);
View getView() const [[clang::lifetimebound]];
+ const int* getData() const [[clang::lifetimebound]];
};
struct [[gsl::Owner]] MyTrivialObj {
@@ -25,6 +26,10 @@ struct [[gsl::Pointer()]] View {
View(const MyTrivialObj &); // Borrows from MyTrivialObj
View();
void use() const;
+
+ const MyObj* data() const;
+ const MyObj& operator*() const;
+ const MyObj* operator->() const;
};
class TriviallyDestructedClass {
@@ -1455,6 +1460,76 @@ void bar() {
}
}
+namespace DereferenceViews {
+const MyObj& testDeref(MyObj obj) {
+ View v = obj; // expected-warning {{address of stack memory is returned later}}
+ return *v; // expected-note {{returned here}}
+}
+const MyObj* testDerefAddr(MyObj obj) {
+ View v = obj; // expected-warning {{address of stack memory is returned later}}
+ return &*v; // expected-note {{returned here}}
+}
+const MyObj* testData(MyObj obj) {
+ View v = obj; // expected-warning {{address of stack memory is returned later}}
+ return v.data(); // expected-note {{returned here}}
+}
+const int* testLifetimeboundAccessorOfMyObj(MyObj obj) {
+ View v = obj; // expected-warning {{address of stack memory is returned later}}
+ const MyObj* ptr = v.data();
+ return ptr->getData(); // expected-note {{returned here}}
+}
+const int* testLifetimeboundAccessorOfMyObjThroughDeref(MyObj obj) {
+ View v = obj; // expected-warning {{address of stack memory is returned later}}
+ return v->getData(); // expected-note {{returned here}}
+}
+} // namespace DereferenceViews
+
+namespace ViewsBeginEndIterators {
+template <typename T>
+struct [[gsl::Pointer]] Iterator {
+ Iterator operator++();
+ T& operator*() const;
+ T* operator->() const;
+ bool operator!=(const Iterator& other) const;
+};
+
+template <typename T>
+struct [[gsl::Owner]] Container {
+using It = Iterator<T>;
+It begin() const [[clang::lifetimebound]];
+It end() const [[clang::lifetimebound]];
+};
+
+MyObj Global;
+
+const MyObj& ContainerMyObjReturnRef(Container<MyObj> c) {
+ for (const MyObj& x : c) { // expected-warning {{address of stack memory is returned later}}
+ return x; // expected-note {{returned here}}
+ }
+ return Global;
+}
+
+View ContainerMyObjReturnView(Container<MyObj> c) {
+ for (const MyObj& x : c) { // expected-warning {{address of stack memory is returned later}}
+ return x; // expected-note {{returned here}}
+ }
+ for (View x : c) { // expected-warning {{address of stack memory is returned later}}
+ return x; // expected-note {{returned here}}
+ }
+ return Global;
+}
+
+View ContainerViewsOk(Container<View> c) {
+ for (View x : c) {
+ return x;
+ }
+ for (const View& x : c) {
+ return x;
+ }
+ return Global;
+}
+} // namespace ViewsBeginEndIterators
+
namespace reference_type_decl_ref_expr {
struct S {
S();
More information about the cfe-commits
mailing list