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();