Source code
Revision control
Copy as Markdown
Other Tools
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#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 <algorithm>
#include <iterator>
#include <limits>
#include <numeric>
#include <string.h>
#include <string_view>
#if !defined(XP_WIN) && !defined(__wasi__)
# include <sys/mman.h>
#endif
#include <type_traits>
#include "jsnum.h"
#include "jstypes.h"
#include "builtin/Array.h"
#include "builtin/DataViewObject.h"
#include "gc/Barrier.h"
#include "gc/MaybeRooted.h"
#include "jit/InlinableNatives.h"
#include "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::convertForSideEffect(JSContext* cx,
HandleValue v) const {
switch (type()) {
case Scalar::BigInt64:
case Scalar::BigUint64: {
return ToBigInt(cx, v) != nullptr;
}
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 ignore;
return ToNumber(cx, v, &ignore);
}
case Scalar::MaxTypedArrayViewType:
case Scalar::Int64:
case Scalar::Simd128:
MOZ_CRASH("Unsupported TypedArray type");
}
MOZ_ASSERT_UNREACHABLE("Invalid scalar type");
return false;
}
/* static */
bool TypedArrayObject::is(HandleValue v) {
return v.isObject() && v.toObject().is<TypedArrayObject>();
}
/* static */
bool TypedArrayObject::ensureHasBuffer(JSContext* cx,
Handle<TypedArrayObject*> tarray) {
if (tarray->hasBuffer()) {
return true;
}
size_t byteLength = tarray->byteLength();
AutoRealm ar(cx, tarray);
Rooted<ArrayBufferObject*> buffer(
cx, ArrayBufferObject::createZeroed(cx, tarray->byteLength()));
if (!buffer) {
return false;
}
// 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 TypedArrayObject::assertZeroLengthArrayData() const {
if (length() == 0 && !hasBuffer()) {
uint8_t* end = fixedData(TypedArrayObject::FIXED_DATA_START);
MOZ_ASSERT(end[0] == ZeroLengthArrayData);
}
}
#endif
void TypedArrayObject::finalize(JS::GCContext* gcx, JSObject* obj) {
MOZ_ASSERT(!IsInsideNursery(obj));
TypedArrayObject* curObj = &obj->as<TypedArrayObject>();
// 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 TypedArrayObject::objectMoved(JSObject* obj, JSObject* old) {
TypedArrayObject* newObj = &obj->as<TypedArrayObject>();
const TypedArrayObject* oldObj = &old->as<TypedArrayObject>();
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();
if (!nursery.isInside(buf)) {
nursery.removeMallocedBufferDuringMinorGC(buf);
size_t nbytes = RoundUp(newObj->byteLength(), sizeof(Value));
AddCellMemory(newObj, nbytes, MemoryUse::TypedArrayElements);
return 0;
}
// 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();
MOZ_ASSERT(nbytes <= Nursery::MaxNurseryBufferSize);
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 (headerSize + nbytes <= GetGCKindBytes(newAllocKind)) {
MOZ_ASSERT(oldObj->hasInlineElements());
#ifdef DEBUG
if (nbytes == 0) {
uint8_t* output = newObj->fixedData(TypedArrayObject::FIXED_DATA_START);
output[0] = ZeroLengthArrayData;
}
#endif
newObj->setInlineElements();
} else {
MOZ_ASSERT(!oldObj->hasInlineElements());
AutoEnterOOMUnsafeRegion oomUnsafe;
nbytes = RoundUp(nbytes, sizeof(Value));
void* data = newObj->zone()->pod_arena_malloc<uint8_t>(
js::ArrayBufferContentsArena, nbytes);
if (!data) {
oomUnsafe.crash(
"Failed to allocate typed array elements while tenuring.");
}
MOZ_ASSERT(!nursery.isInside(data));
newObj->setReservedSlot(DATA_SLOT, PrivateValue(data));
AddCellMemory(newObj, nbytes, MemoryUse::TypedArrayElements);
}
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(),
/* direct = */ nbytes >= sizeof(uintptr_t));
return newObj->hasInlineElements() ? 0 : nbytes;
}
bool TypedArrayObject::hasInlineElements() const {
return elements() == this->fixedData(TypedArrayObject::FIXED_DATA_START) &&
byteLength() <= TypedArrayObject::INLINE_BUFFER_LIMIT;
}
void TypedArrayObject::setInlineElements() {
char* dataSlot = reinterpret_cast<char*>(this) + dataOffset();
*reinterpret_cast<void**>(dataSlot) =
this->fixedData(TypedArrayObject::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;
}
namespace {
static TypedArrayObject* 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);
// 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 = TypedArrayObject::RESERVED_SLOTS;
static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS);
static_assert(nfixed == TypedArrayObject::FIXED_DATA_START);
Rooted<SharedShape*> shape(
cx,
SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto),
nfixed, ObjectFlags()));
if (!shape) {
return nullptr;
}
NativeObject* obj = NativeObject::create(cx, allocKind, heap, shape);
if (!obj) {
return nullptr;
}
return &obj->as<TypedArrayObject>();
}
template <typename NativeType>
class TypedArrayObjectTemplate : public TypedArrayObject {
friend class TypedArrayObject;
public:
static constexpr Scalar::Type ArrayTypeID() {
return TypeIDOfType<NativeType>::id;
}
static constexpr JSProtoKey protoKey() {
return TypeIDOfType<NativeType>::protoKey;
}
static constexpr bool ArrayTypeIsUnsigned() {
return TypeIsUnsigned<NativeType>();
}
static constexpr bool ArrayTypeIsFloatingPoint() {
return TypeIsFloatingPoint<NativeType>();
}
static constexpr size_t BYTES_PER_ELEMENT = sizeof(NativeType);
static JSObject* createPrototype(JSContext* cx, JSProtoKey key) {
Handle<GlobalObject*> global = cx->global();
RootedObject typedArrayProto(
cx, GlobalObject::getOrCreateTypedArrayPrototype(cx, global));
if (!typedArrayProto) {
return nullptr;
}
const JSClass* clasp = TypedArrayObject::protoClassForType(ArrayTypeID());
return GlobalObject::createBlankPrototypeInheriting(cx, clasp,
typedArrayProto);
}
static JSObject* createConstructor(JSContext* cx, JSProtoKey key) {
Handle<GlobalObject*> global = cx->global();
RootedFunction ctorProto(
cx, GlobalObject::getOrCreateTypedArrayConstructor(cx, global));
if (!ctorProto) {
return nullptr;
}
JSFunction* fun = NewFunctionWithProto(
cx, class_constructor, 3, FunctionFlags::NATIVE_CTOR, nullptr,
ClassName(key, cx), ctorProto, gc::AllocKind::FUNCTION, TenuredObject);
if (fun) {
fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor);
}
return fun;
}
static inline const JSClass* instanceClass() {
return TypedArrayObject::classForType(ArrayTypeID());
}
static bool is(HandleValue v) {
return v.isObject() && v.toObject().hasClass(instanceClass());
}
static bool convertValue(JSContext* cx, HandleValue v, NativeType* result);
static TypedArrayObject* 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 TypedArrayObject* makeProtoInstance(JSContext* cx, HandleObject proto,
gc::AllocKind allocKind) {
MOZ_ASSERT(proto);
return NewTypedArrayObject(cx, instanceClass(), proto, allocKind,
gc::Heap::Default);
}
static TypedArrayObject* makeInstance(
JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
size_t byteOffset, size_t len, HandleObject proto,
gc::Heap heap = gc::Heap::Default) {
MOZ_ASSERT(len <= MaxByteLength / BYTES_PER_ELEMENT);
gc::AllocKind allocKind =
buffer ? gc::GetGCObjectKind(instanceClass())
: AllocKindForLazyBuffer(len * BYTES_PER_ELEMENT);
AutoSetNewObjectMetadata metadata(cx);
Rooted<TypedArrayObject*> obj(cx);
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 TypedArrayObject* makeTemplateObject(JSContext* cx, int32_t len) {
MOZ_ASSERT(len >= 0);
size_t nbytes;
MOZ_ALWAYS_TRUE(CalculateAllocSize<NativeType>(len, &nbytes));
bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass())
: AllocKindForLazyBuffer(nbytes);
MOZ_ASSERT(allocKind >= gc::GetGCObjectKind(instanceClass()));
AutoSetNewObjectMetadata metadata(cx);
Rooted<TypedArrayObject*> tarray(
cx, 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(TypedArrayObject* tarray, int32_t len) {
MOZ_ASSERT(len >= 0);
tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, NullValue());
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(TypedArrayObject::FIXED_DATA_START);
output[0] = TypedArrayObject::ZeroLengthArrayData;
}
#endif
}
static void initTypedArrayData(TypedArrayObject* 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 TypedArrayObject* makeTypedArrayWithTemplate(
JSContext* cx, TypedArrayObject* templateObj, int32_t len) {
if (len < 0 || size_t(len) > MaxByteLength / 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 <= MaxByteLength);
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());
TypedArrayObject* 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;
}
static TypedArrayObject* makeTypedArrayWithTemplate(
JSContext* cx, TypedArrayObject* templateObj, HandleObject array) {
MOZ_ASSERT(!IsWrapper(array));
MOZ_ASSERT(!array->is<ArrayBufferObjectMaybeShared>());
return fromArray(cx, array);
}
static TypedArrayObject* makeTypedArrayWithTemplate(
JSContext* cx, TypedArrayObject* templateObj, HandleObject arrayBuffer,
HandleValue byteOffsetValue, HandleValue lengthValue) {
MOZ_ASSERT(!IsWrapper(arrayBuffer));
MOZ_ASSERT(arrayBuffer->is<ArrayBufferObjectMaybeShared>());
uint64_t byteOffset, length;
if (!byteOffsetAndLength(cx, byteOffsetValue, lengthValue, &byteOffset,
&length)) {
return nullptr;
}
return fromBufferSameCompartment(
cx, arrayBuffer.as<ArrayBufferObjectMaybeShared>(), byteOffset, length,
nullptr);
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1 TypedArray ( ...args )
static bool class_constructor(JSContext* cx, unsigned argc, Value* vp) {
AutoJSConstructorProfilerEntry pseudoFrame(cx, "[TypedArray]");
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!ThrowIfNotConstructing(cx, args, "typed array")) {
return false;
}
// Steps 2-6.
JSObject* obj = create(cx, args);
if (!obj) {
return false;
}
args.rval().setObject(*obj);
return true;
}
private:
static JSObject* create(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(args.isConstructing());
// Steps 5 and 6.c.
if (args.length() == 0 || !args[0].isObject()) {
// Step 6.c.ii.
uint64_t len;
if (!ToIndex(cx, args.get(0), JSMSG_BAD_ARRAY_LENGTH, &len)) {
return nullptr;
}
// Steps 5.a and 6.c.iii.
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
return nullptr;
}
return fromLength(cx, len, proto);
}
RootedObject dataObj(cx, &args[0].toObject());
// Step 6.b.i.
// 23.2.5.1.1 AllocateTypedArray, step 1.
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
return nullptr;
}
// Steps 6.b.ii and 6.b.iv.
if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>()) {
return fromArray(cx, dataObj, proto);
}
// Steps 6.b.iii.1-2.
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer, steps 2 and 4.
uint64_t byteOffset, length;
if (!byteOffsetAndLength(cx, args.get(1), args.get(2), &byteOffset,
&length)) {
return nullptr;
}
// Step 6.b.iii.3.
if (dataObj->is<ArrayBufferObjectMaybeShared>()) {
HandleArrayBufferObjectMaybeShared buffer =
dataObj.as<ArrayBufferObjectMaybeShared>();
return fromBufferSameCompartment(cx, buffer, byteOffset, length, proto);
}
return fromBufferWrapped(cx, dataObj, byteOffset, length, proto);
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
// length ) Steps 2 and 4.
static bool byteOffsetAndLength(JSContext* cx, HandleValue byteOffsetValue,
HandleValue lengthValue, uint64_t* byteOffset,
uint64_t* length) {
// Step 2.
*byteOffset = 0;
if (!byteOffsetValue.isUndefined()) {
if (!ToIndex(cx, byteOffsetValue, byteOffset)) {
return false;
}
// Step 7.
if (*byteOffset % BYTES_PER_ELEMENT != 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
Scalar::name(ArrayTypeID()),
Scalar::byteSizeString(ArrayTypeID()));
return false;
}
}
// Step 4.
*length = UINT64_MAX;
if (!lengthValue.isUndefined()) {
if (!ToIndex(cx, lengthValue, length)) {
return false;
}
}
return true;
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
// length ) Steps 5-8.
static bool computeAndCheckLength(
JSContext* cx, HandleArrayBufferObjectMaybeShared bufferMaybeUnwrapped,
uint64_t byteOffset, uint64_t lengthIndex, size_t* length) {
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();
size_t len;
if (lengthIndex == UINT64_MAX) {
// 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;
}
if (byteOffset > bufferByteLength) {
// |byteOffset| is invalid.
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_LENGTH_BOUNDS,
Scalar::name(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);
}
if (len > MaxByteLength / BYTES_PER_ELEMENT) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_TOO_LARGE,
Scalar::name(ArrayTypeID()));
return false;
}
MOZ_ASSERT(len < SIZE_MAX);
*length = len;
return true;
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
// length ) Steps 5-13.
static TypedArrayObject* fromBufferSameCompartment(
JSContext* cx, HandleArrayBufferObjectMaybeShared buffer,
uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto) {
// Steps 5-8.
size_t length = 0;
if (!computeAndCheckLength(cx, buffer, byteOffset, lengthIndex, &length)) {
return nullptr;
}
// Steps 9-13.
return makeInstance(cx, buffer, byteOffset, length, proto);
}
// Create a TypedArray object in another compartment.
//
// ES6 supports creating a TypedArray in global A (using global A's
// TypedArray constructor) backed by an ArrayBuffer created in global B.
//
// Our TypedArrayObject implementation doesn't support a TypedArray in
// compartment A backed by an ArrayBuffer in compartment B. So in this
// case, we create the TypedArray in B (!) and return a cross-compartment
// wrapper.
//
// Extra twist: the spec says the new TypedArray's [[Prototype]] must be
// A's TypedArray.prototype. So even though we're creating the TypedArray
// in B, its [[Prototype]] must be (a cross-compartment wrapper for) the
// TypedArray.prototype in A.
static JSObject* fromBufferWrapped(JSContext* cx, HandleObject bufobj,
uint64_t byteOffset, uint64_t lengthIndex,
HandleObject proto) {
JSObject* unwrapped = CheckedUnwrapStatic(bufobj);
if (!unwrapped) {
ReportAccessDenied(cx);
return nullptr;
}
if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_BAD_ARGS);
return nullptr;
}
RootedArrayBufferObjectMaybeShared unwrappedBuffer(cx);
unwrappedBuffer = &unwrapped->as<ArrayBufferObjectMaybeShared>();
size_t length = 0;
if (!computeAndCheckLength(cx, unwrappedBuffer, byteOffset, lengthIndex,
&length)) {
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;
}
typedArray =
makeInstance(cx, unwrappedBuffer, byteOffset, length, wrappedProto);
if (!typedArray) {
return nullptr;
}
}
if (!cx->compartment()->wrap(cx, &typedArray)) {
return nullptr;
}
return typedArray;
}
public:
static JSObject* fromBuffer(JSContext* cx, HandleObject bufobj,
size_t byteOffset, int64_t lengthInt) {
if (byteOffset % BYTES_PER_ELEMENT != 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
Scalar::name(ArrayTypeID()),
Scalar::byteSizeString(ArrayTypeID()));
return nullptr; // invalid byteOffset
}
uint64_t lengthIndex = lengthInt >= 0 ? uint64_t(lengthInt) : UINT64_MAX;
if (bufobj->is<ArrayBufferObjectMaybeShared>()) {
HandleArrayBufferObjectMaybeShared buffer =
bufobj.as<ArrayBufferObjectMaybeShared>();
return fromBufferSameCompartment(cx, buffer, byteOffset, lengthIndex,
nullptr);
}
return fromBufferWrapped(cx, bufobj, byteOffset, lengthIndex, nullptr);
}
static bool maybeCreateArrayBuffer(JSContext* cx, uint64_t count,
MutableHandle<ArrayBufferObject*> buffer) {
if (count > MaxByteLength / BYTES_PER_ELEMENT) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_ARRAY_LENGTH);
return false;
}
size_t byteLength = count * BYTES_PER_ELEMENT;
MOZ_ASSERT(byteLength <= MaxByteLength);
static_assert(INLINE_BUFFER_LIMIT % BYTES_PER_ELEMENT == 0,
"ArrayBuffer inline storage shouldn't waste any space");
if (byteLength <= INLINE_BUFFER_LIMIT) {
// The array's data can be inline, and the buffer created lazily.
return true;
}
ArrayBufferObject* buf = ArrayBufferObject::createZeroed(cx, byteLength);
if (!buf) {
return false;
}
buffer.set(buf);
return true;
}
// ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
// 23.2.5.1.1 AllocateTypedArray ( constructorName, newTarget, defaultProto [
// , length ] )
static TypedArrayObject* fromLength(JSContext* cx, uint64_t nelements,
HandleObject proto = nullptr,
gc::Heap heap = gc::Heap::Default) {
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) {
return nullptr;
}
return 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());
return jit::AtomicOperations::loadSafeWhenRacy(
tarray->dataPointerEither().cast<NativeType*>() + index);
}
static void setIndex(TypedArrayObject& tarray, size_t index, NativeType val) {
MOZ_ASSERT(index < tarray.length());
jit::AtomicOperations::storeSafeWhenRacy(
tarray.dataPointerEither().cast<NativeType*>() + index, val);
}
static bool getElement(JSContext* cx, TypedArrayObject* tarray, size_t index,
MutableHandleValue val);
static bool getElementPure(TypedArrayObject* tarray, size_t index, Value* vp);
static bool setElement(JSContext* cx, Handle<TypedArrayObject*> obj,
uint64_t index, HandleValue v, ObjectOpResult& result);
};
template <typename NativeType>
bool TypedArrayObjectTemplate<NativeType>::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<int64_t>::convertValue(JSContext* cx,
HandleValue v,
int64_t* result) {
JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v));
return true;
}
template <>
bool TypedArrayObjectTemplate<uint64_t>::convertValue(JSContext* cx,
HandleValue v,
uint64_t* result) {
JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v));
return true;
}
// 9.4.5.11 IntegerIndexedElementSet ( O, index, value )
template <typename NativeType>
/* static */ bool TypedArrayObjectTemplate<NativeType>::setElement(
JSContext* cx, Handle<TypedArrayObject*> obj, uint64_t index, HandleValue v,
ObjectOpResult& result) {
MOZ_ASSERT(!obj->hasDetachedBuffer());
MOZ_ASSERT(index < obj->length());
// 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()) {
MOZ_ASSERT(!obj->hasDetachedBuffer(),
"detaching an array buffer sets the length to zero");
TypedArrayObjectTemplate<NativeType>::setIndex(*obj, index, nativeValue);
}
// Step 5.
return result.succeed();
}
#define CREATE_TYPE_FOR_TYPED_ARRAY(_, T, N) \
typedef TypedArrayObjectTemplate<T> N##Array;
JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPE_FOR_TYPED_ARRAY)
#undef CREATE_TYPE_FOR_TYPED_ARRAY
} /* anonymous namespace */
TypedArrayObject* js::NewTypedArrayWithTemplateAndLength(
JSContext* cx, HandleObject templateObj, int32_t len) {
MOZ_ASSERT(templateObj->is<TypedArrayObject>());
TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
switch (tobj->type()) {
#define CREATE_TYPED_ARRAY(_, T, N) \
case Scalar::N: \
return TypedArrayObjectTemplate<T>::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>());
TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
switch (tobj->type()) {
#define CREATE_TYPED_ARRAY(_, T, N) \
case Scalar::N: \
return TypedArrayObjectTemplate<T>::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>());
TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
switch (tobj->type()) {
#define CREATE_TYPED_ARRAY(_, T, N) \
case Scalar::N: \
return TypedArrayObjectTemplate<T>::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<uint8_t>::fromLength(cx, len, nullptr, heap);
}
template <typename T>
/* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromArray(
JSContext* cx, HandleObject other, HandleObject proto /* = nullptr */) {
// Allow nullptr proto for FriendAPI methods, which don't care about
// subclassing.
if (other->is<TypedArrayObject>()) {
return fromTypedArray(cx, other, /* wrapped= */ false, proto);
}
if (other->is<WrapperObject>() &&
UncheckedUnwrap(other)->is<TypedArrayObject>()) {
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 <typename T>
/* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromTypedArray(
JSContext* cx, HandleObject other, bool isWrapped, HandleObject proto) {
MOZ_ASSERT_IF(!isWrapped, other->is<TypedArrayObject>());
MOZ_ASSERT_IF(isWrapped, other->is<WrapperObject>() &&
UncheckedUnwrap(other)->is<TypedArrayObject>());
Rooted<TypedArrayObject*> srcArray(cx);
if (!isWrapped) {
srcArray = &other->as<TypedArrayObject>();
} else {
srcArray = other->maybeUnwrapAs<TypedArrayObject>();
if (!srcArray) {
ReportAccessDenied(cx);
return nullptr;
}
}
// InitializeTypedArrayFromTypedArray, step 1. (Skipped)
// InitializeTypedArrayFromTypedArray, step 2.
if (srcArray->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_DETACHED);
return nullptr;
}
// InitializeTypedArrayFromTypedArray, steps 3-7. (Skipped)
// InitializeTypedArrayFromTypedArray, step 8.
size_t elementLength = srcArray->length();
// InitializeTypedArrayFromTypedArray, step 9. (Skipped)
// InitializeTypedArrayFromTypedArray, step 10.a. (Partial)
// InitializeTypedArrayFromTypedArray, step 11.a.
Rooted<ArrayBufferObject*> 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::classes[ArrayTypeID()].name);
return nullptr;
}
// Step 6.b.i.
// InitializeTypedArrayFromTypedArray, steps 12-15.
Rooted<TypedArrayObject*> obj(
cx, 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<T, SharedOps>::setFromTypedArray(obj, srcArray, 0)) {
return nullptr;
}
} else {
if (!ElementSpecific<T, UnsharedOps>::setFromTypedArray(obj, srcArray, 0)) {
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<ArrayObject>(), 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 <typename T>
/* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::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<ArrayObject*> array = other.as<ArrayObject>();
// InitializeTypedArrayFromList, step 1.
size_t len = array->getDenseInitializedLength();
// InitializeTypedArrayFromList, step 2.
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, len, &buffer)) {
return nullptr;
}
// Steps 6.b.i.
Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));
if (!obj) {
return nullptr;
}
// InitializeTypedArrayFromList, steps 3-4.
MOZ_ASSERT(!obj->isSharedMemory());
if (!ElementSpecific<T, UnsharedOps>::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<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, len, &buffer)) {
return nullptr;
}
MOZ_ASSERT(len <= MaxByteLength / BYTES_PER_ELEMENT);
// Steps 6.b.i.
Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));
if (!obj) {
return nullptr;
}
// InitializeTypedArrayFromArrayLike, steps 3-4.
MOZ_ASSERT(!obj->isSharedMemory());
if (!ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(cx, obj, arrayLike,
len)) {
return nullptr;
}
// Step 6.b.v.
return obj;
}
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 <typename T>
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<T>(len, &nbytes) ||
nbytes > TypedArrayObject::MaxByteLength) {
return true;
}
res.set(TypedArrayObjectTemplate<T>::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(TypedArrayObjectTemplate<T>::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<T>::class_constructor) { \
return ::GetTemplateObjectForNative<T>(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<TypedArrayObject>();
args.rval().set(tarr->lengthValue());
return true;
}
static bool TypedArray_lengthGetter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<TypedArrayObject::is, LengthGetterImpl>(cx, args);
}
static bool ByteOffsetGetterImpl(JSContext* cx, const CallArgs& args) {
auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
args.rval().set(tarr->byteOffsetValue());
return true;
}
static bool TypedArray_byteOffsetGetter(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<TypedArrayObject::is, ByteOffsetGetterImpl>(cx,
args);
}
static bool ByteLengthGetterImpl(JSContext* cx, const CallArgs& args) {
auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
args.rval().set(tarr->byteLengthValue());
return true;
}
static bool TypedArray_byteLengthGetter(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<TypedArrayObject::is, ByteLengthGetterImpl>(cx,
args);
}
static bool BufferGetterImpl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(TypedArrayObject::is(args.thisv()));
Rooted<TypedArrayObject*> tarray(
cx, &args.thisv().toObject().as<TypedArrayObject>());
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<TypedArrayObject::is, BufferGetterImpl>(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<TypedArrayObject>()) {
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 <typename T>
static inline bool SetFromTypedArray(Handle<TypedArrayObject*> target,
Handle<TypedArrayObject*> source,
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<T, SharedOps>::setFromTypedArray(target, source,
offset);
}
return ElementSpecific<T, UnsharedOps>::setFromTypedArray(target, source,
offset);
}
template <typename T>
static inline bool SetFromNonTypedArray(JSContext* cx,
Handle<TypedArrayObject*> target,
HandleObject source, size_t len,
size_t offset) {
MOZ_ASSERT(!source->is<TypedArrayObject>(), "use SetFromTypedArray");
if (target->isSharedMemory()) {
return ElementSpecific<T, SharedOps>::setFromNonTypedArray(
cx, target, source, len, offset);
}
return ElementSpecific<T, UnsharedOps>::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<TypedArrayObject*> target,
double targetOffset,
Handle<TypedArrayObject*> source) {
// WARNING: |source| may be an unwrapped typed array from a different
// compartment. Proceed with caution!
MOZ_ASSERT(targetOffset >= 0);
// Steps 1-2. (Performed in caller.)
MOZ_ASSERT(!target->hasDetachedBuffer());
// Steps 4-5.
if (source->hasDetachedBuffer()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
// Step 3 (Reordered).
size_t targetLength = target->length();
// 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);