Skip to content

Commit

Permalink
Bug 1761989 - Part 1: Remove species lookup from InitializeTypedArray…
Browse files Browse the repository at this point in the history
…FromTypedArray. r=mgaudet

Implement the changes from <tc39/ecma262#2677> and
<tc39/ecma262#2719>.

The next patches will perform further clean-ups.

Differential Revision: https://phabricator.services.mozilla.com/D152262
  • Loading branch information
anba committed Jul 21, 2022
1 parent 3e93e9c commit f554a1f
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 188 deletions.
3 changes: 0 additions & 3 deletions js/src/tests/jstests.list
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,6 @@ fails-if(!xulRuntime.shell) script test262/language/statements/class/subclass-bu
# https://bugzilla.mozilla.org/show_bug.cgi?id=1648202
skip script test262/built-ins/RegExp/named-groups/non-unicode-property-names-valid.js

# https://bugzilla.mozilla.org/show_bug.cgi?id=1761989
skip script test262/built-ins/TypedArrayConstructors/ctors/no-species.js


###########################################################
# Tests disabled due to issues in test262 importer script #
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,44 @@ for (let ctor of typedArrayConstructors) {
for (let ctor2 of typedArrayConstructors) {
let b = new ctor2(a);
assertEq(Object.getPrototypeOf(b).constructor, ctor2);
assertEq(Object.getPrototypeOf(b.buffer).constructor, g.ArrayBuffer);
assertEq(Object.getPrototypeOf(b.buffer).constructor, ArrayBuffer);
}
}

