diff -r 000000000000 -r 6474c204b198 js/src/vm/TypedArrayObject.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/vm/TypedArrayObject.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,2761 @@ +/* -*- 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 +#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 "gc/Barrier.h" +#include "gc/Marking.h" +#include "jit/AsmJS.h" +#include "jit/AsmJSModule.h" +#include "vm/ArrayBufferObject.h" +#include "vm/GlobalObject.h" +#include "vm/Interpreter.h" +#include "vm/NumericConversions.h" +#include "vm/SharedArrayObject.h" +#include "vm/WrapperObject.h" + +#include "jsatominlines.h" +#include "jsinferinlines.h" +#include "jsobjinlines.h" + +#include "vm/Shape-inl.h" + +using namespace js; +using namespace js::gc; +using namespace js::types; + +using mozilla::IsNaN; +using mozilla::NegativeInfinity; +using mozilla::PodCopy; +using mozilla::PositiveInfinity; +using JS::CanonicalizeNaN; +using JS::GenericNaN; + +static bool +ValueIsLength(const Value &v, uint32_t *len) +{ + if (v.isInt32()) { + int32_t i = v.toInt32(); + if (i < 0) + return false; + *len = i; + return true; + } + + if (v.isDouble()) { + double d = v.toDouble(); + if (IsNaN(d)) + return false; + + uint32_t length = uint32_t(d); + if (d != double(length)) + return false; + + *len = length; + return true; + } + + return false; +} + +/* + * TypedArrayObject + * + * The non-templated base class for the specific typed implementations. + * This class holds all the member variables that are used by + * the subclasses. + */ + +void +TypedArrayObject::neuter(void *newData) +{ + setSlot(LENGTH_SLOT, Int32Value(0)); + setSlot(BYTELENGTH_SLOT, Int32Value(0)); + setSlot(BYTEOFFSET_SLOT, Int32Value(0)); + setPrivate(newData); +} + +ArrayBufferObject * +TypedArrayObject::sharedBuffer() const +{ + return &bufferValue(const_cast(this)).toObject().as(); +} + +/* static */ bool +TypedArrayObject::ensureHasBuffer(JSContext *cx, Handle tarray) +{ + if (tarray->buffer()) + return true; + + Rooted buffer(cx, ArrayBufferObject::create(cx, tarray->byteLength())); + if (!buffer) + return false; + + buffer->addView(tarray); + + memcpy(buffer->dataPointer(), tarray->viewData(), tarray->byteLength()); + InitArrayBufferViewDataPointer(tarray, buffer, 0); + + tarray->setSlot(BUFFER_SLOT, ObjectValue(*buffer)); + return true; +} + +/* static */ int +TypedArrayObject::lengthOffset() +{ + return JSObject::getFixedSlotOffset(LENGTH_SLOT); +} + +/* static */ int +TypedArrayObject::dataOffset() +{ + return JSObject::getPrivateDataOffset(DATA_SLOT); +} + +/* 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 const int TypeIDOfType(); +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_INT8; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_UINT8; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_INT16; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_UINT16; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_INT32; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_UINT32; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_FLOAT32; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_FLOAT64; } +template<> inline const int TypeIDOfType() { return ScalarTypeDescr::TYPE_UINT8_CLAMPED; } + +template +static inline JSObject * +NewArray(JSContext *cx, uint32_t nelements); + +namespace { + +template +class TypedArrayObjectTemplate : public TypedArrayObject +{ + public: + typedef NativeType ThisType; + typedef TypedArrayObjectTemplate ThisTypedArrayObject; + static const int ArrayTypeID() { return TypeIDOfType(); } + static const bool ArrayTypeIsUnsigned() { return TypeIsUnsigned(); } + static const bool ArrayTypeIsFloatingPoint() { return TypeIsFloatingPoint(); } + + static const size_t BYTES_PER_ELEMENT = sizeof(ThisType); + + static inline const Class *protoClass() + { + return &TypedArrayObject::protoClasses[ArrayTypeID()]; + } + + static inline const Class *instanceClass() + { + return &TypedArrayObject::classes[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()) { + JS_ASSERT(sizeof(NativeType) <= 4); + uint32_t n = ToUint32(d); + setIndex(tarray, index, NativeType(n)); + } else if (ArrayTypeID() == ScalarTypeDescr::TYPE_UINT8_CLAMPED) { + // The uint8_clamped type has a special rounding converter + // for doubles. + setIndex(tarray, index, NativeType(d)); + } else { + JS_ASSERT(sizeof(NativeType) <= 4); + int32_t n = ToInt32(d); + setIndex(tarray, index, NativeType(n)); + } + } + + static TypedArrayObject * + makeProtoInstance(JSContext *cx, HandleObject proto, AllocKind allocKind) + { + JS_ASSERT(proto); + + RootedObject obj(cx, NewBuiltinClassInstance(cx, instanceClass(), allocKind)); + if (!obj) + return nullptr; + + types::TypeObject *type = cx->getNewType(obj->getClass(), proto.get()); + if (!type) + return nullptr; + obj->setType(type); + + return &obj->as(); + } + + static TypedArrayObject * + makeTypedInstance(JSContext *cx, uint32_t len, AllocKind allocKind) + { + if (len * sizeof(NativeType) >= TypedArrayObject::SINGLETON_TYPE_BYTE_LENGTH) { + return &NewBuiltinClassInstance(cx, instanceClass(), allocKind, + SingletonObject)->as(); + } + + jsbytecode *pc; + RootedScript script(cx, cx->currentScript(&pc)); + NewObjectKind newKind = script + ? UseNewTypeForInitializer(script, pc, instanceClass()) + : GenericObject; + RootedObject obj(cx, NewBuiltinClassInstance(cx, instanceClass(), allocKind, newKind)); + if (!obj) + return nullptr; + + if (script) { + if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind)) + return nullptr; + } + + return &obj->as(); + } + + static JSObject * + makeInstance(JSContext *cx, Handle buffer, uint32_t byteOffset, uint32_t len, + HandleObject proto) + { + JS_ASSERT_IF(!buffer, byteOffset == 0); + + gc::AllocKind allocKind = buffer + ? GetGCObjectKind(instanceClass()) + : AllocKindForLazyBuffer(len * sizeof(NativeType)); + + Rooted obj(cx); + if (proto) + obj = makeProtoInstance(cx, proto, allocKind); + else + obj = makeTypedInstance(cx, len, allocKind); + if (!obj) + return nullptr; + + obj->setSlot(TYPE_SLOT, Int32Value(ArrayTypeID())); + obj->setSlot(BUFFER_SLOT, ObjectOrNullValue(buffer)); + + if (buffer) { + InitArrayBufferViewDataPointer(obj, buffer, byteOffset); + } else { + void *data = obj->fixedData(FIXED_DATA_START); + obj->initPrivate(data); + memset(data, 0, len * sizeof(NativeType)); + } + + obj->setSlot(LENGTH_SLOT, Int32Value(len)); + obj->setSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset)); + obj->setSlot(BYTELENGTH_SLOT, Int32Value(len * sizeof(NativeType))); + obj->setSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr)); + +#ifdef DEBUG + if (buffer) { + uint32_t arrayByteLength = obj->byteLength(); + uint32_t arrayByteOffset = obj->byteOffset(); + uint32_t bufferByteLength = buffer->byteLength(); + JS_ASSERT_IF(!buffer->isNeutered(), buffer->dataPointer() <= obj->viewData()); + JS_ASSERT(bufferByteLength - arrayByteOffset >= arrayByteLength); + JS_ASSERT(arrayByteOffset <= bufferByteLength); + } + + // Verify that the private slot is at the expected place + JS_ASSERT(obj->numFixedSlots() == DATA_SLOT); +#endif + + if (buffer) + buffer->addView(obj); + + return obj; + } + + static JSObject * + makeInstance(JSContext *cx, Handle bufobj, uint32_t byteOffset, uint32_t len) + { + RootedObject nullproto(cx, nullptr); + return makeInstance(cx, bufobj, byteOffset, len, nullproto); + } + + /* + * 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) + { + /* N.B. this is a constructor for protoClass, not instanceClass! */ + CallArgs args = CallArgsFromVp(argc, vp); + JSObject *obj = create(cx, args); + if (!obj) + return false; + args.rval().setObject(*obj); + return true; + } + + static JSObject * + create(JSContext *cx, const CallArgs& args) + { + /* () or (number) */ + uint32_t len = 0; + if (args.length() == 0 || ValueIsLength(args[0], &len)) + return fromLength(cx, len); + + /* (not an object) */ + if (!args[0].isObject()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return nullptr; + } + + RootedObject dataObj(cx, &args.get(0).toObject()); + + /* + * (typedArray) + * (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 + */ + if (!UncheckedUnwrap(dataObj)->is() && + !UncheckedUnwrap(dataObj)->is()) + { + return fromArray(cx, dataObj); + } + + /* (ArrayBuffer, [byteOffset, [length]]) */ + 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, js_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, js_GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "2"); + return nullptr; + } + } + } + + Rooted proto(cx, nullptr); + return fromBuffer(cx, dataObj, byteOffset, length, proto); + } + + static bool IsThisClass(HandleValue v) { + return v.isObject() && v.toObject().hasClass(instanceClass()); + } + + template + static bool + GetterImpl(JSContext *cx, CallArgs args) + { + JS_ASSERT(IsThisClass(args.thisv())); + args.rval().set(ValueGetter(&args.thisv().toObject().as())); + return true; + } + + // ValueGetter is a function that takes an unwrapped typed array object and + // returns a Value. Given such a function, Getter<> is a native that + // retrieves a given Value, probably from a slot on the object. + template + static bool + Getter(JSContext *cx, unsigned argc, Value *vp) + { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod >(cx, args); + } + + static bool + BufferGetterImpl(JSContext *cx, CallArgs args) + { + JS_ASSERT(IsThisClass(args.thisv())); + Rooted tarray(cx, &args.thisv().toObject().as()); + if (!ensureHasBuffer(cx, tarray)) + return false; + args.rval().set(bufferValue(tarray)); + return true; + } + + // BufferGetter is a function that lazily constructs the array buffer for a + // typed array before fetching it. + static bool + BufferGetter(JSContext *cx, unsigned argc, Value *vp) + { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); + } + + // Define an accessor for a read-only property that invokes a native getter + static bool + DefineGetter(JSContext *cx, HandleObject proto, PropertyName *name, Native native) + { + RootedId id(cx, NameToId(name)); + unsigned attrs = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT; + + Rooted global(cx, cx->compartment()->maybeGlobal()); + JSObject *getter = NewFunction(cx, NullPtr(), native, 0, + JSFunction::NATIVE_FUN, global, NullPtr()); + if (!getter) + return false; + + return DefineNativeProperty(cx, proto, id, UndefinedHandleValue, + JS_DATA_TO_FUNC_PTR(PropertyOp, getter), nullptr, + attrs); + } + + static + bool defineGetters(JSContext *cx, HandleObject proto) + { + if (!DefineGetter(cx, proto, cx->names().length, Getter)) + return false; + + if (!DefineGetter(cx, proto, cx->names().buffer, BufferGetter)) + return false; + + if (!DefineGetter(cx, proto, cx->names().byteLength, Getter)) + return false; + + if (!DefineGetter(cx, proto, cx->names().byteOffset, Getter)) + return false; + + return true; + } + + /* subarray(start[, end]) */ + static bool + fun_subarray_impl(JSContext *cx, CallArgs args) + { + JS_ASSERT(IsThisClass(args.thisv())); + Rooted tarray(cx, &args.thisv().toObject().as()); + + // these are the default values + uint32_t length = tarray->length(); + uint32_t begin = 0, end = length; + + if (args.length() > 0) { + if (!ToClampedIndex(cx, args[0], length, &begin)) + return false; + + if (args.length() > 1) { + if (!ToClampedIndex(cx, args[1], length, &end)) + return false; + } + } + + if (begin > end) + begin = end; + + JSObject *nobj = createSubarray(cx, tarray, begin, end); + if (!nobj) + return false; + args.rval().setObject(*nobj); + return true; + } + + static bool + fun_subarray(JSContext *cx, unsigned argc, Value *vp) + { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); + } + + /* move(begin, end, dest) */ + static bool + fun_move_impl(JSContext *cx, CallArgs args) + { + JS_ASSERT(IsThisClass(args.thisv())); + Rooted tarray(cx, &args.thisv().toObject().as()); + + if (args.length() < 3) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return false; + } + + uint32_t srcBegin; + uint32_t srcEnd; + uint32_t dest; + + uint32_t originalLength = tarray->length(); + if (!ToClampedIndex(cx, args[0], originalLength, &srcBegin) || + !ToClampedIndex(cx, args[1], originalLength, &srcEnd) || + !ToClampedIndex(cx, args[2], originalLength, &dest)) + { + return false; + } + + if (srcBegin > srcEnd) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + + uint32_t lengthDuringMove = tarray->length(); // beware ToClampedIndex + uint32_t nelts = srcEnd - srcBegin; + + MOZ_ASSERT(dest <= INT32_MAX, "size limited to 2**31"); + MOZ_ASSERT(nelts <= INT32_MAX, "size limited to 2**31"); + if (dest + nelts > lengthDuringMove || srcEnd > lengthDuringMove) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return false; + } + + uint32_t byteDest = dest * sizeof(NativeType); + uint32_t byteSrc = srcBegin * sizeof(NativeType); + uint32_t byteSize = nelts * sizeof(NativeType); + +#ifdef DEBUG + uint32_t viewByteLength = tarray->byteLength(); + JS_ASSERT(byteDest <= viewByteLength); + JS_ASSERT(byteSrc <= viewByteLength); + JS_ASSERT(byteDest + byteSize <= viewByteLength); + JS_ASSERT(byteSrc + byteSize <= viewByteLength); + + // Should not overflow because size is limited to 2^31 + JS_ASSERT(byteDest + byteSize >= byteDest); + JS_ASSERT(byteSrc + byteSize >= byteSrc); +#endif + + uint8_t *data = static_cast(tarray->viewData()); + memmove(&data[byteDest], &data[byteSrc], byteSize); + args.rval().setUndefined(); + return true; + } + + static bool + fun_move(JSContext *cx, unsigned argc, Value *vp) + { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); + } + + /* set(array[, offset]) */ + static bool + fun_set_impl(JSContext *cx, CallArgs args) + { + JS_ASSERT(IsThisClass(args.thisv())); + Rooted tarray(cx, &args.thisv().toObject().as()); + + // first arg must be either a typed array or a JS array + if (args.length() == 0 || !args[0].isObject()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return false; + } + + int32_t offset = 0; + if (args.length() > 1) { + if (!ToInt32(cx, args[1], &offset)) + return false; + + if (offset < 0 || uint32_t(offset) > tarray->length()) { + // the given offset is bogus + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_BAD_INDEX, "2"); + return false; + } + } + + if (!args[0].isObject()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return false; + } + + RootedObject arg0(cx, args[0].toObjectOrNull()); + if (arg0->is()) { + if (arg0->as().length() > tarray->length() - offset) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); + return false; + } + + if (!copyFromTypedArray(cx, tarray, arg0, offset)) + return false; + } else { + uint32_t len; + if (!GetLengthProperty(cx, arg0, &len)) + return false; + + if (uint32_t(offset) > tarray->length() || len > tarray->length() - offset) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); + return false; + } + + if (!copyFromArray(cx, tarray, arg0, len, offset)) + return false; + } + + args.rval().setUndefined(); + return true; + } + + static bool + fun_set(JSContext *cx, unsigned argc, Value *vp) + { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); + } + + public: + static JSObject * + fromBuffer(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt, + HandleObject proto) + { + if (!ObjectClassIs(bufobj, ESClass_ArrayBuffer, cx)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return nullptr; // must be arrayBuffer + } + + JS_ASSERT(IsArrayBuffer(bufobj) || bufobj->is()); + 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_ReportError(cx, "Permission denied to access object"); + return nullptr; + } + if (IsArrayBuffer(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. + */ + + Rooted proto(cx); + if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &proto)) + return nullptr; + + InvokeArgs args(cx); + if (!args.init(3)) + return nullptr; + + args.setCallee(cx->compartment()->maybeGlobal()->createArrayFromBuffer()); + args.setThis(ObjectValue(*bufobj)); + args[0].setNumber(byteOffset); + args[1].setInt32(lengthInt); + args[2].setObject(*proto); + + if (!Invoke(cx, args)) + return nullptr; + return &args.rval().toObject(); + } + } + + if (!IsArrayBuffer(bufobj)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return nullptr; // must be arrayBuffer + } + + Rooted buffer(cx, &AsArrayBuffer(bufobj)); + + if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) { + JS_ReportErrorNumber(cx, js_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, js_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, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType) + } + + if (arrayByteLength + byteOffset > buffer->byteLength()) { + JS_ReportErrorNumber(cx, js_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 buffer) + { + // Make sure that array elements evenly divide into the inline buffer's + // size, for the test below. + JS_STATIC_ASSERT((INLINE_BUFFER_LIMIT / sizeof(NativeType)) * sizeof(NativeType) == INLINE_BUFFER_LIMIT); + + 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, js_GetErrorMessage, nullptr, + JSMSG_NEED_DIET, "size and count"); + return false; + } + + buffer.set(ArrayBufferObject::create(cx, nelements * sizeof(NativeType))); + return !!buffer; + } + + static JSObject * + fromLength(JSContext *cx, uint32_t nelements) + { + Rooted buffer(cx); + if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) + return nullptr; + return makeInstance(cx, buffer, 0, nelements); + } + + static JSObject * + fromArray(JSContext *cx, HandleObject other) + { + uint32_t len; + if (other->is()) { + len = other->as().length(); + } else if (!GetLengthProperty(cx, other, &len)) { + return nullptr; + } + + Rooted buffer(cx); + if (!maybeCreateArrayBuffer(cx, len, &buffer)) + return nullptr; + + RootedObject obj(cx, makeInstance(cx, buffer, 0, len)); + if (!obj || !copyFromArray(cx, obj, other, len)) + return nullptr; + return obj; + } + + static const NativeType + getIndex(JSObject *obj, uint32_t index) + { + TypedArrayObject &tarray = obj->as(); + MOZ_ASSERT(index < tarray.length()); + return static_cast(tarray.viewData())[index]; + } + + static void + setIndex(TypedArrayObject &tarray, uint32_t index, NativeType val) + { + MOZ_ASSERT(index < tarray.length()); + static_cast(tarray.viewData())[index] = val; + } + + static Value getIndexValue(JSObject *tarray, uint32_t index); + + static JSObject * + createSubarray(JSContext *cx, HandleObject tarrayArg, uint32_t begin, uint32_t end) + { + Rooted tarray(cx, &tarrayArg->as()); + + if (begin > tarray->length() || end > tarray->length() || begin > end) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return nullptr; + } + + if (!ensureHasBuffer(cx, tarray)) + return nullptr; + + Rooted bufobj(cx, tarray->buffer()); + JS_ASSERT(bufobj); + + uint32_t length = end - begin; + + JS_ASSERT(begin < UINT32_MAX / sizeof(NativeType)); + uint32_t arrayByteOffset = tarray->byteOffset(); + JS_ASSERT(UINT32_MAX - begin * sizeof(NativeType) >= arrayByteOffset); + uint32_t byteOffset = arrayByteOffset + begin * sizeof(NativeType); + + return makeInstance(cx, bufobj, byteOffset, length); + } + + protected: + static NativeType + doubleToNative(double d) + { + if (TypeIsFloatingPoint()) { +#ifdef JS_MORE_DETERMINISTIC + // The JS spec doesn't distinguish among different NaN values, and + // it deliberately doesn't specify the bit pattern written to a + // typed array when NaN is written into it. This bit-pattern + // inconsistency could confuse deterministic testing, so always + // canonicalize NaN values in more-deterministic builds. + d = CanonicalizeNaN(d); +#endif + return NativeType(d); + } + if (MOZ_UNLIKELY(IsNaN(d))) + return NativeType(0); + if (TypeIsUnsigned()) + return NativeType(ToUint32(d)); + return NativeType(ToInt32(d)); + } + + static bool + canConvertInfallibly(const Value &v) + { + return v.isNumber() || v.isBoolean() || v.isNull() || v.isUndefined(); + } + + static NativeType + infallibleValueToNative(const Value &v) + { + if (v.isInt32()) + return v.toInt32(); + if (v.isDouble()) + return doubleToNative(v.toDouble()); + if (v.isBoolean()) + return v.toBoolean(); + if (v.isNull()) + return 0; + + MOZ_ASSERT(v.isUndefined()); + return ArrayTypeIsFloatingPoint() ? NativeType(GenericNaN()) : NativeType(0); + } + + static bool + valueToNative(JSContext *cx, const Value &v, NativeType *result) + { + MOZ_ASSERT(!v.isMagic()); + + if (MOZ_LIKELY(canConvertInfallibly(v))) { + *result = infallibleValueToNative(v); + return true; + } + + double d; + MOZ_ASSERT(v.isString() || v.isObject()); + if (!(v.isString() ? StringToNumber(cx, v.toString(), &d) : ToNumber(cx, v, &d))) + return false; + + *result = doubleToNative(d); + return true; + } + + static bool + copyFromArray(JSContext *cx, HandleObject thisTypedArrayObj, + HandleObject source, uint32_t len, uint32_t offset = 0) + { + Rooted thisTypedArray(cx, &thisTypedArrayObj->as()); + JS_ASSERT(offset <= thisTypedArray->length()); + JS_ASSERT(len <= thisTypedArray->length() - offset); + if (source->is()) + return copyFromTypedArray(cx, thisTypedArray, source, offset); + + uint32_t i = 0; + if (source->isNative()) { + // Attempt fast-path infallible conversion of dense elements up to + // the first potentially side-effectful lookup or conversion. + uint32_t bound = Min(source->getDenseInitializedLength(), len); + + NativeType *dest = static_cast(thisTypedArray->viewData()) + offset; + + const Value *srcValues = source->getDenseElements(); + for (; i < bound; i++) { + // Note: holes don't convert infallibly. + if (!canConvertInfallibly(srcValues[i])) + break; + dest[i] = infallibleValueToNative(srcValues[i]); + } + if (i == len) + return true; + } + + // Convert and copy any remaining elements generically. + RootedValue v(cx); + for (; i < len; i++) { + if (!JSObject::getElement(cx, source, source, i, &v)) + return false; + + NativeType n; + if (!valueToNative(cx, v, &n)) + return false; + + len = Min(len, thisTypedArray->length()); + if (i >= len) + break; + + // Compute every iteration in case getElement acts wacky. + void *data = thisTypedArray->viewData(); + static_cast(data)[offset + i] = n; + } + + return true; + } + + static bool + copyFromTypedArray(JSContext *cx, JSObject *thisTypedArrayObj, JSObject *tarrayObj, + uint32_t offset) + { + TypedArrayObject *thisTypedArray = &thisTypedArrayObj->as(); + TypedArrayObject *tarray = &tarrayObj->as(); + JS_ASSERT(offset <= thisTypedArray->length()); + JS_ASSERT(tarray->length() <= thisTypedArray->length() - offset); + if (tarray->buffer() == thisTypedArray->buffer()) + return copyFromWithOverlap(cx, thisTypedArray, tarray, offset); + + NativeType *dest = static_cast(thisTypedArray->viewData()) + offset; + + if (tarray->type() == thisTypedArray->type()) { + js_memcpy(dest, tarray->viewData(), tarray->byteLength()); + return true; + } + + unsigned srclen = tarray->length(); + switch (tarray->type()) { + case ScalarTypeDescr::TYPE_INT8: { + int8_t *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_UINT8: + case ScalarTypeDescr::TYPE_UINT8_CLAMPED: { + uint8_t *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_INT16: { + int16_t *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_UINT16: { + uint16_t *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_INT32: { + int32_t *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_UINT32: { + uint32_t *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_FLOAT32: { + float *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_FLOAT64: { + double *src = static_cast(tarray->viewData()); + for (unsigned i = 0; i < srclen; ++i) + *dest++ = NativeType(*src++); + break; + } + default: + MOZ_ASSUME_UNREACHABLE("copyFrom with a TypedArrayObject of unknown type"); + } + + return true; + } + + static bool + copyFromWithOverlap(JSContext *cx, JSObject *selfObj, JSObject *tarrayObj, uint32_t offset) + { + TypedArrayObject *self = &selfObj->as(); + TypedArrayObject *tarray = &tarrayObj->as(); + + JS_ASSERT(offset <= self->length()); + + NativeType *dest = static_cast(self->viewData()) + offset; + uint32_t byteLength = tarray->byteLength(); + + if (tarray->type() == self->type()) { + memmove(dest, tarray->viewData(), byteLength); + return true; + } + + // We have to make a copy of the source array here, since + // there's overlap, and we have to convert types. + void *srcbuf = cx->malloc_(byteLength); + if (!srcbuf) + return false; + js_memcpy(srcbuf, tarray->viewData(), byteLength); + + uint32_t len = tarray->length(); + switch (tarray->type()) { + case ScalarTypeDescr::TYPE_INT8: { + int8_t *src = (int8_t*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_UINT8: + case ScalarTypeDescr::TYPE_UINT8_CLAMPED: { + uint8_t *src = (uint8_t*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_INT16: { + int16_t *src = (int16_t*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_UINT16: { + uint16_t *src = (uint16_t*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_INT32: { + int32_t *src = (int32_t*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_UINT32: { + uint32_t *src = (uint32_t*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_FLOAT32: { + float *src = (float*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + case ScalarTypeDescr::TYPE_FLOAT64: { + double *src = (double*) srcbuf; + for (unsigned i = 0; i < len; ++i) + *dest++ = NativeType(*src++); + break; + } + default: + MOZ_ASSUME_UNREACHABLE("copyFromWithOverlap with a TypedArrayObject of unknown type"); + } + + js_free(srcbuf); + return true; + } +}; + +class Int8ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_INT8 }; + static const JSProtoKey key = JSProto_Int8Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Uint8ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_UINT8 }; + static const JSProtoKey key = JSProto_Uint8Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Int16ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_INT16 }; + static const JSProtoKey key = JSProto_Int16Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Uint16ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_UINT16 }; + static const JSProtoKey key = JSProto_Uint16Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Int32ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_INT32 }; + static const JSProtoKey key = JSProto_Int32Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Uint32ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_UINT32 }; + static const JSProtoKey key = JSProto_Uint32Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Float32ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_FLOAT32 }; + static const JSProtoKey key = JSProto_Float32Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Float64ArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_FLOAT64 }; + static const JSProtoKey key = JSProto_Float64Array; + static const JSFunctionSpec jsfuncs[]; +}; +class Uint8ClampedArrayObject : public TypedArrayObjectTemplate { + public: + enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_UINT8_CLAMPED }; + static const JSProtoKey key = JSProto_Uint8ClampedArray; + static const JSFunctionSpec jsfuncs[]; +}; + +} /* anonymous namespace */ + +template +bool +ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext *cx, CallArgs args) +{ + typedef TypedArrayObjectTemplate ArrayType; + JS_ASSERT(IsArrayBuffer(args.thisv())); + JS_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::fromBuffer(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 +Value +TypedArrayObjectTemplate::getIndexValue(JSObject *tarray, uint32_t index) +{ + JS_STATIC_ASSERT(sizeof(NativeType) < 4); + + return Int32Value(getIndex(tarray, index)); +} + +namespace { + +// and we need to specialize for 32-bit integers and floats +template<> +Value +TypedArrayObjectTemplate::getIndexValue(JSObject *tarray, uint32_t index) +{ + return Int32Value(getIndex(tarray, index)); +} + +template<> +Value +TypedArrayObjectTemplate::getIndexValue(JSObject *tarray, uint32_t index) +{ + uint32_t val = getIndex(tarray, index); + return NumberValue(val); +} + +template<> +Value +TypedArrayObjectTemplate::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::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_TYPE_BYTE_LENGTH) + return SingletonObject; + jsbytecode *pc; + JSScript *script = cx->currentScript(&pc); + if (!script) + return GenericObject; + return types::UseNewTypeForInitializer(script, pc, &DataViewObject::class_); +} + +inline DataViewObject * +DataViewObject::create(JSContext *cx, uint32_t byteOffset, uint32_t byteLength, + Handle arrayBuffer, JSObject *protoArg) +{ + JS_ASSERT(byteOffset <= INT32_MAX); + JS_ASSERT(byteLength <= INT32_MAX); + + RootedObject proto(cx, protoArg); + RootedObject obj(cx); + + // This is overflow-safe: 2 * INT32_MAX is still a valid uint32_t. + if (byteOffset + byteLength > arrayBuffer->byteLength()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); + return nullptr; + + } + + NewObjectKind newKind = DataViewNewObjectKind(cx, byteLength, proto); + obj = NewBuiltinClassInstance(cx, &class_, newKind); + if (!obj) + return nullptr; + + if (proto) { + types::TypeObject *type = cx->getNewType(&class_, TaggedProto(proto)); + if (!type) + return nullptr; + obj->setType(type); + } else if (byteLength >= TypedArrayObject::SINGLETON_TYPE_BYTE_LENGTH) { + JS_ASSERT(obj->hasSingletonType()); + } else { + jsbytecode *pc; + RootedScript script(cx, cx->currentScript(&pc)); + if (script) { + if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind)) + return nullptr; + } + } + + DataViewObject &dvobj = obj->as(); + dvobj.setFixedSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset)); + dvobj.setFixedSlot(BYTELENGTH_SLOT, Int32Value(byteLength)); + dvobj.setFixedSlot(BUFFER_SLOT, ObjectValue(*arrayBuffer)); + dvobj.setFixedSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr)); + InitArrayBufferViewDataPointer(&dvobj, arrayBuffer, byteOffset); + JS_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength()); + + // Verify that the private slot is at the expected place + JS_ASSERT(dvobj.numFixedSlots() == DATA_SLOT); + + arrayBuffer->addView(&dvobj); + + return &dvobj; +} + +bool +DataViewObject::construct(JSContext *cx, JSObject *bufobj, const CallArgs &args, HandleObject proto) +{ + if (!IsArrayBuffer(bufobj)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, + "DataView", "ArrayBuffer", bufobj->getClass()->name); + return false; + } + + Rooted buffer(cx, &AsArrayBuffer(bufobj)); + uint32_t bufferLength = buffer->byteLength(); + uint32_t byteOffset = 0; + uint32_t byteLength = bufferLength; + + if (args.length() > 1) { + if (!ToUint32(cx, args[1], &byteOffset)) + return false; + if (byteOffset > INT32_MAX) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, + JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); + return false; + } + + if (args.length() > 2) { + if (!ToUint32(cx, args[2], &byteLength)) + return false; + if (byteLength > INT32_MAX) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, + JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); + return false; + } + } else { + if (byteOffset > bufferLength) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, + JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); + return false; + } + + byteLength = bufferLength - byteOffset; + } + } + + /* The sum of these cannot overflow a uint32_t */ + JS_ASSERT(byteOffset <= INT32_MAX); + JS_ASSERT(byteLength <= INT32_MAX); + + if (byteOffset + byteLength > bufferLength) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); + return false; + } + + JSObject *obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto); + if (!obj) + return false; + args.rval().setObject(*obj); + return true; +} + +bool +DataViewObject::class_constructor(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject bufobj(cx); + if (!GetFirstArgumentAsObject(cx, args, "DataView constructor", &bufobj)) + return false; + + if (bufobj->is() && IsArrayBuffer(UncheckedUnwrap(bufobj))) { + Rooted global(cx, cx->compartment()->maybeGlobal()); + Rooted proto(cx, global->getOrCreateDataViewPrototype(cx)); + if (!proto) + return false; + + InvokeArgs args2(cx); + if (!args2.init(args.length() + 1)) + return false; + args2.setCallee(global->createDataViewForThis()); + args2.setThis(ObjectValue(*bufobj)); + PodCopy(args2.array(), args.array(), args.length()); + args2[args.length()].setObject(*proto); + if (!Invoke(cx, args2)) + return false; + args.rval().set(args2.rval()); + return true; + } + + return construct(cx, bufobj, args, NullPtr()); +} + +template +/* static */ uint8_t * +DataViewObject::getDataPointer(JSContext *cx, Handle obj, uint32_t offset) +{ + const size_t TypeSize = sizeof(NativeType); + if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); + return nullptr; + } + + return static_cast(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 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 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) + { + JS_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) + { + JS_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, + CallArgs &args, NativeType *val, const char *method) +{ + if (args.length() < 1) { + JS_ReportErrorNumber(cx, js_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]); + + uint8_t *data = DataViewObject::getDataPointer(cx, obj, offset); + if (!data) + return false; + + DataViewIO::fromBuffer(val, data, needToSwapBytes(fromLittleEndian)); + 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, 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); +} + +template +/* static */ bool +DataViewObject::write(JSContext *cx, Handle obj, + CallArgs &args, const char *method) +{ + if (args.length() < 2) { + JS_ReportErrorNumber(cx, js_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]); + + uint8_t *data = DataViewObject::getDataPointer(cx, obj, offset); + if (!data) + return false; + + DataViewIO::toBuffer(data, &value, needToSwapBytes(toLittleEndian)); + return true; +} + +bool +DataViewObject::getInt8Impl(JSContext *cx, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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); +} + +bool +DataViewObject::getFloat32Impl(JSContext *cx, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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); +} + +bool +DataViewObject::setFloat32Impl(JSContext *cx, CallArgs args) +{ + JS_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, CallArgs args) +{ + JS_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); +} + +Value +TypedArrayObject::getElement(uint32_t index) +{ + switch (type()) { + case ScalarTypeDescr::TYPE_INT8: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_UINT8: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_UINT8_CLAMPED: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_INT16: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_UINT16: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_INT32: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_UINT32: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_FLOAT32: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + case ScalarTypeDescr::TYPE_FLOAT64: + return TypedArrayObjectTemplate::getIndexValue(this, index); + break; + default: + MOZ_ASSUME_UNREACHABLE("Unknown TypedArray type"); + break; + } +} + +void +TypedArrayObject::setElement(TypedArrayObject &obj, uint32_t index, double d) +{ + MOZ_ASSERT(index < obj.length()); + + switch (obj.type()) { + case ScalarTypeDescr::TYPE_INT8: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_UINT8: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_UINT8_CLAMPED: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_INT16: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_UINT16: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_INT32: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_UINT32: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_FLOAT32: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + case ScalarTypeDescr::TYPE_FLOAT64: + TypedArrayObjectTemplate::setIndexValue(obj, index, d); + break; + default: + MOZ_ASSUME_UNREACHABLE("Unknown TypedArray type"); + break; + } +} + +/*** + *** JS impl + ***/ + +/* + * TypedArrayObject boilerplate + */ + +#ifndef RELEASE_BUILD +# define IMPL_TYPED_ARRAY_STATICS(_typedArray) \ +const JSFunctionSpec _typedArray##Object::jsfuncs[] = { \ + JS_SELF_HOSTED_FN("@@iterator", "ArrayValues", 0, 0), \ + JS_FN("subarray", _typedArray##Object::fun_subarray, 2, JSFUN_GENERIC_NATIVE), \ + JS_FN("set", _typedArray##Object::fun_set, 2, JSFUN_GENERIC_NATIVE), \ + JS_FN("move", _typedArray##Object::fun_move, 3, JSFUN_GENERIC_NATIVE), \ + JS_FS_END \ +} +#else +# define IMPL_TYPED_ARRAY_STATICS(_typedArray) \ +const JSFunctionSpec _typedArray##Object::jsfuncs[] = { \ + JS_SELF_HOSTED_FN("@@iterator", "ArrayValues", 0, 0), \ + JS_FN("subarray", _typedArray##Object::fun_subarray, 2, JSFUN_GENERIC_NATIVE), \ + JS_FN("set", _typedArray##Object::fun_set, 2, JSFUN_GENERIC_NATIVE), \ + JS_FS_END \ +} +#endif + +#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::NullPtr()); \ + } \ + JS_FRIEND_API(bool) JS_Is ## Name ## Array(JSObject *obj) \ + { \ + if (!(obj = CheckedUnwrap(obj))) \ + return false; \ + const Class *clasp = obj->getClass(); \ + return clasp == &TypedArrayObject::classes[TypedArrayObjectTemplate::ArrayTypeID()]; \ + } \ + JS_FRIEND_API(JSObject *) js::Unwrap ## Name ## Array(JSObject *obj) \ + { \ + obj = CheckedUnwrap(obj); \ + if (!obj) \ + return nullptr; \ + const Class *clasp = obj->getClass(); \ + if (clasp == &TypedArrayObject::classes[TypedArrayObjectTemplate::ArrayTypeID()]) \ + 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) + +#define IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Name, ExternalType, InternalType) \ + JS_FRIEND_API(JSObject *) JS_GetObjectAs ## Name ## Array(JSObject *obj, \ + uint32_t *length, \ + ExternalType **data) \ + { \ + if (!(obj = CheckedUnwrap(obj))) \ + return nullptr; \ + \ + const Class *clasp = obj->getClass(); \ + if (clasp != &TypedArrayObject::classes[TypedArrayObjectTemplate::ArrayTypeID()]) \ + return nullptr; \ + \ + TypedArrayObject *tarr = &obj->as(); \ + *length = tarr->length(); \ + *data = static_cast(tarr->viewData()); \ + \ + 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 IMPL_TYPED_ARRAY_PROTO_CLASS(_typedArray) \ +{ \ + #_typedArray "Prototype", \ + JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \ + JSCLASS_HAS_PRIVATE | \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##_typedArray), \ + JS_PropertyStub, /* addProperty */ \ + JS_DeletePropertyStub, /* delProperty */ \ + JS_PropertyStub, /* getProperty */ \ + JS_StrictPropertyStub, /* setProperty */ \ + JS_EnumerateStub, \ + JS_ResolveStub, \ + JS_ConvertStub \ +} + +#define IMPL_TYPED_ARRAY_FAST_CLASS(_typedArray) \ +{ \ + #_typedArray, \ + JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \ + JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##_typedArray), \ + JS_PropertyStub, /* addProperty */ \ + JS_DeletePropertyStub, /* delProperty */ \ + JS_PropertyStub, /* getProperty */ \ + JS_StrictPropertyStub, /* setProperty */ \ + JS_EnumerateStub, \ + JS_ResolveStub, \ + JS_ConvertStub, \ + nullptr, /* finalize */ \ + nullptr, /* call */ \ + nullptr, /* hasInstance */ \ + nullptr, /* construct */ \ + ArrayBufferViewObject::trace, /* trace */ \ +} + +template +static inline bool +InitTypedArrayClass(JSContext *cx) +{ + Rooted global(cx, cx->compartment()->maybeGlobal()); + if (global->isStandardClassResolved(ArrayType::key)) + return true; + + RootedObject proto(cx, global->createBlankPrototype(cx, ArrayType::protoClass())); + if (!proto) + return false; + + RootedFunction ctor(cx); + ctor = global->createConstructor(cx, ArrayType::class_constructor, + ClassName(ArrayType::key, cx), 3); + if (!ctor) + return false; + + if (!LinkConstructorAndPrototype(cx, ctor, proto)) + return false; + + RootedValue bytesValue(cx, Int32Value(ArrayType::BYTES_PER_ELEMENT)); + + if (!JSObject::defineProperty(cx, ctor, + cx->names().BYTES_PER_ELEMENT, bytesValue, + JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_PERMANENT | JSPROP_READONLY) || + !JSObject::defineProperty(cx, proto, + cx->names().BYTES_PER_ELEMENT, bytesValue, + JS_PropertyStub, JS_StrictPropertyStub, + JSPROP_PERMANENT | JSPROP_READONLY)) + { + return false; + } + + if (!ArrayType::defineGetters(cx, proto)) + return false; + + if (!JS_DefineFunctions(cx, proto, ArrayType::jsfuncs)) + return false; + + RootedFunction fun(cx); + fun = + NewFunction(cx, NullPtr(), + ArrayBufferObject::createTypedArrayFromBuffer, + 0, JSFunction::NATIVE_FUN, global, NullPtr()); + if (!fun) + return false; + + if (!GlobalObject::initBuiltinConstructor(cx, global, ArrayType::key, ctor, proto)) + return false; + + global->setCreateArrayFromBuffer(fun); + + return true; +} + +IMPL_TYPED_ARRAY_STATICS(Int8Array); +IMPL_TYPED_ARRAY_STATICS(Uint8Array); +IMPL_TYPED_ARRAY_STATICS(Int16Array); +IMPL_TYPED_ARRAY_STATICS(Uint16Array); +IMPL_TYPED_ARRAY_STATICS(Int32Array); +IMPL_TYPED_ARRAY_STATICS(Uint32Array); +IMPL_TYPED_ARRAY_STATICS(Float32Array); +IMPL_TYPED_ARRAY_STATICS(Float64Array); +IMPL_TYPED_ARRAY_STATICS(Uint8ClampedArray); + +const Class TypedArrayObject::classes[ScalarTypeDescr::TYPE_MAX] = { + IMPL_TYPED_ARRAY_FAST_CLASS(Int8Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Uint8Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Int16Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Uint16Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Int32Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Uint32Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Float32Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Float64Array), + IMPL_TYPED_ARRAY_FAST_CLASS(Uint8ClampedArray) +}; + +const Class TypedArrayObject::protoClasses[ScalarTypeDescr::TYPE_MAX] = { + IMPL_TYPED_ARRAY_PROTO_CLASS(Int8Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Int16Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Uint16Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Int32Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Uint32Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Float32Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Float64Array), + IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8ClampedArray) +}; + +#define CHECK(t, a) { if (t == a::IsThisClass) return true; } +JS_FRIEND_API(bool) +js::IsTypedArrayThisCheck(JS::IsAcceptableThis test) +{ + CHECK(test, Int8ArrayObject); + CHECK(test, Uint8ArrayObject); + CHECK(test, Int16ArrayObject); + CHECK(test, Uint16ArrayObject); + CHECK(test, Int32ArrayObject); + CHECK(test, Uint32ArrayObject); + CHECK(test, Float32ArrayObject); + CHECK(test, Float64ArrayObject); + CHECK(test, Uint8ClampedArrayObject); + return false; +} +#undef CHECK + +static JSObject * +InitArrayBufferClass(JSContext *cx) +{ + Rooted global(cx, cx->compartment()->maybeGlobal()); + if (global->isStandardClassResolved(JSProto_ArrayBuffer)) + return &global->getPrototype(JSProto_ArrayBuffer).toObject(); + + RootedObject arrayBufferProto(cx, global->createBlankPrototype(cx, &ArrayBufferObject::protoClass)); + if (!arrayBufferProto) + return nullptr; + + RootedFunction ctor(cx, global->createConstructor(cx, ArrayBufferObject::class_constructor, + cx->names().ArrayBuffer, 1)); + if (!ctor) + return nullptr; + + if (!LinkConstructorAndPrototype(cx, ctor, arrayBufferProto)) + return nullptr; + + RootedId byteLengthId(cx, NameToId(cx->names().byteLength)); + unsigned attrs = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT; + JSObject *getter = NewFunction(cx, NullPtr(), ArrayBufferObject::byteLengthGetter, 0, + JSFunction::NATIVE_FUN, global, NullPtr()); + if (!getter) + return nullptr; + + if (!DefineNativeProperty(cx, arrayBufferProto, byteLengthId, UndefinedHandleValue, + JS_DATA_TO_FUNC_PTR(PropertyOp, getter), nullptr, attrs)) + return nullptr; + + if (!JS_DefineFunctions(cx, ctor, ArrayBufferObject::jsstaticfuncs)) + return nullptr; + + if (!JS_DefineFunctions(cx, arrayBufferProto, ArrayBufferObject::jsfuncs)) + return nullptr; + + if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_ArrayBuffer, + ctor, arrayBufferProto)) + { + return nullptr; + } + + return arrayBufferProto; +} + +const Class DataViewObject::protoClass = { + "DataViewPrototype", + JSCLASS_HAS_PRIVATE | + JSCLASS_HAS_RESERVED_SLOTS(DataViewObject::RESERVED_SLOTS) | + JSCLASS_HAS_CACHED_PROTO(JSProto_DataView), + JS_PropertyStub, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub +}; + +const Class DataViewObject::class_ = { + "DataView", + JSCLASS_HAS_PRIVATE | + JSCLASS_IMPLEMENTS_BARRIERS | + JSCLASS_HAS_RESERVED_SLOTS(DataViewObject::RESERVED_SLOTS) | + JSCLASS_HAS_CACHED_PROTO(JSProto_DataView), + JS_PropertyStub, /* addProperty */ + JS_DeletePropertyStub, /* delProperty */ + JS_PropertyStub, /* getProperty */ + JS_StrictPropertyStub, /* setProperty */ + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + ArrayBufferViewObject::trace, /* 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 +bool +DataViewObject::getterImpl(JSContext *cx, 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, HandleObject proto) +{ + RootedId id(cx, NameToId(name)); + unsigned attrs = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT; + + Rooted global(cx, cx->compartment()->maybeGlobal()); + JSObject *getter = NewFunction(cx, NullPtr(), DataViewObject::getter, 0, + JSFunction::NATIVE_FUN, global, NullPtr()); + if (!getter) + return false; + + return DefineNativeProperty(cx, proto, id, UndefinedHandleValue, + JS_DATA_TO_FUNC_PTR(PropertyOp, getter), nullptr, attrs); +} + +/* static */ bool +DataViewObject::initClass(JSContext *cx) +{ + Rooted global(cx, cx->compartment()->maybeGlobal()); + if (global->isStandardClassResolved(JSProto_DataView)) + return true; + + RootedObject 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(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; + + /* + * 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, NewFunction(cx, NullPtr(), ArrayBufferObject::createDataViewForThis, + 0, JSFunction::NATIVE_FUN, global, 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) +{ + setSlot(BYTELENGTH_SLOT, Int32Value(0)); + setSlot(BYTEOFFSET_SLOT, Int32Value(0)); + setPrivate(newData); +} + +JSObject * +js_InitTypedArrayClasses(JSContext *cx, HandleObject obj) +{ + if (!InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !InitTypedArrayClass(cx) || + !DataViewObject::initClass(cx)) + { + return nullptr; + } + + return InitArrayBufferClass(cx); +} + +bool +js::IsTypedArrayConstructor(HandleValue v, uint32_t type) +{ + switch (type) { + case ScalarTypeDescr::TYPE_INT8: + return IsNativeFunction(v, Int8ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_UINT8: + return IsNativeFunction(v, Uint8ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_INT16: + return IsNativeFunction(v, Int16ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_UINT16: + return IsNativeFunction(v, Uint16ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_INT32: + return IsNativeFunction(v, Int32ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_UINT32: + return IsNativeFunction(v, Uint32ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_FLOAT32: + return IsNativeFunction(v, Float32ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_FLOAT64: + return IsNativeFunction(v, Float64ArrayObject::class_constructor); + case ScalarTypeDescr::TYPE_UINT8_CLAMPED: + return IsNativeFunction(v, Uint8ClampedArrayObject::class_constructor); + } + MOZ_ASSUME_UNREACHABLE("unexpected typed array type"); +} + +bool +js::IsTypedArrayBuffer(HandleValue v) +{ + return v.isObject() && + (v.toObject().is() || + v.toObject().is()); +} + +ArrayBufferObject & +js::AsTypedArrayBuffer(HandleValue v) +{ + JS_ASSERT(IsTypedArrayBuffer(v)); + if (v.toObject().is()) + return v.toObject().as(); + return v.toObject().as(); +} + +bool +js::StringIsTypedArrayIndex(JSLinearString *str, uint64_t *indexp) +{ + const jschar *s = str->chars(); + const jschar *end = s + str->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; +} + +/* 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(JSArrayBufferViewType) +JS_GetArrayBufferViewType(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return ArrayBufferView::TYPE_MAX; + + if (obj->is()) + return static_cast(obj->as().type()); + else if (obj->is()) + return ArrayBufferView::TYPE_DATAVIEW; + MOZ_ASSUME_UNREACHABLE("invalid ArrayBufferView type"); +} + +JS_FRIEND_API(int8_t *) +JS_GetInt8ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_INT8); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(uint8_t *) +JS_GetUint8ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_UINT8); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(uint8_t *) +JS_GetUint8ClampedArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_UINT8_CLAMPED); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(int16_t *) +JS_GetInt16ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_INT16); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(uint16_t *) +JS_GetUint16ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_UINT16); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(int32_t *) +JS_GetInt32ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_INT32); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(uint32_t *) +JS_GetUint32ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_UINT32); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(float *) +JS_GetFloat32ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_FLOAT32); + return static_cast(tarr->viewData()); +} + +JS_FRIEND_API(double *) +JS_GetFloat64ArrayData(JSObject *obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return nullptr; + TypedArrayObject *tarr = &obj->as(); + JS_ASSERT(tarr->type() == ArrayBufferView::TYPE_FLOAT64); + return static_cast(tarr->viewData()); +} + +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) +{ + 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(); +}