tor-browser

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

harness.js (14292B)


      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 === `canonical_nan` ||
    355    expected === `arithmetic_nan`
    356  ) {
    357    return expected;
    358  } else if (expected instanceof F32x4Pattern) {
    359    return `f32x4(${formatExpected(expected.x)}, ${
    360      formatExpected(expected.y)
    361    }, ${formatExpected(expected.z)}, ${formatExpected(expected.w)})`;
    362  } else if (expected instanceof F64x2Pattern) {
    363    return `f64x2(${formatExpected(expected.x)}, ${
    364      formatExpected(expected.y)
    365    })`;
    366  } else if (expected instanceof EitherVariants) {
    367    return expected.formatExpected();
    368  } else if (expected instanceof RefWithType) {
    369    return expected.formatExpected();
    370  } else if (expected instanceof ExternRefResult) {
    371    return expected.formatExpected();
    372  } else if (expected instanceof HostRefResult) {
    373    return expected.formatExpected();
    374  } else if (typeof (expected) === "object") {
    375    return wasmGlobalToString(expected);
    376  } else {
    377    throw new Error("unknown expected result");
    378  }
    379 }
    380 
    381 class EitherVariants {
    382  constructor(arr) {
    383    this.arr = arr;
    384  }
    385  matches(v) {
    386    return this.arr.some((e) => compareResult(v, e));
    387  }
    388  formatExpected() {
    389    return `either(${this.arr.map(formatExpected).join(", ")})`;
    390  }
    391 }
    392 
    393 function compareResults(results, expected) {
    394  if (results.length !== expected.length) {
    395    return false;
    396  }
    397  for (let i in results) {
    398    if (expected[i] instanceof EitherVariants) {
    399      return expected[i].matches(results[i]);
    400    }
    401    if (!compareResult(results[i], expected[i])) {
    402      return false;
    403    }
    404  }
    405  return true;
    406 }
    407 
    408 function compareResult(result, expected) {
    409  if (
    410    expected === `canonical_nan` ||
    411    expected === `arithmetic_nan`
    412  ) {
    413    return wasmGlobalIsNaN(result, expected);
    414  } else if (expected === null) {
    415    return result.value === null;
    416  } else if (expected instanceof F32x4Pattern) {
    417    return compareResult(
    418      wasmGlobalExtractLane(result, "f32x4", 0),
    419      expected.x,
    420    ) &&
    421      compareResult(wasmGlobalExtractLane(result, "f32x4", 1), expected.y) &&
    422      compareResult(wasmGlobalExtractLane(result, "f32x4", 2), expected.z) &&
    423      compareResult(wasmGlobalExtractLane(result, "f32x4", 3), expected.w);
    424  } else if (expected instanceof F64x2Pattern) {
    425    return compareResult(
    426      wasmGlobalExtractLane(result, "f64x2", 0),
    427      expected.x,
    428    ) &&
    429      compareResult(wasmGlobalExtractLane(result, "f64x2", 1), expected.y);
    430  } else if (expected instanceof RefWithType) {
    431    return expected.test(result);
    432  } else if (expected instanceof ExternRefResult) {
    433    return expected.test(result);
    434  } else if (expected instanceof HostRefResult) {
    435    return expected.test(result);
    436  } else if (typeof (expected) === "object") {
    437    return wasmGlobalsEqual(result, expected);
    438  } else {
    439    throw new Error("unknown expected result");
    440  }
    441 }
    442 
    443 class Thread {
    444  LOC_STATE = 0;
    445  LOC_DID_ERROR = 1;
    446 
    447  STATE_WORKER_READY = 0x60; // "GO"
    448  STATE_SENDING_VALUE = 0xF00D; // feed me values
    449  STATE_GOT_VALUE = 0x600DF00D; // mm delicious values
    450  STATE_RUN_CODE = 0xC0DE;
    451  STATE_DONE = 0xDEAD;
    452 
    453  constructor(sharedModule, sharedModuleName, code) {
    454    this._coord = new Int32Array(new SharedArrayBuffer(4*2));
    455 
    456    setSharedObject(this._coord.buffer);
    457    evalInWorker(`
    458      const _coord = new Int32Array(getSharedObject());
    459 
    460      ${readRelativeToScript("harness.js")}
    461 
    462      function setState(state) {
    463        Atomics.store(_coord, ${this.LOC_STATE}, state);
    464      }
    465      function waitForState(expected) {
    466        while (Atomics.load(_coord, ${this.LOC_STATE}) !== expected) {}
    467      }
    468      function receive() {
    469        waitForState(${this.STATE_SENDING_VALUE});
    470        const x = getSharedObject();
    471        setState(${this.STATE_GOT_VALUE});
    472        return x;
    473      }
    474 
    475      // Tell main thread we are ready
    476      setState(${this.STATE_WORKER_READY});
    477 
    478      // Get shared module's exports from main thread. (We do this one at a
    479      // time for reasons explained below.)
    480      const ${sharedModuleName} = {};
    481      ${Object.keys(sharedModule).map(name =>
    482        `${sharedModuleName}["${name}"] = receive();`
    483      )}
    484      waitForState(${this.STATE_RUN_CODE});
    485 
    486      try {
    487        ${code}
    488      } catch (e) {
    489        Atomics.store(_coord, ${this.LOC_DID_ERROR}, 1);
    490        throw e;
    491      } finally {
    492        setState(${this.STATE_DONE});
    493      }
    494    `);
    495 
    496    // Wait for worker to spawn
    497    this.waitForState(this.STATE_WORKER_READY);
    498 
    499    // Send shared module exports to worker. We send values one at a time
    500    // because setGlobalObject can only take very specific objects, like wasm
    501    // memories, not generic objects like the whole exports object.
    502    for (const exportedValue of Object.values(sharedModule)) {
    503      this.send(exportedValue);
    504    }
    505 
    506    // Give the worker the all-clear to execute its workload
    507    this.setState(this.STATE_RUN_CODE);
    508  }
    509 
    510  setState(state) {
    511    Atomics.store(this._coord, this.LOC_STATE, state);
    512  }
    513 
    514  waitForState(expected) {
    515    while (Atomics.load(this._coord, this.LOC_STATE) !== expected) {}
    516  }
    517 
    518  send(val) {
    519    setSharedObject(val);
    520    this.setState(this.STATE_SENDING_VALUE);
    521    this.waitForState(this.STATE_GOT_VALUE);
    522  }
    523 
    524  wait() {
    525    this.waitForState(this.STATE_DONE);
    526    if (Atomics.load(this._coord, this.LOC_DID_ERROR)) {
    527      throw new Error("Error in worker code. Note that line numbers will not be helpful because of how the harness is loaded.");
    528    }
    529  }
    530 }