diff --git a/js/src/jit-test/tests/TypedObject/bug976697.js b/js/src/jit-test/tests/TypedObject/bug976697.js deleted file mode 100644 index fa1a5de5517..00000000000 --- a/js/src/jit-test/tests/TypedObject/bug976697.js +++ /dev/null @@ -1,15 +0,0 @@ -// Test that instantiating a typed array on top of a neutered buffer -// doesn't trip any asserts. -// -// Any copyright is dedicated to the Public Domain. -// http://creativecommons.org/licenses/publicdomain/ - -x = new ArrayBuffer(); -neuter(x, "same-data"); -new Uint32Array(x); -gc(); - -x = new ArrayBuffer(); -neuter(x, "change-data"); -new Uint32Array(x); -gc(); diff --git a/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js b/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js index 9b86474758e..d8388ce2a2c 100644 --- a/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js +++ b/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js @@ -14,6 +14,24 @@ function testBuiltin(builtin, ...args) { assertEq(instance.called, true); } +function testBuiltinTypedArrays() { + let typedArrays = [Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array]; + + for (let array of typedArrays) { + testBuiltin(array); + testBuiltin(array, 5); + testBuiltin(array, new array()); + testBuiltin(array, new ArrayBuffer()); + } +} testBuiltin(Function); testBuiltin(Object); @@ -37,6 +55,7 @@ testBuiltin(Set); testBuiltin(WeakMap); testBuiltin(WeakSet); testBuiltin(ArrayBuffer); +testBuiltinTypedArrays(); `; diff --git a/js/src/tests/ecma_6/TypedArray/constructor-non-detached.js b/js/src/tests/ecma_6/TypedArray/constructor-non-detached.js new file mode 100644 index 00000000000..73c9ffb6a72 --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/constructor-non-detached.js @@ -0,0 +1,27 @@ +const constructors = [ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array +]; + +for (var constructor of constructors) { + for (var neuterType of ["change-data", "same-data"]) { + var buf = new constructor(); + neuter(buf.buffer, neuterType); + assertThrowsInstanceOf(()=> new constructor(buf), TypeError); + + var buffer = new ArrayBuffer(); + neuter(buffer, neuterType); + assertThrowsInstanceOf(()=> new constructor(buffer), TypeError); + } +} + + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/TypedArrayCommon.h b/js/src/vm/TypedArrayCommon.h index 14342330256..92b821c58b1 100644 --- a/js/src/vm/TypedArrayCommon.h +++ b/js/src/vm/TypedArrayCommon.h @@ -141,6 +141,17 @@ IsAnyTypedArrayClass(const Class* clasp) return IsTypedArrayClass(clasp) || IsSharedTypedArrayClass(clasp); } +inline bool +AnyTypedArrayIsDetached(const JSObject* obj) +{ + if (obj->is()) { + ArrayBufferObject* buffer = obj->as().buffer(); + return buffer && buffer->isNeutered(); + } + // You cannoy detatch a shared array buffer + return false; +} + class SharedOps { public: diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index de81ea4379f..91dead6202b 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -182,6 +182,20 @@ NewArray(JSContext* cx, uint32_t nelements); namespace { +// We allow nullptr for newTarget for all the creation methods, to allow for +// JSFriendAPI functions that don't care about subclassing +static bool +GetPrototypeForInstance(JSContext* cx, HandleObject newTarget, MutableHandleObject proto) +{ + if (newTarget) { + if (!GetPrototypeFromConstructor(cx, newTarget, proto)) + return false; + } else { + proto.set(nullptr); + } + return true; +} + // Note, this template can probably be merged in part with the one in // SharedTypedArrayObject.cpp once our implementation of // TypedArrayObject is closer to ES6: at the moment, our @@ -425,10 +439,13 @@ class TypedArrayObjectTemplate : public TypedArrayObject static JSObject* create(JSContext* cx, const CallArgs& args) { + MOZ_ASSERT(args.isConstructing()); + RootedObject newTarget(cx, &args.newTarget().toObject()); + /* () or (number) */ uint32_t len = 0; if (args.length() == 0 || ValueIsLength(args[0], &len)) - return fromLength(cx, len); + return fromLength(cx, len, newTarget); /* (not an object) */ if (!args[0].isObject()) { @@ -449,9 +466,13 @@ class TypedArrayObjectTemplate : public TypedArrayObject * shared array's values are copied here. */ if (!UncheckedUnwrap(dataObj)->is()) - return fromArray(cx, dataObj); + return fromArray(cx, dataObj, newTarget); /* (ArrayBuffer, [byteOffset, [length]]) */ + RootedObject proto(cx); + if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) + return nullptr; + int32_t byteOffset = 0; int32_t length = -1; @@ -475,7 +496,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject } } - return fromBuffer(cx, dataObj, byteOffset, length); + return fromBufferWithProto(cx, dataObj, byteOffset, length, proto); } public: @@ -529,9 +550,11 @@ class TypedArrayObjectTemplate : public TypedArrayObject * don't have to do anything *uniquely* crazy here. */ - Rooted proto(cx); - if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &proto)) - return nullptr; + RootedObject protoRoot(cx, proto); + if (!protoRoot) { + if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &protoRoot)) + return nullptr; + } InvokeArgs args(cx); if (!args.init(3)) @@ -541,7 +564,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject args.setThis(ObjectValue(*bufobj)); args[0].setNumber(byteOffset); args[1].setInt32(lengthInt); - args[2].setObject(*proto); + args[2].setObject(*protoRoot); if (!Invoke(cx, args)) return nullptr; @@ -556,6 +579,11 @@ class TypedArrayObjectTemplate : public TypedArrayObject Rooted buffer(cx, &AsArrayBuffer(bufobj)); + if (buffer->isNeutered()) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); + return nullptr; + } + if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); return nullptr; // invalid byteOffset @@ -614,16 +642,21 @@ class TypedArrayObjectTemplate : public TypedArrayObject } static JSObject* - fromLength(JSContext* cx, uint32_t nelements) + fromLength(JSContext* cx, uint32_t nelements, HandleObject newTarget = nullptr) { + RootedObject proto(cx); + if (!GetPrototypeForInstance(cx, newTarget, &proto)) + return nullptr; + Rooted buffer(cx); if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) return nullptr; - return makeInstance(cx, buffer, 0, nelements); + + return makeInstance(cx, buffer, 0, nelements, proto); } static JSObject* - fromArray(JSContext* cx, HandleObject other); + fromArray(JSContext* cx, HandleObject other, HandleObject newTarget = nullptr); static const NativeType getIndex(JSObject* obj, uint32_t index) @@ -663,20 +696,35 @@ struct TypedArrayObject::OfType template /* static */ JSObject* -TypedArrayObjectTemplate::fromArray(JSContext* cx, HandleObject other) +TypedArrayObjectTemplate::fromArray(JSContext* cx, HandleObject other, + HandleObject newTarget /* = nullptr */) { + // Allow nullptr newTarget for FriendAPI methods, which don't care about + // subclassing. + RootedObject proto(cx); + uint32_t len; if (IsAnyTypedArray(other)) { + if (!GetPrototypeForInstance(cx, newTarget, &proto)) + return nullptr; + + if (AnyTypedArrayIsDetached(other)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED); + return nullptr; + } len = AnyTypedArrayLength(other); - } else if (!GetLengthProperty(cx, other, &len)) { - return nullptr; + } else { + if (!GetLengthProperty(cx, other, &len)) + return nullptr; + if (!GetPrototypeForInstance(cx, newTarget, &proto)) + return nullptr; } Rooted buffer(cx); if (!maybeCreateArrayBuffer(cx, len, &buffer)) return nullptr; - Rooted obj(cx, makeInstance(cx, buffer, 0, len)); + Rooted obj(cx, makeInstance(cx, buffer, 0, len, proto)); if (!obj || !TypedArrayMethods::setFromArrayLike(cx, obj, other, len)) return nullptr; return obj; @@ -1699,15 +1747,15 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) */ #define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Name,NativeType) \ - JS_FRIEND_API(JSObject*) JS_New ## Name ## Array(JSContext* cx, uint32_t nelements) \ + 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) \ + 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, \ + JS_FRIEND_API(JSObject*) JS_New ## Name ## ArrayWithBuffer(JSContext* cx, \ HandleObject arrayBuffer, uint32_t byteOffset, int32_t length) \ { \ return TypedArrayObjectTemplate::fromBuffer(cx, arrayBuffer, byteOffset, \ @@ -1719,8 +1767,8 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) return false; \ const Class* clasp = obj->getClass(); \ return clasp == TypedArrayObjectTemplate::instanceClass(); \ - } \ - JS_FRIEND_API(JSObject*) js::Unwrap ## Name ## Array(JSObject* obj) \ + } \ + JS_FRIEND_API(JSObject*) js::Unwrap ## Name ## Array(JSObject* obj) \ { \ obj = CheckedUnwrap(obj); \ if (!obj) \ @@ -1729,7 +1777,7 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) if (clasp == TypedArrayObjectTemplate::instanceClass()) \ return obj; \ return nullptr; \ - } \ + } \ const js::Class* const js::detail::Name ## ArrayClassPtr = \ &js::TypedArrayObject::classes[TypedArrayObjectTemplate::ArrayTypeID()];