| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * 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/FloatingPoint.h" |
| #include "mozilla/PodOperations.h" |
| |
| #include <string.h> |
| #if defined(STARBOARD) |
| #elif !defined(XP_WIN) |
| # include <sys/mman.h> |
| #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 "js/Conversions.h" |
| #include "vm/ArrayBufferObject.h" |
| #include "vm/GlobalObject.h" |
| #include "vm/Interpreter.h" |
| #include "vm/TypedArrayCommon.h" |
| #include "vm/WrapperObject.h" |
| |
| #include "jsatominlines.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::IsNaN; |
| using mozilla::NegativeInfinity; |
| using mozilla::PodCopy; |
| using mozilla::PositiveInfinity; |
| using JS::CanonicalizeNaN; |
| using JS::GenericNaN; |
| 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. |
| */ |
| |
| /* static */ int |
| TypedArrayObject::lengthOffset() |
| { |
| return NativeObject::getFixedSlotOffset(LENGTH_SLOT); |
| } |
| |
| /* static */ int |
| TypedArrayObject::dataOffset() |
| { |
| return NativeObject::getPrivateDataOffset(DATA_SLOT); |
| } |
| |
| void |
| TypedArrayObject::neuter(void* newData) |
| { |
| MOZ_ASSERT(!isSharedMemory()); |
| setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(0)); |
| setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(0)); |
| setPrivate(newData); |
| } |
| |
| /* static */ bool |
| TypedArrayObject::is(HandleValue v) |
| { |
| return v.isObject() && v.toObject().is<TypedArrayObject>(); |
| } |
| |
| /* static */ bool |
| TypedArrayObject::ensureHasBuffer(JSContext* cx, Handle<TypedArrayObject*> tarray) |
| { |
| if (tarray->hasBuffer()) |
| return true; |
| |
| Rooted<ArrayBufferObject*> 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()); |
| 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; |
| } |
| |
| /* static */ void |
| TypedArrayObject::trace(JSTracer* trc, JSObject* objArg) |
| { |
| // Handle all tracing required when the object has a buffer. |
| ArrayBufferViewObject::trace(trc, objArg); |
| |
| // If the typed array doesn't have a buffer, it must have a lazy buffer and |
| // its data pointer must point to its inline data. Watch for cases where |
| // the GC moved this object and fix up its data pointer. |
| TypedArrayObject& obj = objArg->as<TypedArrayObject>(); |
| if (!obj.hasBuffer() && obj.getPrivate() != obj.fixedData(FIXED_DATA_START)) { |
| void* oldData = obj.getPrivate(); |
| void* newData = obj.fixedData(FIXED_DATA_START); |
| |
| obj.setPrivateUnbarriered(newData); |
| |
| // If this is a minor GC, set a forwarding pointer for the array data. |
| // This can always be done inline, as AllocKindForLazyBuffer ensures |
| // there is at least a pointer's worth of inline data. |
| trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData, true); |
| } |
| } |
| |
| /* 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<typename ElementType> |
| 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; |
| } |
| |
| template<typename NativeType> |
| class TypedArrayObjectTemplate : public TypedArrayObject |
| { |
| friend class TypedArrayObject; |
| |
| public: |
| typedef NativeType ElementType; |
| |
| static MOZ_CONSTEXPR Scalar::Type ArrayTypeID() { return TypeIDOfType<NativeType>::id; } |
| static bool ArrayTypeIsUnsigned() { return TypeIsUnsigned<NativeType>(); } |
| static bool ArrayTypeIsFloatingPoint() { return TypeIsFloatingPoint<NativeType>(); } |
| |
| static const size_t BYTES_PER_ELEMENT = sizeof(NativeType); |
| |
| static JSObject* |
| createPrototype(JSContext* cx, JSProtoKey key) |
| { |
| Handle<GlobalObject*> global = cx->global(); |
| RootedObject typedArrayProto(cx, GlobalObject::getOrCreateTypedArrayPrototype(cx, global)); |
| if (!typedArrayProto) |
| return nullptr; |
| |
| const Class* clasp = TypedArrayObject::protoClassForType(ArrayTypeID()); |
| return global->createBlankPrototypeInheriting(cx, clasp, typedArrayProto); |
| } |
| |
| static JSObject* |
| createConstructor(JSContext* cx, JSProtoKey key) |
| { |
| Handle<GlobalObject*> global = cx->global(); |
| RootedFunction ctorProto(cx, GlobalObject::getOrCreateTypedArrayConstructor(cx, global)); |
| if (!ctorProto) |
| return nullptr; |
| |
| return NewFunctionWithProto(cx, class_constructor, 3, |
| JSFunction::NATIVE_CTOR, nullptr, |
| ClassName(key, cx), |
| ctorProto, gc::AllocKind::FUNCTION, |
| SingletonObject); |
| } |
| |
| static bool |
| finishClassInit(JSContext* cx, HandleObject ctor, HandleObject proto) |
| { |
| RootedValue bytesValue(cx, Int32Value(BYTES_PER_ELEMENT)); |
| if (!DefineProperty(cx, ctor, cx->names().BYTES_PER_ELEMENT, bytesValue, |
| nullptr, nullptr, JSPROP_PERMANENT | JSPROP_READONLY) || |
| !DefineProperty(cx, proto, cx->names().BYTES_PER_ELEMENT, bytesValue, |
| nullptr, nullptr, JSPROP_PERMANENT | JSPROP_READONLY)) |
| { |
| return false; |
| } |
| |
| RootedFunction fun(cx); |
| fun = NewNativeFunction(cx, ArrayBufferObject::createTypedArrayFromBuffer<NativeType>, |
| 0, nullptr); |
| if (!fun) |
| return false; |
| |
| cx->global()->setCreateArrayFromBuffer<NativeType>(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 void |
| setIndexValue(TypedArrayObject& tarray, uint32_t index, double d) |
| { |
| // If the array is an integer array, we only handle up to |
| // 32-bit ints from this point on. if we want to handle |
| // 64-bit ints, we'll need some changes. |
| |
| // Assign based on characteristics of the destination type |
| if (ArrayTypeIsFloatingPoint()) { |
| setIndex(tarray, index, NativeType(d)); |
| } else if (ArrayTypeIsUnsigned()) { |
| MOZ_ASSERT(sizeof(NativeType) <= 4); |
| uint32_t n = ToUint32(d); |
| setIndex(tarray, index, NativeType(n)); |
| } else if (ArrayTypeID() == Scalar::Uint8Clamped) { |
| // The uint8_clamped type has a special rounding converter |
| // for doubles. |
| setIndex(tarray, index, NativeType(d)); |
| } else { |
| MOZ_ASSERT(sizeof(NativeType) <= 4); |
| int32_t n = ToInt32(d); |
| setIndex(tarray, index, NativeType(n)); |
| } |
| } |
| |
| static TypedArrayObject* |
| makeProtoInstance(JSContext* cx, HandleObject proto, AllocKind allocKind) |
| { |
| MOZ_ASSERT(proto); |
| |
| JSObject* obj = NewObjectWithClassProto(cx, instanceClass(), proto, allocKind); |
| return obj ? &obj->as<TypedArrayObject>() : 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<TypedArrayObject>(); |
| } |
| |
| 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<TypedArrayObject>(); |
| } |
| |
| static TypedArrayObject* |
| makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> 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<TypedArrayObject*> 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. |
| if (!IsInsideNursery(obj) && cx->runtime()->gc.nursery.isInside(buffer->dataPointerEither())) { |
| // 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 the nursery, and a zero-length buffer will erroneously be |
| // perceived as being inside the nursery; sidestep that. |
| if (isSharedMemory) { |
| MOZ_ASSERT(buffer->byteLength() == 0 && |
| cx->runtime()->gc.nursery.start() == |
| buffer->dataPointerEither().unwrapValue()); |
| } else { |
| cx->runtime()->gc.storeBuffer.putWholeCell(obj); |
| } |
| } |
| } else { |
| void* data = obj->fixedData(FIXED_DATA_START); |
| obj->initPrivate(data); |
| memset(data, 0, len * sizeof(NativeType)); |
| } |
| |
| 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()).isNeutered(), |
| 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 neutering. |
| if (buffer && buffer->is<ArrayBufferObject>()) { |
| if (!buffer->as<ArrayBufferObject>().addView(cx, obj)) |
| return nullptr; |
| } |
| |
| return obj; |
| } |
| |
| static TypedArrayObject* |
| makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, |
| uint32_t byteOffset, uint32_t len) |
| { |
| RootedObject proto(cx, nullptr); |
| return makeInstance(cx, buffer, byteOffset, len, proto); |
| } |
| |
| /* |
| * 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_ReportErrorNumber(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<ArrayBufferObjectMaybeShared>()) |
| return fromArray(cx, dataObj, newTarget); |
| |
| /* (ArrayBuffer, [byteOffset, [length]]) */ |
| RootedObject proto(cx); |
| if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) |
| return nullptr; |
| |
| int32_t byteOffset = 0; |
| int32_t length = -1; |
| |
| if (args.length() > 1) { |
| if (!ToInt32(cx, args[1], &byteOffset)) |
| return nullptr; |
| if (byteOffset < 0) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "1"); |
| return nullptr; |
| } |
| |
| if (args.length() > 2) { |
| if (!ToInt32(cx, args[2], &length)) |
| return nullptr; |
| if (length < 0) { |
| JS_ReportErrorNumber(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) |
| { |
| ESClassValue cls; |
| if (!GetBuiltinClass(cx, bufobj, &cls)) |
| return nullptr; |
| if (cls != ESClass_ArrayBuffer && cls != ESClass_SharedArrayBuffer) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return nullptr; |
| } |
| |
| MOZ_ASSERT(IsArrayBuffer(bufobj) || IsSharedArrayBuffer(bufobj) || bufobj->is<ProxyObject>()); |
| if (bufobj->is<ProxyObject>()) { |
| /* |
| * 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_ReportError(cx, "Permission denied to access object"); |
| return nullptr; |
| } |
| if (IsArrayBuffer(wrapped) || IsSharedArrayBuffer(wrapped)) { |
| /* |
| * 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; |
| } |
| |
| InvokeArgs args(cx); |
| if (!args.init(3)) |
| return nullptr; |
| |
| args.setCallee(cx->compartment()->maybeGlobal()->createArrayFromBuffer<NativeType>()); |
| args.setThis(ObjectValue(*bufobj)); |
| args[0].setNumber(byteOffset); |
| args[1].setInt32(lengthInt); |
| args[2].setObject(*protoRoot); |
| |
| if (!Invoke(cx, args)) |
| return nullptr; |
| return &args.rval().toObject(); |
| } |
| } |
| |
| if (!IsArrayBuffer(bufobj) && !IsSharedArrayBuffer(bufobj)) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return nullptr; // must be arrayBuffer |
| } |
| |
| Rooted<ArrayBufferObjectMaybeShared*> buffer(cx); |
| if (IsArrayBuffer(bufobj)) { |
| ArrayBufferObject& buf = AsArrayBuffer(bufobj); |
| if (buf.isNeutered()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); |
| return nullptr; |
| } |
| |
| buffer = static_cast<ArrayBufferObjectMaybeShared*>(&buf); |
| } else { |
| buffer = static_cast<ArrayBufferObjectMaybeShared*>(&AsSharedArrayBuffer(bufobj)); |
| } |
| |
| if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return nullptr; // invalid byteOffset |
| } |
| |
| uint32_t len; |
| if (lengthInt == -1) { |
| len = (buffer->byteLength() - byteOffset) / sizeof(NativeType); |
| if (len * sizeof(NativeType) != buffer->byteLength() - byteOffset) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_TYPED_ARRAY_BAD_ARGS); |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType) |
| } |
| |
| if (arrayByteLength + byteOffset > buffer->byteLength()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return nullptr; // byteOffset + len is too big for the arraybuffer |
| } |
| |
| return makeInstance(cx, buffer, byteOffset, len, proto); |
| } |
| |
| static bool |
| maybeCreateArrayBuffer(JSContext* cx, uint32_t nelements, MutableHandle<ArrayBufferObject*> buffer) |
| { |
| static_assert(INLINE_BUFFER_LIMIT % sizeof(NativeType) == 0, |
| "ArrayBuffer inline storage shouldn't waste any space"); |
| |
| if (nelements <= INLINE_BUFFER_LIMIT / sizeof(NativeType)) { |
| // The array's data can be inline, and the buffer created lazily. |
| return true; |
| } |
| |
| if (nelements >= INT32_MAX / sizeof(NativeType)) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_NEED_DIET, "size and count"); |
| return false; |
| } |
| |
| ArrayBufferObject* buf = ArrayBufferObject::create(cx, nelements * sizeof(NativeType)); |
| 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<ArrayBufferObject*> buffer(cx); |
| if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) |
| return nullptr; |
| |
| return makeInstance(cx, buffer, 0, nelements, proto); |
| } |
| |
| static JSObject* |
| fromArray(JSContext* cx, HandleObject other, HandleObject newTarget = nullptr); |
| |
| static const NativeType |
| getIndex(JSObject* obj, uint32_t index) |
| { |
| TypedArrayObject& tarray = obj->as<TypedArrayObject>(); |
| MOZ_ASSERT(index < tarray.length()); |
| return jit::AtomicOperations::loadSafeWhenRacy(tarray.viewDataEither().cast<NativeType*>() + index); |
| } |
| |
| static void |
| setIndex(TypedArrayObject& tarray, uint32_t index, NativeType val) |
| { |
| MOZ_ASSERT(index < tarray.length()); |
| jit::AtomicOperations::storeSafeWhenRacy(tarray.viewDataEither().cast<NativeType*>() + index, val); |
| } |
| |
| static Value getIndexValue(JSObject* tarray, uint32_t index); |
| }; |
| |
| typedef TypedArrayObjectTemplate<int8_t> Int8Array; |
| typedef TypedArrayObjectTemplate<uint8_t> Uint8Array; |
| typedef TypedArrayObjectTemplate<int16_t> Int16Array; |
| typedef TypedArrayObjectTemplate<uint16_t> Uint16Array; |
| typedef TypedArrayObjectTemplate<int32_t> Int32Array; |
| typedef TypedArrayObjectTemplate<uint32_t> Uint32Array; |
| typedef TypedArrayObjectTemplate<float> Float32Array; |
| typedef TypedArrayObjectTemplate<double> Float64Array; |
| typedef TypedArrayObjectTemplate<uint8_clamped> Uint8ClampedArray; |
| |
| } /* anonymous namespace */ |
| |
| template<typename T> |
| struct TypedArrayObject::OfType |
| { |
| typedef TypedArrayObjectTemplate<T> Type; |
| }; |
| |
| template<typename T> |
| /* static */ JSObject* |
| TypedArrayObjectTemplate<T>::fromArray(JSContext* cx, HandleObject other, |
| HandleObject newTarget /* = nullptr */) |
| { |
| // Allow nullptr newTarget for FriendAPI methods, which don't care about |
| // subclassing. |
| RootedObject proto(cx); |
| |
| uint32_t len; |
| if (IsAnyTypedArray(other)) { |
| if (!GetPrototypeForInstance(cx, newTarget, &proto)) |
| return nullptr; |
| |
| if (AnyTypedArrayIsDetached(other)) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); |
| return nullptr; |
| } |
| len = AnyTypedArrayLength(other); |
| } else { |
| if (!GetLengthProperty(cx, other, &len)) |
| return nullptr; |
| if (!GetPrototypeForInstance(cx, newTarget, &proto)) |
| return nullptr; |
| } |
| |
| Rooted<ArrayBufferObject*> buffer(cx); |
| if (!maybeCreateArrayBuffer(cx, len, &buffer)) |
| return nullptr; |
| |
| Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto)); |
| if (!obj || !TypedArrayMethods<TypedArrayObject>::setFromArrayLike(cx, obj, other, len)) |
| return nullptr; |
| return obj; |
| } |
| |
| bool |
| TypedArrayConstructor(JSContext* cx, unsigned argc, Value* vp) |
| { |
| JS_ReportError(cx, "%%TypedArray%% calling/constructing not implemented yet"); |
| return false; |
| } |
| |
| static bool |
| FinishTypedArrayInit(JSContext* cx, HandleObject ctor, HandleObject proto) |
| { |
| // Define `values` and `@@iterator` manually, because they are supposed to be the same object. |
| RootedId name(cx, NameToId(cx->names().values)); |
| RootedFunction fun(cx, GetSelfHostedFunction(cx, "TypedArrayValues", name, 0)); |
| if (!fun) |
| return false; |
| |
| RootedValue funValue(cx, ObjectValue(*fun)); |
| if (!DefineProperty(cx, proto, cx->names().values, funValue, nullptr, nullptr, 0)) |
| return false; |
| |
| RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator)); |
| if (!DefineProperty(cx, proto, iteratorId, funValue, nullptr, nullptr, 0)) |
| return false; |
| |
| 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<JSGetterOp>, 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<TypedArrayObject::lengthValue>(cx, argc, vp); \ |
| } |
| |
| static bool |
| TypedArray_byteLengthGetter(JSContext* cx, unsigned argc, Value* vp) |
| { |
| return TypedArrayObject::Getter<TypedArrayObject::byteLengthValue>(cx, argc, vp); |
| } |
| |
| static bool |
| TypedArray_byteOffsetGetter(JSContext* cx, unsigned argc, Value* vp) |
| { |
| return TypedArrayObject::Getter<TypedArrayObject::byteOffsetValue>(cx, argc, vp); |
| } |
| |
| bool |
| BufferGetterImpl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(TypedArrayObject::is(args.thisv())); |
| Rooted<TypedArrayObject*> tarray(cx, &args.thisv().toObject().as<TypedArrayObject>()); |
| if (!TypedArrayObject::ensureHasBuffer(cx, tarray)) |
| return false; |
| args.rval().set(TypedArrayObject::bufferValue(tarray)); |
| return true; |
| } |
| |
| static bool |
| TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<TypedArrayObject::is, BufferGetterImpl>(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_PS_END |
| }; |
| |
| /* static */ bool |
| TypedArrayObject::set(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<TypedArrayObject::is, |
| TypedArrayMethods<TypedArrayObject>::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, 2, 0), |
| #endif |
| JS_SELF_HOSTED_FN("copyWithin", "TypedArrayCopyWithin", 3, 0), |
| JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 2, 0), |
| JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0), |
| JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 2, 0), |
| JS_SELF_HOSTED_FN("find", "TypedArrayFind", 2, 0), |
| JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 2, 0), |
| JS_SELF_HOSTED_FN("forEach", "TypedArrayForEach", 2, 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", 2, 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", 2, 0), |
| JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0), |
| JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0), |
| // Both of these are actually defined to the same object in FinishTypedArrayInit. |
| JS_SELF_HOSTED_FN("values", "TypedArrayValues", 0, JSPROP_DEFINE_LATE), |
| JS_SELF_HOSTED_SYM_FN(iterator, "TypedArrayValues", 0, JSPROP_DEFINE_LATE), |
| JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0), |
| JS_FS_END |
| }; |
| |
| /* static */ const JSFunctionSpec |
| TypedArrayObject::staticFunctions[] = { |
| JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0), |
| JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0), |
| JS_FS_END |
| }; |
| |
| /* static */ const 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), |
| nullptr, /* addProperty */ |
| nullptr, /* delProperty */ |
| nullptr, /* getProperty */ |
| nullptr, /* setProperty */ |
| nullptr, /* enumerate */ |
| nullptr, /* resolve */ |
| nullptr, /* mayResolve */ |
| nullptr, /* finalize */ |
| nullptr, /* call */ |
| nullptr, /* hasInstance */ |
| nullptr, /* construct */ |
| nullptr, /* trace */ |
| { |
| GenericCreateConstructor<TypedArrayConstructor, 3, gc::AllocKind::FUNCTION>, |
| GenericCreatePrototype, |
| TypedArrayObject::staticFunctions, |
| nullptr, |
| TypedArrayObject::protoFunctions, |
| TypedArrayObject::protoAccessors, |
| FinishTypedArrayInit, |
| ClassSpec::DontDefineConstructor |
| } |
| }; |
| |
| template<typename T> |
| bool |
| ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args) |
| { |
| typedef TypedArrayObjectTemplate<T> ArrayType; |
| MOZ_ASSERT(IsArrayBuffer(args.thisv())); |
| MOZ_ASSERT(args.length() == 3); |
| |
| Rooted<JSObject*> buffer(cx, &args.thisv().toObject()); |
| Rooted<JSObject*> proto(cx, &args[2].toObject()); |
| |
| Rooted<JSObject*> 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<typename T> |
| bool |
| ArrayBufferObject::createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsArrayBuffer, createTypedArrayFromBufferImpl<T> >(cx, args); |
| } |
| |
| // this default implementation is only valid for integer types |
| // less than 32-bits in size. |
| template<typename NativeType> |
| Value |
| TypedArrayObjectTemplate<NativeType>::getIndexValue(JSObject* tarray, uint32_t index) |
| { |
| static_assert(sizeof(NativeType) < 4, |
| "this method must only handle NativeType values that are " |
| "always exact int32_t values"); |
| |
| return Int32Value(getIndex(tarray, index)); |
| } |
| |
| namespace { |
| |
| // and we need to specialize for 32-bit integers and floats |
| template<> |
| Value |
| TypedArrayObjectTemplate<int32_t>::getIndexValue(JSObject* tarray, uint32_t index) |
| { |
| return Int32Value(getIndex(tarray, index)); |
| } |
| |
| template<> |
| Value |
| TypedArrayObjectTemplate<uint32_t>::getIndexValue(JSObject* tarray, uint32_t index) |
| { |
| uint32_t val = getIndex(tarray, index); |
| return NumberValue(val); |
| } |
| |
| template<> |
| Value |
| TypedArrayObjectTemplate<float>::getIndexValue(JSObject* tarray, uint32_t index) |
| { |
| 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. |
| */ |
| return DoubleValue(CanonicalizeNaN(dval)); |
| } |
| |
| template<> |
| Value |
| TypedArrayObjectTemplate<double>::getIndexValue(JSObject* tarray, uint32_t index) |
| { |
| 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. |
| */ |
| return DoubleValue(CanonicalizeNaN(val)); |
| } |
| |
| } /* 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<ArrayBufferObject*> arrayBuffer, JSObject* protoArg) |
| { |
| MOZ_ASSERT(byteOffset <= INT32_MAX); |
| MOZ_ASSERT(byteLength <= INT32_MAX); |
| MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX); |
| MOZ_ASSERT(!arrayBuffer || !arrayBuffer->is<SharedArrayBufferObject>()); |
| |
| 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<DataViewObject>(); |
| 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, |
| "DataView", "ArrayBuffer", bufobj->getClass()->name); |
| return false; |
| } |
| |
| Rooted<ArrayBufferObject*> 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_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); |
| return false; |
| } |
| } |
| |
| if (buffer->isNeutered()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); |
| return false; |
| } |
| |
| if (args.length() > 1) { |
| if (byteOffset > byteLength) { |
| JS_ReportErrorNumber(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_ReportErrorNumber(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_ReportErrorNumber(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<ArrayBufferObject*> 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<WrapperObject>()); |
| |
| JSObject* unwrapped = CheckedUnwrap(bufobj); |
| if (!unwrapped) { |
| JS_ReportError(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<GlobalObject*> global(cx, cx->compartment()->maybeGlobal()); |
| if (!proto) { |
| proto = global->getOrCreateDataViewPrototype(cx); |
| if (!proto) |
| return false; |
| } |
| |
| InvokeArgs args2(cx); |
| if (!args2.init(3)) |
| return false; |
| args2.setCallee(global->createDataViewForThis()); |
| args2.setThis(ObjectValue(*bufobj)); |
| args2[0].set(PrivateUint32Value(byteOffset)); |
| args2[1].set(PrivateUint32Value(byteLength)); |
| args2[2].setObject(*proto); |
| if (!Invoke(cx, args2)) |
| return false; |
| args.rval().set(args2.rval()); |
| return true; |
| } |
| |
| 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<WrapperObject>()) |
| return constructWrapped(cx, bufobj, args); |
| return constructSameCompartment(cx, bufobj, args); |
| } |
| |
| template <typename NativeType> |
| /* static */ uint8_t* |
| DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint32_t offset) |
| { |
| const size_t TypeSize = sizeof(NativeType); |
| if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); |
| return nullptr; |
| } |
| |
| return static_cast<uint8_t*>(obj->dataPointer()) + offset; |
| } |
| |
| static inline bool |
| needToSwapBytes(bool littleEndian) |
| { |
| #if IS_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 <typename DataType> struct DataToRepType { typedef DataType result; }; |
| template <> struct DataToRepType<int8_t> { typedef uint8_t result; }; |
| template <> struct DataToRepType<uint8_t> { typedef uint8_t result; }; |
| template <> struct DataToRepType<int16_t> { typedef uint16_t result; }; |
| template <> struct DataToRepType<uint16_t> { typedef uint16_t result; }; |
| template <> struct DataToRepType<int32_t> { typedef uint32_t result; }; |
| template <> struct DataToRepType<uint32_t> { typedef uint32_t result; }; |
| template <> struct DataToRepType<float> { typedef uint32_t result; }; |
| template <> struct DataToRepType<double> { typedef uint64_t result; }; |
| |
| template <typename DataType> |
| struct DataViewIO |
| { |
| typedef typename DataToRepType<DataType>::result ReadWriteType; |
| |
| static void fromBuffer(DataType* dest, const uint8_t* unalignedBuffer, bool wantSwap) |
| { |
| MOZ_ASSERT((reinterpret_cast<uintptr_t>(dest) & (Min<size_t>(MOZ_ALIGNOF(void*), sizeof(DataType)) - 1)) == 0); |
| memcpy((void*) dest, unalignedBuffer, sizeof(ReadWriteType)); |
| if (wantSwap) { |
| ReadWriteType* rwDest = reinterpret_cast<ReadWriteType*>(dest); |
| *rwDest = swapBytes(*rwDest); |
| } |
| } |
| |
| static void toBuffer(uint8_t* unalignedBuffer, const DataType* src, bool wantSwap) |
| { |
| MOZ_ASSERT((reinterpret_cast<uintptr_t>(src) & (Min<size_t>(MOZ_ALIGNOF(void*), sizeof(DataType)) - 1)) == 0); |
| ReadWriteType temp = *reinterpret_cast<const ReadWriteType*>(src); |
| if (wantSwap) |
| temp = swapBytes(temp); |
| memcpy(unalignedBuffer, (void*) &temp, sizeof(ReadWriteType)); |
| } |
| }; |
| |
| template<typename NativeType> |
| /* static */ bool |
| DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj, |
| const CallArgs& args, NativeType* val, const char* method) |
| { |
| if (args.length() < 1) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_MORE_ARGS_NEEDED, method, "0", "s"); |
| return false; |
| } |
| |
| uint32_t offset; |
| if (!ToUint32(cx, args[0], &offset)) |
| return false; |
| |
| bool fromLittleEndian = args.length() >= 2 && ToBoolean(args[1]); |
| |
| if (obj->arrayBuffer().isNeutered()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); |
| return false; |
| } |
| |
| uint8_t* data = DataViewObject::getDataPointer<NativeType>(cx, obj, offset); |
| if (!data) |
| return false; |
| |
| DataViewIO<NativeType>::fromBuffer(val, data, needToSwapBytes(fromLittleEndian)); |
| return true; |
| } |
| |
| template <typename NativeType> |
| 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<NativeType>(temp); |
| return true; |
| } |
| |
| template <> |
| inline bool |
| WebIDLCast<float>(JSContext* cx, HandleValue value, float* out) |
| { |
| double temp; |
| if (!ToNumber(cx, value, &temp)) |
| return false; |
| *out = static_cast<float>(temp); |
| return true; |
| } |
| |
| template <> |
| inline bool |
| WebIDLCast<double>(JSContext* cx, HandleValue value, double* out) |
| { |
| return ToNumber(cx, value, out); |
| } |
| |
| template<typename NativeType> |
| /* static */ bool |
| DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj, |
| const CallArgs& args, const char* method) |
| { |
| if (args.length() < 2) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, |
| JSMSG_MORE_ARGS_NEEDED, method, "1", ""); |
| return false; |
| } |
| |
| uint32_t offset; |
| if (!ToUint32(cx, args[0], &offset)) |
| return false; |
| |
| NativeType value; |
| if (!WebIDLCast(cx, args[1], &value)) |
| return false; |
| |
| bool toLittleEndian = args.length() >= 3 && ToBoolean(args[2]); |
| |
| if (obj->arrayBuffer().isNeutered()) { |
| JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); |
| return false; |
| } |
| |
| uint8_t* data = DataViewObject::getDataPointer<NativeType>(cx, obj, offset); |
| if (!data) |
| return false; |
| |
| DataViewIO<NativeType>::toBuffer(data, &value, needToSwapBytes(toLittleEndian)); |
| return true; |
| } |
| |
| bool |
| DataViewObject::getInt8Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getInt8Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::getUint8Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getUint8Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::getInt16Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getInt16Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::getUint16Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getUint16Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::getInt32Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getInt32Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::getUint32Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getUint32Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::getFloat32Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getFloat32Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::getFloat64Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| 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<is, getFloat64Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setInt8Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<int8_t>(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<is, setInt8Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setUint8Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<uint8_t>(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<is, setUint8Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setInt16Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<int16_t>(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<is, setInt16Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setUint16Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<uint16_t>(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<is, setUint16Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setInt32Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<int32_t>(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<is, setInt32Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setUint32Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<uint32_t>(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<is, setUint32Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setFloat32Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<float>(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<is, setFloat32Impl>(cx, args); |
| } |
| |
| bool |
| DataViewObject::setFloat64Impl(JSContext* cx, const CallArgs& args) |
| { |
| MOZ_ASSERT(is(args.thisv())); |
| |
| Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); |
| |
| if (!write<double>(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<is, setFloat64Impl>(cx, args); |
| } |
| |
| Value |
| TypedArrayObject::getElement(uint32_t index) |
| { |
| switch (type()) { |
| case Scalar::Int8: |
| return Int8Array::getIndexValue(this, index); |
| case Scalar::Uint8: |
| return Uint8Array::getIndexValue(this, index); |
| case Scalar::Int16: |
| return Int16Array::getIndexValue(this, index); |
| case Scalar::Uint16: |
| return Uint16Array::getIndexValue(this, index); |
| case Scalar::Int32: |
| return Int32Array::getIndexValue(this, index); |
| case Scalar::Uint32: |
| return Uint32Array::getIndexValue(this, index); |
| case Scalar::Float32: |
| return Float32Array::getIndexValue(this, index); |
| case Scalar::Float64: |
| return Float64Array::getIndexValue(this, index); |
| case Scalar::Uint8Clamped: |
| return Uint8ClampedArray::getIndexValue(this, index); |
| case Scalar::Float32x4: |
| case Scalar::Int32x4: |
| case Scalar::MaxTypedArrayViewType: |
| break; |
| } |
| |
| MOZ_CRASH("Unknown TypedArray type"); |
| } |
| |
| void |
| TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) |
| { |
| MOZ_ASSERT(index < obj.length()); |
| |
| switch (obj.type()) { |
| case Scalar::Int8: |
| Int8Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Uint8: |
| Uint8Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Uint8Clamped: |
| Uint8ClampedArray::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Int16: |
| Int16Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Uint16: |
| Uint16Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Int32: |
| Int32Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Uint32: |
| Uint32Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Float32: |
| Float32Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Float64: |
| Float64Array::setIndexValue(obj, index, d); |
| return; |
| case Scalar::Float32x4: |
| case Scalar::Int32x4: |
| case Scalar::MaxTypedArrayViewType: |
| 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<NativeType>::fromLength(cx, nelements); \ |
| } \ |
| JS_FRIEND_API(JSObject*) JS_New ## Name ## ArrayFromArray(JSContext* cx, HandleObject other) \ |
| { \ |
| return TypedArrayObjectTemplate<NativeType>::fromArray(cx, other); \ |
| } \ |
| JS_FRIEND_API(JSObject*) JS_New ## Name ## ArrayWithBuffer(JSContext* cx, \ |
| HandleObject arrayBuffer, uint32_t byteOffset, int32_t length) \ |
| { \ |
| return TypedArrayObjectTemplate<NativeType>::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<NativeType>::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<NativeType>::instanceClass()) \ |
| return obj; \ |
| return nullptr; \ |
| } \ |
| const js::Class* const js::detail::Name ## ArrayClassPtr = \ |
| &js::TypedArrayObject::classes[TypedArrayObjectTemplate<NativeType>::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) |
| |
| #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<InternalType>::instanceClass()) \ |
| return nullptr; \ |
| \ |
| TypedArrayObject* tarr = &obj->as<TypedArrayObject>(); \ |
| *length = tarr->length(); \ |
| *isShared = tarr->isSharedMemory(); \ |
| *data = static_cast<ExternalType*>(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) |
| |
| #define TYPED_ARRAY_CLASS_SPEC(_typedArray) \ |
| { \ |
| _typedArray::createConstructor, \ |
| _typedArray::createPrototype, \ |
| nullptr, \ |
| nullptr, \ |
| nullptr, \ |
| nullptr, \ |
| _typedArray::finishClassInit, \ |
| JSProto_TypedArray \ |
| } |
| |
| #define IMPL_TYPED_ARRAY_CLASS(_typedArray) \ |
| { \ |
| #_typedArray, \ |
| JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \ |
| JSCLASS_HAS_PRIVATE | \ |
| JSCLASS_HAS_CACHED_PROTO(JSProto_##_typedArray) | \ |
| JSCLASS_DELAY_METADATA_CALLBACK, \ |
| nullptr, /* addProperty */ \ |
| nullptr, /* delProperty */ \ |
| nullptr, /* getProperty */ \ |
| nullptr, /* setProperty */ \ |
| nullptr, /* enumerate */ \ |
| nullptr, /* resolve */ \ |
| nullptr, /* mayResolve */ \ |
| nullptr, /* finalize */ \ |
| nullptr, /* call */ \ |
| nullptr, /* hasInstance */ \ |
| nullptr, /* construct */ \ |
| TypedArrayObject::trace, /* trace */ \ |
| TYPED_ARRAY_CLASS_SPEC(_typedArray) \ |
| } |
| |
| const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = { |
| IMPL_TYPED_ARRAY_CLASS(Int8Array), |
| IMPL_TYPED_ARRAY_CLASS(Uint8Array), |
| IMPL_TYPED_ARRAY_CLASS(Int16Array), |
| IMPL_TYPED_ARRAY_CLASS(Uint16Array), |
| IMPL_TYPED_ARRAY_CLASS(Int32Array), |
| IMPL_TYPED_ARRAY_CLASS(Uint32Array), |
| IMPL_TYPED_ARRAY_CLASS(Float32Array), |
| IMPL_TYPED_ARRAY_CLASS(Float64Array), |
| IMPL_TYPED_ARRAY_CLASS(Uint8ClampedArray) |
| }; |
| |
| // The various typed array prototypes are supposed to 1) be normal objects, |
| // 2) stringify to "[object <name of constructor>]", 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(typedArray, i) \ |
| { \ |
| /* |
| * 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. |
| */ \ |
| #typedArray "Prototype", \ |
| JSCLASS_HAS_CACHED_PROTO(JSProto_##typedArray), \ |
| nullptr, /* addProperty */ \ |
| nullptr, /* delProperty */ \ |
| nullptr, /* getProperty */ \ |
| nullptr, /* setProperty */ \ |
| nullptr, /* enumerate */ \ |
| nullptr, /* resolve */ \ |
| nullptr, /* mayResolve */ \ |
| nullptr, /* finalize */ \ |
| nullptr, /* call */ \ |
| nullptr, /* hasInstance */ \ |
| nullptr, /* construct */ \ |
| nullptr, /* trace */ \ |
| { \ |
| DELEGATED_CLASSSPEC(&TypedArrayObject::classes[i].spec), \ |
| nullptr, \ |
| nullptr, \ |
| nullptr, \ |
| nullptr, \ |
| nullptr, \ |
| nullptr, \ |
| JSProto_TypedArray | ClassSpec::IsDelegated \ |
| } \ |
| } |
| |
| const Class TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = { |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Int8Array, 0), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Array, 1), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Int16Array, 2), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Uint16Array, 3), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Int32Array, 4), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Uint32Array, 5), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Float32Array, 6), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Float64Array, 7), |
| IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8ClampedArray, 8) |
| }; |
| |
| /* 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) |
| }; |
| |
| const Class DataViewObject::class_ = { |
| "DataView", |
| JSCLASS_HAS_PRIVATE | |
| JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | |
| JSCLASS_HAS_CACHED_PROTO(JSProto_DataView), |
| 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 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, 2,0), |
| JS_FN("getUint16", DataViewObject::fun_getUint16, 2,0), |
| JS_FN("getInt32", DataViewObject::fun_getInt32, 2,0), |
| JS_FN("getUint32", DataViewObject::fun_getUint32, 2,0), |
| JS_FN("getFloat32", DataViewObject::fun_getFloat32, 2,0), |
| JS_FN("getFloat64", DataViewObject::fun_getFloat64, 2,0), |
| JS_FN("setInt8", DataViewObject::fun_setInt8, 2,0), |
| JS_FN("setUint8", DataViewObject::fun_setUint8, 2,0), |
| JS_FN("setInt16", DataViewObject::fun_setInt16, 3,0), |
| JS_FN("setUint16", DataViewObject::fun_setUint16, 3,0), |
| JS_FN("setInt32", DataViewObject::fun_setInt32, 3,0), |
| JS_FN("setUint32", DataViewObject::fun_setUint32, 3,0), |
| JS_FN("setFloat32", DataViewObject::fun_setFloat32, 3,0), |
| JS_FN("setFloat64", DataViewObject::fun_setFloat64, 3,0), |
| JS_FS_END |
| }; |
| |
| template<Value ValueGetter(DataViewObject* view)> |
| bool |
| DataViewObject::getterImpl(JSContext* cx, const CallArgs& args) |
| { |
| args.rval().set(ValueGetter(&args.thisv().toObject().as<DataViewObject>())); |
| return true; |
| } |
| |
| template<Value ValueGetter(DataViewObject* view)> |
| bool |
| DataViewObject::getter(JSContext* cx, unsigned argc, Value* vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<is, getterImpl<ValueGetter> >(cx, args); |
| } |
| |
| template<Value ValueGetter(DataViewObject* view)> |
| bool |
| DataViewObject::defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto) |
| { |
| RootedId id(cx, NameToId(name)); |
| unsigned attrs = JSPROP_SHARED | JSPROP_GETTER; |
| |
| Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal()); |
| JSObject* getter = |
| NewNativeFunction(cx, DataViewObject::getter<ValueGetter>, 0, nullptr); |
| 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<GlobalObject*> global(cx, cx->compartment()->maybeGlobal()); |
| if (global->isStandardClassResolved(JSProto_DataView)) |
| return true; |
| |
| RootedNativeObject proto(cx, global->createBlankPrototype(cx, &DataViewObject::protoClass)); |
| if (!proto) |
| return false; |
| |
| RootedFunction ctor(cx, global->createConstructor(cx, DataViewObject::class_constructor, |
| cx->names().DataView, 3)); |
| if (!ctor) |
| return false; |
| |
| if (!LinkConstructorAndPrototype(cx, ctor, proto)) |
| return false; |
| |
| if (!defineGetter<bufferValue>(cx, cx->names().buffer, proto)) |
| return false; |
| |
| if (!defineGetter<byteLengthValue>(cx, cx->names().byteLength, proto)) |
| return false; |
| |
| if (!defineGetter<byteOffsetValue>(cx, cx->names().byteOffset, proto)) |
| return false; |
| |
| if (!JS_DefineFunctions(cx, proto, DataViewObject::jsfuncs)) |
| 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::neuter(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 <typename CharT> |
| 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); |
| |
| /* 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<PropertyDescriptor> desc, ObjectOpResult& result) |
| { |
| MOZ_ASSERT(IsAnyTypedArray(obj)); |
| |
| // These are all substeps of 3.c. |
| // Steps i-vi. |
| // We (wrongly) ignore out of range defines with a value. |
| if (index >= AnyTypedArrayLength(obj)) |
| return result.succeed(); |
| |
| // Step vii. |
| if (desc.isAccessorDescriptor()) |
| return result.fail(JSMSG_CANT_REDEFINE_PROP); |
| |
| // Step viii. |
| if (desc.hasConfigurable() && desc.configurable()) |
| return result.fail(JSMSG_CANT_REDEFINE_PROP); |
| |
| // Step ix. |
| if (desc.hasEnumerable() && !desc.enumerable()) |
| return result.fail(JSMSG_CANT_REDEFINE_PROP); |
| |
| // Step x. |
| if (desc.hasWritable() && !desc.writable()) |
| return result.fail(JSMSG_CANT_REDEFINE_PROP); |
| |
| // Step xi. |
| if (desc.hasValue()) { |
| double d; |
| if (!ToNumber(cx, desc.value(), &d)) |
| return false; |
| |
| if (obj->is<TypedArrayObject>()) |
| TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d); |
| } |
| |
| // Step xii. |
| return result.succeed(); |
| } |
| |
| /* JS Friend API */ |
| |
| JS_FRIEND_API(bool) |
| JS_IsTypedArrayObject(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| return obj ? obj->is<TypedArrayObject>() : false; |
| } |
| |
| JS_FRIEND_API(uint32_t) |
| JS_GetTypedArrayLength(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return 0; |
| return obj->as<TypedArrayObject>().length(); |
| } |
| |
| JS_FRIEND_API(uint32_t) |
| JS_GetTypedArrayByteOffset(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return 0; |
| return obj->as<TypedArrayObject>().byteOffset(); |
| } |
| |
| JS_FRIEND_API(uint32_t) |
| JS_GetTypedArrayByteLength(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return 0; |
| return obj->as<TypedArrayObject>().byteLength(); |
| } |
| |
| JS_FRIEND_API(bool) |
| JS_GetTypedArraySharedness(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return false; |
| return obj->as<TypedArrayObject>().isSharedMemory(); |
| } |
| |
| JS_FRIEND_API(js::Scalar::Type) |
| JS_GetArrayBufferViewType(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return Scalar::MaxTypedArrayViewType; |
| |
| if (obj->is<TypedArrayObject>()) |
| return obj->as<TypedArrayObject>().type(); |
| else if (obj->is<DataViewObject>()) |
| 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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int8); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<int8_t*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint8); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<uint8_t*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint8Clamped); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<uint8_t*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int16); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<int16_t*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint16); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<uint16_t*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int32); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<int32_t*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint32); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<uint32_t*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Float32); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<float*>(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<TypedArrayObject>(); |
| MOZ_ASSERT((int32_t) tarr->type() == Scalar::Float64); |
| *isSharedMemory = tarr->isSharedMemory(); |
| return static_cast<double*>(tarr->viewDataEither().unwrap(/*safe - caller sees isSharedMemory*/)); |
| } |
| |
| JS_FRIEND_API(bool) |
| JS_IsDataViewObject(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| return obj ? obj->is<DataViewObject>() : false; |
| } |
| |
| JS_FRIEND_API(uint32_t) |
| JS_GetDataViewByteOffset(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return 0; |
| return obj->as<DataViewObject>().byteOffset(); |
| } |
| |
| JS_FRIEND_API(void*) |
| JS_GetDataViewData(JSObject* obj, const JS::AutoCheckCannotGC&) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return nullptr; |
| return obj->as<DataViewObject>().dataPointer(); |
| } |
| |
| JS_FRIEND_API(uint32_t) |
| JS_GetDataViewByteLength(JSObject* obj) |
| { |
| obj = CheckedUnwrap(obj); |
| if (!obj) |
| return 0; |
| return obj->as<DataViewObject>().byteLength(); |
| } |
| |
| JS_FRIEND_API(JSObject*) |
| JS_NewDataView(JSContext* cx, HandleObject arrayBuffer, uint32_t byteOffset, int32_t byteLength) |
| { |
| ConstructArgs cargs(cx); |
| if (!cargs.init(3)) |
| return nullptr; |
| |
| RootedObject constructor(cx); |
| JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(&DataViewObject::class_); |
| if (!GetBuiltinConstructor(cx, key, &constructor)) |
| return nullptr; |
| |
| cargs[0].setObject(*arrayBuffer); |
| cargs[1].setNumber(byteOffset); |
| cargs[2].setInt32(byteLength); |
| |
| RootedValue fun(cx, ObjectValue(*constructor)); |
| RootedValue rval(cx); |
| if (!Construct(cx, fun, cargs, fun, &rval)) |
| return nullptr; |
| MOZ_ASSERT(rval.isObject()); |
| return &rval.toObject(); |
| } |