1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! `catch_unwind` and `resume_unwind`, without naming `std`.
//!
//! # Why this exists, and why it is called janky
//!
//! `std` is banned in this tree. But `FatalError` - rustc's "I refuse to compile this" - travels
//! by unwinding to a `catch_fatal_errors` at the top, and it is raised from twelve places
//! including every `emit_fatal`. With no way to catch it, a refused program aborts the process.
//! That is not an edge case: compiling a hello world hit it before the daemon could say what it
//! objected to.
//!
//! The honest fix is to make `FatalError` a return value threaded through the frontend. That is
//! hundreds of functions in code we do not own, and it is the *later* in "janky now, better
//! later". This is the now.
//!
//! # How it catches: the consumer lends it a catcher
//!
//! Catching a panic needs one of exactly two primitives, and neither is stable without `std`:
//!
//! - `std::panic::catch_unwind`, which is `std`;
//! - `core::intrinsics::catch_unwind`, which is `core_intrinsics` (nightly only), plus the
//! `__rust_panic_cleanup` symbol, which can only be declared with
//! `#[rustc_std_internal_symbol]` (`rustc_attrs`, nightly only) to get its mangled name.
//!
//! This module used the second pair until the crate had to build on stable. It now uses
//! neither. The frontend is a library, and whoever links it into a program links a panic
//! runtime too; that is the party that can catch. So the program calls [`install_catcher`]
//! once, at startup, with a function built on whatever it has. A program with `std`:
//!
//! ```ignore (needs std, which this crate cannot name)
//! fn catcher(f: &mut dyn FnMut()) -> Result<(), frontend::unwind_janky::Payload> {
//! std::panic::catch_unwind(std::panic::AssertUnwindSafe(f))
//! }
//! frontend::unwind_janky::install_catcher(catcher);
//! ```
//!
//! A `no_std` program with its own panic runtime installs one built on that runtime. Either way
//! the crate itself names no unstable item and no `std`.
//!
//! # What makes it janky, precisely
//!
//! - **It depends on the program installing a catcher.** Without one, [`catch`] runs the
//! closure and lets a panic go past, as `panic = "abort"` would: a fatal error then ends the
//! process instead of becoming an `Err`. [`unwinding_is_enabled`] reports false in that state,
//! so the startup assert in `frontend_facts` fails loudly instead of silently.
//! - **The payload is whatever the catcher hands back**, `Box<dyn Any + Send>` from `std`, so
//! `downcast_ref` works on it, which is what `catch_fatal_errors` needs.
//! - **It requires `panic = "unwind"`.** Under `panic = "abort"` there are no landing pads and no
//! catcher can catch anything.
use Box;
use Any;
use ;
/// What a caught panic carries.
///
/// The same type `std::panic::catch_unwind` yields, because it is the same payload: the panic
/// runtime built it, this only takes delivery. `downcast_ref` therefore works as it always did.
pub type Payload = ;
/// A panic catcher: run the closure, and return the payload if it panicked.
///
/// Non-generic on purpose, so one function pointer serves every `catch::<F, R>`.
pub type Catcher = fn ;
/// The installed [`Catcher`], or null. A function pointer stored as a data pointer, because
/// `core` has no atomic for function pointers.
static CATCHER: = new;
/// Install the function [`catch`] uses to catch panics. The last call wins.
///
/// Call it once, at startup, from the program that links the panic runtime. See the module
/// header for a `std` example.
/// Run `f`, catching a panic that unwinds out of it.
///
/// With no catcher installed this runs `f` and lets any panic propagate; see the module header.
///
/// # What this does not do
///
/// It does not make `f` safe to have panicked. `std::panic::catch_unwind` requires
/// `UnwindSafe` and every caller in this tree wrapped its closure in `AssertUnwindSafe` to get
/// past it, so the bound was carrying no information; it is not reproduced. A caught panic can
/// still leave a data structure half-updated, and the caller is responsible for not reading one.
/// Continue unwinding with a payload [`catch`] produced.
///
/// **The payload is not preserved.** `std::panic::resume_unwind` re-raises the original object;
/// this panics afresh, so a `downcast` further out sees a `&str` rather than whatever was thrown.
/// Every caller in this tree either discards the payload or has already inspected it - the one
/// that mattered, `catch_fatal_errors`, checks for `FatalErrorMarker` before deciding to resume -
/// so nothing reads it twice today. Fixing it means `__rust_start_panic`, which is the same
/// argument as writing a real unwinder.
!
/// Set across [`resume`]'s own raise, so [`record_panic`] can decline it.
static RESUMING: AtomicBool = new;
/// Whether [`catch`] can actually catch: unwinding is compiled in and a catcher is installed.
///
/// `catch` cannot work under `panic = "abort"`: there are no landing pads, so a panic aborts
/// before anything sees it. Nor can it work before [`install_catcher`]. Either way it would
/// still *compile*, which is the dangerous part - containment that silently never runs. Call
/// this once at startup and say so out loud. (It was a `const fn` while it only read the cfg.)
// ---- what the last panic said ----------------------------------------------------------------
/// The message and location of the most recent panic, for whoever catches it.
///
/// # Why the payload is not enough
///
/// [`resume`] does not preserve the payload: it panics afresh, so a [`catch`] further out
/// downcasts *its* string rather than the original. Every compiler ICE meets
/// `catch_fatal_errors` first, which resumes anything that is not its own `FatalErrorMarker`, so
/// by the time the daemon catches one the message has already been replaced. Recording it where
/// it is still in hand costs one pointer and does not wait on fixing the raise path.
///
/// An atomic pointer rather than a lock because this crate has no dependencies, and the panic
/// path is not where a lock should first be taken. The last writer wins, which is the right
/// answer: a panic while panicking is the one still unwinding.
///
/// Written by the `#[panic_handler]` in `unwind-runtime`, which is the only thing that sees a
/// `PanicInfo`. This half lives here because any target may link this crate, and only a `no_std`
/// binary may link that one.
static LAST_PANIC: AtomicPtr =
new;
/// Record what a panic said. Called from the panic handler, before the raise.
/// What the last panic said, taking it.
///
/// `None` once it has been read, so a later request cannot report a panic that belonged to an
/// earlier one. A caller that catches and finds `None` was not the thing that panicked.