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
use crate::{
binding::{io, vm},
AnyException, AnyObject, Object, RString,
};
/// Ruby's `Marshal`: serializes objects to bytes and back.
///
/// **Never load data from an untrusted source**: loading can instantiate
/// arbitrary classes and run their hooks, just like `Marshal.load` in Ruby.
pub struct Marshal;
impl Marshal {
/// Serializes `object` (Ruby's `Marshal.dump`, `rb_marshal_dump`), or
/// returns the `TypeError` for objects that cannot be dumped (procs,
/// IO, singletons with methods, ...).
///
/// # Examples
///
/// ```
/// use rutie::{Fixnum, Marshal, Object, VM};
/// # VM::init();
///
/// let bytes = Marshal::dump(&Fixnum::new(42)).unwrap();
/// let loaded = Marshal::load(&bytes).unwrap();
///
/// assert_eq!(loaded.try_convert_to::<Fixnum>(), Ok(Fixnum::new(42)));
///
/// assert!(Marshal::dump(&VM::eval("proc {}").unwrap()).is_err());
/// ```
pub fn dump<T: Object>(object: &T) -> Result<RString, AnyException> {
let object = object.value();
vm::protect_value(|| io::marshal_dump(object))
.map(RString::from)
.map_err(AnyException::from)
}
/// Deserializes an object dumped by [`Marshal::dump`](#method.dump) or
/// Ruby's `Marshal.dump` (`rb_marshal_load`), or returns the error for
/// malformed data. See the warning on [`Marshal`](struct.Marshal.html).
///
/// # Examples
///
/// ```
/// use rutie::{Array, Marshal, Object, RString, VM};
/// # VM::init();
///
/// let bytes = VM::eval("Marshal.dump([1, 'two'])").unwrap().try_convert_to::<RString>().unwrap();
/// let array = Marshal::load(&bytes).unwrap().try_convert_to::<Array>().unwrap();
///
/// assert_eq!(array.length(), 2);
/// assert!(Marshal::load(&RString::new_utf8("not marshal data")).is_err());
/// ```
pub fn load(data: &RString) -> Result<AnyObject, AnyException> {
let data = data.value();
vm::protect_value(|| io::marshal_load(data))
.map(AnyObject::from)
.map_err(AnyException::from)
}
}
#[cfg(test)]
mod tests {
use crate::{Fixnum, Hash, Marshal, Object, RString, Symbol, VM};
#[test]
fn test_marshal_round_trip() {
crate::on_ruby_thread(|| {
let mut hash = Hash::new();
hash.store(Symbol::new("key"), RString::new_utf8("value"));
hash.store(Fixnum::new(1), crate::Integer::from(u128::max_value()));
let bytes = Marshal::dump(&hash).unwrap();
let loaded = Marshal::load(&bytes)
.unwrap()
.try_convert_to::<Hash>()
.unwrap();
assert!(loaded.equals(&hash));
assert!(!loaded.is_equal(&hash));
let from_ruby = VM::eval("Marshal.load(Marshal.dump(:sym))").unwrap();
assert_eq!(from_ruby.try_convert_to::<Symbol>(), Ok(Symbol::new("sym")));
let truncated = RString::new_utf8("\u{4}\u{8}[");
assert!(Marshal::load(&truncated).is_err());
assert!(Marshal::dump(&VM::eval("$stdout").unwrap()).is_err());
});
}
}