artichoke_backend/convert/
boxing.rs1use std::ffi::c_void;
2use std::fmt;
3use std::marker::PhantomData;
4use std::mem::ManuallyDrop;
5use std::ops::{Deref, DerefMut};
6use std::ptr;
7
8use spinoso_exception::TypeError;
9
10use crate::Artichoke;
11use crate::core::Value as _;
12use crate::def::NotDefinedError;
13use crate::error::Error;
14use crate::ffi::InterpreterExtractError;
15use crate::sys;
16use crate::types::Ruby;
17use crate::value::Value;
18
19pub struct UnboxedValueGuard<'a, T> {
20 guarded: ManuallyDrop<T>,
21 phantom: PhantomData<&'a mut T>,
22}
23
24impl<T> fmt::Debug for UnboxedValueGuard<'_, T>
25where
26 T: fmt::Debug,
27{
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 f.debug_struct("UnboxedValueGuard")
30 .field("guarded", &self.guarded)
31 .finish()
32 }
33}
34
35impl<T> UnboxedValueGuard<'_, T> {
36 #[must_use]
43 pub fn new(value: T) -> Self {
44 Self {
45 guarded: ManuallyDrop::new(value),
46 phantom: PhantomData,
47 }
48 }
49
50 #[inline]
52 #[must_use]
53 pub fn as_inner_ref(&self) -> &T {
54 &self.guarded
55 }
56
57 #[inline]
65 #[must_use]
66 pub unsafe fn as_inner_mut(&mut self) -> &mut T {
67 &mut self.guarded
68 }
69
70 #[inline]
82 #[must_use]
83 pub unsafe fn take(mut self) -> T {
84 unsafe { ManuallyDrop::take(&mut self.guarded) }
87 }
88}
89
90#[derive(Debug)]
91pub struct HeapAllocated<T>(Box<T>);
92
93impl<T> HeapAllocated<T> {
94 #[must_use]
95 pub fn new(obj: Box<T>) -> Self {
96 Self(obj)
97 }
98}
99
100impl<T> Deref for UnboxedValueGuard<'_, HeapAllocated<T>> {
101 type Target = T;
102
103 fn deref(&self) -> &Self::Target {
104 self.guarded.deref().0.as_ref()
105 }
106}
107
108impl<T> DerefMut for UnboxedValueGuard<'_, HeapAllocated<T>> {
109 fn deref_mut(&mut self) -> &mut Self::Target {
110 let inner = unsafe { self.as_inner_mut() };
114 inner.0.as_mut()
115 }
116}
117
118pub trait HeapAllocatedData {
119 const RUBY_TYPE: &'static str;
120}
121
122#[derive(Debug)]
123pub struct Immediate<T>(T);
124
125impl<T> Immediate<T> {
126 pub fn new(obj: T) -> Self {
127 Self(obj)
128 }
129}
130
131impl<T> Deref for UnboxedValueGuard<'_, Immediate<T>> {
132 type Target = T;
133
134 fn deref(&self) -> &Self::Target {
135 &self.guarded.deref().0
136 }
137}
138
139impl<T> DerefMut for UnboxedValueGuard<'_, Immediate<T>> {
140 fn deref_mut(&mut self) -> &mut Self::Target {
141 &mut self.guarded.deref_mut().0
142 }
143}
144
145pub trait BoxUnboxVmValue {
146 type Unboxed;
147 type Guarded;
148
149 const RUBY_TYPE: &'static str;
150
151 unsafe fn unbox_from_value<'a>(
159 value: &'a mut Value,
160 interp: &mut Artichoke,
161 ) -> Result<UnboxedValueGuard<'a, Self::Guarded>, Error>;
162
163 fn alloc_value(value: Self::Unboxed, interp: &mut Artichoke) -> Result<Value, Error>;
164
165 fn box_into_value(value: Self::Unboxed, into: Value, interp: &mut Artichoke) -> Result<Value, Error>;
166
167 fn free(data: *mut c_void);
173}
174
175impl<T> BoxUnboxVmValue for T
176where
177 T: HeapAllocatedData + Sized + 'static,
178{
179 type Unboxed = Self;
180 type Guarded = HeapAllocated<Self::Unboxed>;
181
182 const RUBY_TYPE: &'static str = <Self as HeapAllocatedData>::RUBY_TYPE;
183
184 unsafe fn unbox_from_value<'a>(
185 value: &'a mut Value,
186 interp: &mut Artichoke,
187 ) -> Result<UnboxedValueGuard<'a, Self::Guarded>, Error> {
188 let Ruby::Data = value.ruby_type() else {
190 let mut message = String::from("uninitialized ");
191 message.push_str(Self::RUBY_TYPE);
192 return Err(TypeError::from(message).into());
193 };
194
195 let mut rclass = {
196 let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
197 let spec = state
198 .classes
199 .get::<Self>()
200 .ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
201 let rclass = spec.rclass();
202 unsafe {
205 interp
206 .with_ffi_boundary(|mrb| rclass.resolve(mrb))?
207 .ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?
208 }
209 };
210
211 let value_rclass = unsafe { interp.with_ffi_boundary(|mrb| sys::mrb_sys_class_of_value(mrb, value.inner()))? };
216 if !ptr::eq(value_rclass, unsafe { rclass.as_mut() }) {
218 let mut message = String::from("Could not extract ");
219 message.push_str(Self::RUBY_TYPE);
220 message.push_str(" from receiver");
221 return Err(TypeError::from(message).into());
222 }
223
224 let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
226 let spec = state
227 .classes
228 .get::<Self>()
229 .ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
230 let data_type = spec.data_type();
231 let embedded_data_ptr =
234 unsafe { interp.with_ffi_boundary(|mrb| sys::mrb_data_check_get_ptr(mrb, value.inner(), data_type))? };
235 if embedded_data_ptr.is_null() {
236 let mut message = String::from("uninitialized ");
239 message.push_str(Self::RUBY_TYPE);
240 return Err(TypeError::from(message).into());
241 }
242
243 let value = unsafe { Box::from_raw(embedded_data_ptr.cast::<Self>()) };
249 Ok(UnboxedValueGuard::new(HeapAllocated::new(value)))
252 }
253
254 fn alloc_value(value: Self::Unboxed, interp: &mut Artichoke) -> Result<Value, Error> {
255 let mut rclass = {
256 let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
257 let spec = state
258 .classes
259 .get::<Self>()
260 .ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
261 let rclass = spec.rclass();
262 unsafe { interp.with_ffi_boundary(|mrb| rclass.resolve(mrb)) }?
263 .ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?
264 };
265
266 let data = Box::new(value);
268 let ptr = Box::into_raw(data);
269
270 let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
272 let spec = state
273 .classes
274 .get::<Self>()
275 .ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
276 let data_type = spec.data_type();
277 let obj = unsafe {
278 interp.with_ffi_boundary(|mrb| {
279 let alloc = sys::mrb_data_object_alloc(mrb, rclass.as_mut(), ptr.cast::<c_void>(), data_type);
280 sys::mrb_sys_obj_value(alloc.cast::<c_void>())
281 })?
282 };
283
284 Ok(interp.protect(Value::from(obj)))
285 }
286
287 fn box_into_value(value: Self::Unboxed, into: Value, interp: &mut Artichoke) -> Result<Value, Error> {
288 let state = interp.state.as_ref().ok_or_else(InterpreterExtractError::new)?;
289 let spec = state
290 .classes
291 .get::<Self>()
292 .ok_or_else(|| NotDefinedError::class(Self::RUBY_TYPE))?;
293
294 let data = Box::new(value);
296 let ptr = Box::into_raw(data);
297
298 let mut obj = into.inner();
300 unsafe {
301 sys::mrb_sys_data_init(&mut obj, ptr.cast::<c_void>(), spec.data_type());
302 }
303 Ok(Value::from(obj))
304 }
305
306 fn free(data: *mut c_void) {
307 let data = data.cast::<Self>();
309 let unboxed = unsafe { Box::from_raw(data) };
311 drop(unboxed);
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use bstr::ByteSlice;
319
320 use crate::test::prelude::*;
321
322 #[derive(Default, Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
324 struct Container(String);
325
326 unsafe extern "C-unwind" fn container_value(mrb: *mut sys::mrb_state, slf: sys::mrb_value) -> sys::mrb_value {
327 unwrap_interpreter!(mrb, to => guard);
328
329 let mut value = Value::from(slf);
330 let Ok(container) = (unsafe { Container::unbox_from_value(&mut value, &mut guard) }) else {
331 return Value::nil().inner();
332 };
333 guard
334 .try_convert_mut(container.0.as_bytes())
335 .unwrap_or_default()
336 .inner()
337 }
338
339 impl HeapAllocatedData for Container {
340 const RUBY_TYPE: &'static str = "Container";
341 }
342
343 #[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
345 struct Flag(bool);
346
347 impl HeapAllocatedData for Box<Flag> {
348 const RUBY_TYPE: &'static str = "Flag";
349 }
350
351 #[test]
352 fn convert_obj_roundtrip() {
353 let mut interp = interpreter();
354 let spec = class::Spec::new("Container", c"Container", None, Some(def::box_unbox_free::<Container>)).unwrap();
355 class::Builder::for_spec(&mut interp, &spec)
356 .value_is_rust_object()
357 .add_method("value", container_value, sys::mrb_args_none())
358 .unwrap()
359 .define()
360 .unwrap();
361 interp.def_class::<Container>(spec).unwrap();
362 let obj = Container(String::from("contained string contents"));
363
364 let mut value = Container::alloc_value(obj, &mut interp).unwrap();
365 let class = value.funcall(&mut interp, "class", &[], None).unwrap();
366 let class_display = class.to_s(&mut interp);
367 assert_eq!(class_display.as_bstr(), b"Container".as_bstr());
368
369 let data = unsafe { Container::unbox_from_value(&mut value, &mut interp) }.unwrap();
370
371 let inner = data.0.as_str();
372 assert_eq!(inner, "contained string contents");
373
374 let inner = value.funcall(&mut interp, "value", &[], None).unwrap();
375 let inner = inner.try_convert_into_mut::<&str>(&mut interp).unwrap();
376 assert_eq!(inner, "contained string contents");
377 }
378
379 #[test]
380 fn convert_obj_not_data() {
381 let mut interp = interpreter();
382
383 let spec = class::Spec::new("Container", c"Container", None, Some(def::box_unbox_free::<Container>)).unwrap();
384 class::Builder::for_spec(&mut interp, &spec)
385 .value_is_rust_object()
386 .add_method("value", container_value, sys::mrb_args_none())
387 .unwrap()
388 .define()
389 .unwrap();
390 interp.def_class::<Container>(spec).unwrap();
391
392 let spec = class::Spec::new("Flag", c"Flag", None, Some(def::box_unbox_free::<Box<Flag>>)).unwrap();
393 class::Builder::for_spec(&mut interp, &spec)
394 .value_is_rust_object()
395 .define()
396 .unwrap();
397 interp.def_class::<Box<Flag>>(spec).unwrap();
398
399 let mut value = interp.try_convert_mut("string").unwrap();
400 let class = value.funcall(&mut interp, "class", &[], None).unwrap();
401 let class_display = class.to_s(&mut interp);
402 assert_eq!(class_display.as_bstr(), b"String".as_bstr());
403
404 let data = unsafe { Container::unbox_from_value(&mut value, &mut interp) };
405 assert!(data.is_err());
406
407 let flag = Box::default();
408 let mut value = Box::<Flag>::alloc_value(flag, &mut interp).unwrap();
409 let class = value.funcall(&mut interp, "class", &[], None).unwrap();
410 let class_display = class.to_s(&mut interp);
411 assert_eq!(class_display.as_bstr(), b"Flag".as_bstr());
412
413 let data = unsafe { Container::unbox_from_value(&mut value, &mut interp) };
414 assert!(data.is_err());
415 }
416}