From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/vm/TypedArrayObject.cpp | 3380 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3380 insertions(+) create mode 100644 js/src/vm/TypedArrayObject.cpp (limited to 'js/src/vm/TypedArrayObject.cpp') diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp new file mode 100644 index 0000000000..0264b481b3 --- /dev/null +++ b/js/src/vm/TypedArrayObject.cpp @@ -0,0 +1,3380 @@ +/* -*- 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/FloatingPoint.h" +#include "mozilla/IntegerTypeTraits.h" +#include "mozilla/PodOperations.h" +#include "mozilla/TextUtils.h" + +#include +#include +#include +#include +#include +#include +#if !defined(XP_WIN) && !defined(__wasi__) +# include +#endif +#include + +#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 "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/Text.h" +#include "util/WindowsWrapper.h" +#include "vm/ArrayBufferObject.h" +#include "vm/FunctionFlags.h" // js::FunctionFlags +#include "vm/GlobalObject.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 "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 JS::CanonicalizeNaN; +using JS::ToInt32; +using JS::ToUint32; +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::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(); +} + +/* static */ +bool TypedArrayObject::ensureHasBuffer(JSContext* cx, + Handle typedArray) { + if (typedArray->hasBuffer()) { + return true; + } + + MOZ_ASSERT(typedArray->is(), + "Resizable TypedArrays always use an ArrayBuffer"); + + Rooted tarray( + cx, &typedArray->as()); + + size_t byteLength = tarray->byteLength(); + + AutoRealm ar(cx, tarray); + Rooted 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(); + + // 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(); + const auto* oldObj = &old->as(); + MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw()); + MOZ_ASSERT(obj->isTenured()); + + // 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); + + // See AllocKindForLazyBuffer. + gc::AllocKind newAllocKind = obj->asTenured().getAllocKind(); + MOZ_ASSERT_IF(nbytes == 0, + headerSize + sizeof(uint8_t) <= GetGCKindBytes(newAllocKind)); + + if (nursery.isInside(buf) && + headerSize + nbytes <= GetGCKindBytes(newAllocKind)) { + 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(this) + dataOffset(); + *reinterpret_cast(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; + } + + double toTruncate = x + 0.5; + uint8_t y = uint8_t(toTruncate); + + /* + * now val is rounded to nearest, ties rounded up. We want + * rounded to nearest ties to even, so check whether we had a + * tie. + */ + if (y == toTruncate) { + /* + * It was a tie (since adding 0.5 gave us the exact integer + * we want). Since we rounded up, we either already have an + * even number or we have an odd number but the number we + * want is one less. So just unconditionally masking out the + * ones bit should do the trick to get us the value we + * want. + */ + return y & ~1; + } + + return y; +} + +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 +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 || + std::is_same_v); + + // 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 || + nfixed == FixedLengthTypedArrayObject::FIXED_DATA_START); + + Rooted shape( + cx, + SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto), + nfixed, ObjectFlags())); + if (!shape) { + return nullptr; + } + + return NativeObject::create(cx, allocKind, heap, shape); +} + +template +class FixedLengthTypedArrayObjectTemplate; + +template +class ResizableTypedArrayObjectTemplate; + +template +class TypedArrayObjectTemplate { + friend class js::TypedArrayObject; + + using FixedLengthTypedArray = FixedLengthTypedArrayObjectTemplate; + using ResizableTypedArray = ResizableTypedArrayObjectTemplate; + + static constexpr auto ByteLengthLimit = TypedArrayObject::ByteLengthLimit; + static constexpr auto INLINE_BUFFER_LIMIT = + FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT; + + public: + static constexpr Scalar::Type ArrayTypeID() { + return TypeIDOfType::id; + } + static constexpr JSProtoKey protoKey() { + return TypeIDOfType::protoKey; + } + + static constexpr bool ArrayTypeIsUnsigned() { + return TypeIsUnsigned(); + } + static constexpr bool ArrayTypeIsFloatingPoint() { + return TypeIsFloatingPoint(); + } + + static constexpr size_t BYTES_PER_ELEMENT = sizeof(NativeType); + + static JSObject* createPrototype(JSContext* cx, JSProtoKey key) { + Handle 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 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()); + + 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()); + + uint64_t byteOffset, length; + if (!byteOffsetAndLength(cx, byteOffsetValue, lengthValue, &byteOffset, + &length)) { + return nullptr; + } + + return fromBufferSameCompartment( + cx, arrayBuffer.as(), 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()) { + 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()) { + auto buffer = dataObj.as(); + 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 bufferMaybeUnwrapped, + uint64_t byteOffset, uint64_t lengthIndex, size_t* length, + bool* 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 = true; + 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 = false; + 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 buffer, + uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto) { + // Steps 5-8. + size_t length = 0; + bool autoLength = false; + 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()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_ARGS); + return nullptr; + } + + Rooted unwrappedBuffer(cx); + unwrappedBuffer = &unwrapped->as(); + + size_t length = 0; + bool autoLength = false; + 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()) { + auto buffer = bufobj.as(); + return fromBufferSameCompartment(cx, buffer, byteOffset, lengthIndex, + nullptr); + } + return fromBufferWrapped(cx, bufobj, byteOffset, lengthIndex, nullptr); + } + + static bool maybeCreateArrayBuffer(JSContext* cx, uint64_t count, + MutableHandle 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 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() + index); + } + + static void setIndex(TypedArrayObject& tarray, size_t index, NativeType val) { + MOZ_ASSERT(index < tarray.length().valueOr(0)); + jit::AtomicOperations::storeSafeWhenRacy( + tarray.dataPointerEither().cast() + 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 obj, + uint64_t index, HandleValue v, ObjectOpResult& result); +}; + +template +class FixedLengthTypedArrayObjectTemplate + : public FixedLengthTypedArrayObject, + public TypedArrayObjectTemplate { + friend class js::TypedArrayObject; + + using TypedArrayTemplate = TypedArrayObjectTemplate; + + 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( + cx, instanceClass(), proto, allocKind, heap); + } + + static FixedLengthTypedArrayObject* makeProtoInstance( + JSContext* cx, HandleObject proto, gc::AllocKind allocKind) { + MOZ_ASSERT(proto); + return NewTypedArrayObject( + cx, instanceClass(), proto, allocKind, gc::Heap::Default); + } + + static FixedLengthTypedArrayObject* makeInstance( + JSContext* cx, Handle 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(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 +class ResizableTypedArrayObjectTemplate + : public ResizableTypedArrayObject, + public TypedArrayObjectTemplate { + friend class js::TypedArrayObject; + + using TypedArrayTemplate = TypedArrayObjectTemplate; + + 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) { + RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey())); + if (!proto) { + return nullptr; + } + return NewTypedArrayObject( + cx, instanceClass(), proto, allocKind, gc::Heap::Default); + } + + static ResizableTypedArrayObject* makeProtoInstance(JSContext* cx, + HandleObject proto, + gc::AllocKind allocKind) { + MOZ_ASSERT(proto); + return NewTypedArrayObject( + cx, instanceClass(), proto, allocKind, gc::Heap::Default); + } + + static ResizableTypedArrayObject* makeInstance( + JSContext* cx, Handle buffer, + size_t byteOffset, size_t len, bool autoLength, HandleObject proto) { + MOZ_ASSERT(buffer); + MOZ_ASSERT(buffer->isResizable()); + MOZ_ASSERT(!buffer->isDetached()); + MOZ_ASSERT(!autoLength || 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); + } + if (!obj || !obj->init(cx, buffer, byteOffset, len, BYTES_PER_ELEMENT)) { + return nullptr; + } + + obj->setFixedSlot(AUTO_LENGTH_SLOT, BooleanValue(autoLength)); + + return obj; + } +}; + +template +bool TypedArrayObjectTemplate::convertValue(JSContext* cx, + HandleValue v, + NativeType* result) { + double d; + if (!ToNumber(cx, v, &d)) { + return false; + } + + if (js::SupportDifferentialTesting()) { + // See the comment in ElementSpecific::doubleToNative. + d = JS::CanonicalizeNaN(d); + } + + // Assign based on characteristics of the destination type + if constexpr (ArrayTypeIsFloatingPoint()) { + *result = NativeType(d); + } else if constexpr (ArrayTypeIsUnsigned()) { + static_assert(sizeof(NativeType) <= 4); + uint32_t n = ToUint32(d); + *result = NativeType(n); + } else if constexpr (ArrayTypeID() == Scalar::Uint8Clamped) { + // The uint8_clamped type has a special rounding converter + // for doubles. + *result = NativeType(d); + } else { + static_assert(sizeof(NativeType) <= 4); + int32_t n = ToInt32(d); + *result = NativeType(n); + } + return true; +} + +template <> +bool TypedArrayObjectTemplate::convertValue(JSContext* cx, + HandleValue v, + int64_t* result) { + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v)); + return true; +} + +template <> +bool TypedArrayObjectTemplate::convertValue(JSContext* cx, + HandleValue v, + uint64_t* result) { + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v)); + return true; +} + +// https://tc39.github.io/proposal-bigint/#sec-integerindexedelementset +// 9.4.5.11 IntegerIndexedElementSet ( O, index, value ) +template +/* static */ bool TypedArrayObjectTemplate::setElement( + JSContext* cx, Handle obj, uint64_t index, HandleValue v, + ObjectOpResult& result) { + MOZ_ASSERT(!obj->hasDetachedBuffer()); + MOZ_ASSERT(index < obj->length().valueOr(0)); + + // Step 1 is enforced by the caller. + + // Steps 2-3. + NativeType nativeValue; + if (!convertValue(cx, v, &nativeValue)) { + return false; + } + + // Step 4. + if (index < obj->length().valueOr(0)) { + MOZ_ASSERT(!obj->hasDetachedBuffer(), + "detaching an array buffer sets the length to zero"); + TypedArrayObjectTemplate::setIndex(*obj, index, nativeValue); + } + + // Step 5. + return result.succeed(); +} + +} /* anonymous namespace */ + +TypedArrayObject* js::NewTypedArrayWithTemplateAndLength( + JSContext* cx, HandleObject templateObj, int32_t len) { + MOZ_ASSERT(templateObj->is()); + TypedArrayObject* tobj = &templateObj->as(); + + switch (tobj->type()) { +#define CREATE_TYPED_ARRAY(_, T, N) \ + case Scalar::N: \ + return FixedLengthTypedArrayObjectTemplate::makeTypedArrayWithTemplate( \ + cx, tobj, len); + JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY) +#undef CREATE_TYPED_ARRAY + default: + MOZ_CRASH("Unsupported TypedArray type"); + } +} + +TypedArrayObject* js::NewTypedArrayWithTemplateAndArray( + JSContext* cx, HandleObject templateObj, HandleObject array) { + MOZ_ASSERT(templateObj->is()); + TypedArrayObject* tobj = &templateObj->as(); + + switch (tobj->type()) { +#define CREATE_TYPED_ARRAY(_, T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::makeTypedArrayWithTemplate(cx, tobj, \ + array); + JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY) +#undef CREATE_TYPED_ARRAY + default: + MOZ_CRASH("Unsupported TypedArray type"); + } +} + +TypedArrayObject* js::NewTypedArrayWithTemplateAndBuffer( + JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer, + HandleValue byteOffset, HandleValue length) { + MOZ_ASSERT(templateObj->is()); + TypedArrayObject* tobj = &templateObj->as(); + + switch (tobj->type()) { +#define CREATE_TYPED_ARRAY(_, T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::makeTypedArrayWithTemplate( \ + cx, tobj, arrayBuffer, byteOffset, length); + JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY) +#undef CREATE_TYPED_ARRAY + default: + MOZ_CRASH("Unsupported TypedArray type"); + } +} + +TypedArrayObject* js::NewUint8ArrayWithLength(JSContext* cx, int32_t len, + gc::Heap heap) { + return TypedArrayObjectTemplate::fromLength(cx, len, nullptr, heap); +} + +template +/* static */ TypedArrayObject* TypedArrayObjectTemplate::fromArray( + JSContext* cx, HandleObject other, HandleObject proto /* = nullptr */) { + // Allow nullptr proto for FriendAPI methods, which don't care about + // subclassing. + if (other->is()) { + return fromTypedArray(cx, other, /* wrapped= */ false, proto); + } + + if (other->is() && + UncheckedUnwrap(other)->is()) { + return fromTypedArray(cx, other, /* wrapped= */ true, proto); + } + + return fromObject(cx, other, proto); +} + +// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78 +// 23.2.5.1 TypedArray ( ...args ) +// 23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray ) +template +/* static */ TypedArrayObject* TypedArrayObjectTemplate::fromTypedArray( + JSContext* cx, HandleObject other, bool isWrapped, HandleObject proto) { + MOZ_ASSERT_IF(!isWrapped, other->is()); + MOZ_ASSERT_IF(isWrapped, other->is() && + UncheckedUnwrap(other)->is()); + + Rooted srcArray(cx); + if (!isWrapped) { + srcArray = &other->as(); + } else { + srcArray = other->maybeUnwrapAs(); + if (!srcArray) { + ReportAccessDenied(cx); + return nullptr; + } + } + + // InitializeTypedArrayFromTypedArray, step 1. (Skipped) + + // InitializeTypedArrayFromTypedArray, step 2. + auto srcLength = srcArray->length(); + if (!srcLength) { + ReportOutOfBounds(cx, srcArray); + return nullptr; + } + + // InitializeTypedArrayFromTypedArray, steps 3-7. (Skipped) + + // InitializeTypedArrayFromTypedArray, step 8. + size_t elementLength = *srcLength; + + // InitializeTypedArrayFromTypedArray, step 9. (Skipped) + + // InitializeTypedArrayFromTypedArray, step 10.a. (Partial) + // InitializeTypedArrayFromTypedArray, step 11.a. + Rooted buffer(cx); + if (!maybeCreateArrayBuffer(cx, elementLength, &buffer)) { + return nullptr; + } + + // InitializeTypedArrayFromTypedArray, step 11.b. + if (Scalar::isBigIntType(ArrayTypeID()) != + Scalar::isBigIntType(srcArray->type())) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE, + srcArray->getClass()->name, + TypedArrayObject::fixedLengthClasses[ArrayTypeID()].name); + return nullptr; + } + + // Step 6.b.i. + // InitializeTypedArrayFromTypedArray, steps 12-15. + Rooted obj(cx, FixedLengthTypedArray::makeInstance( + cx, buffer, 0, elementLength, proto)); + if (!obj) { + return nullptr; + } + + MOZ_RELEASE_ASSERT(!srcArray->hasDetachedBuffer()); + + // InitializeTypedArrayFromTypedArray, steps 10.a. (Remaining parts) + // InitializeTypedArrayFromTypedArray, steps 11.c-f. + MOZ_ASSERT(!obj->isSharedMemory()); + if (srcArray->isSharedMemory()) { + if (!ElementSpecific::setFromTypedArray( + obj, elementLength, srcArray, elementLength, 0)) { + MOZ_ASSERT_UNREACHABLE( + "setFromTypedArray can only fail for overlapping buffers"); + return nullptr; + } + } else { + if (!ElementSpecific::setFromTypedArray( + obj, elementLength, srcArray, elementLength, 0)) { + MOZ_ASSERT_UNREACHABLE( + "setFromTypedArray can only fail for overlapping buffers"); + return nullptr; + } + } + + // Step 6.b.v. + return obj; +} + +static MOZ_ALWAYS_INLINE bool IsOptimizableInit(JSContext* cx, + HandleObject iterable, + bool* optimized) { + MOZ_ASSERT(!*optimized); + + if (!IsPackedArray(iterable)) { + return true; + } + + ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx); + if (!stubChain) { + return false; + } + + return stubChain->tryOptimizeArray(cx, iterable.as(), optimized); +} + +// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78 +// 23.2.5.1 TypedArray ( ...args ) +// 23.2.5.1.4 InitializeTypedArrayFromList ( O, values ) +// 23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike ) +template +/* static */ TypedArrayObject* TypedArrayObjectTemplate::fromObject( + JSContext* cx, HandleObject other, HandleObject proto) { + // Steps 1-4 and 6.a (Already performed in caller). + + // Steps 6.b.i (Allocation deferred until later). + + // Steps 6.b.ii-iii. (Not applicable) + + // Step 6.b.iv. + + bool optimized = false; + if (!IsOptimizableInit(cx, other, &optimized)) { + return nullptr; + } + + // Fast path when iterable is a packed array using the default iterator. + if (optimized) { + // Steps 6.b.iv.2-3. (We don't need to call IterableToList for the fast + // path). + Handle array = other.as(); + + // InitializeTypedArrayFromList, step 1. + size_t len = array->getDenseInitializedLength(); + + // InitializeTypedArrayFromList, step 2. + Rooted buffer(cx); + if (!maybeCreateArrayBuffer(cx, len, &buffer)) { + return nullptr; + } + + // Steps 6.b.i. + Rooted obj( + cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto)); + if (!obj) { + return nullptr; + } + + // InitializeTypedArrayFromList, steps 3-4. + MOZ_ASSERT(!obj->isSharedMemory()); + if (!ElementSpecific::initFromIterablePackedArray(cx, obj, + array)) { + return nullptr; + } + + // InitializeTypedArrayFromList, step 5. (The assertion isn't applicable for + // the fast path). + + // Step 6.b.v. + return obj; + } + + // Step 6.b.iv.1 (Assertion; implicit in our implementation). + + // Step 6.b.iv.2. + RootedValue callee(cx); + RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator)); + if (!GetProperty(cx, other, other, iteratorId, &callee)) { + return nullptr; + } + + // Steps 6.b.iv.3-4. + RootedObject arrayLike(cx); + if (!callee.isNullOrUndefined()) { + // Throw if other[Symbol.iterator] isn't callable. + if (!callee.isObject() || !callee.toObject().isCallable()) { + RootedValue otherVal(cx, ObjectValue(*other)); + UniqueChars bytes = + DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, otherVal, nullptr); + if (!bytes) { + return nullptr; + } + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, + bytes.get()); + return nullptr; + } + + FixedInvokeArgs<2> args2(cx); + args2[0].setObject(*other); + args2[1].set(callee); + + // Step 6.b.iv.3.a. + RootedValue rval(cx); + if (!CallSelfHostedFunction(cx, cx->names().IterableToList, + UndefinedHandleValue, args2, &rval)) { + return nullptr; + } + + // Step 6.b.iv.3.b (Implemented below). + arrayLike = &rval.toObject(); + } else { + // Step 4.a is an assertion: object is not an Iterator. Testing this is + // literally the very last thing we did, so we don't assert here. + + // Step 4.b (Implemented below). + arrayLike = other; + } + + // We implement InitializeTypedArrayFromList in terms of + // InitializeTypedArrayFromArrayLike. + + // InitializeTypedArrayFromArrayLike, step 1. + uint64_t len; + if (!GetLengthProperty(cx, arrayLike, &len)) { + return nullptr; + } + + // InitializeTypedArrayFromArrayLike, step 2. + Rooted buffer(cx); + if (!maybeCreateArrayBuffer(cx, len, &buffer)) { + return nullptr; + } + + MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT); + + // Steps 6.b.i. + Rooted obj( + cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto)); + if (!obj) { + return nullptr; + } + + // InitializeTypedArrayFromArrayLike, steps 3-4. + MOZ_ASSERT(!obj->isSharedMemory()); + if (!ElementSpecific::setFromNonTypedArray(cx, obj, arrayLike, + len)) { + return nullptr; + } + + // Step 6.b.v. + return obj; +} + +static bool TypedArrayConstructor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT, + args.isConstructing() ? "construct" : "call"); + return false; +} + +template +static bool GetTemplateObjectForNative(JSContext* cx, + const JS::HandleValueArray args, + MutableHandleObject res) { + if (args.length() == 0) { + return true; + } + + HandleValue arg = args[0]; + if (arg.isInt32()) { + uint32_t len = 0; + if (arg.toInt32() >= 0) { + len = arg.toInt32(); + } + + size_t nbytes; + if (!js::CalculateAllocSize(len, &nbytes) || + nbytes > TypedArrayObject::ByteLengthLimit) { + return true; + } + + res.set( + FixedLengthTypedArrayObjectTemplate::makeTemplateObject(cx, len)); + return !!res; + } + + // We don't support wrappers, because of the complicated interaction between + // wrapped ArrayBuffers and TypedArrays, see |fromBufferWrapped()|. + if (arg.isObject() && !IsWrapper(&arg.toObject())) { + // We don't use the template's length in the object case, so we can create + // the template typed array with an initial length of zero. + uint32_t len = 0; + res.set( + FixedLengthTypedArrayObjectTemplate::makeTemplateObject(cx, len)); + return !!res; + } + + return true; +} + +/* static */ bool TypedArrayObject::GetTemplateObjectForNative( + JSContext* cx, Native native, const JS::HandleValueArray args, + MutableHandleObject res) { + MOZ_ASSERT(!res); +#define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \ + if (native == &TypedArrayObjectTemplate::class_constructor) { \ + return ::GetTemplateObjectForNative(cx, args, res); \ + } + JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR) +#undef CHECK_TYPED_ARRAY_CONSTRUCTOR + return true; +} + +static bool LengthGetterImpl(JSContext* cx, const CallArgs& args) { + auto* tarr = &args.thisv().toObject().as(); + args.rval().setNumber(tarr->length().valueOr(0)); + return true; +} + +static bool TypedArray_lengthGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +static bool ByteOffsetGetterImpl(JSContext* cx, const CallArgs& args) { + auto* tarr = &args.thisv().toObject().as(); + args.rval().setNumber(tarr->byteOffset().valueOr(0)); + return true; +} + +static bool TypedArray_byteOffsetGetter(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, + args); +} + +static bool ByteLengthGetterImpl(JSContext* cx, const CallArgs& args) { + auto* tarr = &args.thisv().toObject().as(); + args.rval().setNumber(tarr->byteLength().valueOr(0)); + return true; +} + +static bool TypedArray_byteLengthGetter(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, + args); +} + +static bool BufferGetterImpl(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsTypedArrayObject(args.thisv())); + Rooted tarray( + cx, &args.thisv().toObject().as()); + if (!TypedArrayObject::ensureHasBuffer(cx, tarray)) { + return false; + } + args.rval().set(tarray->bufferValue()); + return true; +} + +static bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +// ES2019 draft rev fc9ecdcd74294d0ca3146d4b274e2a8e79565dc3 +// 22.2.3.32 get %TypedArray%.prototype [ @@toStringTag ] +static bool TypedArray_toStringTagGetter(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Steps 1-2. + if (!args.thisv().isObject()) { + args.rval().setUndefined(); + return true; + } + + JSObject* obj = CheckedUnwrapStatic(&args.thisv().toObject()); + if (!obj) { + ReportAccessDenied(cx); + return false; + } + + // Step 3. + if (!obj->is()) { + args.rval().setUndefined(); + return true; + } + + // Steps 4-6. + JSProtoKey protoKey = StandardProtoKeyOrNull(obj); + MOZ_ASSERT(protoKey); + + args.rval().setString(ClassName(protoKey, cx)); + return true; +} + +/* static */ const JSPropertySpec TypedArrayObject::protoAccessors[] = { + JS_PSG("length", TypedArray_lengthGetter, 0), + JS_PSG("buffer", TypedArray_bufferGetter, 0), + JS_PSG("byteLength", TypedArray_byteLengthGetter, 0), + JS_PSG("byteOffset", TypedArray_byteOffsetGetter, 0), + JS_SYM_GET(toStringTag, TypedArray_toStringTagGetter, 0), + JS_PS_END}; + +template +static inline bool SetFromTypedArray(Handle target, + size_t targetLength, + Handle source, + size_t sourceLength, size_t offset) { + // WARNING: |source| may be an unwrapped typed array from a different + // compartment. Proceed with caution! + + if (target->isSharedMemory() || source->isSharedMemory()) { + return ElementSpecific::setFromTypedArray( + target, targetLength, source, sourceLength, offset); + } + return ElementSpecific::setFromTypedArray( + target, targetLength, source, sourceLength, offset); +} + +template +static inline bool SetFromNonTypedArray(JSContext* cx, + Handle target, + HandleObject source, size_t len, + size_t offset) { + MOZ_ASSERT(!source->is(), "use SetFromTypedArray"); + + if (target->isSharedMemory()) { + return ElementSpecific::setFromNonTypedArray( + cx, target, source, len, offset); + } + return ElementSpecific::setFromNonTypedArray( + cx, target, source, len, offset); +} + +// ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f +// 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source ) +static bool SetTypedArrayFromTypedArray(JSContext* cx, + Handle target, + double targetOffset, + size_t targetLength, + Handle source) { + // WARNING: |source| may be an unwrapped typed array from a different + // compartment. Proceed with caution! + + MOZ_ASSERT(targetOffset >= 0); + + // Steps 1-3. (Performed in caller.) + MOZ_ASSERT(!target->hasDetachedBuffer()); + + // Steps 4-5. + auto sourceLength = source->length(); + if (!sourceLength) { + ReportOutOfBounds(cx, source); + return false; + } + + // Steps 13-14 (Split into two checks to provide better error messages). + if (targetOffset > targetLength) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + + // Step 14 (Cont'd). + size_t offset = size_t(targetOffset); + if (*sourceLength > targetLength - offset) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SOURCE_ARRAY_TOO_LONG); + return false; + } + + // Step 15. + if (Scalar::isBigIntType(target->type()) != + Scalar::isBigIntType(source->type())) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE, + source->getClass()->name, target->getClass()->name); + return false; + } + + // Steps 6-12, 16-24. + switch (target->type()) { +#define SET_FROM_TYPED_ARRAY(_, T, N) \ + case Scalar::N: \ + if (!SetFromTypedArray(target, targetLength, source, *sourceLength, \ + offset)) { \ + ReportOutOfMemory(cx); \ + return false; \ + } \ + break; + JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY) +#undef SET_FROM_TYPED_ARRAY + default: + MOZ_CRASH("Unsupported TypedArray type"); + } + + return true; +} + +// ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f +// 23.2.3.24.1 SetTypedArrayFromArrayLike ( target, targetOffset, source ) +static bool SetTypedArrayFromArrayLike(JSContext* cx, + Handle target, + double targetOffset, size_t targetLength, + HandleObject src) { + MOZ_ASSERT(targetOffset >= 0); + + // Steps 1-2. (Performed in caller.) + MOZ_ASSERT(target->length().isSome()); + + // Steps 3-4. (Performed in caller.) + + // Step 5. + uint64_t srcLength; + if (!GetLengthProperty(cx, src, &srcLength)) { + return false; + } + + // Steps 6-7 (Split into two checks to provide better error messages). + if (targetOffset > targetLength) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + + // Step 7 (Cont'd). + size_t offset = size_t(targetOffset); + if (srcLength > targetLength - offset) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_SOURCE_ARRAY_TOO_LONG); + return false; + } + + MOZ_ASSERT(srcLength <= targetLength); + + // Steps 8-9. + if (srcLength > 0) { + switch (target->type()) { +#define SET_FROM_NON_TYPED_ARRAY(_, T, N) \ + case Scalar::N: \ + if (!SetFromNonTypedArray(cx, target, src, srcLength, offset)) \ + return false; \ + break; + JS_FOR_EACH_TYPED_ARRAY(SET_FROM_NON_TYPED_ARRAY) +#undef SET_FROM_NON_TYPED_ARRAY + default: + MOZ_CRASH("Unsupported TypedArray type"); + } + } + + // Step 10. + return true; +} + +// ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f +// 23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] ) +// 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source ) +// 23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source ) +/* static */ +bool TypedArrayObject::set_impl(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsTypedArrayObject(args.thisv())); + + // Steps 1-3 (Validation performed as part of CallNonGenericMethod). + Rooted target( + cx, &args.thisv().toObject().as()); + + // Steps 4-5. + double targetOffset = 0; + if (args.length() > 1) { + // Step 4. + if (!ToInteger(cx, args[1], &targetOffset)) { + return false; + } + + // Step 5. + if (targetOffset < 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + } + + // 23.2.3.24.1, steps 1-2. + // 23.2.3.24.2, steps 1-2. + auto targetLength = target->length(); + if (!targetLength) { + ReportOutOfBounds(cx, target); + return false; + } + + // 23.2.3.24.2, step 4. (23.2.3.24.1 only applies if args[0] is a typed + // array, so it doesn't make a difference there to apply ToObject here.) + RootedObject src(cx, ToObject(cx, args.get(0))); + if (!src) { + return false; + } + + Rooted srcTypedArray(cx); + { + JSObject* obj = CheckedUnwrapStatic(src); + if (!obj) { + ReportAccessDenied(cx); + return false; + } + + if (obj->is()) { + srcTypedArray = &obj->as(); + } + } + + // Steps 6-7. + if (srcTypedArray) { + if (!SetTypedArrayFromTypedArray(cx, target, targetOffset, *targetLength, + srcTypedArray)) { + return false; + } + } else { + if (!SetTypedArrayFromArrayLike(cx, target, targetOffset, *targetLength, + src)) { + return false; + } + } + + // Step 8. + args.rval().setUndefined(); + return true; +} + +/* static */ +bool TypedArrayObject::set(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod( + cx, args); +} + +// ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9 +// 22.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] ) +/* static */ +bool TypedArrayObject::copyWithin_impl(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(IsTypedArrayObject(args.thisv())); + + // Steps 1-2. + Rooted tarray( + cx, &args.thisv().toObject().as()); + + auto arrayLength = tarray->length(); + if (!arrayLength) { + ReportOutOfBounds(cx, tarray); + return false; + } + + // Step 3. + size_t len = *arrayLength; + + // Step 4. + double relativeTarget; + if (!ToInteger(cx, args.get(0), &relativeTarget)) { + return false; + } + + // Step 5. + uint64_t to; + if (relativeTarget < 0) { + to = std::max(len + relativeTarget, 0.0); + } else { + to = std::min(relativeTarget, double(len)); + } + + // Step 6. + double relativeStart; + if (!ToInteger(cx, args.get(1), &relativeStart)) { + return false; + } + + // Step 7. + uint64_t from; + if (relativeStart < 0) { + from = std::max(len + relativeStart, 0.0); + } else { + from = std::min(relativeStart, double(len)); + } + + // Step 8. + double relativeEnd; + if (!args.hasDefined(2)) { + relativeEnd = len; + } else { + if (!ToInteger(cx, args[2], &relativeEnd)) { + return false; + } + } + + // Step 9. + uint64_t final_; + if (relativeEnd < 0) { + final_ = std::max(len + relativeEnd, 0.0); + } else { + final_ = std::min(relativeEnd, double(len)); + } + + // Step 10. + MOZ_ASSERT(to <= len); + uint64_t count; + if (from <= final_) { + count = std::min(final_ - from, len - to); + } else { + count = 0; + } + + // Step 11. + // + // Note that this copies elements effectively by memmove, *not* in + // step 11's specified order. This is unobservable, even when the underlying + // buffer is a SharedArrayBuffer instance, because the access is unordered and + // therefore is allowed to have data races. + + if (count == 0) { + args.rval().setObject(*tarray); + return true; + } + + // Reacquire the length because side-effects may have detached or resized the + // array buffer. + arrayLength = tarray->length(); + if (!arrayLength) { + ReportOutOfBounds(cx, tarray); + return false; + } + + // Recompute the bounds if the current length is smaller. + if (*arrayLength < len) { + MOZ_ASSERT(to + count <= len); + MOZ_ASSERT(from + count <= len); + + len = *arrayLength; + + // Don't copy any bytes if either index is no longer in-bounds. + if (to >= len || from >= len) { + args.rval().setObject(*tarray); + return true; + } + + // Restrict |count| to not copy any bytes after the end of the array. + count = std::min(count, std::min(len - to, len - from)); + MOZ_ASSERT(count > 0); + } + + // Don't multiply by |tarray->bytesPerElement()| in case the compiler can't + // strength-reduce multiplication by 1/2/4/8 into the equivalent shift. + const size_t ElementShift = TypedArrayShift(tarray->type()); + + MOZ_ASSERT((SIZE_MAX >> ElementShift) > to); + size_t byteDest = to << ElementShift; + + MOZ_ASSERT((SIZE_MAX >> ElementShift) > from); + size_t byteSrc = from << ElementShift; + + MOZ_ASSERT((SIZE_MAX >> ElementShift) >= count); + size_t byteSize = count << ElementShift; + +#ifdef DEBUG + { + size_t viewByteLength = len << ElementShift; + MOZ_ASSERT(byteSize <= viewByteLength); + MOZ_ASSERT(byteDest < viewByteLength); + MOZ_ASSERT(byteSrc < viewByteLength); + MOZ_ASSERT(byteDest <= viewByteLength - byteSize); + MOZ_ASSERT(byteSrc <= viewByteLength - byteSize); + } +#endif + + SharedMem data = tarray->dataPointerEither().cast(); + if (tarray->isSharedMemory()) { + jit::AtomicOperations::memmoveSafeWhenRacy(data + byteDest, data + byteSrc, + byteSize); + } else { + memmove(data.unwrapUnshared() + byteDest, data.unwrapUnshared() + byteSrc, + byteSize); + } + + args.rval().setObject(*tarray); + return true; +} + +/* static */ +bool TypedArrayObject::copyWithin(JSContext* cx, unsigned argc, Value* vp) { + AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype", + "copyWithin"); + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + +/* static */ const JSFunctionSpec TypedArrayObject::protoFunctions[] = { + JS_SELF_HOSTED_FN("subarray", "TypedArraySubarray", 2, 0), + JS_FN("set", TypedArrayObject::set, 1, 0), + JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0), + JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 1, 0), + JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0), + JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 1, 0), + JS_SELF_HOSTED_FN("find", "TypedArrayFind", 1, 0), + JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 1, 0), + JS_SELF_HOSTED_FN("findLast", "TypedArrayFindLast", 1, 0), + JS_SELF_HOSTED_FN("findLastIndex", "TypedArrayFindLastIndex", 1, 0), + JS_SELF_HOSTED_FN("forEach", "TypedArrayForEach", 1, 0), + JS_SELF_HOSTED_FN("indexOf", "TypedArrayIndexOf", 2, 0), + JS_SELF_HOSTED_FN("join", "TypedArrayJoin", 1, 0), + JS_SELF_HOSTED_FN("lastIndexOf", "TypedArrayLastIndexOf", 1, 0), + JS_SELF_HOSTED_FN("map", "TypedArrayMap", 1, 0), + JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0), + JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0), + JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0), + JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0), + JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0), + JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0), + JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0), + JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0), + JS_SELF_HOSTED_FN("values", "$TypedArrayValues", 0, 0), + JS_SELF_HOSTED_SYM_FN(iterator, "$TypedArrayValues", 0, 0), + JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0), + JS_SELF_HOSTED_FN("toString", "ArrayToString", 0, 0), + JS_SELF_HOSTED_FN("toLocaleString", "TypedArrayToLocaleString", 2, 0), + JS_SELF_HOSTED_FN("at", "TypedArrayAt", 1, 0), + JS_SELF_HOSTED_FN("toReversed", "TypedArrayToReversed", 0, 0), + JS_SELF_HOSTED_FN("toSorted", "TypedArrayToSorted", 1, 0), + JS_SELF_HOSTED_FN("with", "TypedArrayWith", 2, 0), + JS_FS_END, +}; + +/* static */ const JSFunctionSpec TypedArrayObject::staticFunctions[] = { + JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0), + JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0), + JS_FS_END, +}; + +/* static */ const JSPropertySpec TypedArrayObject::staticProperties[] = { + JS_SELF_HOSTED_SYM_GET(species, "$TypedArraySpecies", 0), + JS_PS_END, +}; + +static JSObject* CreateSharedTypedArrayPrototype(JSContext* cx, + JSProtoKey key) { + return GlobalObject::createBlankPrototype( + cx, cx->global(), &TypedArrayObject::sharedTypedArrayPrototypeClass); +} + +static const ClassSpec TypedArrayObjectSharedTypedArrayPrototypeClassSpec = { + GenericCreateConstructor, + CreateSharedTypedArrayPrototype, + TypedArrayObject::staticFunctions, + TypedArrayObject::staticProperties, + TypedArrayObject::protoFunctions, + TypedArrayObject::protoAccessors, + nullptr, + ClassSpec::DontDefineConstructor, +}; + +/* static */ const JSClass TypedArrayObject::sharedTypedArrayPrototypeClass = { + "TypedArrayPrototype", + JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray), + JS_NULL_CLASS_OPS, + &TypedArrayObjectSharedTypedArrayPrototypeClassSpec, +}; + +namespace { + +// This default implementation is only valid for integer types less +// than 32-bits in size. +template +bool TypedArrayObjectTemplate::getElementPure( + TypedArrayObject* tarray, size_t index, Value* vp) { + static_assert(sizeof(NativeType) < 4, + "this method must only handle NativeType values that are " + "always exact int32_t values"); + + *vp = Int32Value(getIndex(tarray, index)); + return true; +} + +// We need to specialize for floats and other integer types. +template <> +bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, + size_t index, + Value* vp) { + *vp = Int32Value(getIndex(tarray, index)); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElementPure( + TypedArrayObject* tarray, size_t index, Value* vp) { + uint32_t val = getIndex(tarray, index); + *vp = NumberValue(val); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, + size_t index, Value* vp) { + float val = getIndex(tarray, index); + double dval = val; + + /* + * Doubles in typed arrays could be typed-punned arrays of integers. This + * could allow user code to break the engine-wide invariant that only + * canonical nans are stored into jsvals, which means user code could + * confuse the engine into interpreting a double-typed jsval as an + * object-typed jsval. + * + * This could be removed for platforms/compilers known to convert a 32-bit + * non-canonical nan to a 64-bit canonical nan. + */ + *vp = JS::CanonicalizedDoubleValue(dval); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, + size_t index, Value* vp) { + double val = getIndex(tarray, index); + + /* + * Doubles in typed arrays could be typed-punned arrays of integers. This + * could allow user code to break the engine-wide invariant that only + * canonical nans are stored into jsvals, which means user code could + * confuse the engine into interpreting a double-typed jsval as an + * object-typed jsval. + */ + *vp = JS::CanonicalizedDoubleValue(val); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, + size_t index, + Value* vp) { + return false; +} + +template <> +bool TypedArrayObjectTemplate::getElementPure( + TypedArrayObject* tarray, size_t index, Value* vp) { + return false; +} +} /* anonymous namespace */ + +namespace { + +template +bool TypedArrayObjectTemplate::getElement(JSContext* cx, + TypedArrayObject* tarray, + size_t index, + MutableHandleValue val) { + MOZ_ALWAYS_TRUE(getElementPure(tarray, index, val.address())); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElement(JSContext* cx, + TypedArrayObject* tarray, + size_t index, + MutableHandleValue val) { + int64_t n = getIndex(tarray, index); + BigInt* res = BigInt::createFromInt64(cx, n); + if (!res) { + return false; + } + val.setBigInt(res); + return true; +} + +template <> +bool TypedArrayObjectTemplate::getElement(JSContext* cx, + TypedArrayObject* tarray, + size_t index, + MutableHandleValue val) { + uint64_t n = getIndex(tarray, index); + BigInt* res = BigInt::createFromUint64(cx, n); + if (!res) { + return false; + } + val.setBigInt(res); + return true; +} +} /* anonymous namespace */ + +/** + * IsIntegerIndexedObjectOutOfBounds ( iieoRecord ) + * + * IsIntegerIndexedObjectOutOfBounds can be rewritten into the following spec + * steps when inlining the call to + * MakeIntegerIndexedObjectWithBufferWitnessRecord. + * + * 1. Let buffer be O.[[ViewedArrayBuffer]]. + * 2. If IsDetachedBuffer(buffer) is true, then + * a. Return true. + * 3. If IsFixedLengthArrayBuffer(buffer) is true, then + * a. Return false. + * 4. Let bufferByteLength be ArrayBufferByteLength(buffer, order). + * 5. Let byteOffsetStart be O.[[ByteOffset]]. + * 6. If byteOffsetStart > bufferByteLength, then + * a. Return true. + * 7. If O.[[ArrayLength]] is auto, then + * a. Return false. + * 8. Let elementSize be TypedArrayElementSize(O). + * 9. Let byteOffsetEnd be byteOffsetStart + O.[[ArrayLength]] × elementSize. + * 10. If byteOffsetEnd > bufferByteLength, then + * a. Return true. + * 11. Return false. + * + * The additional call to IsFixedLengthArrayBuffer is an optimization to skip + * unnecessary validation which don't apply for fixed length typed arrays. + * + * https://tc39.es/ecma262/#sec-isintegerindexedobjectoutofbounds + * https://tc39.es/ecma262/#sec-makeintegerindexedobjectwithbufferwitnessrecord + */ +mozilla::Maybe TypedArrayObject::byteOffset() const { + if (MOZ_UNLIKELY(hasDetachedBuffer())) { + return mozilla::Nothing{}; + } + + size_t byteOffsetStart = ArrayBufferViewObject::byteOffset(); + + if (MOZ_LIKELY(is())) { + return mozilla::Some(byteOffsetStart); + } + + auto* buffer = bufferEither(); + MOZ_ASSERT(buffer->isResizable()); + + size_t bufferByteLength = buffer->byteLength(); + if (byteOffsetStart > bufferByteLength) { + return mozilla::Nothing{}; + } + + if (as().isAutoLength()) { + return mozilla::Some(byteOffsetStart); + } + + size_t viewByteLength = rawByteLength(); + size_t byteOffsetEnd = byteOffsetStart + viewByteLength; + if (byteOffsetEnd > bufferByteLength) { + return mozilla::Nothing{}; + } + return mozilla::Some(byteOffsetStart); +} + +/** + * IntegerIndexedObjectLength ( iieoRecord ) + * + * IntegerIndexedObjectLength can be rewritten into the following spec + * steps when inlining the calls to IsIntegerIndexedObjectOutOfBounds and + * MakeIntegerIndexedObjectWithBufferWitnessRecord. + * + * 1. Let buffer be O.[[ViewedArrayBuffer]]. + * 2. If IsDetachedBuffer(buffer) is true, then + * a. Return out-of-bounds. + * 3. If IsFixedLengthArrayBuffer(buffer) is true, then + * a. Return O.[[ArrayLength]]. + * 4. Let bufferByteLength be ArrayBufferByteLength(buffer, order). + * 5. Let byteOffsetStart be O.[[ByteOffset]]. + * 6. If byteOffsetStart > bufferByteLength, then + * a. Return out-of-bounds. + * 7. If O.[[ArrayLength]] is auto, then + * a. Let elementSize be TypedArrayElementSize(O). + * b. Return floor((bufferByteLength - byteOffsetStart) / elementSize). + * 8. Let elementSize be TypedArrayElementSize(O). + * 9. Let byteOffsetEnd be byteOffsetStart + O.[[ArrayLength]] × elementSize. + * 10. If byteOffsetEnd > bufferByteLength, then + * a. Return out-of-bounds. + * 11. Return O.[[ArrayLength]]. + * + * The additional call to IsFixedLengthArrayBuffer is an optimization to skip + * unnecessary validation which don't apply for fixed length typed arrays. + * + * https://tc39.es/ecma262/#sec-integerindexedobjectlength + * https://tc39.es/ecma262/#sec-isintegerindexedobjectoutofbounds + * https://tc39.es/ecma262/#sec-makeintegerindexedobjectwithbufferwitnessrecord + */ +mozilla::Maybe TypedArrayObject::length() const { + if (MOZ_UNLIKELY(hasDetachedBuffer())) { + return mozilla::Nothing{}; + } + + if (MOZ_LIKELY(is())) { + size_t arrayLength = rawLength(); + return mozilla::Some(arrayLength); + } + + auto* buffer = bufferEither(); + MOZ_ASSERT(buffer->isResizable()); + + size_t bufferByteLength = buffer->byteLength(); + size_t byteOffsetStart = ArrayBufferViewObject::byteOffset(); + if (byteOffsetStart > bufferByteLength) { + return mozilla::Nothing{}; + } + + if (as().isAutoLength()) { + size_t bytes = bufferByteLength - byteOffsetStart; + return mozilla::Some(bytes / bytesPerElement()); + } + + size_t arrayLength = rawLength(); + size_t byteOffsetEnd = byteOffsetStart + arrayLength * bytesPerElement(); + if (byteOffsetEnd > bufferByteLength) { + return mozilla::Nothing{}; + } + return mozilla::Some(arrayLength); +} + +namespace js { + +template <> +bool TypedArrayObject::getElement(JSContext* cx, size_t index, + MutableHandleValue val) { + switch (type()) { +#define GET_ELEMENT(_, T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::getElement(cx, this, index, val); + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT) +#undef GET_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Simd128: + break; + } + + MOZ_CRASH("Unknown TypedArray type"); +} + +template <> +bool TypedArrayObject::getElement( + JSContext* cx, size_t index, + typename MaybeRooted::MutableHandleType vp) { + return getElementPure(index, vp.address()); +} + +} // namespace js + +bool TypedArrayObject::getElementPure(size_t index, Value* vp) { + switch (type()) { +#define GET_ELEMENT_PURE(_, T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::getElementPure(this, index, vp); + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE) +#undef GET_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Simd128: + break; + } + + MOZ_CRASH("Unknown TypedArray type"); +} + +/* static */ +bool TypedArrayObject::getElements(JSContext* cx, + Handle tarray, + Value* vp) { + size_t length = tarray->length().valueOr(0); + MOZ_ASSERT_IF(length > 0, !tarray->hasDetachedBuffer()); + + switch (tarray->type()) { +#define GET_ELEMENTS(_, T, N) \ + case Scalar::N: \ + for (size_t i = 0; i < length; ++i, ++vp) { \ + if (!TypedArrayObjectTemplate::getElement( \ + cx, tarray, i, MutableHandleValue::fromMarkedLocation(vp))) { \ + return false; \ + } \ + } \ + return true; + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS) +#undef GET_ELEMENTS + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Simd128: + break; + } + + MOZ_CRASH("Unknown TypedArray type"); +} + +/*** + *** JS impl + ***/ + +/* + * TypedArrayObject boilerplate + */ + +static const JSClassOps TypedArrayClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + FixedLengthTypedArrayObject::finalize, // finalize + nullptr, // call + nullptr, // construct + ArrayBufferViewObject::trace, // trace +}; + +static const JSClassOps ResizableTypedArrayClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // enumerate + nullptr, // newEnumerate + nullptr, // resolve + nullptr, // mayResolve + nullptr, // finalize + nullptr, // call + nullptr, // construct + ArrayBufferViewObject::trace, // trace +}; + +static const ClassExtension TypedArrayClassExtension = { + FixedLengthTypedArrayObject::objectMoved, // objectMovedOp +}; + +static const JSPropertySpec + static_prototype_properties[Scalar::MaxTypedArrayViewType][2] = { +#define IMPL_TYPED_ARRAY_PROPERTIES(ExternalType, NativeType, Name) \ + { \ + JS_INT32_PS("BYTES_PER_ELEMENT", \ + TypedArrayObjectTemplate::BYTES_PER_ELEMENT, \ + JSPROP_READONLY | JSPROP_PERMANENT), \ + JS_PS_END, \ + }, + + JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROPERTIES) +#undef IMPL_TYPED_ARRAY_PROPERTIES +}; + +static const ClassSpec + TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] = { +#define IMPL_TYPED_ARRAY_CLASS_SPEC(ExternalType, NativeType, Name) \ + { \ + TypedArrayObjectTemplate::createConstructor, \ + TypedArrayObjectTemplate::createPrototype, \ + nullptr, \ + static_prototype_properties[Scalar::Type::Name], \ + nullptr, \ + static_prototype_properties[Scalar::Type::Name], \ + nullptr, \ + JSProto_TypedArray, \ + }, + + JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS_SPEC) +#undef IMPL_TYPED_ARRAY_CLASS_SPEC +}; + +// Class definitions for fixed length and resizable typed arrays. Stored into a +// 2-dimensional array to ensure the classes are in contiguous memory. +const JSClass TypedArrayObject::anyClasses[2][Scalar::MaxTypedArrayViewType] = { + // Class definitions for fixed length typed arrays. + { +#define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ + { \ + #Name "Array", \ + JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \ + JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_SKIP_NURSERY_FINALIZE | \ + JSCLASS_BACKGROUND_FINALIZE, \ + &TypedArrayClassOps, \ + &TypedArrayObjectClassSpecs[Scalar::Type::Name], \ + &TypedArrayClassExtension, \ + }, + + JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) +#undef IMPL_TYPED_ARRAY_CLASS + }, + + // Class definitions for resizable typed arrays. + { +#define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \ + { \ + #Name "Array", \ + JSCLASS_HAS_RESERVED_SLOTS(ResizableTypedArrayObject::RESERVED_SLOTS) | \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \ + JSCLASS_DELAY_METADATA_BUILDER, \ + &ResizableTypedArrayClassOps, \ + &TypedArrayObjectClassSpecs[Scalar::Type::Name], \ + JS_NULL_CLASS_EXT, \ + }, + + JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS) +#undef IMPL_TYPED_ARRAY_CLASS + }, +}; + +const JSClass ( + &TypedArrayObject::fixedLengthClasses)[Scalar::MaxTypedArrayViewType] = + TypedArrayObject::anyClasses[0]; + +const JSClass ( + &TypedArrayObject::resizableClasses)[Scalar::MaxTypedArrayViewType] = + TypedArrayObject::anyClasses[1]; + +// The various typed array prototypes are supposed to 1) be normal objects, +// 2) stringify to "[object ]", and 3) (Gecko-specific) +// be xrayable. The first and second requirements mandate (in the absence of +// @@toStringTag) a custom class. The third requirement mandates that each +// prototype's class have the relevant typed array's cached JSProtoKey in them. +// Thus we need one class with cached prototype per kind of typed array, with a +// delegated ClassSpec. +// +// Actually ({}).toString.call(Uint8Array.prototype) should throw, because +// Uint8Array.prototype lacks the the typed array internal slots. (Same as +// with %TypedArray%.prototype.) It's not clear this is desirable (see +// above), but it's what we've always done, so keep doing it till we +// implement @@toStringTag or ES6 changes. +const JSClass TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = { +#define IMPL_TYPED_ARRAY_PROTO_CLASS(ExternalType, NativeType, Name) \ + { \ + #Name "Array.prototype", \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array), \ + JS_NULL_CLASS_OPS, \ + &TypedArrayObjectClassSpecs[Scalar::Type::Name], \ + }, + + JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROTO_CLASS) +#undef IMPL_TYPED_ARRAY_PROTO_CLASS +}; + +/* static */ +bool TypedArrayObject::isOriginalLengthGetter(Native native) { + return native == TypedArray_lengthGetter; +} + +/* static */ +bool TypedArrayObject::isOriginalByteOffsetGetter(Native native) { + return native == TypedArray_byteOffsetGetter; +} + +/* static */ +bool TypedArrayObject::isOriginalByteLengthGetter(Native native) { + return native == TypedArray_byteLengthGetter; +} + +bool js::IsTypedArrayConstructor(const JSObject* obj) { +#define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \ + if (IsNativeFunction(obj, TypedArrayObjectTemplate::class_constructor)) { \ + return true; \ + } + JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR) +#undef CHECK_TYPED_ARRAY_CONSTRUCTOR + return false; +} + +bool js::IsTypedArrayConstructor(HandleValue v, Scalar::Type type) { + return IsNativeFunction(v, TypedArrayConstructorNative(type)); +} + +JSNative js::TypedArrayConstructorNative(Scalar::Type type) { +#define TYPED_ARRAY_CONSTRUCTOR_NATIVE(_, T, N) \ + if (type == Scalar::N) { \ + return TypedArrayObjectTemplate::class_constructor; \ + } + JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CONSTRUCTOR_NATIVE) +#undef TYPED_ARRAY_CONSTRUCTOR_NATIVE + + MOZ_CRASH("unexpected typed array type"); +} + +bool js::IsBufferSource(JSObject* object, SharedMem* dataPointer, + size_t* byteLength) { + if (object->is()) { + TypedArrayObject& view = object->as(); + *dataPointer = view.dataPointerEither().cast(); + *byteLength = view.byteLength().valueOr(0); + return true; + } + + if (object->is()) { + DataViewObject& view = object->as(); + *dataPointer = view.dataPointerEither().cast(); + *byteLength = view.byteLength().valueOr(0); + return true; + } + + if (object->is()) { + ArrayBufferObject& buffer = object->as(); + *dataPointer = buffer.dataPointerShared(); + *byteLength = buffer.byteLength(); + return true; + } + + if (object->is()) { + SharedArrayBufferObject& buffer = object->as(); + *dataPointer = buffer.dataPointerShared(); + *byteLength = buffer.byteLength(); + return true; + } + + return false; +} + +template +static inline bool StringIsInfinity(mozilla::Range s) { + static constexpr std::string_view Infinity = "Infinity"; + + // Compilers optimize this to one |cmp| instruction on x64 resp. two for x86, + // when the input is a Latin-1 string, because the string "Infinity" is + // exactly eight characters long, so it can be represented as a single uint64 + // value. + return s.length() == Infinity.length() && + EqualChars(s.begin().get(), Infinity.data(), Infinity.length()); +} + +template +static inline bool StringIsNaN(mozilla::Range s) { + static constexpr std::string_view NaN = "NaN"; + + // "NaN" is not as nicely optimizable as "Infinity", but oh well. + return s.length() == NaN.length() && + EqualChars(s.begin().get(), NaN.data(), NaN.length()); +} + +template +static mozilla::Maybe StringToTypedArrayIndexSlow( + mozilla::Range s) { + const mozilla::RangedPtr start = s.begin(); + const mozilla::RangedPtr end = s.end(); + + const CharT* actualEnd; + double result = js_strtod(start.get(), end.get(), &actualEnd); + + // The complete string must have been parsed. + if (actualEnd != end.get()) { + return mozilla::Nothing(); + } + + // Now convert it back to a string. + ToCStringBuf cbuf; + size_t cstrlen; + const char* cstr = js::NumberToCString(&cbuf, result, &cstrlen); + MOZ_ASSERT(cstr); + + // Both strings must be equal for a canonical numeric index string. + if (s.length() != cstrlen || !EqualChars(start.get(), cstr, cstrlen)) { + return mozilla::Nothing(); + } + + // Directly perform IsInteger() check and encode negative and non-integer + // indices as OOB. + // See 9.4.5.2 [[HasProperty]], steps 3.b.iii and 3.b.v. + // See 9.4.5.3 [[DefineOwnProperty]], steps 3.b.i and 3.b.iii. + // See 9.4.5.8 IntegerIndexedElementGet, steps 5 and 8. + // See 9.4.5.9 IntegerIndexedElementSet, steps 6 and 9. + if (result < 0 || !IsInteger(result)) { + return mozilla::Some(UINT64_MAX); + } + + // Anything equals-or-larger than 2^53 is definitely OOB, encode it + // accordingly so that the cast to uint64_t is well defined. + if (result >= DOUBLE_INTEGRAL_PRECISION_LIMIT) { + return mozilla::Some(UINT64_MAX); + } + + // The string is an actual canonical numeric index. + return mozilla::Some(result); +} + +template +mozilla::Maybe js::StringToTypedArrayIndex( + mozilla::Range s) { + mozilla::RangedPtr cp = s.begin(); + const mozilla::RangedPtr end = s.end(); + + MOZ_ASSERT(cp < end, "caller must check for empty strings"); + + bool negative = false; + if (*cp == '-') { + negative = true; + if (++cp == end) { + return mozilla::Nothing(); + } + } + + if (!IsAsciiDigit(*cp)) { + // Check for "NaN", "Infinity", or "-Infinity". + if ((!negative && StringIsNaN({cp, end})) || + StringIsInfinity({cp, end})) { + return mozilla::Some(UINT64_MAX); + } + return mozilla::Nothing(); + } + + uint32_t digit = AsciiDigitToNumber(*cp++); + + // Don't allow leading zeros. + if (digit == 0 && cp != end) { + // The string may be of the form "0.xyz". The exponent form isn't possible + // when the string starts with "0". + if (*cp == '.') { + return StringToTypedArrayIndexSlow(s); + } + return mozilla::Nothing(); + } + + uint64_t index = digit; + + for (; cp < end; cp++) { + if (!IsAsciiDigit(*cp)) { + // Take the slow path when the string has fractional parts or an exponent. + if (*cp == '.' || *cp == 'e') { + return StringToTypedArrayIndexSlow(s); + } + return mozilla::Nothing(); + } + + digit = AsciiDigitToNumber(*cp); + + static_assert( + uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT) < (UINT64_MAX - 10) / 10, + "2^53 is way below UINT64_MAX, so |10 * index + digit| can't overflow"); + + index = 10 * index + digit; + + // Also take the slow path when the string is larger-or-equals 2^53. + if (index >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) { + return StringToTypedArrayIndexSlow(s); + } + } + + if (negative) { + return mozilla::Some(UINT64_MAX); + } + return mozilla::Some(index); +} + +template mozilla::Maybe js::StringToTypedArrayIndex( + mozilla::Range s); + +template mozilla::Maybe js::StringToTypedArrayIndex( + mozilla::Range s); + +bool js::SetTypedArrayElement(JSContext* cx, Handle obj, + uint64_t index, HandleValue v, + ObjectOpResult& result) { + switch (obj->type()) { +#define SET_TYPED_ARRAY_ELEMENT(_, T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::setElement(cx, obj, index, v, result); + JS_FOR_EACH_TYPED_ARRAY(SET_TYPED_ARRAY_ELEMENT) +#undef SET_TYPED_ARRAY_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Simd128: + break; + } + + MOZ_CRASH("Unsupported TypedArray type"); +} + +bool js::SetTypedArrayElementOutOfBounds(JSContext* cx, + Handle obj, + uint64_t index, HandleValue v, + ObjectOpResult& result) { + // This method is only called for non-existent properties, which means any + // absent indexed properties must be out of range. Unless the typed array is + // backed by a growable SharedArrayBuffer, in which case another thread may + // have grown the buffer. + MOZ_ASSERT(index >= obj->length().valueOr(0) || + (obj->isSharedMemory() && obj->bufferShared()->isGrowable())); + + // The following steps refer to 10.4.5.16 TypedArraySetElement. + + // Steps 1-2. + RootedValue converted(cx); + if (!obj->convertValue(cx, v, &converted)) { + return false; + } + + // Step 3. + if (index < obj->length().valueOr(0)) { + // Side-effects when converting the value may have put the index in-bounds + // when the backing buffer is resizable. + MOZ_ASSERT(obj->hasResizableBuffer()); + return SetTypedArrayElement(cx, obj, index, converted, result); + } + + // Step 4. + return result.succeed(); +} + +// ES2021 draft rev b3f9b5089bcc3ddd8486379015cd11eb1427a5eb +// 9.4.5.3 [[DefineOwnProperty]], step 3.b. +bool js::DefineTypedArrayElement(JSContext* cx, Handle obj, + uint64_t index, + Handle desc, + ObjectOpResult& result) { + // These are all substeps of 3.b. + + // Step i. + if (index >= obj->length().valueOr(0)) { + if (obj->hasDetachedBuffer()) { + return result.fail(JSMSG_TYPED_ARRAY_DETACHED); + } + return result.fail(JSMSG_DEFINE_BAD_INDEX); + } + + // Step ii. + if (desc.isAccessorDescriptor()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + + // Step iii. + if (desc.hasConfigurable() && !desc.configurable()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + + // Step iv. + if (desc.hasEnumerable() && !desc.enumerable()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + + // Step v. + if (desc.hasWritable() && !desc.writable()) { + return result.fail(JSMSG_CANT_REDEFINE_PROP); + } + + // Step vi. + if (desc.hasValue()) { + switch (obj->type()) { +#define DEFINE_TYPED_ARRAY_ELEMENT(_, T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate::setElement(cx, obj, index, \ + desc.value(), result); + JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_ARRAY_ELEMENT) +#undef DEFINE_TYPED_ARRAY_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Simd128: + break; + } + + MOZ_CRASH("Unsupported TypedArray type"); + } + + // Step vii. + return result.succeed(); +} + +template +static constexpr typename std::enable_if_t, U> +UnsignedSortValue(U val) { + return val; +} + +template +static constexpr + typename std::enable_if_t && std::is_signed_v, U> + UnsignedSortValue(U val) { + // Flip sign bit. + return val ^ static_cast(std::numeric_limits::min()); +} + +template +static constexpr + typename std::enable_if_t, UnsignedT> + UnsignedSortValue(UnsignedT val) { + // Flip sign bit for positive numbers; flip all bits for negative numbers, + // except negative NaNs. + using FloatingPoint = mozilla::FloatingPoint; + static_assert(std::is_same_v, + "FloatingPoint::Bits matches the unsigned int representation"); + + // FF80'0000 is negative infinity, (FF80'0000, FFFF'FFFF] are all NaNs with + // the sign-bit set (and the equivalent holds for double values). So any value + // larger than negative infinity is a negative NaN. + constexpr UnsignedT NegativeInfinity = + FloatingPoint::kSignBit | FloatingPoint::kExponentBits; + if (val > NegativeInfinity) { + return val; + } + if (val & FloatingPoint::kSignBit) { + return ~val; + } + return val ^ FloatingPoint::kSignBit; +} + +template +static typename std::enable_if_t || + std::is_same_v> +TypedArrayStdSort(SharedMem data, size_t length) { + T* unwrapped = data.cast().unwrapUnshared(); + std::sort(unwrapped, unwrapped + length); +} + +template +static typename std::enable_if_t> TypedArrayStdSort( + SharedMem data, size_t length) { + // Sort on the unsigned representation for performance reasons. + using UnsignedT = + typename mozilla::UnsignedStdintTypeForSize::Type; + UnsignedT* unwrapped = data.cast().unwrapUnshared(); + std::sort(unwrapped, unwrapped + length, [](UnsignedT x, UnsignedT y) { + constexpr auto SortValue = UnsignedSortValue; + return SortValue(x) < SortValue(y); + }); +} + +template +static typename std::enable_if_t, bool> +TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) { + TypedArrayStdSort(typedArray->dataPointerEither(), length); + return true; +} + +template +static typename std::enable_if_t, bool> +TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) { + // Always create a copy when sorting shared memory backed typed arrays to + // ensure concurrent write accesses doesn't lead to UB when calling std::sort. + auto ptr = cx->make_pod_array(length); + if (!ptr) { + return false; + } + SharedMem unshared = SharedMem::unshared(ptr.get()); + SharedMem data = typedArray->dataPointerShared().cast(); + + Ops::podCopy(unshared, data, length); + + TypedArrayStdSort(unshared.template cast(), length); + + Ops::podCopy(data, unshared, length); + + return true; +} + +template +static bool TypedArrayCountingSort(JSContext* cx, TypedArrayObject* typedArray, + size_t length) { + static_assert(std::is_integral_v || std::is_same_v, + "Counting sort expects integral array elements"); + + // Determined by performance testing. + if (length <= 64) { + return TypedArrayStdSort(cx, typedArray, length); + } + + // Map signed values onto the unsigned range when storing in buffer. + using UnsignedT = + typename mozilla::UnsignedStdintTypeForSize::Type; + constexpr T min = std::numeric_limits::min(); + + constexpr size_t InlineStorage = sizeof(T) == 1 ? 256 : 0; + Vector buffer(cx); + if (!buffer.resize(size_t(std::numeric_limits::max()) + 1)) { + return false; + } + + SharedMem data = typedArray->dataPointerEither().cast(); + + // Populate the buffer. + for (size_t i = 0; i < length; i++) { + T val = Ops::load(data + i); + buffer[UnsignedT(val - min)]++; + } + + // Traverse the buffer in order and write back elements to array. + UnsignedT val = UnsignedT(-1); // intentional overflow on first increment + for (size_t i = 0; i < length;) { + // Invariant: sum(buffer[val:]) == length-i + size_t j; + do { + j = buffer[++val]; + } while (j == 0); + + for (; j > 0; j--) { + Ops::store(data + i++, T(val + min)); + } + } + + return true; +} + +template +static void SortByColumn(SharedMem data, size_t length, SharedMem aux, + uint8_t col) { + static_assert(std::is_unsigned_v, "SortByColumn sorts on unsigned values"); + static_assert(std::is_same_v, + "SortByColumn only works on unshared data"); + + // |counts| is used to compute the starting index position for each key. + // Letting counts[0] always be 0, simplifies the transform step below. + // Example: + // + // Computing frequency counts for the input [1 2 1] gives: + // 0 1 2 3 ... (keys) + // 0 0 2 1 (frequencies) + // + // Transforming frequencies to indexes gives: + // 0 1 2 3 ... (keys) + // 0 0 2 3 (indexes) + + constexpr size_t R = 256; + + // Initialize all entries to zero. + size_t counts[R + 1] = {}; + + const auto ByteAtCol = [col](U x) { + U y = UnsignedSortValue(x); + return static_cast(y >> (col * 8)); + }; + + // Compute frequency counts. + for (size_t i = 0; i < length; i++) { + U val = Ops::load(data + i); + uint8_t b = ByteAtCol(val); + counts[b + 1]++; + } + + // Transform counts to indices. + std::partial_sum(std::begin(counts), std::end(counts), std::begin(counts)); + + // Distribute + for (size_t i = 0; i < length; i++) { + U val = Ops::load(data + i); + uint8_t b = ByteAtCol(val); + size_t j = counts[b]++; + MOZ_ASSERT(j < length, + "index is in bounds when |data| can't be modified concurrently"); + UnsharedOps::store(aux + j, val); + } + + // Copy back + Ops::podCopy(data, aux, length); +} + +template +static bool TypedArrayRadixSort(JSContext* cx, TypedArrayObject* typedArray, + size_t length) { + // Determined by performance testing. + constexpr size_t StdSortMinCutoff = sizeof(T) == 2 ? 64 : 256; + + // Radix sort uses O(n) additional space, limit this space to 64 MB. + constexpr size_t StdSortMaxCutoff = (64 * 1024 * 1024) / sizeof(T); + + if (length <= StdSortMinCutoff || length >= StdSortMaxCutoff) { + return TypedArrayStdSort(cx, typedArray, length); + } + + if constexpr (sizeof(T) == 2) { + // Radix sort uses O(n) additional space, so when |n| reaches 2^16, switch + // over to counting sort to limit the additional space needed to 2^16. + constexpr size_t CountingSortMaxCutoff = 65536; + + if (length >= CountingSortMaxCutoff) { + return TypedArrayCountingSort(cx, typedArray, length); + } + } + + using UnsignedT = + typename mozilla::UnsignedStdintTypeForSize::Type; + + auto ptr = cx->make_zeroed_pod_array(length); + if (!ptr) { + return false; + } + SharedMem aux = SharedMem::unshared(ptr.get()); + + SharedMem data = + typedArray->dataPointerEither().cast(); + + // Always create a copy when sorting shared memory backed typed arrays to + // ensure concurrent write accesses don't lead to computing bad indices. + SharedMem unshared; + SharedMem shared; + UniquePtr ptrUnshared; + if constexpr (std::is_same_v) { + ptrUnshared = cx->make_pod_array(length); + if (!ptrUnshared) { + return false; + } + unshared = SharedMem::unshared(ptrUnshared.get()); + shared = data; + + Ops::podCopy(unshared, shared, length); + + data = unshared; + } + + for (uint8_t col = 0; col < sizeof(UnsignedT); col++) { + SortByColumn(data, length, aux, col); + } + + if constexpr (std::is_same_v) { + Ops::podCopy(shared, unshared, length); + } + + return true; +} + +using TypedArraySortFn = bool (*)(JSContext*, TypedArrayObject*, size_t length); + +template +static constexpr typename std::enable_if_t +TypedArraySort() { + return TypedArrayCountingSort; +} + +template +static constexpr typename std::enable_if_t +TypedArraySort() { + return TypedArrayRadixSort; +} + +template +static constexpr typename std::enable_if_t +TypedArraySort() { + return TypedArrayStdSort; +} + +bool js::intrinsic_TypedArrayNativeSort(JSContext* cx, unsigned argc, + Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + + TypedArrayObject* typedArray = + UnwrapAndDowncastValue(cx, args[0]); + if (!typedArray) { + return false; + } + + auto length = typedArray->length(); + MOZ_RELEASE_ASSERT(length, + "TypedArray is neither detached nor out-of-bounds"); + + bool isShared = typedArray->isSharedMemory(); + switch (typedArray->type()) { +#define SORT(_, T, N) \ + case Scalar::N: \ + if (isShared) { \ + if (!TypedArraySort()(cx, typedArray, *length)) { \ + return false; \ + } \ + } else { \ + if (!TypedArraySort()(cx, typedArray, *length)) { \ + return false; \ + } \ + } \ + break; + JS_FOR_EACH_TYPED_ARRAY(SORT) +#undef SORT + default: + MOZ_CRASH("Unsupported TypedArray type"); + } + + args.rval().set(args[0]); + return true; +} + +/* JS Public API */ + +#define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(ExternalType, NativeType, Name) \ + JS_PUBLIC_API JSObject* JS_New##Name##Array(JSContext* cx, \ + size_t nelements) { \ + return TypedArrayObjectTemplate::fromLength(cx, nelements); \ + } \ + \ + JS_PUBLIC_API JSObject* JS_New##Name##ArrayFromArray(JSContext* cx, \ + HandleObject other) { \ + return TypedArrayObjectTemplate::fromArray(cx, other); \ + } \ + \ + JS_PUBLIC_API JSObject* JS_New##Name##ArrayWithBuffer( \ + JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, \ + int64_t length) { \ + return TypedArrayObjectTemplate::fromBuffer( \ + cx, arrayBuffer, byteOffset, length); \ + } \ + \ + JS_PUBLIC_API JSObject* js::Unwrap##Name##Array(JSObject* obj) { \ + obj = obj->maybeUnwrapIf(); \ + if (!obj) { \ + return nullptr; \ + } \ + const JSClass* clasp = obj->getClass(); \ + if (clasp != FixedLengthTypedArrayObjectTemplate< \ + NativeType>::instanceClass() && \ + clasp != \ + ResizableTypedArrayObjectTemplate::instanceClass()) { \ + return nullptr; \ + } \ + return obj; \ + } \ + \ + JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayLengthAndData( \ + JSObject* obj, size_t* length, bool* isSharedMemory, \ + const JS::AutoRequireNoGC& nogc) { \ + TypedArrayObject* tarr = obj->maybeUnwrapAs(); \ + if (!tarr) { \ + return nullptr; \ + } \ + mozilla::Span span = \ + JS::TypedArray::fromObject(tarr).getData( \ + isSharedMemory, nogc); \ + *length = span.Length(); \ + return span.data(); \ + } \ + \ + JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayData( \ + JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { \ + size_t length; \ + return JS_Get##Name##ArrayLengthAndData(obj, &length, isSharedMemory, \ + nogc); \ + } \ + JS_PUBLIC_API JSObject* JS_GetObjectAs##Name##Array( \ + JSObject* obj, size_t* length, bool* isShared, ExternalType** data) { \ + obj = js::Unwrap##Name##Array(obj); \ + if (!obj) { \ + return nullptr; \ + } \ + TypedArrayObject* tarr = &obj->as(); \ + *length = tarr->length().valueOr(0); \ + *isShared = tarr->isSharedMemory(); \ + *data = static_cast(tarr->dataPointerEither().unwrap( \ + /*safe - caller sees isShared flag*/)); \ + return obj; \ + } + +JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS) +#undef IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS + +JS_PUBLIC_API bool JS_IsTypedArrayObject(JSObject* obj) { + return obj->canUnwrapAs(); +} + +JS_PUBLIC_API size_t JS_GetTypedArrayLength(JSObject* obj) { + TypedArrayObject* tarr = obj->maybeUnwrapAs(); + if (!tarr) { + return 0; + } + return tarr->length().valueOr(0); +} + +JS_PUBLIC_API size_t JS_GetTypedArrayByteOffset(JSObject* obj) { + TypedArrayObject* tarr = obj->maybeUnwrapAs(); + if (!tarr) { + return 0; + } + return tarr->byteOffset().valueOr(0); +} + +JS_PUBLIC_API size_t JS_GetTypedArrayByteLength(JSObject* obj) { + TypedArrayObject* tarr = obj->maybeUnwrapAs(); + if (!tarr) { + return 0; + } + return tarr->byteLength().valueOr(0); +} + +JS_PUBLIC_API bool JS_GetTypedArraySharedness(JSObject* obj) { + TypedArrayObject* tarr = obj->maybeUnwrapAs(); + if (!tarr) { + return false; + } + return tarr->isSharedMemory(); +} + +JS_PUBLIC_API JS::Scalar::Type JS_GetArrayBufferViewType(JSObject* obj) { + ArrayBufferViewObject* view = obj->maybeUnwrapAs(); + if (!view) { + return Scalar::MaxTypedArrayViewType; + } + + if (view->is()) { + return view->as().type(); + } + if (view->is()) { + return Scalar::MaxTypedArrayViewType; + } + MOZ_CRASH("invalid ArrayBufferView type"); +} + +JS_PUBLIC_API size_t JS_MaxMovableTypedArraySize() { + return FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT; +} + +namespace JS { + +const JSClass* const TypedArray_base::fixedLengthClasses = + TypedArrayObject::fixedLengthClasses; +const JSClass* const TypedArray_base::resizableClasses = + TypedArrayObject::resizableClasses; + +#define INSTANTIATE(ExternalType, NativeType, Name) \ + template class TypedArray; +JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE) +#undef INSTANTIATE + +JS::ArrayBufferOrView JS::ArrayBufferOrView::unwrap(JSObject* maybeWrapped) { + if (!maybeWrapped) { + return JS::ArrayBufferOrView(nullptr); + } + auto* ab = maybeWrapped->maybeUnwrapIf(); + if (ab) { + return ArrayBufferOrView::fromObject(ab); + } + + return ArrayBufferView::unwrap(maybeWrapped); +} + +bool JS::ArrayBufferOrView::isDetached() const { + MOZ_ASSERT(obj); + if (obj->is()) { + return obj->as().isDetached(); + } else { + return obj->as().hasDetachedBuffer(); + } +} + +bool JS::ArrayBufferOrView::isResizable() const { + MOZ_ASSERT(obj); + if (obj->is()) { + return obj->as().isResizable(); + } else { + return obj->as().hasResizableBuffer(); + } +} + +JS::TypedArray_base JS::TypedArray_base::fromObject(JSObject* unwrapped) { + if (unwrapped && unwrapped->is()) { + return TypedArray_base(unwrapped); + } + return TypedArray_base(nullptr); +} + +// Template getData function for TypedArrays, implemented here because +// it requires internal APIs. +template +typename mozilla::Span::DataType> +TypedArray::getData(bool* isSharedMemory, const AutoRequireNoGC&) { + using ExternalType = TypedArray::DataType; + if (!obj) { + return nullptr; + } + TypedArrayObject* tarr = &obj->as(); + MOZ_ASSERT(tarr); + *isSharedMemory = tarr->isSharedMemory(); + return {static_cast(tarr->dataPointerEither().unwrap( + /*safe - caller sees isShared*/)), + tarr->length().valueOr(0)}; +}; + +// Force the method defined above to actually be instantianted in this +// compilation unit and emitted into the object file, since otherwise a binary +// could include the header file and emit an undefined symbol that would not be +// satisfied by the linker. (This happens with opt gtest, at least. In a DEBUG +// build, the header contains a call to this function so it will always be +// emitted.) +#define INSTANTIATE_GET_DATA(a, b, Name) \ + template mozilla::Span::DataType> \ + TypedArray::getData(bool* isSharedMemory, \ + const AutoRequireNoGC&); +JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE_GET_DATA) +#undef INSTANTIATE_GET_DATA + +} /* namespace JS */ -- cgit v1.2.3