tor-browser

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

debugger-reject-after-fulfill.js (5487B)


      1 // Throwing error after resolving async function's promise should not
      2 // overwrite the promise's state or value/reason.
      3 // This situation can happen either with debugger interaction or OOM.
      4 
      5 // This testcase relies on the fact that there's a breakpoint after resolving
      6 // the async function's promise, before leaving the async function's frame.
      7 // This function searches for the last breakpoint before leaving the frame.
      8 //
      9 //  - `declCode` should declare an async function `f`, and the function should
     10 //    set global variable `returning` to `true` just before return
     11 //  - `callCode` should call the function `f` and make sure the function's
     12 //    execution reaches the last breakpoint
     13 function searchLastBreakpointBeforeReturn(declCode, callCode) {
     14  const g = newGlobal({ newCompartment: true });
     15  const dbg = new Debugger(g);
     16  g.eval(declCode);
     17 
     18  let hit = false;
     19  let offset = 0;
     20  dbg.onEnterFrame = function(frame) {
     21    if (frame.callee && frame.callee.name == "f") {
     22      frame.onStep = () => {
     23        if (!g.returning) {
     24          return undefined;
     25        }
     26 
     27        offset = frame.offset;
     28        return undefined;
     29      };
     30    }
     31  };
     32 
     33  g.eval(callCode);
     34 
     35  drainJobQueue();
     36 
     37  assertEq(offset != 0, true);
     38 
     39  return offset;
     40 }
     41 
     42 function testWithoutAwait() {
     43  const declCode = `
     44  var returning = false;
     45  async function f() {
     46    return (returning = true, "expected");
     47  };
     48  `;
     49 
     50  const callCode = `
     51  var p = f();
     52  `;
     53 
     54  const offset = searchLastBreakpointBeforeReturn(declCode, callCode);
     55 
     56  const g = newGlobal({ newCompartment: true });
     57  const dbg = new Debugger(g);
     58  g.eval(declCode);
     59 
     60  let onPromiseSettledCount = 0;
     61  dbg.onPromiseSettled = function(promise) {
     62    onPromiseSettledCount++;
     63    // No promise should be rejected.
     64    assertEq(promise.promiseState, "fulfilled");
     65 
     66    // Async function's promise should have expected value.
     67    if (onPromiseSettledCount == 1) {
     68      assertEq(promise.promiseValue, "expected");
     69    }
     70  };
     71 
     72  let hitBreakpoint = false;
     73  dbg.onEnterFrame = function(frame) {
     74    if (frame.callee && frame.callee.name == "f") {
     75      dbg.onEnterFrame = undefined;
     76      frame.script.setBreakpoint(offset, {
     77        hit() {
     78          hitBreakpoint = true;
     79          return { throw: "unexpected" };
     80        }
     81      });
     82    }
     83  };
     84 
     85  enableLastWarning();
     86 
     87  g.eval(`
     88  var fulfilledValue;
     89  var rejected = false;
     90  `);
     91 
     92  g.eval(callCode);
     93 
     94  // The execution reaches to the last breakpoint without running job queue.
     95  assertEq(hitBreakpoint, true);
     96 
     97  const warn = getLastWarning();
     98  assertEq(warn.message,
     99           "unhandlable error after resolving async function's promise");
    100  clearLastWarning();
    101 
    102  // Add reaction handler after resolution.
    103  // This handler's job will be enqueued immediately.
    104  g.eval(`
    105  p.then(x => {
    106    fulfilledValue = x;
    107  }, e => {
    108    rejected = true;
    109  });
    110  `);
    111 
    112  // Run the above handler.
    113  drainJobQueue();
    114 
    115  assertEq(g.fulfilledValue, "expected");
    116  assertEq(onPromiseSettledCount >= 1, true);
    117 }
    118 
    119 function testWithAwait() {
    120  const declCode = `
    121  var resolve;
    122  var p = new Promise(r => { resolve = r });
    123  var returning = false;
    124  async function f() {
    125    await p;
    126    return (returning = true, "expected");
    127  };
    128  `;
    129 
    130  const callCode = `
    131  var p = f();
    132  `;
    133 
    134  const resolveCode = `
    135  resolve("resolve");
    136  `;
    137 
    138  const offset = searchLastBreakpointBeforeReturn(declCode,
    139                                                  callCode + resolveCode);
    140 
    141  const g = newGlobal({newCompartment: true});
    142  const dbg = new Debugger(g);
    143  g.eval(declCode);
    144 
    145  let onPromiseSettledCount = 0;
    146  dbg.onPromiseSettled = function(promise) {
    147    onPromiseSettledCount++;
    148 
    149    // No promise should be rejected.
    150    assertEq(promise.promiseState, "fulfilled");
    151 
    152    // Async function's promise should have expected value.
    153    if (onPromiseSettledCount == 2) {
    154      assertEq(promise.promiseValue, "expected");
    155    }
    156  };
    157 
    158  let hitBreakpoint = false;
    159  dbg.onEnterFrame = function(frame) {
    160    if (frame.callee && frame.callee.name == "f") {
    161      dbg.onEnterFrame = undefined;
    162      frame.script.setBreakpoint(offset, {
    163        hit() {
    164          hitBreakpoint = true;
    165          return { throw: "unexpected" };
    166        }
    167      });
    168    }
    169  };
    170 
    171  enableLastWarning();
    172 
    173  g.eval(`
    174  var fulfilledValue1;
    175  var fulfilledValue2;
    176  var rejected = false;
    177  `);
    178 
    179  g.eval(callCode);
    180 
    181  assertEq(getLastWarning(), null);
    182 
    183  // Add reaction handler before resolution.
    184  // This handler's job will be enqueued when `p` is resolved.
    185  g.eval(`
    186  p.then(x => {
    187    fulfilledValue1 = x;
    188  }, e => {
    189    rejected = true;
    190  });
    191  `);
    192 
    193  g.eval(resolveCode);
    194 
    195  // Run the remaining part of async function, and the above handler.
    196  drainJobQueue();
    197 
    198  // The execution reaches to the last breakpoint after running job queue for
    199  // resolving `p`.
    200  assertEq(hitBreakpoint, true);
    201 
    202  const warn = getLastWarning();
    203  assertEq(warn.message,
    204           "unhandlable error after resolving async function's promise");
    205  clearLastWarning();
    206 
    207  assertEq(g.fulfilledValue1, "expected");
    208  assertEq(g.rejected, false);
    209 
    210  // Add reaction handler after resolution.
    211  // This handler's job will be enqueued immediately.
    212  g.eval(`
    213  p.then(x => {
    214    fulfilledValue2 = x;
    215  }, e => {
    216    rejected = true;
    217  });
    218  `);
    219 
    220  // Run the above handler.
    221  drainJobQueue();
    222 
    223  assertEq(g.fulfilledValue2, "expected");
    224  assertEq(g.rejected, false);
    225  assertEq(onPromiseSettledCount >= 3, true);
    226 }
    227 
    228 testWithoutAwait();
    229 testWithAwait();