summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/JSON/stringify-fastpath.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/non262/JSON/stringify-fastpath.js')
-rw-r--r--js/src/tests/non262/JSON/stringify-fastpath.js212
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);
+}