/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * 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.h" #include "mozilla/Alignment.h" #include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" #include "mozilla/PodOperations.h" #include #ifndef XP_WIN # include #endif #include "jsapi.h" #include "jsarray.h" #include "jscntxt.h" #include "jscpucfg.h" #include "jsnum.h" #include "jsobj.h" #include "jstypes.h" #include "jsutil.h" #ifdef XP_WIN # include "jswin.h" #endif #include "jswrapper.h" #include "builtin/TypedObjectConstants.h" #include "gc/Barrier.h" #include "gc/Marking.h" #include "jit/InlinableNatives.h" #include "js/Conversions.h" #include "vm/ArrayBufferObject.h" #include "vm/GlobalObject.h" #include "vm/Interpreter.h" #include "vm/PIC.h" #include "vm/SelfHosting.h" #include "vm/TypedArrayCommon.h" #include "vm/WrapperObject.h" #include "jsatominlines.h" #include "gc/Nursery-inl.h" #include "gc/StoreBuffer-inl.h" #include "vm/ArrayBufferObject-inl.h" #include "vm/NativeObject-inl.h" #include "vm/Shape-inl.h" using namespace js; using namespace js::gc; using mozilla::AssertedCast; using JS::CanonicalizeNaN; using JS::ToInt32; using JS::ToUint32; /* * 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: MOZ_CRASH("Unsupported TypedArray type"); } MOZ_ASSERT_UNREACHABLE("Invalid scalar type"); return false; } /* static */ int TypedArrayObject::lengthOffset() { return NativeObject::getFixedSlotOffset(LENGTH_SLOT); } /* static */ int TypedArrayObject::dataOffset() { return NativeObject::getPrivateDataOffset(DATA_SLOT); } void TypedArrayObject::notifyBufferDetached(JSContext* cx, void* newData) { MOZ_ASSERT(!isSharedMemory()); setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(0)); setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(0)); // 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. Nursery& nursery = cx->runtime()->gc.nursery; if (isTenured() && !hasBuffer() && !hasInlineElements() && !nursery.isInside(elements())) { js_free(elements()); } setPrivate(newData); } /* static */ bool TypedArrayObject::is(HandleValue v) { return v.isObject() && v.toObject().is(); } /* static */ bool TypedArrayObject::ensureHasBuffer(JSContext* cx, Handle tarray) { if (tarray->hasBuffer()) return true; Rooted buffer(cx, ArrayBufferObject::create(cx, tarray->byteLength())); if (!buffer) return false; if (!buffer->addView(cx, tarray)) return false; // tarray is not shared, because if it were it would have a buffer. memcpy(buffer->dataPointer(), tarray->viewDataUnshared(), tarray->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. Nursery& nursery = cx->runtime()->gc.nursery; if (tarray->isTenured() && !tarray->hasInlineElements() && !nursery.isInside(tarray->elements())) { js_free(tarray->elements()); } tarray->setPrivate(buffer->dataPointer()); tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*buffer)); // Notify compiled jit code that the base pointer has moved. MarkObjectStateChange(cx, tarray); 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 /* static */ void TypedArrayObject::trace(JSTracer* trc, JSObject* objArg) { // Handle all tracing required when the object has a buffer. ArrayBufferViewObject::trace(trc, objArg); } void TypedArrayObject::finalize(FreeOp* fop, JSObject* obj) { MOZ_ASSERT(!IsInsideNursery(obj)); TypedArrayObject* 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()) js_free(curObj->elements()); } /* static */ void TypedArrayObject::objectMoved(JSObject* obj, const JSObject* old) { TypedArrayObject* newObj = &obj->as(); const TypedArrayObject* oldObj = &old->as(); // Typed arrays with a buffer object do not need an update. if (oldObj->hasBuffer()) return; // Update the data slot pointer if it points to the old JSObject. if (oldObj->hasInlineElements()) newObj->setInlineElements(); } /* static */ size_t TypedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* obj, const JSObject* old, gc::AllocKind newAllocKind) { TypedArrayObject* newObj = &obj->as(); const TypedArrayObject* 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; Nursery& nursery = trc->runtime()->gc.nursery; void* buf = oldObj->elements(); if (!nursery.isInside(buf)) { nursery.removeMallocedBuffer(buf); 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 = 0; switch (oldObj->type()) { #define OBJECT_MOVED_TYPED_ARRAY(T, N) \ case Scalar::N: \ nbytes = oldObj->length() * sizeof(T); \ break; JS_FOR_EACH_TYPED_ARRAY(OBJECT_MOVED_TYPED_ARRAY) #undef OBJECT_MOVED_TYPED_ARRAY default: MOZ_CRASH("Unsupported TypedArray type"); } size_t headerSize = dataOffset() + sizeof(HeapSlot); // See AllocKindForLazyBuffer. 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 = JS_ROUNDUP(nbytes, sizeof(Value)); void* data = newObj->zone()->pod_malloc(nbytes); if (!data) oomUnsafe.crash("Failed to allocate typed array elements while tenuring."); MOZ_ASSERT(!nursery.isInside(data)); newObj->initPrivate(data); } 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.maybeSetForwardingPointer(trc, 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(this) + this->dataOffset(); *reinterpret_cast(dataSlot) = this->fixedData(TypedArrayObject::FIXED_DATA_START); } /* Helper clamped uint8_t type */ uint32_t JS_FASTCALL 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; } template static inline JSObject* NewArray(JSContext* cx, uint32_t nelements); namespace { // We allow nullptr for newTarget for all the creation methods, to allow for // JSFriendAPI functions that don't care about subclassing static bool GetPrototypeForInstance(JSContext* cx, HandleObject newTarget, MutableHandleObject proto) { if (newTarget) { if (!GetPrototypeFromConstructor(cx, newTarget, proto)) return false; } else { proto.set(nullptr); } return true; } enum class SpeciesConstructorOverride { None, ArrayBuffer }; template class TypedArrayObjectTemplate : public TypedArrayObject { friend class TypedArrayObject; public: typedef NativeType ElementType; static constexpr Scalar::Type ArrayTypeID() { return TypeIDOfType::id; } static bool ArrayTypeIsUnsigned() { return TypeIsUnsigned(); } static bool ArrayTypeIsFloatingPoint() { return TypeIsFloatingPoint(); } static const 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 Class* clasp = TypedArrayObject::protoClassForType(ArrayTypeID()); return GlobalObject::createBlankPrototypeInheriting(cx, global, 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, JSFunction::NATIVE_CTOR, nullptr, ClassName(key, cx), ctorProto, gc::AllocKind::FUNCTION, SingletonObject); if (fun) fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor); return fun; } static bool getOrCreateCreateArrayFromBufferFunction(JSContext* cx, MutableHandleValue fval) { RootedValue cache(cx, cx->global()->createArrayFromBuffer()); if (cache.isObject()) { MOZ_ASSERT(cache.toObject().is()); fval.set(cache); return true; } RootedFunction fun(cx); fun = NewNativeFunction(cx, ArrayBufferObject::createTypedArrayFromBuffer, 0, nullptr); if (!fun) return false; cx->global()->setCreateArrayFromBuffer(fun); fval.setObject(*fun); return true; } static inline const Class* 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* makeProtoInstance(JSContext* cx, HandleObject proto, AllocKind allocKind) { MOZ_ASSERT(proto); JSObject* obj = NewObjectWithClassProto(cx, instanceClass(), proto, allocKind); return obj ? &obj->as() : nullptr; } static TypedArrayObject* makeTypedInstance(JSContext* cx, uint32_t len, gc::AllocKind allocKind) { const Class* clasp = instanceClass(); if (len * sizeof(NativeType) >= TypedArrayObject::SINGLETON_BYTE_LENGTH) { JSObject* obj = NewBuiltinClassInstance(cx, clasp, allocKind, SingletonObject); if (!obj) return nullptr; return &obj->as(); } jsbytecode* pc; RootedScript script(cx, cx->currentScript(&pc)); NewObjectKind newKind = GenericObject; if (script && ObjectGroup::useSingletonForAllocationSite(script, pc, clasp)) newKind = SingletonObject; RootedObject obj(cx, NewBuiltinClassInstance(cx, clasp, allocKind, newKind)); if (!obj) return nullptr; if (script && !ObjectGroup::setAllocationSiteObjectGroup(cx, script, pc, obj, newKind == SingletonObject)) { return nullptr; } return &obj->as(); } static TypedArrayObject* makeInstance(JSContext* cx, Handle buffer, uint32_t byteOffset, uint32_t len, HandleObject proto) { MOZ_ASSERT_IF(!buffer, byteOffset == 0); gc::AllocKind allocKind = buffer ? GetGCObjectKind(instanceClass()) : AllocKindForLazyBuffer(len * sizeof(NativeType)); // Subclassing mandates that we hand in the proto every time. Most of // the time, though, that [[Prototype]] will not be interesting. If // it isn't, we can do some more TI optimizations. RootedObject checkProto(cx); if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &checkProto)) return nullptr; AutoSetNewObjectMetadata metadata(cx); Rooted obj(cx); if (proto && proto != checkProto) obj = makeProtoInstance(cx, proto, allocKind); else obj = makeTypedInstance(cx, len, allocKind); if (!obj) return nullptr; bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get()); obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer)); // This is invariant. Self-hosting code that sets BUFFER_SLOT // (if it does) must maintain it, should it need to. if (isSharedMemory) obj->setIsSharedMemory(); if (buffer) { obj->initViewData(buffer->dataPointerEither() + byteOffset); // If the buffer is for an inline typed object, the data pointer // may be in the nursery, so include a barrier to make sure this // object is updated if that typed object moves. auto ptr = buffer->dataPointerEither(); if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(ptr)) { // Shared buffer data should never be nursery-allocated, so we // need to fail here if isSharedMemory. However, mmap() can // place a SharedArrayRawBuffer up against the bottom end of a // nursery chunk, and a zero-length buffer will erroneously be // perceived as being inside the nursery; sidestep that. if (isSharedMemory) { MOZ_ASSERT(buffer->byteLength() == 0 && (uintptr_t(ptr.unwrapValue()) & gc::ChunkMask) == 0); } else { cx->runtime()->gc.storeBuffer.putWholeCell(obj); } } } else { void* data = obj->fixedData(FIXED_DATA_START); obj->initPrivate(data); memset(data, 0, len * sizeof(NativeType)); #ifdef DEBUG if (len == 0) { uint8_t* elements = static_cast(data); elements[0] = ZeroLengthArrayData; } #endif } obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(len)); obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset)); #ifdef DEBUG if (buffer) { uint32_t arrayByteLength = obj->byteLength(); uint32_t arrayByteOffset = obj->byteOffset(); uint32_t bufferByteLength = buffer->byteLength(); // Unwraps are safe: both are for the pointer value. if (IsArrayBuffer(buffer.get())) { MOZ_ASSERT_IF(!AsArrayBuffer(buffer.get()).isDetached(), buffer->dataPointerEither().unwrap(/*safe*/) <= obj->viewDataEither().unwrap(/*safe*/)); } MOZ_ASSERT(bufferByteLength - arrayByteOffset >= arrayByteLength); MOZ_ASSERT(arrayByteOffset <= bufferByteLength); } // Verify that the private slot is at the expected place MOZ_ASSERT(obj->numFixedSlots() == TypedArrayObject::DATA_SLOT); #endif // ArrayBufferObjects track their views to support detaching. if (buffer && buffer->is()) { if (!buffer->as().addView(cx, obj)) return nullptr; } return obj; } static TypedArrayObject* makeInstance(JSContext* cx, Handle buffer, uint32_t byteOffset, uint32_t len) { RootedObject proto(cx, nullptr); return makeInstance(cx, buffer, byteOffset, len, proto); } static TypedArrayObject* makeTemplateObject(JSContext* cx, int32_t len) { MOZ_ASSERT(len >= 0); size_t nbytes; MOZ_ALWAYS_TRUE(CalculateAllocSize(len, &nbytes)); MOZ_ASSERT(nbytes < TypedArrayObject::SINGLETON_BYTE_LENGTH); NewObjectKind newKind = TenuredObject; bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT; const Class* clasp = instanceClass(); gc::AllocKind allocKind = !fitsInline ? GetGCObjectKind(clasp) : AllocKindForLazyBuffer(nbytes); MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, clasp)); allocKind = GetBackgroundAllocKind(allocKind); AutoSetNewObjectMetadata metadata(cx); jsbytecode* pc; RootedScript script(cx, cx->currentScript(&pc)); if (script && ObjectGroup::useSingletonForAllocationSite(script, pc, clasp)) newKind = SingletonObject; JSObject* tmp = NewBuiltinClassInstance(cx, clasp, allocKind, newKind); if (!tmp) return nullptr; Rooted tarray(cx, &tmp->as()); initTypedArraySlots(cx, tarray, len); // Template objects do not need memory for its elements, since there // won't be any elements to store. Therefore, we set the pointer to // nullptr and avoid allocating memory that will never be used. tarray->initPrivate(nullptr); if (script && !ObjectGroup::setAllocationSiteObjectGroup(cx, script, pc, tarray, newKind == SingletonObject)) { return nullptr; } return tarray; } static void initTypedArraySlots(JSContext* cx, TypedArrayObject* tarray, int32_t len) { MOZ_ASSERT(len >= 0); tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, NullValue()); tarray->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(AssertedCast(len))); tarray->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(0)); // Verify that the private slot is at the expected place. MOZ_ASSERT(tarray->numFixedSlots() == TypedArrayObject::DATA_SLOT); #ifdef DEBUG if (len == 0) { uint8_t* output = tarray->fixedData(TypedArrayObject::FIXED_DATA_START); output[0] = TypedArrayObject::ZeroLengthArrayData; } #endif } static void initTypedArrayData(JSContext* cx, TypedArrayObject* tarray, int32_t len, void* buf, AllocKind allocKind) { if (buf) { #ifdef DEBUG Nursery& nursery = cx->runtime()->gc.nursery; MOZ_ASSERT_IF(!nursery.isInside(buf) && !tarray->hasInlineElements(), tarray->isTenured()); #endif tarray->initPrivate(buf); } else { size_t nbytes = len * sizeof(NativeType); #ifdef DEBUG size_t dataOffset = TypedArrayObject::dataOffset(); size_t offset = dataOffset + sizeof(HeapSlot); MOZ_ASSERT(offset + nbytes <= GetGCKindBytes(allocKind)); #endif void* data = tarray->fixedData(FIXED_DATA_START); tarray->initPrivate(data); memset(data, 0, nbytes); } } static TypedArrayObject* makeTypedArrayWithTemplate(JSContext* cx, TypedArrayObject* templateObj, int32_t len) { if (len < 0 || uint32_t(len) >= INT32_MAX / sizeof(NativeType)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); return nullptr; } size_t nbytes; MOZ_ALWAYS_TRUE(js::CalculateAllocSize(len, &nbytes)); bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT; AutoSetNewObjectMetadata metadata(cx); const Class* clasp = templateObj->group()->clasp(); gc::AllocKind allocKind = !fitsInline ? GetGCObjectKind(clasp) : AllocKindForLazyBuffer(nbytes); MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, clasp)); allocKind = GetBackgroundAllocKind(allocKind); RootedObjectGroup group(cx, templateObj->group()); NewObjectKind newKind = TenuredObject; ScopedJSFreePtr buf; if (!fitsInline && len > 0) { buf = cx->zone()->pod_malloc(nbytes); if (!buf) { ReportOutOfMemory(cx); return nullptr; } memset(buf, 0, nbytes); } RootedObject tmp(cx, NewObjectWithGroup(cx, group, allocKind, newKind)); if (!tmp) return nullptr; TypedArrayObject* obj = &tmp->as(); initTypedArraySlots(cx, obj, len); initTypedArrayData(cx, obj, len, buf.forget(), allocKind); return obj; } /* * new [Type]Array(length) * new [Type]Array(otherTypedArray) * new [Type]Array(JSArray) * new [Type]Array(ArrayBuffer, [optional] byteOffset, [optional] length) */ static bool class_constructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!ThrowIfNotConstructing(cx, args, "typed array")) return false; JSObject* obj = create(cx, args); if (!obj) return false; args.rval().setObject(*obj); return true; } static JSObject* create(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(args.isConstructing()); RootedObject newTarget(cx, &args.newTarget().toObject()); /* () or (number) */ uint32_t len = 0; if (args.length() == 0 || ValueIsLength(args[0], &len)) return fromLength(cx, len, newTarget); /* (not an object) */ if (!args[0].isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); return nullptr; } RootedObject dataObj(cx, &args.get(0).toObject()); /* * (typedArray) * (sharedTypedArray) * (type[] array) * * Otherwise create a new typed array and copy elements 0..len-1 * properties from the object, treating it as some sort of array. * Note that offset and length will be ignored. Note that a * shared array's values are copied here. */ if (!UncheckedUnwrap(dataObj)->is()) return fromArray(cx, dataObj, newTarget); /* (ArrayBuffer, [byteOffset, [length]]) */ RootedObject proto(cx); if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return nullptr; int32_t byteOffset = 0; if (args.hasDefined(1)) { if (!ToInt32(cx, args[1], &byteOffset)) return nullptr; if (byteOffset < 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "1"); return nullptr; } } int32_t length = -1; if (args.hasDefined(2)) { if (!ToInt32(cx, args[2], &length)) return nullptr; if (length < 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "2"); return nullptr; } } return fromBufferWithProto(cx, dataObj, byteOffset, length, proto); } public: static JSObject* fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt) { return fromBufferWithProto(cx, bufobj, byteOffset, lengthInt, nullptr); } static JSObject* fromBufferWithProto(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt, HandleObject proto) { if (bufobj->is()) { /* * Normally, NonGenericMethodGuard handles the case of transparent * wrappers. However, we have a peculiar situation: we want to * construct the new typed array in the compartment of the buffer, * so that the typed array can point directly at their buffer's * data without crossing compartment boundaries. So we use the * machinery underlying NonGenericMethodGuard directly to proxy the * native call. We will end up with a wrapper in the origin * compartment for a view in the target compartment referencing the * ArrayBufferObject in that same compartment. */ JSObject* wrapped = CheckedUnwrap(bufobj); if (!wrapped) { JS_ReportErrorASCII(cx, "Permission denied to access object"); return nullptr; } if (!IsAnyArrayBuffer(wrapped)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); return nullptr; // must be arrayBuffer } /* * And for even more fun, the new view's prototype should be * set to the origin compartment's prototype object, not the * target's (specifically, the actual view in the target * compartment will use as its prototype a wrapper around the * origin compartment's view.prototype object). * * Rather than hack some crazy solution together, implement * this all using a private helper function, created when * ArrayBufferObject was initialized and cached in the global. * This reuses all the existing cross-compartment crazy so we * don't have to do anything *uniquely* crazy here. */ RootedObject protoRoot(cx, proto); if (!protoRoot) { if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot)) return nullptr; } FixedInvokeArgs<3> args(cx); args[0].setNumber(byteOffset); args[1].setInt32(lengthInt); args[2].setObject(*protoRoot); RootedValue fval(cx); if (!getOrCreateCreateArrayFromBufferFunction(cx, &fval)) return nullptr; RootedValue thisv(cx, ObjectValue(*bufobj)); RootedValue rval(cx); if (!js::Call(cx, fval, thisv, args, &rval)) return nullptr; return &rval.toObject(); } if (!IsAnyArrayBuffer(bufobj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); return nullptr; // must be arrayBuffer } Rooted buffer(cx); if (IsArrayBuffer(bufobj)) { ArrayBufferObject& buf = AsArrayBuffer(bufobj); if (buf.isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return nullptr; } buffer = static_cast(&buf); } else { buffer = static_cast(&AsSharedArrayBuffer(bufobj)); } if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); return nullptr; // invalid byteOffset } uint32_t len; if (lengthInt == -1) { len = (buffer->byteLength() - byteOffset) / sizeof(NativeType); if (len * sizeof(NativeType) != buffer->byteLength() - byteOffset) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N } } else { len = uint32_t(lengthInt); } // Go slowly and check for overflow. uint32_t arrayByteLength = len * sizeof(NativeType); if (len >= INT32_MAX / sizeof(NativeType) || byteOffset >= INT32_MAX - arrayByteLength) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType) } if (arrayByteLength + byteOffset > buffer->byteLength()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); return nullptr; // byteOffset + len is too big for the arraybuffer } return makeInstance(cx, buffer, byteOffset, len, proto); } static bool maybeCreateArrayBuffer(JSContext* cx, uint32_t count, uint32_t unit, HandleObject nonDefaultProto, MutableHandle buffer) { if (count >= INT32_MAX / unit) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NEED_DIET, "size and count"); return false; } uint32_t byteLength = count * unit; MOZ_ASSERT(byteLength < INT32_MAX); static_assert(INLINE_BUFFER_LIMIT % sizeof(NativeType) == 0, "ArrayBuffer inline storage shouldn't waste any space"); if (!nonDefaultProto && byteLength <= INLINE_BUFFER_LIMIT) { // The array's data can be inline, and the buffer created lazily. return true; } ArrayBufferObject* buf = ArrayBufferObject::create(cx, byteLength, nonDefaultProto); if (!buf) return false; buffer.set(buf); return true; } static JSObject* fromLength(JSContext* cx, uint32_t nelements, HandleObject newTarget = nullptr) { RootedObject proto(cx); if (!GetPrototypeForInstance(cx, newTarget, &proto)) return nullptr; Rooted buffer(cx); if (!maybeCreateArrayBuffer(cx, nelements, BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; return makeInstance(cx, buffer, 0, nelements, proto); } static bool AllocateArrayBuffer(JSContext* cx, HandleValue ctor, uint32_t count, uint32_t unit, MutableHandle buffer); static bool CloneArrayBufferNoCopy(JSContext* cx, Handle srcBuffer, bool isWrapped, uint32_t srcByteOffset, uint32_t srcLength, SpeciesConstructorOverride override, MutableHandle buffer); static JSObject* fromArray(JSContext* cx, HandleObject other, HandleObject newTarget = nullptr); static JSObject* fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped, HandleObject newTarget); static JSObject* fromObject(JSContext* cx, HandleObject other, HandleObject newTarget); static const NativeType getIndex(JSObject* obj, uint32_t index) { TypedArrayObject& tarray = obj->as(); MOZ_ASSERT(index < tarray.length()); return jit::AtomicOperations::loadSafeWhenRacy(tarray.viewDataEither().cast() + index); } static void setIndex(TypedArrayObject& tarray, uint32_t index, NativeType val) { MOZ_ASSERT(index < tarray.length()); jit::AtomicOperations::storeSafeWhenRacy(tarray.viewDataEither().cast() + index, val); } static bool getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, MutableHandleValue val); static bool getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp); static bool setElement(JSContext* cx, Handle obj, uint64_t index, HandleValue v, ObjectOpResult& result); static bool defineElement(JSContext* cx, HandleObject obj, uint64_t index, HandleValue v, ObjectOpResult& result); }; template bool TypedArrayObjectTemplate::convertValue(JSContext* cx, HandleValue v, NativeType* result) { double d; if (!ToNumber(cx, v, &d)) { return false; } #ifdef JS_MORE_DETERMINISTIC // See the comment in ElementSpecific::doubleToNative. d = JS::CanonicalizeNaN(d); #endif // Assign based on characteristics of the destination type if (ArrayTypeIsFloatingPoint()) { *result = NativeType(d); } else if (ArrayTypeIsUnsigned()) { MOZ_ASSERT(sizeof(NativeType) <= 4); uint32_t n = ToUint32(d); *result = NativeType(n); } else if (ArrayTypeID() == Scalar::Uint8Clamped) { // The uint8_clamped type has a special rounding converter // for doubles. *result = NativeType(d); } else { MOZ_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 // 7.8 IntegerIndexedElementSet ( O, index, value ) template /* static */ bool TypedArrayObjectTemplate::setElement( JSContext* cx, Handle obj, uint64_t index, HandleValue v, ObjectOpResult& result) { // Steps 1-2 are enforced by the caller. // Steps 3-6. NativeType nativeValue; if (!convertValue(cx, v, &nativeValue)) { return false; } // Step 8. if (obj->hasDetachedBuffer()) { return result.failSoft(JSMSG_TYPED_ARRAY_DETACHED); } // Steps 9-10 are enforced by the caller. // Step 11. uint32_t length = obj->length(); // Step 12. if (index >= length) { return result.failSoft(JSMSG_BAD_INDEX); } // Steps 7, 13-16. TypedArrayObjectTemplate::setIndex(*obj, index, nativeValue); // Step 17. return result.succeed(); } // Version of IntegerIndexedElementSet with no length check, used in // [[DefineOwnProperty]] template /* static */ bool TypedArrayObjectTemplate::defineElement( JSContext* cx, HandleObject obj, uint64_t index, HandleValue v, ObjectOpResult& result) { // Steps 1-2 are enforced by the caller. // Steps 3-6. NativeType nativeValue; if (!convertValue(cx, v, &nativeValue)) { return false; } // Step 8. if (obj->as().hasDetachedBuffer()) { return result.fail(JSMSG_TYPED_ARRAY_DETACHED); } // Steps 9-12 are enforced by the caller. // Steps 7, 13-16. TypedArrayObjectTemplate::setIndex(obj->as(), index, nativeValue); // Step 17. return result.succeed(); } #define CREATE_TYPE_FOR_TYPED_ARRAY(T, N) \ typedef TypedArrayObjectTemplate N##Array; JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPE_FOR_TYPED_ARRAY) #undef CREATE_TYPE_FOR_TYPED_ARRAY } /* anonymous namespace */ TypedArrayObject* js::TypedArrayCreateWithTemplate(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 TypedArrayObjectTemplate::makeTypedArrayWithTemplate(cx, tobj, len); JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY) #undef CREATE_TYPED_ARRAY default: MOZ_CRASH("Unsupported TypedArray type"); } } template struct TypedArrayObject::OfType { typedef TypedArrayObjectTemplate Type; }; // ES 2016 draft Mar 25, 2016 24.1.1.1. // byteLength = count * unit template /* static */ bool TypedArrayObjectTemplate::AllocateArrayBuffer(JSContext* cx, HandleValue ctor, uint32_t count, uint32_t unit, MutableHandle buffer) { // ES 2016 draft Mar 25, 2016 24.1.1.1 step 1 (partially). // ES 2016 draft Mar 25, 2016 9.1.14 steps 1-2. MOZ_ASSERT(ctor.isObject()); RootedObject proto(cx); RootedObject ctorObj(cx, &ctor.toObject()); if (!GetPrototypeFromConstructor(cx, ctorObj, &proto)) return false; JSObject* arrayBufferProto = GlobalObject::getOrCreateArrayBufferPrototype(cx, cx->global()); if (!arrayBufferProto) return false; if (proto == arrayBufferProto) proto = nullptr; // ES 2016 draft Mar 25, 2016 24.1.1.1 steps 1 (remaining part), 2-6. if (!maybeCreateArrayBuffer(cx, count, unit, proto, buffer)) return false; return true; } static bool IsArrayBufferSpecies(JSContext* cx, JSFunction* species) { return IsSelfHostedFunctionWithName(species, cx->names().ArrayBufferSpecies); } static JSObject* GetSpeciesConstructor(JSContext* cx, HandleObject obj, bool isWrapped, SpeciesConstructorOverride override) { if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer)) return nullptr; RootedObject defaultCtor(cx, &cx->global()->getConstructor(JSProto_ArrayBuffer).toObject()); // Use the current global's ArrayBuffer if the override is set. if (override == SpeciesConstructorOverride::ArrayBuffer) return defaultCtor; RootedObject wrappedObj(cx, obj); if (isWrapped && !cx->compartment()->wrap(cx, &wrappedObj)) return nullptr; return SpeciesConstructor(cx, wrappedObj, defaultCtor, IsArrayBufferSpecies); } // ES 2017 draft rev 8633ffd9394b203b8876bb23cb79aff13eb07310 24.1.1.4. template /* static */ bool TypedArrayObjectTemplate::CloneArrayBufferNoCopy(JSContext* cx, Handle srcBuffer, bool isWrapped, uint32_t srcByteOffset, uint32_t srcLength, SpeciesConstructorOverride override, MutableHandle buffer) { // Step 1 (skipped). // Step 2.a. JSObject* ctorObj = GetSpeciesConstructor(cx, srcBuffer, isWrapped, override); if (!ctorObj) return false; RootedValue cloneCtor(cx, ObjectValue(*ctorObj)); // Step 2.b. if (srcBuffer->isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return false; } // Steps 3-4 (skipped). // Steps 5. if (!AllocateArrayBuffer(cx, cloneCtor, srcLength, 1, buffer)) return false; // Step 6. if (srcBuffer->isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return false; } // Steps 7-8 (done in caller). // Step 9. return true; } template /* static */ JSObject* TypedArrayObjectTemplate::fromArray(JSContext* cx, HandleObject other, HandleObject newTarget /* = nullptr */) { // Allow nullptr newTarget for FriendAPI methods, which don't care about // subclassing. if (other->is()) return fromTypedArray(cx, other, /* wrapped= */ false, newTarget); if (other->is() && UncheckedUnwrap(other)->is()) return fromTypedArray(cx, other, /* wrapped= */ true, newTarget); return fromObject(cx, other, newTarget); } // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 // 22.2.4.3 TypedArray ( typedArray ) template /* static */ JSObject* TypedArrayObjectTemplate::fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped, HandleObject newTarget) { // Step 1. MOZ_ASSERT_IF(!isWrapped, other->is()); MOZ_ASSERT_IF(isWrapped, other->is() && UncheckedUnwrap(other)->is()); // Step 2 (done in caller). // Step 4 (partially). RootedObject proto(cx); if (!GetPrototypeForInstance(cx, newTarget, &proto)) return nullptr; // Step 5. Rooted srcArray(cx); if (!isWrapped) { srcArray = &other->as(); if (!TypedArrayObject::ensureHasBuffer(cx, srcArray)) return nullptr; } else { RootedObject unwrapped(cx, CheckedUnwrap(other)); if (!unwrapped) { JS_ReportErrorASCII(cx, "Permission denied to access object"); return nullptr; } JSAutoCompartment ac(cx, unwrapped); srcArray = &unwrapped->as(); if (!TypedArrayObject::ensureHasBuffer(cx, srcArray)) return nullptr; } // Step 6. Rooted srcData(cx, srcArray->bufferEither()); // Step 7. if (srcData->isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return nullptr; } // Step 9. uint32_t elementLength = srcArray->length(); // Steps 10-11. Scalar::Type srcType = srcArray->type(); // Step 12 (skipped). // Step 13. uint32_t srcByteOffset = srcArray->byteOffset(); // Steps 16-17. bool isShared = srcArray->isSharedMemory(); SpeciesConstructorOverride override = isShared ? SpeciesConstructorOverride::ArrayBuffer : SpeciesConstructorOverride::None; // Steps 8, 16-17. Rooted buffer(cx); if (ArrayTypeID() == srcType) { // Step 16.a. uint32_t srcLength = srcArray->byteLength(); // Steps 16.b-c. if (!CloneArrayBufferNoCopy(cx, srcData, isWrapped, srcByteOffset, srcLength, override, &buffer)) { return nullptr; } } else { // Steps 17.a-b. JSObject* ctorObj = GetSpeciesConstructor(cx, srcData, isWrapped, override); if (!ctorObj) return nullptr; RootedValue bufferCtor(cx, ObjectValue(*ctorObj)); // Steps 14-15, 17.c. if (!AllocateArrayBuffer(cx, bufferCtor, elementLength, BYTES_PER_ELEMENT, &buffer)) return nullptr; // Step 17.d. if (srcArray->hasDetachedBuffer()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return nullptr; } } // BigInt proposal 7.24, step 19.c. if (Scalar::isBigIntType(ArrayTypeID()) != Scalar::isBigIntType(srcArray->type())) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); return nullptr; } // Steps 3-4 (remaining part), 18-21. Rooted obj(cx, makeInstance(cx, buffer, 0, elementLength, proto)); if (!obj) return nullptr; // Steps 17.e-h or 24.1.1.4 step 8. if (!TypedArrayMethods::setFromTypedArray(cx, obj, srcArray)) return nullptr; // Step 22. 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); } // ES2017 draft rev 6859bb9ccaea9c6ede81d71e5320e3833b92cb3e // 22.2.4.4 TypedArray ( object ) template /* static */ JSObject* TypedArrayObjectTemplate::fromObject(JSContext* cx, HandleObject other, HandleObject newTarget) { // Steps 1-2 (Already performed in caller). // Steps 3-4 (Allocation deferred until later). RootedObject proto(cx); if (!GetPrototypeForInstance(cx, newTarget, &proto)) return nullptr; bool optimized = false; if (!IsOptimizableInit(cx, other, &optimized)) return nullptr; // Fast path when iterable is a packed array using the default iterator. if (optimized) { // Step 6.a (We don't need to call IterableToList for the fast path). RootedArrayObject array(cx, &other->as()); // Step 6.b. uint32_t len = array->getDenseInitializedLength(); // Step 6.c. Rooted buffer(cx); if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; Rooted obj(cx, makeInstance(cx, buffer, 0, len, proto)); if (!obj) return nullptr; // Steps 6.d-e. if (!TypedArrayMethods::initFromIterablePackedArray(cx, obj, array)) return nullptr; // Step 6.f (The assertion isn't applicable for the fast path). // Step 6.g. return obj; } // Step 5. RootedValue callee(cx); RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator)); if (!GetProperty(cx, other, other, iteratorId, &callee)) return nullptr; // Steps 6-8. 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_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, bytes.get()); return nullptr; } FixedInvokeArgs<2> args2(cx); args2[0].setObject(*other); args2[1].set(callee); // Step 6.a. RootedValue rval(cx); if (!CallSelfHostedFunction(cx, cx->names().IterableToList, UndefinedHandleValue, args2, &rval)) { return nullptr; } // Steps 6.b-g (Implemented in steps 9-13 below). arrayLike = &rval.toObject(); } else { // Step 7 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 8. arrayLike = other; } // Step 9. uint32_t len; if (!GetLengthProperty(cx, arrayLike, &len)) return nullptr; // Step 10. Rooted buffer(cx); if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer)) return nullptr; Rooted obj(cx, makeInstance(cx, buffer, 0, len, proto)); if (!obj) return nullptr; // Steps 11-12. if (!TypedArrayMethods::setFromNonTypedArray(cx, obj, arrayLike, len)) return nullptr; // Step 13. 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; } /* static */ bool TypedArrayObject::GetTemplateObjectForNative(JSContext* cx, Native native, uint32_t len, MutableHandleObject res) { #define CHECK_TYPED_ARRAY_CONSTRUCTOR(T, N) \ if (native == &TypedArrayObjectTemplate::class_constructor) { \ size_t nbytes; \ if (!js::CalculateAllocSize(len, &nbytes)) \ return true; \ \ if (nbytes < TypedArrayObject::SINGLETON_BYTE_LENGTH) { \ res.set(TypedArrayObjectTemplate::makeTemplateObject(cx, len)); \ return !!res; \ } \ } JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR) #undef CHECK_TYPED_ARRAY_CONSTRUCTOR return true; } /* * These next 3 functions are brought to you by the buggy GCC we use to build * B2G ICS. Older GCC versions have a bug in which they fail to compile * reinterpret_casts of templated functions with the message: "insufficient * contextual information to determine type". JS_PSG needs to * reinterpret_cast, so this causes problems for us here. * * We could restructure all this code to make this nicer, but since ICS isn't * going to be around forever (and since this bug is fixed with the newer GCC * versions we use on JB and KK), the workaround here is designed for ease of * removal. When you stop seeing ICS Emulator builds on TBPL, remove these 3 * JSNatives and insert the templated callee directly into the JS_PSG below. */ static bool TypedArray_lengthGetter(JSContext* cx, unsigned argc, Value* vp) { return TypedArrayObject::Getter(cx, argc, vp); \ } static bool TypedArray_byteLengthGetter(JSContext* cx, unsigned argc, Value* vp) { return TypedArrayObject::Getter(cx, argc, vp); } static bool TypedArray_byteOffsetGetter(JSContext* cx, unsigned argc, Value* vp) { return TypedArrayObject::Getter(cx, argc, vp); } bool BufferGetterImpl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(TypedArrayObject::is(args.thisv())); Rooted tarray(cx, &args.thisv().toObject().as()); if (!TypedArrayObject::ensureHasBuffer(cx, tarray)) return false; args.rval().set(TypedArrayObject::bufferValue(tarray)); return true; } /*static*/ bool js::TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /* 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_SELF_HOSTED_SYM_GET(toStringTag, "TypedArrayToStringTag", 0), JS_PS_END }; /* static */ bool TypedArrayObject::set(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod::set>(cx, args); } /* static */ const JSFunctionSpec TypedArrayObject::protoFunctions[] = { JS_SELF_HOSTED_FN("subarray", "TypedArraySubarray", 2, 0), #if 0 /* disabled until perf-testing is completed */ JS_SELF_HOSTED_FN("set", "TypedArraySet", 2, 0), #else JS_FN("set", TypedArrayObject::set, 1, 0), #endif JS_SELF_HOSTED_FN("copyWithin", "TypedArrayCopyWithin", 3, 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", 2, 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), /* ES2022 additions */ JS_SELF_HOSTED_FN("at", "TypedArrayAt", 1, 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 const ClassSpec TypedArrayObjectSharedTypedArrayPrototypeClassSpec = { GenericCreateConstructor, GenericCreatePrototype, TypedArrayObject::staticFunctions, TypedArrayObject::staticProperties, TypedArrayObject::protoFunctions, TypedArrayObject::protoAccessors, nullptr, ClassSpec::DontDefineConstructor }; /* static */ const Class TypedArrayObject::sharedTypedArrayPrototypeClass = { // Actually ({}).toString.call(%TypedArray%.prototype) should throw, // because %TypedArray%.prototype lacks the the typed array internal // slots. (It's not clear this is desirable -- particularly applied to // the actual typed array prototypes, see below -- but it's what ES6 // draft 20140824 requires.) But this is about as much as we can do // until we implement @@toStringTag. "???", JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray), JS_NULL_CLASS_OPS, &TypedArrayObjectSharedTypedArrayPrototypeClassSpec }; template bool ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args) { typedef TypedArrayObjectTemplate ArrayType; MOZ_ASSERT(IsAnyArrayBuffer(args.thisv())); MOZ_ASSERT(args.length() == 3); Rooted buffer(cx, &args.thisv().toObject()); Rooted proto(cx, &args[2].toObject()); Rooted obj(cx); double byteOffset = args[0].toNumber(); MOZ_ASSERT(0 <= byteOffset); MOZ_ASSERT(byteOffset <= UINT32_MAX); MOZ_ASSERT(byteOffset == uint32_t(byteOffset)); obj = ArrayType::fromBufferWithProto(cx, buffer, uint32_t(byteOffset), args[1].toInt32(), proto); if (!obj) return false; args.rval().setObject(*obj); return true; } template bool ArrayBufferObject::createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod >(cx, args); } // This default implementation is only valid for integer types less // than 32-bits in size. template bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_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; } namespace { // We need to specialize for floats and other integer types. template<> bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { *vp = Int32Value(getIndex(tarray, index)); return true; } template<> bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { uint32_t val = getIndex(tarray, index); *vp = NumberValue(val); return true; } template<> bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_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 = DoubleValue(CanonicalizeNaN(dval)); return true; } template<> bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_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 = DoubleValue(CanonicalizeNaN(val)); return true; } template <> bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { return false; } template <> bool TypedArrayObjectTemplate::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { return false; } } /* anonymous namespace */ namespace { template bool TypedArrayObjectTemplate::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, MutableHandleValue val) { MOZ_ALWAYS_TRUE(getElementPure(tarray, index, val.address())); return true; } template <> bool TypedArrayObjectTemplate::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_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(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_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 */ static NewObjectKind DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto) { if (!proto && byteLength >= TypedArrayObject::SINGLETON_BYTE_LENGTH) return SingletonObject; jsbytecode* pc; JSScript* script = cx->currentScript(&pc); if (script && ObjectGroup::useSingletonForAllocationSite(script, pc, &DataViewObject::class_)) return SingletonObject; return GenericObject; } DataViewObject* DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, Handle arrayBuffer, JSObject* protoArg) { if (arrayBuffer->isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return nullptr; } MOZ_ASSERT(byteOffset <= INT32_MAX); MOZ_ASSERT(byteLength <= INT32_MAX); MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX); MOZ_ASSERT(!arrayBuffer || !arrayBuffer->is()); RootedObject proto(cx, protoArg); RootedObject obj(cx); NewObjectKind newKind = DataViewNewObjectKind(cx, byteLength, proto); obj = NewObjectWithClassProto(cx, &class_, proto, newKind); if (!obj) return nullptr; if (!proto) { if (byteLength >= TypedArrayObject::SINGLETON_BYTE_LENGTH) { MOZ_ASSERT(obj->isSingleton()); } else { jsbytecode* pc; RootedScript script(cx, cx->currentScript(&pc)); if (script && !ObjectGroup::setAllocationSiteObjectGroup(cx, script, pc, obj, newKind == SingletonObject)) { return nullptr; } } } // Caller should have established these preconditions, and no // (non-self-hosted) JS code has had an opportunity to run so nothing can // have invalidated them. MOZ_ASSERT(byteOffset <= arrayBuffer->byteLength()); MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength()); DataViewObject& dvobj = obj->as(); dvobj.setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset)); dvobj.setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(byteLength)); dvobj.setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*arrayBuffer)); dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset); // Include a barrier if the data view's data pointer is in the nursery, as // is done for typed arrays. if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(arrayBuffer->dataPointer())) cx->runtime()->gc.storeBuffer.putWholeCell(obj); // Verify that the private slot is at the expected place MOZ_ASSERT(dvobj.numFixedSlots() == TypedArrayObject::DATA_SLOT); if (!arrayBuffer->addView(cx, &dvobj)) return nullptr; return &dvobj; } bool DataViewObject::getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args, uint32_t* byteOffsetPtr, uint32_t* byteLengthPtr) { if (!IsArrayBuffer(bufobj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, "DataView", "ArrayBuffer", bufobj->getClass()->name); return false; } Rooted buffer(cx, &AsArrayBuffer(bufobj)); uint32_t byteOffset = 0; uint32_t byteLength = buffer->byteLength(); if (args.length() > 1) { if (!ToUint32(cx, args[1], &byteOffset)) return false; if (byteOffset > INT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); return false; } } if (buffer->isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return false; } if (args.length() > 1) { if (byteOffset > byteLength) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); return false; } if (args.get(2).isUndefined()) { byteLength -= byteOffset; } else { if (!ToUint32(cx, args[2], &byteLength)) return false; if (byteLength > INT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); return false; } MOZ_ASSERT(byteOffset + byteLength >= byteOffset, "can't overflow: both numbers are less than INT32_MAX"); if (byteOffset + byteLength > buffer->byteLength()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); return false; } } } /* The sum of these cannot overflow a uint32_t */ MOZ_ASSERT(byteOffset <= INT32_MAX); MOZ_ASSERT(byteLength <= INT32_MAX); *byteOffsetPtr = byteOffset; *byteLengthPtr = byteLength; return true; } bool DataViewObject::constructSameCompartment(JSContext* cx, HandleObject bufobj, const CallArgs& args) { MOZ_ASSERT(args.isConstructing()); assertSameCompartment(cx, bufobj); uint32_t byteOffset, byteLength; if (!getAndCheckConstructorArgs(cx, bufobj, args, &byteOffset, &byteLength)) return false; RootedObject proto(cx); RootedObject newTarget(cx, &args.newTarget().toObject()); if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return false; Rooted buffer(cx, &AsArrayBuffer(bufobj)); JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto); if (!obj) return false; args.rval().setObject(*obj); return true; } // Create a DataView object in another compartment. // // ES6 supports creating a DataView in global A (using global A's DataView // constructor) backed by an ArrayBuffer created in global B. // // Our DataViewObject implementation doesn't support a DataView in // compartment A backed by an ArrayBuffer in compartment B. So in this case, // we create the DataView in B (!) and return a cross-compartment wrapper. // // Extra twist: the spec says the new DataView's [[Prototype]] must be // A's DataView.prototype. So even though we're creating the DataView in B, // its [[Prototype]] must be (a cross-compartment wrapper for) the // DataView.prototype in A. // // As if this were not confusing enough, the way we actually do this is also // tricky. We call compartment A's createDataViewForThis method, passing it // bufobj as `this`. That calls ArrayBufferObject::createDataViewForThis(), // which uses CallNonGenericMethod to switch to compartment B so that // the new DataView is created there. bool DataViewObject::constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args) { MOZ_ASSERT(args.isConstructing()); MOZ_ASSERT(bufobj->is()); JSObject* unwrapped = CheckedUnwrap(bufobj); if (!unwrapped) { JS_ReportErrorASCII(cx, "Permission denied to access object"); return false; } // NB: This entails the IsArrayBuffer check uint32_t byteOffset, byteLength; if (!getAndCheckConstructorArgs(cx, unwrapped, args, &byteOffset, &byteLength)) return false; // Make sure to get the [[Prototype]] for the created view from this // compartment. RootedObject proto(cx); RootedObject newTarget(cx, &args.newTarget().toObject()); if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return false; Rooted global(cx, cx->compartment()->maybeGlobal()); if (!proto) { proto = GlobalObject::getOrCreateDataViewPrototype(cx, global); if (!proto) return false; } FixedInvokeArgs<3> args2(cx); args2[0].set(PrivateUint32Value(byteOffset)); args2[1].set(PrivateUint32Value(byteLength)); args2[2].setObject(*proto); RootedValue fval(cx, global->createDataViewForThis()); RootedValue thisv(cx, ObjectValue(*bufobj)); return js::Call(cx, fval, thisv, args2, args.rval()); } bool DataViewObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (!ThrowIfNotConstructing(cx, args, "DataView")) return false; RootedObject bufobj(cx); if (!GetFirstArgumentAsObject(cx, args, "DataView constructor", &bufobj)) return false; if (bufobj->is()) return constructWrapped(cx, bufobj, args); return constructSameCompartment(cx, bufobj, args); } template /* static */ uint8_t* DataViewObject::getDataPointer(JSContext* cx, Handle obj, uint64_t offset) { const size_t TypeSize = sizeof(NativeType); if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); return nullptr; } MOZ_ASSERT(offset < UINT32_MAX); return static_cast(obj->dataPointer()) + uint32_t(offset); } static inline bool needToSwapBytes(bool littleEndian) { #if MOZ_LITTLE_ENDIAN return !littleEndian; #else return littleEndian; #endif } static inline uint8_t swapBytes(uint8_t x) { return x; } static inline uint16_t swapBytes(uint16_t x) { return ((x & 0xff) << 8) | (x >> 8); } static inline uint32_t swapBytes(uint32_t x) { return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24); } static inline uint64_t swapBytes(uint64_t x) { uint32_t a = x & UINT32_MAX; uint32_t b = x >> 32; return (uint64_t(swapBytes(a)) << 32) | swapBytes(b); } template struct DataToRepType { typedef DataType result; }; template <> struct DataToRepType { typedef uint8_t result; }; template <> struct DataToRepType { typedef uint8_t result; }; template <> struct DataToRepType { typedef uint16_t result; }; template <> struct DataToRepType { typedef uint16_t result; }; template <> struct DataToRepType { typedef uint32_t result; }; template <> struct DataToRepType { typedef uint32_t result; }; template <> struct DataToRepType { typedef uint64_t result; }; template <> struct DataToRepType { typedef uint64_t result; }; template <> struct DataToRepType { typedef uint32_t result; }; template <> struct DataToRepType { typedef uint64_t result; }; template struct DataViewIO { typedef typename DataToRepType::result ReadWriteType; static void fromBuffer(DataType* dest, const uint8_t* unalignedBuffer, bool wantSwap) { MOZ_ASSERT((reinterpret_cast(dest) & (Min(MOZ_ALIGNOF(void*), sizeof(DataType)) - 1)) == 0); memcpy((void*) dest, unalignedBuffer, sizeof(ReadWriteType)); if (wantSwap) { ReadWriteType* rwDest = reinterpret_cast(dest); *rwDest = swapBytes(*rwDest); } } static void toBuffer(uint8_t* unalignedBuffer, const DataType* src, bool wantSwap) { MOZ_ASSERT((reinterpret_cast(src) & (Min(MOZ_ALIGNOF(void*), sizeof(DataType)) - 1)) == 0); ReadWriteType temp = *reinterpret_cast(src); if (wantSwap) temp = swapBytes(temp); memcpy(unalignedBuffer, (void*) &temp, sizeof(ReadWriteType)); } }; template /* static */ bool DataViewObject::read(JSContext* cx, Handle obj, const CallArgs& args, NativeType* val, const char* method) { // Steps 1-2. done by the caller // Step 3. unnecessary assert // Step 4. uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; // Step 5. bool isLittleEndian = args.length() >= 2 && ToBoolean(args[1]); // Steps 6-7. if (obj->arrayBuffer().isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return false; } // Steps 8-12. uint8_t* data = DataViewObject::getDataPointer(cx, obj, getIndex); if (!data) return false; // Step 13. DataViewIO::fromBuffer(val, data, needToSwapBytes(isLittleEndian)); return true; } template static inline bool WebIDLCast(JSContext* cx, HandleValue value, NativeType* out) { int32_t temp; if (!ToInt32(cx, value, &temp)) return false; // Technically, the behavior of assigning an out of range value to a signed // variable is undefined. In practice, compilers seem to do what we want // without issuing any warnings. *out = static_cast(temp); return true; } template <> inline bool WebIDLCast(JSContext* cx, HandleValue value, int64_t* out) { RootedBigInt bi(cx, ToBigInt(cx, value)); if (!bi) { return false; } *out = BigInt::toInt64(bi); return true; } template <> inline bool WebIDLCast(JSContext* cx, HandleValue value, uint64_t* out) { RootedBigInt bi(cx, ToBigInt(cx, value)); if (!bi) { return false; } *out = BigInt::toUint64(bi); return true; } template <> inline bool WebIDLCast(JSContext* cx, HandleValue value, float* out) { double temp; if (!ToNumber(cx, value, &temp)) return false; *out = static_cast(temp); return true; } template <> inline bool WebIDLCast(JSContext* cx, HandleValue value, double* out) { return ToNumber(cx, value, out); } // https://tc39.github.io/ecma262/#sec-setviewvalue // SetViewValue ( view, requestIndex, isLittleEndian, type, value ) template /* static */ bool DataViewObject::write(JSContext* cx, Handle obj, const CallArgs& args, const char* method) { // Steps 1-2. done by the caller // Step 3. unnecessary assert // Step 4. uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; // Step 5. Extended by the BigInt proposal to call either ToBigInt or ToNumber NativeType value; if (!WebIDLCast(cx, args.get(1), &value)) return false; #ifdef JS_MORE_DETERMINISTIC // See the comment in ElementSpecific::doubleToNative. if (TypeIsFloatingPoint()) value = JS::CanonicalizeNaN(value); #endif // Step 6. bool isLittleEndian = args.length() >= 3 && ToBoolean(args[2]); // Steps 7-8. if (obj->arrayBuffer().isDetached()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); return false; } // Steps 9-13. uint8_t* data = DataViewObject::getDataPointer(cx, obj, getIndex); if (!data) return false; // Step 14. DataViewIO::toBuffer(data, &value, needToSwapBytes(isLittleEndian)); return true; } bool DataViewObject::getInt8Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); int8_t val; if (!read(cx, thisView, args, &val, "getInt8")) return false; args.rval().setInt32(val); return true; } bool DataViewObject::fun_getInt8(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::getUint8Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); uint8_t val; if (!read(cx, thisView, args, &val, "getUint8")) return false; args.rval().setInt32(val); return true; } bool DataViewObject::fun_getUint8(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::getInt16Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); int16_t val; if (!read(cx, thisView, args, &val, "getInt16")) return false; args.rval().setInt32(val); return true; } bool DataViewObject::fun_getInt16(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::getUint16Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); uint16_t val; if (!read(cx, thisView, args, &val, "getUint16")) return false; args.rval().setInt32(val); return true; } bool DataViewObject::fun_getUint16(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::getInt32Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); int32_t val; if (!read(cx, thisView, args, &val, "getInt32")) return false; args.rval().setInt32(val); return true; } bool DataViewObject::fun_getInt32(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::getUint32Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); uint32_t val; if (!read(cx, thisView, args, &val, "getUint32")) return false; args.rval().setNumber(val); return true; } bool DataViewObject::fun_getUint32(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // BigInt proposal 7.26 // DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] ) bool DataViewObject::getBigInt64Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); int64_t val; if (!read(cx, thisView, args, &val, "getBigInt64")) { return false; } BigInt* bi = BigInt::createFromInt64(cx, val); if (!bi) { return false; } args.rval().setBigInt(bi); return true; } bool DataViewObject::fun_getBigInt64(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // BigInt proposal 7.27 // DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] ) bool DataViewObject::getBigUint64Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); int64_t val; if (!read(cx, thisView, args, &val, "getBigUint64")) { return false; } BigInt* bi = BigInt::createFromUint64(cx, val); if (!bi) { return false; } args.rval().setBigInt(bi); return true; } bool DataViewObject::fun_getBigUint64(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::getFloat32Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); float val; if (!read(cx, thisView, args, &val, "getFloat32")) return false; args.rval().setDouble(CanonicalizeNaN(val)); return true; } bool DataViewObject::fun_getFloat32(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::getFloat64Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); double val; if (!read(cx, thisView, args, &val, "getFloat64")) return false; args.rval().setDouble(CanonicalizeNaN(val)); return true; } bool DataViewObject::fun_getFloat64(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setInt8Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setInt8")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setInt8(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setUint8Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setUint8")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setUint8(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setInt16Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setInt16")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setInt16(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setUint16Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setUint16")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setUint16(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setInt32Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setInt32")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setInt32(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setUint32Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setUint32")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setUint32(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // BigInt proposal 7.28 // DataView.prototype.setBigInt64 ( byteOffset, value [ , littleEndian ] ) bool DataViewObject::setBigInt64Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setBigInt64")) { return false; } args.rval().setUndefined(); return true; } bool DataViewObject::fun_setBigInt64(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } // BigInt proposal 7.29 // DataView.prototype.setBigUint64 ( byteOffset, value [ , littleEndian ] ) bool DataViewObject::setBigUint64Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setBigUint64")) { return false; } args.rval().setUndefined(); return true; } bool DataViewObject::fun_setBigUint64(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setFloat32Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setFloat32")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setFloat32(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } bool DataViewObject::setFloat64Impl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); Rooted thisView(cx, &args.thisv().toObject().as()); if (!write(cx, thisView, args, "setFloat64")) return false; args.rval().setUndefined(); return true; } bool DataViewObject::fun_setFloat64(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } namespace js { template <> bool TypedArrayObject::getElement(ExclusiveContext* cx, uint32_t index, MutableHandleValue val) { switch (type()) { #define GET_ELEMENT(T, N) \ case Scalar::N: \ return N##Array::getElement(cx, this, index, val); JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT) #undef GET_ELEMENT case Scalar::Int64: case Scalar::MaxTypedArrayViewType: break; } MOZ_CRASH("Unknown TypedArray type"); } template <> bool TypedArrayObject::getElement( ExclusiveContext* cx, uint32_t index, typename MaybeRooted::MutableHandleType vp) { return getElementPure(index, vp.address()); } } // namespace js bool TypedArrayObject::getElementPure(uint32_t index, Value* vp) { switch (type()) { #define GET_ELEMENT_PURE(T, N) \ case Scalar::N: \ return N##Array::getElementPure(this, index, vp); JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE) #undef GET_ELEMENT case Scalar::Int64: case Scalar::MaxTypedArrayViewType: break; } MOZ_CRASH("Unknown TypedArray type"); } /* static */ bool TypedArrayObject::getElements(JSContext* cx, Handle tarray, Value* vp) { uint32_t length = tarray->length(); MOZ_ASSERT_IF(length > 0, !tarray->hasDetachedBuffer()); switch (tarray->type()) { #define GET_ELEMENTS(T, N) \ case Scalar::N: \ for (uint32_t i = 0; i < length; ++i, ++vp) { \ if (!N##Array::getElement(cx, tarray, i, \ MutableHandleValue::fromMarkedLocation(vp))) { \ return false; \ } \ } \ return true; JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS) #undef GET_ELEMENTS default: MOZ_CRASH("Unknown TypedArray type"); case Scalar::MaxTypedArrayViewType: case Scalar::Int64: break; } MOZ_CRASH("Unknown TypedArray type"); } /*** *** JS impl ***/ /* * TypedArrayObject boilerplate */ #define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Name,NativeType) \ JS_FRIEND_API(JSObject*) JS_New ## Name ## Array(JSContext* cx, uint32_t nelements) \ { \ return TypedArrayObjectTemplate::fromLength(cx, nelements); \ } \ JS_FRIEND_API(JSObject*) JS_New ## Name ## ArrayFromArray(JSContext* cx, HandleObject other) \ { \ return TypedArrayObjectTemplate::fromArray(cx, other); \ } \ JS_FRIEND_API(JSObject*) JS_New ## Name ## ArrayWithBuffer(JSContext* cx, \ HandleObject arrayBuffer, uint32_t byteOffset, int32_t length) \ { \ return TypedArrayObjectTemplate::fromBuffer(cx, arrayBuffer, byteOffset, \ length); \ } \ JS_FRIEND_API(bool) JS_Is ## Name ## Array(JSObject* obj) \ { \ if (!(obj = CheckedUnwrap(obj))) \ return false; \ const Class* clasp = obj->getClass(); \ return clasp == TypedArrayObjectTemplate::instanceClass(); \ } \ JS_FRIEND_API(JSObject*) js::Unwrap ## Name ## Array(JSObject* obj) \ { \ obj = CheckedUnwrap(obj); \ if (!obj) \ return nullptr; \ const Class* clasp = obj->getClass(); \ if (clasp == TypedArrayObjectTemplate::instanceClass()) \ return obj; \ return nullptr; \ } \ const js::Class* const js::detail::Name ## ArrayClassPtr = \ &js::TypedArrayObject::classes[TypedArrayObjectTemplate::ArrayTypeID()]; IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Int8, int8_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint8, uint8_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint8Clamped, uint8_clamped) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Int16, int16_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint16, uint16_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Int32, int32_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint32, uint32_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Float32, float) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Float64, double) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(BigInt64, int64_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(BigUint64, uint64_t) #define IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Name, ExternalType, InternalType) \ JS_FRIEND_API(JSObject*) JS_GetObjectAs ## Name ## Array(JSObject* obj, \ uint32_t* length, \ bool* isShared, \ ExternalType** data) \ { \ if (!(obj = CheckedUnwrap(obj))) \ return nullptr; \ \ const Class* clasp = obj->getClass(); \ if (clasp != TypedArrayObjectTemplate::instanceClass()) \ return nullptr; \ \ TypedArrayObject* tarr = &obj->as(); \ *length = tarr->length(); \ *isShared = tarr->isSharedMemory(); \ *data = static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isShared flag*/)); \ \ return obj; \ } IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int8, int8_t, int8_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint8, uint8_t, uint8_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint8Clamped, uint8_t, uint8_clamped) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int16, int16_t, int16_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint16, uint16_t, uint16_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int32, int32_t, int32_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint32, uint32_t, uint32_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float32, float, float) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(BigInt64, int64_t, int64_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(BigUint64, uint32_t, uint64_t) static const ClassOps TypedArrayClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ TypedArrayObject::finalize, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ TypedArrayObject::trace, /* trace */ }; static const ClassExtension TypedArrayClassExtension = { nullptr, TypedArrayObject::objectMoved, }; #define IMPL_TYPED_ARRAY_PROPERTIES(_type) \ { \ JS_INT32_PS("BYTES_PER_ELEMENT", _type##Array::BYTES_PER_ELEMENT, \ JSPROP_READONLY | JSPROP_PERMANENT), \ JS_PS_END \ } static const JSPropertySpec static_prototype_properties[Scalar::MaxTypedArrayViewType][2] = { IMPL_TYPED_ARRAY_PROPERTIES(Int8), IMPL_TYPED_ARRAY_PROPERTIES(Uint8), IMPL_TYPED_ARRAY_PROPERTIES(Int16), IMPL_TYPED_ARRAY_PROPERTIES(Uint16), IMPL_TYPED_ARRAY_PROPERTIES(Int32), IMPL_TYPED_ARRAY_PROPERTIES(Uint32), IMPL_TYPED_ARRAY_PROPERTIES(Float32), IMPL_TYPED_ARRAY_PROPERTIES(Float64), IMPL_TYPED_ARRAY_PROPERTIES(Uint8Clamped), IMPL_TYPED_ARRAY_PROPERTIES(BigInt64), IMPL_TYPED_ARRAY_PROPERTIES(BigUint64) }; #define IMPL_TYPED_ARRAY_CLASS_SPEC(_type) \ { \ _type##Array::createConstructor, \ _type##Array::createPrototype, \ nullptr, \ static_prototype_properties[Scalar::Type::_type], \ nullptr, \ static_prototype_properties[Scalar::Type::_type], \ nullptr, \ JSProto_TypedArray \ } static const ClassSpec TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_CLASS_SPEC(Int8), IMPL_TYPED_ARRAY_CLASS_SPEC(Uint8), IMPL_TYPED_ARRAY_CLASS_SPEC(Int16), IMPL_TYPED_ARRAY_CLASS_SPEC(Uint16), IMPL_TYPED_ARRAY_CLASS_SPEC(Int32), IMPL_TYPED_ARRAY_CLASS_SPEC(Uint32), IMPL_TYPED_ARRAY_CLASS_SPEC(Float32), IMPL_TYPED_ARRAY_CLASS_SPEC(Float64), IMPL_TYPED_ARRAY_CLASS_SPEC(Uint8Clamped), IMPL_TYPED_ARRAY_CLASS_SPEC(BigInt64), IMPL_TYPED_ARRAY_CLASS_SPEC(BigUint64) }; #define IMPL_TYPED_ARRAY_CLASS(_type) \ { \ #_type "Array", \ JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \ JSCLASS_HAS_PRIVATE | \ JSCLASS_HAS_CACHED_PROTO(JSProto_##_type##Array) | \ JSCLASS_DELAY_METADATA_BUILDER | \ JSCLASS_SKIP_NURSERY_FINALIZE | \ JSCLASS_BACKGROUND_FINALIZE, \ &TypedArrayClassOps, \ &TypedArrayObjectClassSpecs[Scalar::Type::_type], \ &TypedArrayClassExtension \ } const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_CLASS(Int8), IMPL_TYPED_ARRAY_CLASS(Uint8), IMPL_TYPED_ARRAY_CLASS(Int16), IMPL_TYPED_ARRAY_CLASS(Uint16), IMPL_TYPED_ARRAY_CLASS(Int32), IMPL_TYPED_ARRAY_CLASS(Uint32), IMPL_TYPED_ARRAY_CLASS(Float32), IMPL_TYPED_ARRAY_CLASS(Float64), IMPL_TYPED_ARRAY_CLASS(Uint8Clamped), IMPL_TYPED_ARRAY_CLASS(BigInt64), IMPL_TYPED_ARRAY_CLASS(BigUint64) }; #define IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(_type) \ { \ DELEGATED_CLASSSPEC(TypedArrayObject::classes[Scalar::Type::_type].spec), \ nullptr, \ nullptr, \ nullptr, \ nullptr, \ nullptr, \ nullptr, \ JSProto_TypedArray | ClassSpec::IsDelegated \ } static const ClassSpec TypedArrayObjectProtoClassSpecs[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Int8), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint8), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Int16), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint16), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Int32), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint32), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Float32), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Float64), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint8Clamped), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(BigInt64), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(BigUint64) }; // 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. #define IMPL_TYPED_ARRAY_PROTO_CLASS(_type) \ { \ /* * 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. */ \ #_type "ArrayPrototype", \ JSCLASS_HAS_CACHED_PROTO(JSProto_##_type##Array), \ JS_NULL_CLASS_OPS, \ &TypedArrayObjectProtoClassSpecs[Scalar::Type::_type] \ } const Class TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_PROTO_CLASS(Int8), IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8), IMPL_TYPED_ARRAY_PROTO_CLASS(Int16), IMPL_TYPED_ARRAY_PROTO_CLASS(Uint16), IMPL_TYPED_ARRAY_PROTO_CLASS(Int32), IMPL_TYPED_ARRAY_PROTO_CLASS(Uint32), IMPL_TYPED_ARRAY_PROTO_CLASS(Float32), IMPL_TYPED_ARRAY_PROTO_CLASS(Float64), IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Clamped), IMPL_TYPED_ARRAY_PROTO_CLASS(BigInt64), IMPL_TYPED_ARRAY_PROTO_CLASS(BigUint64) }; /* static */ bool TypedArrayObject::isOriginalLengthGetter(Native native) { return native == TypedArray_lengthGetter; } const Class DataViewObject::protoClass = { "DataViewPrototype", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_DataView) }; static const ClassOps DataViewObjectClassOps = { nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ ArrayBufferViewObject::trace }; const Class DataViewObject::class_ = { "DataView", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_DataView), &DataViewObjectClassOps }; const JSFunctionSpec DataViewObject::jsfuncs[] = { JS_FN("getInt8", DataViewObject::fun_getInt8, 1,0), JS_FN("getUint8", DataViewObject::fun_getUint8, 1,0), JS_FN("getInt16", DataViewObject::fun_getInt16, 1,0), JS_FN("getUint16", DataViewObject::fun_getUint16, 1,0), JS_FN("getInt32", DataViewObject::fun_getInt32, 1,0), JS_FN("getUint32", DataViewObject::fun_getUint32, 1,0), JS_FN("getFloat32", DataViewObject::fun_getFloat32, 1,0), JS_FN("getFloat64", DataViewObject::fun_getFloat64, 1,0), JS_FN("setInt8", DataViewObject::fun_setInt8, 2,0), JS_FN("setUint8", DataViewObject::fun_setUint8, 2,0), JS_FN("setInt16", DataViewObject::fun_setInt16, 2,0), JS_FN("setUint16", DataViewObject::fun_setUint16, 2,0), JS_FN("setInt32", DataViewObject::fun_setInt32, 2,0), JS_FN("setUint32", DataViewObject::fun_setUint32, 2,0), JS_FN("setFloat32", DataViewObject::fun_setFloat32, 2,0), JS_FN("setFloat64", DataViewObject::fun_setFloat64, 2,0), JS_FS_END }; const JSFunctionSpec DataViewObject::bigIntMethods[] = { JS_FN("getBigInt64", DataViewObject::fun_getBigInt64, 1, 0), JS_FN("getBigUint64", DataViewObject::fun_getBigUint64, 1, 0), JS_FN("setBigInt64", DataViewObject::fun_setBigInt64, 2, 0), JS_FN("setBigUint64", DataViewObject::fun_setBigUint64, 2, 0), JS_FS_END}; template bool DataViewObject::getterImpl(JSContext* cx, const CallArgs& args) { args.rval().set(ValueGetter(&args.thisv().toObject().as())); return true; } template bool DataViewObject::getter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod >(cx, args); } template bool DataViewObject::defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto) { RootedId id(cx, NameToId(name)); RootedAtom atom(cx, IdToFunctionName(cx, id, FunctionPrefixKind::Get)); if (!atom) return false; unsigned attrs = JSPROP_SHARED | JSPROP_GETTER; Rooted global(cx, cx->compartment()->maybeGlobal()); JSObject* getter = NewNativeFunction(cx, DataViewObject::getter, 0, atom); if (!getter) return false; return NativeDefineProperty(cx, proto, id, UndefinedHandleValue, JS_DATA_TO_FUNC_PTR(GetterOp, getter), nullptr, attrs); } /* static */ bool DataViewObject::initClass(JSContext* cx) { Rooted global(cx, cx->compartment()->maybeGlobal()); if (global->isStandardClassResolved(JSProto_DataView)) return true; RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &DataViewObject::protoClass)); if (!proto) return false; RootedFunction ctor(cx, GlobalObject::createConstructor(cx, DataViewObject::class_constructor, cx->names().DataView, 3)); if (!ctor) return false; if (!LinkConstructorAndPrototype(cx, ctor, proto)) return false; if (!defineGetter(cx, cx->names().buffer, proto)) return false; if (!defineGetter(cx, cx->names().byteLength, proto)) return false; if (!defineGetter(cx, cx->names().byteOffset, proto)) return false; if (!JS_DefineFunctions(cx, proto, DataViewObject::jsfuncs)) return false; if (!JS_DefineFunctions(cx, proto, DataViewObject::bigIntMethods)) return false; if (!DefineToStringTag(cx, proto, cx->names().DataView)) return false; /* * Create a helper function to implement the craziness of * |new DataView(new otherWindow.ArrayBuffer())|, and install it in the * global for use by the DataViewObject constructor. */ RootedFunction fun(cx, NewNativeFunction(cx, ArrayBufferObject::createDataViewForThis, 0, nullptr)); if (!fun) return false; if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_DataView, ctor, proto)) return false; global->setCreateDataViewForThis(fun); return true; } void DataViewObject::notifyBufferDetached(void* newData) { setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(0)); setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(0)); setPrivate(newData); } JSObject* js::InitDataViewClass(JSContext* cx, HandleObject obj) { if (!DataViewObject::initClass(cx)) return nullptr; return &cx->global()->getPrototype(JSProto_DataView).toObject(); } bool js::IsTypedArrayConstructor(HandleValue v, uint32_t type) { switch (type) { case Scalar::Int8: return IsNativeFunction(v, Int8Array::class_constructor); case Scalar::Uint8: return IsNativeFunction(v, Uint8Array::class_constructor); case Scalar::Int16: return IsNativeFunction(v, Int16Array::class_constructor); case Scalar::Uint16: return IsNativeFunction(v, Uint16Array::class_constructor); case Scalar::Int32: return IsNativeFunction(v, Int32Array::class_constructor); case Scalar::Uint32: return IsNativeFunction(v, Uint32Array::class_constructor); case Scalar::Float32: return IsNativeFunction(v, Float32Array::class_constructor); case Scalar::Float64: return IsNativeFunction(v, Float64Array::class_constructor); case Scalar::Uint8Clamped: return IsNativeFunction(v, Uint8ClampedArray::class_constructor); case Scalar::MaxTypedArrayViewType: break; } MOZ_CRASH("unexpected typed array type"); } template bool js::StringIsTypedArrayIndex(const CharT* s, size_t length, uint64_t* indexp) { const CharT* end = s + length; if (s == end) return false; bool negative = false; if (*s == '-') { negative = true; if (++s == end) return false; } if (!JS7_ISDEC(*s)) return false; uint64_t index = 0; uint32_t digit = JS7_UNDEC(*s++); /* Don't allow leading zeros. */ if (digit == 0 && s != end) return false; index = digit; for (; s < end; s++) { if (!JS7_ISDEC(*s)) return false; digit = JS7_UNDEC(*s); /* Watch for overflows. */ if ((UINT64_MAX - digit) / 10 < index) index = UINT64_MAX; else index = 10 * index + digit; } if (negative) *indexp = UINT64_MAX; else *indexp = index; return true; } template bool js::StringIsTypedArrayIndex(const char16_t* s, size_t length, uint64_t* indexp); template bool js::StringIsTypedArrayIndex(const Latin1Char* s, size_t length, uint64_t* indexp); bool js::SetTypedArrayElement(JSContext* cx, Handle obj, uint64_t index, HandleValue v, ObjectOpResult& result) { TypedArrayObject* tobj = &obj->as(); switch (tobj->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: break; } MOZ_CRASH("Unsupported TypedArray type"); } /* ES6 draft rev 34 (2015 Feb 20) 9.4.5.3 [[DefineOwnProperty]] step 3.c. */ bool js::DefineTypedArrayElement(JSContext* cx, HandleObject obj, uint64_t index, Handle desc, ObjectOpResult& result) { MOZ_ASSERT(obj->is()); // These are all substeps of 3.b. // Steps i-iii are handled by the caller. // Steps iv-v. // We (wrongly) ignore out of range defines with a value. uint32_t length = obj->as().length(); if (index >= length) return result.succeed(); // Step vi. if (desc.isAccessorDescriptor()) return result.fail(JSMSG_CANT_REDEFINE_PROP); // Step vii. if (desc.hasConfigurable() && desc.configurable()) return result.fail(JSMSG_CANT_REDEFINE_PROP); // Step viii. if (desc.hasEnumerable() && !desc.enumerable()) return result.fail(JSMSG_CANT_REDEFINE_PROP); // Step ix. if (desc.hasWritable() && !desc.writable()) return result.fail(JSMSG_CANT_REDEFINE_PROP); // Step x. if (desc.hasValue()) { TypedArrayObject* tobj = &obj->as(); switch (tobj->type()) { #define DEFINE_TYPED_ARRAY_ELEMENT(T, N) \ case Scalar::N: \ return TypedArrayObjectTemplate::defineElement(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: break; } } // Step xii. return result.succeed(); } /* JS Friend API */ JS_FRIEND_API(bool) JS_IsTypedArrayObject(JSObject* obj) { obj = CheckedUnwrap(obj); return obj ? obj->is() : false; } JS_FRIEND_API(uint32_t) JS_GetTypedArrayLength(JSObject* obj) { obj = CheckedUnwrap(obj); if (!obj) return 0; return obj->as().length(); } JS_FRIEND_API(uint32_t) JS_GetTypedArrayByteOffset(JSObject* obj) { obj = CheckedUnwrap(obj); if (!obj) return 0; return obj->as().byteOffset(); } JS_FRIEND_API(uint32_t) JS_GetTypedArrayByteLength(JSObject* obj) { obj = CheckedUnwrap(obj); if (!obj) return 0; return obj->as().byteLength(); } JS_FRIEND_API(bool) JS_GetTypedArraySharedness(JSObject* obj) { obj = CheckedUnwrap(obj); if (!obj) return false; return obj->as().isSharedMemory(); } JS_FRIEND_API(js::Scalar::Type) JS_GetArrayBufferViewType(JSObject* obj) { obj = CheckedUnwrap(obj); if (!obj) return Scalar::MaxTypedArrayViewType; if (obj->is()) return obj->as().type(); if (obj->is()) return Scalar::MaxTypedArrayViewType; MOZ_CRASH("invalid ArrayBufferView type"); } JS_FRIEND_API(int8_t*) JS_GetInt8ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int8); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isShared*/)); } JS_FRIEND_API(uint8_t*) JS_GetUint8ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint8); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(uint8_t*) JS_GetUint8ClampedArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint8Clamped); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(int16_t*) JS_GetInt16ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int16); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(uint16_t*) JS_GetUint16ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint16); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(int32_t*) JS_GetInt32ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int32); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(uint32_t*) JS_GetUint32ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint32); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(float*) JS_GetFloat32ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Float32); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(double*) JS_GetFloat64ArrayData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; TypedArrayObject* tarr = &obj->as(); MOZ_ASSERT((int32_t) tarr->type() == Scalar::Float64); *isSharedMemory = tarr->isSharedMemory(); return static_cast(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); } JS_FRIEND_API(bool) JS_IsDataViewObject(JSObject* obj) { obj = CheckedUnwrap(obj); return obj ? obj->is() : false; } JS_FRIEND_API(uint32_t) JS_GetDataViewByteOffset(JSObject* obj) { obj = CheckedUnwrap(obj); if (!obj) return 0; return obj->as().byteOffset(); } JS_FRIEND_API(void*) JS_GetDataViewData(JSObject* obj, const JS::AutoCheckCannotGC&) { obj = CheckedUnwrap(obj); if (!obj) return nullptr; return obj->as().dataPointer(); } JS_FRIEND_API(uint32_t) JS_GetDataViewByteLength(JSObject* obj) { obj = CheckedUnwrap(obj); if (!obj) return 0; return obj->as().byteLength(); } JS_FRIEND_API(JSObject*) JS_NewDataView(JSContext* cx, HandleObject arrayBuffer, uint32_t byteOffset, int32_t byteLength) { RootedObject constructor(cx); JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(&DataViewObject::class_); if (!GetBuiltinConstructor(cx, key, &constructor)) return nullptr; FixedConstructArgs<3> cargs(cx); cargs[0].setObject(*arrayBuffer); cargs[1].setNumber(byteOffset); cargs[2].setInt32(byteLength); RootedValue fun(cx, ObjectValue(*constructor)); RootedObject obj(cx); if (!Construct(cx, fun, cargs, fun, &obj)) return nullptr; return obj; }