# Lifetime Safety Analysis

```{contents}
:local: true
```

## 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

```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.

```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:

```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.

```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:

```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
* -
    ```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
    }
    ```
  -
    ```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
* -
    ```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
    }
    ```
  -
    ```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
* -
    ```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
    }
    ```
  -
    ```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
* -
    ```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
      }
    };
    ```
  -
    ```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
* -
    ```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
    }
    ```
  -
    ```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.

```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`.

Fix-it hints normally insert `[[clang::lifetimebound]]`. If a visible
object-like macro expands to `[[clang::lifetimebound]]` or
`__attribute__((lifetimebound))`, Clang will use the last such macro
visible at the insertion point. To force a project-specific macro spelling,
use `-lifetime-safety-lifetimebound-macro=<macro>`.

```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)=

## 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-intra-tu-constructor-suggestions`: Suggestions for constructors local to the translation unit.
  - `-Wlifetime-safety-cross-tu-suggestions`: Suggestions for functions visible across translation units (e.g., in headers).
    - `-Wlifetime-safety-cross-tu-constructor-suggestions`: Suggestions for constructors visible across translation units.

- `-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
* -
    ```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);
    }
    ```
  -
    ```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.

```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.

```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 `-lifetime-safety-max-cfg-blocks=N` language
option.
