mri_sys/
helpers.rs

1//! A thin, slightly higher level interface to it all.
2//!
3//! Turn if off with `default-features = false` for this crate in your manifest.
4
5// TODO:
6//   - integer to ruby
7//   - ruby to integer?
8//   - line numbers passed to eval func
9
10pub use self::protect::{catch_unwind, CaughtException};
11pub use self::value::Value;
12
13/// A binding is basically an execution context.
14/// Variables and classes defined inside a binding are only
15/// accessible within that binding.
16#[derive(Copy, Clone, Debug)]
17pub struct Binding(pub Value);
18
19impl Binding {
20    /// The top level binding (the global context) accessible in Ruby via `TOPLEVEL_BINDING`
21    /// constant. This binding is a superset of all other bindings. Executions on it act
22    /// as if they were executed in all possible bindings.
23    pub fn top_level() -> Self {
24        Binding(unsafe { std::classes::Object().constant_unprotected("TOPLEVEL_BINDING") })
25    }
26
27    /// Allocates a brand new new binding. Equivalent to `Kernel#binding`.
28    pub fn allocate() -> Self {
29        unsafe { Binding(std::modules::Kernel().send_unprotected("binding", &[])) }
30    }
31}
32
33/// Evaluate Ruby code in a specific binding.
34///
35/// Rescues all Ruby exceptions.
36// TODO: implement line numbers
37pub fn eval(
38    ruby_code: &str,
39    binding: Binding,
40    filename_for_debugging: Option<&str>,
41) -> Result<Value, CaughtException> {
42    crate::helpers::catch_unwind(|| unsafe {
43        eval_unprotected(ruby_code, binding, filename_for_debugging)
44    })
45}
46
47/// Evaluates Ruby code in the given binding whilst allowing Ruby unwinding
48/// to unsafely propagate to Rust (causes segfaults when it hits Rust layer).
49pub unsafe fn eval_unprotected(
50    ruby_code: &str,
51    binding: Binding,
52    filename_for_debugging: Option<&str>,
53) -> Value {
54    let code_string = crate::helpers::to_ruby::string(ruby_code);
55    let filename = filename_for_debugging.map(crate::helpers::to_ruby::string);
56    // let line_number = crate::Qnil; // the argument is optional..but how to construct ruby int from rust?
57
58    let mut argv = vec![
59        *code_string,
60    ];
61
62    if let Some(filename) = filename { argv.push(*filename) };
63    // if let Some(line_number) = line_number { argv.push(*line_number) };
64
65    binding.0.send_unprotected("eval", &argv[..])
66}
67
68/// Converting Rust values to Ruby values.
69pub mod to_ruby {
70    use super::Value;
71
72    /// Dereferences to a Ruby value. Makes sure the underlying data is not dropped.
73    pub struct WrappedWithData<T, D> {
74        value: T,
75        _data: D,
76    }
77
78    /// Convert a Rust `&str` to a Ruby `String`
79    pub fn string(string: &str)
80        -> WrappedWithData<Value, std::ffi::CString>  {
81        let cstring = std::ffi::CString::new(string).unwrap();
82        let string_as_value = unsafe {
83            crate::rb_str_new_cstr(cstring.as_ptr())
84        };
85
86        WrappedWithData {
87            _data: cstring,
88            value: Value(string_as_value),
89        }
90    }
91
92    /// Convert a Rust `&str` to a Ruby `ID` / symbol
93    pub fn symbol(string: &str)
94        -> WrappedWithData<crate::ID, std::ffi::CString>  {
95        let cstring = std::ffi::CString::new(string).unwrap();
96        let string_as_value = unsafe {
97            crate::rb_intern(cstring.as_ptr())
98        };
99
100        WrappedWithData {
101            _data: cstring,
102            value: string_as_value,
103        }
104    }
105
106    impl<T, D> AsRef<T> for WrappedWithData<T, D> {
107        fn as_ref(&self) -> &T { &self.value }
108    }
109
110    impl<T, D> std::ops::Deref for WrappedWithData<T, D> {
111        type Target = T;
112
113        fn deref(&self) -> &T { &self.value }
114    }
115}
116
117/// Wraps `rb_protect` Ruby functionality for unwind handing.
118mod protect {
119    use super::{to_ruby, Value};
120    use crate::VALUE;
121
122    /// Wraps a Ruby exception `Value` and exposes its values usable in Rust.
123    #[derive(Debug)]
124    pub struct CaughtException {
125        pub exception_object: Value,
126        pub exception_class_name: String,
127        pub message: String,
128    }
129
130    /// Compare the class and message of an exception against another.
131    impl PartialEq for CaughtException {
132        fn eq(&self, rhs: &Self) -> bool {
133            let CaughtException {
134                ref exception_class_name, ref message,
135                exception_object: _,
136            } = *self;
137
138            *exception_class_name == rhs.exception_class_name &&
139                *message == rhs.message
140        }
141    }
142
143    impl Eq for CaughtException { }
144
145    /// Wrapper over `rb_protect`, catches any Ruby exception within the given function.
146    ///
147    /// If you're calling into Ruby through this helper module only then you don't need this
148    /// because all safe APIs run this for you. However if you're invoking raw Ruby APIs that
149    /// can unwind directly, you probably do need this.
150    pub fn catch_unwind<F>(
151        mut f: F,
152    ) -> Result<Value, CaughtException>
153        where F: FnOnce() -> Value {
154        let mut state: libc::c_int = 1;
155
156        // WARNING: Don't read this after `rb_protect` is called. The `catch_unwind_internal`
157        // function zeros this pointer within this callframe in place upon execution.
158        // FnOnce's a bitch.
159        let mut fn_ptr_buf: *mut F = &mut f;
160
161        let fn_ptr_buf_ref: &mut *mut F = &mut fn_ptr_buf;
162        let fn_ptr_buf_ptr: *mut *mut F = fn_ptr_buf_ref as _;
163        let fn_ptr_buf_ptr_uint = fn_ptr_buf_ptr as usize; // sorry
164
165        let fn_ptr_buf_ptr_as_ruby_string = to_ruby::string(&fn_ptr_buf_ptr_uint.to_string());
166
167        let catch_unwind_internal_args = (*fn_ptr_buf_ptr_as_ruby_string).0;
168
169        let result = unsafe {
170            crate::rb_protect(catch_unwind_internal::<F>, catch_unwind_internal_args, &mut state)
171        };
172
173        if state == 0 {
174            Ok(Value(result))
175        } else {
176            let exception_object: Value = Value(unsafe { crate::rb_errinfo() });
177            unsafe { crate::rb_set_errinfo(crate::Qnil) }; // clear the exception as per guidelines
178
179            let message = unsafe { exception_object.send_unprotected("message", &[]).to_s_unprotected() };
180            let class_name = exception_object.object_class_name();
181
182            Err(CaughtException {
183                exception_object,
184                exception_class_name: class_name,
185                message,
186            })
187        }
188    }
189
190    extern "C" fn catch_unwind_internal<F>(
191        fn_ptr_as_ruby_string: VALUE,
192    ) -> VALUE
193        where F: FnOnce() -> Value {
194        let fn_ptr_buf_ptr_as_rust_string: String = unsafe {
195            Value(fn_ptr_as_ruby_string).to_s_unprotected()
196        };
197
198        let fn_ptr_buf_ptr_uint: usize = match fn_ptr_buf_ptr_as_rust_string.parse() {
199            Ok(uint) => uint,
200            Err(..) => {
201                eprintln!("this should never happen, choking on our own string");
202                std::process::abort();
203            },
204        };
205        let fn_ptr_buf_ptr: *mut *mut F = fn_ptr_buf_ptr_uint as _;
206        let fn_ptr_buf_ref: &mut *mut F = unsafe { std::mem::transmute(fn_ptr_buf_ptr) };
207
208        let fn_ptr: *mut F = std::mem::replace(fn_ptr_buf_ref, std::ptr::null_mut());
209        let fn_ref: &mut F = unsafe { std::mem::transmute(fn_ptr) };
210        let f = std::mem::replace(fn_ref, unsafe { std::mem::MaybeUninit::zeroed().assume_init() });
211
212        (f)().0
213    }
214
215    /// Formats the exception like `<ClassName>: <message>`
216    impl std::fmt::Display for CaughtException {
217        fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
218            write!(fmt, "{}: {}", self.exception_class_name, self.message)
219        }
220    }
221
222    impl std::error::Error for CaughtException { }
223}
224
225mod value {
226    use super::{to_ruby, CaughtException};
227    use crate::VALUE;
228
229    /// Wraps a plain old Ruby FFI `VALUE` with much more functionality.
230    #[derive(Copy, Clone, PartialEq, Eq)]
231    #[repr(transparent)]
232    pub struct Value(pub VALUE);
233
234    impl Value {
235        /// The Ruby `nil` value.
236        pub const NIL: Self = Value(crate::Qnil);
237        /// The Ruby `true` value.
238        pub const TRUE: Self = Value(crate::Qtrue);
239        /// The Ruby `false` value.
240        pub const FALSE: Self = Value(crate::Qfalse);
241
242        /// Sends a Ruby method and returns the result.
243        pub fn send(
244            &self,
245            method_name: &str,
246            arguments: &[Value],
247        ) -> Result<Value, CaughtException> {
248            crate::helpers::catch_unwind(|| unsafe {
249                self.send_unprotected(method_name, arguments)
250            })
251        }
252
253        /// Sends a Ruby method without rescuing Ruby unwind.
254        pub unsafe fn send_unprotected(
255            &self,
256            method_name: &str,
257            arguments: &[Value],
258        ) -> Value {
259            let function_symbol = to_ruby::symbol(method_name);
260            let arguments = Value::convert_array(arguments);
261            Value(crate::rb_funcallv(self.0, *function_symbol, arguments.len() as _, arguments.as_ptr()))
262        }
263
264        /// Gets a constant by name. Equivalent to `Object#const_get(constant_name)`.
265        pub fn constant(
266            &self,
267            constant_name: &str,
268        ) -> Result<Value, CaughtException> {
269            crate::helpers::catch_unwind(|| unsafe { self.constant_unprotected(constant_name) })
270        }
271
272        /// Gets a constant by name. Equivalent to `Object#const_get(constant_name)`.
273        pub unsafe fn constant_unprotected(
274            &self,
275            constant_name: &str,
276        ) -> Value {
277            let constant_symbol = to_ruby::symbol(constant_name);
278            Value(crate::rb_const_get(self.0, *constant_symbol) )
279        }
280
281        /// Sets a constant. Equivalent to `Object#const_set(constant_name, value)`.
282        pub fn set_constant(
283            &self,
284            constant_name: &str,
285            value: Value,
286        ) -> Result<(), CaughtException> {
287            crate::helpers::catch_unwind(|| unsafe {
288                self.set_constant_unprotected(constant_name, value);
289
290                Value::NIL
291            }).map(|_| ())
292        }
293
294        /// Sets a constant. Equivalent to `Object#const_set(constant_name, value)`.
295        pub unsafe fn set_constant_unprotected(
296            &self,
297            constant_name: &str,
298            value: Value,
299        ) {
300            let constant_symbol = to_ruby::symbol(constant_name);
301            crate::rb_const_set(self.0, *constant_symbol, value.0)
302        }
303
304        /// Convert a Ruby value to a Rust string.
305        ///
306        /// Calls `Object#to_s` and then converts the result to a string.
307        pub fn to_s(&self) -> Result<String, CaughtException> {
308            super::catch_unwind(|| unsafe {
309                self.send_unprotected("to_s", &[])
310            }).map(|v| unsafe { v.assert_is_string_and_convert_to_string_unprotected() })
311        }
312
313        /// Convert a Ruby value to a Rust string.
314        pub unsafe fn to_s_unprotected(&self) -> String {
315            self.send_unprotected("to_s", &[])
316                .assert_is_string_and_convert_to_string_unprotected()
317        }
318
319        unsafe fn assert_is_string_and_convert_to_string_unprotected(&self) -> String {
320            let cstring_ptr = crate::rb_string_value_cstr(&self.0);
321            let cstr = std::ffi::CStr::from_ptr(cstring_ptr);
322
323            cstr.to_str().expect("invalid UTF-8").to_owned()
324        }
325
326        /// Convert a Ruby string to a Rust string.
327        /// Gets the class name for an object
328        pub fn object_class_name(&self) -> String {
329            unsafe {
330                let cstr_ptr = crate::rb_obj_classname(self.0);
331                std::ffi::CStr::from_ptr(cstr_ptr).to_str().unwrap().to_owned()
332            }
333        }
334
335        /// Calls `Object#inspect`
336        pub fn inspect(&self) -> Result<Value, CaughtException> {
337            super::catch_unwind(|| unsafe { self.inspect_unprotected() })
338        }
339
340        /// Calls `Object#inspect`
341        pub unsafe fn inspect_unprotected(&self) -> Value {
342            self.send_unprotected("inspect", &[])
343        }
344
345        /// Checks if this object is of the given value type.
346        pub fn is_of_value_type(&self, value_type: crate::value_type) -> bool {
347            crate::TYPE_P(self.0, value_type)
348        }
349
350        /// Checks if this value is `nil`.
351        pub fn is_nil(&self) -> bool { self.0 == crate::Qnil }
352
353
354        pub fn convert_array(values: &[Value]) -> &[VALUE] {
355            unsafe { std::mem::transmute(values) } // safe because of #[repr(transparent)]
356        }
357    }
358
359    impl std::fmt::Display for Value {
360        fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
361            self.to_s().unwrap_or_else(|e| format!("ERROR: unexpected ruby exception: {}", e)).fmt(fmt)
362        }
363    }
364
365    impl std::fmt::Debug for Value {
366        fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
367            super::catch_unwind(|| unsafe {
368                let inspect_string = self.inspect_unprotected().to_s_unprotected();
369                write!(fmt, "{}", inspect_string).ok();
370
371                Value::NIL
372            }).expect("Ruby method #inspect failed");
373
374            Ok(())
375        }
376    }
377
378    impl std::str::FromStr for Value {
379        type Err = CaughtException;
380
381        fn from_str(s: &str) -> Result<Self, CaughtException> {
382            Ok(*to_ruby::string(s))
383        }
384    }
385
386    impl From<bool> for Value {
387        fn from(b: bool) -> Self {
388            if b { Value::TRUE } else { Value::FALSE }
389        }
390    }
391
392    impl From<VALUE> for Value {
393        fn from(v: VALUE) -> Self { Value(v) }
394    }
395
396    impl Into<VALUE> for Value {
397        fn into(self) -> VALUE { self.0 }
398    }
399
400    impl AsRef<VALUE> for Value {
401        fn as_ref(&self) -> &VALUE { &self.0 }
402    }
403
404    impl std::ops::Deref for Value {
405        type Target = VALUE;
406
407        fn deref(&self) -> &VALUE { &self.0 }
408    }
409
410    impl std::ops::DerefMut for Value {
411        fn deref_mut(&mut self) -> &mut VALUE { &mut self.0 }
412    }
413}
414
415/// Get the builtin global/static class/module `Value` instances like `Kernel`, `Object`,
416/// `Integer`, etc.
417pub mod std {
418    #![allow(non_snake_case)]
419
420    pub mod modules {
421        use super::super::Value;
422
423        pub fn Kernel() -> Value { Value(unsafe { crate::rb_mKernel }) }
424        pub fn Math() -> Value { Value(unsafe { crate::rb_mMath }) }
425    }
426
427    pub mod classes {
428        use super::super::Value;
429
430        pub fn Object() -> Value { Value(unsafe { crate::rb_cObject}) }
431        pub fn Array() -> Value { Value(unsafe { crate::rb_cArray}) }
432        pub fn Binding() -> Value { Value(unsafe { crate::rb_cBinding}) }
433        pub fn Class() -> Value { Value(unsafe { crate::rb_cClass}) }
434        pub fn Module() -> Value { Value(unsafe { crate::rb_cModule}) }
435        pub fn NilClass() -> Value { Value(unsafe { crate::rb_cNilClass}) }
436        pub fn Integer() -> Value { Value(unsafe { crate::rb_cInteger}) }
437        pub fn Hash() -> Value { Value(unsafe { crate::rb_cHash}) }
438        pub fn Float() -> Value { Value(unsafe { crate::rb_cFloat}) }
439    }
440}