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
use std::borrow::Cow;
use std::error;
use std::fmt;
use std::hint;

use crate::sys;
use crate::{Artichoke, Guard};

#[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)
    }
}

/// 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 entrypoint into Rust from mruby.
pub unsafe fn raise<T>(mut guard: Guard<'_>, exception: T) -> !
where
    T: RubyException + fmt::Debug,
{
    let exc = exception.as_mrb_value(&mut guard);
    let mrb: *mut sys::mrb_state = guard.mrb.as_mut();
    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);
    } else {
        error!("unable to raise {:?}", 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,
            "RuntimeError\0".as_ptr() as *const i8,
            "Unable to raise exception".as_ptr() as *const i8,
        );
    }
    // unreachable: `raise` will unwind the stack with longjmp.
    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.
#[allow(clippy::module_name_repetitions)]
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 raiseable [`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> {}

impl error::Error for &dyn RubyException {}