artichoke_backend/
exception_handler.rs1use std::borrow::Cow;
2use std::error;
3use std::fmt;
4
5use bstr::BString;
6use scolapasta_string_escape::format_debug_escape_into;
7
8use crate::Artichoke;
9use crate::core::{TryConvertMut, Value as _};
10use crate::error::{Error, RubyException};
11use crate::gc::MrbGarbageCollection;
12use crate::sys;
13use crate::value::Value;
14
15#[derive(Default, Debug)]
19pub struct Builder(CaughtException);
20
21impl Builder {
22 #[must_use]
24 pub fn new() -> Self {
25 Self::default()
26 }
27
28 #[must_use]
29 pub fn with_value(mut self, value: Value) -> Self {
30 self.0.value = value;
31 self
32 }
33
34 #[must_use]
35 pub fn with_name(mut self, name: String) -> Self {
36 self.0.name = name;
37 self
38 }
39
40 #[must_use]
41 pub fn with_message(mut self, message: Vec<u8>) -> Self {
42 self.0.message = message.into();
43 self
44 }
45
46 #[must_use]
47 pub fn finish(self) -> CaughtException {
48 self.0
49 }
50}
51
52#[derive(Default, Debug, Clone)]
56pub struct CaughtException {
57 value: Value,
58 name: String,
59 message: BString,
60}
61
62impl CaughtException {
63 #[must_use]
65 pub fn builder() -> Builder {
66 Builder::new()
67 }
68
69 #[must_use]
71 pub fn with_value_class_and_message(value: Value, name: String, message: Vec<u8>) -> Self {
72 let message = message.into();
73 Self { value, name, message }
74 }
75}
76
77impl fmt::Display for CaughtException {
78 fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.write_str(&self.name())?;
80 f.write_str(" (")?;
81 format_debug_escape_into(&mut f, self.message())?;
82 f.write_str(")")?;
83 Ok(())
84 }
85}
86
87impl error::Error for CaughtException {}
88
89impl RubyException for CaughtException {
90 fn message(&self) -> Cow<'_, [u8]> {
91 self.message.as_slice().into()
92 }
93
94 fn name(&self) -> Cow<'_, str> {
95 self.name.as_str().into()
96 }
97
98 fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
99 let backtrace = self.value.funcall(interp, "backtrace", &[], None).ok()?;
100 let backtrace = interp.try_convert_mut(backtrace).ok()?;
101 Some(backtrace)
102 }
103
104 fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
105 let _ = interp;
106 Some(self.value.inner())
107 }
108}
109
110impl From<CaughtException> for Box<dyn RubyException> {
111 fn from(exc: CaughtException) -> Self {
112 Box::new(exc)
113 }
114}
115
116impl From<CaughtException> for Error {
117 fn from(exc: CaughtException) -> Self {
118 Self::from(Box::<dyn RubyException>::from(exc))
119 }
120}
121
122pub fn last_error(interp: &mut Artichoke, exception: Value) -> Result<Error, Error> {
130 let mut arena = interp.create_arena_savepoint()?;
131
132 let class = exception.funcall(&mut arena, "class", &[], None)?;
161 let classname = class.funcall(&mut arena, "name", &[], None)?;
162 let classname = classname.try_convert_into_mut::<&str>(&mut arena)?;
163 let message = exception.funcall(&mut arena, "message", &[], None)?;
164 let message = message.try_convert_into_mut::<&[u8]>(&mut arena)?;
165
166 let exc = CaughtException::builder()
167 .with_value(exception)
168 .with_name(classname.into())
169 .with_message(message.to_vec())
170 .finish();
171 Ok(Error::from(exc))
172}
173
174#[cfg(test)]
175mod tests {
176 use bstr::ByteSlice;
177
178 use crate::test::prelude::*;
179
180 #[test]
181 fn return_exception() {
182 let mut interp = interpreter();
183 let err = interp.eval(b"raise ArgumentError.new('waffles')").unwrap_err();
184 assert_eq!("ArgumentError", err.name().as_ref());
185 assert_eq!(b"waffles".as_bstr(), err.message().as_ref().as_bstr());
186 let expected_backtrace = b"(eval):1".to_vec();
187 let backtrace = bstr::join("\n", err.vm_backtrace(&mut interp).unwrap());
188 assert_eq!(backtrace.as_bstr(), expected_backtrace.as_bstr());
189 }
190
191 #[test]
192 fn return_exception_with_no_backtrace() {
193 let mut interp = interpreter();
194 let err = interp.eval(b"def bad; (; end").unwrap_err();
195 assert_eq!("SyntaxError", err.name().as_ref());
196 assert_eq!(b"syntax error".as_bstr(), err.message().as_ref().as_bstr());
197 assert_eq!(None, err.vm_backtrace(&mut interp));
198 }
199
200 #[test]
201 fn raise_does_not_panic_or_segfault() {
202 let mut interp = interpreter();
203 interp.eval(b"raise 'foo'").unwrap_err();
204 interp.eval(b"raise 'foo'").unwrap_err();
205 interp.eval(br#"eval("raise 'foo'")"#).unwrap_err();
206 interp.eval(br#"eval("raise 'foo'")"#).unwrap_err();
207 interp.eval(b"require 'foo'").unwrap_err();
208 interp.eval(b"require 'foo'").unwrap_err();
209 interp.eval(br#"eval("require 'foo'")"#).unwrap_err();
210 interp.eval(br#"eval("require 'foo'")"#).unwrap_err();
211 interp.eval(b"Regexp.compile(2)").unwrap_err();
212 interp.eval(b"Regexp.compile(2)").unwrap_err();
213 #[cfg(feature = "core-regexp")]
214 {
215 interp.eval(br#"eval("Regexp.compile(2)")"#).unwrap_err();
216 interp.eval(br#"eval("Regexp.compile(2)")"#).unwrap_err();
217 }
218 #[cfg(feature = "stdlib-forwardable")]
219 {
220 const REQUIRE_TEST: &[u8] = b"\
221def fail
222 begin
223 require 'foo'
224 rescue LoadError
225 require 'forwardable'
226 end
227end
228
229fail
230";
231 interp.eval(REQUIRE_TEST).unwrap();
232 }
233 let kernel = interp.eval(b"Kernel").unwrap();
234 kernel.funcall(&mut interp, "raise", &[], None).unwrap_err();
235 kernel.funcall(&mut interp, "raise", &[], None).unwrap_err();
236 }
237}