Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 */