1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! This crate provides high-level bindings to the [Ruby] virtual machine.
//!
//! # Installation
//!
//! This crate is available [on crates.io][crate] and can be used by adding the
//! following to your project's [`Cargo.toml`]:
//!
//! ```toml
//! [dependencies]
//! rosy = "0.0.2"
//! ```
//!
//! Rosy has functionality that is only available for certain Ruby versions. The
//! following features can currently be enabled:
//!
//! - `ruby_2_6`
//!
//! For example:
//!
//! ```toml
//! [dependencies.rosy]
//! version = "0.0.2"
//! features = ["ruby_2_6"]
//! ```
//!
//! Finally add this to your crate root (`main.rs` or `lib.rs`):
//!
//! ```
//! extern crate rosy;
//! ```
//!
//! # Initialization
//!
//! The Ruby virtual machine is initialized via [`vm::init`]:
//!
//! ```
//! rosy::vm::init().expect("Failed to initialize Ruby");
//! ```
//!
//! This should be called
//! once by the thread expected to be associated with Ruby. All mutations to
//! Ruby objects from there on are only safe from that same thread since the VM
//! is not known to be thread-safe.
//!
//! # Cleaning Up
//!
//! When done with the Ruby VM, one should call [`vm::destroy`], which will
//! return a status code appropriate for exiting the program.
//!
//! ```
//! # rosy::vm::init().unwrap();
//! if let Err(code) = unsafe { rosy::vm::destroy() } {
//!     std::process::exit(code);
//! }
//! ```
//!
//! # Catching Ruby Exceptions
//!
//! With Rosy, your Rust code can be [`protected`](fn.protected.html) from Ruby
//! exceptions when calling unchecked functions that may throw.
//!
//! Not catching an exception from Rust will result in a segmentation fault at
//! best. As a result, every function that throws an exception is annotated as
//! [`unsafe`] in Rust-land. If a function is found to not uphold this
//! invariant, please report it at [issue #4][issue4] or file a pull request to
//! fix this.
//!
//! ```
//! # rosy::vm::init().unwrap();
//! use rosy::{Object, String};
//!
//! let string = String::from("hello\r\n");
//!
//! let value: usize = rosy::protected(|| unsafe {
//!     string.call_unchecked("chomp!");
//!     string.len()
//! }).unwrap();
//!
//! assert_eq!(value, 5);
//! ```
//!
//! [`Cargo.toml`]: https://doc.rust-lang.org/cargo/reference/manifest.html
//! [crate]: https://crates.io/crates/rosy
//! [Ruby]: https://www.ruby-lang.org
//! [`vm::init`]: vm/fn.init.html
//! [`vm::destroy`]: vm/fn.destroy.html
//! [`unsafe`]: https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
//! [issue4]: https://github.com/oceanpkg/rosy/issues/4

#![cfg_attr(nightly, feature(doc_cfg))]
#![deny(missing_docs)]
#![cfg_attr(all(test, nightly), feature(test))]

#[cfg(all(test, nightly))]
extern crate test;

include!(env!("ROSY_RUBY_VERSION_CONST"));

use std::{mem, ptr};

#[path = "ruby_bindings/mod.rs"]
mod ruby;

mod rosy;
mod util;
pub mod array;
pub mod exception;
pub mod gc;
pub mod hash;
pub mod mixin;
pub mod object;
pub mod prelude;
pub mod string;
pub mod symbol;
pub mod vm;

#[doc(inline)]
pub use self::{
    array::Array,
    exception::{AnyException, Exception},
    hash::Hash,
    mixin::{Mixin, Class, Module},
    object::{AnyObject, Object, RosyObject},
    rosy::Rosy,
    string::String,
    symbol::{Symbol, SymbolId},
};

/// A simplified form of
/// [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) for
/// when exceptions are caught.
pub type Result<T = (), E = AnyException> = std::result::Result<T, E>;

/// Calls `f` and returns its output or an exception if one is raised in `f`.
///
/// # Examples
///
/// This is great for calling methods that may not exist:
///
/// ```
/// # rosy::vm::init();
/// use rosy::{Object, String, protected};
///
/// let string = String::from("¡Hola!");
/// let result = protected(|| unsafe { string.call_unchecked("likes_pie?") });
///
/// assert!(result.is_err());
/// ```
///
/// Calls can even be nested like so:
///
/// ```
/// # rosy::vm::init();
/// use rosy::{Object, String, protected};
///
/// let string = String::from("Hiii!!!");
///
/// let outer = protected(|| {
///     protected(|| unsafe {
///         string.call_unchecked("likes_pie?")
///     }).unwrap_err();
///     string
/// });
///
/// assert_eq!(outer.unwrap(), string);
/// ```
#[inline]
pub fn protected<F, O>(f: F) -> Result<O>
    where F: FnOnce() -> O
{
    if util::matches_ruby_size_align::<O>() {
        return unsafe { protected_size_opt(f) };
    }

    unsafe extern "C" fn wrapper<F, O>(ctx: ruby::VALUE) -> ruby::VALUE
        where F: FnOnce() -> O
    {
        let (f, out) = &mut *(ctx as *mut (Option<F>, &mut O));

        // Get the `F` out of `Option<F>` to call by-value, which is required by
        // the `FnOnce` trait
        let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked());

        ptr::write(*out, f());

        AnyObject::nil().raw()
    }
    unsafe {
        // Required to prevent stack unwinding (if there's a `panic!` in `f()`)
        // from dropping `out`, which is uninitialized memory until `f()`
        use mem::ManuallyDrop;

        // These shenanigans allow us to pass in a pointer to `f`, with a
        // pointer to its uninitialized output, into `rb_protect` to make them
        // accessible from `wrapper`
        let mut out = ManuallyDrop::new(mem::uninitialized::<O>());
        let mut ctx = (Some(f), &mut *out);
        let ctx = &mut ctx as *mut (Option<F>, &mut O) as ruby::VALUE;

        let mut err = 0;
        ruby::rb_protect(Some(wrapper::<F, O>), ctx, &mut err);
        match err {
            0 => Ok(ManuallyDrop::into_inner(out)),
            _ => Err(AnyException::_take_current()),
        }
    }
}

// A version of `protected` that makes use of the size and layout of `O`
// matching that of `ruby::VALUE`. This slightly reduces the number of emitted
// instructions and removes the need for stack-allocating `ctx`.
#[inline]
unsafe fn protected_size_opt<F, O>(f: F) -> Result<O>
    where F: FnOnce() -> O
{
    use mem::ManuallyDrop;

    unsafe extern "C" fn wrapper<F, O>(ctx: ruby::VALUE) -> ruby::VALUE
        where F: FnOnce() -> O
    {
        let f: &mut Option<F> = &mut *(ctx as *mut Option<F>);

        // Get the `F` out of `Option<F>` to call by-value, which is required by
        // the `FnOnce` trait
        let f = f.take().unwrap_or_else(|| std::hint::unreachable_unchecked());

        let value = ManuallyDrop::new(f());
        ptr::read(&value as *const ManuallyDrop<O> as *const ruby::VALUE)
    }

    let mut ctx = Some(f);
    let ctx = &mut ctx as *mut Option<F> as ruby::VALUE;

    let mut err = 0;
    let val = ruby::rb_protect(Some(wrapper::<F, O>), ctx, &mut err);
    match err {
        0 => Ok(ptr::read(&val as *const ruby::VALUE as *const O)),
        _ => Err(AnyException::_take_current()),
    }
}