artichoke_backend/convert/
boxing.rsuse std::ffi::c_void;
use std::fmt;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::ptr;
use spinoso_exception::TypeError;
use crate::core::Value as _;
use crate::def::NotDefinedError;
use crate::error::Error;
use crate::ffi::InterpreterExtractError;
use crate::sys;
use crate::types::Ruby;
use crate::value::Value;
use crate::Artichoke;
pub struct UnboxedValueGuard<'a, T> {
guarded: ManuallyDrop<T>,
phantom: PhantomData<&'a mut T>,
}
impl<'a, T> fmt::Debug for UnboxedValueGuard<'a, T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnboxedValueGuard")
.field("guarded", &self.guarded)
.finish()
}
}
impl<'a, T> UnboxedValueGuard<'a, T> {
#[must_use]
pub fn new(value: T) -> Self {
Self {
guarded: ManuallyDrop::new(value),
phantom: PhantomData,
}
}
#[inline]
#[must_use]
pub fn as_inner_ref(&self) -> &T {
&self.guarded
}
#[inline]
#[must_use]
pub unsafe fn as_inner_mut(&mut self) -> &mut T {
&mut self.guarded
}
#[inline]
#[must_use]
pub unsafe fn take(mut self) -> T {
ManuallyDrop::take(&mut self.guarded)
}
}
#[derive(Debug)]
pub struct HeapAllocated<T>(Box<T>);
impl<T> HeapAllocated<T> {
#[must_use]
pub fn new(obj: Box<T>) -> Self {
Self(obj)
}
}
impl<'a, T> Deref for UnboxedValueGuard<'a, HeapAllocated<T>> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.guarded.deref().0.as_ref()
}
}
impl<'a, T> DerefMut for UnboxedValueGuard<'a, HeapAllocated<T>> {
fn deref_mut(&mut self) -> &mut Self::Target {
let inner = unsafe { self.as_inner_mut() };
inner.0.as_mut()
}
}
pub trait HeapAllocatedData {
const RUBY_TYPE: &'static str;
}
#[derive(Debug)]
pub struct Immediate<T>(T);
impl<T> Immediate<T> {
pub fn new(obj: T) -> Self {
Self(obj)
}
}
impl<'a, T> Deref for UnboxedValueGuard<'a, Immediate<T>> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.guarded.deref().0
}
}
impl<'a, T> DerefMut for UnboxedValueGuard<'a, Immediate<T>> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guarded.deref_mut().0
}
}
pub trait BoxUnboxVmValue {
type Unboxed;
type Guarded;
const RUBY_TYPE: &'static str;
unsafe fn unbox_from_value<'a>(
value: &'a mut Value,
interp: &mut Artichoke,
) -> Result<UnboxedValueGuard<'a, Self::Guarded>, Error>;
fn alloc_value(value: Self::Unboxed, interp: &mut Artichoke) -> Result<Value, Error>;
fn box_into_value(value: Self::Unboxed, into: Value, interp: &mut Artichoke) -> Result<Value, Error>;
fn free(data: *mut c_void);
}
impl<T> BoxUnboxVmValue for T
where
T: HeapAllocatedData + Sized + 'static,
{
type Unboxed = Self;
type Guarded = HeapAllocated<Self::Unboxed>;
const RUBY_TYPE: &'static str = <Self as HeapAllocatedData>::RUBY_TYPE;
unsafe fn unbox_from_value<'a>(
value: &'a mut Value,
interp: &mut Artichoke,
) -> Result<UnboxedValueGuard<'a, Self::Guarded>, Error> {
if value.ruby_type() != Ruby::Data {
let mut message = String::from("uninitialized ");
message.push_str(Self::RUBY_TYPE);
return Err(TypeError::from(message).into());
}
let mut rclass = {
let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
let spec = state
.classes
.get::<Self>()
.ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
let rclass = spec.rclass();
interp
.with_ffi_boundary(|mrb| rclass.resolve(mrb))?
.ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?
};
let value_rclass = interp.with_ffi_boundary(|mrb| sys::mrb_sys_class_of_value(mrb, value.inner()))?;
if !ptr::eq(value_rclass, rclass.as_mut()) {
let mut message = String::from("Could not extract ");
message.push_str(Self::RUBY_TYPE);
message.push_str(" from receiver");
return Err(TypeError::from(message).into());
}
let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
let spec = state
.classes
.get::<Self>()
.ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
let data_type = spec.data_type();
let embedded_data_ptr =
interp.with_ffi_boundary(|mrb| sys::mrb_data_check_get_ptr(mrb, value.inner(), data_type))?;
if embedded_data_ptr.is_null() {
let mut message = String::from("uninitialized ");
message.push_str(Self::RUBY_TYPE);
return Err(TypeError::from(message).into());
}
let value = Box::from_raw(embedded_data_ptr.cast::<Self>());
Ok(UnboxedValueGuard::new(HeapAllocated::new(value)))
}
fn alloc_value(value: Self::Unboxed, interp: &mut Artichoke) -> Result<Value, Error> {
let mut rclass = {
let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
let spec = state
.classes
.get::<Self>()
.ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
let rclass = spec.rclass();
unsafe { interp.with_ffi_boundary(|mrb| rclass.resolve(mrb)) }?
.ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?
};
let data = Box::new(value);
let ptr = Box::into_raw(data);
let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
let spec = state
.classes
.get::<Self>()
.ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
let data_type = spec.data_type();
let obj = unsafe {
interp.with_ffi_boundary(|mrb| {
let alloc = sys::mrb_data_object_alloc(mrb, rclass.as_mut(), ptr.cast::<c_void>(), data_type);
sys::mrb_sys_obj_value(alloc.cast::<c_void>())
})?
};
Ok(interp.protect(Value::from(obj)))
}
fn box_into_value(value: Self::Unboxed, into: Value, interp: &mut Artichoke) -> Result<Value, Error> {
let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
let spec = state
.classes
.get::<Self>()
.ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
let data = Box::new(value);
let ptr = Box::into_raw(data);
let mut obj = into.inner();
unsafe {
sys::mrb_sys_data_init(&mut obj, ptr.cast::<c_void>(), spec.data_type());
}
Ok(Value::from(obj))
}
fn free(data: *mut c_void) {
let data = data.cast::<Self>();
let unboxed = unsafe { Box::from_raw(data) };
drop(unboxed);
}
}
#[cfg(test)]
mod tests {
use bstr::ByteSlice;
use crate::test::prelude::*;
#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct Container(String);
unsafe extern "C" fn container_value(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value {
unwrap_interpreter!(mrb, to => guard);
let mut value = Value::from(slf);
let result = if let Ok(container) = Container::unbox_from_value(&mut value, &mut guard) {
guard.try_convert_mut(container.0.as_bytes()).unwrap_or_default()
} else {
Value::nil()
};
result.inner()
}
impl HeapAllocatedData for Container {
const RUBY_TYPE: &'static str = "Container";
}
#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct Flag(bool);
impl HeapAllocatedData for Box<Flag> {
const RUBY_TYPE: &'static str = "Flag";
}
#[test]
fn convert_obj_roundtrip() {
let mut interp = interpreter();
let spec = class::Spec::new(
"Container",
qed::const_cstr_from_str!("Container\0"),
None,
Some(def::box_unbox_free::<Container>),
)
.unwrap();
class::Builder::for_spec(&mut interp, &spec)
.value_is_rust_object()
.add_method("value", container_value, sys::mrb_args_none())
.unwrap()
.define()
.unwrap();
interp.def_class::<Container>(spec).unwrap();
let obj = Container(String::from("contained string contents"));
let mut value = Container::alloc_value(obj, &mut interp).unwrap();
let class = value.funcall(&mut interp, "class", &[], None).unwrap();
let class_display = class.to_s(&mut interp);
assert_eq!(class_display.as_bstr(), b"Container".as_bstr());
let data = unsafe { Container::unbox_from_value(&mut value, &mut interp) }.unwrap();
let inner = data.0.as_str();
assert_eq!(inner, "contained string contents");
let inner = value.funcall(&mut interp, "value", &[], None).unwrap();
let inner = inner.try_convert_into_mut::<&str>(&mut interp).unwrap();
assert_eq!(inner, "contained string contents");
}
#[test]
fn convert_obj_not_data() {
let mut interp = interpreter();
let spec = class::Spec::new(
"Container",
qed::const_cstr_from_str!("Container\0"),
None,
Some(def::box_unbox_free::<Container>),
)
.unwrap();
class::Builder::for_spec(&mut interp, &spec)
.value_is_rust_object()
.add_method("value", container_value, sys::mrb_args_none())
.unwrap()
.define()
.unwrap();
interp.def_class::<Container>(spec).unwrap();
let spec = class::Spec::new(
"Flag",
qed::const_cstr_from_str!("Flag\0"),
None,
Some(def::box_unbox_free::<Box<Flag>>),
)
.unwrap();
class::Builder::for_spec(&mut interp, &spec)
.value_is_rust_object()
.define()
.unwrap();
interp.def_class::<Box<Flag>>(spec).unwrap();
let mut value = interp.try_convert_mut("string").unwrap();
let class = value.funcall(&mut interp, "class", &[], None).unwrap();
let class_display = class.to_s(&mut interp);
assert_eq!(class_display.as_bstr(), b"String".as_bstr());
let data = unsafe { Container::unbox_from_value(&mut value, &mut interp) };
assert!(data.is_err());
let flag = Box::default();
let mut value = Box::<Flag>::alloc_value(flag, &mut interp).unwrap();
let class = value.funcall(&mut interp, "class", &[], None).unwrap();
let class_display = class.to_s(&mut interp);
assert_eq!(class_display.as_bstr(), b"Flag".as_bstr());
let data = unsafe { Container::unbox_from_value(&mut value, &mut interp) };
assert!(data.is_err());
}
}