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
