Skip to content

Update track_caller to use the attribute template #1911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 93 additions & 85 deletions src/attributes/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,111 +502,119 @@ in the standard library for runtime feature detection on these platforms.
r[attributes.codegen.track_caller]
## The `track_caller` attribute

r[attributes.codegen.track_caller.allowed-positions]
The `track_caller` attribute may be applied to any function with [`"Rust"` ABI][rust-abi]
with the exception of the entry point `fn main`.

r[attributes.codegen.track_caller.traits]
When applied to functions and methods in trait declarations, the attribute applies to all implementations. If the trait provides a
default implementation with the attribute, then the attribute also applies to override implementations.

r[attributes.codegen.track_caller.extern]
When applied to a function in an `extern` block the attribute must also be applied to any linked
implementations, otherwise undefined behavior results. When applied to a function which is made
available to an `extern` block, the declaration in the `extern` block must also have the attribute,
otherwise undefined behavior results.
r[attributes.codegen.track_caller.intro]
The *`track_caller` [attribute][attributes]* is used on functions to indicate that the caller should be tracked for the purpose of using [`Location`] to determine the caller.

r[attributes.codegen.track_caller.behavior]
### Behavior

Applying the attribute to a function `f` allows code within `f` to get a hint of the [`Location`] of
the "topmost" tracked call that led to `f`'s invocation. At the point of observation, an
implementation behaves as if it walks up the stack from `f`'s frame to find the nearest frame of an
*unattributed* function `outer`, and it returns the [`Location`] of the tracked call in `outer`.
> [!EXAMPLE]
> ```rust
> #[track_caller]
> fn f() {
> println!("{}", std::panic::Location::caller());
> }
> ```

```rust
#[track_caller]
fn f() {
println!("{}", std::panic::Location::caller());
}
```
r[attributes.codegen.track_caller.syntax]
The `track_caller` attribute uses the [MetaWord] syntax and thus does not take any inputs.

> [!NOTE]
> `core` provides [`core::panic::Location::caller`] for observing caller locations. It wraps the [`core::intrinsics::caller_location`] intrinsic implemented by `rustc`.

