diff options
Diffstat (limited to 'js/src/tests/non262/JSON/stringify-fastpath.js')
-rw-r--r-- | js/src/tests/non262/JSON/stringify-fastpath.js | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/js/src/tests/non262/JSON/stringify-fastpath.js b/js/src/tests/non262/JSON/stringify-fastpath.js new file mode 100644 index 0000000000..3b1d9e3021 --- /dev/null +++ b/js/src/tests/non262/JSON/stringify-fastpath.js @@ -0,0 +1,212 @@ +// |reftest| skip-if(!xulRuntime.shell) -- not sure why, but invisible errors when running in browser. + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +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); +} |