Source code
Revision control
Copy as Markdown
Other Tools
// |reftest| skip-if(!xulRuntime.shell) -- not sure why, but invisible errors when running in browser.
/*
* Any copyright is dedicated to the Public Domain.
*/
if (typeof JSONStringify === "undefined") {
var JSONStringify = SpecialPowers.Cu.getJSTestingFunctions().JSONStringify;
}
var report = this.console?.error || this.printErr;
report("BUGNUMBER: 1837410");
function compare(obj) {
let slow;
try { slow = JSONStringify(obj, "SlowOnly"); } catch (e) { slow = "" + e; }
let maybeFast;
try { maybeFast = JSONStringify(obj, "Normal"); } catch (e) { maybeFast = "" + e; }
if (slow !== maybeFast) {
report(`Slow:\n${slow}\nFast:\n${maybeFast}`);
return 1;
}
return 0;
}
function newBigInt(val = 7n) {
let result;
function grabThis() { result = this; }
grabThis.call(BigInt(val));
return result;
}
function testBothPaths() {
let failures = 0;
failures += compare([, 3, undefined, ,]);
failures += compare({ a: 1, b: undefined });
failures += compare({ a: undefined, b: 1 });
failures += compare({ a: 1, b: Symbol("fnord") });
const obj = {};
obj.first = true;
obj[0] = 'a';
obj[1] = undefined;
obj[2] = 'b';
obj.last = true;
failures += compare(obj);
const cyclic = {};
cyclic.one = 1;
cyclic.two = { me: cyclic };
failures += compare(cyclic);
const sparse = [10, 20, 30];
sparse[1000] = 40;
failures += compare(sparse);
const arr = [10, 20, 30];
arr.other = true;
failures += compare(arr);
const arr2 = new Array(5);
arr2[1] = 'hello';
arr2[3] = 'world';
failures += compare(arr2);
const big = { p1: 1, p2: 2, p3: 3, p4: 4, p5: 5, p6: 6, p7: 7, p8: 8, p9: 9 };
failures += compare(big);
failures += compare(new Number(3));
failures += compare(new Boolean(true));
failures += compare(Number.NaN);
failures += compare(undefined);
failures += compare({ x: () => 1 });
failures += compare({ x: newBigInt() });
const sparse2 = [];
Object.defineProperty(sparse2, "0", { value: 7, enumerable: false });
failures += compare(sparse2);
return failures;
}
function checkFast(value, expectWhySlow) {
let whySlow;
let json;
try {
json = JSONStringify(value, "FastOnly");
} catch (e) {
const m = e.message.match(/failed mandatory fast path: (\S+)/);
if (!m) {
report("Expected fast path fail, got " + e);
return 1;
}
whySlow = m[1];
}
if (expectWhySlow) {
if (!whySlow) {
report("Expected to bail out of fast path but unexpectedly succeeded");
report((new Error).stack);
report(json);
return 1;
} else if (whySlow != expectWhySlow) {
report(`Expected to bail out of fast path because ${expectWhySlow} but bailed because ${whySlow}`);
return 1;
}
} else {
if (whySlow) {
report("Expected fast path to succeed, bailed because: " + whySlow);
return 1; // Fail
}
}
return 0;
}
function testFastPath() {
let failures = 0;
failures += checkFast({});
failures += checkFast([]);
failures += checkFast({ x: true });
failures += checkFast([, , 10, ,]);
failures += checkFast({ x: undefined });
failures += checkFast({ x: Symbol() });
failures += checkFast({ x: new Set([10,20,30]) });
failures += checkFast("primitive", "PRIMITIVE");
failures += checkFast(true, "PRIMITIVE");
failures += checkFast(7, "PRIMITIVE");
failures += checkFast({ x: new Uint8Array(3) }, "INELIGIBLE_OBJECT");
failures += checkFast({ x: new Number(3) }, "INELIGIBLE_OBJECT");
failures += checkFast({ x: new Boolean(true) }, "INELIGIBLE_OBJECT");
failures += checkFast({ x: newBigInt(3) }, "INELIGIBLE_OBJECT");
failures += checkFast(Number.NaN, "PRIMITIVE");
failures += checkFast(undefined, "PRIMITIVE");
// Array has enumerated indexed + non-indexed slots.
const nonElements = [];
Object.defineProperty(nonElements, 0, { value: "hi", enumerated: true, configurable: true });
nonElements.named = 7;
failures += checkFast(nonElements, "INELIGIBLE_OBJECT");
nonElements.splice(0);
failures += checkFast(nonElements);
// Array's prototype has indexed slot and/or inherited element.
const proto = {};
Object.defineProperty(proto, "0", { value: 1, enumerable: false });
const holy = [, , 3];
Object.setPrototypeOf(holy, proto);
failures += checkFast(holy, "INELIGIBLE_OBJECT");
Object.setPrototypeOf(holy, { 1: true });
failures += checkFast(holy, "INELIGIBLE_OBJECT");
// This is probably redundant with one of the above, but it was
// found by a fuzzer at one point.
const accessorProto = Object.create(Array.prototype);
Object.defineProperty(accessorProto, "0", {
get() { return 2; }, set() { }
});
const child = [];
Object.setPrototypeOf(child, accessorProto);
child.push(1);
failures += checkFast(child, "INELIGIBLE_OBJECT");
failures += checkFast({ get x() { return 1; } }, "NON_DATA_PROPERTY");
const self = {};
self.self = self;
failures += checkFast(self, "DEEP_RECURSION");
const ouroboros = ['head', 'middle', []];
let p = ouroboros[2];
let middle;
for (let i = 0; i < 1000; i++) {
p.push('middle', 'middle');
p = p[2] = [];
if (i == 10) {
middle = p;
}
}
failures += checkFast(ouroboros, "DEEP_RECURSION"); // Acyclic but deep
p[2] = ouroboros;
failures += checkFast(ouroboros, "DEEP_RECURSION"); // Cyclic and deep
middle[2] = ouroboros;
failures += checkFast(ouroboros, "DEEP_RECURSION"); // Cyclic after 10 recursions
failures += checkFast({ 0: true, 1: true, 10000: true }, "INELIGIBLE_OBJECT");
const arr = [1, 2, 3];
arr[10000] = 4;
failures += checkFast(arr, "INELIGIBLE_OBJECT");
failures += checkFast({ x: 12n }, "BIGINT");
failures += checkFast({ x: new Date() }, "HAVE_TOJSON");
failures += checkFast({ toJSON() { return "json"; } }, "HAVE_TOJSON");
const custom = { toJSON() { return "value"; } };
failures += checkFast(Object.create(custom), "HAVE_TOJSON");
return failures;
}
let failures = testBothPaths() + testFastPath();
if (typeof reportCompare === "function") {
reportCompare(0, failures);
} else {
assertEq(failures, 0);
}