artichoke_backend/extn/core/exception/
mod.rs

1//! Ruby error handling types.
2//!
3//! This module implements the [`Exception`] class from Ruby Core. It is a
4//! collection of error types that the interpreter uses to unwind the stack in
5//! the event of an error with [`Kernel#raise`].
6//!
7//! You can use these types by accessing them in the interpreter. The types are
8//! globally available in the root namespace.
9//!
10//! ```ruby
11//! RuntimeError.new
12//!
13//! raise ArgumentError, "missing semicolon"
14//! ```
15//!
16//! This module implements the core exception types with [`spinoso-exception`]
17//! and re-exports these types.
18//!
19//! [`Exception`]: https://ruby-doc.org/core-3.1.2/Exception.html
20//! [`Kernel#raise`]: https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-raise
21//! [`spinoso-exception`]: spinoso_exception
22
23use std::borrow::Cow;
24
25#[doc(inline)]
26pub use spinoso_exception::core::*;
27
28use crate::extn::prelude::*;
29
30pub(in crate::extn) mod mruby;
31
32/// Implement traits to convert Spinoso exceptions to artichoke-backend error
33/// types.
34///
35/// This macro implements the artichoke-backend trait [`RubyException`]. This
36/// trait differs from [`RubyException` in spinoso-exception] by having
37/// additional APIs for gluing to the mruby VM, such as [backtraces].
38///
39/// [`RubyException` in spinoso-exception]: spinoso_exception::RubyException
40/// [backtraces]: RubyException::vm_backtrace
41macro_rules! ruby_exception_impl {
42    ($exc:ty) => {
43        impl From<$exc> for Error {
44            fn from(exception: $exc) -> Error {
45                let err: Box<dyn RubyException> = Box::new(exception);
46                Self::from(err)
47            }
48        }
49
50        impl RubyException for $exc {
51            fn message(&self) -> Cow<'_, [u8]> {
52                Cow::Borrowed(Self::message(self))
53            }
54
55            fn name(&self) -> Cow<'_, str> {
56                Cow::Borrowed(Self::name(self))
57            }
58
59            fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
60                let _ = interp;
61                None
62            }
63
64            fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
65                let message = interp.try_convert_mut(self.message()).ok()?;
66                let value = interp.new_instance::<Self>(&[message]).ok().flatten()?;
67                Some(value.inner())
68            }
69        }
70    };
71}
72
73ruby_exception_impl!(Exception);
74ruby_exception_impl!(NoMemoryError);
75ruby_exception_impl!(ScriptError);
76ruby_exception_impl!(LoadError);
77ruby_exception_impl!(NotImplementedError);
78ruby_exception_impl!(SyntaxError);
79ruby_exception_impl!(SecurityError);
80ruby_exception_impl!(SignalException);
81ruby_exception_impl!(Interrupt);
82// Default for `rescue`.
83ruby_exception_impl!(StandardError);
84ruby_exception_impl!(ArgumentError);
85ruby_exception_impl!(UncaughtThrowError);
86ruby_exception_impl!(EncodingError);
87ruby_exception_impl!(FiberError);
88ruby_exception_impl!(IOError);
89ruby_exception_impl!(EOFError);
90ruby_exception_impl!(IndexError);
91ruby_exception_impl!(KeyError);
92ruby_exception_impl!(StopIteration);
93ruby_exception_impl!(LocalJumpError);
94ruby_exception_impl!(NameError);
95ruby_exception_impl!(NoMethodError);
96ruby_exception_impl!(RangeError);
97ruby_exception_impl!(FloatDomainError);
98ruby_exception_impl!(RegexpError);
99// Default `Exception` type for `raise`.
100ruby_exception_impl!(RuntimeError);
101ruby_exception_impl!(FrozenError);
102ruby_exception_impl!(SystemCallError);
103// TODO: Implement `Errno` family of exceptions.
104ruby_exception_impl!(ThreadError);
105ruby_exception_impl!(TypeError);
106ruby_exception_impl!(ZeroDivisionError);
107ruby_exception_impl!(SystemExit);
108ruby_exception_impl!(SystemStackError);
109// Fatal interpreter error. Impossible to rescue.
110ruby_exception_impl!(Fatal);
111
112#[cfg(test)]
113mod tests {
114    use bstr::ByteSlice;
115
116    use crate::test::prelude::*;
117
118    struct Run;
119
120    unsafe extern "C-unwind" fn run_run(mrb: *mut sys::mrb_state, _slf: sys::mrb_value) -> sys::mrb_value {
121        unwrap_interpreter!(mrb, to => guard);
122        let exc = RuntimeError::with_message("something went wrong");
123        // SAFETY: only Copy objects remain on the stack
124        unsafe { error::raise(guard, exc) }
125    }
126
127    impl File for Run {
128        type Artichoke = Artichoke;
129
130        type Error = Error;
131
132        fn require(interp: &mut Artichoke) -> Result<(), Self::Error> {
133            let spec = class::Spec::new("Run", c"Run", None, None).unwrap();
134            class::Builder::for_spec(interp, &spec)
135                .add_self_method("run", run_run, sys::mrb_args_none())?
136                .define()?;
137            interp.def_class::<Self>(spec)?;
138            Ok(())
139        }
140    }
141
142    #[test]
143    fn raise() {
144        let mut interp = interpreter();
145        Run::require(&mut interp).unwrap();
146        let err = interp.eval(b"Run.run").unwrap_err();
147        assert_eq!("RuntimeError", err.name().as_ref());
148        assert_eq!(b"something went wrong".as_bstr(), err.message().as_ref().as_bstr());
149        let expected_backtrace = b"(eval):1:in run\n(eval):1".to_vec();
150        let actual_backtrace = bstr::join("\n", err.vm_backtrace(&mut interp).unwrap());
151        assert_eq!(expected_backtrace.as_bstr(), actual_backtrace.as_bstr());
152    }
153}