tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }