diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/bindings/TypedArray.h | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/bindings/TypedArray.h')
-rw-r--r-- | dom/bindings/TypedArray.h | 1095 |
1 files changed, 1095 insertions, 0 deletions
diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h new file mode 100644 index 0000000000..36c46f47b1 --- /dev/null +++ b/dom/bindings/TypedArray.h @@ -0,0 +1,1095 @@ +/* -*- 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 <string> +#include <type_traits> +#include <utility> + +#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 "js/friend/ErrorMessages.h" +#include "mozilla/Attributes.h" +#include "mozilla/Buffer.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/Result.h" +#include "mozilla/Vector.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/SpiderMonkeyInterface.h" +#include "nsIGlobalObject.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. + * + * Accessing the data of a TypedArray is tricky. The underlying storage is in a + * JS object, which is subject to the JS GC. The memory for the array can be + * either inline in the JSObject or stored separately. If the data is stored + * inline then the exact location in memory of the data buffer can change as a + * result of a JS GC (which is a moving GC). Running JS code can also mutate the + * data (including the length). For all these reasons one has to be careful when + * holding a pointer to the data or keeping a local copy of the length value. On + * the other hand, most code that takes a TypedArray has to access its data at + * some point, to process it in some way. The TypedArray class tries to supply a + * number of helper APIs, so that the most common cases of processing the data + * can be done safely, without having to check the caller very closely for + * potential security issues. The main classes of processing TypedArray data + * are: + * + * 1) Appending a copy of the data (or of a subset of the data) to a different + * data structure + * 2) Copying the data (or a subset of the data) into a different data + * structure + * 3) Creating a new data structure with a copy of the data (or of a subset of + * the data) + * 4) Processing the data in some other way + * + * The APIs for the first 3 classes all return a boolean and take an optional + * argument named aCalculator. aCalculator should be a lambda taking a size_t + * argument which will be passed the total length of the data in the typed + * array. aCalculator is allowed to return a std::pair<size_t, size_t> or a + * Maybe<std::pair<size_t, size_t>>. The return value should contain the offset + * and the length of the subset of the data that should be appended, copied or + * used for creating a new datastructure. If the calculation can fail then + * aCalculator should return a Maybe<std::pair<size_t, size_t>>, with Nothing() + * signaling that the operation should be aborted. + * The return value of these APIs will be false if appending, copying or + * creating a structure with the new data failed, or if the optional aCalculator + * lambda returned Nothing(). + * + * Here are the different APIs: + * + * 1) Appending to a different data structure + * + * There are AppendDataTo helpers for nsCString, nsTArray<T>, + * FallibleTArray<T> and Vector<T>. The signatures are: + * + * template <typename... Calculator> + * [[nodiscard]] bool AppendDataTo(nsCString& aResult, Calculator&&... + * aCalculator) const; + * + * template <typename T, typename... Calculator> + * [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, Calculator&&... + * aCalculator) const; + * + * template <typename T, typename... Calculator> + * [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult, + * Calculator&&... aCalculator) const; + * + * template <typename T, typename... Calculator> + * [[nodiscard]] bool AppendDataTo(Vector<T>& aResult, Calculator&&... + * aCalculator) const; + * + * The data (or the calculated subset) will be appended to aResult by using + * the appropriate fallible API. If the append fails then AppendDataTo will + * return false. The aCalculator optional argument is described above. + * + * Examples: + * + * Vector<uint32_t> array; + * if (!aUint32Array.AppendDataTo(array)) { + * aError.ThrowTypeError("Failed to copy data from typed array"); + * return; + * } + * + * size_t offset, length; + * … // Getting offset and length values from somewhere. + * FallibleTArray<float> array; + * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) { + * size_t dataLength = std::min(aLength - offset, length); + * return std::make_pair(offset, dataLength); + * }) { + * aError.ThrowTypeError("Failed to copy data from typed array"); + * return; + * } + * + * size_t offset, length; + * … // Getting offset and length values from somewhere. + * FallibleTArray<float> array; + * if (!aFloat32Array.AppendDataTo(array, [&](const size_t& aLength) { + * if (aLength < offset + length) { + * return Maybe<std::pair<size_t, size_t>>(); + * } + * size_t dataLength = std::min(aLength - offset, length); + * return Some(std::make_pair(offset, dataLength)); + * })) { + * aError.ThrowTypeError("Failed to copy data from typed array"); + * return; + * } + * + * + * 2) Copying into a different data structure + * + * There is a CopyDataTo helper for a fixed-size buffer. The signature is: + * + * template <typename T, size_t N, typename... Calculator> + * [[nodiscard]] bool CopyDataTo(T (&aResult)[N], + * Calculator&&... aCalculator) const; + * + * The data (or the calculated subset) will be copied to aResult, starting + * at aResult[0]. If the length of the data to be copied is bigger than the + * size of the fixed-size buffer (N) then nothing will be copied and + * CopyDataTo will return false. The aCalculator optional argument is + * described above. + * + * Examples: + * + * float data[3]; + * if (!aFloat32Array.CopyDataTo(data)) { + * aError.ThrowTypeError("Typed array doesn't contain the right amount" + * "of data"); + * return; + * } + * + * size_t offset; + * … // Getting offset value from somewhere. + * uint32_t data[3]; + * if (!aUint32Array.CopyDataTo(data, [&](const size_t& aLength) { + * if (aLength - offset != ArrayLength(data)) { + * aError.ThrowTypeError("Typed array doesn't contain the right" + * " amount of data"); + * return Maybe<std::pair<size_t, size_t>>(); + * } + * return Some(std::make_pair(offset, ArrayLength(data))); + * }) { + * return; + * } + * + * 3) Creating a new data structure with a copy of the data (or a subset of the + * data) + * + * There are CreateFromData helper for creating a Vector<element_type>, a + * UniquePtr<element_type[]> and a Buffer<element_type>. + * + * template <typename... Calculator> + * [[nodiscard]] Maybe<Vector<element_type>> + * CreateFromData<Vector<element_type>>( + * Calculator&&... aCalculator) const; + * + * template <typename... Calculator> + * [[nodiscard]] Maybe<UniquePtr<element_type[]>> + * CreateFromData<UniquePtr<element_type[]>>( + * Calculator&&... aCalculator) const; + * + * template <typename... Calculator> + * [[nodiscard]] Maybe<Buffer<element_type>> + * CreateFromData<Buffer<element_type>>( + * Calculator&&... aCalculator) const; + * + * A new container will be created, and the data (or the calculated subset) + * will be copied to it. The container will be returned inside a Maybe<…>. + * If creating the container with the right size fails then Nothing() will + * be returned. The aCalculator optional argument is described above. + * + * Examples: + * + * Maybe<Buffer<uint8_t>> buffer = + * aUint8Array.CreateFromData<Buffer<uint8_t>>(); + * if (buffer.isNothing()) { + * aError.ThrowTypeError("Failed to create a buffer"); + * return; + * } + * + * size_t offset, length; + * … // Getting offset and length values from somewhere. + * Maybe<Buffer<uint8_t>> buffer = + * aUint8Array.CreateFromData<Buffer<uint8_t>>([&]( + * const size_t& aLength) { + * if (aLength - offset != ArrayLength(data)) { + * aError.ThrowTypeError( + * "Typed array doesn't contain the right amount" of data"); + * return Maybe<std::pair<size_t, size_t>>(); + * } + * return Some(std::make_pair(offset, ArrayLength(data))); + * }); + * if (buffer.isNothing()) { + * return; + * } + * + * 4) Processing the data in some other way + * + * This is the API for when none of the APIs above are appropriate for your + * usecase. As these are the most dangerous APIs you really should check + * first if you can't use one of the safer alternatives above. The reason + * these APIs are more dangerous is because they give access to the typed + * array's data directly, and the location of that data can be changed by + * the JS GC (due to generational and/or compacting collection). There are + * two APIs for processing data: + * + * template <typename Processor> + * [[nodiscard]] ProcessReturnType<Processor> ProcessData( + * Processor&& aProcessor) const; + * + * template <typename Processor> + * [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData( + * Processor&& aProcessor) const; + * + * ProcessData will call the lambda with as arguments a |const Span<…>&| + * wrapping the data pointer and length for the data in the typed array, and + * a |JS::AutoCheckCannotGC&&|. The lambda will execute in a context where + * GC is not allowed. + * + * ProcessFixedData will call the lambda with as argument |const Span<…>&|. + * For small typed arrays where the data is stored inline in the typed + * array, and thus could move during GC, then the data will be copied into a + * fresh out-of-line allocation before the lambda is called. + * + * The signature of the lambdas for ProcessData and ProcessFixedData differ + * in that the ProcessData lambda will additionally be passed a nogc token + * to prevent GC from occurring and invalidating the data. If the processing + * you need to do in the lambda can't be proven to not GC then you should + * probably use ProcessFixedData instead. There are cases where you need to + * do something that can cause a GC but you don't actually need to access + * the data anymore. A good example would be throwing a JS exception and + * return. For those very specific cases you can call nogc.reset() before + * doing anything that causes a GC. Be extra careful to not access the data + * after you called nogc.reset(). + * + * Extra care must be taken to not let the Span<…> or any pointers derived + * from it escape the lambda, as the position in memory of the typed array's + * data can change again once we leave the lambda (invalidating the + * pointers). The lambda passed to ProcessData is not allowed to do anything + * that will trigger a GC, and the GC rooting hazard analysis will enforce + * that. + * + * Both ProcessData and ProcessFixedData will pin the typed array's length + * while calling the lambda, to block any changes to the length of the data. + * Note that this means that the lambda itself isn't allowed to change the + * length of the typed array's data. Any attempt to change the length will + * throw a JS exception. + * + * The return type of ProcessData and ProcessFixedData depends on the return + * type of the lambda, as they forward the return value from the lambda to + * the caller of ProcessData or ProcessFixedData. + * + * Examples: + * + * aUint32Array.ProcessData([] (const Span<uint32_t>& aData, + * JS::AutoCheckCannotGC&&) { + * for (size_t i = 0; i < aData.Length(); ++i) { + * aData[i] = i; + * } + * }); + * + * aUint32Array.ProcessData([&] (const Span<uint32_t>& aData, + * JS::AutoCheckCannotGC&& nogc) { + * for (size_t i = 0; i < aData.Length(); ++i) { + * if (!aData[i]) { + * nogc.reset(); + * ThrowJSException("Data shouldn't contain 0"); + * return; + * }; + * DoSomething(aData[i]); + * } + * }); + * + * uint8_t max = aUint8Array.ProcessData([] (const Span<uint8_t>& aData) { + * return std::max_element(aData.cbegin(), aData.cend()); + * }); + * + * aUint8Array.ProcessFixedData([] (const Span<uint8_t>& aData) { + * return CallFunctionThatMightGC(aData); + * }); + * + * + * In addition to the above APIs we provide helpers to call them on the typed + * array members of WebIDL unions. We have helpers for the 4 different sets of + * APIs above. The return value of the helpers depends on whether the union can + * contain a type other than a typed array. If the union can't contain a type + * other than a typed array then the return type is simply the type returned by + * the corresponding API above. If the union can contain a type other than a + * typed array then the return type of the helper is a Maybe<…> wrapping the + * actual return type, with Nothing() signifying that the union contained a + * non-typed array value. + * + * template <typename ToType, typename T> + * [[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, + * ToType& aResult); + * + * template <typename ToType, typename T> + * [[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion); + * + * template <typename T, typename Processor> + * [[nodiscard]] auto ProcessTypedArrays( + * const T& aUnion, Processor&& aProcessor); + * + * template <typename T, typename Processor> + * [[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion, + * Processor&& aProcessor); + * + */ + +template <class ArrayT> +struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage, + AllTypedArraysBase { + using element_type = typename ArrayT::DataType; + + TypedArray_base() = default; + TypedArray_base(TypedArray_base&& aOther) = default; + + 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. + // + // 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. + + public: + /** + * Helper functions to append a copy of this typed array's data to a + * container. Returns false if the allocation for copying the data fails. + * + * aCalculator is an optional argument to which one can pass a lambda + * expression that will calculate the offset and length of the data to copy + * out of the typed array. aCalculator will be called with one argument of + * type size_t set to the length of the data in the typed array. It is allowed + * to return a std::pair<size_t, size_t> or a Maybe<std::pair<size_t, size_t>> + * containing the offset and the length of the subset of the data that we + * should copy. If the calculation can fail then aCalculator should return a + * Maybe<std::pair<size_t, size_t>>, if .isNothing() returns true for the + * return value then AppendDataTo will return false and the data won't be + * copied. + */ + + template <typename... Calculator> + [[nodiscard]] bool AppendDataTo(nsCString& aResult, + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "AppendDataTo takes at most one aCalculator"); + + return ProcessDataHelper( + [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { + return aResult.Append(aData, fallible); + }, + std::forward<Calculator>(aCalculator)...); + } + + template <typename T, typename... Calculator> + [[nodiscard]] bool AppendDataTo(nsTArray<T>& aResult, + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "AppendDataTo takes at most one aCalculator"); + + return ProcessDataHelper( + [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { + return aResult.AppendElements(aData, fallible); + }, + std::forward<Calculator>(aCalculator)...); + } + + template <typename T, typename... Calculator> + [[nodiscard]] bool AppendDataTo(FallibleTArray<T>& aResult, + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "AppendDataTo takes at most one aCalculator"); + + return ProcessDataHelper( + [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { + return aResult.AppendElements(aData, fallible); + }, + std::forward<Calculator>(aCalculator)...); + } + + template <typename T, typename... Calculator> + [[nodiscard]] bool AppendDataTo(Vector<T>& aResult, + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "AppendDataTo takes at most one aCalculator"); + + return ProcessDataHelper( + [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { + return aResult.append(aData.Elements(), aData.Length()); + }, + std::forward<Calculator>(aCalculator)...); + } + + /** + * Helper functions to copy this typed array's data to a container. This will + * clear any existing data in the container. + * + * See the comments for AppendDataTo for information on the aCalculator + * argument. + */ + + template <typename T, size_t N, typename... Calculator> + [[nodiscard]] bool CopyDataTo(T (&aResult)[N], + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "CopyDataTo takes at most one aCalculator"); + + return ProcessDataHelper( + [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { + if (aData.Length() != N) { + return false; + } + for (size_t i = 0; i < N; ++i) { + aResult[i] = aData[i]; + } + return true; + }, + std::forward<Calculator>(aCalculator)...); + } + + /** + * Helper functions to copy this typed array's data to a newly created + * container. Returns Nothing() if creating the container with the right size + * fails. + * + * See the comments for AppendDataTo for information on the aCalculator + * argument. + */ + + template <typename T, typename... Calculator, + typename IsVector = + std::enable_if_t<std::is_same_v<Vector<element_type>, T>>> + [[nodiscard]] Maybe<Vector<element_type>> CreateFromData( + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "CreateFromData takes at most one aCalculator"); + + return CreateFromDataHelper<T>( + [&](const Span<const element_type>& aData, + Vector<element_type>& aResult) { + if (!aResult.initCapacity(aData.Length())) { + return false; + } + aResult.infallibleAppend(aData.Elements(), aData.Length()); + return true; + }, + std::forward<Calculator>(aCalculator)...); + } + + template <typename T, typename... Calculator, + typename IsUniquePtr = + std::enable_if_t<std::is_same_v<T, UniquePtr<element_type[]>>>> + [[nodiscard]] Maybe<UniquePtr<element_type[]>> CreateFromData( + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "CreateFromData takes at most one aCalculator"); + + return CreateFromDataHelper<T>( + [&](const Span<const element_type>& aData, + UniquePtr<element_type[]>& aResult) { + aResult = + MakeUniqueForOverwriteFallible<element_type[]>(aData.Length()); + if (!aResult.get()) { + return false; + } + memcpy(aResult.get(), aData.Elements(), aData.LengthBytes()); + return true; + }, + std::forward<Calculator>(aCalculator)...); + } + + template <typename T, typename... Calculator, + typename IsBuffer = + std::enable_if_t<std::is_same_v<T, Buffer<element_type>>>> + [[nodiscard]] Maybe<Buffer<element_type>> CreateFromData( + Calculator&&... aCalculator) const { + static_assert(sizeof...(aCalculator) <= 1, + "CreateFromData takes at most one aCalculator"); + + return CreateFromDataHelper<T>( + [&](const Span<const element_type>& aData, + Buffer<element_type>& aResult) { + Maybe<Buffer<element_type>> buffer = + Buffer<element_type>::CopyFrom(aData); + if (buffer.isNothing()) { + return false; + } + aResult = buffer.extract(); + return true; + }, + std::forward<Calculator>(aCalculator)...); + } + + private: + template <typename Processor, typename R = decltype(std::declval<Processor>()( + std::declval<Span<element_type>>(), + std::declval<JS::AutoCheckCannotGC>()))> + using ProcessNoGCReturnType = R; + + template <typename Processor> + [[nodiscard]] static inline ProcessNoGCReturnType<Processor> + CallProcessorNoGC(const Span<element_type>& aData, Processor&& aProcessor, + JS::AutoCheckCannotGC&& nogc) { + MOZ_ASSERT( + aData.IsEmpty() || aData.Elements(), + "We expect a non-null data pointer for typed arrays that aren't empty"); + + return aProcessor(aData, std::move(nogc)); + } + + template <typename Processor, typename R = decltype(std::declval<Processor>()( + std::declval<Span<element_type>>()))> + using ProcessReturnType = R; + + template <typename Processor> + [[nodiscard]] static inline ProcessReturnType<Processor> CallProcessor( + const Span<element_type>& aData, Processor&& aProcessor) { + MOZ_ASSERT( + aData.IsEmpty() || aData.Elements(), + "We expect a non-null data pointer for typed arrays that aren't empty"); + + return aProcessor(aData); + } + + struct MOZ_STACK_CLASS LengthPinner { + explicit LengthPinner(const TypedArray_base* aTypedArray) + : mTypedArray(aTypedArray), + mWasPinned( + !JS::PinArrayBufferOrViewLength(aTypedArray->Obj(), true)) {} + ~LengthPinner() { + if (!mWasPinned) { + JS::PinArrayBufferOrViewLength(mTypedArray->Obj(), false); + } + } + + private: + const TypedArray_base* mTypedArray; + bool mWasPinned; + }; + + template <typename Processor, typename Calculator> + [[nodiscard]] bool ProcessDataHelper( + Processor&& aProcessor, Calculator&& aCalculateOffsetAndLength) const { + LengthPinner pinner(this); + + JS::AutoCheckCannotGC nogc; // `data` is GC-sensitive. + Span<element_type> data = GetCurrentData(); + const auto& offsetAndLength = aCalculateOffsetAndLength(data.Length()); + size_t offset, length; + if constexpr (std::is_convertible_v<decltype(offsetAndLength), + std::pair<size_t, size_t>>) { + std::tie(offset, length) = offsetAndLength; + } else { + if (offsetAndLength.isNothing()) { + return false; + } + std::tie(offset, length) = offsetAndLength.value(); + } + + return CallProcessorNoGC(data.Subspan(offset, length), + std::forward<Processor>(aProcessor), + std::move(nogc)); + } + + template <typename Processor> + [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessDataHelper( + Processor&& aProcessor) const { + LengthPinner pinner(this); + // The data from GetCurrentData() is GC sensitive. + JS::AutoCheckCannotGC nogc; + return CallProcessorNoGC( + GetCurrentData(), std::forward<Processor>(aProcessor), std::move(nogc)); + } + + public: + template <typename Processor> + [[nodiscard]] ProcessNoGCReturnType<Processor> ProcessData( + Processor&& aProcessor) const { + return ProcessDataHelper(std::forward<Processor>(aProcessor)); + } + + template <typename Processor> + [[nodiscard]] ProcessReturnType<Processor> ProcessFixedData( + Processor&& aProcessor) const { + mozilla::dom::AutoJSAPI jsapi; + if (!jsapi.Init(mImplObj)) { +#if defined(EARLY_BETA_OR_EARLIER) + if constexpr (std::is_same_v<ArrayT, JS::ArrayBufferView>) { + if (!mImplObj) { + MOZ_CRASH("Null mImplObj"); + } + if (!xpc::NativeGlobal(mImplObj)) { + MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)"); + } + if (!xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()) { + MOZ_CRASH("Null xpc::NativeGlobal(mImplObj)->GetGlobalJSObject()"); + } + } +#endif + MOZ_CRASH("Failed to get JSContext"); + } +#if defined(EARLY_BETA_OR_EARLIER) + if constexpr (std::is_same_v<ArrayT, JS::ArrayBufferView>) { + JS::Rooted<JSObject*> view(jsapi.cx(), + js::UnwrapArrayBufferView(mImplObj)); + if (!view) { + if (JSObject* unwrapped = js::CheckedUnwrapStatic(mImplObj)) { + if (!js::UnwrapArrayBufferView(unwrapped)) { + MOZ_CRASH( + "Null " + "js::UnwrapArrayBufferView(js::CheckedUnwrapStatic(mImplObj))"); + } + view = unwrapped; + } else { + MOZ_CRASH("Null js::CheckedUnwrapStatic(mImplObj)"); + } + } + if (!JS::IsArrayBufferViewShared(view)) { + JSAutoRealm ar(jsapi.cx(), view); + bool unused; + bool noBuffer; + { + JSObject* buffer = + JS_GetArrayBufferViewBuffer(jsapi.cx(), view, &unused); + noBuffer = !buffer; + } + if (noBuffer) { + if (JS_IsTypedArrayObject(view)) { + JS::Value bufferSlot = + JS::GetReservedSlot(view, /* BUFFER_SLOT */ 0); + if (bufferSlot.isNull()) { + MOZ_CRASH("TypedArrayObject with bufferSlot containing null"); + } else if (bufferSlot.isBoolean()) { + // If we're here then TypedArrayObject::ensureHasBuffer must have + // failed in the call to JS_GetArrayBufferViewBuffer. + if (JS_IsThrowingOutOfMemory(jsapi.cx())) { + size_t length = JS_GetTypedArrayByteLength(view); + if (!JS::GetReservedSlot(view, /* DATA_SLOT */ 3) + .isUndefined() && + length <= JS_MaxMovableTypedArraySize()) { + MOZ_CRASH( + "We did run out of memory, maybe trying to uninline the " + "buffer"); + } + if (length < INT32_MAX) { + MOZ_CRASH( + "We did run out of memory trying to create a buffer " + "smaller than 2GB - 1"); + } else if (length < UINT32_MAX) { + MOZ_CRASH( + "We did run out of memory trying to create a between 2GB " + "and 4GB - 1"); + } else { + MOZ_CRASH( + "We did run out of memory trying to create a buffer " + "bigger than 4GB - 1"); + } + } else if (JS_IsExceptionPending(jsapi.cx())) { + JS::Rooted<JS::Value> exn(jsapi.cx()); + if (JS_GetPendingException(jsapi.cx(), &exn) && + exn.isObject()) { + JS::Rooted<JSObject*> exnObj(jsapi.cx(), &exn.toObject()); + JSErrorReport* err = + JS_ErrorFromException(jsapi.cx(), exnObj); + if (err && err->errorNumber == JSMSG_BAD_ARRAY_LENGTH) { + MOZ_CRASH("Length was too big"); + } + } + } + // Did ArrayBufferObject::createBufferAndData fail without OOM? + MOZ_CRASH("TypedArrayObject with bufferSlot containing boolean"); + } else if (bufferSlot.isObject()) { + if (!bufferSlot.toObjectOrNull()) { + MOZ_CRASH( + "TypedArrayObject with bufferSlot containing null object"); + } else { + MOZ_CRASH( + "JS_GetArrayBufferViewBuffer failed but bufferSlot " + "contains a non-null object"); + } + } else { + MOZ_CRASH( + "TypedArrayObject with bufferSlot containing weird value"); + } + } else { + MOZ_CRASH("JS_GetArrayBufferViewBuffer failed for DataViewObject"); + } + } + } + } +#endif + if (!JS::EnsureNonInlineArrayBufferOrView(jsapi.cx(), mImplObj)) { + MOZ_CRASH("small oom when moving inline data out-of-line"); + } + LengthPinner pinner(this); + + return CallProcessor(GetCurrentData(), std::forward<Processor>(aProcessor)); + } + + private: + Span<element_type> GetCurrentData() const { + MOZ_ASSERT(inited()); + MOZ_RELEASE_ASSERT( + !ArrayT::fromObject(mImplObj).isResizable(), + "Bindings must have checked ArrayBuffer{View} is non-resizable"); + + // Intentionally return a pointer and length that escape from a nogc region. + // Private so it can only be used in very limited situations. + JS::AutoCheckCannotGC nogc; + bool shared; + Span<element_type> span = + ArrayT::fromObject(mImplObj).getData(&shared, nogc); + MOZ_RELEASE_ASSERT(span.Length() <= INT32_MAX, + "Bindings must have checked ArrayBuffer{View} length"); + return span; + } + + template <typename T, typename F, typename... Calculator> + [[nodiscard]] Maybe<T> CreateFromDataHelper( + F&& aCreator, Calculator&&... aCalculator) const { + Maybe<T> result; + bool ok = ProcessDataHelper( + [&](const Span<const element_type>& aData, JS::AutoCheckCannotGC&&) { + result.emplace(); + return aCreator(aData, *result); + }, + std::forward<Calculator>(aCalculator)...); + + if (!ok) { + return Nothing(); + } + + return result; + } + + TypedArray_base(const TypedArray_base&) = delete; +}; + +template <class ArrayT> +struct TypedArray : public TypedArray_base<ArrayT> { + using Base = TypedArray_base<ArrayT>; + using element_type = typename Base::element_type; + + TypedArray() = default; + + TypedArray(TypedArray&& aOther) = default; + + static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, + size_t length, ErrorResult& error) { + return CreateCommon(cx, creator, length, error).asObject(); + } + + static inline JSObject* Create(JSContext* cx, size_t length, + ErrorResult& error) { + return CreateCommon(cx, length, error).asObject(); + } + + static inline JSObject* Create(JSContext* cx, nsWrapperCache* creator, + Span<const element_type> data, + ErrorResult& error) { + ArrayT array = CreateCommon(cx, creator, data.Length(), error); + if (!error.Failed() && !data.IsEmpty()) { + CopyFrom(cx, data, array); + } + return array.asObject(); + } + + static inline JSObject* Create(JSContext* cx, Span<const element_type> data, + ErrorResult& error) { + ArrayT array = CreateCommon(cx, data.Length(), error); + if (!error.Failed() && !data.IsEmpty()) { + CopyFrom(cx, data, array); + } + return array.asObject(); + } + + private: + template <typename> + friend class TypedArrayCreator; + + static inline ArrayT CreateCommon(JSContext* cx, nsWrapperCache* creator, + size_t length, ErrorResult& error) { + JS::Rooted<JSObject*> creatorWrapper(cx); + Maybe<JSAutoRealm> ar; + if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) { + ar.emplace(cx, creatorWrapper); + } + + return CreateCommon(cx, length, error); + } + static inline ArrayT CreateCommon(JSContext* cx, size_t length, + ErrorResult& error) { + ArrayT array = CreateCommon(cx, length); + if (array) { + return array; + } + error.StealExceptionFromJSContext(cx); + return ArrayT::fromObject(nullptr); + } + // NOTE: this leaves any exceptions on the JSContext, and the caller is + // required to deal with them. + static inline ArrayT CreateCommon(JSContext* cx, size_t length) { + return ArrayT::create(cx, length); + } + static inline void CopyFrom(JSContext* cx, + const Span<const element_type>& data, + ArrayT& dest) { + JS::AutoCheckCannotGC nogc; + bool isShared; + mozilla::Span<element_type> span = dest.getData(&isShared, nogc); + MOZ_ASSERT(span.size() == data.size(), + "Didn't create a large enough typed array object?"); + // Data will not be shared, until a construction protocol exists + // for constructing shared data. + MOZ_ASSERT(!isShared); + memcpy(span.Elements(), data.Elements(), data.LengthBytes()); + } + + TypedArray(const TypedArray&) = delete; +}; + +template <JS::Scalar::Type GetViewType(JSObject*)> +struct ArrayBufferView_base : public TypedArray_base<JS::ArrayBufferView> { + private: + using Base = TypedArray_base<JS::ArrayBufferView>; + + 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<JS::Int8Array>; +using Uint8Array = TypedArray<JS::Uint8Array>; +using Uint8ClampedArray = TypedArray<JS::Uint8ClampedArray>; +using Int16Array = TypedArray<JS::Int16Array>; +using Uint16Array = TypedArray<JS::Uint16Array>; +using Int32Array = TypedArray<JS::Int32Array>; +using Uint32Array = TypedArray<JS::Uint32Array>; +using Float32Array = TypedArray<JS::Float32Array>; +using Float64Array = TypedArray<JS::Float64Array>; +using ArrayBufferView = ArrayBufferView_base<JS_GetArrayBufferViewType>; +using ArrayBuffer = TypedArray<JS::ArrayBuffer>; + +// 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 <typename TypedArrayType> +class MOZ_STACK_CLASS TypedArrayCreator { + typedef nsTArray<typename TypedArrayType::element_type> ArrayType; + + public: + explicit TypedArrayCreator(const ArrayType& aArray) : mArray(aArray) {} + + // NOTE: this leaves any exceptions on the JSContext, and the caller is + // required to deal with them. + JSObject* Create(JSContext* aCx) const { + auto array = TypedArrayType::CreateCommon(aCx, mArray.Length()); + if (array) { + TypedArrayType::CopyFrom(aCx, mArray, array); + } + return array.asObject(); + } + + private: + const ArrayType& mArray; +}; + +namespace binding_detail { + +template <typename Union, typename UnionMemberType, typename = int> +struct ApplyToTypedArray; + +#define APPLY_IMPL(type) \ + template <typename Union> \ + struct ApplyToTypedArray<Union, type, decltype((void)&Union::Is##type, 0)> { \ + /* Return type of calling the lambda with a TypedArray 'type'. */ \ + template <typename F> \ + using FunReturnType = decltype(std::declval<F>()(std::declval<type>())); \ + \ + /* Whether the return type of calling the lambda with a TypedArray */ \ + /* 'type' is void. */ \ + template <typename F> \ + static constexpr bool FunReturnsVoid = \ + std::is_same_v<FunReturnType<F>, void>; \ + \ + /* The return type of calling Apply with a union that has 'type' as */ \ + /* one of its union member types depends on the return type of */ \ + /* calling the lambda. This return type will be bool if the lambda */ \ + /* returns void, or it will be a Maybe<…> with the inner type being */ \ + /* the actual return type of calling the lambda. If the union */ \ + /* contains a value of the right type, then calling Apply will return */ \ + /* either 'true', or 'Some(…)' containing the return value of calling */ \ + /* the lambda. If the union does not contain a value of the right */ \ + /* type, then calling Apply will return either 'false', or */ \ + /* 'Nothing()'. */ \ + template <typename F> \ + using ApplyReturnType = \ + std::conditional_t<FunReturnsVoid<F>, bool, Maybe<FunReturnType<F>>>; \ + \ + public: \ + template <typename F> \ + static ApplyReturnType<F> Apply(const Union& aUnion, F&& aFun) { \ + if (!aUnion.Is##type()) { \ + return ApplyReturnType<F>(); /* false or Nothing() */ \ + } \ + if constexpr (FunReturnsVoid<F>) { \ + std::forward<F>(aFun)(aUnion.GetAs##type()); \ + return true; \ + } else { \ + return Some(std::forward<F>(aFun)(aUnion.GetAs##type())); \ + } \ + } \ + }; + +APPLY_IMPL(Int8Array) +APPLY_IMPL(Uint8Array) +APPLY_IMPL(Uint8ClampedArray) +APPLY_IMPL(Int16Array) +APPLY_IMPL(Uint16Array) +APPLY_IMPL(Int32Array) +APPLY_IMPL(Uint32Array) +APPLY_IMPL(Float32Array) +APPLY_IMPL(Float64Array) +APPLY_IMPL(ArrayBufferView) +APPLY_IMPL(ArrayBuffer) + +#undef APPLY_IMPL + +// The binding code generate creates an alias of this type for every WebIDL +// union that contains a typed array type, with the right value for H (which +// will be true if there are non-typedarray types in the union). +template <typename T, bool H, typename FirstUnionMember, + typename... UnionMembers> +struct ApplyToTypedArraysHelper { + static constexpr bool HasNonTypedArrayMembers = H; + template <typename Fun> + static auto Apply(const T& aUnion, Fun&& aFun) { + auto result = ApplyToTypedArray<T, FirstUnionMember>::Apply( + aUnion, std::forward<Fun>(aFun)); + if constexpr (sizeof...(UnionMembers) == 0) { + return result; + } else { + if (result) { + return result; + } else { + return ApplyToTypedArraysHelper<T, H, UnionMembers...>::template Apply< + Fun>(aUnion, std::forward<Fun>(aFun)); + } + } + } +}; + +template <typename T, typename Fun> +auto ApplyToTypedArrays(const T& aUnion, Fun&& aFun) { + using ApplyToTypedArrays = typename T::ApplyToTypedArrays; + + auto result = + ApplyToTypedArrays::template Apply<Fun>(aUnion, std::forward<Fun>(aFun)); + if constexpr (ApplyToTypedArrays::HasNonTypedArrayMembers) { + return result; + } else { + MOZ_ASSERT(result, "Didn't expect union members other than typed arrays"); + + if constexpr (std::is_same_v<std::remove_cv_t< + std::remove_reference_t<decltype(result)>>, + bool>) { + return; + } else { + return result.extract(); + } + } +} + +} // namespace binding_detail + +template <typename T, typename ToType, + std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> +[[nodiscard]] auto AppendTypedArrayDataTo(const T& aUnion, ToType& aResult) { + return binding_detail::ApplyToTypedArrays( + aUnion, [&](const auto& aTypedArray) { + return aTypedArray.AppendDataTo(aResult); + }); +} + +template <typename ToType, typename T, + std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> +[[nodiscard]] auto CreateFromTypedArrayData(const T& aUnion) { + return binding_detail::ApplyToTypedArrays( + aUnion, [&](const auto& aTypedArray) { + return aTypedArray.template CreateFromData<ToType>(); + }); +} + +template <typename T, typename Processor, + std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> +[[nodiscard]] auto ProcessTypedArrays(const T& aUnion, Processor&& aProcessor) { + return binding_detail::ApplyToTypedArrays( + aUnion, [&](const auto& aTypedArray) { + return aTypedArray.ProcessData(std::forward<Processor>(aProcessor)); + }); +} + +template <typename T, typename Processor, + std::enable_if_t<is_dom_union_with_typedarray_members<T>, int> = 0> +[[nodiscard]] auto ProcessTypedArraysFixed(const T& aUnion, + Processor&& aProcessor) { + return binding_detail::ApplyToTypedArrays( + aUnion, [&](const auto& aTypedArray) { + return aTypedArray.ProcessFixedData( + std::forward<Processor>(aProcessor)); + }); +} + +} // namespace mozilla::dom + +#endif /* mozilla_dom_TypedArray_h */ |