https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/183058
>From 20719aca4f7222109ef0ab437f7127ba9cac45dc Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Tue, 24 Feb 2026 13:59:05 +0000 Subject: [PATCH 1/3] user-docs --- clang/docs/LifetimeSafety.rst | 449 ++++++++++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 clang/docs/LifetimeSafety.rst diff --git a/clang/docs/LifetimeSafety.rst b/clang/docs/LifetimeSafety.rst new file mode 100644 index 0000000000000..4c27f3aea5244 --- /dev/null +++ b/clang/docs/LifetimeSafety.rst @@ -0,0 +1,449 @@ +====================== +Lifetime Safety Analysis +====================== + +.. contents:: + :local: + +Introduction +============ + +Clang Lifetime Safety Analysis is a C++ language extension which warns about +potential dangling pointer defects in code. The analysis aims to detect +when a pointer, reference or view type (such as ``std::string_view``) refers to an object +that is no longer alive, a condition that leads to use-after-free bugs and +security vulnerabilities. Common examples include pointers to stack variables +that have gone out of scope, fields holding views to stack-allocated objects +(dangling-field), returning pointers/references to stack variables +(return stack address) or iterators into container elements invalidated by +container operations (e.g., ``std::vector::push_back``) + +The analysis design is inspired by `Polonius, the Rust borrow checker <https://github.com/rust-lang/polonius>`_, +but adapted to C++ idioms and constraints, such as the lack of borrow checker exclusivity (alias-xor-mutability). +Further details on the analysis method can be found in the `RFC on Discourse <https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291/>`_. + +This is compile-time analysis; there is no run-time overhead. +It tracks pointer validity through intra-procedural data-flow analysis, supporting a form of gradual typing. While it does +not require lifetime annotations to get started, in their absence, the analysis +treats function calls with opaque semantics, potentially missing dangling pointer issues or producing false positives. As more functions are annotated +with attributes like ``[[clang::lifetimebound]]``, ``[[gsl::Owner]]``, and +``[[gsl::Pointer]]``, the analysis can see through these contracts and enforce +lifetime safety at call sites with higher accuracy. This approach supports +gradual adoption in existing codebases. It is still very much under active development, +but it is mature enough to be used in production codebases. + +Getting Started +---------------- + +.. code-block:: c++ + + #include <string> + #include <string_view> + + void simple_dangle() { + std::string_view v; + { + std::string s = "hello"; + v = s; // 'v' borrows from 's'. + } // 's' is destroyed here, 'v' becomes dangling. + (void)v; // WARNING! 'v' is used after 's' has been destroyed. + } + +This example demonstrates +a basic use-after-scope defect. The ``std::string_view`` object ``v`` holds a +reference to ``s``, a ``std::string``. When ``s`` goes out of +scope at the end of the inner block, ``v`` becomes a dangling reference, and +its subsequent use is flagged by the analysis. + +Running The Analysis +-------------------- + +To run the analysis, compile with the ``-Wlifetime-safety`` flag, e.g. + +.. code-block:: bash + + clang -c -Wlifetime-safety example.cpp + +This flag enables a core set of lifetime safety checks. For more fine-grained +control over warnings, see :ref:`warning_flags`. + +Lifetime Annotations +==================== + +While lifetime analysis can detect many issues without annotations, its +precision increases significantly when types and functions are annotated with +lifetime contracts. These annotations clarify ownership semantics and lifetime +dependencies, enabling the analysis to reason more accurately about pointer +validity across function calls. + +Owner and Pointer Types +----------------------- + +Lifetime analysis distinguishes between types that own the data they point to +(Owners) and types that are non-owning views or references to data owned by +others (Pointers). This distinction is made using GSL-style attributes: + +* ``[[gsl::Owner]]``: For types that manage the lifetime of a resource, + like ``std::string``, ``std::vector``, ``std::unique_ptr``. +* ``[[gsl::Pointer]]``: For non-owning types that borrow resources, + like ``std::string_view``, ``gsl::span``, or raw pointers (which are + implicitly treated as pointers). + +Many common STL types, such as ``std::string_view`` and container iterators, +are automatically recognized as Pointers or Owners. You can annotate your own +types using these attributes: + +.. code-block:: c++ + + #include <string> + #include <string_view> + + // Owning type + struct [[gsl::Owner]] MyObj { + std::string Data = "Hello"; + }; + + // Non-owning view type + struct [[gsl::Pointer]] View { + std::string_view SV; + View() = default; + View(const MyObj& O) : SV(O.Data) {} + void use() const {} + }; + + void test() { + View v; + { + MyObj o; + v = o; + } // o is destroyed + v.use(); // WARNING: object whose reference is captured does not live long enough + } + +Without these annotations, the analysis may not be able to determine whether a +type is owning or borrowing, which can affect analysis precision. For more +details on these attributes, see the Clang attribute reference for +`gsl::Owner <https://clang.llvm.org/docs/AttributeReference.html#gsl-owner>`_ and +`gsl::Pointer <https://clang.llvm.org/docs/AttributeReference.html#gsl-pointer>`_. + +LifetimeBound +------------- + +The ``[[clang::lifetimebound]]`` attribute can be applied to function parameters +or to the implicit ``this`` parameter of a method (by placing it after the +method declarator). It indicates that the returned pointer or reference is +valid only as long as the attributed parameter or ``this`` object is alive. +This is crucial for functions that return views or references to their +arguments. + +.. code-block:: c++ + + #include <string> + #include <string_view> + + struct MyOwner { + std::string s; + std::string_view getView() const [[clang::lifetimebound]] { return s; } + }; + + void test_lifetimebound() { + std::string_view sv; + sv = MyOwner().getView(); // getView() is called on a temporary MyOwner + // MyOwner temporary is destroyed here. + (void)sv; // WARNING: object whose reference is captured does not live long enough + } + +Without ``[[clang::lifetimebound]]`` on ``getView()``, the analysis would not +know that the value returned by ``getView()`` depends on the temporary +``MyOwner`` object, and it would not be able to diagnose the dangling ``sv``. + +For more details, see `lifetimebound <https://clang.llvm.org/docs/AttributeReference.html#lifetimebound>`_. + +NoEscape +-------- + +The ``[[clang::noescape]]`` attribute can be applied to function parameters of +pointer or reference type. It indicates that the function will not allow the +parameter to escape its scope, for example, by returning it or assigning it to +a field or global variable. This is useful for parameters passed to callbacks +or visitors that are only used during the call and not stored. + +For more details, see `noescape <https://clang.llvm.org/docs/AttributeReference.html#noescape>`_. + +Checks Performed +================ + +Use-After-Scope +--------------- + +This is the simplest dangling pointer scenario, where a pointer or reference +outlives the stack variable it refers to. + +.. code-block:: c++ + + void use_after_scope() { + int* p; + { + int i = 0; + p = &i; // p borrows from i + } // i is destroyed, p dangles + (void)*p; // WARNING: use-after-scope + } + +Return of stack address +----------------------- + +This check warns when a function returns a pointer or reference to a +stack-allocated variable, which will be destroyed when the function returns, +leaving the caller with a dangling pointer. + +.. code-block:: c++ + + #include <string> + #include <string_view> + + std::string_view return_stack_string_view() { + std::string s = "hello"; + return s; // WARNING: address of stack memory is returned + } + +Dangling field +-------------- + +This check warns when a constructor or method assigns a pointer to a +stack-allocated variable or temporary to a field of the class, and the +stack variable's lifetime is shorter than the object's lifetime. + +.. code-block:: c++ + + #include <string> + #include <string_view> + + struct DanglingField { + std::string_view view; + // WARNING: 's' is a temporary that will be destroyed after the + // constructor finishes, leaving 'view' dangling. + DanglingField(std::string s) : view(s) {} + }; + +Use-after-invalidation (experimental) +------------------------------------- + +This check warns when a reference to a container element (such as an iterator, +pointer or reference) is used after a container operation that may have +invalidated it. For example, adding elements to ``std::vector`` may cause +reallocation, invalidating all existing iterators, pointers and references to +its elements. + +.. note:: + Container invalidation checking is highly experimental and may produce false + positives or miss some invalidations. Field-sensitivity is also limited. + +.. code-block:: c++ + + #include <vector> + + void use_after_invalidation(std::vector<int>& v) { + int* p = &v[0]; + v.push_back(4); // push_back might reallocate and invalidate p + *p = 10; // WARNING: use after invalidation + } + +Annotation Inference and Suggestions +==================================== + +In addition to detecting lifetime violations, the analysis can suggest adding +``[[clang::lifetimebound]]`` to function parameters or methods when it detects +that a pointer/reference to a parameter or ``this`` escapes via the return +value. This helps improve API contracts and allows the analysis to perform +more accurate checks in calling code. + +To enable annotation suggestions, use ``-Wlifetime-safety-suggestions``. + +.. code-block:: c++ + + #include <string_view> + + // The analysis will suggest adding [[clang::lifetimebound]] to 'a' + // because 'a' is returned. + std::string_view return_view(std::string_view a) { // warning: parameter in intra-TU function should be marked [[clang::lifetimebound]] + return a; // note: param returned here + } + +TU-Wide analysis and Inference +------------------------------ + +By default, lifetime analysis is intra-procedural for error checking. +However, for annotation inference to be effective, lifetime information needs +to propagate across function calls. You can enable experimental +Translation-Unit-wide analysis using: + +* ``-flifetime-safety-inference``: Enables inference of ``lifetimebound`` + attributes across functions in a TU. +* ``-fexperimental-lifetime-safety-tu-analysis``: Enables TU-wide analysis + for better inference results. + +.. _warning_flags: + +Warning flags +============= + +Lifetime safety warnings are organized into hierarchical groups, allowing users to +enable categories of checks incrementally. For example, ``-Wlifetime-safety`` +enables all dangling pointer checks, while ``-Wlifetime-safety-permissive`` +enables only the high-confidence subset of these checks. + +* **``-Wlifetime-safety-all``**: Enables all lifetime safety warnings, including + dangling pointer checks, annotation suggestions, and annotation validations. + +* **``-Wlifetime-safety``**: Enables dangling pointer checks from both the + ``permissive`` and ``strict`` groups listed below. + * **``-Wlifetime-safety-permissive``**: Enables high-confidence checks for dangling + pointers. Recommended for initial adoption. + * **``-Wlifetime-safety-use-after-scope``**: Warns when a pointer to + a stack variable is used after the variable's lifetime has ended. + * **``-Wlifetime-safety-return-stack-addr``**: Warns when a function + returns a pointer or reference to one of its local stack variables. + * **``-Wlifetime-safety-dangling-field``**: Warns when a class field is + assigned a pointer to a temporary or stack variable whose lifetime + is shorter than the class instance. + * **``-Wlifetime-safety-strict``**: Enables stricter and experimental checks. These + may produce false positives in code that uses move semantics heavily, as + the analysis might conservatively assume a use-after-free even if + ownership was transferred. + * **``-Wlifetime-safety-use-after-scope-moved``**: Same as + ``-Wlifetime-safety-use-after-scope`` but for cases where the + variable may have been moved from before its destruction. + * **``-Wlifetime-safety-return-stack-addr-moved``**: Same as + ``-Wlifetime-safety-return-stack-addr`` but for cases where the + variable may have been moved from. + * **``-Wlifetime-safety-dangling-field-moved``**: Same as + ``-Wlifetime-safety-dangling-field`` but for cases where the + variable may have been moved from. + * **``-Wlifetime-safety-invalidation``**: Warns when a container + iterator or reference to an element is used after an operation + that may invalidate it (Experimental). + +* **``-Wlifetime-safety-suggestions``**: Enables suggestions to add + ``[[clang::lifetimebound]]`` to function parameters and ``this`` + parameters. + * **``-Wlifetime-safety-intra-tu-suggestions``**: Suggestions for functions + local to the translation unit. + * **``-Wlifetime-safety-cross-tu-suggestions``**: Suggestions for functions + visible across translation units (e.g., in headers). + +* **``-Wlifetime-safety-validations``**: Enables checks that validate existing + lifetime annotations. + * **``-Wlifetime-safety-noescape``**: Warns when a parameter marked with + ``[[clang::noescape]]`` escapes the function. + +Limitations +=========== + +Move Semantics and False Positives +---------------------------------- +When an object is moved from, its state becomes unspecified. If pointers or +views were created that refer to the object *before* it was moved, those +pointers may become invalid after the move. Because the analysis cannot always +know if a move operation invalidates outstanding pointers or simply transfers +ownership, it issues ``-Wlifetime-safety-*-moved`` warnings in these situations. +These warnings indicate a *potential* dangling issue but may be false positives +if ownership was safely transferred and the resource remains alive. +``std::unique_ptr::release()`` is treated similarly to ``std::move()`` in this +regard, as it also relinquishes ownership. + +To avoid these warnings and prevent potential bugs, follow the +**"move-first-then-alias"** pattern: ensure that views or raw pointers are +created *after* a potential move, sourcing them from the new owner rather than +aliasing an object that is about to be moved. + +For example, when initializing fields in a constructor, moving from a parameter *after* using it to initialize a view field can lead to warnings: + +.. code-block:: c++ + + #include <string> + #include <string_view> + #include <utility> + + struct BadFieldOrder { + std::string_view view; + std::string s_owned; + // WARNING: 'view' is initialized from 's', then 's' is moved-from, + // leaving 'view' pointing to a moved-from string. + BadFieldOrder(std::string s) : view(s), s_owned(std::move(s)) {} // -Wlifetime-safety-dangling-field-moved + }; + + // CORRECT: Move 's' into 's_owned' first, then initialize 'view' from 's_owned'. + struct GoodFieldOrder { + std::string s_owned; + std::string_view view; + GoodFieldOrder(std::string s) : s_owned(std::move(s)), view(s_owned) {} // OK + }; + +The same principle applies when creating other aliases via ``get()`` or ``release()`` before moving or releasing ownership: + +.. code-block:: c++ + + #include <memory> + #include <utility> + + void use(int*); + void take_ownership(std::unique_ptr<int>); + + void test_aliasing_before_move() { + int* p; + { + auto u = std::make_unique<int>(1); + p = u.get(); // p aliases u's content + take_ownership(std::move(u)); // u is moved-from + } + // 'p' now points to memory whose ownership was transferred, + // and it might be invalid depending on what take_ownership does. + use(p); // WARNING: -Wlifetime-safety-use-after-scope-moved + } + +Dangling Fields and Intra-Procedural Analysis +--------------------------------------------- +The lifetime analysis is intra-procedural. It analyzes one function or method at +a time. +This means if a field is assigned a pointer to a local variable or temporary +inside a constructor or method, and that local's lifetime ends before the method +returns, the analysis will issue a ``-Wlifetime-safety-dangling-field`` warning. +It must do so even if no *other* method of the class ever accesses this field, +because it cannot see how other methods are implemented or used. + +.. code-block:: c++ + + #include <string> + #include <string_view> + + struct MyWidget { + std::string_view name_; + MyWidget(std::string name) : name_(name) {} // WARNING: 'name' is destroyed when ctor ends, leaving 'name_' dangling + const char* data() { return name_.data(); } // Potential use-after-free if called + }; + +In this case, ``name_`` dangles after the constructor finishes. +Even if ``data()`` is never called, the analysis flags the dangling assignment +in the constructor because it represents a latent bug. +The recommended approach is to ensure fields only point to objects that outlive +the field itself, for example by storing an owned object (e.g., ``std::string``) +or ensuring the borrowed object (e.g., one passed by ``const&``) has a +sufficient lifetime. + + +Heap and Globals +---------------- + +Currently, the analysis focuses on dangling pointers to stack variables, +temporaries, and function parameters. It does not track lifetimes of heap- +allocated memory or global variables. + +Performance +=========== + +Lifetime analysis relies on Clang's CFG (Control Flow Graph). For functions +with very large or complex CFGs, analysis time can be significant. To mitigate +this, the analysis will skip functions where the number of CFG blocks exceeds +a certain threshold, controlled by the ``-flifetime-safety-max-cfg-blocks=N`` language +option. >From f2264d87b54266a5b3ab0edc6600830d394df8bf Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Tue, 24 Feb 2026 15:06:25 +0100 Subject: [PATCH 2/3] Apply changes from code browser Apply changes from code browser --- clang/docs/LifetimeSafety.rst | 41 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/clang/docs/LifetimeSafety.rst b/clang/docs/LifetimeSafety.rst index 4c27f3aea5244..6a4f18ef50e53 100644 --- a/clang/docs/LifetimeSafety.rst +++ b/clang/docs/LifetimeSafety.rst @@ -357,28 +357,35 @@ To avoid these warnings and prevent potential bugs, follow the created *after* a potential move, sourcing them from the new owner rather than aliasing an object that is about to be moved. -For example, when initializing fields in a constructor, moving from a parameter *after* using it to initialize a view field can lead to warnings: +For example: .. code-block:: c++ - #include <string> - #include <string_view> - #include <utility> + #include <memory> - struct BadFieldOrder { - std::string_view view; - std::string s_owned; - // WARNING: 'view' is initialized from 's', then 's' is moved-from, - // leaving 'view' pointing to a moved-from string. - BadFieldOrder(std::string s) : view(s), s_owned(std::move(s)) {} // -Wlifetime-safety-dangling-field-moved - }; + void use(int*); + void take(std::unique_ptr<int>&&); - // CORRECT: Move 's' into 's_owned' first, then initialize 'view' from 's_owned'. - struct GoodFieldOrder { - std::string s_owned; - std::string_view view; - GoodFieldOrder(std::string s) : s_owned(std::move(s)), view(s_owned) {} // OK - }; + void bar() { + std::unique_ptr<int> b; + int* p; + { + auto a = std::make_unique<int>(42); + p = a.get(); // p aliases a's content + b = std::move(a); // a is moved-from + } + use(p); // WARNING: -Wlifetime-safety-use-after-scope-moved + } + + void foo() { + int* p; + { + auto a = std::make_unique<int>(42); + p = a.get(); // p aliases a's content + take(std::move(a)); // a is moved-from and goes out of scope + } + use(p); // WARNING: -Wlifetime-safety-use-after-scope-moved + } The same principle applies when creating other aliases via ``get()`` or ``release()`` before moving or releasing ownership: >From a4985805d9c8b4b0ad0964cc2716386e33f5937f Mon Sep 17 00:00:00 2001 From: Utkarsh Saxena <[email protected]> Date: Tue, 24 Feb 2026 15:11:33 +0100 Subject: [PATCH 3/3] Apply changes from code browser Apply changes from code browser --- clang/docs/LifetimeSafety.rst | 458 ++++++++++++++++++++++------------ clang/docs/index.rst | 1 + 2 files changed, 301 insertions(+), 158 deletions(-) diff --git a/clang/docs/LifetimeSafety.rst b/clang/docs/LifetimeSafety.rst index 6a4f18ef50e53..ff657fa42a2fb 100644 --- a/clang/docs/LifetimeSafety.rst +++ b/clang/docs/LifetimeSafety.rst @@ -1,6 +1,6 @@ -====================== +======================== Lifetime Safety Analysis -====================== +======================== .. contents:: :local: @@ -44,16 +44,18 @@ Getting Started std::string_view v; { std::string s = "hello"; - v = s; // 'v' borrows from 's'. - } // 's' is destroyed here, 'v' becomes dangling. - (void)v; // WARNING! 'v' is used after 's' has been destroyed. + v = s; // warning: object whose reference is captured does not live long enough + } // note: destroyed here + std::cout << v; // note: later used here } This example demonstrates -a basic use-after-scope defect. The ``std::string_view`` object ``v`` holds a +a basic use-after-scope bug. The ``std::string_view`` object ``v`` holds a reference to ``s``, a ``std::string``. When ``s`` goes out of -scope at the end of the inner block, ``v`` becomes a dangling reference, and -its subsequent use is flagged by the analysis. +scope at the end of the inner block, ``v`` becomes a dangling reference. +The analysis flags the assignment ``v = s`` as defective because ``s`` is +destroyed while ``v`` is still alive and points to ``s``, and adds a note +to where ``v`` is used after ``s`` has been destroyed. Running The Analysis -------------------- @@ -98,12 +100,12 @@ types using these attributes: #include <string> #include <string_view> - // Owning type + // Owner type struct [[gsl::Owner]] MyObj { std::string Data = "Hello"; }; - // Non-owning view type + // View type struct [[gsl::Pointer]] View { std::string_view SV; View() = default; @@ -115,9 +117,9 @@ types using these attributes: View v; { MyObj o; - v = o; - } // o is destroyed - v.use(); // WARNING: object whose reference is captured does not live long enough + v = o; // warning: object whose reference is captured does not live long enough + } // note: destroyed here + v.use(); // note: later used here } Without these annotations, the analysis may not be able to determine whether a @@ -149,8 +151,9 @@ arguments. void test_lifetimebound() { std::string_view sv; sv = MyOwner().getView(); // getView() is called on a temporary MyOwner - // MyOwner temporary is destroyed here. - (void)sv; // WARNING: object whose reference is captured does not live long enough + // warning: object whose reference is captured does not live long enough + // note: destroyed here + (void)sv; // note: later used here } Without ``[[clang::lifetimebound]]`` on ``getView()``, the analysis would not @@ -173,39 +176,97 @@ For more details, see `noescape <https://clang.llvm.org/docs/AttributeReference. Checks Performed ================ -Use-After-Scope + +.. raw:: html + + <style> + /* Align text to left and add red/green colors */ + table.colored-code-table td, table.colored-code-table th { text-align: left !important; } + table.colored-code-table td:first-child, table.colored-code-table th:first-child { background-color: #ffeaea !important; } + table.colored-code-table td:nth-child(2), table.colored-code-table th:nth-child(2) { background-color: #eafaea !important; } + table.colored-code-table td .highlight, table.colored-code-table td pre { background-color: transparent !important; border: none !important; } + + div.bad-code { background-color: #ffeaea !important; padding: 5px; border-left: 4px solid #ff6b6b; text-align: left !important; } + div.bad-code .highlight, div.bad-code pre { background-color: transparent !important; border: none !important; } + + div.good-code { background-color: #eafaea !important; padding: 5px; border-left: 4px solid #51cf66; text-align: left !important; } + div.good-code .highlight, div.good-code pre { background-color: transparent !important; border: none !important; } + </style> + +Use after scope --------------- This is the simplest dangling pointer scenario, where a pointer or reference outlives the stack variable it refers to. -.. code-block:: c++ - - void use_after_scope() { - int* p; - { - int i = 0; - p = &i; // p borrows from i - } // i is destroyed, p dangles - (void)*p; // WARNING: use-after-scope - } +.. list-table:: + :widths: 50 50 + :header-rows: 1 + :class: colored-code-table + + * - Use after scope + - Correct + * - + .. code-block:: c++ + + void foo() { + int* p; + { + int i = 0; + p = &i; // warning: 'p' does not live long enough + } // note: destroyed here + (void)*p; // note: later used here + } + - + .. code-block:: c++ + + void foo() { + int i = 0; + int* p; + { + p = &i; // OK! + } + (void)*p; + } Return of stack address ----------------------- This check warns when a function returns a pointer or reference to a stack-allocated variable, which will be destroyed when the function returns, -leaving the caller with a dangling pointer. +leaving the caller with a dangling pointer.◊ -.. code-block:: c++ +.. list-table:: + :widths: 50 50 + :header-rows: 1 + :class: colored-code-table - #include <string> - #include <string_view> + * - Return of stack address + - Correct + * - + .. code-block:: c++ + + #include <string> + #include <string_view> + + std::string_view bar() { + std::string s = "on stack"; + std::string_view result = s; + // warning: address of stack variable 's' is returned later + return result; // note: returned here + } + - + .. code-block:: c++ + + #include <string> + #include <string_view> + + std::string bar() { + std::string s = "on stack"; + std::string_view result = s; + return result; // OK! + } - std::string_view return_stack_string_view() { - std::string s = "hello"; - return s; // WARNING: address of stack memory is returned - } Dangling field -------------- @@ -214,19 +275,48 @@ This check warns when a constructor or method assigns a pointer to a stack-allocated variable or temporary to a field of the class, and the stack variable's lifetime is shorter than the object's lifetime. -.. code-block:: c++ - - #include <string> - #include <string_view> - - struct DanglingField { - std::string_view view; - // WARNING: 's' is a temporary that will be destroyed after the - // constructor finishes, leaving 'view' dangling. - DanglingField(std::string s) : view(s) {} - }; - -Use-after-invalidation (experimental) +.. list-table:: + :widths: 50 50 + :header-rows: 1 + :class: colored-code-table + + + * - Dangling field + - Correct + * - + .. code-block:: c++ + + #include <string> + #include <string_view> + + // Constructor finishes, leaving 'field' dangling. + struct DanglingField { + std::string_view field; // note: this field dangles + DanglingField(std::string s) { + field = s; // warning: stack variable 's' escapes to a field + } + }; + - + .. code-block:: c++ + + // Make the field an owner. + struct DanglingField { + std::string field; + DanglingField(std::string s) { + field = s; + } + }; + // Or take a string_view parameter. + struct DanglingField { + std::string_view field; + DanglingField(std::string_view s [[clang::lifetimebound]]) { + field = s; + } + }; + }; + + +Use after invalidation (experimental) ------------------------------------- This check warns when a reference to a container element (such as an iterator, @@ -239,15 +329,35 @@ its elements. Container invalidation checking is highly experimental and may produce false positives or miss some invalidations. Field-sensitivity is also limited. -.. code-block:: c++ +.. list-table:: + :widths: 50 50 + :header-rows: 1 + :class: colored-code-table - #include <vector> - void use_after_invalidation(std::vector<int>& v) { - int* p = &v[0]; - v.push_back(4); // push_back might reallocate and invalidate p - *p = 10; // WARNING: use after invalidation - } + * - Use after invalidation (experimental) + - Correct + * - + .. code-block:: c++ + + #include <vector> + + void baz(std::vector<int>& v) { + int* p = &v[0]; // warning: 'v' is later invalidated + v.push_back(4); // note: invalidated here + *p = 10; // note: later used here + } + - + .. code-block:: c++ + + #include <vector> + + void baz(std::vector<int>& v) { + v.push_back(4); + int* p = &v[0]; // OK! + *p = 10; + } + Annotation Inference and Suggestions ==================================== @@ -264,14 +374,15 @@ To enable annotation suggestions, use ``-Wlifetime-safety-suggestions``. #include <string_view> - // The analysis will suggest adding [[clang::lifetimebound]] to 'a' - // because 'a' is returned. - std::string_view return_view(std::string_view a) { // warning: parameter in intra-TU function should be marked [[clang::lifetimebound]] + // The analysis will suggest adding [[clang::lifetimebound]] to 'a'. + std::string_view return_view(std::string_view a) { + // ^^^^^^^^^^^^^^^^^^ + // warning: parameter 'a' should be marked [[clang::lifetimebound]] return a; // note: param returned here } -TU-Wide analysis and Inference ------------------------------- +Translation-Unit-Wide Analysis and Inference +-------------------------------------------- By default, lifetime analysis is intra-procedural for error checking. However, for annotation inference to be effective, lifetime information needs @@ -293,124 +404,162 @@ enable categories of checks incrementally. For example, ``-Wlifetime-safety`` enables all dangling pointer checks, while ``-Wlifetime-safety-permissive`` enables only the high-confidence subset of these checks. -* **``-Wlifetime-safety-all``**: Enables all lifetime safety warnings, including +* ``-Wlifetime-safety-all``: Enables all lifetime safety warnings, including dangling pointer checks, annotation suggestions, and annotation validations. -* **``-Wlifetime-safety``**: Enables dangling pointer checks from both the - ``permissive`` and ``strict`` groups listed below. - * **``-Wlifetime-safety-permissive``**: Enables high-confidence checks for dangling - pointers. Recommended for initial adoption. - * **``-Wlifetime-safety-use-after-scope``**: Warns when a pointer to - a stack variable is used after the variable's lifetime has ended. - * **``-Wlifetime-safety-return-stack-addr``**: Warns when a function - returns a pointer or reference to one of its local stack variables. - * **``-Wlifetime-safety-dangling-field``**: Warns when a class field is - assigned a pointer to a temporary or stack variable whose lifetime - is shorter than the class instance. - * **``-Wlifetime-safety-strict``**: Enables stricter and experimental checks. These - may produce false positives in code that uses move semantics heavily, as - the analysis might conservatively assume a use-after-free even if - ownership was transferred. - * **``-Wlifetime-safety-use-after-scope-moved``**: Same as - ``-Wlifetime-safety-use-after-scope`` but for cases where the - variable may have been moved from before its destruction. - * **``-Wlifetime-safety-return-stack-addr-moved``**: Same as - ``-Wlifetime-safety-return-stack-addr`` but for cases where the - variable may have been moved from. - * **``-Wlifetime-safety-dangling-field-moved``**: Same as - ``-Wlifetime-safety-dangling-field`` but for cases where the - variable may have been moved from. - * **``-Wlifetime-safety-invalidation``**: Warns when a container - iterator or reference to an element is used after an operation - that may invalidate it (Experimental). - -* **``-Wlifetime-safety-suggestions``**: Enables suggestions to add - ``[[clang::lifetimebound]]`` to function parameters and ``this`` - parameters. - * **``-Wlifetime-safety-intra-tu-suggestions``**: Suggestions for functions - local to the translation unit. - * **``-Wlifetime-safety-cross-tu-suggestions``**: Suggestions for functions - visible across translation units (e.g., in headers). - -* **``-Wlifetime-safety-validations``**: Enables checks that validate existing - lifetime annotations. - * **``-Wlifetime-safety-noescape``**: Warns when a parameter marked with - ``[[clang::noescape]]`` escapes the function. +* ``-Wlifetime-safety``: Enables dangling pointer checks from both the ``permissive`` and ``strict`` groups listed below. + + * ``-Wlifetime-safety-permissive``: Enables high-confidence checks for dangling pointers. **Recommended for initial adoption.** + + * ``-Wlifetime-safety-use-after-scope``: Warns when a pointer to a stack variable is used after the variable's lifetime has ended. + * ``-Wlifetime-safety-return-stack-addr``: Warns when a function returns a pointer or reference to one of its local stack variables. + * ``-Wlifetime-safety-dangling-field``: Warns when a class field is assigned a pointer to a temporary or stack variable whose lifetime is shorter than the class instance. + + * ``-Wlifetime-safety-strict``: Enables stricter and experimental checks. These may produce false positives in code that uses move semantics heavily, as the analysis might conservatively assume a use-after-free even if ownership was transferred. + + * ``-Wlifetime-safety-use-after-scope-moved``: Same as ``-Wlifetime-safety-use-after-scope`` but for cases where the variable may have been moved from before its destruction. + * ``-Wlifetime-safety-return-stack-addr-moved``: Same as ``-Wlifetime-safety-return-stack-addr`` but for cases where the variable may have been moved from. + * ``-Wlifetime-safety-dangling-field-moved``: Same as ``-Wlifetime-safety-dangling-field`` but for cases where the variable may have been moved from. + * ``-Wlifetime-safety-invalidation``: Warns when a container iterator or reference to an element is used after an operation that may invalidate it (Experimental). + +* ``-Wlifetime-safety-suggestions``: Enables suggestions to add ``[[clang::lifetimebound]]`` to function parameters and ``this`` parameters. + + * ``-Wlifetime-safety-intra-tu-suggestions``: Suggestions for functions local to the translation unit. + * ``-Wlifetime-safety-cross-tu-suggestions``: Suggestions for functions visible across translation units (e.g., in headers). + +* ``-Wlifetime-safety-validations``: Enables checks that validate existing lifetime annotations. + + * ``-Wlifetime-safety-noescape``: Warns when a parameter marked with ``[[clang::noescape]]`` escapes the function. Limitations =========== -Move Semantics and False Positives ----------------------------------- -When an object is moved from, its state becomes unspecified. If pointers or -views were created that refer to the object *before* it was moved, those -pointers may become invalid after the move. Because the analysis cannot always -know if a move operation invalidates outstanding pointers or simply transfers -ownership, it issues ``-Wlifetime-safety-*-moved`` warnings in these situations. -These warnings indicate a *potential* dangling issue but may be false positives -if ownership was safely transferred and the resource remains alive. -``std::unique_ptr::release()`` is treated similarly to ``std::move()`` in this -regard, as it also relinquishes ownership. - -To avoid these warnings and prevent potential bugs, follow the -**"move-first-then-alias"** pattern: ensure that views or raw pointers are -created *after* a potential move, sourcing them from the new owner rather than -aliasing an object that is about to be moved. +Move Semantics +-------------- +The analysis does not currently track ownership transfers through move operations. +Instead, it uses scope-based lifetime tracking: when an owner goes out of scope, +the analysis assumes the resource is destroyed, even if ownership was transferred +via ``std::move()`` or ``std::unique_ptr::release()``. + +This means that if a pointer or view is created from an owner, and that owner is +later moved-from and goes out of scope, the analysis will issue a +``-Wlifetime-safety-*-moved`` warning. This warning indicates that the pointer +may be dangling, even though the resource may still be alive under a new owner. +These are often false positives when ownership has been safely transferred. + +To avoid these warnings and ensure correctness, follow the +**"move-first-then-alias"** pattern: create views or raw pointers *after* the +ownership transfer, sourcing them from the new owner rather than the original +owner that will go out of scope. For example: +.. list-table:: + :widths: 50 50 + :header-rows: 1 + :align: left + :class: colored-code-table + + * - Anti-Pattern: Aliasing Before Move + - Good Practice: Move-First-Then-Alias + * - + .. code-block:: c++ + + #include <memory> + + void use(int*); + + void bar() { + std::unique_ptr<int> b; + int* p; + { + auto a = std::make_unique<int>(42); + p = a.get(); // warning! + b = std::move(a); + } + use(p); + } + - + .. code-block:: c++ + + #include <memory> + + void use(int*); + + void bar() { + std::unique_ptr<int> b; + int* p; + { + auto a = std::make_unique<int>(42); + b = std::move(a); + p = b.get(); // OK! + } + use(p); + } + +The same principle applies when moving ownership using ``std::unique_ptr::release()``: + .. code-block:: c++ + :class: bad-code #include <memory> + #include <utility> void use(int*); - void take(std::unique_ptr<int>&&); + void take_ownership(int*); - void bar() { - std::unique_ptr<int> b; + void test_aliasing_before_release() { int* p; { - auto a = std::make_unique<int>(42); - p = a.get(); // p aliases a's content - b = std::move(a); // a is moved-from - } - use(p); // WARNING: -Wlifetime-safety-use-after-scope-moved - } - - void foo() { - int* p; - { - auto a = std::make_unique<int>(42); - p = a.get(); // p aliases a's content - take(std::move(a)); // a is moved-from and goes out of scope - } - use(p); // WARNING: -Wlifetime-safety-use-after-scope-moved + auto u = std::make_unique<int>(1); + p = u.get(); + // ^ warning: 'u' does not live long enough! + take_ownership(u.release()); + } + use(p); } -The same principle applies when creating other aliases via ``get()`` or ``release()`` before moving or releasing ownership: +``std::unique_ptr`` with custom deleters +---------------------------------------- +The analysis assumes standard ownership semantics for owner types like +``std::unique_ptr``: when a ``unique_ptr`` goes out of scope, it is assumed +that the owned object is destroyed and its memory is deallocated. +However, ``std::unique_ptr`` can be used with a custom deleter that modifies +this behavior. For example, a custom deleter might keep the memory alive +by transferring it to a memory pool, or simply do nothing, allowing +another system to manage the lifetime. + +Because the analysis relies on scope-based lifetime for owners, it does not +support custom deleters that extend the lifetime of the owned object beyond +the lifetime of the ``std::unique_ptr``. In such cases, the analysis will +assume the object is destroyed when the ``std::unique_ptr`` goes out of scope, +leading to false positive warnings if pointers to the object are used afterward. .. code-block:: c++ #include <memory> - #include <utility> void use(int*); - void take_ownership(std::unique_ptr<int>); - void test_aliasing_before_move() { + struct NoOpDeleter { + void operator()(int* p) const { + // Do not delete p, memory is managed elsewhere. + } + }; + + void test_custom_deleter() { int* p; { - auto u = std::make_unique<int>(1); - p = u.get(); // p aliases u's content - take_ownership(std::move(u)); // u is moved-from - } - // 'p' now points to memory whose ownership was transferred, - // and it might be invalid depending on what take_ownership does. - use(p); // WARNING: -Wlifetime-safety-use-after-scope-moved + std::unique_ptr<int, NoOpDeleter> u(new int(42)); + p = u.get(); // warning: object whose reference is captured does not live long enough + } // note: destroyed here + // With NoOpDeleter, p would still be valid here. + // But analysis assumes standard unique_ptr semantics and memory being freed. + use(p); // note: later used here } -Dangling Fields and Intra-Procedural Analysis ---------------------------------------------- +Dangling Fields +--------------- The lifetime analysis is intra-procedural. It analyzes one function or method at a time. This means if a field is assigned a pointer to a local variable or temporary @@ -425,8 +574,8 @@ because it cannot see how other methods are implemented or used. #include <string_view> struct MyWidget { - std::string_view name_; - MyWidget(std::string name) : name_(name) {} // WARNING: 'name' is destroyed when ctor ends, leaving 'name_' dangling + std::string_view name_; // note: this field dangles + MyWidget(std::string name) : name_(name) {} // warning: address of stack memory escapes to a field const char* data() { return name_.data(); } // Potential use-after-free if called }; @@ -439,18 +588,11 @@ or ensuring the borrowed object (e.g., one passed by ``const&``) has a sufficient lifetime. -Heap and Globals ----------------- - -Currently, the analysis focuses on dangling pointers to stack variables, -temporaries, and function parameters. It does not track lifetimes of heap- -allocated memory or global variables. - Performance =========== Lifetime analysis relies on Clang's CFG (Control Flow Graph). For functions -with very large or complex CFGs, analysis time can be significant. To mitigate -this, the analysis will skip functions where the number of CFG blocks exceeds +with very large or complex CFGs, analysis time can sometimes be significant. To mitigate +this, the analysis allows to skip functions where the number of CFG blocks exceeds a certain threshold, controlled by the ``-flifetime-safety-max-cfg-blocks=N`` language option. diff --git a/clang/docs/index.rst b/clang/docs/index.rst index 9647d1cd2fae9..f086daee9181f 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -26,6 +26,7 @@ Using Clang as a Compiler CrossCompilation ClangStaticAnalyzer ThreadSafetyAnalysis + LifetimeSafety SafeBuffers ScalableStaticAnalysisFramework/Framework DataFlowAnalysisIntro _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
