blob: 249da7137be8f45b2940e4691e9e8712e0f69bed [file] [log] [blame] [edit]
function assert(cond, msg) {
if (!cond) throw new Error(msg ?? "assertion failed");
}
// ---- Correctness: spec edge cases ----
// Non-cell primitives
assert(Array.isArray(undefined) === false, "undefined");
assert(Array.isArray(null) === false, "null");
assert(Array.isArray(true) === false, "true");
assert(Array.isArray(false) === false, "false");
assert(Array.isArray(0) === false, "0");
assert(Array.isArray(-0) === false, "-0");
assert(Array.isArray(NaN) === false, "NaN");
assert(Array.isArray(Infinity) === false, "Infinity");
assert(Array.isArray(1.5) === false, "1.5");
assert(Array.isArray(42n) === false, "BigInt");
// Cell non-arrays
assert(Array.isArray("string") === false, "string");
assert(Array.isArray({}) === false, "plain object");
assert(Array.isArray(function () { }) === false, "function");
assert(Array.isArray(/regex/) === false, "regexp");
assert(Array.isArray(new Map()) === false, "Map");
assert(Array.isArray(new Set()) === false, "Set");
assert(Array.isArray(Symbol()) === false, "Symbol");
// Arrays
assert(Array.isArray([]) === true, "empty array");
assert(Array.isArray([1, 2, 3]) === true, "array literal");
assert(Array.isArray(new Array(5)) === true, "new Array");
// Derived arrays (subclasses — DerivedArrayType)
class MyArray extends Array { }
assert(Array.isArray(new MyArray()) === true, "derived array");
assert(Array.isArray(new MyArray(3)) === true, "derived array with size");
// Zero-argument call -> false (spec: arg is undefined)
assert(Array.isArray() === false, "no args");
// Proxy wrapping an array -> true (spec: IsArray recurses through proxy)
const proxiedArray = new Proxy([], {});
assert(Array.isArray(proxiedArray) === true, "Proxy(array)");
// Proxy wrapping a non-array -> false
const proxiedObj = new Proxy({}, {});
assert(Array.isArray(proxiedObj) === false, "Proxy(object)");
// Nested proxy -> true (spec recurses)
const doubleProxied = new Proxy(new Proxy([], {}), {});
assert(Array.isArray(doubleProxied) === true, "Proxy(Proxy(array))");
// Revoked proxy -> throws TypeError
const { proxy: revokedProxy, revoke } = Proxy.revocable([], {});
revoke();
let threw = false;
try { Array.isArray(revokedProxy); } catch (e) { threw = e instanceof TypeError; }
assert(threw, "revoked proxy must throw TypeError");
// ---- Tier tests ----
// Each function is called enough times to force up through the tiers.
// Mixing non-cell and cell inputs in the same hot function was the bug trigger.
// LLInt / Baseline (no DFG)
function testMixedNoDFG(v) {
return Array.isArray(v);
}
noDFG(testMixedNoDFG);
noFTL(testMixedNoDFG);
for (let i = 0; i < 1000; i++) {
assert(testMixedNoDFG(undefined) === false);
assert(testMixedNoDFG([]) === true);
assert(testMixedNoDFG(null) === false);
assert(testMixedNoDFG(true) === false);
assert(testMixedNoDFG({}) === false);
}
// DFG (no FTL): mixed types must not cause BadType exits / jettisons
function testMixedDFG(v) {
return Array.isArray(v);
}
noFTL(testMixedDFG);
const mixed = [undefined, [], null, false, {}, new MyArray(), 42, "str", new Proxy([], {})];
for (let i = 0; i < 10000; i++) {
const result = testMixedDFG(mixed[i % mixed.length]);
assert(typeof result === "boolean", `DFG: expected boolean for input index ${i % mixed.length}`);
}
// DFG: known-array input — abstract interpreter should fold to constant true
function testKnownArray(arr) {
return Array.isArray(arr);
}
noFTL(testKnownArray);
noInline(testKnownArray);
for (let i = 0; i < 10000; i++) {
assert(testKnownArray([1, 2, 3]) === true, "DFG constant fold: known array");
}
// DFG: known non-cell — abstract interpreter should fold to constant false
function testKnownNonCell(x) {
return Array.isArray(x);
}
noFTL(testKnownNonCell);
noInline(testKnownNonCell);
for (let i = 0; i < 10000; i++) {
assert(testKnownNonCell(i) === false, "DFG constant fold: known number");
}
// FTL: hot mixed-type loop — must compile stably without exits
function testFTLMixed(v) {
return Array.isArray(v);
}
noInline(testFTLMixed);
const ftlInputs = [[], new MyArray(), null, undefined, false, {}, 0, "s"];
for (let i = 0; i < 200000; i++) {
testFTLMixed(ftlInputs[i & 7]);
}
assert(testFTLMixed([]) === true, "FTL: array");
assert(testFTLMixed(new MyArray()) === true, "FTL: derived array");
assert(testFTLMixed(null) === false, "FTL: null");
assert(testFTLMixed(undefined) === false, "FTL: undefined");
assert(testFTLMixed({}) === false, "FTL: plain object");
// FTL: known-array constant folding in hot path
function testFTLKnownArray(arr) {
return Array.isArray(arr);
}
noInline(testFTLKnownArray);
const arrInput = [1];
for (let i = 0; i < 200000; i++) {
assert(testFTLKnownArray(arrInput) === true, "FTL constant fold: known array");
}
// FTL: proxy slow path reachable at FTL tier
function testFTLProxy(v) {
return Array.isArray(v);
}
noInline(testFTLProxy);
const proxyArr = new Proxy([], {});
for (let i = 0; i < 200000; i++) {
// Warm with arrays (fast path), occasionally hit the proxy slow path
testFTLProxy(i % 100 === 0 ? proxyArr : []);
}
assert(testFTLProxy(proxyArr) === true, "FTL proxy slow path returns true");
assert(testFTLProxy(new Proxy({}, {})) === false, "FTL proxy slow path returns false");