tor-browser

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

transferable-cleanup.js (10051B)


      1 /*
      2 * Any copyright is dedicated to the Public Domain.
      3 * http://creativecommons.org/licenses/publicdomain/
      4 */
      5 
      6 // The tests expect the clone buffer to be collected at a certain point.
      7 // I don't think collecting it sooner would break anything, but just in case:
      8 gczeal(0);
      9 
     10 function testBasic() {
     11    const desc = "basic canTransfer, writeTransfer, freeTransfer";
     12    const BASE = 100;
     13    const obj = makeSerializable(BASE + 1);
     14    let s = serialize(obj, [obj]);
     15    assertEq("" + obj.log, "" + [101, "?", 101, "W"], "serialize " + desc);
     16    s = null;
     17    gc();
     18    print(`serialize ${arguments.callee.name}: ${obj.log}`);
     19    assertEq("" + obj.log, "" + [
     20        // canTransfer(obj) then writeTransfer(obj), then obj is read with
     21        // a backreference.
     22        101, "?", 101, "W",
     23        // Then discard the serialized data, triggering freeTransfer(obj).
     24        101, "F"
     25    ], "serialize " + desc);
     26    obj.log = null;
     27 }
     28 
     29 function testErrorDuringWrite() {
     30    const desc = "canTransfer, write=>error";
     31    const BASE = 200;
     32    const obj = makeSerializable(BASE + 1);
     33    const ab = new ArrayBuffer(100);
     34    detachArrayBuffer(ab);
     35    try {
     36        serialize([obj, ab], [obj]);
     37    } catch (e) {
     38    }
     39    gc();
     40    print(`${arguments.callee.name}: ${obj.log}`);
     41    assertEq("" + obj.log, "" + [201, "?"], desc);
     42    obj.log = null;
     43 }
     44 
     45 function testErrorDuringTransfer() {
     46    const desc = "canTransfer, write(ab), writeTransfer(obj), writeTransfer(ab)=>error, freeTransfer";
     47    const BASE = 300;
     48    const obj = makeSerializable(BASE + 1);
     49    const ab = new ArrayBuffer(100);
     50    detachArrayBuffer(ab);
     51    try {
     52        serialize([obj, ab], [obj, ab]);
     53    } catch (e) {
     54    }
     55    gc();
     56    print(`${arguments.callee.name}: ${obj.log}`);
     57    assertEq("" + obj.log, "" + [
     58        // canTransfer(obj) then writeTransfer(obj)
     59        301, "?", 301, "W",
     60        // error reading ab, freeTransfer(obj)
     61        301, "F"
     62    ], desc);
     63    obj.log = null;
     64 }
     65 
     66 function testMultiOkHelper(g, BASE, desc) {
     67    const obj = makeSerializable(BASE + 1);
     68    const obj2 = makeSerializable(BASE + 2);
     69    const obj3 = makeSerializable(BASE + 3);
     70    serialize([obj, obj2, obj3], [obj, obj3]);
     71    gc();
     72    print(`${arguments.callee.name}(${BASE}): ${obj.log}`);
     73    assertEq("" + obj.log, "" + [
     74        // canTransfer(obj then obj3).
     75        BASE + 1, "?", BASE + 3, "?",
     76        // write(obj2), which happens before transferring.
     77        BASE + 2, "w",
     78        // writeTransfer(obj then obj3).
     79        BASE + 1, "W", BASE + 3, "W",
     80        // discard the clone buffer without deserializing, so freeTransfer(obj1+obj3).
     81        BASE + 1, "F", BASE + 3, "F"
     82    ], desc);
     83    obj.log = null;
     84 }
     85 
     86 function testMultiOk() {
     87    const desc = "write 3 objects, transfer obj1 and obj3 only, write obj2";
     88    testMultiOkHelper(globalThis, 400, desc);
     89 }
     90 
     91 function testMultiOkCrossRealm() {
     92    const desc = "write 3 objects, transfer obj1 and obj2 only, cross-realm";
     93    testMultiOkHelper(newGlobal({ newCompartment: true }), 500, desc);
     94 }
     95 
     96 function printTrace(callee, global, base, log, phase = undefined) {
     97    phase = phase ? `${phase} ` : "";
     98    const test = callee.replace("Helper", "") + (global === globalThis ? "" : "CrossRealm");
     99    print(`${phase}${test}(${base}): ${log}`);
    100 }
    101 
    102 function testMultiOkWithDeserializeHelper(g, BASE, desc) {
    103    const obj = makeSerializable(BASE + 1);
    104    const obj2 = makeSerializable(BASE + 2);
    105    const obj3 = makeSerializable(BASE + 3);
    106    let s = serialize([obj, obj2, obj3], [obj, obj3]);
    107    gc();
    108    printTrace(arguments.callee.name, g, BASE, obj.log, "serialize");
    109    assertEq("" + obj.log, "" + [
    110        // canTransfer(obj+obj3).
    111        BASE + 1, "?", BASE + 3, "?",
    112        // write(obj2).
    113        BASE + 2, "w",
    114        // writeTransfer(obj1+obj3). All good.
    115        BASE + 1, "W", BASE + 3, "W",
    116        // Do not collect the clone buffer. It owns obj1+obj3 now.
    117    ], "serialize " + desc);
    118    obj.log = null;
    119 
    120    let clone = deserialize(s);
    121    s = null;
    122    gc();
    123    printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize");
    124    assertEq("" + obj.log, "" + [
    125        // readTransfer(obj1+obj3).
    126        BASE + 1, "R", BASE + 3, "R",
    127        // read(obj2). The full clone is successful.
    128        BASE + 2, "r",
    129    ], "deserialize " + desc);
    130    obj.log = null;
    131 }
    132 
    133 function testMultiOkWithDeserialize() {
    134    const desc = "write 3 objects, transfer obj1 and obj3 only, write obj2, deserialize";
    135    testMultiOkWithDeserializeHelper(globalThis, 600, desc);
    136 }
    137 
    138 function testMultiOkWithDeserializeCrossRealm() {
    139    const desc = "write 3 objects, transfer obj1 and obj2 only, deserialize, cross-realm";
    140    testMultiOkWithDeserializeHelper(newGlobal({ newCompartment: true }), 700, desc);
    141 }
    142 
    143 function testMultiWithDeserializeReadTransferErrorHelper(g, BASE, desc) {
    144    const obj = makeSerializable(BASE + 1, 0);
    145    const obj2 = makeSerializable(BASE + 2, 1);
    146    const obj3 = makeSerializable(BASE + 3, 1);
    147    let s = serialize([obj, obj2, obj3], [obj, obj3]);
    148    gc();
    149    printTrace(arguments.callee.name, g, BASE, obj.log, "serialize");
    150    assertEq("" + obj.log, "" + [
    151        // Successful serialization.
    152        BASE + 1, "?", BASE + 3, "?",
    153        BASE + 2, "w",
    154        BASE + 1, "W", BASE + 3, "W",
    155    ], "serialize " + desc);
    156    obj.log = null;
    157 
    158    try {
    159        let clone = deserialize(s);
    160        assertEq(true, false, "should throw");
    161    } catch (e) {
    162        assertEq(e.message.includes("invalid transferable"), true);
    163    }
    164 
    165    try {
    166        // This fails without logging anything, since the re-transfer will be caught
    167        // by looking at its header before calling any callbacks.
    168        let clone = deserialize(s);
    169    } catch (e) {
    170        assertEq(e.message.includes("cannot transfer twice"), true);
    171    }
    172 
    173    s = null;
    174    gc();
    175    printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize");
    176    assertEq("" + obj.log, "" + [
    177        // readTransfer(obj) then readTransfer(obj3) which fails.
    178        BASE + 1, "R", BASE + 3, "R",
    179        // obj2 has not been read at all because we errored out during readTransferMap(),
    180        // which comes before the main reading. obj transfer data is now owned by its
    181        // clone. obj3 transfer data was not successfully handed over to a new object,
    182        // so it is still owned by the clone buffer and must be discarded with freeTransfer.
    183        // 'F' means the data is freed.
    184        BASE + 3, "F",
    185    ], "deserialize " + desc);
    186    obj.log = null;
    187 }
    188 
    189 function testMultiWithDeserializeReadTransferError() {
    190    const desc = "write 3 objects, transfer obj1 and obj3 only, fail during readTransfer(obj3)";
    191    testMultiWithDeserializeReadTransferErrorHelper(globalThis, 800, desc);
    192 }
    193 
    194 function testMultiWithDeserializeReadTransferErrorCrossRealm() {
    195    const desc = "write 3 objects, transfer obj1 and obj2 only, fail during readTransfer(obj3), cross-realm";
    196    testMultiWithDeserializeReadTransferErrorHelper(newGlobal({ newCompartment: true }), 900, desc);
    197 }
    198 
    199 function testMultiWithDeserializeReadErrorHelper(g, BASE, desc) {
    200    const obj = makeSerializable(BASE + 1, 2);
    201    const obj2 = makeSerializable(BASE + 2, 2);
    202    const obj3 = makeSerializable(BASE + 3, 2);
    203    let s = serialize([obj, obj2, obj3], [obj, obj3]);
    204    gc();
    205    printTrace(arguments.callee.name, g, BASE, obj.log, "serialize");
    206    assertEq("" + obj.log, "" + [
    207        // Same as above. Everything is fine.
    208        BASE + 1, "?", BASE + 3, "?",
    209        BASE + 2, "w",
    210        BASE + 1, "W", BASE + 3, "W",
    211    ], "serialize " + desc);
    212    obj.log = null;
    213 
    214    try {
    215        let clone = deserialize(s);
    216    } catch (e) {
    217        assertEq(e.message.includes("Failed as requested"), true);
    218    }
    219    s = null;
    220    gc();
    221    printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize");
    222    assertEq("" + obj.log, "" + [
    223        // The transferred objects will get restored via the readTransfer() hook
    224        // and thus will own their transferred data. They will get discarded during
    225        // the GC, but that doesn't call any hooks. When the GC collects `s`,
    226        // it will look for any transferrable data to release, but will
    227        // find that it no longer owns any such data and will do nothing.
    228        BASE + 1, "R", BASE + 3, "R",
    229        BASE + 2, "r",
    230    ], "deserialize " + desc);
    231    obj.log = null;
    232 }
    233 
    234 function testMultiWithDeserializeReadError() {
    235    const desc = "write 3 objects, transfer obj1 and obj3 only, fail during read(obj2)";
    236    testMultiWithDeserializeReadErrorHelper(globalThis, 1000, desc);
    237 }
    238 
    239 function testMultiWithDeserializeReadErrorCrossRealm() {
    240    const desc = "write 3 objects, transfer obj1 and obj2 only, fail during read(obj2), cross-realm";
    241    testMultiWithDeserializeReadErrorHelper(newGlobal({ newCompartment: true }), 1100, desc);
    242 }
    243 
    244 function testCorruptedTransferMapHeader() {
    245    const ab = new ArrayBuffer(100);
    246    const s = serialize({ ab, seven: 7 }, [ab], { scope: "DifferentProcess" });
    247    const ia = new Int32Array(s.arraybuffer);
    248    ia[2] = 4; // Invalid, out of range TransferableMapHeader
    249    s.arraybuffer = ia.buffer;
    250    try {
    251        deserialize(s);
    252        assertEq(true, false, "should throw for invalid TM header");
    253    } catch (e) {
    254        assertEq(e.message.includes("invalid transfer map header"), true);
    255    }
    256    ia[2] = -1; // This should be using unsigned comparison, so this will be caught.
    257    s.arraybuffer = ia.buffer;
    258    try {
    259        deserialize(s);
    260        assertEq(true, false, "should throw for invalid TM header");
    261    } catch (e) {
    262        assertEq(e.message.includes("invalid transfer map header"), true);
    263    }
    264 }
    265 
    266 testBasic();
    267 testErrorDuringWrite();
    268 testErrorDuringTransfer();
    269 testMultiOk();
    270 testMultiOkCrossRealm();
    271 testMultiOkWithDeserialize();
    272 testMultiOkWithDeserializeCrossRealm();
    273 testMultiWithDeserializeReadTransferError();
    274 testMultiWithDeserializeReadTransferErrorCrossRealm();
    275 testMultiWithDeserializeReadError();
    276 testMultiWithDeserializeReadErrorCrossRealm();
    277 testCorruptedTransferMapHeader();