author | André Bargull <andre.bargull@gmail.com> |
Wed, 19 Apr 2017 06:07:16 -0700 | |
changeset 353994 | 4126ffda915309da1578a2b57d8d726000cc91f0 |
parent 353993 | feb9622d58ab943909b06461da6c5fa8658186e4 |
child 353995 | 8f44dcf0d3406f4d44b04b86fff06a37937f2179 |
push id | 31684 |
push user | cbook@mozilla.com |
push date | Thu, 20 Apr 2017 09:13:26 +0000 |
treeherder | mozilla-central@27311156637f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | Waldo |
bugs | 1345115 |
milestone | 55.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/public/Class.h +++ b/js/public/Class.h @@ -850,17 +850,17 @@ static const uint32_t JSCLASS_FOREGROUND // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was // previously allowed, but is now an ES5 violation and thus unsupported. // // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at // the beginning of every global object's slots for use by the // application. static const uint32_t JSCLASS_GLOBAL_APPLICATION_SLOTS = 5; static const uint32_t JSCLASS_GLOBAL_SLOT_COUNT = - JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 46; + JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 37; #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0) #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp) \ (((clasp)->flags & JSCLASS_IS_GLOBAL) \ && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/src/builtin/DataViewObject.cpp +++ b/js/src/builtin/DataViewObject.cpp @@ -232,22 +232,16 @@ DataViewObject::constructSameCompartment // 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>()); RootedObject unwrapped(cx, CheckedUnwrap(bufobj)); if (!unwrapped) {
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence-nonstandard.js @@ -0,0 +1,35 @@ +// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] ) + +// Contrary to the ES2017 specification, we don't allow to create a TypedArray +// object with a detached ArrayBuffer. We consider the current specification to +// be incorrect. + +const otherGlobal = newGlobal(); + +function* createBuffers() { + var lengths = [0, 8]; + for (let length of lengths) { + let buffer = new ArrayBuffer(length); + yield {buffer, detach: () => detachArrayBuffer(buffer)}; + } + + for (let length of lengths) { + let buffer = new otherGlobal.ArrayBuffer(length); + yield {buffer, detach: () => otherGlobal.detachArrayBuffer(buffer)}; + } +} + +// Ensure we handle the case when ToIndex(length) detaches the array buffer. +for (let {buffer, detach} of createBuffers()) { + let length = { + valueOf() { + detach(); + return 0; + } + }; + + assertThrowsInstanceOf(() => new Int32Array(buffer, 0, length), TypeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true);
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-buffer-sequence.js @@ -0,0 +1,202 @@ +// 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] ) + +// Ensure the various error conditions are tested in the correct order. + +const otherGlobal = newGlobal(); + +function* createBuffers(lengths = [0, 8]) { + for (let length of lengths) { + let buffer = new ArrayBuffer(length); + yield {buffer, detach: () => detachArrayBuffer(buffer)}; + } + + for (let length of lengths) { + let buffer = new otherGlobal.ArrayBuffer(length); + yield {buffer, detach: () => otherGlobal.detachArrayBuffer(buffer)}; + } +} + +const poisonedValue = new Proxy({}, new Proxy({}, { + get() { + // Throws an exception when any proxy trap is invoked. + throw new Error("Poisoned Value"); + } +})); + +class ExpectedError extends Error { } + +function ConstructorWithThrowingPrototype(detach) { + return Object.defineProperty(function(){}.bind(null), "prototype", { + get() { + if (detach) + detach(); + throw new ExpectedError(); + } + }); +} + +function ValueThrowing(detach) { + return { + valueOf() { + if (detach) + detach(); + throw new ExpectedError(); + } + }; +} + +function ValueReturning(value, detach) { + return { + valueOf() { + if (detach) + detach(); + return value; + } + }; +} + +// Ensure step 4 |AllocateTypedArray| is executed before step 6 |ToIndex(byteOffset)|. +for (let {buffer} of createBuffers()) { + let constructor = ConstructorWithThrowingPrototype(); + + assertThrowsInstanceOf(() => + Reflect.construct(Int32Array, [buffer, poisonedValue, 0], constructor), ExpectedError); +} + +// Ensure step 4 |AllocateTypedArray| is executed before step 8 |IsDetachedBuffer(buffer)|. +for (let {buffer, detach} of createBuffers()) { + let constructor = ConstructorWithThrowingPrototype(); + + detach(); + assertThrowsInstanceOf(() => + Reflect.construct(Int32Array, [buffer, 0, 0], constructor), ExpectedError); +} + +// Ensure step 4 |AllocateTypedArray| is executed before step 8 |IsDetachedBuffer(buffer)|. +// - Variant: Detach buffer dynamically. +for (let {buffer, detach} of createBuffers()) { + let constructor = ConstructorWithThrowingPrototype(detach); + + assertThrowsInstanceOf(() => + Reflect.construct(Int32Array, [buffer, 0, 0], constructor), ExpectedError); +} + +// Ensure step 4 |AllocateTypedArray| is executed before step 11.a |ToIndex(length)|. +for (let {buffer} of createBuffers()) { + let constructor = ConstructorWithThrowingPrototype(); + + assertThrowsInstanceOf(() => + Reflect.construct(Int32Array, [buffer, 0, poisonedValue], constructor), ExpectedError); +} + +// Ensure step 6 |ToIndex(byteOffset)| is executed before step 8 |IsDetachedBuffer(buffer)|. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = ValueThrowing(); + + detach(); + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), ExpectedError); +} + +// Ensure step 6 |ToIndex(byteOffset)| is executed before step 8 |IsDetachedBuffer(buffer)|. +// - Variant: Detach buffer dynamically. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = ValueThrowing(detach); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), ExpectedError); +} + +// Ensure step 6 |ToIndex(byteOffset)| is executed before step 11.a |ToIndex(length)|. +for (let {buffer} of createBuffers()) { + let byteOffset = ValueThrowing(); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, poisonedValue), ExpectedError); +} + +// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 8 |IsDetachedBuffer(buffer)|. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = 1; + + detach(); + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), RangeError); +} + +// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 8 |IsDetachedBuffer(buffer)|. +// - Variant: Detach buffer dynamically. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = ValueReturning(1, detach); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, 0), RangeError); +} + +// Ensure step 7 |offset modulo elementSize ≠ 0| is executed before step 11.a |ToIndex(length)|. +for (let {buffer} of createBuffers()) { + assertThrowsInstanceOf(() => new Int32Array(buffer, 1, poisonedValue), RangeError); +} + +// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.a |bufferByteLength modulo elementSize ≠ 0|. +for (let {buffer, detach} of createBuffers([1, 9])) { + let byteOffset = 0; + + detach(); + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError); +} + +// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.a |bufferByteLength modulo elementSize ≠ 0|. +// - Variant: Detach buffer dynamically. +for (let {buffer, detach} of createBuffers([1, 9])) { + let byteOffset = ValueReturning(0, detach); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError); +} + +// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.c |newByteLength < 0|. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = 64; + + detach(); + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError); +} + +// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 10.c |newByteLength < 0|. +// - Variant: Detach buffer dynamically. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = ValueReturning(64, detach); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset), TypeError); +} + +// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 11.a |ToIndex(length)|. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = 0; + + detach(); + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, poisonedValue), TypeError); +} + +// Ensure step 8 |IsDetachedBuffer(buffer)| is executed before step 11.a |ToIndex(length)|. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = ValueReturning(0, detach); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, poisonedValue), TypeError); +} + +// Ensure step 11.c |offset+newByteLength > bufferByteLength| is executed when buffer is detached. +// - Case A: The given byteOffset is too large. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = 64; + let length = ValueReturning(0, detach); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), RangeError); +} + +// Ensure step 11.c |offset+newByteLength > bufferByteLength| is executed when buffer is detached. +// - Case B: The given length is too large. +for (let {buffer, detach} of createBuffers()) { + let byteOffset = 0; + let length = ValueReturning(64, detach); + + assertThrowsInstanceOf(() => new Int32Array(buffer, byteOffset, length), RangeError); +} + +if (typeof reportCompare === "function") + reportCompare(true, true);
--- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -249,21 +249,16 @@ class ArrayBufferObject : public ArrayBu static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes, HandleObject proto = nullptr, NewObjectKind newKind = GenericObject); // Create an ArrayBufferObject that is safely finalizable and can later be // initialize()d to become a real, content-visible ArrayBufferObject. static ArrayBufferObject* createEmpty(JSContext* cx); - template<typename T> - static bool createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args); - template<typename T> - static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp); - static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex, Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex, uint32_t count); static void trace(JSTracer* trc, JSObject* obj); static void objectMoved(JSObject* obj, const JSObject* old); static BufferContents externalizeContents(JSContext* cx,
--- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -67,30 +67,16 @@ class GlobalObject : public NativeObject */ static const unsigned STANDARD_CLASS_SLOTS = JSProto_LIMIT * 2; enum : unsigned { /* Various function values needed by the engine. */ EVAL = APPLICATION_SLOTS + STANDARD_CLASS_SLOTS, THROWTYPEERROR, - /* - * Instances of the internal createArrayFromBuffer function used by the - * typed array code, one per typed array element type. - */ - FROM_BUFFER_UINT8, - FROM_BUFFER_INT8, - FROM_BUFFER_UINT16, - FROM_BUFFER_INT16, - FROM_BUFFER_UINT32, - FROM_BUFFER_INT32, - FROM_BUFFER_FLOAT32, - FROM_BUFFER_FLOAT64, - FROM_BUFFER_UINT8CLAMPED, - /* One-off properties stored after slots for built-ins. */ LEXICAL_ENVIRONMENT, EMPTY_GLOBAL_SCOPE, ITERATOR_PROTO, ARRAY_ITERATOR_PROTO, STRING_ITERATOR_PROTO, LEGACY_GENERATOR_OBJECT_PROTO, STAR_GENERATOR_OBJECT_PROTO, @@ -255,31 +241,16 @@ class GlobalObject : public NativeObject } bool errorClassesInitialized() const { return classIsInitialized(JSProto_Error); } bool dataViewClassInitialized() const { return classIsInitialized(JSProto_DataView); } - Value createArrayFromBufferHelper(uint32_t slot) const { - MOZ_ASSERT(FROM_BUFFER_UINT8 <= slot && slot <= FROM_BUFFER_UINT8CLAMPED); - return getSlot(slot); - } - - void setCreateArrayFromBufferHelper(uint32_t slot, Handle<JSFunction*> fun) { - MOZ_ASSERT(getSlotRef(slot).isUndefined()); - setSlot(slot, ObjectValue(*fun)); - } - - public: - template<typename T> - inline void setCreateArrayFromBuffer(Handle<JSFunction*> fun); - - private: // Disallow use of unqualified JSObject::create in GlobalObject. static GlobalObject* create(...) = delete; friend struct ::JSRuntime; static GlobalObject* createInternal(JSContext* cx, const Class* clasp); public: static GlobalObject* @@ -774,19 +745,16 @@ class GlobalObject : public NativeObject JSObject* getThrowTypeError() const { const Value v = getReservedSlot(THROWTYPEERROR); MOZ_ASSERT(v.isObject(), "attempting to access [[ThrowTypeError]] too early"); return &v.toObject(); } - template<typename T> - inline Value createArrayFromBuffer() const; - static bool isRuntimeCodeGenEnabled(JSContext* cx, Handle<GlobalObject*> global); // Warn about use of the deprecated watch/unwatch functions in the global // in which |obj| was created, if no prior warning was given. static bool warnOnceAboutWatch(JSContext* cx, HandleObject obj) { // Temporarily disabled until we've provided a watch/unwatch workaround for // debuggers like Firebug (bug 934669). //return warnOnceAbout(cx, obj, WARN_WATCH_DEPRECATED, JSMSG_OBJECT_WATCH_DEPRECATED); @@ -884,142 +852,16 @@ class GlobalObject : public NativeObject } // Returns either this global's star-generator function prototype, or null // if that object was never created. Dodgy; for use only in also-dodgy // GlobalHelperThreadState::mergeParseTaskCompartment(). JSObject* getStarGeneratorFunctionPrototype(); }; -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<uint8_t>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<int8_t>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_INT8, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<uint16_t>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_UINT16, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<int16_t>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_INT16, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<uint32_t>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_UINT32, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<int32_t>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_INT32, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<float>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_FLOAT32, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<double>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_FLOAT64, fun); -} - -template<> -inline void -GlobalObject::setCreateArrayFromBuffer<uint8_clamped>(Handle<JSFunction*> fun) -{ - setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8CLAMPED, fun); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<uint8_t>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_UINT8); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<int8_t>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_INT8); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<uint16_t>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_UINT16); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<int16_t>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_INT16); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<uint32_t>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_UINT32); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<int32_t>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_INT32); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<float>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_FLOAT32); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<double>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_FLOAT64); -} - -template<> -inline Value -GlobalObject::createArrayFromBuffer<uint8_clamped>() const -{ - return createArrayFromBufferHelper(FROM_BUFFER_UINT8CLAMPED); -} - /* * Unless otherwise specified, define ctor.prototype = proto as non-enumerable, * non-configurable, and non-writable; and define proto.constructor = ctor as * non-enumerable but configurable and writable. */ extern bool LinkConstructorAndPrototype(JSContext* cx, JSObject* ctor, JSObject* proto, unsigned prototypeAttrs = JSPROP_PERMANENT | JSPROP_READONLY,
--- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -379,38 +379,16 @@ class TypedArrayObjectTemplate : public SingletonObject); if (fun) fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor); return fun; } - static bool - getOrCreateCreateArrayFromBufferFunction(JSContext* cx, MutableHandleValue fval) - { - RootedValue cache(cx, cx->global()->createArrayFromBuffer<NativeType>()); - if (cache.isObject()) { - MOZ_ASSERT(cache.toObject().is<JSFunction>()); - fval.set(cache); - return true; - } - - RootedFunction fun(cx); - fun = NewNativeFunction(cx, ArrayBufferObject::createTypedArrayFromBuffer<NativeType>, - 0, nullptr); - if (!fun) - return false; - - cx->global()->setCreateArrayFromBuffer<NativeType>(fun); - - fval.setObject(*fun); - return true; - } - static inline const Class* instanceClass() { return TypedArrayObject::classForType(ArrayTypeID()); } static bool is(HandleValue v) { return v.isObject() && v.toObject().hasClass(instanceClass()); } @@ -478,16 +456,17 @@ class TypedArrayObjectTemplate : public 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); + MOZ_ASSERT_IF(buffer, !buffer->isDetached()); 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. @@ -570,24 +549,16 @@ class TypedArrayObjectTemplate : public 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); - } - - static TypedArrayObject* makeTemplateObject(JSContext* cx, int32_t len) { MOZ_ASSERT(len >= 0); size_t nbytes; MOZ_ALWAYS_TRUE(CalculateAllocSize<NativeType>(len, &nbytes)); MOZ_ASSERT(nbytes < TypedArrayObject::SINGLETON_BYTE_LENGTH); NewObjectKind newKind = TenuredObject; bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT; @@ -730,16 +701,17 @@ class TypedArrayObjectTemplate : public JSObject* obj = create(cx, args); if (!obj) return false; args.rval().setObject(*obj); return true; } + private: static JSObject* create(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(args.isConstructing()); RootedObject newTarget(cx, &args.newTarget().toObject()); /* () or (number) */ uint32_t len = 0; @@ -762,170 +734,301 @@ class TypedArrayObjectTemplate : public * 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]]) */ + // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] ) + + // Step 4. + // 22.2.4.2.1 AllocateTypedArray, step 1. RootedObject proto(cx); if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return nullptr; + if (dataObj->is<ArrayBufferObjectMaybeShared>()) { + HandleArrayBufferObjectMaybeShared buffer = dataObj.as<ArrayBufferObjectMaybeShared>(); + + // WARNING: |buffer| may be detached after calling this function! + uint32_t bufferByteLength; + uint32_t byteOffset; + int32_t length; + if (!getConstructorArgsForBuffer(cx, buffer, args, &bufferByteLength, &byteOffset, + &length)) + { + return nullptr; + } + + return fromBufferSameCompartment(cx, buffer, bufferByteLength, byteOffset, length, + proto); + } + + RootedArrayBufferObjectMaybeShared buffer(cx); + if (!checkedUnwrapArrayBuffer(cx, dataObj, &buffer)) + return nullptr; + + // WARNING: |buffer| may be detached after calling this function! + uint32_t bufferByteLength; + uint32_t byteOffset; + int32_t length; + if (!getConstructorArgsForBuffer(cx, buffer, args, &bufferByteLength, &byteOffset, + &length)) + { + return nullptr; + } + + return fromBufferWrapped(cx, buffer, bufferByteLength, byteOffset, length, proto); + } + + // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 + // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] ) + // Steps 6-9, 11.a. + static bool + getConstructorArgsForBuffer(JSContext* cx, + HandleArrayBufferObjectMaybeShared bufferMaybeUnwrapped, + const CallArgs& args, uint32_t* bufferByteLengthPtr, + uint32_t* byteOffsetPtr, int32_t* lengthPtr) + { int32_t byteOffset = 0; if (args.hasDefined(1)) { + // Step 6. if (!ToInt32(cx, args[1], &byteOffset)) - return nullptr; + return false; if (byteOffset < 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "1"); - return nullptr; + return false; + } + + // Step 7. + if (byteOffset % sizeof(NativeType) != 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); + return false; } } + // Step 8. + if (bufferMaybeUnwrapped->isDetached()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); + return false; + } + + // Step 9. + uint32_t bufferByteLength = bufferMaybeUnwrapped->byteLength(); + int32_t length = -1; if (args.hasDefined(2)) { + // Step 11.a. if (!ToInt32(cx, args[2], &length)) - return nullptr; + return false; if (length < 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "2"); - return nullptr; - } - } - - return fromBufferWithProto(cx, dataObj, byteOffset, length, proto); - } - - public: - static JSObject* - fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt) { - return fromBufferWithProto(cx, bufobj, byteOffset, lengthInt, nullptr); - } - - static JSObject* - fromBufferWithProto(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt, - HandleObject proto) - { - if (bufobj->is<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) { - ReportAccessDenied(cx); - return nullptr; - } - - if (!IsAnyArrayBuffer(wrapped)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); - return nullptr; // must be arrayBuffer + return false; } - /* - * And for even more fun, the new view's prototype should be - * set to the origin compartment's prototype object, not the - * target's (specifically, the actual view in the target - * compartment will use as its prototype a wrapper around the - * origin compartment's view.prototype object). - * - * Rather than hack some crazy solution together, implement - * this all using a private helper function, created when - * ArrayBufferObject was initialized and cached in the global. - * This reuses all the existing cross-compartment crazy so we - * don't have to do anything *uniquely* crazy here. - */ - - RootedObject protoRoot(cx, proto); - if (!protoRoot) { - if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot)) - return nullptr; - } - - FixedInvokeArgs<3> args(cx); - - args[0].setNumber(byteOffset); - args[1].setInt32(lengthInt); - args[2].setObject(*protoRoot); - - RootedValue fval(cx); - if (!getOrCreateCreateArrayFromBufferFunction(cx, &fval)) - return nullptr; - - RootedValue thisv(cx, ObjectValue(*bufobj)); - RootedValue rval(cx); - if (!js::Call(cx, fval, thisv, args, &rval)) - return nullptr; - - return &rval.toObject(); + // Step 11.c (Performed by callers through computeAndCheckLength). } - if (!IsAnyArrayBuffer(bufobj)) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); - return nullptr; // must be arrayBuffer - } + *bufferByteLengthPtr = bufferByteLength; + *byteOffsetPtr = uint32_t(byteOffset); + *lengthPtr = length; + return true; + } - Rooted<ArrayBufferObjectMaybeShared*> buffer(cx); - if (IsArrayBuffer(bufobj)) { - ArrayBufferObject& buf = AsArrayBuffer(bufobj); - if (buf.isDetached()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); - return nullptr; - } - - buffer = static_cast<ArrayBufferObjectMaybeShared*>(&buf); - } else { - buffer = static_cast<ArrayBufferObjectMaybeShared*>(&AsSharedArrayBuffer(bufobj)); - } - - if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) { + // ES2017 draft rev 6390c2f1b34b309895d31d8c0512eac8660a0210 + // 22.2.4.5 TypedArray ( buffer [ , byteOffset [ , length ] ] ) + // Steps 7, 10.a-c, 11.b-c. + static bool + computeAndCheckLength(JSContext* cx, uint32_t bufferByteLength, uint32_t byteOffset, + int32_t lengthInt, uint32_t* length) + { + // Steps 7, 10.c, 11.c. + if (byteOffset > bufferByteLength || byteOffset % sizeof(NativeType) != 0) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); - return nullptr; // invalid byteOffset + return false; // invalid byteOffset } uint32_t len; - if (lengthInt == -1) { - len = (buffer->byteLength() - byteOffset) / sizeof(NativeType); - if (len * sizeof(NativeType) != buffer->byteLength() - byteOffset) { + if (lengthInt < 0) { + // Step 10.b. + uint32_t newByteLength = bufferByteLength - byteOffset; + len = newByteLength / sizeof(NativeType); + + // Step 10.a. + if (len * sizeof(NativeType) != newByteLength) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); - return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N + return false; // given byte array doesn't map exactly to sizeof(NativeType) * N } } else { + // Step 11.b (implicit). len = uint32_t(lengthInt); } + // Step 11.c. // Go slowly and check for overflow. uint32_t arrayByteLength = len * sizeof(NativeType); if (len >= INT32_MAX / sizeof(NativeType) || byteOffset >= INT32_MAX - arrayByteLength) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); - return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType) + return false; // overflow when calculating byteOffset + len * sizeof(NativeType) + } + + // Step 11.c. + if (arrayByteLength + byteOffset > bufferByteLength) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); + return false; // byteOffset + len is too big for the arraybuffer } - if (arrayByteLength + byteOffset > buffer->byteLength()) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, - JSMSG_TYPED_ARRAY_CONSTRUCT_BOUNDS); - return nullptr; // byteOffset + len is too big for the arraybuffer + *length = len; + return true; + } + + static JSObject* + fromBufferSameCompartment(JSContext* cx, + HandleArrayBufferObjectMaybeShared bufferMaybeDetached, + uint32_t bufferByteLength, uint32_t byteOffset, int32_t lengthInt, + HandleObject proto) + { + uint32_t length; + if (!computeAndCheckLength(cx, bufferByteLength, byteOffset, lengthInt, &length)) + return nullptr; + + // The specification allows to create a typed array with a detached + // array buffer (ToIndex in step 11.a can detach the array buffer). We + // don't allow this to happen and instead throw a TypeError. This is + // observable from the user's point of view, but we consider the + // current specification to be incorrect. + if (bufferMaybeDetached->isDetached()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); + return nullptr; + } + MOZ_ASSERT(bufferMaybeDetached->byteLength() == bufferByteLength); + + return makeInstance(cx, bufferMaybeDetached, byteOffset, length, proto); + } + + // Create a TypedArray object in another compartment. + // + // ES6 supports creating a TypedArray in global A (using global A's + // TypedArray constructor) backed by an ArrayBuffer created in global B. + // + // Our TypedArrayObject implementation doesn't support a TypedArray in + // compartment A backed by an ArrayBuffer in compartment B. So in this + // case, we create the TypedArray in B (!) and return a cross-compartment + // wrapper. + // + // Extra twist: the spec says the new TypedArray's [[Prototype]] must be + // A's TypedArray.prototype. So even though we're creating the TypedArray + // in B, its [[Prototype]] must be (a cross-compartment wrapper for) the + // TypedArray.prototype in A. + static JSObject* + fromBufferWrapped(JSContext* cx, + HandleArrayBufferObjectMaybeShared unwrappedBufferMaybeDetached, + uint32_t bufferByteLength, uint32_t byteOffset, int32_t lengthInt, + HandleObject proto) + { + uint32_t length; + if (!computeAndCheckLength(cx, bufferByteLength, byteOffset, lengthInt, &length)) + return nullptr; + + // The specification allows to create a typed array with a detached + // array buffer (ToIndex in step 11.a can detach the array buffer). We + // don't allow this to happen and instead throw a TypeError. This is + // observable from the user's point of view, but we consider the + // current specification to be incorrect. + if (unwrappedBufferMaybeDetached->isDetached()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); + return nullptr; + } + MOZ_ASSERT(unwrappedBufferMaybeDetached->byteLength() == bufferByteLength); + + // Make sure to get the [[Prototype]] for the created typed array from + // this compartment. + RootedObject protoRoot(cx, proto); + if (!protoRoot) { + if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot)) + return nullptr; } - return makeInstance(cx, buffer, byteOffset, len, proto); + RootedObject typedArray(cx); + { + JSAutoCompartment ac(cx, unwrappedBufferMaybeDetached); + + RootedObject wrappedProto(cx, protoRoot); + if (!cx->compartment()->wrap(cx, &wrappedProto)) + return nullptr; + + typedArray = + makeInstance(cx, unwrappedBufferMaybeDetached, byteOffset, length, wrappedProto); + if (!typedArray) + return nullptr; + } + + if (!cx->compartment()->wrap(cx, &typedArray)) + return nullptr; + + return typedArray; + } + + static bool + checkedUnwrapArrayBuffer(JSContext* cx, HandleObject bufobj, + MutableHandleArrayBufferObjectMaybeShared buffer) + { + JSObject* unwrapped = CheckedUnwrap(bufobj); + if (!unwrapped) { + ReportAccessDenied(cx); + return false; + } + + if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return false; + } + + buffer.set(&unwrapped->as<ArrayBufferObjectMaybeShared>()); + return true; + } + + public: + static JSObject* + fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt) + { + if (bufobj->is<ArrayBufferObjectMaybeShared>()) { + HandleArrayBufferObjectMaybeShared buffer = bufobj.as<ArrayBufferObjectMaybeShared>(); + + if (buffer->isDetached()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TYPED_ARRAY_DETACHED); + return nullptr; + } + + return fromBufferSameCompartment(cx, buffer, buffer->byteLength(), byteOffset, + lengthInt, nullptr); + } + + RootedArrayBufferObjectMaybeShared buffer(cx); + if (!checkedUnwrapArrayBuffer(cx, bufobj, &buffer)) + return nullptr; + + if (buffer->isDetached()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); + return nullptr; + } + + return fromBufferWrapped(cx, buffer, buffer->byteLength(), byteOffset, lengthInt, nullptr); } static bool maybeCreateArrayBuffer(JSContext* cx, uint32_t count, uint32_t unit, HandleObject nonDefaultProto, MutableHandle<ArrayBufferObject*> buffer) { if (count >= INT32_MAX / unit) { @@ -1774,48 +1877,16 @@ TypedArrayObject::sharedTypedArrayProtot // draft 20140824 requires.) But this is about as much as we can do // until we implement @@toStringTag. "???", JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray), JS_NULL_CLASS_OPS, &TypedArrayObjectSharedTypedArrayPrototypeClassSpec }; -template<typename T> -bool -ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args) -{ - typedef TypedArrayObjectTemplate<T> ArrayType; - MOZ_ASSERT(IsAnyArrayBuffer(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<IsAnyArrayBuffer, 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 "
--- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -422,33 +422,16 @@ TypedArrayElemSize(Scalar::Type viewType // target[targetOffset + unsafeSrcCrossCompartment.length - 1] = // unsafeSrcCrossCompartment[unsafeSrcCrossCompartment.length - 1] // // where the source element range doesn't overlap the target element range in // memory. extern void SetDisjointTypedElements(TypedArrayObject* target, uint32_t targetOffset, TypedArrayObject* unsafeSrcCrossCompartment); -static inline bool -IsAnyArrayBuffer(HandleObject obj) -{ - return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj); -} - -static inline bool -IsAnyArrayBuffer(JSObject* obj) -{ - return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj); -} - -static inline bool -IsAnyArrayBuffer(HandleValue v) -{ - return v.isObject() && IsAnyArrayBuffer(&v.toObject()); -} } // namespace js template <> inline bool JSObject::is<js::TypedArrayObject>() const { return js::IsTypedArrayClass(getClass());