rquickjs_core/value/
array_buffer.rs

1use crate::{qjs, Ctx, Error, FromJs, IntoJs, JsLifetime, Object, Result, Value};
2use alloc::vec::Vec;
3use core::{
4    ffi::c_void,
5    fmt,
6    mem::{self, size_of, ManuallyDrop, MaybeUninit},
7    ops::Deref,
8    ptr::NonNull,
9    result::Result as StdResult,
10    slice,
11};
12
13use super::typed_array::TypedArrayItem;
14
15pub struct RawArrayBuffer {
16    pub len: usize,
17    pub ptr: NonNull<u8>,
18}
19
20#[derive(Debug, Clone, Copy, Eq, PartialEq)]
21pub enum AsSliceError {
22    BufferUsed,
23    InvalidAlignment,
24}
25
26impl fmt::Display for AsSliceError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        match self {
29            AsSliceError::BufferUsed => write!(f, "Buffer was already used"),
30            AsSliceError::InvalidAlignment => {
31                write!(f, "Buffer had a different alignment than was requested")
32            }
33        }
34    }
35}
36
37/// Rust representation of a JavaScript object of class ArrayBuffer.
38///
39#[derive(Debug, PartialEq, Clone, Eq, Hash)]
40#[repr(transparent)]
41pub struct ArrayBuffer<'js>(pub(crate) Object<'js>);
42
43unsafe impl<'js> JsLifetime<'js> for ArrayBuffer<'js> {
44    type Changed<'to> = ArrayBuffer<'to>;
45}
46
47impl<'js> ArrayBuffer<'js> {
48    /// Create array buffer from vector data
49    pub fn new<T: Copy>(ctx: Ctx<'js>, src: impl Into<Vec<T>>) -> Result<Self> {
50        let mut src = ManuallyDrop::new(src.into());
51        let ptr = src.as_mut_ptr();
52        let capacity = src.capacity();
53        let size = src.len() * size_of::<T>();
54
55        extern "C" fn drop_raw<T>(_rt: *mut qjs::JSRuntime, opaque: *mut c_void, ptr: *mut c_void) {
56            let ptr = ptr as *mut T;
57            let capacity = opaque as usize;
58            // reconstruct vector in order to free data
59            // the length of actual data does not matter for copyable types
60            unsafe { Vec::from_raw_parts(ptr, capacity, capacity) };
61        }
62
63        Ok(Self(Object(unsafe {
64            let val = qjs::JS_NewArrayBuffer(
65                ctx.as_ptr(),
66                ptr as _,
67                size as _,
68                Some(drop_raw::<T>),
69                capacity as _,
70                false,
71            );
72            ctx.handle_exception(val).inspect_err(|_| {
73                // don't forget to free data when error occurred
74                Vec::from_raw_parts(ptr, capacity, capacity);
75            })?;
76            Value::from_js_value(ctx, val)
77        })))
78    }
79
80    /// Create array buffer from slice
81    pub fn new_copy<T: Copy>(ctx: Ctx<'js>, src: impl AsRef<[T]>) -> Result<Self> {
82        let src = src.as_ref();
83        let ptr = src.as_ptr();
84        let size = core::mem::size_of_val(src);
85
86        Ok(Self(Object(unsafe {
87            let val = qjs::JS_NewArrayBufferCopy(ctx.as_ptr(), ptr as _, size as _);
88            ctx.handle_exception(val)?;
89            Value::from_js_value(ctx.clone(), val)
90        })))
91    }
92
93    /// Get the length of the array buffer in bytes.
94    pub fn len(&self) -> usize {
95        Self::get_raw(&self.0).expect("Not an ArrayBuffer").len
96    }
97
98    /// Returns whether an array buffer is empty.
99    pub fn is_empty(&self) -> bool {
100        self.len() == 0
101    }
102
103    /// Returns the underlying bytes of the buffer,
104    ///
105    /// Returns `None` if the array is detached.
106    pub fn as_bytes(&self) -> Option<&[u8]> {
107        let raw = Self::get_raw(self.as_value())?;
108        Some(unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) })
109    }
110
111    /// Returns a slice if the buffer underlying buffer is properly aligned for the type and the
112    /// buffer is not detached.
113    pub fn as_slice<T: TypedArrayItem>(&self) -> StdResult<&[T], AsSliceError> {
114        let raw = Self::get_raw(&self.0).ok_or(AsSliceError::BufferUsed)?;
115        if raw.ptr.as_ptr().align_offset(mem::align_of::<T>()) != 0 {
116            return Err(AsSliceError::InvalidAlignment);
117        }
118        let len = raw.len / size_of::<T>();
119        Ok(unsafe { slice::from_raw_parts(raw.ptr.as_ptr().cast(), len) })
120    }
121
122    /// Detach array buffer
123    pub fn detach(&mut self) {
124        unsafe { qjs::JS_DetachArrayBuffer(self.0.ctx.as_ptr(), self.0.as_js_value()) }
125    }
126
127    /// Reference to value
128    #[inline]
129    pub fn as_value(&self) -> &Value<'js> {
130        self.0.as_value()
131    }
132
133    /// Convert into value
134    #[inline]
135    pub fn into_value(self) -> Value<'js> {
136        self.0.into_value()
137    }
138
139    /// Convert from value
140    pub fn from_value(value: Value<'js>) -> Option<Self> {
141        Self::from_object(Object::from_value(value).ok()?)
142    }
143
144    /// Reference as an object
145    #[inline]
146    pub fn as_object(&self) -> &Object<'js> {
147        &self.0
148    }
149
150    /// Convert into an object
151    #[inline]
152    pub fn into_object(self) -> Object<'js> {
153        self.0
154    }
155
156    /// Convert from an object
157    pub fn from_object(object: Object<'js>) -> Option<Self> {
158        if Self::get_raw(&object.0).is_some() {
159            Some(Self(object))
160        } else {
161            None
162        }
163    }
164
165    /// Returns a structure with data about the raw buffer which this object contains.
166    ///
167    /// Returns None if the buffer was already used.
168    pub fn as_raw(&self) -> Option<RawArrayBuffer> {
169        Self::get_raw(self.as_value())
170    }
171
172    pub(crate) fn get_raw(val: &Value<'js>) -> Option<RawArrayBuffer> {
173        let ctx = val.ctx();
174        let val = val.as_js_value();
175        let mut size = MaybeUninit::<qjs::size_t>::uninit();
176        let ptr = unsafe { qjs::JS_GetArrayBuffer(ctx.as_ptr(), size.as_mut_ptr(), val) };
177
178        if let Some(ptr) = NonNull::new(ptr) {
179            let len = unsafe { size.assume_init() }
180                .try_into()
181                .expect(qjs::SIZE_T_ERROR);
182            Some(RawArrayBuffer { len, ptr })
183        } else {
184            None
185        }
186    }
187}
188
189impl<'js, T: TypedArrayItem> AsRef<[T]> for ArrayBuffer<'js> {
190    fn as_ref(&self) -> &[T] {
191        self.as_slice().expect("ArrayBuffer was detached")
192    }
193}
194
195impl<'js> Deref for ArrayBuffer<'js> {
196    type Target = Object<'js>;
197
198    fn deref(&self) -> &Self::Target {
199        self.as_object()
200    }
201}
202
203impl<'js> AsRef<Object<'js>> for ArrayBuffer<'js> {
204    fn as_ref(&self) -> &Object<'js> {
205        self.as_object()
206    }
207}
208
209impl<'js> AsRef<Value<'js>> for ArrayBuffer<'js> {
210    fn as_ref(&self) -> &Value<'js> {
211        self.as_value()
212    }
213}
214
215impl<'js> FromJs<'js> for ArrayBuffer<'js> {
216    fn from_js(_: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
217        let ty_name = value.type_name();
218        if let Some(v) = Self::from_value(value) {
219            Ok(v)
220        } else {
221            Err(Error::new_from_js(ty_name, "ArrayBuffer"))
222        }
223    }
224}
225
226impl<'js> IntoJs<'js> for ArrayBuffer<'js> {
227    fn into_js(self, _: &Ctx<'js>) -> Result<Value<'js>> {
228        Ok(self.into_value())
229    }
230}
231
232impl<'js> Object<'js> {
233    /// Returns whether the object is an instance of [`ArrayBuffer`].
234    pub fn is_array_buffer(&self) -> bool {
235        ArrayBuffer::get_raw(&self.0).is_some()
236    }
237
238    /// Interpret as [`ArrayBuffer`]
239    ///
240    /// # Safety
241    /// You should be sure that the object actually is the required type.
242    pub unsafe fn ref_array_buffer(&self) -> &ArrayBuffer {
243        mem::transmute(self)
244    }
245
246    /// Turn the object into an array buffer if the object is an instance of [`ArrayBuffer`].
247    pub fn as_array_buffer(&self) -> Option<&ArrayBuffer> {
248        self.is_array_buffer()
249            .then_some(unsafe { self.ref_array_buffer() })
250    }
251}
252
253#[cfg(test)]
254mod test {
255    use crate::*;
256
257    #[test]
258    fn from_javascript_i8() {
259        test_with(|ctx| {
260            let val: ArrayBuffer = ctx
261                .eval(
262                    r#"
263                        new Int8Array([0, -5, 1, 11]).buffer
264                    "#,
265                )
266                .unwrap();
267            assert_eq!(val.len(), 4);
268            assert_eq!(val.as_ref() as &[i8], &[0i8, -5, 1, 11]);
269        });
270    }
271
272    #[test]
273    fn into_javascript_i8() {
274        test_with(|ctx| {
275            let val = ArrayBuffer::new(ctx.clone(), [-1i8, 0, 22, 5]).unwrap();
276            ctx.globals().set("a", val).unwrap();
277            let res: i8 = ctx
278                .eval(
279                    r#"
280                        let v = new Int8Array(a);
281                        v.length != 4 ? 1 :
282                        v[0] != -1 ? 2 :
283                        v[1] != 0 ? 3 :
284                        v[2] != 22 ? 4 :
285                        v[3] != 5 ? 5 :
286                        0
287                    "#,
288                )
289                .unwrap();
290            assert_eq!(res, 0);
291        })
292    }
293
294    #[test]
295    fn from_javascript_f32() {
296        test_with(|ctx| {
297            let val: ArrayBuffer = ctx
298                .eval(
299                    r#"
300                        new Float32Array([0.5, -5.25, 123.125]).buffer
301                    "#,
302                )
303                .unwrap();
304            assert_eq!(val.len(), 12);
305            assert_eq!(val.as_ref() as &[f32], &[0.5f32, -5.25, 123.125]);
306        });
307    }
308
309    #[test]
310    fn into_javascript_f32() {
311        test_with(|ctx| {
312            let val = ArrayBuffer::new(ctx.clone(), [-1.5f32, 0.0, 2.25]).unwrap();
313            ctx.globals().set("a", val).unwrap();
314            let res: i8 = ctx
315                .eval(
316                    r#"
317                        let v = new Float32Array(a);
318                        a.byteLength != 12 ? 1 :
319                        v.length != 3 ? 2 :
320                        v[0] != -1.5 ? 3 :
321                        v[1] != 0 ? 4 :
322                        v[2] != 2.25 ? 5 :
323                        0
324                    "#,
325                )
326                .unwrap();
327            assert_eq!(res, 0);
328        })
329    }
330
331    #[test]
332    fn as_bytes() {
333        test_with(|ctx| {
334            let val: ArrayBuffer = ctx
335                .eval(
336                    r#"
337                        new Uint32Array([0xCAFEDEAD,0xFEEDBEAD]).buffer
338                    "#,
339                )
340                .unwrap();
341            let mut res = [0; 8];
342            let bytes_0 = 0xCAFEDEADu32.to_ne_bytes();
343            res[..4].copy_from_slice(&bytes_0);
344            let bytes_1 = 0xFEEDBEADu32.to_ne_bytes();
345            res[4..].copy_from_slice(&bytes_1);
346
347            assert_eq!(val.as_bytes().unwrap(), &res)
348        });
349    }
350}