tor-browser

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

calls.js (15110B)


      1 // -----------------------------------------------------------------------------
      2 // This file contains tests checking Wasm functions with throwing functions and
      3 // try-catch code involving more complex control flow, testing that multiple
      4 // values returned from calls in try code are not affected by multiple branching
      5 // towards the landing pad, as well as making sure exceptions carrying multiple
      6 // values of any Wasm numtype transport the exception values correctly across
      7 // calls.
      8 //
      9 // There are tests for local direct calls, for imported direct calls, for
     10 // indirect calls in a local table with local functions, for indirect calls in a
     11 // local table of imported functions, and for indirect calls in an imported
     12 // table of imported functions.
     13 //
     14 // - TODO: Add reftype values, when support for reftypes in exceptions is
     15 //   implemented.
     16 // -----------------------------------------------------------------------------
     17 
     18 load(libdir + "eqArrayHelper.js");
     19 
     20 // All individual tests take a string 'localThrow' as argument, and will be run
     21 // with each element of the result of the following function.
     22 
     23 function generateLocalThrows(types, baseThrow) {
     24  // Arguments:
     25  // - 'types': A string of space separated Wasm types.
     26  // - 'baseThrow': A string with a Wasm instruction sequence of Wasm functype
     27  //                `[${types}]-> [t*], which takes `types` arguments and ends
     28  //                up throwing the tag '$exn'.
     29  // Result:
     30  // - A JS array of strings, each representing a Wasm instruction sequence
     31  //   which is like `baseThrow', i.e., a Wasm instruction sequence of Wasm
     32  //   functype `[${types}]-> [t*], which takes `types` arguments and ends up
     33  //   throwing the tag '$exn'. The result does not include 'baseThrow'.
     34  //
     35  // All strings in Wasm text format.
     36 
     37  // Basic throws;
     38  let catchlessTryThrow =
     39      `try (param ${types})
     40         ${baseThrow}
     41       end`;
     42 
     43  let catchlessThrowExnref =
     44      `try_table (param ${types})
     45         ${baseThrow}
     46       end`;
     47 
     48  let catchAndThrow =
     49      `try (param ${types})
     50         ${baseThrow}
     51       catch $exn
     52         ${baseThrow}
     53       catch_all
     54       end`;
     55 
     56  let catchAndThrowExnref =
     57      `(block $join (param ${types})
     58          (block $catch (param ${types}) (result ${types})
     59            (block $catchAll (param ${types})
     60              try_table (param ${types}) (catch $exn $catch) (catch_all $catchAll)
     61                ${baseThrow}
     62                unreachable
     63              end
     64            )
     65            br $join
     66          )
     67          ${baseThrow}
     68       )`;
     69 
     70  let blockThrow =
     71      `(block (param ${types})
     72         ${baseThrow})`;
     73 
     74  // This Wasm code requires that the function it appears in has an i32 local
     75  // with name "$ifPredicate".
     76  let conditionalThrow =
     77      `(if (param ${types})
     78         (local.get $ifPredicate)
     79         (then ${baseThrow})
     80         (else ${baseThrow}))`;
     81 
     82  // Including try-delegate.
     83  let baseDelegate =
     84      `try (param ${types})
     85        ${baseThrow}
     86       delegate 0`;
     87 
     88  // Delegate just outside the block.
     89  let nestedDelegate1InBlock =
     90      `(block $label1 (param ${types})
     91         try (param ${types})
     92           ${baseThrow}
     93         delegate $label1)`;
     94 
     95  let basicThrows = [catchlessTryThrow, blockThrow, conditionalThrow,
     96                     baseDelegate, nestedDelegate1InBlock, catchlessThrowExnref, catchAndThrowExnref];
     97 
     98  // Secondary throws (will end up inside a catch block).
     99 
    100  let baseRethrow =
    101      `(rethrow 0)`;
    102 
    103  let nestedRethrow =
    104      `try (param ${types})
    105         ${baseThrow}
    106       catch $exn
    107         (rethrow 1)
    108       catch_all
    109         (rethrow 0)
    110       end`;
    111 
    112  let catchAllRethrowOriginal =
    113      `try (param ${types})
    114         ${baseThrow}
    115       catch_all
    116         (rethrow 1)
    117       end`;
    118 
    119  let secondaryThrows =
    120      [].concat(basicThrows,
    121                [baseRethrow, nestedRethrow, catchAllRethrowOriginal]);
    122 
    123  // Nestings.
    124 
    125  function basicNesting (basicThrow, secondaryThrow) {
    126    return `try (param ${types})
    127              ${basicThrow}
    128            catch $exn
    129              ${secondaryThrow}
    130            catch_all
    131            end`;
    132  };
    133 
    134  let result = [];
    135 
    136  for (let basicThrow of basicThrows) {
    137    result.push(basicThrow);
    138    let isExnref = basicThrow == catchlessThrowExnref || basicThrow == catchAndThrowExnref;
    139    for (let secondaryThrow of secondaryThrows) {
    140      let isRethrow = secondaryThrow == baseRethrow || secondaryThrow == nestedRethrow || secondaryThrow == catchAllRethrowOriginal;
    141      if (isExnref && isRethrow) {
    142        continue;
    143      }
    144      result.push(basicNesting(basicThrow, secondaryThrow));
    145    }
    146  }
    147 
    148  return result;
    149 };
    150 
    151 {
    152  // Some variables to be used in all tests.
    153  let typesJS = ["i32", "i64", "f32", "f64", "externref"];
    154  let types = typesJS.join(" ");
    155 
    156  // The following depend on whether simd is enabled or not. We write it like
    157  // this so we can run this test also when SIMD is not enabled.
    158  let exntype = "";
    159  let wrongV128 = "";
    160  let throwV128 = "";
    161  let checkV128Value = "";
    162 
    163  if (wasmSimdEnabled()) {
    164    exntype = types + " v128";
    165    wrongV128 = `(v128.const i32x4 11 22 33 44)`;
    166    throwV128 = `(v128.const i32x4 55 66 77 88)`;
    167    checkV128Value = `;; Check the V128 value
    168             ${throwV128}
    169             (i32x4.eq)
    170             (i32x4.all_true)`;
    171  } else {
    172    exntype = types + " i32";
    173    wrongV128 = "(i32.const 0)";
    174    throwV128 = "(i32.const 1)";
    175    checkV128Value = "";
    176  }
    177 
    178  let exnTypeDef = `(type $exnType (func (param ${exntype})))`;
    179 
    180  let throwValues =
    181      `;; Values to throw.
    182               (i32.const 2)
    183               (i64.const 3)
    184               (f32.const 4)
    185               (f64.const 13.37)
    186               (local.get $correctRef)
    187               ${throwV128}`;
    188 
    189  // The last 1 is the result of the test that the v128 value is correct, done
    190  // in wasm code (if simd is enabled).
    191  let correctResultsJS = [2, 3n, 4, 13.37, "foo", 1];
    192 
    193  let wrongValues =
    194      `;; Wrong values
    195                    (i32.const 5)
    196                    (i64.const 6)
    197                    (f32.const 0.1)
    198                    (f64.const 0.6437)
    199                    (local.get $wrongRef)
    200                    ${wrongV128}`;
    201 
    202  // The individual tests. -----------------------------------------------------
    203 
    204  function testDirectCallsThrowing(localThrow) {
    205    // Test direct function calls throwing any numeric value.
    206 
    207    let throwifTypeInline =
    208        // The result of the "throwif" function will be used as an argument the
    209        // second time "throwif" is called.
    210        `(param $ifPredicate i32) (param $correctRef externref) (result i32)`;
    211 
    212    let moduleHeaderThrowif =
    213        `(module
    214           ${exnTypeDef}
    215           (tag $exn (export "exn") (type $exnType))
    216           (func $throwif (export "throwif") ${throwifTypeInline}
    217             (if
    218               (local.get $ifPredicate)
    219               (then
    220                 ${throwValues}
    221                 ${localThrow}))
    222             (i32.const 1))`;
    223 
    224    let testModuleRest =
    225        `(tag $notThrownExn)
    226         (func $doNothing)
    227         (func (export "testFunc") (param $correctRef externref)
    228                                   (param $wrongRef externref)
    229                                   (result ${types} i32)
    230                                   (local $ifPredicate i32)
    231           (local.get $ifPredicate)
    232           try (param i32) (result ${exntype})
    233             (local.get $wrongRef)
    234             (call $throwif) ;; Returns 1.
    235             (call $doNothing) ;; Does nothing.
    236             (local.get $correctRef)
    237             (call $throwif) ;; Throws $exn.
    238             (drop)
    239             ${wrongValues} ;; Won't reach this point.
    240             ${localThrow}
    241             unreachable
    242           catch $notThrownExn
    243             ${wrongValues}
    244           catch $exn
    245           end
    246           ${checkV128Value}))`;
    247 
    248    function testDirectLocalCallsThrowing() {
    249      let mod = moduleHeaderThrowif + testModuleRest;
    250      // console.log("DIRECT LOCAL MOD = " + mod);  // Uncomment for debugging.
    251 
    252      assertEqArray(wasmEvalText(mod).exports.testFunc("foo", "bar"),
    253                    correctResultsJS);
    254    };
    255 
    256    function testDirectImportedCallsThrowing() {
    257      let exports = wasmEvalText(moduleHeaderThrowif + `)`).exports;
    258      // Uncomment for debugging.
    259      //console.log("DIRECT EXPORTS = " + moduleHeaderThrowif + ")");
    260 
    261      let mod =
    262          `(module
    263             ${exnTypeDef}
    264             (import "m" "exn" (tag $exn (type $exnType)))
    265             (import "m" "throwif" (func $throwif ${throwifTypeInline}))` +
    266          testModuleRest;
    267      // console.log("DIRECT IMPORT MOD = " + mod); // Uncomment for debugging.
    268 
    269      assertEqArray(
    270        wasmEvalText(mod, { m : exports}).exports.testFunc("foo", "bar"),
    271        correctResultsJS);
    272    };
    273 
    274    testDirectLocalCallsThrowing();
    275    testDirectImportedCallsThrowing();
    276  };
    277 
    278  function testIndirectCallsThrowing(localThrow) {
    279    // Test indirect calls throwing exceptions.
    280 
    281    let indirectFunctypeInline = `(param ${exntype})
    282                                  (result ${exntype})`;
    283    let getIndirectArgs = `(local.get 0) ;; i32
    284             (local.get 1) ;; i64
    285             (local.get 2) ;; f32
    286             (local.get 3) ;; f64
    287             (local.get 4) ;; ref
    288             ;; v128
    289             (local.get 5)`;
    290 
    291    let testFunctypeInline = `(param $correctRef externref)
    292                                     (param $wrongRef externref)
    293                                     ;; The last i32 result is the v128 check.
    294                                     (result ${types} i32)`;
    295 
    296    let moduleHeader =
    297        `(module
    298           ${exnTypeDef}
    299           (type $indirectFunctype (func ${indirectFunctypeInline}))
    300           (tag $exn (export "exn") (type $exnType))
    301           (tag $emptyExn (export "emptyExn"))
    302           (func $throwExn (export "throwExn") ${indirectFunctypeInline}
    303                                               (local $ifPredicate i32)
    304             ${getIndirectArgs}
    305             ${localThrow}
    306             unreachable)
    307           (func $throwEmptyExn (export "throwEmptyExn")
    308                                ${indirectFunctypeInline}
    309             (throw $emptyExn)
    310             unreachable)
    311           (func $returnArgs (export "returnArgs") ${indirectFunctypeInline}
    312             ${getIndirectArgs})
    313           (table (export "tab") funcref (elem $throwExn       ;; 0
    314                                               $throwEmptyExn  ;; 1
    315                                               $returnArgs))   ;; 2
    316           `;
    317 
    318    // The main test function happens to have the same Wasm functype as the
    319    // indirect calls.
    320    let testFuncHeader = `(func (export "testFunc") ${testFunctypeInline}
    321                                     (local $ifPredicate i32)
    322             `;
    323 
    324    // To test indirect calls to a local table of local functions
    325    function moduleIndirectLocalLocal(functionBody) {
    326      return moduleHeader + testFuncHeader + functionBody + `))`;
    327    };
    328 
    329    let exports = wasmEvalText(moduleHeader + ")").exports;
    330    // Uncomment for debugging.
    331    //console.log("INDIRECT EXPORTS = " + moduleHeader + ")");
    332 
    333    let moduleHeaderImporting =
    334        `(module
    335           ${exnTypeDef}
    336           (type $indirectFunctype (func ${indirectFunctypeInline}))
    337           (import "m" "exn" (tag $exn (type $exnType)))
    338           (import "m" "emptyExn" (tag $emptyExn))
    339           (import "m" "throwExn" (func $throwExn (type $indirectFunctype)))
    340           (import "m" "throwEmptyExn"
    341                   (func $throwEmptyExn (type $indirectFunctype)))
    342           (import "m" "returnArgs"
    343                   (func $returnArgs (type $indirectFunctype)))`;
    344 
    345    // To test indirect calls to a local table of imported functions.
    346    function moduleIndirectLocalImport(functionBody) {
    347      return moduleHeaderImporting +
    348        `(table funcref (elem $throwExn $throwEmptyExn $returnArgs))` +
    349        testFuncHeader + functionBody + `))`;
    350    };
    351 
    352    // To test indirect calls to an imported table of imported functions.
    353    function moduleIndirectImportImport(functionBody) {
    354      return moduleHeaderImporting +
    355        `(import "m" "tab" (table 3 funcref))` +
    356        testFuncHeader + functionBody + `))`;
    357    };
    358 
    359    function getModuleTextsForFunctionBody(functionBody) {
    360      return [moduleIndirectLocalLocal(functionBody),
    361              moduleIndirectLocalImport(functionBody),
    362              moduleIndirectImportImport(functionBody)];
    363    };
    364 
    365    // The function bodies for the tests.
    366 
    367    // Three indirect calls, the middle of which throws and will be caught. The
    368    // results of the first and second indirect calls are used by the next
    369    // indirect call. This should be called from try code, to check that the
    370    // pad-branches don't interfere with the results of each call.
    371    let indirectThrow = `${throwValues}
    372               (call_indirect (type $indirectFunctype) (i32.const 2)) ;; returnArgs
    373               (call_indirect (type $indirectFunctype) (i32.const 0)) ;; throwExn
    374               drop drop ;; Drop v128 and externref to do trivial and irrelevant ops.
    375               (f64.const 5)
    376               (f64.add)
    377               (local.get $wrongRef)
    378               ${wrongV128}
    379               ;; throwEmptyExn
    380               (call_indirect (type $indirectFunctype) (i32.const 1))
    381               unreachable`;
    382 
    383    // Simple try indirect throw and catch.
    384    let simpleTryIndirect = `
    385            try (result ${exntype})
    386              ${indirectThrow}
    387              catch $emptyExn
    388                 ${wrongValues}
    389              catch $exn
    390              catch_all
    391                 ${wrongValues}
    392            end
    393            ${checkV128Value}`;
    394 
    395    // Indirect throw/catch_all, with a simple try indirect throw nested in the
    396    // catch_all.
    397    let nestedTryIndirect =
    398        `try (result ${types} i32)
    399           ${wrongValues}
    400           ;; throwEmptyExn
    401           (call_indirect (type $indirectFunctype) (i32.const 1))
    402           drop ;; Drop the last v128 value.
    403           (i32.const 0)
    404         catch_all
    405            ${simpleTryIndirect}
    406         end`;
    407 
    408    let functionBodies = [simpleTryIndirect, nestedTryIndirect];
    409 
    410    // Test throwing from indirect calls.
    411    for (let functionBody of functionBodies) {
    412      // console.log("functionBody = : " + functionBody); // Uncomment for debugging.
    413 
    414      for (let mod of getModuleTextsForFunctionBody(functionBody)) {
    415        //console.log("mod = : " + mod); // Uncomment for debugging.
    416 
    417        let testFunction = wasmEvalText(mod, { m : exports}).exports.testFunc;
    418        assertEqArray(testFunction("foo", "bar"),
    419                      correctResultsJS);
    420      }
    421    }
    422  };
    423 
    424  // Run all tests. ------------------------------------------------------------
    425 
    426  let localThrows =
    427      ["(throw $exn)"].concat(generateLocalThrows(exntype, "(throw $exn)"));
    428 
    429  for (let localThrow of localThrows) {
    430    // Uncomment for debugging.
    431    // console.log("Testing with localThrow = " + localThrow);
    432 
    433    testDirectCallsThrowing(localThrow);
    434    testIndirectCallsThrowing(localThrow);
    435  }
    436 }