mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 21:00:50 +00:00
Bug 1055472 - Part 14: Make the various TypedArray constructors properly subclassable. (r=Waldo, r=bhackett)
This commit is contained in:
parent
3f478a674a
commit
98f1fa22e2
@ -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();
|
@ -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();
|
||||
|
||||
`;
|
||||
|
||||
|
29
js/src/tests/ecma_6/TypedArray/constructor-non-detached.js
Normal file
29
js/src/tests/ecma_6/TypedArray/constructor-non-detached.js
Normal 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);
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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()];
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user