stringify-fastpath.js (6565B)
1 // |reftest| skip-if(!xulRuntime.shell) -- not sure why, but invisible errors when running in browser. 2 3 /* 4 * Any copyright is dedicated to the Public Domain. 5 * https://creativecommons.org/publicdomain/zero/1.0/ 6 */ 7 8 if (typeof JSONStringify === "undefined") { 9 var JSONStringify = SpecialPowers.Cu.getJSTestingFunctions().JSONStringify; 10 } 11 12 var report = this.console?.error || this.printErr; 13 14 report("BUGNUMBER: 1837410"); 15 16 function compare(obj) { 17 let slow; 18 try { slow = JSONStringify(obj, "SlowOnly"); } catch (e) { slow = "" + e; } 19 let maybeFast; 20 try { maybeFast = JSONStringify(obj, "Normal"); } catch (e) { maybeFast = "" + e; } 21 if (slow !== maybeFast) { 22 report(`Slow:\n${slow}\nFast:\n${maybeFast}`); 23 return 1; 24 } 25 return 0; 26 } 27 28 function newBigInt(val = 7n) { 29 let result; 30 function grabThis() { result = this; } 31 grabThis.call(BigInt(val)); 32 return result; 33 } 34 35 function testBothPaths() { 36 let failures = 0; 37 failures += compare([, 3, undefined, ,]); 38 failures += compare({ a: 1, b: undefined }); 39 failures += compare({ a: undefined, b: 1 }); 40 failures += compare({ a: 1, b: Symbol("fnord") }); 41 42 const obj = {}; 43 obj.first = true; 44 obj[0] = 'a'; 45 obj[1] = undefined; 46 obj[2] = 'b'; 47 obj.last = true; 48 failures += compare(obj); 49 50 const cyclic = {}; 51 cyclic.one = 1; 52 cyclic.two = { me: cyclic }; 53 failures += compare(cyclic); 54 55 const sparse = [10, 20, 30]; 56 sparse[1000] = 40; 57 failures += compare(sparse); 58 59 const arr = [10, 20, 30]; 60 arr.other = true; 61 failures += compare(arr); 62 63 const arr2 = new Array(5); 64 arr2[1] = 'hello'; 65 arr2[3] = 'world'; 66 failures += compare(arr2); 67 68 const big = { p1: 1, p2: 2, p3: 3, p4: 4, p5: 5, p6: 6, p7: 7, p8: 8, p9: 9 }; 69 failures += compare(big); 70 71 failures += compare(new Number(3)); 72 failures += compare(new Boolean(true)); 73 failures += compare(Number.NaN); 74 failures += compare(undefined); 75 failures += compare({ x: () => 1 }); 76 77 failures += compare({ x: newBigInt() }); 78 79 const sparse2 = []; 80 Object.defineProperty(sparse2, "0", { value: 7, enumerable: false }); 81 failures += compare(sparse2); 82 83 return failures; 84 } 85 86 function checkFast(value, expectWhySlow) { 87 let whySlow; 88 let json; 89 try { 90 json = JSONStringify(value, "FastOnly"); 91 } catch (e) { 92 const m = e.message.match(/failed mandatory fast path: (\S+)/); 93 if (!m) { 94 report("Expected fast path fail, got " + e); 95 return 1; 96 } 97 whySlow = m[1]; 98 } 99 100 if (expectWhySlow) { 101 if (!whySlow) { 102 report("Expected to bail out of fast path but unexpectedly succeeded"); 103 report((new Error).stack); 104 report(json); 105 return 1; 106 } else if (whySlow != expectWhySlow) { 107 report(`Expected to bail out of fast path because ${expectWhySlow} but bailed because ${whySlow}`); 108 return 1; 109 } 110 } else { 111 if (whySlow) { 112 report("Expected fast path to succeed, bailed because: " + whySlow); 113 return 1; // Fail 114 } 115 } 116 117 return 0; 118 } 119 120 function testFastPath() { 121 let failures = 0; 122 failures += checkFast({}); 123 failures += checkFast([]); 124 failures += checkFast({ x: true }); 125 failures += checkFast([, , 10, ,]); 126 failures += checkFast({ x: undefined }); 127 failures += checkFast({ x: Symbol() }); 128 failures += checkFast({ x: new Set([10,20,30]) }); 129 130 failures += checkFast("primitive", "PRIMITIVE"); 131 failures += checkFast(true, "PRIMITIVE"); 132 failures += checkFast(7, "PRIMITIVE"); 133 134 failures += checkFast({ x: new Uint8Array(3) }, "INELIGIBLE_OBJECT"); 135 failures += checkFast({ x: new Number(3) }, "INELIGIBLE_OBJECT"); 136 failures += checkFast({ x: new Boolean(true) }, "INELIGIBLE_OBJECT"); 137 failures += checkFast({ x: newBigInt(3) }, "INELIGIBLE_OBJECT"); 138 failures += checkFast(Number.NaN, "PRIMITIVE"); 139 failures += checkFast(undefined, "PRIMITIVE"); 140 141 // Array has enumerated indexed + non-indexed slots. 142 const nonElements = []; 143 Object.defineProperty(nonElements, 0, { value: "hi", enumerated: true, configurable: true }); 144 nonElements.named = 7; 145 failures += checkFast(nonElements, "INELIGIBLE_OBJECT"); 146 147 nonElements.splice(0); 148 failures += checkFast(nonElements); 149 150 // Array's prototype has indexed slot and/or inherited element. 151 const proto = {}; 152 Object.defineProperty(proto, "0", { value: 1, enumerable: false }); 153 const holy = [, , 3]; 154 Object.setPrototypeOf(holy, proto); 155 failures += checkFast(holy, "INELIGIBLE_OBJECT"); 156 Object.setPrototypeOf(holy, { 1: true }); 157 failures += checkFast(holy, "INELIGIBLE_OBJECT"); 158 159 // This is probably redundant with one of the above, but it was 160 // found by a fuzzer at one point. 161 const accessorProto = Object.create(Array.prototype); 162 Object.defineProperty(accessorProto, "0", { 163 get() { return 2; }, set() { } 164 }); 165 const child = []; 166 Object.setPrototypeOf(child, accessorProto); 167 child.push(1); 168 failures += checkFast(child, "INELIGIBLE_OBJECT"); 169 170 failures += checkFast({ get x() { return 1; } }, "NON_DATA_PROPERTY"); 171 172 const self = {}; 173 self.self = self; 174 failures += checkFast(self, "DEEP_RECURSION"); 175 const ouroboros = ['head', 'middle', []]; 176 let p = ouroboros[2]; 177 let middle; 178 for (let i = 0; i < 1000; i++) { 179 p.push('middle', 'middle'); 180 p = p[2] = []; 181 if (i == 10) { 182 middle = p; 183 } 184 } 185 failures += checkFast(ouroboros, "DEEP_RECURSION"); // Acyclic but deep 186 p[2] = ouroboros; 187 failures += checkFast(ouroboros, "DEEP_RECURSION"); // Cyclic and deep 188 middle[2] = ouroboros; 189 failures += checkFast(ouroboros, "DEEP_RECURSION"); // Cyclic after 10 recursions 190 191 failures += checkFast({ 0: true, 1: true, 10000: true }, "INELIGIBLE_OBJECT"); 192 const arr = [1, 2, 3]; 193 arr[10000] = 4; 194 failures += checkFast(arr, "INELIGIBLE_OBJECT"); 195 196 failures += checkFast({ x: 12n }, "BIGINT"); 197 198 failures += checkFast({ x: new Date() }, "HAVE_TOJSON"); 199 failures += checkFast({ toJSON() { return "json"; } }, "HAVE_TOJSON"); 200 const custom = { toJSON() { return "value"; } }; 201 failures += checkFast(Object.create(custom), "HAVE_TOJSON"); 202 203 return failures; 204 } 205 206 let failures = testBothPaths() + testFastPath(); 207 208 if (typeof reportCompare === "function") { 209 reportCompare(0, failures); 210 } else { 211 assertEq(failures, 0); 212 }