[clang] [LifetimeSafety] Add user documentation (PR #183058)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 16 04:00:34 PDT 2026
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/183058
>From 695d1b72ad57fe7e2d187d5ac1a286611189204c Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 c1984f0332f6f4a1e0f5fc82030663cdc33e4597 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 8472065689a3ae61223a67d0074317fd568bf3c8 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
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 | 496 ++++++++++++++++++++++------------
clang/docs/index.rst | 1 +
2 files changed, 321 insertions(+), 176 deletions(-)
diff --git a/clang/docs/LifetimeSafety.rst b/clang/docs/LifetimeSafety.rst
index 6a4f18ef50e53..b5dcb1133b4ff 100644
--- a/clang/docs/LifetimeSafety.rst
+++ b/clang/docs/LifetimeSafety.rst
@@ -1,6 +1,6 @@
-======================
+========================
Lifetime Safety Analysis
-======================
+========================
.. contents::
:local:
@@ -19,18 +19,21 @@ that have gone out of scope, fields holding views to stack-allocated objects
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).
+but adapted to C++ idioms and constraints, such as the lack of exclusivity enforcement (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
+It tracks pointer validity through intra-procedural data-flow analysis. 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
+treats function calls optimistically, assuming no lifetime effects, thereby potentially missing dangling pointer issues. As more functions are annotated
+with attributes like `clang::lifetimebound <https://clang.llvm.org/docs/AttributeReference.html#lifetimebound>`_, `gsl::Owner <https://clang.llvm.org/docs/AttributeReference.html#gsl-owner>`_, and
+`gsl::Pointer <https://clang.llvm.org/docs/AttributeReference.html#gsl-pointer>`_, the analysis can see through these lifetime 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.
+gradual adoption in existing codebases.
+
+.. note::
+ This analysis is designed for bug finding, not verification. It may miss some
+ lifetime issues and can produce false positives. It does not guarantee the
Getting Started
----------------
@@ -44,25 +47,27 @@ 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
-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.
+a basic use-after-scope bug. The ``std::string_view`` object ``v`` holds a
+reference to ``s``, a ``std::string``. The lifetime of ``s`` ends at the end of
+the inner block, causing ``v`` to become 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
--------------------
-To run the analysis, compile with the ``-Wlifetime-safety`` flag, e.g.
+To run the analysis, compile with the ``-Wlifetime-safety-permissive`` flag, e.g.
.. code-block:: bash
- clang -c -Wlifetime-safety example.cpp
+ clang -c -Wlifetime-safety-permissive example.cpp
This flag enables a core set of lifetime safety checks. For more fine-grained
control over warnings, see :ref:`warning_flags`.
@@ -86,7 +91,7 @@ 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
+ like ``std::string_view``, or raw pointers (which are
implicitly treated as pointers).
Many common STL types, such as ``std::string_view`` and container iterators,
@@ -98,12 +103,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 +120,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
@@ -131,8 +136,8 @@ 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.
+method declarator). It indicates that the returned pointer or reference becomes
+invalid when the attributed parameter or ``this`` object is destroyed.
This is crucial for functions that return views or references to their
arguments.
@@ -149,8 +154,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,22 +179,58 @@ For more details, see `noescape <https://clang.llvm.org/docs/AttributeReference.
Checks Performed
================
-Use-After-Scope
----------------
-This is the simplest dangling pointer scenario, where a pointer or reference
-outlives the stack variable it refers to.
+.. raw:: html
-.. code-block:: c++
+ <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; }
- 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
- }
+ 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 scenario occurs when a pointer or reference outlives
+the scope of the stack variable it refers to.
+
+.. 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
-----------------------
@@ -197,36 +239,86 @@ 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++
+.. 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++
- std::string_view return_stack_string_view() {
- std::string s = "hello";
- return s; // WARNING: address of stack memory is returned
- }
+ #include <string>
+ #include <string_view>
-Dangling field
---------------
+ 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++
-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.
+ #include <string>
+ #include <string_view>
-.. code-block:: c++
+ std::string bar() {
+ std::string s = "on stack";
+ std::string_view result = s;
+ return result; // OK!
+ }
- #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) {}
- };
+Dangling field
+--------------
-Use-after-invalidation (experimental)
+This check warns when a constructor or method assigns a pointer to a
+stack-allocated variable or temporary to a field of the class.
+
+.. 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,
@@ -237,17 +329,37 @@ its elements.
.. note::
Container invalidation checking is highly experimental and may produce false
- positives or miss some invalidations. Field-sensitivity is also limited.
+ positives.
-.. 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,19 +376,20 @@ 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
to propagate across function calls. You can enable experimental
-Translation-Unit-wide analysis using:
+translation-unit-wide analysis using:
* ``-flifetime-safety-inference``: Enables inference of ``lifetimebound``
attributes across functions in a TU.
@@ -293,124 +406,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 +576,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 +590,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 c2974a4b2f9ea..89ca6d73d9d8d 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -27,6 +27,7 @@ Using Clang as a Compiler
CrossCompilation
ClangStaticAnalyzer
ThreadSafetyAnalysis
+ LifetimeSafety
SafeBuffers
ScalableStaticAnalysisFramework/index
DataFlowAnalysisIntro
More information about the cfe-commits
mailing list