Thanks to visit codestin.com
Credit goes to docs.rs

magnus/
error.rs

1//! Rust types for working with Ruby Exceptions and other interrupts.
2//!
3//! See also [`Ruby`](Ruby#errors) for more error related methods.
4
5use std::{any::Any, borrow::Cow, ffi::CString, fmt, mem::transmute, os::raw::c_int};
6
7use rb_sys::{
8    rb_bug, rb_ensure, rb_errinfo, rb_exc_fatal, rb_exc_raise, rb_iter_break_value, rb_jump_tag,
9    rb_protect, rb_set_errinfo, rb_warning, ruby_special_consts, VALUE,
10};
11
12use crate::{
13    class::Class,
14    exception::Exception,
15    into_value::IntoValue,
16    module::Module,
17    value::{private::ReprValue as _, ReprValue, Value},
18    ExceptionClass, Ruby,
19};
20
21/// An error returned to indicate an attempt to interact with the Ruby API from
22/// a non-Ruby thread or without acquiring the GVL.
23#[derive(Debug)]
24pub enum RubyUnavailableError {
25    /// GVL is not locked.
26    GvlUnlocked,
27    /// Current thread is not a Ruby thread.
28    NonRubyThread,
29}
30
31impl fmt::Display for RubyUnavailableError {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            Self::NonRubyThread => write!(f, "Current thread is not a Ruby thread."),
35            Self::GvlUnlocked => write!(f, "GVL is not locked."),
36        }
37    }
38}
39
40impl std::error::Error for RubyUnavailableError {}
41
42/// # Errors
43///
44/// Functions for working with errors and flow control encoded as an [`Error`].
45///
46/// See also [`Error`] and the [`error`](self) module.
47impl Ruby {
48    /// Create a new error that will break from a loop when returned to Ruby.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use magnus::{prelude::*, Error, Ruby};
54    ///
55    /// fn example(ruby: &Ruby) -> Result<(), Error> {
56    ///     let i: i64 =
57    ///         ruby.range_new(1, 100, false)?
58    ///             .block_call("each", (), |ruby, args, _block| {
59    ///                 let i = i64::try_convert(*args.get(0).unwrap())?;
60    ///                 if i % 3 == 0 && i % 5 == 0 {
61    ///                     Err(ruby.iter_break_value(i))
62    ///                 } else {
63    ///                     Ok(())
64    ///                 }
65    ///             })?;
66    ///
67    ///     assert_eq!(i, 15);
68    ///     Ok(())
69    /// }
70    /// # Ruby::init(example).unwrap()
71    /// ```
72    pub fn iter_break_value<T>(&self, val: T) -> Error
73    where
74        T: IntoValue,
75    {
76        let val = self.into_value(val);
77        protect(|| {
78            unsafe { rb_iter_break_value(val.as_rb_value()) };
79            // we never get here, but this is needed to satisfy the type system
80            #[allow(unreachable_code)]
81            self.qnil()
82        })
83        .unwrap_err()
84    }
85
86    /// Outputs `s` to Ruby's stderr if Ruby is configured to output warnings.
87    pub fn warning(&self, s: &str) {
88        let s = CString::new(s).unwrap();
89        unsafe { rb_warning(s.as_ptr()) };
90    }
91}
92
93/// Shorthand for `std::result::Result<T, magnus::Error>`.
94pub type Result<T> = std::result::Result<T, Error>;
95
96/// The possible types of [`Error`].
97#[derive(Debug, Clone)]
98pub enum ErrorType {
99    /// An interrupt, such as `break` or `throw`.
100    Jump(Tag),
101    /// An error generated in Rust code that will raise an exception when
102    /// returned to Ruby.
103    Error(ExceptionClass, Cow<'static, str>),
104    /// A Ruby `Exception` captured from Ruby as an Error.
105    Exception(Exception),
106}
107
108/// Wrapper type for Ruby `Exception`s or other interrupts.
109#[derive(Debug, Clone)]
110pub struct Error(ErrorType);
111
112impl Error {
113    /// Create a new `Error` that can be raised as a Ruby `Exception` with
114    /// `msg`.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use magnus::{function, prelude::*, Error, Exception, Ruby};
120    ///
121    /// fn bang(ruby: &Ruby) -> Result<(), Error> {
122    ///     Err(Error::new(ruby.exception_runtime_error(), "BANG"))
123    /// }
124    ///
125    /// fn example(ruby: &Ruby) -> Result<(), Error> {
126    ///     ruby.define_global_function("bang", function!(bang, 0));
127    ///
128    ///     let error: Exception = ruby.eval(
129    ///         "
130    ///             begin
131    ///               bang
132    ///             rescue => e
133    ///               e
134    ///             end
135    ///             ",
136    ///     )?;
137    ///
138    ///     assert!(error.is_kind_of(ruby.exception_runtime_error()));
139    ///     let msg: String = error.funcall("message", ())?;
140    ///     assert_eq!(msg, "BANG");
141    ///
142    ///     Ok(())
143    /// }
144    /// # Ruby::init(example).unwrap()
145    /// ```
146    pub fn new<T>(class: ExceptionClass, msg: T) -> Self
147    where
148        T: Into<Cow<'static, str>>,
149    {
150        Self(ErrorType::Error(class, msg.into()))
151    }
152
153    pub(crate) fn from_tag(tag: Tag) -> Self {
154        Self(ErrorType::Jump(tag))
155    }
156
157    /// Create a new error that will break from a loop when returned to Ruby.
158    ///
159    /// # Panics
160    ///
161    /// Panics if called from a non-Ruby thread. See [`Ruby::iter_break_value`]
162    /// for the non-panicking version.
163    ///
164    /// # Examples
165    ///
166    /// ```
167    /// # #![allow(deprecated)]
168    /// use magnus::{prelude::*, Error};
169    /// # let _cleanup = unsafe { magnus::embed::init() };
170    ///
171    /// let i: i64 = magnus::Range::new(1, 100, false)
172    ///     .unwrap()
173    ///     .block_call("each", (), |_ruby, args, _block| {
174    ///         let i = i64::try_convert(*args.get(0).unwrap())?;
175    ///         if i % 3 == 0 && i % 5 == 0 {
176    ///             Err(Error::iter_break(i))
177    ///         } else {
178    ///             Ok(())
179    ///         }
180    ///     })
181    ///     .unwrap();
182    ///
183    /// assert_eq!(i, 15);
184    /// ```
185    #[cfg_attr(
186        not(feature = "old-api"),
187        deprecated(note = "please use `Ruby::iter_break_value` instead")
188    )]
189    #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
190    #[inline]
191    pub fn iter_break<T>(val: T) -> Self
192    where
193        T: IntoValue,
194    {
195        get_ruby!().iter_break_value(val)
196    }
197
198    /// Matches the internal `Exception` against `class` with same semantics as
199    /// Ruby's `rescue`.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// use magnus::{exception::ExceptionClass, prelude::*, Error, RModule, Ruby, TryConvert, Value};
205    ///
206    /// fn example(ruby: &Ruby) -> Result<(), Error> {
207    ///     let err: Error = ruby
208    ///         .eval::<Value>(
209    ///             "
210    ///       class ExampleError < StandardError
211    ///       end
212    ///       module Tag
213    ///       end
214    ///       class SpecificError < ExampleError
215    ///         include Tag
216    ///       end
217    ///       raise SpecificError
218    ///     ",
219    ///         )
220    ///         .unwrap_err();
221    ///
222    ///     fn get<T: TryConvert>(ruby: &Ruby, name: &str) -> Result<T, Error> {
223    ///         ruby.class_object().const_get::<_, T>(name)
224    ///     }
225    ///     assert!(err.is_kind_of(get::<ExceptionClass>(ruby, "SpecificError")?));
226    ///     assert!(err.is_kind_of(get::<ExceptionClass>(ruby, "ExampleError")?));
227    ///     assert!(err.is_kind_of(get::<ExceptionClass>(ruby, "StandardError")?));
228    ///     assert!(err.is_kind_of(get::<ExceptionClass>(ruby, "Exception")?));
229    ///     assert!(err.is_kind_of(get::<RModule>(ruby, "Tag")?));
230    ///
231    ///     assert!(!err.is_kind_of(get::<ExceptionClass>(ruby, "NoMethodError")?));
232    ///     assert!(!err.is_kind_of(get::<RModule>(ruby, "Math")?));
233    ///
234    ///     Ok(())
235    /// }
236    /// # Ruby::init(example).unwrap()
237    /// ```
238    pub fn is_kind_of<T>(&self, class: T) -> bool
239    where
240        T: ReprValue + Module,
241    {
242        match self.0 {
243            ErrorType::Jump(_) => false,
244            ErrorType::Error(c, _) => c.is_inherited(class),
245            ErrorType::Exception(e) => e.is_kind_of(class),
246        }
247    }
248
249    /// Consumes `self`, returning an `Exception`.
250    ///
251    /// # Panics
252    ///
253    /// Panics if called on an `Error::Jump`.
254    fn exception(self) -> Exception {
255        let handle = unsafe { Ruby::get_unchecked() };
256        match self.0 {
257            ErrorType::Jump(_) => panic!("Error::exception() called on {}", self),
258            ErrorType::Error(class, msg) => {
259                match class.new_instance((handle.str_new(msg.as_ref()),)) {
260                    Ok(e) | Err(Error(ErrorType::Exception(e))) => e,
261                    Err(err) => unreachable!("*very* unexpected error: {}", err),
262                }
263            }
264            ErrorType::Exception(e) => e,
265        }
266    }
267
268    /// Returns the [`ErrorType`] for self.
269    pub fn error_type(&self) -> &ErrorType {
270        &self.0
271    }
272
273    /// Returns the inner [`Value`] of `self`, if there is one.
274    ///
275    /// The returned `Value` may be a subclass or an instance of `Exception`.
276    ///
277    /// This function is provided for rare cases where the `Error` needs to be
278    /// stored on the heap and the inner value needs to be
279    /// [marked](`crate::gc::Marker::mark`) to avoid being garbage collected.
280    pub fn value(&self) -> Option<Value> {
281        match self.0 {
282            ErrorType::Jump(_) => None,
283            ErrorType::Error(c, _) => Some(c.as_value()),
284            ErrorType::Exception(e) => Some(e.as_value()),
285        }
286    }
287
288    /// Create an `Error` from the error value of [`std::panic::catch_unwind`].
289    ///
290    /// The Ruby Exception will be `fatal`, terminating the Ruby process, but
291    /// allowing cleanup code to run.
292    pub(crate) fn from_panic(e: Box<dyn Any + Send + 'static>) -> Self {
293        let msg = if let Some(&m) = e.downcast_ref::<&'static str>() {
294            m.into()
295        } else if let Some(m) = e.downcast_ref::<String>() {
296            m.clone().into()
297        } else {
298            "panic".into()
299        };
300        Self(ErrorType::Error(
301            unsafe { Ruby::get_unchecked().exception_fatal() },
302            msg,
303        ))
304    }
305}
306
307impl fmt::Display for Error {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        match &self.0 {
310            ErrorType::Jump(s) => s.fmt(f),
311            ErrorType::Error(e, m) => write!(f, "{}: {}", e, m),
312            ErrorType::Exception(e) => e.fmt(f),
313        }
314    }
315}
316
317impl From<Exception> for Error {
318    fn from(val: Exception) -> Self {
319        Self(ErrorType::Exception(val))
320    }
321}
322
323/// Conversions into [`Error`].
324pub trait IntoError {
325    /// Convert `self` into [`Error`].
326    fn into_error(self, ruby: &Ruby) -> Error;
327}
328
329impl IntoError for Error {
330    #[inline]
331    fn into_error(self, _: &Ruby) -> Error {
332        self
333    }
334}
335
336/// A wrapper to make a [`Error`] [`Send`] + [`Sync`].
337///
338/// [`Error`] is not [`Send`] or [`Sync`] as it provides a way to call some of
339/// Ruby's APIs, which are not safe to call from a non-Ruby thread.
340///
341/// [`Error`] is safe to send between Ruby threads, but Rust's trait system
342/// currently can not model this detail.
343///
344/// To resolve this, the `OpaqueError` type makes an [`Error`] [`Send`] +
345/// [`Sync`] by removing the ability use it with any Ruby APIs.
346///
347/// [`OpaqueError::into_error_with`] provides a way to safely get an [`Error`]
348/// from a `OpaqueError`].
349///
350/// Note that `OpaqueError` contains a Ruby value, so must be kept on the stack
351/// of a Ruby thread to prevent it from being Garbage Collected (or otherwise
352/// protected from premature GC).
353#[derive(Clone)]
354pub struct OpaqueError(ErrorType);
355
356unsafe impl Send for OpaqueError {}
357unsafe impl Sync for OpaqueError {}
358
359impl OpaqueError {
360    /// Convert an `OpaqueError` into an [`Error`].
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// use magnus::{error::OpaqueError, Error, Ruby};
366    /// # let _cleanup = unsafe { magnus::embed::init() };
367    ///
368    /// let ruby = Ruby::get().unwrap(); // errors on non-Ruby thread
369    /// let opaque_err = OpaqueError::from(Error::new(ruby.exception_runtime_error(), "test"));
370    ///
371    /// // send to another Ruby thread
372    ///
373    /// let ruby = Ruby::get().unwrap(); // errors on non-Ruby thread
374    /// let err = OpaqueError::into_error_with(opaque_err, &ruby);
375    /// assert!(err.is_kind_of(ruby.exception_runtime_error()));
376    /// ```
377    #[allow(unused_variables)]
378    pub fn into_error_with(this: Self, handle: &Ruby) -> Error {
379        Error(this.0)
380    }
381}
382
383impl From<Error> for OpaqueError {
384    fn from(err: Error) -> Self {
385        Self(err.0)
386    }
387}
388
389impl IntoError for OpaqueError {
390    #[inline]
391    fn into_error(self, _: &Ruby) -> Error {
392        Error(self.0)
393    }
394}
395
396/// The state of a call to Ruby exiting early, interrupting the normal flow
397/// of code.
398#[derive(Debug, Clone, Copy)]
399#[repr(i32)]
400pub enum Tag {
401    // None = 0,
402    /// Early return from a block.
403    Return = 1,
404    /// Break from a block.
405    Break = 2,
406    /// Early return from a block, continuing to next block call.
407    Next = 3,
408    /// Break from a block after an error, block will be subsequently re-run.
409    Retry = 4,
410    /// Break from a block that will be subsequently re-run.
411    Redo = 5,
412    /// Ruby stack unwound with an error.
413    Raise = 6,
414    /// Ruby stack unwound as flow control.
415    Throw = 7,
416    /// Block or method exiting early due to unrecoverable error.
417    Fatal = 8,
418}
419
420impl Tag {
421    fn resume(self) -> ! {
422        unsafe { rb_jump_tag(self as c_int) };
423    }
424}
425
426impl fmt::Display for Tag {
427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        match self {
429            // Self::None => write!(f, "None"),
430            Self::Return => write!(f, "Return"),
431            Self::Break => write!(f, "Break"),
432            Self::Next => write!(f, "Next"),
433            Self::Retry => write!(f, "Retry"),
434            Self::Redo => write!(f, "Redo"),
435            Self::Raise => write!(f, "Raise"),
436            Self::Throw => write!(f, "Throw"),
437            Self::Fatal => write!(f, "Fatal"),
438        }
439    }
440}
441
442/// Calls the given closure, rescuing Ruby Exceptions and returning them as
443/// an [`Error`].
444///
445/// All functions exposed by magnus that call Ruby in a way that may raise
446/// already use this internally, but this is provided for anyone calling
447/// the Ruby C API directly.
448pub(crate) fn protect<F, T>(func: F) -> Result<T>
449where
450    F: FnOnce() -> T,
451    T: ReprValue,
452{
453    // nested function as this is totally unsafe to call out of this context
454    // arg should not be a VALUE, but a mutable pointer to F, cast to VALUE
455    unsafe extern "C" fn call<F, T>(arg: VALUE) -> VALUE
456    where
457        F: FnOnce() -> T,
458        T: ReprValue,
459    {
460        let closure = (*(arg as *mut Option<F>)).take().unwrap();
461        (closure)().as_rb_value()
462    }
463
464    // Tag::None
465    let mut state = 0;
466    // rb_protect takes:
467    // arg1: function pointer that returns a VALUE
468    // arg2: a VALUE
469    // arg3: a pointer to an int.
470    // rb_protect then calls arg1 with arg2 and returns the VALUE that arg1
471    // returns. If a Ruby exception is raised (or other interrupt) the VALUE
472    // returned is instead Qnil, and arg3 is set to non-zero.
473    // As arg2 is only ever passed to arg1 and otherwise not touched we can
474    // pack in whatever data we want that will fit into a VALUE. This is part
475    // of the api and safe to do.
476    // In this case we use arg2 to pass a pointer the Rust closure we actually
477    // want to call, and arg1 is just a simple adapter to call arg2.
478    let result = unsafe {
479        let mut some_func = Some(func);
480        let closure = &mut some_func as *mut Option<F> as VALUE;
481        rb_protect(Some(call::<F, T>), closure, &mut state as *mut c_int)
482    };
483
484    match state {
485        // Tag::None
486        0 => unsafe { Ok(T::from_value_unchecked(Value::new(result))) },
487        // Tag::Raise
488        6 => unsafe {
489            let ex = Exception::from_rb_value_unchecked(rb_errinfo());
490            rb_set_errinfo(Ruby::get_unchecked().qnil().as_rb_value());
491            Err(ex.into())
492        },
493        other => Err(Error::from_tag(unsafe { transmute(other) })),
494    }
495}
496
497pub(crate) fn ensure<F1, F2, T>(func: F1, ensure: F2) -> T
498where
499    F1: FnOnce() -> T,
500    F2: FnOnce(),
501    T: ReprValue,
502{
503    unsafe extern "C" fn call_func<F1, T>(arg: VALUE) -> VALUE
504    where
505        F1: FnOnce() -> T,
506        T: ReprValue,
507    {
508        let closure = (*(arg as *mut Option<F1>)).take().unwrap();
509        (closure)().as_rb_value()
510    }
511
512    unsafe extern "C" fn call_ensure<F2>(arg: VALUE) -> VALUE
513    where
514        F2: FnOnce(),
515    {
516        let closure = (*(arg as *mut Option<F2>)).take().unwrap();
517        (closure)();
518        ruby_special_consts::RUBY_Qnil as VALUE
519    }
520
521    let result = unsafe {
522        let call_func_ptr = call_func::<F1, T> as unsafe extern "C" fn(VALUE) -> VALUE;
523        let mut some_func = Some(func);
524        let func_closure = &mut some_func as *mut Option<F1> as VALUE;
525        let call_ensure_ptr = call_ensure::<F2> as unsafe extern "C" fn(VALUE) -> VALUE;
526        let mut some_ensure = Some(ensure);
527        let ensure_closure = &mut some_ensure as *mut Option<F2> as VALUE;
528        rb_ensure(
529            Some(call_func_ptr),
530            func_closure,
531            Some(call_ensure_ptr),
532            ensure_closure,
533        )
534    };
535
536    unsafe { T::from_value_unchecked(Value::new(result)) }
537}
538
539pub(crate) fn raise(e: Error) -> ! {
540    match e.0 {
541        ErrorType::Jump(tag) => tag.resume(),
542        ErrorType::Error(class, _)
543            if class.as_rb_value()
544                == unsafe { Ruby::get_unchecked().exception_fatal().as_rb_value() } =>
545        {
546            unsafe { rb_exc_fatal(e.exception().as_rb_value()) }
547            // friendly reminder: we really never get here, and as such won't
548            // drop any values still in scope, make sure everything has been
549            // consumed/dropped
550        }
551        _ => {
552            unsafe { rb_exc_raise(e.exception().as_rb_value()) }
553            // friendly reminder: we really never get here, and as such won't
554            // drop any values still in scope, make sure everything has been
555            // consumed/dropped
556        }
557    };
558}
559
560pub(crate) fn bug_from_panic(e: Box<dyn Any + Send + 'static>, or: &str) -> ! {
561    let msg: Cow<'_, str> = if let Some(&m) = e.downcast_ref::<&'static str>() {
562        m.into()
563    } else if let Some(m) = e.downcast_ref::<String>() {
564        m.clone().into()
565    } else {
566        or.into()
567    };
568    bug(&msg)
569}
570
571/// Immediately terminate the process, printing Ruby internal state for
572/// debugging.
573pub fn bug(s: &str) -> ! {
574    let s = CString::new(s).unwrap_or_else(|_| CString::new("panic").unwrap());
575    unsafe { rb_bug(s.as_ptr()) };
576    // as we never get here `s` isn't dropped, technically this is a memory
577    // leak, in practice we don't care because we just hard crashed
578}
579
580/// Outputs `s` to Ruby's stderr if Ruby is configured to output warnings.
581///
582/// Otherwise does nothing.
583///
584/// # Panics
585///
586/// Panics if called from a non-Ruby thread. See [`Ruby::warning`] for the
587/// non-panicking version.
588#[cfg_attr(
589    not(feature = "old-api"),
590    deprecated(note = "please use `Ruby::warning` instead")
591)]
592#[cfg_attr(docsrs, doc(cfg(feature = "old-api")))]
593#[inline]
594pub fn warning(s: &str) {
595    get_ruby!().warning(s)
596}