CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
693 lines
25 KiB
ReStructuredText
693 lines
25 KiB
ReStructuredText
========================
|
|
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, pointers to heap objects that have been
|
|
freed, 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 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. While it does
|
|
not require lifetime annotations to get started, in their absence, the analysis
|
|
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.
|
|
|
|
.. 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
|
|
absence of all lifetime bugs.
|
|
|
|
Getting Started
|
|
----------------
|
|
|
|
.. code-block:: c++
|
|
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
void simple_dangle() {
|
|
std::string_view v;
|
|
{
|
|
std::string s = "hello";
|
|
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 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-permissive`` flag, e.g.
|
|
|
|
.. code-block:: bash
|
|
|
|
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`.
|
|
|
|
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``, 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>
|
|
|
|
// Owner type
|
|
struct [[gsl::Owner]] MyObj {
|
|
std::string Data = "Hello";
|
|
};
|
|
|
|
// 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; // 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
|
|
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>`_.
|
|
|
|
.. note::
|
|
Types with mixed ownership semantics (owning some data while holding views to
|
|
other data) or types with multiple view fields with different lifetimes should
|
|
not be annotated. The analysis does not yet support expressing such nuanced
|
|
lifetime relationships.
|
|
Future enhancements, such as named lifetimes, may provide better support for
|
|
these patterns.
|
|
|
|
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 value becomes invalid when
|
|
the attributed parameter or ``this`` object is destroyed.
|
|
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
|
|
// 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
|
|
know that the value returned by ``getView()`` depends on the temporary
|
|
``MyOwner`` object, and it would not be able to diagnose the dangling ``sv``.
|
|
|
|
The analysis also tracks record types returned from functions and constructors
|
|
with ``[[clang::lifetimebound]]`` annotated parameters:
|
|
|
|
.. code-block:: c++
|
|
|
|
#include <string>
|
|
|
|
struct StringView {
|
|
StringView();
|
|
StringView(const std::string &s [[clang::lifetimebound]]);
|
|
};
|
|
|
|
StringView getStringView(const std::string &s [[clang::lifetimebound]]);
|
|
|
|
void test() {
|
|
StringView a, b;
|
|
{
|
|
std::string s = "temp";
|
|
StringView tmp(s); // warning: object whose reference is captured does not live long enough
|
|
a = tmp;
|
|
b = getStringView(s); // warning: object whose reference is captured does not live long enough
|
|
} // note: destroyed here
|
|
(void)a; // note: later used here
|
|
(void)b; // note: later used here
|
|
}
|
|
|
|
For more details, see `lifetimebound <https://clang.llvm.org/docs/AttributeReference.html#lifetimebound>`_.
|
|
|
|
NoEscape
|
|
--------
|
|
|
|
.. _Wlifetime-safety-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
|
|
================
|
|
|
|
|
|
.. 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 check warns when a pointer or reference is used after the stack variable
|
|
it refers to has gone out of 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;
|
|
}
|
|
|
|
Use after free
|
|
--------------
|
|
|
|
This check warns when a pointer or reference is used after the object it refers
|
|
to has been freed.
|
|
|
|
.. list-table::
|
|
:widths: 50 50
|
|
:header-rows: 1
|
|
:class: colored-code-table
|
|
|
|
* - Use after free
|
|
- Correct
|
|
* -
|
|
.. code-block:: c++
|
|
|
|
void foo() {
|
|
int *p = new int(0); // warning: allocated object does not live long enough
|
|
delete p; // note: freed here
|
|
(void)*p; // note: later used here
|
|
}
|
|
-
|
|
.. code-block:: c++
|
|
|
|
void foo() {
|
|
int *p = new int(0);
|
|
(void)*p;
|
|
delete p; // OK!
|
|
}
|
|
|
|
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.
|
|
|
|
.. list-table::
|
|
:widths: 50 50
|
|
:header-rows: 1
|
|
:class: colored-code-table
|
|
|
|
* - 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!
|
|
}
|
|
|
|
|
|
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.
|
|
|
|
.. 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 pointer, reference or view is used after an operation
|
|
that may have invalidated it. This includes references to container elements
|
|
used after a container operation, and pointers to objects managed by owners such
|
|
as ``std::unique_ptr`` after operations like ``reset``. For example, adding
|
|
elements to ``std::vector`` may cause reallocation, invalidating all existing
|
|
iterators, pointers and references to its elements.
|
|
|
|
.. note::
|
|
Invalidation checking is highly experimental and may produce false positives.
|
|
|
|
.. list-table::
|
|
:widths: 50 50
|
|
:header-rows: 1
|
|
:class: colored-code-table
|
|
|
|
|
|
* - 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;
|
|
}
|
|
|
|
The analysis also treats explicit destruction as invalidation. Explicit
|
|
destructor calls and ``std::destroy_at`` invalidate pointers, references and
|
|
views into the destroyed object.
|
|
|
|
.. code-block:: c++
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
void explicit_destruction() {
|
|
std::string s = "hello";
|
|
const char *p = s.data(); // warning: object whose reference is captured is later invalidated
|
|
std::destroy_at(&s); // note: invalidated here
|
|
(void)*p; // note: later used here
|
|
}
|
|
|
|
void unique_ptr_reset() {
|
|
std::unique_ptr<int> u(new int(0));
|
|
int *p = u.get(); // warning: object whose reference is captured is later invalidated
|
|
u.reset(); // note: invalidated here
|
|
(void)*p; // note: later used here
|
|
}
|
|
|
|
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'.
|
|
std::string_view return_view(std::string_view a) {
|
|
// ^^^^^^^^^^^^^^^^^^
|
|
// warning: parameter 'a' should be marked [[clang::lifetimebound]]
|
|
return a; // note: param returned here
|
|
}
|
|
|
|
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:
|
|
|
|
* ``-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-use-after-free``: Warns when a pointer to an object is used after it's been freed.
|
|
* ``-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 pointer, reference, iterator or view is used after an operation that may invalidate it, such as container mutation or explicit destruction (e.g., ``std::unique_ptr::reset``, ``std::destroy_at``) (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-lifetimebound-violation``: Warns when the analysis cannot verify that the return value can be lifetime bound to a parameter marked with ``[[clang::lifetimebound]]``.
|
|
|
|
Limitations
|
|
===========
|
|
|
|
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_ownership(int*);
|
|
|
|
void test_aliasing_before_release() {
|
|
int* p;
|
|
{
|
|
auto u = std::make_unique<int>(1);
|
|
p = u.get();
|
|
// ^ warning: 'u' does not live long enough!
|
|
take_ownership(u.release());
|
|
}
|
|
use(p);
|
|
}
|
|
|
|
``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>
|
|
|
|
void use(int*);
|
|
|
|
struct NoOpDeleter {
|
|
void operator()(int* p) const {
|
|
// Do not delete p, memory is managed elsewhere.
|
|
}
|
|
};
|
|
|
|
void test_custom_deleter() {
|
|
int* p;
|
|
{
|
|
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
|
|
---------------
|
|
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_; // 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
|
|
};
|
|
|
|
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.
|
|
|
|
|
|
Performance
|
|
===========
|
|
|
|
Lifetime analysis relies on Clang's CFG (Control Flow Graph). For functions
|
|
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.
|