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