> [!NOTE]
> Because the resulting `Location` is a hint, an implementation may halt its walk up the stack early. See [Limitations](#limitations) for important caveats.
r[attributes.codegen.track_caller.allowed-positions]
The `track_caller` attribute may only be applied to:

#### Examples
- [Free functions][items.fn]
- [Inherent associated functions][items.associated.fn]
- [Trait impl functions][items.impl.trait]
- [Trait definition functions][items.traits]
- [External block functions][items.extern.fn]
- [Closures][expr.closure]

When `f` is called directly by `calls_f`, code in `f` observes its callsite within `calls_f`:
All functions must have the [`"Rust"` ABI][rust-abi].

```rust
# #[track_caller]
# fn f() {
# println!("{}", std::panic::Location::caller());
# }
fn calls_f() {
f(); // <-- f() prints this location
}
```
It may not be applied to the [the `main` function][crate.main].

When `f` is called by another attributed function `g` which is in turn called by `calls_g`, code in
both `f` and `g` observes `g`'s callsite within `calls_g`:
r[attributes.codegen.track_caller.duplicates]
Duplicate instances of the `track_caller` attribute are ignored.

```rust
# #[track_caller]
# fn f() {
# println!("{}", std::panic::Location::caller());
# }
#[track_caller]
fn g() {
println!("{}", std::panic::Location::caller());
f();
}
> [!NOTE]
> `rustc` warns on on duplicate `track_caller` attributes.

fn calls_g() {
g(); // <-- g() prints this location twice, once itself and once from f()
}
```
r[attributes.codegen.track_caller.traits]
When applied to functions and methods in trait declarations, the `track_caller` attribute applies to all implementations. If the trait provides a default implementation with the attribute, then the attribute also applies to override implementations.

When `g` is called by another attributed function `h` which is in turn called by `calls_h`, all code
in `f`, `g`, and `h` observes `h`'s callsite within `calls_h`:
r[attributes.codegen.track_caller.extern]
When applied to a function in an `extern` block, the `track_caller` attribute must also be applied to any linked implementations, otherwise undefined behavior results. When applied to a function which is made available to an `extern` block, the declaration in the `extern` block must also have the attribute, otherwise undefined behavior results.

```rust
# #[track_caller]
# fn f() {
# println!("{}", std::panic::Location::caller());
# }
# #[track_caller]
# fn g() {
# println!("{}", std::panic::Location::caller());
# f();
# }
#[track_caller]
fn h() {
println!("{}", std::panic::Location::caller());
g();
}
r[attributes.codegen.track_caller.behavior]
Applying the `track_caller` attribute to a function `f` allows code within `f` to get a hint of the [`Location`] of the *topmost* tracked call that led to `f`'s invocation. At the point of observation, an implementation behaves as if it walks up the stack from `f`'s frame to find the nearest frame of an *unattributed* function `outer`, and it returns the [`Location`] of the tracked call in `outer`.

fn calls_h() {
h(); // <-- prints this location three times, once itself, once from g(), once from f()
}
```
> [!NOTE]
> `core` provides [`core::panic::Location::caller`] for observing caller locations. It wraps the [`core::intrinsics::caller_location`] intrinsic implemented by `rustc`.

And so on.
> [!NOTE]
> Because the resulting `Location` is a hint, an implementation may halt its walk up the stack early. See [Limitations](#track_caller-limitations) for important caveats.

> [!EXAMPLE]
> When `f` is called directly by `calls_f`, code in `f` observes its callsite within `calls_f`:
>
> ```rust
> # #[track_caller]
> # fn f() {
> # println!("{}", std::panic::Location::caller());
> # }
> fn calls_f() {
> f(); // <-- f() prints this location
> }
> ```
>
> When `f` is called by another attributed function `g` which is in turn called by `calls_g`, code in both `f` and `g` observes `g`'s callsite within `calls_g`:
>
> ```rust
> # #[track_caller]
> # fn f() {
> # println!("{}", std::panic::Location::caller());
> # }
> #[track_caller]
> fn g() {
> println!("{}", std::panic::Location::caller());
> f();
> }
>
> fn calls_g() {
> g(); // <-- g() prints this location twice, once itself and once from f()
> }
> ```
>
> When `g` is called by another attributed function `h` which is in turn called by `calls_h`, all code in `f`, `g`, and `h` observes `h`'s callsite within `calls_h`:
>
> ```rust
> # #[track_caller]
> # fn f() {
> # println!("{}", std::panic::Location::caller());
> # }
> # #[track_caller]
> # fn g() {
> # println!("{}", std::panic::Location::caller());
> # f();
> # }
> #[track_caller]
> fn h() {
> println!("{}", std::panic::Location::caller());
> g();
> }
>
> fn calls_h() {
> h(); // <-- prints this location three times, once itself, once from g(), once from f()
> }
> ```
>
> And so on.

r[attributes.codegen.track_caller.limits]
### Limitations
### `track_caller` limitations

r[attributes.codegen.track_caller.hint]
This information is a hint and implementations are not required to preserve it.

r[attributes.codegen.track_caller.decay]
In particular, coercing a function with `#[track_caller]` to a function pointer creates a shim which
appears to observers to have been called at the attributed function's definition site, losing actual
caller information across virtual calls. A common example of this coercion is the creation of a
trait object whose methods are attributed.
In particular, coercing a function with `#[track_caller]` to a function pointer creates a shim which appears to observers to have been called at the attributed function's definition site, losing actual caller information across virtual calls. A common example of this coercion is the creation of a trait object whose methods are attributed.

> [!NOTE]
> The aforementioned shim for function pointers is necessary because `rustc` implements `track_caller` in a codegen context by appending an implicit parameter to the function ABI, but this would be unsound for an indirect call because the parameter is not a part of the function's type and a given function pointer type may or may not refer to a function with the attribute. The creation of a shim hides the implicit parameter from callers of the function pointer, preserving soundness.
Expand Down