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
//! Support for catching a panic while a panicked `Task` is being unwound.
//!
#![no_std]
#![feature(core_intrinsics)]
extern crate alloc;
extern crate task;
use core::mem::ManuallyDrop;
use alloc::boxed::Box;
use task::KillReason;
/// Invokes the given closure `f`, catching a panic as it is unwinding the stack.
///
/// Returns `Ok(R)` if the closure `f` executes and returns successfully,
/// otherwise returns `Err(cause)` if the closure panics, where `cause` is the original cause of the panic.
///
/// This function behaves similarly to the libstd version,
/// so please see its documentation here: <https://doc.rust-lang.org/std/panic/fn.catch_unwind.html>.
pub fn catch_unwind_with_arg<F, A, R>(f: F, arg: A) -> Result<R, KillReason>
where F: FnOnce(A) -> R,
{
// The `try()` intrinsic accepts only one "data" pointer as its only argument.
let mut ti_arg = TryIntrinsicArg {
func: ManuallyDrop::new(f),
arg: ManuallyDrop::new(arg),
// The initial value of `ret` doesn't matter. It will get replaced in all code paths.
ret: ManuallyDrop::new(Err(KillReason::Exception(0))),
};
// Invoke the actual try() intrinsic, which will jump to `call_func_with_arg`
let _try_val = unsafe {
core::intrinsics::r#try(
try_intrinsic_trampoline::<F, A, R>,
&mut ti_arg as *mut _ as *mut u8,
panic_callback::<F, A, R>,
)
};
// When `try` returns zero, it means the function ran successfully without panicking.
// The `Ok(R)` value was assigned to `ret` at the end of `try_intrinsic_trampoline()` below.
// When `try` returns non-zero, it means the function panicked.
// The `panic_callback()` would have already been invoked, and it would have set `ret` to `Err(KillReason)`.
//
// In both cases, we can just return the value `ret` field, which has been assigned the proper value.
ManuallyDrop::into_inner(ti_arg.ret)
}
/// This function will be automatically invoked by the `try` intrinsic above
/// upon catching a panic.
/// # Arguments
/// * a pointer to the
/// * a pointer to the arbitrary object passed around during the unwinding process,
/// which in Theseus is a pointer to the `UnwindingContext`.
fn panic_callback<F, A, R>(data_ptr: *mut u8, exception_object: *mut u8) where F: FnOnce(A) -> R {
let data = unsafe { &mut *(data_ptr as *mut TryIntrinsicArg<F, A, R>) };
let unwinding_context_boxed = unsafe { Box::from_raw(exception_object as *mut unwind::UnwindingContext) };
let unwinding_context = *unwinding_context_boxed;
let (_stack_frame_iter, cause, _taskref) = unwinding_context.into();
data.ret = ManuallyDrop::new(Err(cause));
}
/// A struct to accommodate the weird signature of `core::intrinsics::try`,
/// which accepts only a single pointer to this structure.
/// We model this after Rust libstd's wrappers around gcc-based unwinding, but modify it to contain one argument.
struct TryIntrinsicArg<F, A, R> where F: FnOnce(A) -> R {
/// The function that will be invoked in the `try()` intrinsic.
func: ManuallyDrop<F>,
/// The argument that will be passed into the above function.
arg: ManuallyDrop<A>,
/// The return value of the above function, which is an output parameter.
/// Note that this is only filled in by the `try()` intrinsic if the function returns successfully.
ret: ManuallyDrop<Result<R, KillReason>>,
}
/// This is the function that the `try()` intrinsic will jump to.
/// Since that intrinsic requires a `fn` ptr, we can't just directly call a closure `F` here because it's a `FnOnce` trait.
///
/// This function should not be called directly in our code.
fn try_intrinsic_trampoline<F, A, R>(try_intrinsic_arg: *mut u8) where F: FnOnce(A) -> R {
unsafe {
let data = try_intrinsic_arg as *mut TryIntrinsicArg<F, A, R>;
let data = &mut *data;
let f = ManuallyDrop::take(&mut data.func);
let a = ManuallyDrop::take(&mut data.arg);
data.ret = ManuallyDrop::new(
Ok(f(a)) // actually invoke the function
);
}
}
/// Resumes the unwinding procedure after it was caught with [`catch_unwind_with_arg()`].
///
/// This is analogous to the Rust's [`std::panic::resume_unwind()`] in that it is
/// intended to be used to continue unwinding after a panic was caught.
///
/// The argument is a [`KillReason`] instead of a typical Rust panic "payload"
/// (which is usually `Box<Any + Send>`) for two reasons:
/// 1. `KillReason` is the type returned by [`catch_unwind_with_arg()`] upon failure,
/// so it makes sense to continue unwinding with that same error type.
/// 2. It's more flexible than the standard Rust panic info type because it must also
/// represent the possibility of a non-panic failure, e.g., a machine exception.
///
/// [`std::panic::resume_unwind()`]: https://doc.rust-lang.org/std/panic/fn.resume_unwind.html
pub fn resume_unwind(caught_panic_reason: KillReason) -> ! {
// We can skip up to 2 frames here: `unwind::start_unwinding` and `resume_unwind` (this function)
let result = unwind::start_unwinding(caught_panic_reason, 2);
// `start_unwinding` should not return
panic!("BUG: start_unwinding() returned {:?}. This is an unexpected failure, as no unwinding occurred. Task: {:?}.",
result,
task::get_my_current_task()
);
}