rquickjs_core/value/
array_buffer.rs

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