Skip to main content

mozjs/
typedarray.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5//! High-level, safe bindings for JS typed array APIs. Allows creating new
6//! typed arrays or wrapping existing JS reflectors, and prevents reinterpreting
7//! existing buffers as different types except in well-defined cases.
8
9use std::ptr::NonNull;
10
11use crate::context::NoGC;
12use crate::conversions::ConversionResult;
13use crate::conversions::FromJSValConvertible;
14use crate::conversions::ToJSValConvertible;
15use crate::glue::GetFloat32ArrayLengthAndData;
16use crate::glue::GetFloat64ArrayLengthAndData;
17use crate::glue::GetInt16ArrayLengthAndData;
18use crate::glue::GetInt32ArrayLengthAndData;
19use crate::glue::GetInt8ArrayLengthAndData;
20use crate::glue::GetUint16ArrayLengthAndData;
21use crate::glue::GetUint32ArrayLengthAndData;
22use crate::glue::GetUint8ArrayLengthAndData;
23use crate::glue::GetUint8ClampedArrayLengthAndData;
24use crate::jsapi::GetArrayBufferData;
25use crate::jsapi::GetArrayBufferLengthAndData;
26use crate::jsapi::GetArrayBufferViewLengthAndData;
27use crate::jsapi::Heap;
28use crate::jsapi::JSContext;
29use crate::jsapi::JSObject;
30use crate::jsapi::JSTracer;
31use crate::jsapi::JS_GetArrayBufferViewType;
32use crate::jsapi::JS_GetFloat32ArrayData;
33use crate::jsapi::JS_GetFloat64ArrayData;
34use crate::jsapi::JS_GetInt16ArrayData;
35use crate::jsapi::JS_GetInt32ArrayData;
36use crate::jsapi::JS_GetInt8ArrayData;
37use crate::jsapi::JS_GetTypedArraySharedness;
38use crate::jsapi::JS_GetUint16ArrayData;
39use crate::jsapi::JS_GetUint32ArrayData;
40use crate::jsapi::JS_GetUint8ArrayData;
41use crate::jsapi::JS_GetUint8ClampedArrayData;
42use crate::jsapi::JS_NewFloat32Array;
43use crate::jsapi::JS_NewFloat64Array;
44use crate::jsapi::JS_NewInt16Array;
45use crate::jsapi::JS_NewInt32Array;
46use crate::jsapi::JS_NewInt8Array;
47use crate::jsapi::JS_NewUint16Array;
48use crate::jsapi::JS_NewUint32Array;
49use crate::jsapi::JS_NewUint8Array;
50use crate::jsapi::JS_NewUint8ClampedArray;
51use crate::jsapi::NewArrayBuffer;
52use crate::jsapi::Type;
53use crate::jsapi::UnwrapArrayBuffer;
54use crate::jsapi::UnwrapArrayBufferView;
55use crate::jsapi::UnwrapFloat32Array;
56use crate::jsapi::UnwrapFloat64Array;
57use crate::jsapi::UnwrapInt16Array;
58use crate::jsapi::UnwrapInt32Array;
59use crate::jsapi::UnwrapInt8Array;
60use crate::jsapi::UnwrapUint16Array;
61use crate::jsapi::UnwrapUint32Array;
62use crate::jsapi::UnwrapUint8Array;
63use crate::jsapi::UnwrapUint8ClampedArray;
64use crate::rust::CustomTrace;
65use crate::rust::{HandleValue, MutableHandleObject, MutableHandleValue};
66
67use std::cell::Cell;
68use std::ptr;
69
70/// Trait that specifies how pointers to wrapped objects are stored. It supports
71/// two variants, one with bare pointer (to be rooted on stack using
72/// CustomAutoRooter) and wrapped in a Box<Heap<T>>, which can be stored in a
73/// heap-allocated structure, to be rooted with JSTraceable-implementing tracers
74/// (currently implemented in Servo).
75pub trait JSObjectStorage {
76    fn as_raw(&self) -> *mut JSObject;
77    fn from_raw(raw: *mut JSObject) -> Self;
78}
79
80impl JSObjectStorage for *mut JSObject {
81    fn as_raw(&self) -> *mut JSObject {
82        *self
83    }
84    fn from_raw(raw: *mut JSObject) -> Self {
85        raw
86    }
87}
88
89impl JSObjectStorage for Box<Heap<*mut JSObject>> {
90    fn as_raw(&self) -> *mut JSObject {
91        self.get()
92    }
93    #[cfg_attr(feature = "crown", expect(crown::unrooted_must_root))]
94    fn from_raw(raw: *mut JSObject) -> Self {
95        let boxed = Box::new(Heap::default());
96        boxed.set(raw);
97        boxed
98    }
99}
100
101impl<T: TypedArrayElement, S: JSObjectStorage> FromJSValConvertible for TypedArray<T, S> {
102    type Config = ();
103
104    fn safe_from_jsval(
105        _cx: &mut crate::context::JSContext,
106        value: HandleValue,
107        _option: (),
108    ) -> Result<ConversionResult<Self>, ()> {
109        if value.get().is_object() {
110            Self::from(value.get().to_object()).map(ConversionResult::Success)
111        } else {
112            Err(())
113        }
114    }
115}
116
117impl<T: TypedArrayElement, S: JSObjectStorage> ToJSValConvertible for TypedArray<T, S> {
118    #[inline]
119    unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
120        ToJSValConvertible::to_jsval(&self.object.as_raw(), cx, rval);
121    }
122}
123
124pub enum CreateWith<'a, T: 'a> {
125    Length(usize),
126    Slice(&'a [T]),
127}
128
129#[derive(Clone, Copy)]
130enum ArrayData<T> {
131    NotYetComputed,
132    Detached,
133    Computed(NonNull<[T]>),
134}
135
136/// A typed array wrapper.
137pub struct TypedArray<T: TypedArrayElement, S: JSObjectStorage> {
138    object: S,
139    computed: Cell<ArrayData<T::Element>>,
140}
141
142unsafe impl<T> CustomTrace for TypedArray<T, *mut JSObject>
143where
144    T: TypedArrayElement,
145{
146    fn trace(&self, trc: *mut JSTracer) {
147        self.object.trace(trc);
148    }
149}
150
151impl<T: TypedArrayElement, S: JSObjectStorage> TypedArray<T, S> {
152    /// Create a typed array representation that wraps an existing JS reflector.
153    /// This operation will fail if attempted on a JS object that does not match
154    /// the expected typed array details.
155    pub fn from(object: *mut JSObject) -> Result<Self, ()> {
156        if object.is_null() {
157            return Err(());
158        }
159        unsafe {
160            let unwrapped = T::unwrap_array(object);
161            if unwrapped.is_null() {
162                return Err(());
163            }
164
165            Ok(TypedArray {
166                object: S::from_raw(unwrapped),
167                computed: Cell::new(ArrayData::NotYetComputed),
168            })
169        }
170    }
171
172    fn data(&self) -> Option<NonNull<[T::Element]>> {
173        if let ArrayData::Computed(data) = self.computed.get() {
174            return Some(data);
175        }
176
177        let data = unsafe { T::length_and_data(self.object.as_raw()) };
178        self.computed.set(if let Some(data) = data {
179            ArrayData::Computed(data)
180        } else {
181            ArrayData::Detached
182        });
183        data
184    }
185
186    /// Returns the number of elements in the underlying typed array.
187    pub fn len(&self) -> usize {
188        self.data().map_or(0, |data| data.len())
189    }
190
191    /// # Unsafety
192    ///
193    /// Returned wrapped pointer to the underlying `JSObject` is meant to be
194    /// read-only, modifying it can lead to Undefined Behaviour and violation
195    /// of TypedArray API guarantees.
196    ///
197    /// Practically, this exists only to implement `JSTraceable` trait in Servo
198    /// for Box<Heap<*mut JSObject>> variant.
199    pub unsafe fn underlying_object(&self) -> &S {
200        &self.object
201    }
202
203    /// Retrieves an owned data that's represented by the typed array.
204    /// Returns None if the underlying buffer is detached.
205    #[allow(deprecated)]
206    pub fn to_vec(&self) -> Option<Vec<T::Element>>
207    where
208        T::Element: Clone,
209    {
210        // This is safe, because we immediately copy from the underlying buffer
211        // to an owned collection. Otherwise, one needs to be careful, since
212        // the underlying buffer can easily invalidated when transferred with
213        // postMessage to another thread (To remedy that, we shouldn't
214        // execute any JS code between getting the data pointer and using it).
215        unsafe { self.as_slice().map(|slice| slice.to_vec()) }
216    }
217
218    /// # Unsafety
219    ///
220    /// The returned slice can be invalidated if the underlying typed array
221    /// is neutered.
222    ///
223    /// # Panics
224    ///
225    /// Panics if the underlying data points to a nullptr.
226    #[deprecated = "use as_slice_safe instead"]
227    pub unsafe fn as_slice(&self) -> Option<&[T::Element]> {
228        self.data().map(|data| data.as_ref())
229    }
230
231    /// Returns None if the underlying array buffer is detached.
232    /// Otherwise, returns Some with the slice data.
233    pub fn as_slice_safe<'a>(&self, _no_gc: &'a NoGC) -> Option<&'a [T::Element]> {
234        // SAFETY: The slice can only be invalidated by invoking JS engine
235        //         behaviour that detaches the underlying typed array.
236        //         The slice's lifetime is bounded by the provided NoGC token,
237        //         which prevents any JS engine interaction.
238        self.data().map(|data| unsafe { data.as_ref() })
239    }
240
241    /// # Unsafety
242    ///
243    /// The returned slice can be invalidated if the underlying typed array
244    /// is neutered.
245    ///
246    /// The underlying `JSObject` can be aliased, which can lead to
247    /// Undefined Behavior due to mutable aliasing.
248    ///
249    /// # Panics
250    ///
251    /// Panics if the underlying data points to a nullptr.
252    #[deprecated = "use as_mut_slice_safe instead"]
253    pub unsafe fn as_mut_slice(&mut self) -> Option<&mut [T::Element]> {
254        self.data().map(|mut data| data.as_mut())
255    }
256
257    pub fn as_mut_slice_safe<'a>(&mut self, _no_gc: &'a NoGC) -> Option<&'a mut [T::Element]> {
258        // SAFETY: The slice can only be invalidated by invoking JS engine
259        //         behaviour that detaches the underlying typed array.
260        //         The slice's lifetime is bounded by the provided NoGC token,
261        //         which prevents any JS engine interaction.
262        self.data().map(|mut data| unsafe { data.as_mut() })
263    }
264
265    /// Return a boolean flag which denotes whether the underlying buffer
266    /// is a SharedArrayBuffer.
267    pub fn is_shared(&self) -> bool {
268        unsafe { JS_GetTypedArraySharedness(self.object.as_raw()) }
269    }
270}
271
272impl<T: TypedArrayElementCreator + TypedArrayElement, S: JSObjectStorage> TypedArray<T, S> {
273    /// Create a new JS typed array, optionally providing initial data that will
274    /// be copied into the newly-allocated buffer. Returns the new JS reflector.
275    pub unsafe fn create(
276        cx: *mut JSContext,
277        with: CreateWith<T::Element>,
278        mut result: MutableHandleObject,
279    ) -> Result<(), ()> {
280        let length = match with {
281            CreateWith::Length(len) => len,
282            CreateWith::Slice(slice) => slice.len(),
283        };
284
285        result.set(T::create_new(cx, length));
286        if result.get().is_null() {
287            return Err(());
288        }
289
290        if let CreateWith::Slice(data) = with {
291            Self::update_raw(data, result.get());
292        }
293
294        Ok(())
295    }
296
297    ///  Update an existed JS typed array
298    pub fn update(&mut self, data: &[T::Element]) {
299        unsafe {
300            Self::update_raw(data, self.object.as_raw());
301        }
302    }
303
304    unsafe fn update_raw(data: &[T::Element], result: *mut JSObject) {
305        let Some(mut buffer) = T::length_and_data(result) else {
306            return;
307        };
308        assert!(data.len() <= buffer.len());
309        ptr::copy_nonoverlapping(
310            data.as_ptr(),
311            buffer.as_mut().as_mut_ptr(), /* as *mut T::Element*/
312            data.len(),
313        );
314    }
315}
316
317/// Internal trait used to associate an element type with an underlying representation
318/// and various functions required to manipulate typed arrays of that element type.
319pub trait TypedArrayElement {
320    /// Underlying primitive representation of this element type.
321    type Element: Copy;
322    /// Unwrap a typed array JS reflector for this element type.
323    unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject;
324    /// Retrieve the length and data of a typed array's buffer for this element type.
325    unsafe fn length_and_data(obj: *mut JSObject) -> Option<NonNull<[Self::Element]>>;
326}
327
328/// Internal trait for creating new typed arrays.
329pub trait TypedArrayElementCreator: TypedArrayElement {
330    /// Create a new typed array.
331    unsafe fn create_new(cx: *mut JSContext, length: usize) -> *mut JSObject;
332    /// Get the data.
333    unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element;
334}
335
336macro_rules! typed_array_element {
337    ($t: ident,
338     $element: ty,
339     $unwrap: ident,
340     $length_and_data: ident) => {
341        /// A kind of typed array.
342        pub struct $t;
343
344        impl TypedArrayElement for $t {
345            type Element = $element;
346            unsafe fn unwrap_array(obj: *mut JSObject) -> *mut JSObject {
347                $unwrap(obj)
348            }
349
350            unsafe fn length_and_data(obj: *mut JSObject) -> Option<NonNull<[Self::Element]>> {
351                let mut len = 0;
352                let mut shared = false;
353                let mut data = ptr::null_mut();
354                $length_and_data(obj, &mut len, &mut shared, &mut data);
355                assert!(!shared);
356                NonNull::new(data).map(|data| NonNull::slice_from_raw_parts(data, len))
357            }
358        }
359    };
360
361    ($t: ident,
362     $element: ty,
363     $unwrap: ident,
364     $length_and_data: ident,
365     $create_new: ident,
366     $get_data: ident) => {
367        typed_array_element!($t, $element, $unwrap, $length_and_data);
368
369        impl TypedArrayElementCreator for $t {
370            unsafe fn create_new(cx: *mut JSContext, length: usize) -> *mut JSObject {
371                $create_new(cx, length)
372            }
373
374            unsafe fn get_data(obj: *mut JSObject) -> *mut Self::Element {
375                let mut shared = false;
376                let data = $get_data(obj, &mut shared, ptr::null_mut());
377                assert!(!shared);
378                data
379            }
380        }
381    };
382}
383
384typed_array_element!(
385    Uint8,
386    u8,
387    UnwrapUint8Array,
388    GetUint8ArrayLengthAndData,
389    JS_NewUint8Array,
390    JS_GetUint8ArrayData
391);
392typed_array_element!(
393    Uint16,
394    u16,
395    UnwrapUint16Array,
396    GetUint16ArrayLengthAndData,
397    JS_NewUint16Array,
398    JS_GetUint16ArrayData
399);
400typed_array_element!(
401    Uint32,
402    u32,
403    UnwrapUint32Array,
404    GetUint32ArrayLengthAndData,
405    JS_NewUint32Array,
406    JS_GetUint32ArrayData
407);
408typed_array_element!(
409    Int8,
410    i8,
411    UnwrapInt8Array,
412    GetInt8ArrayLengthAndData,
413    JS_NewInt8Array,
414    JS_GetInt8ArrayData
415);
416typed_array_element!(
417    Int16,
418    i16,
419    UnwrapInt16Array,
420    GetInt16ArrayLengthAndData,
421    JS_NewInt16Array,
422    JS_GetInt16ArrayData
423);
424typed_array_element!(
425    Int32,
426    i32,
427    UnwrapInt32Array,
428    GetInt32ArrayLengthAndData,
429    JS_NewInt32Array,
430    JS_GetInt32ArrayData
431);
432typed_array_element!(
433    Float32,
434    f32,
435    UnwrapFloat32Array,
436    GetFloat32ArrayLengthAndData,
437    JS_NewFloat32Array,
438    JS_GetFloat32ArrayData
439);
440typed_array_element!(
441    Float64,
442    f64,
443    UnwrapFloat64Array,
444    GetFloat64ArrayLengthAndData,
445    JS_NewFloat64Array,
446    JS_GetFloat64ArrayData
447);
448typed_array_element!(
449    ClampedU8,
450    u8,
451    UnwrapUint8ClampedArray,
452    GetUint8ClampedArrayLengthAndData,
453    JS_NewUint8ClampedArray,
454    JS_GetUint8ClampedArrayData
455);
456typed_array_element!(
457    ArrayBufferU8,
458    u8,
459    UnwrapArrayBuffer,
460    GetArrayBufferLengthAndData,
461    NewArrayBuffer,
462    GetArrayBufferData
463);
464typed_array_element!(
465    ArrayBufferViewU8,
466    u8,
467    UnwrapArrayBufferView,
468    GetArrayBufferViewLengthAndData
469);
470
471// Default type aliases, uses bare pointer by default, since stack lifetime
472// should be the most common scenario
473macro_rules! array_alias {
474    ($arr: ident, $heap_arr: ident, $elem: ty) => {
475        pub type $arr = TypedArray<$elem, *mut JSObject>;
476        pub type $heap_arr = TypedArray<$elem, Box<Heap<*mut JSObject>>>;
477    };
478}
479
480array_alias!(Uint8ClampedArray, HeapUint8ClampedArray, ClampedU8);
481array_alias!(Uint8Array, HeapUint8Array, Uint8);
482array_alias!(Int8Array, HeapInt8Array, Int8);
483array_alias!(Uint16Array, HeapUint16Array, Uint16);
484array_alias!(Int16Array, HeapInt16Array, Int16);
485array_alias!(Uint32Array, HeapUint32Array, Uint32);
486array_alias!(Int32Array, HeapInt32Array, Int32);
487array_alias!(Float32Array, HeapFloat32Array, Float32);
488array_alias!(Float64Array, HeapFloat64Array, Float64);
489array_alias!(ArrayBuffer, HeapArrayBuffer, ArrayBufferU8);
490array_alias!(ArrayBufferView, HeapArrayBufferView, ArrayBufferViewU8);
491
492impl<S: JSObjectStorage> TypedArray<ArrayBufferViewU8, S> {
493    pub fn get_array_type(&self) -> Type {
494        unsafe { JS_GetArrayBufferViewType(self.object.as_raw()) }
495    }
496}
497
498#[macro_export]
499macro_rules! typedarray {
500    (&in($cx:expr) $($t:tt)*) => {
501        typedarray!(in(unsafe {$cx.raw_cx_no_gc()}) $($t)*);
502    };
503    (in($cx:expr) let $name:ident : $ty:ident = $init:expr) => {
504        let mut __array =
505            $crate::typedarray::$ty::from($init).map($crate::rust::CustomAutoRooter::new);
506
507        let $name = __array.as_mut().map(|ok| ok.root($cx));
508    };
509    (in($cx:expr) let mut $name:ident : $ty:ident = $init:expr) => {
510        let mut __array =
511            $crate::typedarray::$ty::from($init).map($crate::rust::CustomAutoRooter::new);
512
513        let mut $name = __array.as_mut().map(|ok| ok.root($cx));
514    };
515}