lithium/intrinsic.rs
1use core::mem::ManuallyDrop;
2
3union Data<Func, Catch, T, E> {
4 init: (ManuallyDrop<Func>, ManuallyDrop<Catch>),
5 ok: ManuallyDrop<T>,
6 err: ManuallyDrop<E>,
7}
8
9/// Catch unwinding from a function.
10///
11/// Runs `func`. If `func` doesn't unwind, wraps its return value in `Ok` and returns. If `func`
12/// unwinds, runs `catch` inside the catch handler and wraps its return value in `Err`. If `catch`
13/// unwinds, the process aborts.
14///
15/// The argument to `catch` is target-dependent and matches the exception object as supplied by
16/// [`core::intrinsics::catch_unwind`]. See rustc sources for specifics.
17#[allow(
18 clippy::missing_errors_doc,
19 reason = "`Err` value is described immediately"
20)]
21#[inline]
22pub fn intercept<Func: FnOnce() -> T, Catch: FnOnce(*mut u8) -> E, T, E>(
23 func: Func,
24 catch: Catch,
25) -> Result<T, E> {
26 let mut data: Data<Func, Catch, T, E> = Data {
27 init: (ManuallyDrop::new(func), ManuallyDrop::new(catch)),
28 };
29
30 // SAFETY: `do_catch` is marked as `#[rustc_nounwind]`
31 if unsafe {
32 core::intrinsics::catch_unwind(
33 do_call::<Func, Catch, T, E>,
34 (&raw mut data).cast(),
35 do_catch::<Func, Catch, T, E>,
36 )
37 } == 0i32
38 {
39 // SAFETY: If zero was returned, no unwinding happened, so `do_call` must have finished till
40 // the assignment to `data.ok`.
41 Ok(ManuallyDrop::into_inner(unsafe { data.ok }))
42 } else {
43 // SAFETY: If a non-zero value was returned, unwinding has happened, so `do_catch` was
44 // invoked, thus `data.err` is initialized now.
45 Err(ManuallyDrop::into_inner(unsafe { data.err }))
46 }
47}
48
49// This function should be unsafe, but isn't due to the definition of `catch_unwind`.
50#[inline]
51fn do_call<Func: FnOnce() -> R, Catch: FnOnce(*mut u8) -> E, R, E>(data: *mut u8) {
52 // SAFETY: `data` is provided by the `catch_unwind` intrinsic, which copies the pointer to the
53 // `data` variable.
54 let data: &mut Data<Func, Catch, R, E> = unsafe { &mut *data.cast() };
55 // SAFETY: This function is called at the start of the process, so the `init.0` field is still
56 // initialized.
57 let func = unsafe { ManuallyDrop::take(&mut data.init.0) };
58 data.ok = ManuallyDrop::new(func());
59}
60
61// This function should be unsafe, but isn't due to the definition of `catch_unwind`.
62#[inline]
63#[rustc_nounwind]
64fn do_catch<Func: FnOnce() -> R, Catch: FnOnce(*mut u8) -> E, R, E>(data: *mut u8, ex: *mut u8) {
65 // SAFETY: `data` is provided by the `catch_unwind` intrinsic, which copies the pointer to the
66 // `data` variable.
67 let data: &mut Data<Func, Catch, R, E> = unsafe { &mut *data.cast() };
68 // SAFETY: This function is called immediately after `do_call`, so the `init.1` field is still
69 // initialized.
70 let catch = unsafe { ManuallyDrop::take(&mut data.init.1) };
71 data.err = ManuallyDrop::new(catch(ex));
72}