Skip to main content

rquickjs_core/value/
array_buffer.rs

1use crate::{
2    markers::ParallelSend, qjs, Ctx, Error, FromJs, IntoJs, JsLifetime, Object, Result, Value,
3};
4use alloc::boxed::Box;
5use alloc::sync::Arc;
6use alloc::vec::Vec;
7use core::{
8    ffi::c_void,
9    fmt,
10    mem::{self, size_of, ManuallyDrop, MaybeUninit},
11    ops::Deref,
12    ptr::NonNull,
13    result::Result as StdResult,
14    slice,
15};
16
17use super::typed_array::TypedArrayItem;
18
19/// A contiguous byte region owned by `self` and usable as the backing store
20/// of an [`ArrayBuffer`].
21///
22/// # Safety
23///
24/// * `as_ptr()` must return a pointer that is valid for reads (and writes,
25///   when used via [`ArrayBuffer::from_source`] / [`ArrayBuffer::from_source_shared`])
26///   of `len()` bytes.
27/// * The returned pointer must remain valid until `self` is dropped, including
28///   across moves of `self`.
29pub unsafe trait ArrayBufferSource {
30    fn as_ptr(&self) -> *mut u8;
31    fn len(&self) -> usize;
32    fn is_empty(&self) -> bool {
33        self.len() == 0
34    }
35}
36
37macro_rules! impl_array_buffer_source {
38    ($($t:ty),* $(,)?) => {
39        $(
40            unsafe impl ArrayBufferSource for $t {
41                fn as_ptr(&self) -> *mut u8 {
42                    <[u8]>::as_ptr(self) as *mut u8
43                }
44                fn len(&self) -> usize {
45                    <[u8]>::len(self)
46                }
47            }
48        )*
49    };
50}
51
52impl_array_buffer_source!(Vec<u8>, alloc::boxed::Box<[u8]>, Arc<[u8]>, Arc<Vec<u8>>);
53
54#[cfg(feature = "bytes")]
55impl_array_buffer_source!(bytes::Bytes);
56
57pub struct RawArrayBuffer {
58    pub len: usize,
59    pub ptr: NonNull<u8>,
60}
61
62#[derive(Debug, Clone, Copy, Eq, PartialEq)]
63pub enum AsSliceError {
64    BufferUsed,
65    InvalidAlignment,
66}
67
68impl fmt::Display for AsSliceError {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            AsSliceError::BufferUsed => write!(f, "Buffer was already used"),
72            AsSliceError::InvalidAlignment => {
73                write!(f, "Buffer had a different alignment than was requested")
74            }
75        }
76    }
77}
78
79/// Rust representation of a JavaScript object of class ArrayBuffer.
80///
81#[derive(Debug, PartialEq, Clone, Eq, Hash)]
82#[repr(transparent)]
83pub struct ArrayBuffer<'js>(pub(crate) Object<'js>);
84
85unsafe impl<'js> JsLifetime<'js> for ArrayBuffer<'js> {
86    type Changed<'to> = ArrayBuffer<'to>;
87}
88
89impl<'js> ArrayBuffer<'js> {
90    /// Create array buffer from vector data
91    pub fn new<T: Copy>(ctx: Ctx<'js>, src: impl Into<Vec<T>>) -> Result<Self> {
92        let mut src = ManuallyDrop::new(src.into());
93        let ptr = src.as_mut_ptr();
94        let capacity = src.capacity();
95        let size = src.len() * size_of::<T>();
96
97        extern "C" fn drop_raw<T>(_rt: *mut qjs::JSRuntime, opaque: *mut c_void, ptr: *mut c_void) {
98            let ptr = ptr as *mut T;
99            let capacity = opaque as usize;
100            // reconstruct vector in order to free data
101            // the length of actual data does not matter for copyable types
102            unsafe { Vec::from_raw_parts(ptr, capacity, capacity) };
103        }
104
105        Ok(Self(Object(unsafe {
106            let val = qjs::JS_NewArrayBuffer(
107                ctx.as_ptr(),
108                ptr as _,
109                size as _,
110                Some(drop_raw::<T>),
111                capacity as _,
112                false,
113            );
114            ctx.handle_exception(val).inspect_err(|_| {
115                // don't forget to free data when error occurred
116                Vec::from_raw_parts(ptr, capacity, capacity);
117            })?;
118            Value::from_js_value(ctx, val)
119        })))
120    }
121
122    /// Create array buffer from slice
123    pub fn new_copy<T: Copy>(ctx: Ctx<'js>, src: impl AsRef<[T]>) -> Result<Self> {
124        let src = src.as_ref();
125        let ptr = src.as_ptr();
126        let size = core::mem::size_of_val(src);
127
128        Ok(Self(Object(unsafe {
129            let val = qjs::JS_NewArrayBufferCopy(ctx.as_ptr(), ptr as _, size as _);
130            ctx.handle_exception(val)?;
131            Value::from_js_value(ctx.clone(), val)
132        })))
133    }
134
135    /// Create an `ArrayBuffer` from a source that owns its backing bytes.
136    ///
137    /// The source is moved into the buffer; JS has exclusive mutable access
138    /// until the buffer is collected, at which point the source is dropped.
139    /// Using this with a shared-immutable source (`Arc<[u8]>`, `bytes::Bytes`,
140    /// …) is unsound; use [`from_source_immutable`](Self::from_source_immutable) for those.
141    pub fn from_source<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
142    where
143        S: ArrayBufferSource + ParallelSend + 'static,
144    {
145        let ptr = src.as_ptr();
146        let len = src.len();
147        unsafe { Self::from_external(ctx, ptr, len, false, false, move || drop(src)) }
148    }
149
150    /// Create a `SharedArrayBuffer` from a source that owns its backing bytes.
151    ///
152    /// See [`from_source`](Self::from_source) for ownership semantics.
153    pub fn from_source_shared<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
154    where
155        S: ArrayBufferSource + ParallelSend + 'static,
156    {
157        let ptr = src.as_ptr();
158        let len = src.len();
159        unsafe { Self::from_external(ctx, ptr, len, true, false, move || drop(src)) }
160    }
161
162    /// Create an `ArrayBuffer` that JS sees as immutable, backed by a
163    /// possibly shared-immutable source.
164    ///
165    /// JS writes throw, so it is sound for the caller to keep holding
166    /// shared-immutable references to the backing store (`Arc<[u8]>`,
167    /// `bytes::Bytes`, …).
168    pub fn from_source_immutable<S>(ctx: Ctx<'js>, src: S) -> Result<Self>
169    where
170        S: ArrayBufferSource + ParallelSend + 'static,
171    {
172        let ptr = src.as_ptr();
173        let len = src.len();
174        unsafe { Self::from_external(ctx, ptr, len, false, true, move || drop(src)) }
175    }
176
177    /// Internal helper backing the safe `from_source*` constructors.
178    ///
179    /// `drop_fn` is invoked exactly once: when the buffer is garbage-collected,
180    /// or synchronously if construction fails.
181    ///
182    /// # Safety
183    ///
184    /// `ptr` must point to `len` bytes of valid memory until `drop_fn` runs.
185    unsafe fn from_external<F>(
186        ctx: Ctx<'js>,
187        ptr: *mut u8,
188        len: usize,
189        is_shared: bool,
190        immutable: bool,
191        drop_fn: F,
192    ) -> Result<Self>
193    where
194        F: FnOnce() + ParallelSend + 'static,
195    {
196        extern "C" fn shim<F: FnOnce()>(
197            _rt: *mut qjs::JSRuntime,
198            opaque: *mut c_void,
199            _ptr: *mut c_void,
200        ) {
201            unsafe {
202                let boxed: Box<F> = Box::from_raw(opaque as *mut F);
203                (*boxed)();
204            }
205        }
206
207        let opaque = Box::into_raw(Box::new(drop_fn)) as *mut c_void;
208
209        Ok(Self(Object(unsafe {
210            let val = qjs::JS_NewArrayBuffer(
211                ctx.as_ptr(),
212                ptr,
213                len as _,
214                Some(shim::<F>),
215                opaque,
216                is_shared,
217            );
218            if let Err(e) = ctx.handle_exception(val) {
219                shim::<F>(qjs::JS_GetRuntime(ctx.as_ptr()), opaque, ptr as *mut c_void);
220                return Err(e);
221            }
222            if immutable {
223                qjs::JS_SetImmutableArrayBuffer(val, true);
224            }
225            Value::from_js_value(ctx, val)
226        })))
227    }
228
229    /// Get the length of the array buffer in bytes.
230    pub fn len(&self) -> usize {
231        Self::get_raw(&self.0).expect("Not an ArrayBuffer").len
232    }
233
234    /// Returns whether an array buffer is empty.
235    pub fn is_empty(&self) -> bool {
236        self.len() == 0
237    }
238
239    /// Returns the underlying bytes of the buffer,
240    ///
241    /// Returns `None` if the array is detached.
242    pub fn as_bytes(&self) -> Option<&[u8]> {
243        let raw = Self::get_raw(self.as_value())?;
244        Some(unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) })
245    }
246
247    /// Returns a slice if the buffer underlying buffer is properly aligned for the type and the
248    /// buffer is not detached.
249    pub fn as_slice<T: TypedArrayItem>(&self) -> StdResult<&[T], AsSliceError> {
250        let raw = Self::get_raw(&self.0).ok_or(AsSliceError::BufferUsed)?;
251        if raw.ptr.as_ptr().align_offset(mem::align_of::<T>()) != 0 {
252            return Err(AsSliceError::InvalidAlignment);
253        }
254        let len = raw.len / size_of::<T>();
255        Ok(unsafe { slice::from_raw_parts(raw.ptr.as_ptr().cast(), len) })
256    }
257
258    /// Detach array buffer
259    pub fn detach(&mut self) {
260        unsafe { qjs::JS_DetachArrayBuffer(self.0.ctx.as_ptr(), self.0.as_js_value()) }
261    }
262
263    /// Reference to value
264    #[inline]
265    pub fn as_value(&self) -> &Value<'js> {
266        self.0.as_value()
267    }
268
269    /// Convert into value
270    #[inline]
271    pub fn into_value(self) -> Value<'js> {
272        self.0.into_value()
273    }
274
275    /// Convert from value
276    pub fn from_value(value: Value<'js>) -> Option<Self> {
277        Self::from_object(Object::from_value(value).ok()?)
278    }
279
280    /// Reference as an object
281    #[inline]
282    pub fn as_object(&self) -> &Object<'js> {
283        &self.0
284    }
285
286    /// Convert into an object
287    #[inline]
288    pub fn into_object(self) -> Object<'js> {
289        self.0
290    }
291
292    /// Convert from an object
293    pub fn from_object(object: Object<'js>) -> Option<Self> {
294        if Self::get_raw(&object.0).is_some() {
295            Some(Self(object))
296        } else {
297            None
298        }
299    }
300
301    /// Returns a structure with data about the raw buffer which this object contains.
302    ///
303    /// Returns None if the buffer was already used.
304    pub fn as_raw(&self) -> Option<RawArrayBuffer> {
305        Self::get_raw(self.as_value())
306    }
307
308    pub(crate) fn get_raw(val: &Value<'js>) -> Option<RawArrayBuffer> {
309        let ctx = val.ctx();
310        let val = val.as_js_value();
311        let mut size = MaybeUninit::<qjs::size_t>::uninit();
312        let ptr = unsafe { qjs::JS_GetArrayBuffer(ctx.as_ptr(), size.as_mut_ptr(), val) };
313
314        if let Some(ptr) = NonNull::new(ptr) {
315            let len = unsafe { size.assume_init() }
316                .try_into()
317                .expect(qjs::SIZE_T_ERROR);
318            Some(RawArrayBuffer { len, ptr })
319        } else {
320            None
321        }
322    }
323}
324
325impl<'js, T: TypedArrayItem> AsRef<[T]> for ArrayBuffer<'js> {
326    fn as_ref(&self) -> &[T] {
327        self.as_slice().expect("ArrayBuffer was detached")
328    }
329}
330
331impl<'js> Deref for ArrayBuffer<'js> {
332    type Target = Object<'js>;
333
334    fn deref(&self) -> &Self::Target {
335        self.as_object()
336    }
337}
338
339impl<'js> AsRef<Object<'js>> for ArrayBuffer<'js> {
340    fn as_ref(&self) -> &Object<'js> {
341        self.as_object()
342    }
343}
344
345impl<'js> AsRef<Value<'js>> for ArrayBuffer<'js> {
346    fn as_ref(&self) -> &Value<'js> {
347        self.as_value()
348    }
349}
350
351impl<'js> FromJs<'js> for ArrayBuffer<'js> {
352    fn from_js(_: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
353        let ty_name = value.type_name();
354        if let Some(v) = Self::from_value(value) {
355            Ok(v)
356        } else {
357            Err(Error::new_from_js(ty_name, "ArrayBuffer"))
358        }
359    }
360}
361
362impl<'js> IntoJs<'js> for ArrayBuffer<'js> {
363    fn into_js(self, _: &Ctx<'js>) -> Result<Value<'js>> {
364        Ok(self.into_value())
365    }
366}
367
368impl<'js> Object<'js> {
369    /// Returns whether the object is an instance of [`ArrayBuffer`].
370    pub fn is_array_buffer(&self) -> bool {
371        ArrayBuffer::get_raw(&self.0).is_some()
372    }
373
374    /// Interpret as [`ArrayBuffer`]
375    ///
376    /// # Safety
377    /// You should be sure that the object actually is the required type.
378    pub unsafe fn ref_array_buffer(&self) -> &ArrayBuffer {
379        mem::transmute(self)
380    }
381
382    /// Turn the object into an array buffer if the object is an instance of [`ArrayBuffer`].
383    pub fn as_array_buffer(&self) -> Option<&ArrayBuffer> {
384        self.is_array_buffer()
385            .then_some(unsafe { self.ref_array_buffer() })
386    }
387}
388
389#[cfg(test)]
390mod test {
391    use crate::*;
392    use alloc::sync::Arc;
393
394    #[test]
395    fn from_javascript_i8() {
396        test_with(|ctx| {
397            let val: ArrayBuffer = ctx
398                .eval(
399                    r#"
400                        new Int8Array([0, -5, 1, 11]).buffer
401                    "#,
402                )
403                .unwrap();
404            assert_eq!(val.len(), 4);
405            assert_eq!(val.as_ref() as &[i8], &[0i8, -5, 1, 11]);
406        });
407    }
408
409    #[test]
410    fn into_javascript_i8() {
411        test_with(|ctx| {
412            let val = ArrayBuffer::new(ctx.clone(), [-1i8, 0, 22, 5]).unwrap();
413            ctx.globals().set("a", val).unwrap();
414            let res: i8 = ctx
415                .eval(
416                    r#"
417                        let v = new Int8Array(a);
418                        v.length != 4 ? 1 :
419                        v[0] != -1 ? 2 :
420                        v[1] != 0 ? 3 :
421                        v[2] != 22 ? 4 :
422                        v[3] != 5 ? 5 :
423                        0
424                    "#,
425                )
426                .unwrap();
427            assert_eq!(res, 0);
428        })
429    }
430
431    #[test]
432    fn from_javascript_f32() {
433        test_with(|ctx| {
434            let val: ArrayBuffer = ctx
435                .eval(
436                    r#"
437                        new Float32Array([0.5, -5.25, 123.125]).buffer
438                    "#,
439                )
440                .unwrap();
441            assert_eq!(val.len(), 12);
442            assert_eq!(val.as_ref() as &[f32], &[0.5f32, -5.25, 123.125]);
443        });
444    }
445
446    #[test]
447    fn into_javascript_f32() {
448        test_with(|ctx| {
449            let val = ArrayBuffer::new(ctx.clone(), [-1.5f32, 0.0, 2.25]).unwrap();
450            ctx.globals().set("a", val).unwrap();
451            let res: i8 = ctx
452                .eval(
453                    r#"
454                        let v = new Float32Array(a);
455                        a.byteLength != 12 ? 1 :
456                        v.length != 3 ? 2 :
457                        v[0] != -1.5 ? 3 :
458                        v[1] != 0 ? 4 :
459                        v[2] != 2.25 ? 5 :
460                        0
461                    "#,
462                )
463                .unwrap();
464            assert_eq!(res, 0);
465        })
466    }
467
468    #[test]
469    fn as_bytes() {
470        test_with(|ctx| {
471            let val: ArrayBuffer = ctx
472                .eval(
473                    r#"
474                        new Uint32Array([0xCAFEDEAD,0xFEEDBEAD]).buffer
475                    "#,
476                )
477                .unwrap();
478            let mut res = [0; 8];
479            let bytes_0 = 0xCAFEDEADu32.to_ne_bytes();
480            res[..4].copy_from_slice(&bytes_0);
481            let bytes_1 = 0xFEEDBEADu32.to_ne_bytes();
482            res[4..].copy_from_slice(&bytes_1);
483
484            assert_eq!(val.as_bytes().unwrap(), &res)
485        });
486    }
487
488    #[test]
489    fn from_source_external_buffer() {
490        use core::sync::atomic::{AtomicBool, Ordering};
491
492        static DROPPED: AtomicBool = AtomicBool::new(false);
493
494        struct Tracker(alloc::boxed::Box<[u8]>);
495        unsafe impl ArrayBufferSource for Tracker {
496            fn as_ptr(&self) -> *mut u8 {
497                self.0.as_ptr()
498            }
499            fn len(&self) -> usize {
500                self.0.len()
501            }
502        }
503        impl Drop for Tracker {
504            fn drop(&mut self) {
505                DROPPED.store(true, Ordering::SeqCst);
506            }
507        }
508
509        let rt = crate::Runtime::new().unwrap();
510        let c = crate::Context::full(&rt).unwrap();
511        c.with(|ctx| {
512            let src = Tracker(alloc::vec![1u8, 2, 3, 4].into_boxed_slice());
513            let ab = ArrayBuffer::from_source(ctx.clone(), src).unwrap();
514            assert_eq!(ab.len(), 4);
515            assert_eq!(ab.as_bytes().unwrap(), &[1, 2, 3, 4]);
516        });
517        rt.run_gc();
518        assert!(DROPPED.load(Ordering::SeqCst));
519    }
520
521    #[test]
522    fn from_source_error_invokes_drop_fn() {
523        use core::sync::atomic::{AtomicBool, Ordering};
524
525        static DROPPED: AtomicBool = AtomicBool::new(false);
526
527        // A source that lies about its length to force construction failure,
528        // and signals via `Drop` that the source was released exactly once.
529        struct BadSource(#[allow(dead_code)] alloc::boxed::Box<[u8]>);
530        unsafe impl ArrayBufferSource for BadSource {
531            fn as_ptr(&self) -> *mut u8 {
532                self.0.as_ptr()
533            }
534            fn len(&self) -> usize {
535                i64::MAX as usize
536            }
537        }
538        impl Drop for BadSource {
539            fn drop(&mut self) {
540                DROPPED.store(true, Ordering::SeqCst);
541            }
542        }
543
544        let rt = crate::Runtime::new().unwrap();
545        let c = crate::Context::full(&rt).unwrap();
546        c.with(|ctx| {
547            let src = BadSource(alloc::vec![1u8, 2, 3, 4].into_boxed_slice());
548            let err = ArrayBuffer::from_source(ctx.clone(), src);
549            assert!(err.is_err());
550        });
551        assert!(DROPPED.load(Ordering::SeqCst));
552    }
553
554    #[test]
555    fn from_source_immutable_arc_slices() {
556        struct ArcSlice {
557            arc: Arc<Vec<u8>>,
558            offset: usize,
559            len: usize,
560        }
561        unsafe impl ArrayBufferSource for ArcSlice {
562            fn as_ptr(&self) -> *mut u8 {
563                unsafe { self.arc.as_ptr().add(self.offset) }
564            }
565            fn len(&self) -> usize {
566                self.len
567            }
568        }
569
570        let buf: Arc<Vec<u8>> = Arc::new((0u8..16).collect());
571        let weak = Arc::downgrade(&buf);
572
573        let rt = crate::Runtime::new().unwrap();
574        let c = crate::Context::full(&rt).unwrap();
575        c.with(|ctx| {
576            let mk = |offset: usize, len: usize| -> ArrayBuffer<'_> {
577                ArrayBuffer::from_source_immutable(
578                    ctx.clone(),
579                    ArcSlice {
580                        arc: buf.clone(),
581                        offset,
582                        len,
583                    },
584                )
585                .unwrap()
586            };
587            let full = mk(0, 16);
588            let head = mk(0, 4);
589            let tail = mk(12, 4);
590            let middle = mk(4, 8);
591
592            assert_eq!(full.as_bytes().unwrap(), (0u8..16).collect::<Vec<_>>());
593            assert_eq!(head.as_bytes().unwrap(), &[0, 1, 2, 3]);
594            assert_eq!(tail.as_bytes().unwrap(), &[12, 13, 14, 15]);
595            assert_eq!(middle.as_bytes().unwrap(), (4u8..12).collect::<Vec<_>>());
596            assert_eq!(Arc::strong_count(&buf), 5);
597
598            ctx.globals().set("buf", full).unwrap();
599
600            let after: u8 = ctx
601                .eval::<u8, _>(
602                    r#"
603                        const arr = new Uint8Array(buf);
604                        arr[0] = 99;
605                        arr[0];
606                    "#,
607                )
608                .unwrap();
609            assert_eq!(after, 0, "immutable ArrayBuffer must not accept writes");
610
611            let writer = ctx.eval::<(), _>(
612                r#"
613                    "use strict";
614                    new DataView(buf).setUint8(0, 99);
615                "#,
616            );
617            assert!(
618                writer.is_err(),
619                "DataView write on immutable buffer must throw"
620            );
621        });
622
623        drop(buf);
624        rt.run_gc();
625        drop(c);
626        drop(rt);
627        assert!(weak.upgrade().is_none());
628    }
629
630    #[test]
631    fn from_source_vec() {
632        let rt = crate::Runtime::new().unwrap();
633        let c = crate::Context::full(&rt).unwrap();
634        c.with(|ctx| {
635            let ab = ArrayBuffer::from_source(ctx.clone(), alloc::vec![1u8, 2, 3, 4]).unwrap();
636            assert_eq!(ab.as_bytes().unwrap(), &[1, 2, 3, 4]);
637        });
638    }
639
640    #[test]
641    fn from_source_immutable_arc() {
642        let arc: Arc<[u8]> = Arc::from((0u8..8).collect::<Vec<_>>().into_boxed_slice());
643        let weak = Arc::downgrade(&arc);
644        assert_eq!(Arc::strong_count(&arc), 1);
645
646        let rt = crate::Runtime::new().unwrap();
647        let c = crate::Context::full(&rt).unwrap();
648
649        // Case 1: JS drops first while Rust keeps its Arc clone.
650        c.with(|ctx| {
651            let _ab = ArrayBuffer::from_source_immutable(ctx.clone(), arc.clone()).unwrap();
652            assert_eq!(Arc::strong_count(&arc), 2, "Arc cloned into drop closure");
653            // _ab drops at end of block; shim runs; closure drops its Arc clone.
654        });
655        assert_eq!(
656            Arc::strong_count(&arc),
657            1,
658            "drop closure must release its Arc clone when ArrayBuffer is freed"
659        );
660        assert!(
661            weak.upgrade().is_some(),
662            "allocation must stay alive while Rust still holds the Arc"
663        );
664
665        // Case 2: Rust drops first, JS keeps the buffer (stored in globals).
666        c.with(|ctx| {
667            let ab = ArrayBuffer::from_source_immutable(ctx.clone(), arc.clone()).unwrap();
668            ctx.globals().set("buf", ab).unwrap();
669            assert_eq!(Arc::strong_count(&arc), 2);
670        });
671        drop(arc);
672        assert!(
673            weak.upgrade().is_some(),
674            "allocation must stay alive: JS still holds the buffer via the Arc clone in the closure"
675        );
676        assert_eq!(
677            weak.strong_count(),
678            1,
679            "only the closure's Arc clone should remain"
680        );
681
682        // Drop the context: JS buffer is freed, shim runs, Arc clone released.
683        drop(c);
684        drop(rt);
685        assert!(
686            weak.upgrade().is_none(),
687            "allocation must be freed after both Rust and JS release their handles"
688        );
689    }
690
691    #[cfg(feature = "bytes")]
692    #[test]
693    fn from_source_immutable_bytes() {
694        let data: bytes::Bytes = (0u8..8).collect::<Vec<_>>().into();
695        let rt = crate::Runtime::new().unwrap();
696        let c = crate::Context::full(&rt).unwrap();
697        c.with(|ctx| {
698            let ab = ArrayBuffer::from_source_immutable(ctx.clone(), data.clone()).unwrap();
699            assert_eq!(ab.as_bytes().unwrap(), (0u8..8).collect::<Vec<_>>());
700        });
701    }
702}