Bug 1165053 - Part 7: Call SpeciesConstructor in TypedArray ctors. r=lth

This commit is contained in:
Tooru Fujisawa 2016-03-05 18:57:51 +09:00
parent b97c87c62f
commit 671b135804
6 changed files with 374 additions and 17 deletions

View File

@ -0,0 +1,41 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
const sharedConstructors = [];
if (typeof SharedArrayBuffer != "undefined")
sharedConstructors.push(sharedConstructor(Int8Array),
sharedConstructor(Uint8Array),
sharedConstructor(Int16Array),
sharedConstructor(Uint16Array),
sharedConstructor(Int32Array),
sharedConstructor(Uint32Array),
sharedConstructor(Float32Array),
sharedConstructor(Float64Array));
var g = newGlobal();
var arr = [1, 2, 3];
for (var constructor of constructors.concat(sharedConstructors)) {
var tarr = new constructor(arr);
for (var constructor2 of constructors) {
var copied = new constructor2(tarr);
assertEq(copied.buffer.byteLength, arr.length * constructor2.BYTES_PER_ELEMENT);
g.tarr = tarr;
copied = g.eval(`new ${constructor2.name}(tarr);`);
assertEq(copied.buffer.byteLength, arr.length * constructor2.BYTES_PER_ELEMENT);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,75 @@
// |reftest| skip-if(!xulRuntime.shell)
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
let g = newGlobal();
// Both TypedArray and ArrayBuffer from different global.
for (let ctor of constructors) {
let a = g.eval(`new ${ctor.name}([1, 2, 3, 4, 5]);`);
for (let ctor2 of constructors) {
let b = new ctor2(a);
assertEq(Object.getPrototypeOf(b).constructor, ctor2);
assertEq(Object.getPrototypeOf(b.buffer).constructor, g.ArrayBuffer);
}
}
// Only ArrayBuffer from different global.
let called = false;
let origSpecies = Object.getOwnPropertyDescriptor(ArrayBuffer, Symbol.species);
let modSpecies = {
get() {
called = true;
return g.ArrayBuffer;
}
};
for (let ctor of constructors) {
let a = new ctor([1, 2, 3, 4, 5]);
for (let ctor2 of constructors) {
called = false;
Object.defineProperty(ArrayBuffer, Symbol.species, modSpecies);
let b = new ctor2(a);
Object.defineProperty(ArrayBuffer, Symbol.species, origSpecies);
assertEq(called, true);
assertEq(Object.getPrototypeOf(b).constructor, ctor2);
assertEq(Object.getPrototypeOf(b.buffer).constructor, g.ArrayBuffer);
}
}
// Only TypedArray from different global.
g.otherArrayBuffer = ArrayBuffer;
g.eval(`
var called = false;
var origSpecies = Object.getOwnPropertyDescriptor(ArrayBuffer, Symbol.species);
var modSpecies = {
get() {
called = true;
return otherArrayBuffer;
}
};
`);
for (let ctor of constructors) {
let a = g.eval(`new ${ctor.name}([1, 2, 3, 4, 5]);`);
for (let ctor2 of constructors) {
g.called = false;
g.eval(`Object.defineProperty(ArrayBuffer, Symbol.species, modSpecies);`);
let b = new ctor2(a);
g.eval(`Object.defineProperty(ArrayBuffer, Symbol.species, origSpecies);`);
assertEq(g.called, true);
assertEq(Object.getPrototypeOf(b).constructor, ctor2);
assertEq(Object.getPrototypeOf(b.buffer).constructor, ArrayBuffer);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -0,0 +1,63 @@
const constructors = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
let logs = [];
for (let ctor of constructors) {
let arr = new ctor([1, 2, 3, 4, 5, 6, 7, 8]);
let ctorObj = {};
let proxyProto = new Proxy({}, {
get(that, name) {
logs.push("get proto." + String(name));
if (name == "constructor")
return ctorObj;
throw new Error("unexpected prop access");
}
});
arr.buffer.constructor = {
get [Symbol.species]() {
logs.push("get @@species");
let C = new Proxy(function(...args) {
logs.push("call ctor");
return new ArrayBuffer(...args);
}, {
get(that, name) {
logs.push("get ctor." + String(name));
if (name == "prototype") {
return proxyProto;
}
throw new Error("unexpected prop access");
}
});
return C;
}
};
for (let ctor2 of constructors) {
logs.length = 0;
let arr2 = new ctor2(arr);
assertDeepEq(logs, ["get @@species", "get ctor.prototype"]);
logs.length = 0;
assertEq(Object.getPrototypeOf(arr2.buffer), proxyProto);
assertDeepEq(logs, []);
logs.length = 0;
assertEq(arr2.buffer.constructor, ctorObj);
assertDeepEq(logs, ["get proto.constructor"]);
}
}
if (typeof reportCompare === "function")
reportCompare(true, true);

View File

@ -27,6 +27,14 @@ ArrayBufferObjectMaybeShared::dataPointerEither()
return buf->as<SharedArrayBufferObject>().dataPointerShared();
}
inline bool
ArrayBufferObjectMaybeShared::isDetached() const
{
if (this->is<ArrayBufferObject>())
return this->as<ArrayBufferObject>().isDetached();
return false;
}
inline uint32_t
AnyArrayBufferByteLength(const ArrayBufferObjectMaybeShared* buf)
{

View File

@ -80,6 +80,8 @@ class ArrayBufferObjectMaybeShared : public NativeObject
return AnyArrayBufferByteLength(this);
}
inline bool isDetached() const;
inline SharedMem<uint8_t*> dataPointerEither();
};

View File

@ -635,12 +635,13 @@ class TypedArrayObjectTemplate : public TypedArrayObject
}
static bool
maybeCreateArrayBuffer(JSContext* cx, uint32_t nelements, MutableHandle<ArrayBufferObject*> buffer)
maybeCreateArrayBuffer(JSContext* cx, uint32_t nelements, HandleObject nonDefaultProto,
MutableHandle<ArrayBufferObject*> buffer)
{
static_assert(INLINE_BUFFER_LIMIT % sizeof(NativeType) == 0,
"ArrayBuffer inline storage shouldn't waste any space");
if (nelements <= INLINE_BUFFER_LIMIT / sizeof(NativeType)) {
if (!nonDefaultProto && nelements <= INLINE_BUFFER_LIMIT / sizeof(NativeType)) {
// The array's data can be inline, and the buffer created lazily.
return true;
}
@ -651,7 +652,8 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return false;
}
ArrayBufferObject* buf = ArrayBufferObject::create(cx, nelements * sizeof(NativeType));
ArrayBufferObject* buf = ArrayBufferObject::create(cx, nelements * sizeof(NativeType),
nonDefaultProto);
if (!buf)
return false;
@ -667,17 +669,26 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr;
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, nelements, &buffer))
if (!maybeCreateArrayBuffer(cx, nelements, nullptr, &buffer))
return nullptr;
return makeInstance(cx, buffer, 0, nelements, proto);
}
static bool
AllocateArrayBuffer(JSContext* cx, HandleValue ctor, uint32_t elementLength,
MutableHandle<ArrayBufferObject*> buffer);
static bool
CloneArrayBufferNoCopy(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> srcBuffer,
bool isWrapped, uint32_t srcByteOffset,
MutableHandle<ArrayBufferObject*> buffer);
static JSObject*
fromArray(JSContext* cx, HandleObject other, HandleObject newTarget = nullptr);
static JSObject*
fromTypedArray(JSContext* cx, HandleObject other, HandleObject newTarget);
fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped, HandleObject newTarget);
static JSObject*
fromObject(JSContext* cx, HandleObject other, HandleObject newTarget);
@ -718,6 +729,107 @@ struct TypedArrayObject::OfType
typedef TypedArrayObjectTemplate<T> Type;
};
// ES 2016 draft Mar 25, 2016 24.1.1.1.
template<typename T>
/* static */ bool
TypedArrayObjectTemplate<T>::AllocateArrayBuffer(JSContext* cx, HandleValue ctor,
uint32_t elementLength,
MutableHandle<ArrayBufferObject*> buffer)
{
// ES 2016 draft Mar 25, 2016 24.1.1.1 step 1 (partially).
// ES 2016 draft Mar 25, 2016 9.1.14 steps 1-2.
MOZ_ASSERT(ctor.isObject());
RootedObject proto(cx);
RootedObject ctorObj(cx, &ctor.toObject());
if (!GetPrototypeFromConstructor(cx, ctorObj, &proto))
return false;
JSObject* arrayBufferProto = GlobalObject::getOrCreateArrayBufferPrototype(cx, cx->global());
if (!arrayBufferProto)
return false;
if (proto == arrayBufferProto)
proto = nullptr;
// ES 2016 draft Mar 25, 2016 24.1.1.1 steps 1 (remaining part), 2-6.
if (!maybeCreateArrayBuffer(cx, elementLength, proto, buffer))
return false;
return true;
}
static bool
GetSpeciesConstructor(JSContext* cx, HandleObject obj, bool isWrapped, MutableHandleValue ctor)
{
if (!isWrapped) {
if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer))
return false;
RootedValue defaultCtor(cx, cx->global()->getConstructor(JSProto_ArrayBuffer));
if (!SpeciesConstructor(cx, obj, defaultCtor, ctor))
return false;
return true;
}
{
JSAutoCompartment ac(cx, obj);
if (!GlobalObject::ensureConstructor(cx, cx->global(), JSProto_ArrayBuffer))
return false;
RootedValue defaultCtor(cx, cx->global()->getConstructor(JSProto_ArrayBuffer));
if (!SpeciesConstructor(cx, obj, defaultCtor, ctor))
return false;
}
return JS_WrapValue(cx, ctor);
}
// ES 2016 draft Mar 25, 2016 24.1.1.4.
template<typename T>
/* static */ bool
TypedArrayObjectTemplate<T>::CloneArrayBufferNoCopy(JSContext* cx,
Handle<ArrayBufferObjectMaybeShared*> srcBuffer,
bool isWrapped, uint32_t srcByteOffset,
MutableHandle<ArrayBufferObject*> buffer)
{
// Step 1 (skipped).
// Step 2.a.
RootedValue cloneCtor(cx);
if (!GetSpeciesConstructor(cx, srcBuffer, isWrapped, &cloneCtor))
return false;
// Step 2.b.
if (srcBuffer->isDetached()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
// Step 3 (skipped).
// Steps 4-5.
uint32_t srcLength = srcBuffer->byteLength();
MOZ_ASSERT(srcByteOffset <= srcLength);
// Step 6.
uint32_t cloneLength = srcLength - srcByteOffset;
// Step 7 (skipped).
// Steps 8.
MOZ_ASSERT(cloneLength % BYTES_PER_ELEMENT == 0);
if (!AllocateArrayBuffer(cx, cloneCtor, cloneLength / BYTES_PER_ELEMENT, buffer))
return false;
// Step 9.
if (srcBuffer->isDetached()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
// Steps 10-11 (done in caller).
// Step 12.
return true;
}
template<typename T>
/* static */ JSObject*
TypedArrayObjectTemplate<T>::fromArray(JSContext* cx, HandleObject other,
@ -726,7 +838,10 @@ TypedArrayObjectTemplate<T>::fromArray(JSContext* cx, HandleObject other,
// Allow nullptr newTarget for FriendAPI methods, which don't care about
// subclassing.
if (other->is<TypedArrayObject>())
return fromTypedArray(cx, other, newTarget);
return fromTypedArray(cx, other, /* wrapped= */ false, newTarget);
if (other->is<WrapperObject>() && UncheckedUnwrap(other)->is<TypedArrayObject>())
return fromTypedArray(cx, other, /* wrapped= */ true, newTarget);
return fromObject(cx, other, newTarget);
}
@ -734,11 +849,14 @@ TypedArrayObjectTemplate<T>::fromArray(JSContext* cx, HandleObject other,
// ES 2016 draft Mar 25, 2016 22.2.4.3.
template<typename T>
/* static */ JSObject*
TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other,
TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, bool isWrapped,
HandleObject newTarget)
{
// Step 1.
MOZ_ASSERT(other->is<TypedArrayObject>());
MOZ_ASSERT_IF(!isWrapped, other->is<TypedArrayObject>());
MOZ_ASSERT_IF(isWrapped,
other->is<WrapperObject>() &&
UncheckedUnwrap(other)->is<TypedArrayObject>());
// Step 2 (done in caller).
@ -748,10 +866,30 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other,
return nullptr;
// Step 5.
Rooted<TypedArrayObject*> srcArray(cx, &other->as<TypedArrayObject>());
Rooted<TypedArrayObject*> srcArray(cx);
if (!isWrapped) {
srcArray = &other->as<TypedArrayObject>();
if (!TypedArrayObject::ensureHasBuffer(cx, srcArray))
return nullptr;
} else {
RootedObject unwrapped(cx, CheckedUnwrap(other));
if (!unwrapped) {
JS_ReportError(cx, "Permission denied to access object");
return nullptr;
}
// Steps 6-7.
if (srcArray->hasDetachedBuffer()) {
JSAutoCompartment ac(cx, unwrapped);
srcArray = &unwrapped->as<TypedArrayObject>();
if (!TypedArrayObject::ensureHasBuffer(cx, srcArray))
return nullptr;
}
// Step 6.
Rooted<ArrayBufferObjectMaybeShared*> srcData(cx, srcArray->bufferEither());
// Step 7.
if (srcData->isDetached()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return nullptr;
}
@ -759,12 +897,41 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other,
// Steps 10.
uint32_t elementLength = srcArray->length();
// Steps 8-9, 11-18.
Rooted<ArrayBufferObject*> buffer(cx);
if (!maybeCreateArrayBuffer(cx, elementLength, &buffer))
return nullptr;
// Steps 11-12.
Scalar::Type srcType = srcArray->type();
// Steps 3, 4 (remaining part), 19-23.
// Step 13 (skipped).
// Step 14.
uint32_t srcByteOffset = srcArray->byteOffset();
// Steps 15-16 (skipped).
// Our AllocateArrayBuffer receives elementLength instead of byteLength.
// Steps 8-9, 17.
Rooted<ArrayBufferObject*> buffer(cx);
if (ArrayTypeID() == srcType) {
// Step 17.a.
if (!CloneArrayBufferNoCopy(cx, srcData, isWrapped, srcByteOffset, &buffer))
return nullptr;
} else {
// Step 18.a.
RootedValue bufferCtor(cx);
if (!GetSpeciesConstructor(cx, srcData, isWrapped, &bufferCtor))
return nullptr;
// Step 18.b.
if (!AllocateArrayBuffer(cx, bufferCtor, elementLength, &buffer))
return nullptr;
// Step 18.c.
if (srcArray->hasDetachedBuffer()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return nullptr;
}
}
// Steps 3, 4 (remaining part), 19-22.
Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, elementLength, proto));
if (!obj)
return nullptr;
@ -773,6 +940,7 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other,
if (!TypedArrayMethods<TypedArrayObject>::setFromTypedArray(cx, obj, srcArray))
return nullptr;
// Step 23.
return obj;
}
@ -790,7 +958,7 @@ TypedArrayObjectTemplate<T>::fromObject(JSContext* cx, HandleObject other, Handl
return nullptr;
if (!GetPrototypeForInstance(cx, newTarget, &proto))
return nullptr;
if (!maybeCreateArrayBuffer(cx, len, &buffer))
if (!maybeCreateArrayBuffer(cx, len, nullptr, &buffer))
return nullptr;
Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));