| # Panicking in Rust | |
| ## Step 1: Invocation of the `panic!` macro. | |
| There are actually two panic macros - one defined in `core`, and one defined in `std`. | |
| This is due to the fact that code in `core` can panic. | |
| `core` is built before `std`, | |
| but we want panics to use the same machinery at runtime, whether they originate in `core` or `std`. | |
| ### core definition of panic! | |
| The `core` `panic!` macro eventually makes the following call (in [`library/core/src/panicking.rs`]): | |
| ```rust | |
| // NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call | |
| extern "Rust" { | |
| #[lang = "panic_impl"] | |
| fn panic_impl(pi: &PanicInfo<'_>) -> !; | |
| } | |
| let pi = PanicInfo::new( | |
| &fmt, | |
| Location::caller(), | |
| /* can_unwind */ true, | |
| /* force_no_backtrace */ false, | |
| ); | |
| unsafe { panic_impl(&pi) } | |
| ``` | |
| Actually resolving this goes through several layers of indirection: | |
| 1. In [`compiler/rustc_hir/src/weak_lang_items.rs`], `panic_impl` is | |
| declared as 'weak lang item', with the symbol `rust_begin_unwind`. | |
| This is used in `rustc_hir_analysis/src/collect.rs` to set the actual symbol name to | |
| `rust_begin_unwind`. | |
| Note that `panic_impl` is declared in an `extern "Rust"` block, | |
| which means that core will attempt to call a foreign symbol called `rust_begin_unwind` | |
| (to be resolved at link time) | |
| 2. In [`library/std/src/panicking.rs`], we have this definition: | |
| ```rust | |
| /// Entry point of panics from the core crate (`panic_impl` lang item). | |
| #[cfg(not(any(test, doctest)))] | |
| #[panic_handler] | |
| pub fn panic_handler(info: &core::panic::PanicInfo<'_>) -> ! { | |
| ... | |
| } | |
| ``` | |
| The special `panic_handler` attribute is resolved via [`compiler/rustc_passes/src/lang_items.rs`]. | |
| The [`extract_ast`] function converts the `panic_handler` attribute to a `panic_impl` lang item. | |
| Now, we have a matching `panic_handler` lang item in the `std`. | |
| This function goes | |
| through the same process as the `extern { fn panic_impl }` definition in `core`, ending | |
| up with a symbol name of `rust_begin_unwind`. | |
| At link time, the symbol reference in `core` | |
| will be resolved to the definition of `std` (the function called `panic_handler` in the | |
| Rust source). | |
| Thus, control flow will pass from core to std at runtime. | |
| This allows panics from `core` | |
| to go through the same infrastructure that other panics use (panic hooks, unwinding, etc) | |
| ### std implementation of panic! | |
| This is where the actual panic-related logic begins. | |
| In [`library/std/src/panicking.rs`], | |
| control passes to `panic_with_hook`. | |
| This method is responsible for invoking the global panic hook, and checking for double panics. | |
| Finally, | |
| we call `__rust_start_panic`, which is provided by the panic runtime. | |
| The call to `__rust_start_panic` is very weird - it is passed a `*mut &mut dyn PanicPayload`, | |
| converted to an `usize`. | |
| Let's break this type down: | |
| 1. `PanicPayload` is an internal trait. | |
| It is implemented for `PanicPayload` | |
| (a wrapper around the user-supplied payload type), and has a method | |
| `fn take_box(&mut self) -> *mut (dyn Any + Send)`. | |
| This method takes the user-provided payload (`T: Any + Send`), | |
| boxes it, and converts the box to a raw pointer. | |
| 2. When we call `__rust_start_panic`, we have an `&mut dyn PanicPayload`. | |
| However, this is a fat pointer (twice the size of a `usize`). | |
| To pass this to the panic runtime across an FFI boundary, we take a mutable | |
| reference *to this mutable reference* (`&mut &mut dyn PanicPayload`), and convert it to a raw | |
| pointer (`*mut &mut dyn PanicPayload`). | |
| The outer raw pointer is a thin pointer, since it points to a `Sized` type (a mutable reference). | |
| Therefore, we can convert this thin pointer into a `usize`, | |
| which is suitable for passing across an FFI boundary. | |
| Finally, we call `__rust_start_panic` with this `usize`. | |
| We have now entered the panic runtime. | |
| ## Step 2: The panic runtime | |
| Rust provides two panic runtimes: `panic_abort` and `panic_unwind`. | |
| The user chooses between them at build time via their `Cargo.toml` | |
| `panic_abort` is extremely simple: its implementation of `__rust_start_panic` just aborts, | |
| as you would expect. | |
| `panic_unwind` is the more interesting case. | |
| In its implementation of `__rust_start_panic`, we take the `usize`, convert | |
| it back to a `*mut &mut dyn PanicPayload`, dereference it, and call `take_box` | |
| on the `&mut dyn PanicPayload`. | |
| At this point, we have a raw pointer to the payload | |
| itself (a `*mut (dyn Send + Any)`): that is, a raw pointer to the actual value | |
| provided by the user who called `panic!`. | |
| At this point, the platform-independent code ends. | |
| We now call into platform-specific unwinding logic (e.g `unwind`). | |
| This code is responsible for unwinding the stack, running any 'landing pads' associated | |
| with each frame (currently, running destructors), and transferring control | |
| to the `catch_unwind` frame. | |
| Note that all panics either abort the process or get caught by some call to `catch_unwind`. | |
| In particular, in std's [runtime service], | |
| the call to the user-provided `main` function is wrapped in `catch_unwind`. | |
| [runtime service]: https://github.com/rust-lang/rust/blob/HEAD/library/std/src/rt.rs | |
| [`library/core/src/panicking.rs`]: https://doc.rust-lang.org/core/panicking/index.html | |
| [`compiler/rustc_hir/src/weak_lang_items.rs`]: https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc_hir/src/weak_lang_items.rs | |
| [`library/std/src/panicking.rs`]: https://github.com/rust-lang/rust/blob/HEAD/library/std/src/panicking.rs | |
| [`compiler/rustc_passes/src/lang_items.rs`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_passes/lang_items/index.html | |
| [`extract_ast`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_passes/lang_items/fn.extract_ast.html |