Author: Utkarsh Saxena
Date: 2026-01-28T18:06:05Z
New Revision: b52dc8cc8c96a327c787b0155a6bbe91170d426b

URL: 
https://github.com/llvm/llvm-project/commit/b52dc8cc8c96a327c787b0155a6bbe91170d426b
DIFF: 
https://github.com/llvm/llvm-project/commit/b52dc8cc8c96a327c787b0155a6bbe91170d426b.diff

LOG: [LifetimeSafety] Track transparent member functions for "all" GSL pointers 
(#177660)

Track transparent member functions (like data, begin, end, operator*,
etc) for all gsl::Pointer types and not just for STL types. This is a
change in semantics of `gsl::Pointer` annotations which now handles
specially **named** functions differently.

1. Tracking more methods that return pointers or references to objects
owned by the implicit object argument
2. Ensuring that methods like `data()`, dereference operators, and
begin/end iterators are properly tracked

The changes allow the analyzer to detect more cases where addresses of
stack memory are returned, particularly when working with GSL pointer
types that provide access to their underlying objects through methods
like `data()` or operators like `*` and `->`.

One particular idiom that is now detected is iterators involved in
range-based for-loops.

```cpp

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;
}
```

Added: 
    

Modified: 
    clang/include/clang/Basic/AttrDocs.td
    clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
    clang/test/Sema/warn-lifetime-safety.cpp

Removed: 
    


################################################################################
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();


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

Reply via email to