/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Typed array, ArrayBuffer, and DataView creation, predicate, and accessor * functions. */ #ifndef js_experimental_TypedData_h #define js_experimental_TypedData_h #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH #include "mozilla/Casting.h" // mozilla::AssertedCast #include // size_t #include // {,u}int8_t, {,u}int16_t, {,u}int32_t #include "jstypes.h" // JS_PUBLIC_API #include "js/Object.h" // JS::GetClass, JS::GetReservedSlot, JS::GetMaybePtrFromReservedSlot #include "js/RootingAPI.h" // JS::Handle, JS_DECLARE_IS_HEAP_CONSTRUCTIBLE_TYPE #include "js/ScalarType.h" // JS::Scalar::Type #include "js/Wrapper.h" // js::CheckedUnwrapStatic struct JSClass; class JS_PUBLIC_API JSObject; namespace JS { class JS_PUBLIC_API AutoRequireNoGC; } // namespace JS // JS_FOR_EACH_TYPED_ARRAY(MACRO) expands MACRO once for each specific // typed array subtype (Int8Array, Float64Array, ...), passing arguments // as MACRO(ExternalT, NativeT, Name) where // // ExternalT - externally-exposed element type (eg uint8_t) // // NativeT - element type used for the implementation (eg js::uint8_clamped_t) // Note that this type is not exposed publicly. Internal files need to // #include to see it. // // Name - a name usable as both a JS::Scalar::Type value (eg // JS::Scalar::Uint8Clamped) or the stem of a full typed array name (eg // Uint8ClampedArray) // #define JS_FOR_EACH_TYPED_ARRAY(MACRO) \ MACRO(int8_t, int8_t, Int8) \ MACRO(uint8_t, uint8_t, Uint8) \ MACRO(int16_t, int16_t, Int16) \ MACRO(uint16_t, uint16_t, Uint16) \ MACRO(int32_t, int32_t, Int32) \ MACRO(uint32_t, uint32_t, Uint32) \ MACRO(float, float, Float32) \ MACRO(double, double, Float64) \ MACRO(uint8_t, js::uint8_clamped, Uint8Clamped) \ MACRO(int64_t, int64_t, BigInt64) \ MACRO(uint64_t, uint64_t, BigUint64) /* * JS_New(type)Array: * * Create a new typed array with nelements elements. * * These functions (except the WithBuffer variants) fill in the array with * zeros. * * JS_New(type)ArrayFromArray: * * Create a new typed array and copy in values from the given object. The * object is used as if it were an array; that is, the new array (if * successfully created) will have length given by array.length, and its * elements will be those specified by array[0], array[1], and so on, after * conversion to the typed array element type. * * JS_New(type)ArrayWithBuffer: * * Create a new typed array using the given ArrayBuffer or * SharedArrayBuffer for storage. The length value is optional; if -1 * is passed, enough elements to use up the remainder of the byte * array is used as the default value. */ #define DECLARE_TYPED_ARRAY_CREATION_API(ExternalType, NativeType, Name) \ extern JS_PUBLIC_API JSObject* JS_New##Name##Array(JSContext* cx, \ size_t nelements); \ extern JS_PUBLIC_API JSObject* JS_New##Name##ArrayFromArray( \ JSContext* cx, JS::Handle array); \ extern JS_PUBLIC_API JSObject* JS_New##Name##ArrayWithBuffer( \ JSContext* cx, JS::Handle arrayBuffer, size_t byteOffset, \ int64_t length); JS_FOR_EACH_TYPED_ARRAY(DECLARE_TYPED_ARRAY_CREATION_API) #undef DECLARE_TYPED_ARRAY_CREATION_API /** * Check whether obj supports JS_GetTypedArray* APIs. Note that this may return * false if a security wrapper is encountered that denies the unwrapping. If * this test or one of the JS_Is*Array tests succeeds, then it is safe to call * the various accessor JSAPI calls defined below. */ extern JS_PUBLIC_API bool JS_IsTypedArrayObject(JSObject* obj); /** * Check whether obj supports JS_GetArrayBufferView* APIs. Note that this may * return false if a security wrapper is encountered that denies the * unwrapping. If this test or one of the more specific tests succeeds, then it * is safe to call the various ArrayBufferView accessor JSAPI calls defined * below. */ extern JS_PUBLIC_API bool JS_IsArrayBufferViewObject(JSObject* obj); /** * Return the isShared flag of a typed array, which denotes whether * the underlying buffer is a SharedArrayBuffer. * * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow * be known that it would pass such a test: it is a typed array or a wrapper of * a typed array, and the unwrapping will succeed. */ extern JS_PUBLIC_API bool JS_GetTypedArraySharedness(JSObject* obj); /* * Test for specific typed array types (ArrayBufferView subtypes) and return * the unwrapped object if so, else nullptr. Never throws. */ namespace js { extern JS_PUBLIC_API JSObject* UnwrapArrayBufferView(JSObject* obj); extern JS_PUBLIC_API JSObject* UnwrapReadableStream(JSObject* obj); namespace detail { constexpr size_t TypedArrayLengthSlot = 1; constexpr size_t TypedArrayDataSlot = 3; } // namespace detail // This one isn't inlined because it's rather tricky (by dint of having to deal // with a dozen-plus classes and varying slot layouts. extern JS_PUBLIC_API void GetArrayBufferViewLengthAndData(JSObject* obj, size_t* length, bool* isSharedMemory, uint8_t** data); } // namespace js /* * JS_GetObjectAs(type)Array(JSObject* maybeWrapped, size_t* length, bool* * isSharedMemory, element_type** data) * * Unwrap Typed arrays all at once. Return nullptr without throwing if the * object cannot be viewed as the correct typed array, or the typed array * object on success, filling both outparameters. */ #define DECLARE_GET_OBJECT_AS(ExternalType, NativeType, Name) \ extern JS_PUBLIC_API JSObject* JS_GetObjectAs##Name##Array( \ JSObject* maybeWrapped, size_t* length, bool* isSharedMemory, \ ExternalType** data); JS_FOR_EACH_TYPED_ARRAY(DECLARE_GET_OBJECT_AS) #undef DECLARE_GET_OBJECT_AS extern JS_PUBLIC_API JSObject* JS_GetObjectAsArrayBufferView( JSObject* obj, size_t* length, bool* isSharedMemory, uint8_t** data); /* * Get the type of elements in a typed array, or MaxTypedArrayViewType if a * DataView. * * |obj| must have passed a JS_IsArrayBufferView/JS_Is*Array test, or somehow * be known that it would pass such a test: it is an ArrayBufferView or a * wrapper of an ArrayBufferView, and the unwrapping will succeed. */ extern JS_PUBLIC_API JS::Scalar::Type JS_GetArrayBufferViewType(JSObject* obj); /** * Return the number of elements in a typed array. * * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow * be known that it would pass such a test: it is a typed array or a wrapper of * a typed array, and the unwrapping will succeed. */ extern JS_PUBLIC_API size_t JS_GetTypedArrayLength(JSObject* obj); /** * Return the byte offset from the start of an ArrayBuffer to the start of a * typed array view. * * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow * be known that it would pass such a test: it is a typed array or a wrapper of * a typed array, and the unwrapping will succeed. */ extern JS_PUBLIC_API size_t JS_GetTypedArrayByteOffset(JSObject* obj); /** * Return the byte length of a typed array. * * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow * be known that it would pass such a test: it is a typed array or a wrapper of * a typed array, and the unwrapping will succeed. */ extern JS_PUBLIC_API size_t JS_GetTypedArrayByteLength(JSObject* obj); /** * More generic name for JS_GetTypedArrayByteLength to cover DataViews as well */ extern JS_PUBLIC_API size_t JS_GetArrayBufferViewByteLength(JSObject* obj); /** * More generic name for JS_GetTypedArrayByteOffset to cover DataViews as well */ extern JS_PUBLIC_API size_t JS_GetArrayBufferViewByteOffset(JSObject* obj); /** * Same as above, but for any kind of ArrayBufferView. Prefer the type-specific * versions when possible. */ extern JS_PUBLIC_API void* JS_GetArrayBufferViewData( JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&); /** * Return a "fixed" pointer (one that will not move during a GC) to the * ArrayBufferView's data. Note that this will not keep the object alive; the * holding object should be rooted or traced. If the view is storing the data * inline, this will copy the data to the provided buffer, returning nullptr if * bufSize is inadequate. * * Avoid using this unless necessary. JS_GetArrayBufferViewData is simpler and * more efficient because it requires the caller to ensure that a GC will not * occur and thus does not need to handle movable data. */ extern JS_PUBLIC_API uint8_t* JS_GetArrayBufferViewFixedData(JSObject* obj, uint8_t* buffer, size_t bufSize); /** * If the bufSize passed to JS_GetArrayBufferViewFixedData is at least this * many bytes, then any copied data is guaranteed to fit into the provided * buffer. */ extern JS_PUBLIC_API size_t JS_MaxMovableTypedArraySize(); /** * Return the ArrayBuffer or SharedArrayBuffer underlying an ArrayBufferView. * This may return a detached buffer. |obj| must be an object that would * return true for JS_IsArrayBufferViewObject(). */ extern JS_PUBLIC_API JSObject* JS_GetArrayBufferViewBuffer( JSContext* cx, JS::Handle obj, bool* isSharedMemory); /** * Create a new DataView using the given buffer for storage. The given buffer * must be an ArrayBuffer or SharedArrayBuffer (or a cross-compartment wrapper * of either type), and the offset and length must fit within the bounds of the * buffer. Currently, nullptr will be returned and an exception will be thrown * if these conditions do not hold, but do not depend on that behavior. */ JS_PUBLIC_API JSObject* JS_NewDataView(JSContext* cx, JS::Handle buffer, size_t byteOffset, size_t byteLength); namespace JS { /* * Returns whether the passed array buffer view is 'large': its byteLength >= 2 * GB. * * |obj| must pass a JS_IsArrayBufferViewObject test. */ JS_PUBLIC_API bool IsLargeArrayBufferView(JSObject* obj); namespace detail { // Map from eg Uint8Clamped -> uint8_t, Uint8 -> uint8_t, or Float64 -> // double. Used as the DataType within a JS::TypedArray specialization. template struct ExternalTypeOf {}; #define DEFINE_ELEMENT_TYPES(ExternalT, NativeT, Name) \ template <> \ struct ExternalTypeOf { \ using Type = ExternalT; \ }; JS_FOR_EACH_TYPED_ARRAY(DEFINE_ELEMENT_TYPES) #undef DEFINE_ELEMENT_TYPES template using ExternalTypeOf_t = typename ExternalTypeOf::Type; } // namespace detail // A class holding a JSObject referring to a buffer of data. Either an // ArrayBufferObject or some sort of ArrayBufferViewObject (see below). // Note that this will always hold an unwrapped object. class JS_PUBLIC_API ArrayBufferOrView { public: // Typed Arrays will set this to their specific element type. // Everything else just claims to expose things as uint8_t*. using DataType = uint8_t; protected: JSObject* obj; explicit ArrayBufferOrView(JSObject* unwrapped) : obj(unwrapped) {} public: // ArrayBufferOrView subclasses will set `obj` to nullptr if wrapping an // object of the wrong type. So this allows: // // auto view = JS::TypedArray::fromObject(obj); // if (!view) { ... } // explicit operator bool() const { return !!obj; } // `obj` must be an unwrapped ArrayBuffer or view, or nullptr. static inline ArrayBufferOrView fromObject(JSObject* unwrapped); // Unwrap an ArrayBuffer or view. Returns ArrayBufferOrView(nullptr) if // `maybeWrapped` is the wrong type or fails unwrapping. Never throw. static ArrayBufferOrView unwrap(JSObject* maybeWrapped); // Allow use as Rooted. void trace(JSTracer* trc) { if (obj) { js::gc::TraceExternalEdge(trc, &obj, "ArrayBufferOrView object"); } } bool isDetached() const; void exposeToActiveJS() const { if (obj) { js::BarrierMethods::exposeToJS(obj); } } JSObject* asObject() const { exposeToActiveJS(); return obj; } JSObject* asObjectUnbarriered() const { return obj; } JSObject** addressOfObject() { return &obj; } bool operator==(const ArrayBufferOrView& other) const { return obj == other.asObjectUnbarriered(); } bool operator!=(const ArrayBufferOrView& other) const { return obj != other.asObjectUnbarriered(); } }; class JS_PUBLIC_API ArrayBuffer : public ArrayBufferOrView { protected: explicit ArrayBuffer(JSObject* unwrapped) : ArrayBufferOrView(unwrapped) {} public: static const JSClass* const UnsharedClass; static const JSClass* const SharedClass; static ArrayBuffer fromObject(JSObject* unwrapped) { if (unwrapped) { const JSClass* clasp = GetClass(unwrapped); if (clasp == UnsharedClass || clasp == SharedClass) { return ArrayBuffer(unwrapped); } } return ArrayBuffer(nullptr); } static ArrayBuffer unwrap(JSObject* maybeWrapped); static ArrayBuffer create(JSContext* cx, size_t nbytes); bool isDetached() const; bool isSharedMemory() const; uint8_t* getLengthAndData(size_t* length, bool* isSharedMemory, const JS::AutoRequireNoGC&); uint8_t* getData(bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { size_t length; return getLengthAndData(&length, isSharedMemory, nogc); } }; // A view into an ArrayBuffer, either a DataViewObject or a Typed Array variant. class JS_PUBLIC_API ArrayBufferView : public ArrayBufferOrView { protected: explicit ArrayBufferView(JSObject* unwrapped) : ArrayBufferOrView(unwrapped) {} public: static inline ArrayBufferView fromObject(JSObject* unwrapped); static ArrayBufferView unwrap(JSObject* maybeWrapped) { if (!maybeWrapped) { return ArrayBufferView(nullptr); } ArrayBufferView view = fromObject(maybeWrapped); if (view) { return view; } return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); } bool isDetached() const; bool isSharedMemory() const; uint8_t* getLengthAndData(size_t* length, bool* isSharedMemory, const JS::AutoRequireNoGC&); uint8_t* getData(bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { size_t length; return getLengthAndData(&length, isSharedMemory, nogc); } // Must only be called if !isDetached(). size_t getByteLength(const JS::AutoRequireNoGC&); }; class JS_PUBLIC_API DataView : public ArrayBufferView { protected: explicit DataView(JSObject* unwrapped) : ArrayBufferView(unwrapped) {} public: static const JSClass* const ClassPtr; static DataView fromObject(JSObject* unwrapped) { if (unwrapped && GetClass(unwrapped) == ClassPtr) { return DataView(unwrapped); } return DataView(nullptr); } static DataView unwrap(JSObject* maybeWrapped) { if (!maybeWrapped) { return DataView(nullptr); } DataView view = fromObject(maybeWrapped); if (view) { return view; } return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); } }; // Base type of all Typed Array variants. class JS_PUBLIC_API TypedArray_base : public ArrayBufferView { protected: explicit TypedArray_base(JSObject* unwrapped) : ArrayBufferView(unwrapped) {} static const JSClass* const classes; public: static TypedArray_base fromObject(JSObject* unwrapped); static TypedArray_base unwrap(JSObject* maybeWrapped) { if (!maybeWrapped) { return TypedArray_base(nullptr); } TypedArray_base view = fromObject(maybeWrapped); if (view) { return view; } return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); } }; template class JS_PUBLIC_API TypedArray : public TypedArray_base { protected: explicit TypedArray(JSObject* unwrapped) : TypedArray_base(unwrapped) {} public: using DataType = detail::ExternalTypeOf_t; static constexpr JS::Scalar::Type Scalar = TypedArrayElementType; // This cannot be a static data member because on Windows, // __declspec(dllexport) causes the class to be instantiated immediately, // leading to errors when later explicit specializations of inline member // functions are encountered ("error: explicit specialization of 'ClassPtr' // after instantiation"). And those inlines need to be defined outside of the // class due to order dependencies. This is the only way I could get it to // work on both Windows and POSIX. static const JSClass* clasp() { return &TypedArray_base::classes[static_cast(TypedArrayElementType)]; } static TypedArray create(JSContext* cx, size_t nelements); static TypedArray fromArray(JSContext* cx, HandleObject other); static TypedArray fromBuffer(JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, int64_t length); // Return an interface wrapper around `obj`, or around nullptr if `obj` is not // an unwrapped typed array of the correct type. static TypedArray fromObject(JSObject* unwrapped) { if (unwrapped && GetClass(unwrapped) == clasp()) { return TypedArray(unwrapped); } return TypedArray(nullptr); } static TypedArray unwrap(JSObject* maybeWrapped) { if (!maybeWrapped) { return TypedArray(nullptr); } TypedArray view = fromObject(maybeWrapped); if (view) { return view; } return fromObject(js::CheckedUnwrapStatic(maybeWrapped)); } // Return a pointer to the start of the data referenced by a typed array. The // data is still owned by the typed array, and should not be modified on // another thread. Furthermore, the pointer can become invalid on GC (if the // data is small and fits inside the array's GC header), so callers must take // care not to hold on across anything that could GC. // // |obj| must have passed a JS_Is*Array test, or somehow be known that it // would pass such a test: it is a typed array or a wrapper of a typed array, // and the unwrapping will succeed. // // |*isSharedMemory| will be set to true if the typed array maps a // SharedArrayBuffer, otherwise to false. // DataType* getLengthAndData(size_t* length, bool* isSharedMemory, const JS::AutoRequireNoGC& nogc); DataType* getData(bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { size_t length; return getLengthAndData(&length, isSharedMemory, nogc); } }; ArrayBufferOrView ArrayBufferOrView::fromObject(JSObject* unwrapped) { if (ArrayBuffer::fromObject(unwrapped) || ArrayBufferView::fromObject(unwrapped)) { return ArrayBufferOrView(unwrapped); } return ArrayBufferOrView(nullptr); } ArrayBufferView ArrayBufferView::fromObject(JSObject* unwrapped) { if (TypedArray_base::fromObject(unwrapped) || DataView::fromObject(unwrapped)) { return ArrayBufferView(unwrapped); } return ArrayBufferView(nullptr); } } /* namespace JS */ /* * JS_Get(type)ArrayData(JSObject* obj, * bool* isSharedMemory, * const JS::AutoRequireNoGC&) * * js::Get(type)ArrayLengthAndData(JSObject* obj, * size_t* length, * bool* isSharedMemory, * const JS::AutoRequireNoGC&) * * Return a pointer to the start of the data referenced by a typed array. The * data is still owned by the typed array, and should not be modified on * another thread. Furthermore, the pointer can become invalid on GC (if the * data is small and fits inside the array's GC header), so callers must take * care not to hold on across anything that could GC. * * |obj| must have passed a JS_Is*Array test, or somehow be known that it would * pass such a test: it is a typed array or a wrapper of a typed array, and the * unwrapping will succeed. * * |*isSharedMemory| will be set to true if the typed array maps a * SharedArrayBuffer, otherwise to false. */ #define JS_DEFINE_DATA_AND_LENGTH_ACCESSOR(ExternalType, NativeType, Name) \ extern JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayData( \ JSObject* maybeWrapped, bool* isSharedMemory, \ const JS::AutoRequireNoGC&); \ \ namespace js { \ inline void Get##Name##ArrayLengthAndData(JSObject* unwrapped, \ size_t* length, \ bool* isSharedMemory, \ ExternalType** data) { \ MOZ_ASSERT(JS::GetClass(unwrapped) == \ JS::TypedArray::clasp()); \ const JS::Value& lenSlot = \ JS::GetReservedSlot(unwrapped, detail::TypedArrayLengthSlot); \ *length = size_t(lenSlot.toPrivate()); \ *isSharedMemory = JS_GetTypedArraySharedness(unwrapped); \ *data = JS::GetMaybePtrFromReservedSlot( \ unwrapped, detail::TypedArrayDataSlot); \ } \ \ JS_PUBLIC_API JSObject* Unwrap##Name##Array(JSObject* maybeWrapped); \ } /* namespace js */ JS_FOR_EACH_TYPED_ARRAY(JS_DEFINE_DATA_AND_LENGTH_ACCESSOR) #undef JS_DEFINE_DATA_AND_LENGTH_ACCESSOR namespace JS { #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ template <> \ inline JS::TypedArray \ JS::TypedArray::create(JSContext* cx, size_t nelements) { \ return fromObject(JS_New##Name##Array(cx, nelements)); \ }; \ \ template <> \ inline JS::TypedArray \ JS::TypedArray::fromArray(JSContext* cx, \ HandleObject other) { \ return fromObject(JS_New##Name##ArrayFromArray(cx, other)); \ }; \ \ template <> \ inline JS::TypedArray \ JS::TypedArray::fromBuffer( \ JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, \ int64_t length) { \ return fromObject( \ JS_New##Name##ArrayWithBuffer(cx, arrayBuffer, byteOffset, length)); \ }; JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) #undef IMPL_TYPED_ARRAY_CLASS // Create simple names like Int8Array, Float32Array, etc. #define JS_DECLARE_CLASS_ALIAS(ExternalType, NativeType, Name) \ using Name##Array = TypedArray; JS_FOR_EACH_TYPED_ARRAY(JS_DECLARE_CLASS_ALIAS) #undef JS_DECLARE_CLASS_ALIAS } // namespace JS namespace js { template using EnableIfABOVType = std::enable_if_t>; template class WrappedPtrOperations> { auto get() const { return static_cast(this)->get(); } public: explicit operator bool() const { return bool(get()); } JSObject* asObject() const { return get().asObject(); } bool isDetached() const { return get().isDetached(); } bool isSharedMemory() const { return get().isSharedMemory(); } typename T::DataType* getLengthAndData(size_t* length, bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { return get().getLengthAndData(length, isSharedMemory, nogc); } typename T::DataType* getData(bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { return get().getData(isSharedMemory, nogc); } }; // Allow usage within Heap. template struct IsHeapConstructibleType> : public std::true_type { }; template struct BarrierMethods> { static gc::Cell* asGCThingOrNull(T view) { return reinterpret_cast(view.asObjectUnbarriered()); } static void postWriteBarrier(T* viewp, T prev, T next) { BarrierMethods::postWriteBarrier(viewp->addressOfObject(), prev.asObjectUnbarriered(), next.asObjectUnbarriered()); } static void exposeToJS(T view) { view.exposeToActiveJS(); } static void readBarrier(T view) { JSObject* obj = view.asObjectUnbarriered(); if (obj) { js::gc::IncrementalReadBarrier(JS::GCCellPtr(obj)); } } }; } // namespace js namespace JS { template struct SafelyInitialized> { static T create() { return T::fromObject(nullptr); } }; } // namespace JS /* * JS_Is(type)Array(JSObject* maybeWrapped) * * Test for specific typed array types. */ #define DECLARE_IS_ARRAY_TEST(_1, _2, Name) \ inline JS_PUBLIC_API bool JS_Is##Name##Array(JSObject* maybeWrapped) { \ return JS::TypedArray::unwrap(maybeWrapped).asObject(); \ } JS_FOR_EACH_TYPED_ARRAY(DECLARE_IS_ARRAY_TEST) #undef DECLARE_IS_ARRAY_TEST #endif // js_experimental_TypedData_h