/* -*- 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/. */ #ifndef mozilla_dom_TypedArray_h #define mozilla_dom_TypedArray_h #include #include "js/ArrayBuffer.h" #include "js/ArrayBufferMaybeShared.h" #include "js/experimental/TypedData.h" // js::Unwrap(Ui|I)nt(8|16|32)Array, js::Get(Ui|I)nt(8|16|32)ArrayLengthAndData, js::UnwrapUint8ClampedArray, js::GetUint8ClampedArrayLengthAndData, js::UnwrapFloat(32|64)Array, js::GetFloat(32|64)ArrayLengthAndData, JS_GetArrayBufferViewType #include "js/GCAPI.h" // JS::AutoCheckCannotGC #include "js/RootingAPI.h" // JS::Rooted #include "js/ScalarType.h" // JS::Scalar::Type #include "js/SharedArrayBuffer.h" #include "mozilla/Attributes.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/SpiderMonkeyInterface.h" #include "nsWrapperCache.h" #include "nsWrapperCacheInlines.h" namespace mozilla::dom { /* * Various typed array classes for argument conversion. We have a base class * that has a way of initializing a TypedArray from an existing typed array, and * a subclass of the base class that supports creation of a relevant typed array * or array buffer object. */ template struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage, AllTypedArraysBase { using element_type = typename ArrayT::DataType; TypedArray_base() : mData(nullptr), mLength(0), mShared(false), mComputed(false) {} TypedArray_base(TypedArray_base&& aOther) : SpiderMonkeyInterfaceObjectStorage(std::move(aOther)), mData(aOther.mData), mLength(aOther.mLength), mShared(aOther.mShared), mComputed(aOther.mComputed) { aOther.Reset(); } private: mutable element_type* mData; mutable uint32_t mLength; mutable bool mShared; mutable bool mComputed; public: inline bool Init(JSObject* obj) { MOZ_ASSERT(!inited()); mImplObj = mWrappedObj = ArrayT::unwrap(obj).asObject(); return inited(); } // About shared memory: // // Any DOM TypedArray as well as any DOM ArrayBufferView can map the // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer. If // the TypedArray maps a SharedArrayBuffer the Length() and Data() // accessors on the DOM view will return zero and nullptr; to get // the actual length and data, call the LengthAllowShared() and // DataAllowShared() accessors instead. // // Two methods are available for determining if a DOM view maps // shared memory. The IsShared() method is cheap and can be called // if the view has been computed; the JS_GetTypedArraySharedness() // method is slightly more expensive and can be called on the Obj() // value if the view may not have been computed and if the value is // known to represent a JS TypedArray. // // (Just use JS::IsSharedArrayBuffer() to test if any object is of // that type.) // // Code that elects to allow views that map shared memory to be used // -- ie, code that "opts in to shared memory" -- should generally // not access the raw data buffer with standard C++ mechanisms as // that creates the possibility of C++ data races, which is // undefined behavior. The JS engine will eventually export (bug // 1225033) a suite of methods that avoid undefined behavior. // // Callers of Obj() that do not opt in to shared memory can produce // better diagnostics by checking whether the JSObject in fact maps // shared memory and throwing an error if it does. However, it is // safe to use the value of Obj() without such checks. // // The DOM TypedArray abstraction prevents the underlying buffer object // from being accessed directly, but JS_GetArrayBufferViewBuffer(Obj()) // will obtain the buffer object. Code that calls that function must // not assume the returned buffer is an ArrayBuffer. That is guarded // against by an out parameter on that call that communicates the // sharedness of the buffer. // // Finally, note that the buffer memory of a SharedArrayBuffer is // not detachable. inline bool IsShared() const { MOZ_ASSERT(mComputed); return mShared; } inline element_type* Data() const { MOZ_ASSERT(mComputed); return mData; } // Return a pointer to data that will not move during a GC. // // For some smaller views, this will copy the data into the provided buffer // and return that buffer as the pointer. Otherwise, this will return a // direct pointer to the actual data with no copying. If the provided buffer // is not large enough, nullptr will be returned. If bufSize is at least // JS_MaxMovableTypedArraySize(), the data is guaranteed to fit. inline element_type* FixedData(uint8_t* buffer, size_t bufSize) const { MOZ_ASSERT(mComputed); return JS_GetArrayBufferViewFixedData(mImplObj, buffer, bufSize); } inline uint32_t Length() const { MOZ_ASSERT(mComputed); return mLength; } inline void ComputeState() const { MOZ_ASSERT(inited()); MOZ_ASSERT(!mComputed); size_t length; JS::AutoCheckCannotGC nogc; mData = ArrayT::fromObject(mImplObj).getLengthAndData(&length, &mShared, nogc); MOZ_RELEASE_ASSERT(length <= INT32_MAX, "Bindings must have checked ArrayBuffer{View} length"); mLength = length; mComputed = true; } inline void Reset() { // This method mostly exists to inform the GC rooting hazard analysis that // the variable can be considered dead, at least until you do anything else // with it. mData = nullptr; mLength = 0; mShared = false; mComputed = false; } private: TypedArray_base(const TypedArray_base&) = delete; }; template struct TypedArray : public TypedArray_base { using Base = TypedArray_base; using element_type = typename Base::element_type; TypedArray() = default; TypedArray(TypedArray&& aOther) = default; static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, uint32_t length, const element_type* data = nullptr) { JS::Rooted creatorWrapper(cx); Maybe ar; if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) { ar.emplace(cx, creatorWrapper); } return CreateCommon(cx, length, data); } static inline JSObject* Create(JSContext* cx, uint32_t length, const element_type* data = nullptr) { return CreateCommon(cx, length, data); } static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, Span data) { // Span<> uses size_t as a length, and we use uint32_t instead. if (MOZ_UNLIKELY(data.Length() > UINT32_MAX)) { JS_ReportOutOfMemory(cx); return nullptr; } return Create(cx, creator, data.Length(), data.Elements()); } static inline JSObject* Create(JSContext* cx, Span data) { // Span<> uses size_t as a length, and we use uint32_t instead. if (MOZ_UNLIKELY(data.Length() > UINT32_MAX)) { JS_ReportOutOfMemory(cx); return nullptr; } return CreateCommon(cx, data.Length(), data.Elements()); } private: static inline JSObject* CreateCommon(JSContext* cx, uint32_t length, const element_type* data) { auto array = ArrayT::create(cx, length); if (!array) { return nullptr; } if (data) { JS::AutoCheckCannotGC nogc; bool isShared; element_type* buf = array.getData(&isShared, nogc); // Data will not be shared, until a construction protocol exists // for constructing shared data. MOZ_ASSERT(!isShared); memcpy(buf, data, length * sizeof(element_type)); } return array.asObject(); } TypedArray(const TypedArray&) = delete; }; template struct ArrayBufferView_base : public TypedArray_base { private: using Base = TypedArray_base; public: ArrayBufferView_base() : Base(), mType(JS::Scalar::MaxTypedArrayViewType) {} ArrayBufferView_base(ArrayBufferView_base&& aOther) : Base(std::move(aOther)), mType(aOther.mType) { aOther.mType = JS::Scalar::MaxTypedArrayViewType; } private: JS::Scalar::Type mType; public: inline bool Init(JSObject* obj) { if (!Base::Init(obj)) { return false; } mType = GetViewType(this->Obj()); return true; } inline JS::Scalar::Type Type() const { MOZ_ASSERT(this->inited()); return mType; } }; using Int8Array = TypedArray; using Uint8Array = TypedArray; using Uint8ClampedArray = TypedArray; using Int16Array = TypedArray; using Uint16Array = TypedArray; using Int32Array = TypedArray; using Uint32Array = TypedArray; using Float32Array = TypedArray; using Float64Array = TypedArray; using ArrayBufferView = ArrayBufferView_base; using ArrayBuffer = TypedArray; // A class for converting an nsTArray to a TypedArray // Note: A TypedArrayCreator must not outlive the nsTArray it was created from. // So this is best used to pass from things that understand nsTArray to // things that understand TypedArray, as with ToJSValue. template class TypedArrayCreator { typedef nsTArray ArrayType; public: explicit TypedArrayCreator(const ArrayType& aArray) : mArray(aArray) {} JSObject* Create(JSContext* aCx) const { return TypedArrayType::Create(aCx, mArray.Length(), mArray.Elements()); } private: const ArrayType& mArray; }; } // namespace mozilla::dom #endif /* mozilla_dom_TypedArray_h */