https://github.com/usx95 updated 
https://github.com/llvm/llvm-project/pull/177660

>From e5902caee14a71a5a87c8dfa3e6fb8da41099f20 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <[email protected]>
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..2b932a679239d 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'.
+  std::string_view 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();

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to