tor-browser

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

cast-abstract.js (9792B)


      1 load(libdir + "wasm-binary.js");
      2 
      3 // Replace this with a string like 'non-null (ref $s1) -> (ref any) -> (ref any)' to test exactly one specific case. Makes debugging easier.
      4 const specificTest = '';
      5 
      6 const preamble = `
      7  (type $s1 (sub (struct)))
      8  (type $s2 (sub $s1 (struct (field i32))))
      9  (type $a1 (sub (array (ref null $s1))))
     10  (type $a2 (sub $a1 (array (ref null $s2))))
     11  (type $ft1 (sub (func (param (ref $s1)) (result (ref null $a1)))))
     12  (type $ft2 (sub $ft1 (func (param (ref null $s1)) (result (ref null $a2)))))
     13  (type $ft3 (sub (func (param (ref $a2)) (result i32))))
     14  (type $ft4 (sub $ft3 (func (param (ref $a1)) (result i32))))
     15 
     16  (func $f1 (type $ft1) ref.null $a1)
     17  (func $f2 (type $ft2) ref.null $a2)
     18  (func $f3 (type $ft3) i32.const 0)
     19  (func $f4 (type $ft4) i32.const 0)
     20  (elem declare func $f1 $f2 $f3 $f4) ;; allow funcs to be ref.func'd
     21 `;
     22 
     23 // All the concrete subtype relationships present in the preamble.
     24 // [x, y] means x <: y.
     25 const subtypes = [
     26  ['$s2', '$s1'],
     27  ['$a2', '$a1'],
     28  ['$ft2', '$ft1'],
     29  ['$ft4', '$ft3'],
     30 ];
     31 
     32 const any = { name: 'any', top: 'anyref' };
     33 const eq = { name: 'eq', top: 'anyref' };
     34 const struct = { name: 'struct', top: 'anyref' };
     35 const array = { name: 'array', top: 'anyref' };
     36 const s1 = {
     37  index: 0,
     38  name: '$s1',
     39  make: 'struct.new_default $s1',
     40  top: 'anyref',
     41 };
     42 const s2 = {
     43  index: 1,
     44  name: '$s2',
     45  make: 'struct.new_default $s2',
     46  top: 'anyref',
     47 };
     48 const a1 = {
     49  index: 2,
     50  name: '$a1',
     51  make: '(array.new_default $a1 (i32.const 10))',
     52  top: 'anyref',
     53 };
     54 const a2 = {
     55  index: 3,
     56  name: '$a2',
     57  make: '(array.new_default $a2 (i32.const 10))',
     58  top: 'anyref',
     59 };
     60 const i31 = { name: 'i31', make: '(ref.i31 (i32.const 123))', top: 'anyref' };
     61 const none = { name: 'none', none: true, top: 'anyref' };
     62 
     63 const func = { name: 'func', top: 'funcref' };
     64 const ft1 = { index: 4, name: '$ft1', make: 'ref.func $f1', top: 'funcref' };
     65 const ft2 = { index: 5, name: '$ft2', make: 'ref.func $f2', top: 'funcref' };
     66 const ft3 = { index: 6, name: '$ft3', make: 'ref.func $f3', top: 'funcref' };
     67 const ft4 = { index: 7, name: '$ft4', make: 'ref.func $f4', top: 'funcref' };
     68 const nofunc = { name: 'nofunc', none: true, top: 'funcref' };
     69 
     70 const extern = { name: 'extern', make: '(extern.convert_any (struct.new_default $s1))', top: 'externref' }
     71 const noextern = { name: 'noextern', none: true, top: 'externref' }
     72 
     73 const typeSets = [
     74  [any, eq, struct, s1, s2, none],
     75  [any, eq, array, a1, a2, none],
     76  [any, eq, s1, s2, a1, a2, none],
     77  [any, eq, i31, none],
     78  [any, eq, i31, s1, none],
     79  [any, eq, i31, a1, none],
     80 
     81  [func, ft1, ft2, nofunc],
     82  [func, ft3, ft4, nofunc],
     83  [func, ft1, ft2, ft3, ft4, nofunc],
     84 
     85  [extern, noextern],
     86 ];
     87 
     88 const nullables = [       // for example:
     89  [true, true, true],     // null $s1 -> null any -> null eq
     90  [true, true, false],    // null $s1 -> null any -> eq
     91  [true, false, true],    // null $s1 -> any -> null eq
     92  [true, false, false],   // null $s1 -> any -> eq
     93  [false, true, true],    // $s1 -> null any -> null eq
     94  [false, true, false],   // $s1 -> null any -> eq
     95  [false, false, true],   // $s1 -> any -> null eq
     96  [false, false, false],  // $s1 -> any -> eq
     97 ]
     98 
     99 function isSubtype(src, dest) {
    100  if (src.name === dest.name) {
    101    return true;
    102  }
    103  for (const [src2, dest2] of subtypes) {
    104    if (src.name === src2 && dest.name === dest2) {
    105      return true;
    106    }
    107  }
    108  return false;
    109 }
    110 
    111 let numCases = 0;
    112 
    113 // This will generate an enormous pile of test cases. All of these should be valid,
    114 // as in passing WebAssembly.validate, but some may not be good casts at runtime.
    115 for (const typeSet of typeSets) {
    116  for (const start of typeSet) {
    117    for (const middle of typeSet) {
    118      for (const end of typeSet) {
    119        for (const [nullable0, nullable1, nullable2] of nullables) {
    120          for (const makeNull of [true, false]) {
    121            const concrete0 = !!start.make;
    122            const concrete1 = !!middle.make;
    123            const concrete2 = !!end.make;
    124 
    125            if (!concrete0 && !makeNull) {
    126              // We can only start with null values for abstract types
    127              continue;
    128            }
    129 
    130            if (!nullable0 && makeNull) {
    131              // Can't use null as a valid value for a non-nullable type
    132              continue;
    133            }
    134 
    135            numCases += 1;
    136 
    137            let good1 = true;
    138            let good2 = true;
    139 
    140            // Null values will fail casts to non-nullable types
    141            if (makeNull) {
    142              if (!nullable1) {
    143                good1 = false;
    144              }
    145              if (!nullable2) {
    146                good2 = false;
    147              }
    148            }
    149 
    150            // Bottom types can't represent non-null, so this will always fail
    151            if (!makeNull) {
    152              if (middle.none) {
    153                good1 = false;
    154              }
    155              if (end.none) {
    156                good2 = false;
    157              }
    158            }
    159 
    160            // Concrete values are subject to subtyping relationships
    161            if (!makeNull) {
    162              if (concrete1 && !isSubtype(start, middle)) {
    163                good1 = false;
    164              }
    165              if (concrete2 && !isSubtype(start, end)) {
    166                good2 = false;
    167              }
    168            }
    169 
    170            let emoji1 = good1 ? '✅' : '❌';
    171            let emoji2 = good2 ? '✅' : '❌';
    172 
    173            if (!good1) {
    174              good2 = false;
    175              emoji2 = '❓';
    176            }
    177 
    178            const name = `${makeNull ? 'null' : 'non-null'} (ref ${nullable0 ? 'null ' : ''}${start.name}) -> (ref ${nullable1 ? 'null ' : ''}${middle.name}) -> (ref ${nullable2 ? 'null ' : ''}${end.name})`;
    179            if (specificTest && name != specificTest) {
    180              continue;
    181            }
    182 
    183            print(`${emoji1}${emoji2} ${name}`);
    184            const make = makeNull ? `ref.null ${start.name}` : start.make;
    185            const type0 = `(ref ${nullable0 ? 'null ' : ''}${start.name})`;
    186            const type1 = `(ref ${nullable1 ? 'null ' : ''}${middle.name})`;
    187            const type2 = `(ref ${nullable2 ? 'null ' : ''}${end.name})`;
    188            const moduleText = `(module
    189              ${preamble}
    190              (func (export "make") (result ${type0})
    191                ${make}
    192              )
    193              (func (export "cast1") (param ${type0}) (result ${type1})
    194                local.get 0
    195                ref.cast ${type1}
    196              )
    197              (func (export "cast2") (param ${type1}) (result ${type2})
    198                local.get 0
    199                ref.cast ${type2}
    200              )
    201 
    202              (func (export "test1") (param ${type0}) (result i32)
    203                local.get 0
    204                ref.test ${type1}
    205              )
    206              (func (export "test2") (param ${type1}) (result i32)
    207                local.get 0
    208                ref.test ${type2}
    209              )
    210 
    211              ;; these are basically ref.test but with branches
    212              (func (export "branch1") (param ${type0}) (result i32)
    213                (block (result ${type1})
    214                  local.get 0
    215                  br_on_cast 0 ${start.top} ${type1}
    216                  drop
    217                  (return (i32.const 0))
    218                )
    219                drop
    220                (return (i32.const 1))
    221              )
    222              (func (export "branch2") (param ${type1}) (result i32)
    223                (block (result ${type2})
    224                  local.get 0
    225                  br_on_cast 0 ${middle.top} ${type2}
    226                  drop
    227                  (return (i32.const 0))
    228                )
    229                drop
    230                (return (i32.const 1))
    231              )
    232              (func (export "branchfail1") (param ${type0}) (result i32)
    233                (block (result ${middle.top})
    234                  local.get 0
    235                  br_on_cast_fail 0 ${start.top} ${type1}
    236                  drop
    237                  (return (i32.const 1))
    238                )
    239                drop
    240                (return (i32.const 0))
    241              )
    242              (func (export "branchfail2") (param ${type1}) (result i32)
    243                (block (result ${end.top})
    244                  local.get 0
    245                  br_on_cast_fail 0 ${middle.top} ${type2}
    246                  drop
    247                  (return (i32.const 1))
    248                )
    249                drop
    250                (return (i32.const 0))
    251              )
    252            )`;
    253 
    254            function assertCast(func, good) {
    255              if (good) {
    256                return [func(), true];
    257              } else {
    258                assertErrorMessage(func, WebAssembly.RuntimeError, /bad cast/);
    259                return [null, false];
    260              }
    261            }
    262 
    263            try {
    264              // The casts are split up so the stack trace will show you which cast is failing.
    265              const {
    266                make,
    267                cast1, cast2,
    268                test1, test2,
    269                branch1, branch2,
    270                branchfail1, branchfail2,
    271              } = wasmEvalText(moduleText).exports;
    272 
    273              const val = make();
    274              const [res1, ok1] = assertCast(() => cast1(val), good1);
    275              assertEq(test1(val), good1 ? 1 : 0);
    276              assertEq(branch1(val), good1 ? 1 : 0);
    277              assertEq(branchfail1(val), good1 ? 1 : 0);
    278              if (ok1) {
    279                assertCast(() => cast2(res1), good2);
    280                assertEq(test2(res1), good2 ? 1 : 0);
    281                assertEq(branch2(res1), good2 ? 1 : 0);
    282                assertEq(branchfail2(res1), good2 ? 1 : 0);
    283              }
    284            } catch (e) {
    285              print("Failed! Module text was:");
    286              print(moduleText);
    287              throw e;
    288            }
    289          }
    290        }
    291      }
    292    }
    293  }
    294 }
    295 
    296 print(`we hope you have enjoyed these ${numCases} test cases 😁`);