artichoke_backend/error.rs
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
//! Error types for Ruby exceptions and unwinding support.
//!
//! This module contains the boxed trait object and underlying trait that
//! unifies all error types in this crate as a Ruby `Exception`.
//!
//! [`raise`] can convert any [`RubyException`] into an unwinding action.
use std::borrow::Cow;
use std::error;
use std::ffi::CStr;
use std::fmt;
use std::hint;
use crate::sys;
use crate::{Artichoke, Guard};
/// The `Error` type, a wrapper around a dynamic exception type.
///
/// `Error` works a lot like `Box<dyn std::error::Error>`, but with these
/// differences:
///
/// - `Error` requires that the error is `'static`.
/// - `Error` requires that the error implement [`RubyException`].
/// - `Error` can convert itself to a backtrace on the underlying Ruby VM.
///
/// All types that implement [`RubyException`] are able to be converted to
/// `Error`.
#[derive(Debug)]
pub struct Error(Box<dyn RubyException>);
impl RubyException for Error {
fn message(&self) -> Cow<'_, [u8]> {
self.0.message()
}
/// Class name of the `Exception`.
fn name(&self) -> Cow<'_, str> {
self.0.name()
}
fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
self.0.vm_backtrace(interp)
}
fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
self.0.as_mrb_value(interp)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl error::Error for Error {}
impl From<Box<dyn RubyException>> for Error {
fn from(exc: Box<dyn RubyException>) -> Self {
Self(exc)
}
}
static RUNTIME_ERROR_CSTR: &CStr = qed::const_cstr_from_str!("RuntimeError\0");
static UNABLE_TO_RAISE_MESSAGE: &CStr = qed::const_cstr_from_str!("Unable to raise exception\0");
/// Raise implementation for [`RubyException`] boxed trait objects.
///
/// # Safety
///
/// This function unwinds the stack with `longjmp`, which will ignore all Rust
/// landing pads for panics and exit routines for cleaning up borrows. Callers
/// should ensure that only [`Copy`] items are alive in the current stack frame.
///
/// Because this precondition must hold for all frames between the caller and
/// the closest [`sys::mrb_protect`] landing pad, this function should only be
/// called in the entry point into Rust from mruby.
pub unsafe fn raise<T>(mut guard: Guard<'_>, exception: T) -> !
where
T: RubyException + fmt::Debug,
{
// Convert the `RubyException` into a raisable boxed Ruby value.
let exc = exception.as_mrb_value(&mut guard);
// Pull out the raw pointer to the `mrb_state` so we can drop down to raw
// `MRB_API` functions.
let mrb: *mut sys::mrb_state = guard.mrb.as_mut();
// Ensure the Artichoke `State` is moved back into the `mrb_state`.
drop(guard);
if let Some(exc) = exc {
// Any non-`Copy` objects that we haven't cleaned up at this point will
// leak, so drop everything.
drop(exception);
// `mrb_exc_raise` will call longjmp which will unwind the stack.
sys::mrb_exc_raise(mrb, exc);
// SAFETY: This line is unreachable because `raise` will unwind the
// stack with `longjmp` when calling `sys::mrb_exc_raise` in the
// preceding line.
hint::unreachable_unchecked()
}
// Being unable to turn the given exception into an `mrb_value` is a bug, so
// log loudly to stderr and attempt to fallback to a runtime error.
emit_fatal_warning!("Unable to raise exception: {:?}", exception);
// Any non-`Copy` objects that we haven't cleaned up at this point will
// leak, so drop everything.
drop(exception);
// `mrb_sys_raise` will call longjmp which will unwind the stack.
sys::mrb_sys_raise(mrb, RUNTIME_ERROR_CSTR.as_ptr(), UNABLE_TO_RAISE_MESSAGE.as_ptr());
// SAFETY: This line is unreachable because `raise` will unwind the stack
// with `longjmp` when calling `sys::mrb_exc_raise` in the preceding line.
hint::unreachable_unchecked()
}
/// Polymorphic exception type that corresponds to Ruby's `Exception`.
///
/// All types that implement `RubyException` can be raised with
/// [`error::raise`](raise). Rust code can re-raise a trait object to
/// propagate exceptions from native code back into the interpreter.
pub trait RubyException: error::Error + 'static {
/// Message of the `Exception`.
///
/// This value is a byte slice since Ruby `String`s are equivalent to
/// `Vec<u8>`.
fn message(&self) -> Cow<'_, [u8]>;
/// Class name of the `Exception`.
fn name(&self) -> Cow<'_, str>;
/// Optional backtrace specified by a `Vec` of frames.
fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>>;
/// Return a raise-able [`sys::mrb_value`].
fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value>;
}
impl RubyException for Box<dyn RubyException> {
fn message(&self) -> Cow<'_, [u8]> {
self.as_ref().message()
}
fn name(&self) -> Cow<'_, str> {
self.as_ref().name()
}
fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
self.as_ref().vm_backtrace(interp)
}
fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
self.as_ref().as_mrb_value(interp)
}
}
impl error::Error for Box<dyn RubyException> {}