artichoke_backend/convert/
bytes.rs

1use core::mem;
2use std::borrow::Cow;
3use std::ffi::{OsStr, OsString};
4
5use scolapasta_path::{os_str_to_bytes, os_string_to_bytes};
6use spinoso_string::String;
7
8use crate::Artichoke;
9use crate::convert::BoxUnboxVmValue;
10use crate::core::TryConvertMut;
11use crate::error::Error;
12use crate::value::Value;
13
14impl TryConvertMut<Vec<u8>, Value> for Artichoke {
15    type Error = Error;
16
17    fn try_convert_mut(&mut self, value: Vec<u8>) -> Result<Value, Self::Error> {
18        let s = String::utf8(value);
19        let value = String::alloc_value(s, self)?;
20        Ok(self.protect(value))
21    }
22}
23
24impl TryConvertMut<&[u8], Value> for Artichoke {
25    type Error = Error;
26
27    fn try_convert_mut(&mut self, value: &[u8]) -> Result<Value, Self::Error> {
28        self.try_convert_mut(value.to_vec())
29    }
30}
31
32impl<'a> TryConvertMut<Cow<'a, [u8]>, Value> for Artichoke {
33    type Error = Error;
34
35    fn try_convert_mut(&mut self, value: Cow<'a, [u8]>) -> Result<Value, Self::Error> {
36        match value {
37            Cow::Borrowed(bytes) => self.try_convert_mut(bytes),
38            Cow::Owned(bytes) => self.try_convert_mut(bytes),
39        }
40    }
41}
42
43impl TryConvertMut<OsString, Value> for Artichoke {
44    type Error = Error;
45
46    fn try_convert_mut(&mut self, value: OsString) -> Result<Value, Self::Error> {
47        let bytes = os_string_to_bytes(value)?;
48        self.try_convert_mut(bytes)
49    }
50}
51
52impl TryConvertMut<&OsStr, Value> for Artichoke {
53    type Error = Error;
54
55    fn try_convert_mut(&mut self, value: &OsStr) -> Result<Value, Self::Error> {
56        let bytes = os_str_to_bytes(value)?;
57        self.try_convert_mut(bytes)
58    }
59}
60
61impl<'a> TryConvertMut<Cow<'a, OsStr>, Value> for Artichoke {
62    type Error = Error;
63
64    fn try_convert_mut(&mut self, value: Cow<'a, OsStr>) -> Result<Value, Self::Error> {
65        match value {
66            Cow::Borrowed(value) => {
67                let bytes = os_str_to_bytes(value)?;
68                self.try_convert_mut(bytes)
69            }
70            Cow::Owned(value) => {
71                let bytes = os_string_to_bytes(value)?;
72                self.try_convert_mut(bytes)
73            }
74        }
75    }
76}
77
78impl TryConvertMut<Value, Vec<u8>> for Artichoke {
79    type Error = Error;
80
81    fn try_convert_mut(&mut self, mut value: Value) -> Result<Vec<u8>, Self::Error> {
82        let s = unsafe { String::unbox_from_value(&mut value, self)? };
83        Ok(s.clone().into_vec())
84    }
85}
86
87impl<'a> TryConvertMut<Value, &'a [u8]> for Artichoke {
88    type Error = Error;
89
90    fn try_convert_mut(&mut self, mut value: Value) -> Result<&'a [u8], Self::Error> {
91        self.protect(value);
92        let s = unsafe { String::unbox_from_value(&mut value, self)? };
93        // SAFETY: This transmute modifies the lifetime of the byte slice pulled
94        // out of the boxed `String`. This requires that no garbage collections
95        // that reclaim `value` occur while this slice is alive. This is
96        // enforced for at least this entry from an mruby trampoline by the call
97        // to `protect` above.
98        //
99        // FIXME: does this unbound lifetime and transmute below allow
100        // extracting `&'static [u8]`?
101        let slice = unsafe { mem::transmute::<&'_ [u8], &'a [u8]>(s.as_slice()) };
102        Ok(slice)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use bstr::ByteSlice;
109
110    use crate::test::prelude::*;
111
112    #[test]
113    fn fail_convert() {
114        let mut interp = interpreter();
115        // get a Ruby value that can't be converted to a primitive type.
116        let value = interp.eval(b"Object.new").unwrap();
117        let result = value.try_convert_into_mut::<Vec<u8>>(&mut interp);
118        assert!(result.is_err());
119    }
120
121    #[test]
122    fn convert_with_trailing_nul() {
123        let mut interp = interpreter();
124        let bytes: &[u8] = &[0];
125        let value = interp.try_convert_mut(bytes).unwrap();
126        let retrieved_bytes = value.try_convert_into_mut::<&[u8]>(&mut interp).unwrap();
127        assert_eq!(bytes.as_bstr(), retrieved_bytes.as_bstr());
128
129        let len = value.funcall(&mut interp, "bytesize", &[], None).unwrap();
130        let len = len.try_convert_into::<usize>(&interp).unwrap();
131        assert_eq!(len, 1);
132
133        let empty = value.funcall(&mut interp, "empty?", &[], None).unwrap();
134        let empty = empty.try_convert_into::<bool>(&interp).unwrap();
135        assert!(!empty);
136
137        let zero = interp.convert(0);
138        let one = interp.convert(1);
139
140        let str_bytes = value.funcall(&mut interp, "bytes", &[], None).unwrap();
141        let first = str_bytes.funcall(&mut interp, "[]", &[zero], None).unwrap();
142        let first = first.try_convert_into::<i64>(&interp).unwrap();
143        assert_eq!(first, 0_i64);
144
145        let slice = value.funcall(&mut interp, "byteslice", &[zero, one], None).unwrap();
146        let slice = slice.try_convert_into_mut::<Option<&[u8]>>(&mut interp).unwrap();
147        let expected: Option<&[u8]> = Some(&[0]);
148        assert_eq!(slice, expected);
149    }
150
151    #[test]
152    fn prop_convert_to_vec() {
153        let mut interp = interpreter();
154        run_arbitrary::<Vec<u8>>(|bytes| {
155            let value = interp.try_convert_mut(bytes).unwrap();
156            assert_eq!(value.ruby_type(), Ruby::String);
157        });
158    }
159
160    #[test]
161    fn prop_byte_string_borrowed() {
162        let mut interp = interpreter();
163        run_arbitrary::<Vec<u8>>(|bytes| {
164            // Borrowed converter
165            let value = interp.try_convert_mut(bytes.clone()).unwrap();
166            let len = value.funcall(&mut interp, "bytesize", &[], None).unwrap();
167            let len = len.try_convert_into::<usize>(&interp).unwrap();
168            assert_eq!(len, bytes.len());
169
170            let empty = value.funcall(&mut interp, "empty?", &[], None).unwrap();
171            let empty = empty.try_convert_into::<bool>(&interp).unwrap();
172            assert_eq!(empty, bytes.is_empty());
173
174            let zero = interp.convert(0);
175            let one = interp.convert(1);
176
177            let str_bytes = value.funcall(&mut interp, "bytes", &[], None).unwrap();
178            let first = str_bytes.funcall(&mut interp, "[]", &[zero], None).unwrap();
179            let first = first.try_convert_into::<Option<i64>>(&interp).unwrap();
180            assert_eq!(first, bytes.first().copied().map(i64::from));
181
182            let slice = value.funcall(&mut interp, "byteslice", &[zero, one], None).unwrap();
183            let slice = slice.try_convert_into_mut::<Option<&[u8]>>(&mut interp).unwrap();
184            assert_eq!(slice.unwrap_or_default(), bytes.get(0..1).unwrap_or_default());
185
186            let recovered: Vec<u8> = interp.try_convert_mut(value).unwrap();
187            assert_eq!(recovered, bytes);
188        });
189    }
190
191    #[test]
192    fn prop_byte_string_owned() {
193        let mut interp = interpreter();
194        run_arbitrary::<Vec<u8>>(|bytes| {
195            // Owned converter
196            let value = interp.try_convert_mut(bytes.clone()).unwrap();
197            let len = value.funcall(&mut interp, "bytesize", &[], None).unwrap();
198            let len = len.try_convert_into::<usize>(&interp).unwrap();
199            assert_eq!(len, bytes.len());
200
201            let empty = value.funcall(&mut interp, "empty?", &[], None).unwrap();
202            let empty = empty.try_convert_into::<bool>(&interp).unwrap();
203            assert_eq!(empty, bytes.is_empty());
204
205            let zero = interp.convert(0);
206            let one = interp.convert(1);
207
208            let str_bytes = value.funcall(&mut interp, "bytes", &[], None).unwrap();
209            let first = str_bytes.funcall(&mut interp, "[]", &[zero], None).unwrap();
210            let first = first.try_convert_into::<Option<i64>>(&interp).unwrap();
211            assert_eq!(first, bytes.first().copied().map(i64::from));
212
213            let slice = value.funcall(&mut interp, "byteslice", &[zero, one], None).unwrap();
214            let slice = slice.try_convert_into_mut::<Option<&[u8]>>(&mut interp).unwrap();
215            assert_eq!(slice.unwrap_or_default(), bytes.get(0..1).unwrap_or_default());
216
217            let recovered: Vec<u8> = interp.try_convert_mut(value).unwrap();
218            assert_eq!(recovered, bytes);
219        });
220    }
221
222    #[test]
223    fn prop_roundtrip() {
224        let mut interp = interpreter();
225        run_arbitrary::<Vec<u8>>(|bytes| {
226            let value = interp.try_convert_mut(bytes.as_slice()).unwrap();
227            let value = value.try_convert_into_mut::<Vec<u8>>(&mut interp).unwrap();
228            assert_eq!(value, bytes);
229        });
230    }
231
232    #[test]
233    fn prop_roundtrip_err() {
234        let mut interp = interpreter();
235        for b in [true, false] {
236            let value = interp.convert(b);
237            let value = value.try_convert_into_mut::<Vec<u8>>(&mut interp);
238            assert!(value.is_err());
239        }
240    }
241}