| # Implicit caller location | |
| Approved in [RFC 2091], this feature enables the accurate reporting of caller location during panics | |
| initiated from functions like `Option::unwrap`, `Result::expect`, and `Index::index`. | |
| This feature adds the [`#[track_caller]`][attr-reference] attribute for functions, the | |
| [`caller_location`][intrinsic] intrinsic, and the stabilization-friendly | |
| [`core::panic::Location::caller`][wrapper] wrapper. | |
| ## Motivating example | |
| Take this example program: | |
| ```rust | |
| fn main() { | |
| let foo: Option<()> = None; | |
| foo.unwrap(); // this should produce a useful panic message! | |
| } | |
| ``` | |
| Prior to Rust 1.42, panics like this `unwrap()` printed a location in core: | |
| ``` | |
| $ rustc +1.41.0 example.rs; example.exe | |
| thread 'main' panicked at 'called `Option::unwrap()` on a `None` value',...core\macros\mod.rs:15:40 | |
| note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. | |
| ``` | |
| As of 1.42, we get a much more helpful message: | |
| ``` | |
| $ rustc +1.42.0 example.rs; example.exe | |
| thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', example.rs:3:5 | |
| note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace | |
| ``` | |
| These error messages are achieved through a combination of changes to `panic!` internals to make use | |
| of `core::panic::Location::caller` and a number of `#[track_caller]` annotations in the standard | |
| library which propagate caller information. | |
| ## Reading caller location | |
| Previously, `panic!` made use of the `file!()`, `line!()`, and `column!()` macros to construct a | |
| [`Location`] pointing to where the panic occurred. | |
| These macros couldn't be given an overridden location, | |
| so functions which intentionally invoked `panic!` couldn't provide their own location, | |
| hiding the actual source of error. | |
| Internally, `panic!()` now calls [`core::panic::Location::caller()`][wrapper] to find out where it | |
| was expanded. | |
| This function is itself annotated with `#[track_caller]` and wraps the | |
| [`caller_location`][intrinsic] compiler intrinsic implemented by rustc. | |
| This intrinsic is easiest explained in terms of how it works in a `const` context. | |
| ## Caller location in `const` | |
| There are two main phases to returning the caller location in a const context: walking up the stack | |
| to find the right location and allocating a const value to return. | |
| ### Finding the right `Location` | |
| In a const context we "walk up the stack" from where the intrinsic is invoked, stopping when we | |
| reach the first function call in the stack which does *not* have the attribute. | |
| This walk is in [`InterpCx::find_closest_untracked_caller_location()`][const-find-closest]. | |
| Starting at the bottom, we iterate up over stack [`Frame`][const-frame]s in the | |
| [`InterpCx::stack`][const-stack], calling | |
| [`InstanceKind::requires_caller_location`][requires-location] on the | |
| [`Instance`s from each `Frame`][frame-instance]. | |
| We stop once we find one that returns `false` and | |
| return the span of the *previous* frame which was the "topmost" tracked function. | |
| ### Allocating a static `Location` | |
| Once we have a `Span`, we need to allocate static memory for the `Location`, which is performed by | |
| the [`TyCtxt::const_caller_location()`][const-location-query] query. | |
| Internally this calls [`InterpCx::alloc_caller_location()`][alloc-location] and results in a unique | |
| [memory kind][location-memory-kind] (`MemoryKind::CallerLocation`). | |
| The SSA codegen backend is able to emit code for these same values, | |
| and we use this code there as well. | |
| Once our `Location` has been allocated in static memory, our intrinsic returns a reference to it. | |
| ## Generating code for `#[track_caller]` callees | |
| To generate efficient code for a tracked function and its callers, we need to provide the same | |
| behavior from the intrinsic's point of view without having a stack to walk up at runtime. | |
| We invert the approach: | |
| as we grow the stack down we pass an additional argument to calls of tracked functions | |
| rather than walking up the stack when the intrinsic is called. | |
| That additional argument can be returned wherever the caller location is queried. | |
| The argument we append is of type `&'static core::panic::Location<'static>`. | |
| A reference was chosen to avoid unnecessary copying because a pointer is a third the size of | |
| `std::mem::size_of::<core::panic::Location>() == 24` at time of writing. | |
| When generating a call to a function which is tracked, we pass the location argument the value of | |
| [`FunctionCx::get_caller_location`][fcx-get]. | |
| If the calling function is tracked, `get_caller_location` returns the local in | |
| [`FunctionCx::caller_location`][fcx-location] which was populated by the current caller's caller. | |
| In these cases the intrinsic "returns" a reference which was actually provided in an argument to its | |
| caller. | |
| If the calling function is not tracked, `get_caller_location` allocates a `Location` static from | |
| the current `Span` and returns a reference to that. | |
| We more efficiently achieve the same behavior as a loop starting from the bottom by passing a single | |
| `&Location` value through the `caller_location` fields of multiple `FunctionCx`s as we grow the | |
| stack downward. | |
| ### Codegen examples | |
| What does this transformation look like in practice? | |
| Take this example which uses the new feature: | |
| ```rust | |
| #![feature(track_caller)] | |
| use std::panic::Location; | |
| #[track_caller] | |
| fn print_caller() { | |
| println!("called from {}", Location::caller()); | |
| } | |
| fn main() { | |
| print_caller(); | |
| } | |
| ``` | |
| Here `print_caller()` appears to take no arguments, but we compile it to something like this: | |
| ```rust | |
| #![feature(panic_internals)] | |
| use std::panic::Location; | |
| fn print_caller(caller: &Location) { | |
| println!("called from {}", caller); | |
| } | |
| fn main() { | |
| print_caller(&Location::internal_constructor(file!(), line!(), column!())); | |
| } | |
| ``` | |
| ### Dynamic dispatch | |
| In codegen contexts we have to modify the callee ABI to pass this information down the stack, but | |
| the attribute expressly does *not* modify the type of the function. | |
| The ABI change must be transparent to type checking and remain sound in all uses. | |
| Direct calls to tracked functions will always know the full codegen flags for the callee and can | |
| generate appropriate code. | |
| Indirect callers won't have this information and it's not encoded in | |
| the type of the function pointer they call, so we generate a [`ReifyShim`] around the function | |
| whenever taking a pointer to it. | |
| This shim isn't able to report the actual location of the indirect | |
| call (the function's definition site is reported instead), but it prevents miscompilation and is | |
| probably the best we can do without modifying fully-stabilized type signatures. | |
| > *Note:* We always emit a [`ReifyShim`] when taking a pointer to a tracked function. While the | |
| > constraint here is imposed by codegen contexts, we don't know during MIR construction of the shim | |
| > whether we'll be called in a const context (safe to ignore shim) or in a codegen context (unsafe | |
| > to ignore shim). Even if we did know, the results from const and codegen contexts must agree. | |
| ## The attribute | |
| The `#[track_caller]` attribute is checked alongside other codegen attributes to ensure the | |
| function: | |
| * has the `"Rust"` ABI (as opposed to e.g., `"C"`) | |
| * is not a closure | |
| * is not `#[naked]` | |
| If the use is valid, we set [`CodegenFnAttrsFlags::TRACK_CALLER`][attrs-flags]. | |
| This flag influences the return value of | |
| [`InstanceKind::requires_caller_location`][requires-location] which is in turn | |
| used in both const and codegen contexts to ensure correct propagation. | |
| ### Traits | |
| When applied to trait method implementations, the attribute works as it does for regular functions. | |
| When applied to a trait method prototype, | |
| the attribute applies to all implementations of the method. | |
| When applied to a default trait method implementation, the attribute takes effect on | |
| that implementation *and* any overrides. | |
| Examples: | |
| ```rust | |
| #![feature(track_caller)] | |
| macro_rules! assert_tracked { | |
| () => {{ | |
| let location = std::panic::Location::caller(); | |
| assert_eq!(location.file(), file!()); | |
| assert_ne!(location.line(), line!(), "line should be outside this fn"); | |
| println!("called at {}", location); | |
| }}; | |
| } | |
| trait TrackedFourWays { | |
| /// All implementations inherit `#[track_caller]`. | |
| #[track_caller] | |
| fn blanket_tracked(); | |
| /// Implementors can annotate themselves. | |
| fn local_tracked(); | |
| /// This implementation is tracked (overrides are too). | |
| #[track_caller] | |
| fn default_tracked() { | |
| assert_tracked!(); | |
| } | |
| /// Overrides of this implementation are tracked (it is too). | |
| #[track_caller] | |
| fn default_tracked_to_override() { | |
| assert_tracked!(); | |
| } | |
| } | |
| /// This impl uses the default impl for `default_tracked` and provides its own for | |
| /// `default_tracked_to_override`. | |
| impl TrackedFourWays for () { | |
| fn blanket_tracked() { | |
| assert_tracked!(); | |
| } | |
| #[track_caller] | |
| fn local_tracked() { | |
| assert_tracked!(); | |
| } | |
| fn default_tracked_to_override() { | |
| assert_tracked!(); | |
| } | |
| } | |
| fn main() { | |
| <() as TrackedFourWays>::blanket_tracked(); | |
| <() as TrackedFourWays>::default_tracked(); | |
| <() as TrackedFourWays>::default_tracked_to_override(); | |
| <() as TrackedFourWays>::local_tracked(); | |
| } | |
| ``` | |
| ## Background/history | |
| Broadly speaking, this feature's goal is to improve common Rust error messages without breaking | |
| stability guarantees, requiring modifications to end-user source, relying on platform-specific | |
| debug-info, or preventing user-defined types from having the same error-reporting benefits. | |
| Improving the output of these panics has been a goal of proposals since at least mid-2016 (see | |
| [non-viable alternatives] in the approved RFC for details). | |
| It took two more years until RFC 2091 was approved, | |
| much of its [rationale] for this feature's design having been discovered through the | |
| discussion around several earlier proposals. | |
| The design in the original RFC limited itself to implementations that could be done inside the | |
| compiler at the time without significant refactoring. | |
| However in the year and a half between the approval of the RFC and the actual implementation work, | |
| a [revised design] was proposed and written up on the tracking issue. | |
| During the course of implementing that, it was also discovered that an | |
| implementation was possible without modifying the number of arguments in a function's MIR, which | |
| would simplify later stages and unlock use in traits. | |
| Because the RFC's implementation strategy could not readily support traits, the semantics were not | |
| originally specified. | |
| They have since been implemented following the path which seemed most correct | |
| to the author and reviewers. | |
| [RFC 2091]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md | |
| [attr-reference]: https://doc.rust-lang.org/reference/attributes/codegen.html#the-track_caller-attribute | |
| [intrinsic]: https://doc.rust-lang.org/nightly/core/intrinsics/fn.caller_location.html | |
| [wrapper]: https://doc.rust-lang.org/nightly/core/panic/struct.Location.html#method.caller | |
| [non-viable alternatives]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md#non-viable-alternatives | |
| [rationale]: https://github.com/rust-lang/rfcs/blob/master/text/2091-inline-semantic.md#rationale | |
| [revised design]: https://github.com/rust-lang/rust/issues/47809#issuecomment-443538059 | |
| [attrs-flags]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/middle/codegen_fn_attrs/struct.CodegenFnAttrFlags.html#associatedconstant.TRACK_CALLER | |
| [`ReifyShim`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.InstanceKind.html#variant.ReifyShim | |
| [`Location`]: https://doc.rust-lang.org/core/panic/struct.Location.html | |
| [const-find-closest]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/interpret/struct.InterpCx.html#method.find_closest_untracked_caller_location | |
| [requires-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/instance/enum.InstanceKind.html#method.requires_caller_location | |
| [alloc-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/interpret/struct.InterpCx.html#method.alloc_caller_location | |
| [fcx-location]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/mir/struct.FunctionCx.html#structfield.caller_location | |
| [const-location-query]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html#method.const_caller_location | |
| [location-memory-kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/interpret/enum.MemoryKind.html#variant.CallerLocation | |
| [const-frame]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/interpret/struct.Frame.html | |
| [const-stack]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/interpret/struct.InterpCx.html#structfield.stack | |
| [fcx-get]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_codegen_ssa/mir/struct.FunctionCx.html#method.get_caller_location | |
| [frame-instance]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_const_eval/interpret/struct.Frame.html#structfield.instance |