tor-browser

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

memory-discard.js (8027B)


      1 // |jit-test| skip-if: !wasmMemoryControlEnabled()
      2 
      3 // This tests memory.discard and WebAssembly.Memory.discard() by placing data
      4 // (the alphabet) halfway across a page boundary, then discarding the first
      5 // page. The first half of the alphabet should be zeroed; the second should
      6 // not. The memory should remain readable and writable.
      7 //
      8 // The ultimate goal is to release physical pages of memory back to the
      9 // operating system, but we can't really observe memory metrics here. Oh well.
     10 
     11 function initModule(discardOffset, discardLen, discardViaJS, shared, memType = 'i32') {
     12    const memProps = shared ? '4 4 shared' : '4'; // 4 pages
     13    const text = `(module
     14        (memory (export "memory") ${memType} ${memProps})
     15        (data "abcdefghijklmnopqrstuvwxyz")
     16        (func (export "init")
     17            ;; splat alphabet halfway across the 3/4 page boundary.
     18            ;; 196595 = (65536 * 3) - (26 / 2)
     19            (memory.init 0 (${memType}.const 196595) (i32.const 0) (i32.const 26))
     20        )
     21        (func (export "letter") (param i32) (result i32)
     22            (i32.load8_u (${memType}.add (${memType}.const 196595) ${memType == 'i64' ? '(i64.extend_i32_u (local.get 0))' : '(local.get 0)'}))
     23        )
     24        (func (export "discard")
     25            (memory.discard (${memType}.const ${discardOffset}) (${memType}.const ${discardLen}))
     26        )
     27    )`;
     28    const exp = wasmEvalText(text).exports;
     29 
     30    return [exp, discardViaJS ? () => exp.memory.discard(discardOffset, discardLen) : exp.discard];
     31 }
     32 
     33 function checkRegion(exp, min, max, expectLetters) {
     34    for (let i = min; i < max; i++) {
     35        const c = "a".charCodeAt(0) + i;
     36        const expected = expectLetters ? c : 0;
     37        assertEq(exp.letter(i), expected, `checking position of letter ${String.fromCharCode(c)}`);
     38    }
     39 }
     40 function checkFirstHalf(exp, expectLetters) { return checkRegion(exp, 0, 13, expectLetters) }
     41 function checkSecondHalf(exp, expectLetters) { return checkRegion(exp, 13, 26, expectLetters) }
     42 function checkWholeAlphabet(exp, expectLetters) { return checkRegion(exp, 0, 26, expectLetters) }
     43 
     44 function testAll(func) {
     45    func(false, false, 'i32');
     46    func(false, true, 'i32');
     47    func(true, false, 'i32');
     48    func(true, true, 'i32');
     49    func(false, false, 'i64');
     50    func(false, true, 'i64');
     51    func(true, false, 'i64');
     52    func(true, true, 'i64');
     53 }
     54 
     55 testAll(function testHappyPath(discardViaJS, shared, memType) {
     56    // Only page 3 of memory, half the alphabet
     57    const [exp, discard] = initModule(65536 * 2, 65536, discardViaJS, shared, memType);
     58 
     59    // All zero to start
     60    checkWholeAlphabet(exp, false);
     61 
     62    // Then all alphabet
     63    exp.init();
     64    checkWholeAlphabet(exp, true);
     65 
     66    // Discarding the first page clears the first half of the alphabet
     67    discard();
     68    checkFirstHalf(exp, false);
     69    checkSecondHalf(exp, true);
     70 
     71    // We should be able to write back to a discarded region of memory
     72    exp.init();
     73    checkWholeAlphabet(exp, true);
     74 
     75    // ...and then discard again
     76    discard();
     77    checkFirstHalf(exp, false);
     78    checkSecondHalf(exp, true);
     79 });
     80 
     81 testAll(function testZeroLen(discardViaJS, shared) {
     82    // Discard zero bytes
     83    const [exp, discard] = initModule(PageSizeInBytes * 2, 0, discardViaJS, shared);
     84 
     85    // Init the stuff
     86    exp.init();
     87    checkWholeAlphabet(exp, true);
     88 
     89    // Discarding zero bytes should be valid...
     90    discard();
     91 
     92    // ...but should have no effect.
     93    checkWholeAlphabet(exp, true);
     94 });
     95 
     96 testAll(function testWithGrow(discardViaJS, shared, memType) {
     97    if (shared) {
     98        return; // shared memories cannot grow
     99    }
    100 
    101    // Only page 3 of memory, half the alphabet. There is no max size on the
    102    // memory, so it will be subject to moving grows in 32-bit mode.
    103    const [exp, discard] = initModule(65536 * 2, 65536, discardViaJS, false, memType);
    104 
    105    // Start with the whole alphabet
    106    exp.init();
    107    checkWholeAlphabet(exp, true);
    108 
    109    // Discarding the first page clears the first half of the alphabet
    110    discard();
    111    checkFirstHalf(exp, false);
    112    checkSecondHalf(exp, true);
    113 
    114    // Oops we just grew by a preposterous amount, time to move
    115    exp.memory.grow(memType === "i64" ? 200n : 200);
    116 
    117    // The memory should still be readable
    118    checkFirstHalf(exp, false);
    119    checkSecondHalf(exp, true);
    120 
    121    // We should be able to write back to a discarded region of memory
    122    exp.init();
    123    checkWholeAlphabet(exp, true);
    124 
    125    // ...and then discard again
    126    discard();
    127    checkFirstHalf(exp, false);
    128    checkSecondHalf(exp, true);
    129 });
    130 
    131 testAll(function testOOB(discardViaJS, shared) {
    132    // Discard two pages where there is only one
    133    const [exp, discard] = initModule(PageSizeInBytes * 3, PageSizeInBytes * 2, discardViaJS, shared);
    134 
    135    exp.init();
    136    checkWholeAlphabet(exp, true);
    137    assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
    138 
    139    // Ensure nothing was discarded
    140    checkWholeAlphabet(exp, true);
    141 });
    142 
    143 testAll(function testOOB2(discardViaJS, shared) {
    144    // Discard two pages starting near the end of 32-bit address space
    145    // (would trigger an overflow in 32-bit world)
    146    const [exp, discard] = initModule(2 ** 32 - PageSizeInBytes, PageSizeInBytes * 2, discardViaJS, shared);
    147 
    148    exp.init();
    149    checkWholeAlphabet(exp, true);
    150    assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
    151 
    152    // Ensure nothing was discarded
    153    checkWholeAlphabet(exp, true);
    154 });
    155 
    156 testAll(function testOOB3(discardViaJS, shared) {
    157    // Discard nearly an entire 32-bit address space's worth of pages. Very exciting!
    158    const [exp, discard] = initModule(0, 2 ** 32 - PageSizeInBytes, discardViaJS, shared);
    159 
    160    exp.init();
    161    checkWholeAlphabet(exp, true);
    162    assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
    163 
    164    // Ensure nothing was discarded
    165    checkWholeAlphabet(exp, true);
    166 });
    167 
    168 (function testOOB4() {
    169    // Discard an entire 32-bit address space's worth of pages. JS can do this
    170    // even to 32-bit memories because it can handle numbers bigger
    171    // than 2^32 - 1.
    172    const [exp, _] = initModule(0, 0, false); // pass zero to allow the wasm module to validate
    173    const discard = () => exp.memory.discard(0, 2 ** 32);
    174 
    175    exp.init();
    176    checkWholeAlphabet(exp, true);
    177    assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
    178 
    179    // Ensure nothing was discarded
    180    checkWholeAlphabet(exp, true);
    181 })();
    182 
    183 (function testOverflow() {
    184    // Discard UINT64_MAX - (2 pages), starting from page 4. This overflows but puts both start and end in bounds.
    185    // This cannot be done with a JS discard because JS can't actually represent big enough integers.
    186 
    187    // The big ol' number here is 2^64 - (65536 * 2)
    188    const [exp, discard] = initModule(65536 * 3, `18_446_744_073_709_420_544`, false, false, 'i64');
    189 
    190    // Init the stuff
    191    exp.init();
    192    checkWholeAlphabet(exp, true);
    193 
    194    // Discarding should not be valid when it goes out of bounds
    195    assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /out of bounds/);
    196 
    197    // Ensure nothing was discarded
    198    checkWholeAlphabet(exp, true);
    199 })();
    200 
    201 testAll(function testMisalignedStart(discardViaJS, shared) {
    202    // Discard only the first half of the alphabet (this misaligns the start)
    203    const [exp, discard] = initModule(PageSizeInBytes * 3 - 13, 13, discardViaJS, shared);
    204 
    205    exp.init();
    206    checkWholeAlphabet(exp, true);
    207    assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /unaligned/);
    208 
    209    // Ensure nothing was discarded
    210    checkWholeAlphabet(exp, true);
    211 });
    212 
    213 testAll(function testMisalignedEnd(discardViaJS, shared) {
    214    // Discard only the second half of the alphabet (this misaligns the end)
    215    const [exp, discard] = initModule(PageSizeInBytes * 3, 13, discardViaJS, shared);
    216 
    217    exp.init();
    218    checkWholeAlphabet(exp, true);
    219    assertErrorMessage(() => discard(), WebAssembly.RuntimeError, /unaligned/);
    220 
    221    // Ensure nothing was discarded
    222    checkWholeAlphabet(exp, true);
    223 });