mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-26 03:35:33 +00:00
Bug 1165053 - Part 7: Call SpeciesConstructor in TypedArray ctors. r=lth
This commit is contained in:
parent
b97c87c62f
commit
671b135804
@ -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);
|
@ -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);
|
@ -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);
|
@ -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)
|
||||
{
|
||||
|
@ -80,6 +80,8 @@ class ArrayBufferObjectMaybeShared : public NativeObject
|
||||
return AnyArrayBufferByteLength(this);
|
||||
}
|
||||
|
||||
inline bool isDetached() const;
|
||||
|
||||
inline SharedMem<uint8_t*> dataPointerEither();
|
||||
};
|
||||
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user