artichoke_backend/
ffi.rs

1//! Functions for interacting directly with mruby structs from [`sys`].
2//!
3//! These functions are unsafe. Use them carefully.
4
5use std::borrow::Cow;
6use std::error;
7use std::fmt;
8use std::mem;
9use std::ptr::{self, NonNull};
10
11use spinoso_exception::Fatal;
12
13use crate::Artichoke;
14use crate::core::{ClassRegistry, TryConvertMut};
15use crate::error::{Error, RubyException};
16use crate::state::State;
17use crate::sys;
18
19/// Extract an [`Artichoke`] interpreter from the user data pointer on a
20/// [`sys::mrb_state`].
21///
22/// Calling this function will move the [`State`] out of the [`sys::mrb_state`]
23/// into the [`Artichoke`] interpreter.
24///
25/// # Safety
26///
27/// This function assumes that the user data pointer was created with
28/// [`Box::into_raw`] and that the pointer is to a non-free'd
29/// [`Box`]`<`[`State`]`>`.
30pub unsafe fn from_user_data(mrb: *mut sys::mrb_state) -> Result<Artichoke, InterpreterExtractError> {
31    let Some(mut mrb) = NonNull::new(mrb) else {
32        emit_fatal_warning!("ffi: Attempted to extract Artichoke from null `mrb_state`");
33        return Err(InterpreterExtractError::new());
34    };
35
36    // SAFETY: `mrb` is valid and non-null by caller contract and non-null check.
37    let ud = mem::replace(unsafe { &mut mrb.as_mut().ud }, ptr::null_mut());
38    let state = if let Some(state) = NonNull::new(ud) {
39        state.cast::<State>()
40    } else {
41        // SAFETY: `mrb` is valid and non-null by caller contract and non-null check.
42        let alloc_ud = mem::replace(unsafe { &mut mrb.as_mut().allocf_ud }, ptr::null_mut());
43        let Some(state) = NonNull::new(alloc_ud) else {
44            emit_fatal_warning!("ffi: Attempted to extract Artichoke from null `mrb_state->ud` pointer");
45            return Err(InterpreterExtractError::new());
46        };
47        state.cast::<State>()
48    };
49
50    // SAFETY: An `Artichoke`-initialized `mrb_state` has a `State` in the user
51    // data pointer.
52    let state = unsafe { Box::from_raw(state.as_ptr()) };
53    Ok(Artichoke::new(mrb, state))
54}
55
56/// Failed to extract Artichoke interpreter at an FFI boundary.
57#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
58pub struct InterpreterExtractError {
59    _private: (),
60}
61
62impl InterpreterExtractError {
63    /// Constructs a new, default `InterpreterExtractError`.
64    #[must_use]
65    pub const fn new() -> Self {
66        Self { _private: () }
67    }
68}
69
70impl fmt::Display for InterpreterExtractError {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.write_str("Failed to extract Artichoke Ruby interpreter from mrb_state userdata")
73    }
74}
75
76impl error::Error for InterpreterExtractError {}
77
78impl RubyException for InterpreterExtractError {
79    fn message(&self) -> Cow<'_, [u8]> {
80        Cow::Borrowed(b"Failed to extract Artichoke Ruby interpreter from mrb_state")
81    }
82
83    fn name(&self) -> Cow<'_, str> {
84        "fatal".into()
85    }
86
87    fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
88        let _ = interp;
89        None
90    }
91
92    fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
93        let message = interp.try_convert_mut(self.message()).ok()?;
94        let value = interp.new_instance::<Fatal>(&[message]).ok().flatten()?;
95        Some(value.inner())
96    }
97}
98
99impl From<InterpreterExtractError> for Error {
100    fn from(exception: InterpreterExtractError) -> Self {
101        let err: Box<dyn RubyException> = Box::new(exception);
102        Self::from(err)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use std::ptr::{self, NonNull};
109
110    use crate::ffi;
111    use crate::test::prelude::*;
112
113    #[test]
114    fn from_user_data_null_pointer() {
115        let err = unsafe { ffi::from_user_data(ptr::null_mut()) };
116        assert_eq!(err.err(), Some(InterpreterExtractError::new()));
117    }
118
119    #[test]
120    fn from_user_data_null_user_data() {
121        let mut interp = crate::interpreter().unwrap();
122        let mrb = interp.mrb.as_ptr();
123        let err = unsafe {
124            // fake null user data
125            (*mrb).ud = ptr::null_mut();
126            ffi::from_user_data(mrb)
127        };
128        assert_eq!(err.err(), Some(InterpreterExtractError::new()));
129        interp.mrb = NonNull::new(mrb).unwrap();
130        interp.close();
131    }
132
133    #[test]
134    fn from_user_data() {
135        let interp = crate::interpreter().unwrap();
136        let res = unsafe {
137            let mrb = Artichoke::into_raw(interp);
138            ffi::from_user_data(mrb)
139        };
140        assert!(res.is_ok());
141        res.unwrap().close();
142    }
143}