// Only ArrayBuffer from different global.
let called = false;
let origSpecies = Object.getOwnPropertyDescriptor(ArrayBuffer, Symbol.species);
let modSpecies = {
get() {
called = true;
return g.ArrayBuffer;
throw new Error("unexpected @@species access");
}
};
for (let ctor of typedArrayConstructors) {
let a = new ctor([1, 2, 3, 4, 5]);
for (let ctor2 of typedArrayConstructors) {
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);
assertEq(Object.getPrototypeOf(b.buffer).constructor, 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;
throw new Error("unexpected @@species access");
}
};
`);
for (let ctor of typedArrayConstructors) {
let a = g.eval(`new ${ctor.name}([1, 2, 3, 4, 5]);`);
for (let ctor2 of typedArrayConstructors) {
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);
}
Expand Down
38 changes: 3 additions & 35 deletions js/src/tests/non262/TypedArray/constructor-ArrayBuffer-species.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,17 @@
let logs = [];
for (let ctor of typedArrayConstructors) {
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;
throw new Error("unexpected @@species access");
}
};

for (let ctor2 of typedArrayConstructors) {
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"]);
assertEq(Object.getPrototypeOf(arr2.buffer), ArrayBuffer.prototype);
assertEq(arr2.buffer.constructor, ArrayBuffer);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,85 +5,29 @@

const thisGlobal = this;
const otherGlobal = newGlobal();
const ta_i32 = otherGlobal.eval("new Int32Array(0)");

function assertBufferPrototypeFrom(newTypedArray, prototype) {
var typedArrayName = newTypedArray.constructor.name;
const typedArrays = [otherGlobal.eval("new Int32Array(0)")];

assertEq(Object.getPrototypeOf(newTypedArray), thisGlobal[typedArrayName].prototype);
assertEq(Object.getPrototypeOf(newTypedArray.buffer), prototype);
}

const EMPTY = {};

// Test SpeciesConstructor() implementation selects the correct (fallback) constructor.
const testCases = [
// Create the array buffer from the species constructor.
{ constructor: EMPTY, prototype: otherGlobal.ArrayBuffer.prototype },

// Use %ArrayBuffer% from this global if constructor is undefined.
{ constructor: undefined, prototype: ArrayBuffer.prototype },

// Use %ArrayBuffer% from this global if species is undefined.
{ constructor: {[Symbol.species]: undefined}, prototype: ArrayBuffer.prototype },

// Use %ArrayBuffer% from this global if species is null.
{ constructor: {[Symbol.species]: null}, prototype: ArrayBuffer.prototype },
];

for (let { constructor, prototype } of testCases) {
if (constructor !== EMPTY) {
ta_i32.buffer.constructor = constructor;
}

// Same element type.
assertBufferPrototypeFrom(new Int32Array(ta_i32), prototype);

// Different element type.
assertBufferPrototypeFrom(new Int16Array(ta_i32), prototype);
}


// Also ensure TypeErrors are thrown from the correct global.
const errorTestCases = [
// Constructor property is neither undefined nor an object.
{ constructor: null },
{ constructor: 123 },

// Species property is neither undefined/null nor a constructor function.
{ constructor: { [Symbol.species]: 123 } },
{ constructor: { [Symbol.species]: [] } },
{ constructor: { [Symbol.species]: () => {} } },
];

for (let { constructor } of errorTestCases) {
ta_i32.buffer.constructor = constructor;

// Same element type.
assertThrowsInstanceOf(() => new Int32Array(ta_i32), TypeError);

// Different element type.
assertThrowsInstanceOf(() => new Int32Array(ta_i32), TypeError);
}


// TypedArrays using SharedArrayBuffers never call the SpeciesConstructor operation.
if (this.SharedArrayBuffer) {
const ta_i32_shared = otherGlobal.eval("new Int32Array(new SharedArrayBuffer(0))");
typedArrays.push(otherGlobal.eval("new Int32Array(new SharedArrayBuffer(0))"));
}

Object.defineProperty(ta_i32_shared.buffer, "constructor", {
for (let typedArray of typedArrays) {
// Ensure the "constructor" property isn't accessed.
Object.defineProperty(typedArray.buffer, "constructor", {
get() {
throw new Error("constructor property accessed");
}
});

// Same element type.
assertBufferPrototypeFrom(new Int32Array(ta_i32_shared), ArrayBuffer.prototype);
for (let ctor of typedArrayConstructors) {
let newTypedArray = new ctor(typedArray);

// Different element type.
assertBufferPrototypeFrom(new Int16Array(ta_i32_shared), ArrayBuffer.prototype);
assertEq(Object.getPrototypeOf(newTypedArray), ctor.prototype);
assertEq(Object.getPrototypeOf(newTypedArray.buffer), ArrayBuffer.prototype);
assertEq(newTypedArray.buffer.constructor, ArrayBuffer);
}
}


if (typeof reportCompare === "function")
reportCompare(0, 0);
72 changes: 2 additions & 70 deletions js/src/vm/TypedArrayObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,6 @@ static TypedArrayObject* NewTypedArrayObject(JSContext* cx,
return &obj->as<TypedArrayObject>();
}

enum class SpeciesConstructorOverride { None, ArrayBuffer };

template <typename NativeType>
class TypedArrayObjectTemplate : public TypedArrayObject {
friend class TypedArrayObject;
Expand Down Expand Up @@ -1141,67 +1139,6 @@ template <typename T>
return true;
}

static bool IsArrayBufferSpecies(JSContext* cx, JSFunction* species) {
return IsSelfHostedFunctionWithName(species, cx->names().ArrayBufferSpecies);
}

static JSObject* GetBufferSpeciesConstructor(
JSContext* cx, Handle<TypedArrayObject*> typedArray, bool isWrapped,
SpeciesConstructorOverride override) {
RootedObject defaultCtor(
cx, GlobalObject::getOrCreateArrayBufferConstructor(cx, cx->global()));
if (!defaultCtor) {
return nullptr;
}

// Use the current global's ArrayBuffer if the override is set.
if (override == SpeciesConstructorOverride::ArrayBuffer) {
return defaultCtor;
}

RootedObject obj(cx, typedArray->bufferEither());
if (!obj) {
MOZ_ASSERT(!isWrapped);

// The buffer was never exposed to content code, so if
// 1. %ArrayBufferPrototype%.constructor == %ArrayBuffer%, and
// 2. %ArrayBuffer%[@@species] == ArrayBufferSpecies
// we don't have to reify the buffer object and can simply return the
// default array buffer constructor.

JSObject* proto =
GlobalObject::getOrCreateArrayBufferPrototype(cx, cx->global());
if (!proto) {
return nullptr;
}

Value ctor;
bool found;
if (GetOwnPropertyPure(cx, proto, NameToId(cx->names().constructor), &ctor,
&found) &&
ctor.isObject() && &ctor.toObject() == defaultCtor) {
jsid speciesId = PropertyKey::Symbol(cx->wellKnownSymbols().species);
JSFunction* getter;
if (GetOwnGetterPure(cx, defaultCtor, speciesId, &getter) && getter &&
IsArrayBufferSpecies(cx, getter)) {
return defaultCtor;
}
}

if (!TypedArrayObject::ensureHasBuffer(cx, typedArray)) {
return nullptr;
}

obj.set(typedArray->bufferEither());
} else {
if (isWrapped && !cx->compartment()->wrap(cx, &obj)) {
return nullptr;
}
}

return SpeciesConstructor(cx, obj, defaultCtor, IsArrayBufferSpecies);
}

template <typename T>
/* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromArray(
JSContext* cx, HandleObject other, HandleObject proto /* = nullptr */) {
Expand Down Expand Up @@ -1271,13 +1208,8 @@ template <typename T>
// Steps 10-15 (skipped).

// Steps 16-17.
bool isShared = srcArray->isSharedMemory();
SpeciesConstructorOverride override =
isShared ? SpeciesConstructorOverride::ArrayBuffer
: SpeciesConstructorOverride::None;

RootedObject bufferCtor(
cx, GetBufferSpeciesConstructor(cx, srcArray, isWrapped, override));
cx, GlobalObject::getOrCreateArrayBufferConstructor(cx, cx->global()));
if (!bufferCtor) {
return nullptr;
}
Expand Down Expand Up @@ -1316,7 +1248,7 @@ template <typename T>

// Steps 19.c-f or 24.1.1.4 steps 5-7.
MOZ_ASSERT(!obj->isSharedMemory());
if (isShared) {
if (srcArray->isSharedMemory()) {
if (!ElementSpecific<T, SharedOps>::setFromTypedArray(obj, srcArray, 0)) {
return nullptr;
}
Expand Down

0 comments on commit f554a1f

Please sign in to comment.