Bug 1055472 - Part 14: Make the various TypedArray constructors properly subclassable. (r=Waldo, r=bhackett)

This commit is contained in:
Eric Faust 2015-11-13 18:22:22 -08:00
parent 3f478a674a
commit 98f1fa22e2
6 changed files with 143 additions and 49 deletions

View File

@ -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();

View File

@ -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();
`;

View File

@ -0,0 +1,29 @@
// |reftest| skip-if(!xulRuntime.shell)
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);

View File

@ -1290,10 +1290,21 @@ intrinsic_ConstructorForTypedArray(JSContext* cx, unsigned argc, Value* vp)
RootedObject object(cx, &args[0].toObject());
JSProtoKey protoKey = StandardProtoKeyOrNull(object);
MOZ_ASSERT(protoKey);
RootedValue ctor(cx, cx->global()->getConstructor(protoKey));
MOZ_ASSERT(ctor.isObject());
args.rval().set(ctor);
// While it may seem like an invariant that in any compartment,
// seeing a typed array object implies that the TypedArray constructor
// for that type is initialized on the compartment's global, this is not
// the case. When we construct a typed array given a cross-compartment
// ArrayBuffer, we put the constructed TypedArray in the same compartment
// as the ArrayBuffer. Since we use the prototype from the initial
// compartment, and never call the constructor in the ArrayBuffer's
// compartment from script, we are not guaranteed to have initialized
// the constructor.
RootedObject ctor(cx);
if (!GetBuiltinConstructor(cx, protoKey, &ctor))
return false;
args.rval().setObject(*ctor);
return true;
}

View File

@ -141,6 +141,17 @@ IsAnyTypedArrayClass(const Class* clasp)
return IsTypedArrayClass(clasp) || IsSharedTypedArrayClass(clasp);
}
inline bool
AnyTypedArrayIsDetached(const JSObject* obj)
{
if (obj->is<TypedArrayObject>()) {
ArrayBufferObject* buffer = obj->as<TypedArrayObject>().buffer();
return buffer && buffer->isNeutered();
}
// You cannot detatch a shared array buffer
return false;
}
class SharedOps
{
public:

View File

@ -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
@ -291,17 +305,8 @@ class TypedArrayObjectTemplate : public TypedArrayObject
{
MOZ_ASSERT(proto);
RootedObject obj(cx, NewBuiltinClassInstance(cx, instanceClass(), allocKind));
if (!obj)
return nullptr;
ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, obj->getClass(),
TaggedProto(proto.get()));
if (!group)
return nullptr;
obj->setGroup(group);
return &obj->as<TypedArrayObject>();
JSObject* obj = NewObjectWithClassProto(cx, instanceClass(), proto, allocKind);
return obj ? &obj->as<TypedArrayObject>() : nullptr;
}
static TypedArrayObject*
@ -425,10 +430,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 +457,13 @@ class TypedArrayObjectTemplate : public TypedArrayObject
* shared array's values are copied here.
*/
if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObject>())
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 +487,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
}
return fromBuffer(cx, dataObj, byteOffset, length);
return fromBufferWithProto(cx, dataObj, byteOffset, length, proto);
}
public:
@ -529,9 +541,11 @@ class TypedArrayObjectTemplate : public TypedArrayObject
* don't have to do anything *uniquely* crazy here.
*/
Rooted<JSObject*> 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 +555,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 +570,11 @@ class TypedArrayObjectTemplate : public TypedArrayObject
Rooted<ArrayBufferObject*> 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 +633,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<ArrayBufferObject*> 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 +687,35 @@ struct TypedArrayObject::OfType
template<typename T>
/* static */ JSObject*
TypedArrayObjectTemplate<T>::fromArray(JSContext* cx, HandleObject other)
TypedArrayObjectTemplate<T>::fromArray(JSContext* cx, HandleObject other,
HandleObject newTarget /* = nullptr */)
{
// Allow nullptr newTarget for FriendAPI methods, which don't care about
// subclassing.
RootedObject proto(cx);
uint32_t len;
if (IsAnyTypedArray(other)) {
if (!GetPrototypeForInstance(cx, newTarget, &proto))
return nullptr;
if (AnyTypedArrayIsDetached(other)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return nullptr;
}
len = AnyTypedArrayLength(other);
} else if (!GetLengthProperty(cx, other, &len)) {
return nullptr;
} else {
if (!GetLengthProperty(cx, other, &len))
return nullptr;
if (!GetPrototypeForInstance(cx, newTarget, &proto))
return nullptr;
}
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, len, &buffer))
return nullptr;
Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len));
Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));
if (!obj || !TypedArrayMethods<TypedArrayObject>::setFromArrayLike(cx, obj, other, len))
return nullptr;
return obj;
@ -1699,15 +1738,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<NativeType>::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<NativeType>::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<NativeType>::fromBuffer(cx, arrayBuffer, byteOffset, \
@ -1719,8 +1758,8 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d)
return false; \
const Class* clasp = obj->getClass(); \
return clasp == TypedArrayObjectTemplate<NativeType>::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 +1768,7 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d)
if (clasp == TypedArrayObjectTemplate<NativeType>::instanceClass()) \
return obj; \
return nullptr; \
} \
} \
const js::Class* const js::detail::Name ## ArrayClassPtr = \
&js::TypedArrayObject::classes[TypedArrayObjectTemplate<NativeType>::ArrayTypeID()];