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
