artichoke_backend/
def.rs

1use std::borrow::Cow;
2use std::error;
3use std::ffi::{CStr, c_void};
4use std::fmt;
5use std::io::{self, Write as _};
6use std::ptr::NonNull;
7
8use spinoso_exception::{NameError, ScriptError};
9
10use crate::Artichoke;
11use crate::class;
12use crate::convert::BoxUnboxVmValue;
13use crate::core::{ClassRegistry, TryConvertMut};
14use crate::error::{Error, RubyException};
15use crate::module;
16use crate::sys;
17
18/// Typedef for an mruby free function for an [`mrb_value`](sys::mrb_value) with
19/// `tt` [`MRB_TT_CDATA`].
20///
21/// [`MRB_TT_CDATA`]: sys::mrb_vtype::MRB_TT_CDATA
22pub type Free = unsafe extern "C" fn(mrb: *mut sys::mrb_state, data: *mut c_void);
23
24/// A generic implementation of a [`Free`] function for [`mrb_value`]s that
25/// store an owned copy of a [`Box`] smart pointer.
26///
27/// This function ultimately calls [`Box::from_raw`] on the data pointer and
28/// drops the resulting [`Box`].
29///
30/// # Safety
31///
32/// The given `data` pointer must be non-null and allocated by [`Box`].
33///
34/// This function assumes that the data pointer is to an [`Box`]`<T>` created by
35/// [`Box::into_raw`]. This function bounds `T` by [`BoxUnboxVmValue`] which
36/// boxes `T` for the mruby VM like this.
37///
38/// This function assumes it is called by the mruby VM as a free function for an
39/// [`MRB_TT_CDATA`].
40///
41/// [`mrb_value`]: sys::mrb_value
42/// [`MRB_TT_CDATA`]: sys::mrb_vtype::MRB_TT_CDATA
43pub unsafe extern "C" fn box_unbox_free<T>(_mrb: *mut sys::mrb_state, data: *mut c_void)
44where
45    T: 'static + BoxUnboxVmValue,
46{
47    // Ideally we'd be able to have the `data` argument in the function signature
48    // declared as `Option<NonNull<c_void>>` which is FFI safe, but this function
49    // is eventually passed into a bindgen-generated mruby struct, which expects
50    // the `*mut c_void` argument.
51    let Some(data) = NonNull::new(data) else {
52        // If we enter this branch, we have almost certainly encountered a bug.
53        // Rather than attempt a free and virtually guaranteed segfault, log
54        // loudly and short-circuit; a leak is better than a crash.
55        //
56        // `box_unbox_free::<T>` is only ever called in an FFI context when
57        // there are C frames in the stack. Using `eprintln!` or unwrapping the
58        // error from `write!` here is undefined behavior and may result in an
59        // abort. Instead, suppress the error.
60        let _ignored = write!(
61            io::stderr(),
62            "Received null pointer in box_unbox_free::<{}>",
63            T::RUBY_TYPE,
64        );
65        return;
66    };
67    // Only attempt to free if we are given a non-null pointer.
68    // SAFETY: this function may never unwind due to trait implementer contract.
69    T::free(data.as_ptr());
70}
71
72#[cfg(test)]
73mod free_test {
74    use crate::convert::HeapAllocatedData;
75
76    fn prototype(_func: super::Free) {}
77
78    #[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
79    struct Data(String);
80
81    impl HeapAllocatedData for Data {
82        const RUBY_TYPE: &'static str = "Data";
83    }
84
85    #[test]
86    fn free_prototype() {
87        prototype(super::box_unbox_free::<Data>);
88    }
89}
90
91/// Typedef for a method exposed in the mruby interpreter.
92///
93/// This function signature is used for all types of mruby methods, including
94/// instance methods, class methods, singleton methods, and global methods.
95///
96/// `slf` is the method receiver, e.g. `s` in the following invocation of
97/// `String#start_with?`.
98///
99/// ```ruby
100/// s = 'artichoke crate'
101/// s.start_with?('artichoke')
102/// ```
103///
104/// To extract method arguments, use [`mrb_get_args!`] and the supplied
105/// interpreter.
106pub type Method = unsafe extern "C-unwind" fn(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value;
107
108#[derive(Debug, Clone, Hash, PartialEq, Eq)]
109pub struct ClassScope {
110    name: Box<str>,
111    name_cstr: &'static CStr,
112    enclosing_scope: Option<Box<EnclosingRubyScope>>,
113}
114
115#[derive(Debug, Clone, Hash, PartialEq, Eq)]
116pub struct ModuleScope {
117    name: Box<str>,
118    name_cstr: &'static CStr,
119    name_symbol: u32,
120    enclosing_scope: Option<Box<EnclosingRubyScope>>,
121}
122
123/// Typesafe wrapper for the [`RClass *`] of the enclosing scope for an mruby
124/// `Module` or `Class`.
125///
126/// In Ruby, classes and modules can be defined inside another class or module.
127/// mruby only supports resolving [`RClass`] pointers relative to an enclosing
128/// scope. This can be the top level with [`mrb_class_get`] and
129/// [`mrb_module_get`] or it can be under another class with
130/// [`mrb_class_get_under`] or module with [`mrb_module_get_under`].
131///
132/// Because there is no C API to resolve class and module names directly, each
133/// class-like holds a reference to its enclosing scope so it can recursively
134/// resolve its enclosing [`RClass *`].
135///
136/// [`RClass *`]: sys::RClass
137/// [`RClass`]: sys::RClass
138/// [`mrb_class_get`]: sys::mrb_class_get
139/// [`mrb_module_get`]: sys::mrb_module_get
140/// [`mrb_class_get_under`]: sys::mrb_class_get_under
141/// [`mrb_module_get_under`]: sys::mrb_module_get_under
142#[derive(Debug, Clone, Hash, PartialEq, Eq)]
143pub enum EnclosingRubyScope {
144    /// Reference to a Ruby `Class` enclosing scope.
145    Class(ClassScope),
146    /// Reference to a Ruby `Module` enclosing scope.
147    Module(ModuleScope),
148}
149
150impl EnclosingRubyScope {
151    /// Factory for [`EnclosingRubyScope::Class`] that clones a [`class::Spec`].
152    ///
153    /// This function is useful when extracting an enclosing scope from the
154    /// class registry.
155    #[must_use]
156    pub fn class(spec: &class::Spec) -> Self {
157        let name_cstr = spec.name_c_str();
158        Self::Class(ClassScope {
159            name: String::from(spec.name()).into_boxed_str(),
160            name_cstr,
161            enclosing_scope: spec.enclosing_scope().cloned().map(Box::new),
162        })
163    }
164
165    /// Factory for [`EnclosingRubyScope::Module`] that clones a
166    /// [`module::Spec`].
167    ///
168    /// This function is useful when extracting an enclosing scope from the
169    /// module registry.
170    #[must_use]
171    pub fn module(spec: &module::Spec) -> Self {
172        let name_cstr = spec.name_c_str();
173        Self::Module(ModuleScope {
174            name: String::from(spec.name()).into_boxed_str(),
175            name_cstr,
176            name_symbol: spec.name_symbol(),
177            enclosing_scope: spec.enclosing_scope().cloned().map(Box::new),
178        })
179    }
180
181    /// Resolve the [`RClass *`] of the wrapped class or module.
182    ///
183    /// Return [`None`] if the class-like has no [`EnclosingRubyScope`].
184    ///
185    /// The current implementation results in recursive calls to this function
186    /// for each enclosing scope.
187    ///
188    /// # Safety
189    ///
190    /// This function must be called within an [`Artichoke::with_ffi_boundary`]
191    /// closure because the FFI APIs called in this function may require access
192    /// to the Artichoke [`State`].
193    ///
194    /// [`RClass *`]: sys::RClass
195    /// [`State`]: crate::state::State
196    pub unsafe fn rclass(&self, mrb: *mut sys::mrb_state) -> Option<NonNull<sys::RClass>> {
197        match self {
198            Self::Class(scope) => {
199                let enclosing_scope = scope.enclosing_scope.clone().map(|scope| *scope);
200                let rclass = class::Rclass::new(scope.name_cstr, enclosing_scope);
201                // SAFETY: callers must uphold that `mrb` is a valid interpreter.
202                unsafe { rclass.resolve(mrb) }
203            }
204            Self::Module(scope) => {
205                let enclosing_scope = scope.enclosing_scope.clone().map(|scope| *scope);
206                let rclass = module::Rclass::new(scope.name_symbol, scope.name_cstr, enclosing_scope);
207                // SAFETY: callers must uphold that `mrb` is a valid interpreter.
208                unsafe { rclass.resolve(mrb) }
209            }
210        }
211    }
212
213    /// Get the fully-qualified name of the wrapped class or module.
214    ///
215    /// For example, in the following Ruby code, `C` has a fully-qualified name
216    /// of `A::B::C`.
217    ///
218    /// ```ruby
219    /// module A
220    ///   class B
221    ///     module C
222    ///       CONST = 1
223    ///     end
224    ///   end
225    /// end
226    /// ```
227    ///
228    /// The current implementation results in recursive calls to this function
229    /// for each enclosing scope.
230    #[must_use]
231    pub fn fqname(&self) -> Cow<'_, str> {
232        let (name, enclosing_scope) = match self {
233            Self::Class(scope) => (&*scope.name, &scope.enclosing_scope),
234            Self::Module(scope) => (&*scope.name, &scope.enclosing_scope),
235        };
236        let Some(scope) = enclosing_scope else {
237            return name.into();
238        };
239        let mut fqname = String::from(scope.fqname());
240        fqname.push_str("::");
241        fqname.push_str(name);
242        fqname.into()
243    }
244}
245
246#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
247pub struct ConstantNameError(Cow<'static, str>);
248
249impl From<&'static str> for ConstantNameError {
250    fn from(name: &'static str) -> Self {
251        Self(name.into())
252    }
253}
254
255impl From<String> for ConstantNameError {
256    fn from(name: String) -> Self {
257        Self(name.into())
258    }
259}
260
261impl From<Cow<'static, str>> for ConstantNameError {
262    fn from(name: Cow<'static, str>) -> Self {
263        Self(name)
264    }
265}
266
267impl ConstantNameError {
268    #[must_use]
269    pub const fn new() -> Self {
270        Self(Cow::Borrowed(""))
271    }
272}
273
274impl fmt::Display for ConstantNameError {
275    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276        f.write_str("Invalid constant name contained a NUL byte")
277    }
278}
279
280impl error::Error for ConstantNameError {}
281
282impl RubyException for ConstantNameError {
283    fn message(&self) -> Cow<'_, [u8]> {
284        Cow::Borrowed(b"Invalid constant name contained a NUL byte")
285    }
286
287    fn name(&self) -> Cow<'_, str> {
288        "NameError".into()
289    }
290
291    fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
292        let _ = interp;
293        None
294    }
295
296    fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
297        let message = interp.try_convert_mut(self.message()).ok()?;
298        let value = interp.new_instance::<NameError>(&[message]).ok().flatten()?;
299        Some(value.inner())
300    }
301}
302
303impl From<ConstantNameError> for Error {
304    fn from(exception: ConstantNameError) -> Self {
305        let err: Box<dyn RubyException> = Box::new(exception);
306        Self::from(err)
307    }
308}
309
310#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
311pub enum NotDefinedError {
312    EnclosingScope(Cow<'static, str>),
313    Super(Cow<'static, str>),
314    Class(Cow<'static, str>),
315    Method(Cow<'static, str>),
316    Module(Cow<'static, str>),
317    GlobalConstant(Cow<'static, str>),
318    ClassConstant(Cow<'static, str>),
319    ModuleConstant(Cow<'static, str>),
320}
321
322impl NotDefinedError {
323    pub fn enclosing_scope<T>(item: T) -> Self
324    where
325        T: Into<Cow<'static, str>>,
326    {
327        Self::EnclosingScope(item.into())
328    }
329
330    pub fn super_class<T>(item: T) -> Self
331    where
332        T: Into<Cow<'static, str>>,
333    {
334        Self::Super(item.into())
335    }
336
337    pub fn class<T>(item: T) -> Self
338    where
339        T: Into<Cow<'static, str>>,
340    {
341        Self::Class(item.into())
342    }
343
344    pub fn method<T>(item: T) -> Self
345    where
346        T: Into<Cow<'static, str>>,
347    {
348        Self::Method(item.into())
349    }
350
351    pub fn module<T>(item: T) -> Self
352    where
353        T: Into<Cow<'static, str>>,
354    {
355        Self::Module(item.into())
356    }
357
358    pub fn global_constant<T>(item: T) -> Self
359    where
360        T: Into<Cow<'static, str>>,
361    {
362        Self::GlobalConstant(item.into())
363    }
364
365    pub fn class_constant<T>(item: T) -> Self
366    where
367        T: Into<Cow<'static, str>>,
368    {
369        Self::ClassConstant(item.into())
370    }
371
372    pub fn module_constant<T>(item: T) -> Self
373    where
374        T: Into<Cow<'static, str>>,
375    {
376        Self::ModuleConstant(item.into())
377    }
378
379    #[must_use]
380    pub fn fqdn(&self) -> &str {
381        match self {
382            Self::EnclosingScope(fqdn) | Self::Super(fqdn) | Self::Class(fqdn) | Self::Module(fqdn) => fqdn.as_ref(),
383            Self::GlobalConstant(name)
384            | Self::ClassConstant(name)
385            | Self::Method(name)
386            | Self::ModuleConstant(name) => name.as_ref(),
387        }
388    }
389
390    #[must_use]
391    pub const fn item_type(&self) -> &str {
392        match self {
393            Self::EnclosingScope(_) => "enclosing scope",
394            Self::Super(_) => "super class",
395            Self::Class(_) => "class",
396            Self::Method(_) => "method",
397            Self::Module(_) => "module",
398            Self::GlobalConstant(_) => "global constant",
399            Self::ClassConstant(_) => "class constant",
400            Self::ModuleConstant(_) => "module constant",
401        }
402    }
403}
404
405impl fmt::Display for NotDefinedError {
406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407        f.write_str(self.item_type())?;
408        f.write_str(" '")?;
409        f.write_str(self.fqdn())?;
410        f.write_str("' not defined")?;
411        Ok(())
412    }
413}
414
415impl error::Error for NotDefinedError {}
416
417impl RubyException for NotDefinedError {
418    fn message(&self) -> Cow<'_, [u8]> {
419        let mut message = String::from(self.item_type());
420        message.push(' ');
421        message.push_str(self.fqdn());
422        message.push_str(" not defined");
423        message.into_bytes().into()
424    }
425
426    fn name(&self) -> Cow<'_, str> {
427        "ScriptError".into()
428    }
429
430    fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
431        let _ = interp;
432        None
433    }
434
435    fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
436        let message = interp.try_convert_mut(self.message()).ok()?;
437        let value = interp.new_instance::<ScriptError>(&[message]).ok().flatten()?;
438        Some(value.inner())
439    }
440}
441
442impl From<NotDefinedError> for Error {
443    fn from(exception: NotDefinedError) -> Self {
444        let err: Box<dyn RubyException> = Box::new(exception);
445        Self::from(err)
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    mod fqname {
452        use crate::test::prelude::*;
453
454        /// `A`
455        #[derive(Debug)]
456        struct Root;
457
458        /// `A::B`
459        #[derive(Debug)]
460        struct ModuleUnderRoot;
461
462        /// `A::C`
463        #[derive(Debug)]
464        struct ClassUnderRoot;
465
466        /// `A::B::D`
467        #[derive(Debug)]
468        struct ClassUnderModule;
469
470        /// `A::C::E`
471        #[derive(Debug)]
472        struct ModuleUnderClass;
473
474        /// `A::C::F`
475        #[derive(Debug)]
476        struct ClassUnderClass;
477
478        #[test]
479        fn integration_test() {
480            // Setup: define module and class hierarchy
481            let mut interp = interpreter();
482            let root = module::Spec::new(&mut interp, "A", c"A", None).unwrap();
483            let mod_under_root =
484                module::Spec::new(&mut interp, "B", c"B", Some(EnclosingRubyScope::module(&root))).unwrap();
485            let cls_under_root = class::Spec::new("C", c"C", Some(EnclosingRubyScope::module(&root)), None).unwrap();
486            let cls_under_mod =
487                class::Spec::new("D", c"D", Some(EnclosingRubyScope::module(&mod_under_root)), None).unwrap();
488            let mod_under_cls =
489                module::Spec::new(&mut interp, "E", c"E", Some(EnclosingRubyScope::class(&cls_under_root))).unwrap();
490            let cls_under_cls =
491                class::Spec::new("F", c"F", Some(EnclosingRubyScope::class(&cls_under_root)), None).unwrap();
492            module::Builder::for_spec(&mut interp, &root).define().unwrap();
493            module::Builder::for_spec(&mut interp, &mod_under_root)
494                .define()
495                .unwrap();
496            class::Builder::for_spec(&mut interp, &cls_under_root).define().unwrap();
497            class::Builder::for_spec(&mut interp, &cls_under_mod).define().unwrap();
498            module::Builder::for_spec(&mut interp, &mod_under_cls).define().unwrap();
499            class::Builder::for_spec(&mut interp, &cls_under_cls).define().unwrap();
500            interp.def_module::<Root>(root).unwrap();
501            interp.def_module::<ModuleUnderRoot>(mod_under_root).unwrap();
502            interp.def_class::<ClassUnderRoot>(cls_under_root).unwrap();
503            interp.def_class::<ClassUnderModule>(cls_under_mod).unwrap();
504            interp.def_module::<ModuleUnderClass>(mod_under_cls).unwrap();
505            interp.def_class::<ClassUnderClass>(cls_under_cls).unwrap();
506
507            let root = interp.module_spec::<Root>().unwrap().unwrap();
508            assert_eq!(root.fqname().as_ref(), "A");
509            let mod_under_root = interp.module_spec::<ModuleUnderRoot>().unwrap().unwrap();
510            assert_eq!(mod_under_root.fqname().as_ref(), "A::B");
511            let cls_under_root = interp.class_spec::<ClassUnderRoot>().unwrap().unwrap();
512            assert_eq!(cls_under_root.fqname().as_ref(), "A::C");
513            let cls_under_mod = interp.class_spec::<ClassUnderModule>().unwrap().unwrap();
514            assert_eq!(cls_under_mod.fqname().as_ref(), "A::B::D");
515            let mod_under_cls = interp.module_spec::<ModuleUnderClass>().unwrap().unwrap();
516            assert_eq!(mod_under_cls.fqname().as_ref(), "A::C::E");
517            let cls_under_cls = interp.class_spec::<ClassUnderClass>().unwrap().unwrap();
518            assert_eq!(cls_under_cls.fqname().as_ref(), "A::C::F");
519        }
520    }
521
522    mod functional {
523        use crate::test::prelude::*;
524
525        #[derive(Debug)]
526        struct Class;
527
528        #[derive(Debug)]
529        struct Module;
530
531        extern "C-unwind" fn value(_mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value {
532            unsafe {
533                match slf.tt {
534                    sys::mrb_vtype::MRB_TT_CLASS => sys::mrb_sys_fixnum_value(8),
535                    sys::mrb_vtype::MRB_TT_MODULE => sys::mrb_sys_fixnum_value(27),
536                    sys::mrb_vtype::MRB_TT_OBJECT => sys::mrb_sys_fixnum_value(64),
537                    _ => sys::mrb_sys_fixnum_value(125),
538                }
539            }
540        }
541
542        #[test]
543        fn define_method() {
544            let mut interp = interpreter();
545            let class = class::Spec::new("DefineMethodTestClass", c"DefineMethodTestClass", None, None).unwrap();
546            class::Builder::for_spec(&mut interp, &class)
547                .add_method("value", value, sys::mrb_args_none())
548                .unwrap()
549                .add_self_method("value", value, sys::mrb_args_none())
550                .unwrap()
551                .define()
552                .unwrap();
553            interp.def_class::<Class>(class).unwrap();
554            let module =
555                module::Spec::new(&mut interp, "DefineMethodTestModule", c"DefineMethodTestModule", None).unwrap();
556            module::Builder::for_spec(&mut interp, &module)
557                .add_method("value", value, sys::mrb_args_none())
558                .unwrap()
559                .add_self_method("value", value, sys::mrb_args_none())
560                .unwrap()
561                .define()
562                .unwrap();
563            interp.def_module::<Module>(module).unwrap();
564
565            interp
566                .eval(b"class DynamicTestClass; include DefineMethodTestModule; extend DefineMethodTestModule; end")
567                .unwrap();
568            interp
569                .eval(b"module DynamicTestModule; extend DefineMethodTestModule; end")
570                .unwrap();
571
572            let result = interp.eval(b"DefineMethodTestClass.new.value").unwrap();
573            let result = result.try_convert_into::<i64>(&interp).unwrap();
574            assert_eq!(result, 64);
575            let result = interp.eval(b"DefineMethodTestClass.value").unwrap();
576            let result = result.try_convert_into::<i64>(&interp).unwrap();
577            assert_eq!(result, 8);
578            let result = interp.eval(b"DefineMethodTestModule.value").unwrap();
579            let result = result.try_convert_into::<i64>(&interp).unwrap();
580            assert_eq!(result, 27);
581            let result = interp.eval(b"DynamicTestClass.new.value").unwrap();
582            let result = result.try_convert_into::<i64>(&interp).unwrap();
583            assert_eq!(result, 64);
584            let result = interp.eval(b"DynamicTestClass.value").unwrap();
585            let result = result.try_convert_into::<i64>(&interp).unwrap();
586            assert_eq!(result, 8);
587            let result = interp.eval(b"DynamicTestModule.value").unwrap();
588            let result = result.try_convert_into::<i64>(&interp).unwrap();
589            assert_eq!(result, 27);
590        }
591    }
592}