tor-browser

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

harness.js (14381B)


      1 "use strict";
      2 
      3 /* Copyright 2021 Mozilla Foundation
      4 *
      5 * Licensed under the Apache License, Version 2.0 (the "License");
      6 * you may not use this file except in compliance with the License.
      7 * You may obtain a copy of the License at
      8 *
      9 *     http://www.apache.org/licenses/LICENSE-2.0
     10 *
     11 * Unless required by applicable law or agreed to in writing, software
     12 * distributed under the License is distributed on an "AS IS" BASIS,
     13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 * See the License for the specific language governing permissions and
     15 * limitations under the License.
     16 */
     17 
     18 if (!wasmIsSupported()) {
     19  quit();
     20 }
     21 
     22 function partialOobWriteMayWritePartialData() {
     23  let arm_native = getBuildConfiguration("arm") && !getBuildConfiguration("arm-simulator");
     24  let arm64_native = getBuildConfiguration("arm64") && !getBuildConfiguration("arm64-simulator");
     25  let riscv64_native = getBuildConfiguration("riscv64") && !getBuildConfiguration("riscv64-simulator");
     26  return arm_native || arm64_native || riscv64_native;
     27 }
     28 
     29 function bytes(type, bytes) {
     30  var typedBuffer = new Uint8Array(bytes);
     31  return wasmGlobalFromArrayBuffer(type, typedBuffer.buffer);
     32 }
     33 function value(type, value) {
     34  return new WebAssembly.Global({
     35    value: type,
     36    mutable: false,
     37  }, value);
     38 }
     39 
     40 function i8x16(elements) {
     41  let typedBuffer = new Uint8Array(elements);
     42  return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer);
     43 }
     44 function i16x8(elements) {
     45  let typedBuffer = new Uint16Array(elements);
     46  return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer);
     47 }
     48 function i32x4(elements) {
     49  let typedBuffer = new Uint32Array(elements);
     50  return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer);
     51 }
     52 function i64x2(elements) {
     53  let typedBuffer = new BigUint64Array(elements);
     54  return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer);
     55 }
     56 function f32x4(elements) {
     57  let typedBuffer = new Float32Array(elements);
     58  return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer);
     59 }
     60 function f64x2(elements) {
     61  let typedBuffer = new Float64Array(elements);
     62  return wasmGlobalFromArrayBuffer("v128", typedBuffer.buffer);
     63 }
     64 
     65 function either(...arr) {
     66  return new EitherVariants(arr);
     67 }
     68 
     69 class F32x4Pattern {
     70  constructor(x, y, z, w) {
     71    this.x = x;
     72    this.y = y;
     73    this.z = z;
     74    this.w = w;
     75  }
     76 }
     77 
     78 class F64x2Pattern {
     79  constructor(x, y) {
     80    this.x = x;
     81    this.y = y;
     82  }
     83 }
     84 
     85 class RefWithType {
     86  constructor(type) {
     87    this.type = type;
     88  }
     89 
     90  formatExpected() {
     91    return `RefWithType(${this.type})`;
     92  }
     93 
     94  test(refGlobal) {
     95    try {
     96      new WebAssembly.Global({value: this.type}, refGlobal.value);
     97      return true;
     98    } catch (err) {
     99      assertEq(err instanceof TypeError, true, `wrong type of error when creating global: ${err}`);
    100      assertEq(!!err.message.match(/can only pass/), true, `wrong type of error when creating global: ${err}`);
    101      return false;
    102    }
    103  }
    104 }
    105 
    106 // ref.extern values created by spec tests will be JS objects of the form
    107 // { [externsym]: <number> }. Other externref values are possible to observe
    108 // if extern.convert_any is used.
    109 let externsym = Symbol("externref");
    110 function externref(s) {
    111  return { [externsym]: s };
    112 }
    113 
    114 class ExternRefResult {
    115  constructor(n) {
    116    this.n = n;
    117  }
    118 
    119  formatExpected() {
    120    return `ref.extern ${this.n}`;
    121  }
    122 
    123  test(global) {
    124    // the global's value can either be an externref or just a plain old JS number
    125    let result = global.value;
    126    if (typeof global.value === "object" && externsym in global.value) {
    127      result = global.value[externsym];
    128    }
    129    return result === this.n;
    130  }
    131 }
    132 
    133 // ref.host values created by spectests will be whatever the JS API does to
    134 // convert the given value to anyref. It should implicitly be like any.convert_extern.
    135 function hostref(v) {
    136  const { internalizeNum } = new WebAssembly.Instance(
    137    new WebAssembly.Module(wasmTextToBinary(`(module
    138      (func (import "test" "coerce") (param i32) (result anyref))
    139      (func (export "internalizeNum") (param i32) (result anyref)
    140        (call 0 (local.get 0))
    141      )
    142    )`)),
    143    { "test": { "coerce": x => x } },
    144  ).exports;
    145  return internalizeNum(v);
    146 }
    147 
    148 class HostRefResult {
    149  constructor(n) {
    150    this.n = n;
    151  }
    152 
    153  formatExpected() {
    154    return `ref.host ${this.n}`;
    155  }
    156 
    157  test(externrefGlobal) {
    158    assertEq(externsym in externrefGlobal.value, true, `HostRefResult only works with externref inputs`);
    159    return externrefGlobal.value[externsym] === this.n;
    160  }
    161 }
    162 
    163 // https://github.com/WebAssembly/spec/blob/main/interpreter/README.md#spectest-host-module
    164 let linkage = {
    165  "spectest": {
    166    global_i32: 666,
    167    global_i64: 666n,
    168    global_f32: 666.6,
    169    global_f64: 666.6,
    170 
    171    table: new WebAssembly.Table({
    172      initial: 10,
    173      maximum: 20,
    174      element: "anyfunc",
    175    }),
    176    table64: new WebAssembly.Table({
    177      address: "i64",
    178      initial: 10n,
    179      maximum: 20n,
    180      element: "anyfunc",
    181    }),
    182 
    183    memory: new WebAssembly.Memory({ initial: 1, maximum: 2 }),
    184 
    185    print: console.log.bind(console),
    186    print_i32: console.log.bind(console),
    187    print_i64: console.log.bind(console),
    188    print_f32: console.log.bind(console),
    189    print_f64: console.log.bind(console),
    190    print_i32_f32: console.log.bind(console),
    191    print_f64_f64: console.log.bind(console),
    192  },
    193 };
    194 
    195 function module(source) {
    196  let bytecode = wasmTextToBinary(source);
    197  let module = new WebAssembly.Module(bytecode);
    198  return module;
    199 }
    200 
    201 function instantiate(source) {
    202  let bytecode = wasmTextToBinary(source);
    203  let module = new WebAssembly.Module(bytecode);
    204  let instance = new WebAssembly.Instance(module, linkage);
    205  return instance.exports;
    206 }
    207 
    208 function instantiateFromModule(module) {
    209  let instance = new WebAssembly.Instance(module, linkage);
    210  return instance.exports;
    211 }
    212 
    213 function register(instance, name) {
    214  linkage[name] = instance;
    215 }
    216 
    217 function invoke(instance, field, params) {
    218  let func = instance[field];
    219  assertEq(func instanceof Function, true, "expected a function");
    220  return wasmLosslessInvoke(func, ...params);
    221 }
    222 
    223 function get(instance, field) {
    224  let global = instance[field];
    225  assertEq(
    226    global instanceof WebAssembly.Global,
    227    true,
    228    "expected a WebAssembly.Global",
    229  );
    230  return global;
    231 }
    232 
    233 function assert_trap(thunk, message) {
    234  try {
    235    thunk();
    236    throw new Error(`got no error`);
    237  } catch (err) {
    238    if (err instanceof WebAssembly.RuntimeError) {
    239      return;
    240    }
    241    err.message = `expected trap (${message}): ${err.message}`;
    242    throw err;
    243  }
    244 }
    245 
    246 let StackOverflow;
    247 try {
    248  (function f() {
    249    1 + f();
    250  })();
    251 } catch (e) {
    252  StackOverflow = e.constructor;
    253 }
    254 function assert_exhaustion(thunk, message) {
    255  try {
    256    thunk();
    257    throw new Error(`got no error`);
    258  } catch (err) {
    259    if (err instanceof StackOverflow) {
    260      return;
    261    }
    262    err.message = `expected exhaustion (${message}): ${err.message}`;
    263    throw err;
    264  }
    265 }
    266 
    267 function assert_invalid(thunk, message) {
    268  try {
    269    thunk();
    270    throw new Error(`got no error`);
    271  } catch (err) {
    272    if (err instanceof WebAssembly.LinkError || err instanceof WebAssembly.CompileError) {
    273      return;
    274    }
    275    err.message = `expected invalid module (${message}): ${err.message}`;
    276    throw err;
    277  }
    278 }
    279 
    280 function assert_unlinkable(thunk, message) {
    281  try {
    282    thunk();
    283    throw new Error(`got no error`);
    284  } catch (err) {
    285    if (err instanceof WebAssembly.LinkError || err instanceof WebAssembly.CompileError) {
    286      return;
    287    }
    288    err.message = `expected an unlinkable module (${message}): ${err.message}`;
    289    throw err;
    290  }
    291 }
    292 
    293 function assert_malformed(thunk, message) {
    294  try {
    295    thunk();
    296    throw new Error(`got no error`);
    297  } catch (err) {
    298    if (
    299      err instanceof TypeError ||
    300      err instanceof SyntaxError ||
    301      err instanceof WebAssembly.CompileError ||
    302      err instanceof WebAssembly.LinkError
    303    ) {
    304      return;
    305    }
    306    err.message = `expected a malformed module (${message}): ${err.message}`;
    307    throw err;
    308  }
    309 }
    310 
    311 function assert_exception(thunk) {
    312  let thrown = false;
    313  try {
    314    thunk();
    315  } catch (err) {
    316    thrown = true;
    317  }
    318  assertEq(thrown, true, "expected an exception to be thrown");
    319 }
    320 
    321 function assert_return(thunk, expected) {
    322  let results = thunk();
    323 
    324  if (results === undefined) {
    325    results = [];
    326  } else if (!Array.isArray(results)) {
    327    results = [results];
    328  }
    329  if (!Array.isArray(expected)) {
    330    expected = [expected];
    331  }
    332 
    333  if (!compareResults(results, expected)) {
    334    let got = results.map((x) => formatResult(x)).join(", ");
    335    let wanted = expected.map((x) => formatExpected(x)).join(", ");
    336    assertEq(
    337      `[${got}]`,
    338      `[${wanted}]`,
    339    );
    340    assertEq(true, false, `${got} !== ${wanted}`);
    341  }
    342 }
    343 
    344 function formatResult(result) {
    345  if (typeof (result) === "object") {
    346    return wasmGlobalToString(result);
    347  } else {
    348    return `${result}`;
    349  }
    350 }
    351 
    352 function formatExpected(expected) {
    353  if (
    354    expected === `f32_canonical_nan` ||
    355    expected === `f32_arithmetic_nan` ||
    356    expected === `f64_canonical_nan` ||
    357    expected === `f64_arithmetic_nan`
    358  ) {
    359    return expected;
    360  } else if (expected instanceof F32x4Pattern) {
    361    return `f32x4(${formatExpected(expected.x)}, ${
    362      formatExpected(expected.y)
    363    }, ${formatExpected(expected.z)}, ${formatExpected(expected.w)})`;
    364  } else if (expected instanceof F64x2Pattern) {
    365    return `f64x2(${formatExpected(expected.x)}, ${
    366      formatExpected(expected.y)
    367    })`;
    368  } else if (expected instanceof EitherVariants) {
    369    return expected.formatExpected();
    370  } else if (expected instanceof RefWithType) {
    371    return expected.formatExpected();
    372  } else if (expected instanceof ExternRefResult) {
    373    return expected.formatExpected();
    374  } else if (expected instanceof HostRefResult) {
    375    return expected.formatExpected();
    376  } else if (typeof (expected) === "object") {
    377    return wasmGlobalToString(expected);
    378  } else {
    379    throw new Error("unknown expected result");
    380  }
    381 }
    382 
    383 class EitherVariants {
    384  constructor(arr) {
    385    this.arr = arr;
    386  }
    387  matches(v) {
    388    return this.arr.some((e) => compareResult(v, e));
    389  }
    390  formatExpected() {
    391    return `either(${this.arr.map(formatExpected).join(", ")})`;
    392  }
    393 }
    394 
    395 function compareResults(results, expected) {
    396  if (results.length !== expected.length) {
    397    return false;
    398  }
    399  for (let i in results) {
    400    if (expected[i] instanceof EitherVariants) {
    401      return expected[i].matches(results[i]);
    402    }
    403    if (!compareResult(results[i], expected[i])) {
    404      return false;
    405    }
    406  }
    407  return true;
    408 }
    409 
    410 function compareResult(result, expected) {
    411  if (
    412    expected === `canonical_nan` ||
    413    expected === `arithmetic_nan`
    414  ) {
    415    return wasmGlobalIsNaN(result, expected);
    416  } else if (expected === null) {
    417    return result.value === null;
    418  } else if (expected instanceof F32x4Pattern) {
    419    return compareResult(
    420      wasmGlobalExtractLane(result, "f32x4", 0),
    421      expected.x,
    422    ) &&
    423      compareResult(wasmGlobalExtractLane(result, "f32x4", 1), expected.y) &&
    424      compareResult(wasmGlobalExtractLane(result, "f32x4", 2), expected.z) &&
    425      compareResult(wasmGlobalExtractLane(result, "f32x4", 3), expected.w);
    426  } else if (expected instanceof F64x2Pattern) {
    427    return compareResult(
    428      wasmGlobalExtractLane(result, "f64x2", 0),
    429      expected.x,
    430    ) &&
    431      compareResult(wasmGlobalExtractLane(result, "f64x2", 1), expected.y);
    432  } else if (expected instanceof RefWithType) {
    433    return expected.test(result);
    434  } else if (expected instanceof ExternRefResult) {
    435    return expected.test(result);
    436  } else if (expected instanceof HostRefResult) {
    437    return expected.test(result);
    438  } else if (typeof (expected) === "object") {
    439    return wasmGlobalsEqual(result, expected);
    440  } else {
    441    throw new Error("unknown expected result");
    442  }
    443 }
    444 
    445 class Thread {
    446  LOC_STATE = 0;
    447  LOC_DID_ERROR = 1;
    448 
    449  STATE_WORKER_READY = 0x60; // "GO"
    450  STATE_SENDING_VALUE = 0xF00D; // feed me values
    451  STATE_GOT_VALUE = 0x600DF00D; // mm delicious values
    452  STATE_RUN_CODE = 0xC0DE;
    453  STATE_DONE = 0xDEAD;
    454 
    455  constructor(sharedModule, sharedModuleName, code) {
    456    this._coord = new Int32Array(new SharedArrayBuffer(4*2));
    457 
    458    setSharedObject(this._coord.buffer);
    459    evalInWorker(`
    460      const _coord = new Int32Array(getSharedObject());
    461 
    462      ${readRelativeToScript("harness.js")}
    463 
    464      function setState(state) {
    465        Atomics.store(_coord, ${this.LOC_STATE}, state);
    466      }
    467      function waitForState(expected) {
    468        while (Atomics.load(_coord, ${this.LOC_STATE}) !== expected) {}
    469      }
    470      function receive() {
    471        waitForState(${this.STATE_SENDING_VALUE});
    472        const x = getSharedObject();
    473        setState(${this.STATE_GOT_VALUE});
    474        return x;
    475      }
    476 
    477      // Tell main thread we are ready
    478      setState(${this.STATE_WORKER_READY});
    479 
    480      // Get shared module's exports from main thread. (We do this one at a
    481      // time for reasons explained below.)
    482      const ${sharedModuleName} = {};
    483      ${Object.keys(sharedModule).map(name =>
    484        `${sharedModuleName}["${name}"] = receive();`
    485      )}
    486      waitForState(${this.STATE_RUN_CODE});
    487 
    488      try {
    489        ${code}
    490      } catch (e) {
    491        Atomics.store(_coord, ${this.LOC_DID_ERROR}, 1);
    492        throw e;
    493      } finally {
    494        setState(${this.STATE_DONE});
    495      }
    496    `);
    497 
    498    // Wait for worker to spawn
    499    this.waitForState(this.STATE_WORKER_READY);
    500 
    501    // Send shared module exports to worker. We send values one at a time
    502    // because setGlobalObject can only take very specific objects, like wasm
    503    // memories, not generic objects like the whole exports object.
    504    for (const exportedValue of Object.values(sharedModule)) {
    505      this.send(exportedValue);
    506    }
    507 
    508    // Give the worker the all-clear to execute its workload
    509    this.setState(this.STATE_RUN_CODE);
    510  }
    511 
    512  setState(state) {
    513    Atomics.store(this._coord, this.LOC_STATE, state);
    514  }
    515 
    516  waitForState(expected) {
    517    while (Atomics.load(this._coord, this.LOC_STATE) !== expected) {}
    518  }
    519 
    520  send(val) {
    521    setSharedObject(val);
    522    this.setState(this.STATE_SENDING_VALUE);
    523    this.waitForState(this.STATE_GOT_VALUE);
    524  }
    525 
    526  wait() {
    527    this.waitForState(this.STATE_DONE);
    528    if (Atomics.load(this._coord, this.LOC_DID_ERROR)) {
    529      throw new Error("Error in worker code. Note that line numbers will not be helpful because of how the harness is loaded.");
    530    }
    531  }
    532 }