artichoke_backend/
block.rs

1use std::borrow::Cow;
2use std::error;
3use std::fmt;
4
5use spinoso_exception::{Fatal, TypeError};
6
7use crate::Artichoke;
8use crate::core::{ClassRegistry, TryConvertMut, Value as _};
9use crate::error::{Error, RubyException};
10use crate::exception_handler;
11use crate::sys::{self, protect};
12use crate::types::{self, Ruby};
13use crate::value::Value;
14
15#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
16pub struct NoBlockGiven(Ruby);
17
18impl fmt::Display for NoBlockGiven {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        f.write_str("no block given")
21    }
22}
23
24impl error::Error for NoBlockGiven {}
25
26impl RubyException for NoBlockGiven {
27    fn message(&self) -> Cow<'_, [u8]> {
28        Cow::Borrowed(b"no block given")
29    }
30
31    fn name(&self) -> Cow<'_, str> {
32        "TypeError".into()
33    }
34
35    fn vm_backtrace(&self, interp: &mut Artichoke) -> Option<Vec<Vec<u8>>> {
36        let _ = interp;
37        None
38    }
39
40    fn as_mrb_value(&self, interp: &mut Artichoke) -> Option<sys::mrb_value> {
41        let message = interp.try_convert_mut(self.message()).ok()?;
42        let value = interp.new_instance::<TypeError>(&[message]).ok().flatten()?;
43        Some(value.inner())
44    }
45}
46
47impl From<NoBlockGiven> for Error {
48    fn from(exception: NoBlockGiven) -> Self {
49        let err: Box<dyn RubyException> = Box::new(exception);
50        Self::from(err)
51    }
52}
53
54impl From<Value> for NoBlockGiven {
55    fn from(value: Value) -> Self {
56        Self(value.ruby_type())
57    }
58}
59
60impl From<sys::mrb_value> for NoBlockGiven {
61    fn from(value: sys::mrb_value) -> Self {
62        Self(types::ruby_from_mrb_value(value))
63    }
64}
65
66impl From<Ruby> for NoBlockGiven {
67    fn from(ruby_type: Ruby) -> Self {
68        Self(ruby_type)
69    }
70}
71
72impl Default for NoBlockGiven {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78impl NoBlockGiven {
79    /// Construct a new, empty no block given error.
80    ///
81    /// The inner Ruby type is `nil`.
82    #[must_use]
83    pub const fn new() -> Self {
84        Self(Ruby::Nil)
85    }
86
87    /// Return the [`Ruby`] type of the object given instead of a block.
88    #[must_use]
89    pub const fn ruby_type(self) -> Ruby {
90        self.0
91    }
92}
93
94#[derive(Default, Debug, Clone, Copy)]
95pub struct Block(sys::mrb_value);
96
97impl From<sys::mrb_value> for Option<Block> {
98    fn from(value: sys::mrb_value) -> Self {
99        if let Ruby::Nil = types::ruby_from_mrb_value(value) {
100            None
101        } else {
102            Some(Block(value))
103        }
104    }
105}
106
107impl TryFrom<sys::mrb_value> for Block {
108    type Error = NoBlockGiven;
109
110    fn try_from(value: sys::mrb_value) -> Result<Self, Self::Error> {
111        if let Some(block) = value.into() {
112            Ok(block)
113        } else {
114            Err(NoBlockGiven::from(value))
115        }
116    }
117}
118
119impl Block {
120    /// Construct a `Block` from a Ruby value.
121    #[must_use]
122    pub fn new(block: sys::mrb_value) -> Option<Self> {
123        if let Ruby::Nil = types::ruby_from_mrb_value(block) {
124            None
125        } else {
126            Some(Self(block))
127        }
128    }
129
130    /// Construct a `Block` from a Ruby value.
131    ///
132    /// # Safety
133    ///
134    /// The block must not be `nil`.
135    #[must_use]
136    pub const unsafe fn new_unchecked(block: sys::mrb_value) -> Self {
137        Self(block)
138    }
139
140    #[inline]
141    #[must_use]
142    pub const fn inner(&self) -> sys::mrb_value {
143        self.0
144    }
145
146    pub fn yield_arg(&self, interp: &mut Artichoke, arg: &Value) -> Result<Value, Error> {
147        if arg.is_dead(interp) {
148            let message = "Value yielded to block is dead. \
149                           This indicates a bug in the mruby garbage collector. \
150                           Please leave a comment at https://github.com/artichoke/artichoke/issues/1336.";
151            return Err(Fatal::with_message(message).into());
152        }
153        let result = unsafe { interp.with_ffi_boundary(|mrb| protect::block_yield(mrb, self.inner(), arg.inner()))? };
154        match result {
155            Ok(value) => {
156                let value = interp.protect(Value::from(value));
157                if value.is_unreachable() {
158                    // Unreachable values are internal to the mruby interpreter
159                    // and interacting with them via the C API is unspecified
160                    // and may result in a segfault.
161                    //
162                    // See: <https://github.com/mruby/mruby/issues/4460>
163                    Err(Fatal::from("Unreachable Ruby value").into())
164                } else {
165                    Ok(value)
166                }
167            }
168            Err(exception) => {
169                let exception = interp.protect(Value::from(exception));
170                Err(exception_handler::last_error(interp, exception)?)
171            }
172        }
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use crate::test::prelude::*;
179
180    #[test]
181    fn yield_arg_is_dead() {
182        // construct a dead value
183        let mut interp = interpreter();
184        let mut arena = interp.create_arena_savepoint().unwrap();
185
186        let dead = arena.eval(b"'dead'").unwrap();
187        arena.eval(b"'live'").unwrap();
188        arena.restore();
189        interp.full_gc().unwrap();
190
191        assert!(dead.is_dead(&mut interp));
192
193        // now ensure that it produces a fatal error when passed to `yield_arg`
194        let block = Block::default();
195
196        let error = block.yield_arg(&mut interp, &dead).unwrap_err();
197        assert_eq!(error.name().as_ref(), "fatal");
198    }
199}