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

This commit is contained in:
Eric Faust 2015-11-13 18:22:22 -08:00
parent e4d13c5d69
commit 6f08f54062
5 changed files with 125 additions and 35 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,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);

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 cannoy 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
@ -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<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 +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<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 +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<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 +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<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 +696,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 +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<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 +1767,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 +1777,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()];