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/. */
#include "vm/TypedArrayObject-inl.h"
#include "vm/TypedArrayObject.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/IntegerTypeTraits.h"
#include "mozilla/Likely.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/TextUtils.h"
#include <algorithm>
#include <iterator>
#include <limits>
#include <numeric>
#include <string.h>
#include <string_view>
#if !defined(XP_WIN) && !defined(__wasi__)
# include <sys/mman.h>
#endif
#include <type_traits>
#include "jsnum.h"
#include "jstypes.h"
#include "builtin/Array.h"
#include "builtin/DataViewObject.h"
#include "gc/Barrier.h"
#include "gc/MaybeRooted.h"
#include "jit/InlinableNatives.h"
#include "jit/TrampolineNatives.h"
#include "js/Conversions.h"
#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewType, JS_GetTypedArray{Length,ByteOffset,ByteLength}, JS_IsTypedArrayObject
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/PropertySpec.h"
#include "js/ScalarType.h" // JS::Scalar::Type
#include "js/UniquePtr.h"
#include "js/Wrapper.h"
#include "util/DifferentialTesting.h"
#include "util/StringBuilder.h"
#include "util/Text.h"
#include "util/WindowsWrapper.h"
#include "vm/ArrayBufferObject.h"
#include "vm/Float16.h"
#include "vm/FunctionFlags.h" // js::FunctionFlags
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/PIC.h"
#include "vm/SelfHosting.h"
#include "vm/SharedMem.h"
#include "vm/Uint8Clamped.h"
#include "vm/WrapperObject.h"
#include "builtin/Sorting-inl.h"
#include "gc/Nursery-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/Compartment-inl.h"
#include "vm/GeckoProfiler-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using mozilla::IsAsciiDigit;
/*
* TypedArrayObject
*
* The non-templated base class for the specific typed implementations.
* This class holds all the member variables that are used by
* the subclasses.
*/
bool TypedArrayObject::convertValue(JSContext* cx, HandleValue v,
MutableHandleValue result) const {
switch (type()) {
case Scalar::BigInt64:
case Scalar::BigUint64: {
BigInt* bi = ToBigInt(cx, v);
if (!bi) {
return false;
}
result.setBigInt(bi);
return true;
}
case Scalar::Int8:
case Scalar::Uint8:
case Scalar::Int16:
case Scalar::Uint16:
case Scalar::Int32:
case Scalar::Uint32:
case Scalar::Float16:
case Scalar::Float32:
case Scalar::Float64:
case Scalar::Uint8Clamped: {
double num;
if (!ToNumber(cx, v, &num)) {
return false;
}
result.setNumber(num);
return true;
}
case Scalar::MaxTypedArrayViewType:
case Scalar::Int64:
case Scalar::Simd128:
MOZ_CRASH("Unsupported TypedArray type");
}
MOZ_ASSERT_UNREACHABLE("Invalid scalar type");
return false;
}
static bool IsTypedArrayObject(HandleValue v) {
return v.isObject() && v.toObject().is<TypedArrayObject>();
}
#ifdef NIGHTLY_BUILD
static bool IsUint8ArrayObject(HandleValue v) {
return IsTypedArrayObject(v) &&
v.toObject().as<TypedArrayObject>().type() == Scalar::Uint8;
}
#endif
/* static */
bool TypedArrayObject::ensureHasBuffer(JSContext* cx,
Handle<TypedArrayObject*> typedArray) {
if (typedArray->hasBuffer()) {
return true;
}
MOZ_ASSERT(typedArray->is<FixedLengthTypedArrayObject>(),
"Resizable TypedArrays always use an ArrayBuffer");
Rooted<FixedLengthTypedArrayObject*> tarray(
cx, &typedArray->as<FixedLengthTypedArrayObject>());
size_t byteLength = tarray->byteLength();
AutoRealm ar(cx, tarray);
Rooted<ArrayBufferObject*> buffer(
cx, ArrayBufferObject::createZeroed(cx, tarray->byteLength()));
if (!buffer) {
return false;
}
buffer->pinLength(tarray->isLengthPinned());
// Attaching the first view to an array buffer is infallible.
MOZ_ALWAYS_TRUE(buffer->addView(cx, tarray));
// tarray is not shared, because if it were it would have a buffer.
memcpy(buffer->dataPointer(), tarray->dataPointerUnshared(), byteLength);
// If the object is in the nursery, the buffer will be freed by the next
// nursery GC. Free the data slot pointer if the object has no inline data.
size_t nbytes = RoundUp(byteLength, sizeof(Value));
Nursery& nursery = cx->nursery();
if (tarray->isTenured() && !tarray->hasInlineElements() &&
!nursery.isInside(tarray->elements())) {
js_free(tarray->elements());
RemoveCellMemory(tarray, nbytes, MemoryUse::TypedArrayElements);
}
tarray->setFixedSlot(TypedArrayObject::DATA_SLOT,
PrivateValue(buffer->dataPointer()));
tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*buffer));
return true;
}
#ifdef DEBUG
void FixedLengthTypedArrayObject::assertZeroLengthArrayData() const {
if (length() == 0 && !hasBuffer()) {
uint8_t* end = fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
MOZ_ASSERT(end[0] == ZeroLengthArrayData);
}
}
#endif
void FixedLengthTypedArrayObject::finalize(JS::GCContext* gcx, JSObject* obj) {
MOZ_ASSERT(!IsInsideNursery(obj));
auto* curObj = &obj->as<FixedLengthTypedArrayObject>();
// Template objects or discarded objects (which didn't have enough room
// for inner elements) don't have anything to free.
if (!curObj->elementsRaw()) {
return;
}
curObj->assertZeroLengthArrayData();
// Typed arrays with a buffer object do not need to be free'd
if (curObj->hasBuffer()) {
return;
}
// Free the data slot pointer if it does not point into the old JSObject.
if (!curObj->hasInlineElements()) {
size_t nbytes = RoundUp(curObj->byteLength(), sizeof(Value));
gcx->free_(obj, curObj->elements(), nbytes, MemoryUse::TypedArrayElements);
}
}
/* static */
size_t FixedLengthTypedArrayObject::objectMoved(JSObject* obj, JSObject* old) {
auto* newObj = &obj->as<FixedLengthTypedArrayObject>();
const auto* oldObj = &old->as<FixedLengthTypedArrayObject>();
MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw());
// Typed arrays with a buffer object do not need an update.
if (oldObj->hasBuffer()) {
return 0;
}
if (!IsInsideNursery(old)) {
// Update the data slot pointer if it points to the old JSObject.
if (oldObj->hasInlineElements()) {
newObj->setInlineElements();
}
return 0;
}
void* buf = oldObj->elements();
// Discarded objects (which didn't have enough room for inner elements) don't
// have any data to move.
if (!buf) {
return 0;
}
Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery();
// Determine if we can use inline data for the target array. If this is
// possible, the nursery will have picked an allocation size that is large
// enough.
size_t nbytes = oldObj->byteLength();
bool canUseDirectForward = nbytes >= sizeof(uintptr_t);
constexpr size_t headerSize = dataOffset() + sizeof(HeapSlot);
gc::AllocKind allocKind = oldObj->allocKindForTenure();
MOZ_ASSERT_IF(obj->isTenured(), obj->asTenured().getAllocKind() == allocKind);
MOZ_ASSERT_IF(nbytes == 0,
headerSize + sizeof(uint8_t) <= GetGCKindBytes(allocKind));
if (nursery.isInside(buf) &&
headerSize + nbytes <= GetGCKindBytes(allocKind)) {
MOZ_ASSERT(oldObj->hasInlineElements());
#ifdef DEBUG
if (nbytes == 0) {
uint8_t* output =
newObj->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
output[0] = ZeroLengthArrayData;
}
#endif
newObj->setInlineElements();
mozilla::PodCopy(newObj->elements(), oldObj->elements(), nbytes);
// Set a forwarding pointer for the element buffers in case they were
// preserved on the stack by Ion.
nursery.setForwardingPointerWhileTenuring(
oldObj->elements(), newObj->elements(), canUseDirectForward);
return 0;
}
// Non-inline allocations are rounded up.
nbytes = RoundUp(nbytes, sizeof(Value));
Nursery::WasBufferMoved result = nursery.maybeMoveBufferOnPromotion(
&buf, newObj, nbytes, MemoryUse::TypedArrayElements,
ArrayBufferContentsArena);
if (result == Nursery::BufferMoved) {
newObj->setReservedSlot(DATA_SLOT, PrivateValue(buf));
// Set a forwarding pointer for the element buffers in case they were
// preserved on the stack by Ion.
nursery.setForwardingPointerWhileTenuring(
oldObj->elements(), newObj->elements(), canUseDirectForward);
return nbytes;
}
return 0;
}
bool FixedLengthTypedArrayObject::hasInlineElements() const {
return elements() ==
this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START) &&
byteLength() <= FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
}
void FixedLengthTypedArrayObject::setInlineElements() {
char* dataSlot = reinterpret_cast<char*>(this) + dataOffset();
*reinterpret_cast<void**>(dataSlot) =
this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
}
/* Helper clamped uint8_t type */
uint32_t js::ClampDoubleToUint8(const double x) {
// Not < so that NaN coerces to 0
if (!(x > 0)) {
return 0;
}
if (x >= 255) {
return 255;
}
// Convert with truncation.
uint8_t y = uint8_t(x);
// Now |y| is rounded toward zero. We want rounded to nearest, ties to even.
double r = x - double(y);
// It was a tie (since the difference is exactly 0.5). Round up if the number
// is odd.
if (r == 0.5) {
return y + (y & 1);
}
// Round up if truncation incorrectly rounded down.
return y + (r > 0.5);
}
static void ReportOutOfBounds(JSContext* cx, TypedArrayObject* typedArray) {
if (typedArray->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_DETACHED);
} else {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_RESIZED_BOUNDS);
}
}
namespace {
template <class TypedArrayType>
static TypedArrayType* NewTypedArrayObject(JSContext* cx, const JSClass* clasp,
HandleObject proto,
gc::AllocKind allocKind,
gc::Heap heap) {
MOZ_ASSERT(proto);
MOZ_ASSERT(CanChangeToBackgroundAllocKind(allocKind, clasp));
allocKind = ForegroundToBackgroundAllocKind(allocKind);
static_assert(std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> ||
std::is_same_v<TypedArrayType, ResizableTypedArrayObject>);
// Fixed length typed arrays can store data inline so we only use fixed slots
// to cover the reserved slots, ignoring the AllocKind.
MOZ_ASSERT(ClassCanHaveFixedData(clasp));
constexpr size_t nfixed = TypedArrayType::RESERVED_SLOTS;
static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS);
static_assert(!std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> ||
nfixed == FixedLengthTypedArrayObject::FIXED_DATA_START);
Rooted<SharedShape*> shape(
cx,
SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto),
nfixed, ObjectFlags()));
if (!shape) {
return nullptr;
}
return NativeObject::create<TypedArrayType>(cx, allocKind, heap, shape);
}
template <typename NativeType>
class FixedLengthTypedArrayObjectTemplate;
template <typename NativeType>
class ResizableTypedArrayObjectTemplate;
template <typename NativeType>
class TypedArrayObjectTemplate {
friend class js::TypedArrayObject;
using FixedLengthTypedArray = FixedLengthTypedArrayObjectTemplate<NativeType>;
using ResizableTypedArray = ResizableTypedArrayObjectTemplate<NativeType>;
using AutoLength = ArrayBufferViewObject::AutoLength;
static constexpr auto ByteLengthLimit = TypedArrayObject::ByteLengthLimit;
static constexpr auto INLINE_BUFFER_LIMIT =
FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
public:
static constexpr Scalar::Type ArrayTypeID() {
return TypeIDOfType<NativeType>::id;
}
static constexpr JSProtoKey protoKey() {
return TypeIDOfType<NativeType>::protoKey;
}
static constexpr size_t BYTES_PER_ELEMENT = sizeof(NativeType);
static JSObject* createPrototype(JSContext* cx, JSProtoKey key) {
Handle<GlobalObject*> global = cx->global();
RootedObject typedArrayProto(
cx, GlobalObject::getOrCreateTypedArrayPrototype(cx, global));
if (!typedArrayProto) {
return nullptr;
}
const JSClass* clasp = TypedArrayObject::protoClassForType(ArrayTypeID());
return GlobalObject::createBlankPrototypeInheriting(cx, clasp,
typedArrayProto);
}
static JSObject* createConstructor(JSContext* cx, JSProtoKey key) {
Handle<GlobalObject*> global = cx->global();
RootedFunction ctorProto(
cx, GlobalObject::getOrCreateTypedArrayConstructor(cx, global));
if (!ctorProto) {
return nullptr;
}
JSFunction* fun = NewFunctionWithProto(
cx, class_constructor, 3, FunctionFlags::NATIVE_CTOR, nullptr,
ClassName(key, cx), ctorProto, gc::AllocKind::FUNCTION, TenuredObject);
if (fun) {
fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor);
}
return fun;
}
static bool convertValue(JSContext* cx, HandleValue v, NativeType* result);
static TypedArrayObject* makeTypedArrayWithTemplate(
JSContext* cx, TypedArrayObject* templateObj, HandleObject array) {
MOZ_ASSERT(!IsWrapper(array));
MOZ_ASSERT(!array->is<ArrayBufferObjectMaybeShared>());
return fromArray(cx, array);
}
static TypedArrayObject* makeTypedArrayWithTemplate(
JSContext* cx, TypedArrayObject* templateObj, HandleObject arrayBuffer,
HandleValue byteOffsetValue, HandleValue lengthValue) {
MOZ_ASSERT(!IsWrapper(arrayBuffer));
MOZ_ASSERT(arrayBuffer->is<ArrayBufferObjectMaybeShared>());
uint64_t byteOffset, length;
if (!byteOffsetAndLength(cx, byteOffsetValue, lengthValue, &byteOffset,
&length)) {
return nullptr;
}
return fromBufferSameCompartment(
cx, arrayBuffer.as<ArrayBufferObjectMaybeShared>(), byteOffset, length,
nullptr);
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1 TypedArray ( ...args )
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp) {
AutoJSConstructorProfilerEntry pseudoFrame(cx, "[TypedArray]");
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!ThrowIfNotConstructing(cx, args, "typed array")) {
return false;
}
// Steps 2-6.
JSObject* obj = create(cx, args);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
private:
static JSObject* create(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(args.isConstructing());
// Steps 5 and 6.c.
if (args.length() == 0 || !args[0].isObject()) {
// Step 6.c.ii.
uint64_t len;
if (!ToIndex(cx, args.get(0), JSMSG_BAD_ARRAY_LENGTH, &len)) {
return nullptr;
}
// Steps 5.a and 6.c.iii.
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
return nullptr;
}
return fromLength(cx, len, proto);
}
RootedObject dataObj(cx, &args[0].toObject());
// Step 6.b.i.
// 23.2.5.1.1 AllocateTypedArray, step 1.
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
return nullptr;
}
// Steps 6.b.ii and 6.b.iv.
if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>()) {
return fromArray(cx, dataObj, proto);
}
// Steps 6.b.iii.1-2.
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer, steps 2 and 4.
uint64_t byteOffset, length;
if (!byteOffsetAndLength(cx, args.get(1), args.get(2), &byteOffset,
&length)) {
return nullptr;
}
// Step 6.b.iii.3.
if (dataObj->is<ArrayBufferObjectMaybeShared>()) {
auto buffer = dataObj.as<ArrayBufferObjectMaybeShared>();
return fromBufferSameCompartment(cx, buffer, byteOffset, length, proto);
}
return fromBufferWrapped(cx, dataObj, byteOffset, length, proto);
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
// length ) Steps 2 and 4.
static bool byteOffsetAndLength(JSContext* cx, HandleValue byteOffsetValue,
HandleValue lengthValue, uint64_t* byteOffset,
uint64_t* length) {
// Step 2.
*byteOffset = 0;
if (!byteOffsetValue.isUndefined()) {
if (!ToIndex(cx, byteOffsetValue, byteOffset)) {
return false;
}
// Step 7.
if (*byteOffset % BYTES_PER_ELEMENT != 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
Scalar::name(ArrayTypeID()),
Scalar::byteSizeString(ArrayTypeID()));
return false;
}
}
// Step 4.
*length = UINT64_MAX;
if (!lengthValue.isUndefined()) {
if (!ToIndex(cx, lengthValue, length)) {
return false;
}
}
return true;
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
// length ) Steps 5-8.
static bool computeAndCheckLength(
JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> bufferMaybeUnwrapped,
uint64_t byteOffset, uint64_t lengthIndex, size_t* length,
AutoLength* autoLength) {
MOZ_ASSERT(byteOffset % BYTES_PER_ELEMENT == 0);
MOZ_ASSERT(byteOffset < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
MOZ_ASSERT_IF(lengthIndex != UINT64_MAX,
lengthIndex < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
// Step 5.
if (bufferMaybeUnwrapped->isDetached()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
// Step 6.
size_t bufferByteLength = bufferMaybeUnwrapped->byteLength();
MOZ_ASSERT(bufferByteLength <= ByteLengthLimit);
size_t len;
if (lengthIndex == UINT64_MAX) {
// Check if |byteOffset| valid.
if (byteOffset > bufferByteLength) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_LENGTH_BOUNDS,
Scalar::name(ArrayTypeID()));
return false;
}
// Resizable buffers without an explicit length are auto-length.
if (bufferMaybeUnwrapped->isResizable()) {
*length = 0;
*autoLength = AutoLength::Yes;
return true;
}
// Steps 7.a and 7.c.
if (bufferByteLength % BYTES_PER_ELEMENT != 0) {
// The given byte array doesn't map exactly to
// |BYTES_PER_ELEMENT * N|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_MISALIGNED,
Scalar::name(ArrayTypeID()),
Scalar::byteSizeString(ArrayTypeID()));
return false;
}
// Step 7.b.
size_t newByteLength = bufferByteLength - size_t(byteOffset);
len = newByteLength / BYTES_PER_ELEMENT;
} else {
// Step 8.a.
uint64_t newByteLength = lengthIndex * BYTES_PER_ELEMENT;
// Step 8.b.
if (byteOffset + newByteLength > bufferByteLength) {
// |byteOffset + newByteLength| is too big for the arraybuffer
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_ARRAY_LENGTH_BOUNDS,
Scalar::name(ArrayTypeID()));
return false;
}
len = size_t(lengthIndex);
}
MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
*length = len;
*autoLength = AutoLength::No;
return true;
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
// length ) Steps 5-13.
static TypedArrayObject* fromBufferSameCompartment(
JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto) {
// Steps 5-8.
size_t length = 0;
auto autoLength = AutoLength::No;
if (!computeAndCheckLength(cx, buffer, byteOffset, lengthIndex, &length,
&autoLength)) {
return nullptr;
}
if (!buffer->isResizable()) {
// Steps 9-13.
return FixedLengthTypedArray::makeInstance(cx, buffer, byteOffset, length,
proto);
}
return ResizableTypedArray::makeInstance(cx, buffer, byteOffset, length,
autoLength, proto);
}
// Create a TypedArray object in another compartment.
//
// ES6 supports creating a TypedArray in global A (using global A's
// TypedArray constructor) backed by an ArrayBuffer created in global B.
//
// Our TypedArrayObject implementation doesn't support a TypedArray in
// compartment A backed by an ArrayBuffer in compartment B. So in this
// case, we create the TypedArray in B (!) and return a cross-compartment
// wrapper.
//
// Extra twist: the spec says the new TypedArray's [[Prototype]] must be
// A's TypedArray.prototype. So even though we're creating the TypedArray
// in B, its [[Prototype]] must be (a cross-compartment wrapper for) the
// TypedArray.prototype in A.
static JSObject* fromBufferWrapped(JSContext* cx, HandleObject bufobj,
uint64_t byteOffset, uint64_t lengthIndex,
HandleObject proto) {
JSObject* unwrapped = CheckedUnwrapStatic(bufobj);
if (!unwrapped) {
ReportAccessDenied(cx);
return nullptr;
}
if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_BAD_ARGS);
return nullptr;
}
Rooted<ArrayBufferObjectMaybeShared*> unwrappedBuffer(cx);
unwrappedBuffer = &unwrapped->as<ArrayBufferObjectMaybeShared>();
size_t length = 0;
auto autoLength = AutoLength::No;
if (!computeAndCheckLength(cx, unwrappedBuffer, byteOffset, lengthIndex,
&length, &autoLength)) {
return nullptr;
}
// Make sure to get the [[Prototype]] for the created typed array from
// this compartment.
RootedObject protoRoot(cx, proto);
if (!protoRoot) {
protoRoot = GlobalObject::getOrCreatePrototype(cx, protoKey());
if (!protoRoot) {
return nullptr;
}
}
RootedObject typedArray(cx);
{
JSAutoRealm ar(cx, unwrappedBuffer);
RootedObject wrappedProto(cx, protoRoot);
if (!cx->compartment()->wrap(cx, &wrappedProto)) {
return nullptr;
}
if (!unwrappedBuffer->isResizable()) {
typedArray = FixedLengthTypedArray::makeInstance(
cx, unwrappedBuffer, byteOffset, length, wrappedProto);
} else {
typedArray = ResizableTypedArray::makeInstance(
cx, unwrappedBuffer, byteOffset, length, autoLength, wrappedProto);
}
if (!typedArray) {
return nullptr;
}
}
if (!cx->compartment()->wrap(cx, &typedArray)) {
return nullptr;
}
return typedArray;
}
public:
static JSObject* fromBuffer(JSContext* cx, HandleObject bufobj,
size_t byteOffset, int64_t lengthInt) {
if (byteOffset % BYTES_PER_ELEMENT != 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
Scalar::name(ArrayTypeID()),
Scalar::byteSizeString(ArrayTypeID()));
return nullptr; // invalid byteOffset
}
uint64_t lengthIndex = lengthInt >= 0 ? uint64_t(lengthInt) : UINT64_MAX;
if (bufobj->is<ArrayBufferObjectMaybeShared>()) {
auto buffer = bufobj.as<ArrayBufferObjectMaybeShared>();
return fromBufferSameCompartment(cx, buffer, byteOffset, lengthIndex,
nullptr);
}
return fromBufferWrapped(cx, bufobj, byteOffset, lengthIndex, nullptr);
}
static bool maybeCreateArrayBuffer(JSContext* cx, uint64_t count,
MutableHandle<ArrayBufferObject*> buffer) {
if (count > ByteLengthLimit / BYTES_PER_ELEMENT) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_ARRAY_LENGTH);
return false;
}
size_t byteLength = count * BYTES_PER_ELEMENT;
MOZ_ASSERT(byteLength <= ByteLengthLimit);
static_assert(INLINE_BUFFER_LIMIT % BYTES_PER_ELEMENT == 0,
"ArrayBuffer inline storage shouldn't waste any space");
if (byteLength <= INLINE_BUFFER_LIMIT) {
// The array's data can be inline, and the buffer created lazily.
return true;
}
ArrayBufferObject* buf = ArrayBufferObject::createZeroed(cx, byteLength);
if (!buf) {
return false;
}
buffer.set(buf);
return true;
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.1 AllocateTypedArray ( constructorName, newTarget, defaultProto [
// , length ] )
static TypedArrayObject* fromLength(JSContext* cx, uint64_t nelements,
HandleObject proto = nullptr,
gc::Heap heap = gc::Heap::Default) {
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) {
return nullptr;
}
return FixedLengthTypedArray::makeInstance(cx, buffer, 0, nelements, proto,
heap);
}
static TypedArrayObject* fromArray(JSContext* cx, HandleObject other,
HandleObject proto = nullptr);
static TypedArrayObject* fromTypedArray(JSContext* cx, HandleObject other,
bool isWrapped, HandleObject proto);
static TypedArrayObject* fromObject(JSContext* cx, HandleObject other,
HandleObject proto);
static const NativeType getIndex(TypedArrayObject* tarray, size_t index) {
MOZ_ASSERT(index < tarray->length().valueOr(0));
return jit::AtomicOperations::loadSafeWhenRacy(
tarray->dataPointerEither().cast<NativeType*>() + index);
}
static void setIndex(TypedArrayObject& tarray, size_t index, NativeType val) {
MOZ_ASSERT(index < tarray.length().valueOr(0));
jit::AtomicOperations::storeSafeWhenRacy(
tarray.dataPointerEither().cast<NativeType*>() + index, val);
}
static bool getElement(JSContext* cx, TypedArrayObject* tarray, size_t index,
MutableHandleValue val);
static bool getElementPure(TypedArrayObject* tarray, size_t index, Value* vp);
static bool setElement(JSContext* cx, Handle<TypedArrayObject*> obj,
uint64_t index, HandleValue v, ObjectOpResult& result);
};
template <typename NativeType>
class FixedLengthTypedArrayObjectTemplate
: public FixedLengthTypedArrayObject,
public TypedArrayObjectTemplate<NativeType> {
friend class js::TypedArrayObject;
using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>;
public:
using TypedArrayTemplate::ArrayTypeID;
using TypedArrayTemplate::BYTES_PER_ELEMENT;
using TypedArrayTemplate::protoKey;
static inline const JSClass* instanceClass() {
static_assert(ArrayTypeID() <
std::size(TypedArrayObject::fixedLengthClasses));
return &TypedArrayObject::fixedLengthClasses[ArrayTypeID()];
}
static FixedLengthTypedArrayObject* newBuiltinClassInstance(
JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) {
RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey()));
if (!proto) {
return nullptr;
}
return NewTypedArrayObject<FixedLengthTypedArrayObject>(
cx, instanceClass(), proto, allocKind, heap);
}
static FixedLengthTypedArrayObject* makeProtoInstance(
JSContext* cx, HandleObject proto, gc::AllocKind allocKind) {
MOZ_ASSERT(proto);
return NewTypedArrayObject<FixedLengthTypedArrayObject>(
cx, instanceClass(), proto, allocKind, gc::Heap::Default);
}
static FixedLengthTypedArrayObject* makeInstance(
JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
size_t byteOffset, size_t len, HandleObject proto,
gc::Heap heap = gc::Heap::Default) {
MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
gc::AllocKind allocKind =
buffer ? gc::GetGCObjectKind(instanceClass())
: AllocKindForLazyBuffer(len * BYTES_PER_ELEMENT);
AutoSetNewObjectMetadata metadata(cx);
FixedLengthTypedArrayObject* obj;
if (proto) {
obj = makeProtoInstance(cx, proto, allocKind);
} else {
obj = newBuiltinClassInstance(cx, allocKind, heap);
}
if (!obj || !obj->init(cx, buffer, byteOffset, len, BYTES_PER_ELEMENT)) {
return nullptr;
}
return obj;
}
static FixedLengthTypedArrayObject* makeTemplateObject(JSContext* cx,
int32_t len) {
MOZ_ASSERT(len >= 0);
size_t nbytes;
MOZ_ALWAYS_TRUE(CalculateAllocSize<NativeType>(len, &nbytes));
bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass())
: AllocKindForLazyBuffer(nbytes);
MOZ_ASSERT(allocKind >= gc::GetGCObjectKind(instanceClass()));
AutoSetNewObjectMetadata metadata(cx);
auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured);
if (!tarray) {
return nullptr;
}
initTypedArraySlots(tarray, len);
// Template objects don't need memory for their elements, since there
// won't be any elements to store.
MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined());
return tarray;
}
static void initTypedArraySlots(FixedLengthTypedArrayObject* tarray,
int32_t len) {
MOZ_ASSERT(len >= 0);
tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue());
tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT, PrivateValue(len));
tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT,
PrivateValue(size_t(0)));
#ifdef DEBUG
if (len == 0) {
uint8_t* output =
tarray->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
output[0] = TypedArrayObject::ZeroLengthArrayData;
}
#endif
}
static void initTypedArrayData(FixedLengthTypedArrayObject* tarray, void* buf,
size_t nbytes, gc::AllocKind allocKind) {
if (buf) {
InitReservedSlot(tarray, TypedArrayObject::DATA_SLOT, buf, nbytes,
MemoryUse::TypedArrayElements);
} else {
#ifdef DEBUG
constexpr size_t dataOffset = ArrayBufferViewObject::dataOffset();
constexpr size_t offset = dataOffset + sizeof(HeapSlot);
MOZ_ASSERT(offset + nbytes <= GetGCKindBytes(allocKind));
#endif
void* data = tarray->fixedData(FIXED_DATA_START);
tarray->initReservedSlot(DATA_SLOT, PrivateValue(data));
memset(data, 0, nbytes);
}
}
static FixedLengthTypedArrayObject* makeTypedArrayWithTemplate(
JSContext* cx, TypedArrayObject* templateObj, int32_t len) {
if (len < 0 || size_t(len) > ByteLengthLimit / BYTES_PER_ELEMENT) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_ARRAY_LENGTH);
return nullptr;
}
size_t nbytes = size_t(len) * BYTES_PER_ELEMENT;
MOZ_ASSERT(nbytes <= ByteLengthLimit);
bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
AutoSetNewObjectMetadata metadata(cx);
gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass())
: AllocKindForLazyBuffer(nbytes);
MOZ_ASSERT(templateObj->getClass() == instanceClass());
RootedObject proto(cx, templateObj->staticPrototype());
auto* obj = makeProtoInstance(cx, proto, allocKind);
if (!obj) {
return nullptr;
}
initTypedArraySlots(obj, len);
void* buf = nullptr;
if (!fitsInline) {
MOZ_ASSERT(len > 0);
nbytes = RoundUp(nbytes, sizeof(Value));
buf = cx->nursery().allocateZeroedBuffer(obj, nbytes,
js::ArrayBufferContentsArena);
if (!buf) {
ReportOutOfMemory(cx);
return nullptr;
}
}
initTypedArrayData(obj, buf, nbytes, allocKind);
return obj;
}
};
template <typename NativeType>
class ResizableTypedArrayObjectTemplate
: public ResizableTypedArrayObject,
public TypedArrayObjectTemplate<NativeType> {
friend class js::TypedArrayObject;
using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>;
public:
using TypedArrayTemplate::ArrayTypeID;
using TypedArrayTemplate::BYTES_PER_ELEMENT;
using TypedArrayTemplate::protoKey;
static inline const JSClass* instanceClass() {
static_assert(ArrayTypeID() <
std::size(TypedArrayObject::resizableClasses));
return &TypedArrayObject::resizableClasses[ArrayTypeID()];
}
static ResizableTypedArrayObject* newBuiltinClassInstance(
JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) {
RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey()));
if (!proto) {
return nullptr;
}
return NewTypedArrayObject<ResizableTypedArrayObject>(
cx, instanceClass(), proto, allocKind, heap);
}
static ResizableTypedArrayObject* makeProtoInstance(JSContext* cx,
HandleObject proto,
gc::AllocKind allocKind) {
MOZ_ASSERT(proto);
return NewTypedArrayObject<ResizableTypedArrayObject>(
cx, instanceClass(), proto, allocKind, gc::Heap::Default);
}
static ResizableTypedArrayObject* makeInstance(
JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
size_t byteOffset, size_t len, AutoLength autoLength,
HandleObject proto) {
MOZ_ASSERT(buffer);
MOZ_ASSERT(buffer->isResizable());
MOZ_ASSERT(!buffer->isDetached());
MOZ_ASSERT(autoLength == AutoLength::No || len == 0,
"length is zero for 'auto' length views");
MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass());
AutoSetNewObjectMetadata metadata(cx);
ResizableTypedArrayObject* obj;
if (proto) {
obj = makeProtoInstance(cx, proto, allocKind);
} else {
obj = newBuiltinClassInstance(cx, allocKind, gc::Heap::Default);
}
if (!obj || !obj->initResizable(cx, buffer, byteOffset, len,
BYTES_PER_ELEMENT, autoLength)) {
return nullptr;
}
return obj;
}
static ResizableTypedArrayObject* makeTemplateObject(JSContext* cx) {
gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass());
AutoSetNewObjectMetadata metadata(cx);
auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured);
if (!tarray) {
return nullptr;
}