diff options
Diffstat (limited to 'js/src/jit-test/tests/structured-clone/transferable-cleanup.js')
-rw-r--r-- | js/src/jit-test/tests/structured-clone/transferable-cleanup.js | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/structured-clone/transferable-cleanup.js b/js/src/jit-test/tests/structured-clone/transferable-cleanup.js new file mode 100644 index 0000000000..e55cb480f4 --- /dev/null +++ b/js/src/jit-test/tests/structured-clone/transferable-cleanup.js @@ -0,0 +1,243 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +// The tests expect the clone buffer to be collected at a certain point. +// I don't think collecting it sooner would break anything, but just in case: +gczeal(0); + +function testBasic() { + const desc = "basic canTransfer, writeTransfer, freeTransfer"; + const BASE = 100; + const obj = makeSerializable(BASE + 1); + let s = serialize(obj, [obj]); + assertEq("" + obj.log, "" + [101, "?", 101, "W"], "serialize " + desc); + s = null; + gc(); + print(`serialize ${arguments.callee.name}: ${obj.log}`); + assertEq("" + obj.log, "" + [ + // canTransfer(obj) then writeTransfer(obj), then obj is read with + // a backreference. + 101, "?", 101, "W", + // Then discard the serialized data, triggering freeTransfer(obj). + 101, "F" + ], "serialize " + desc); + obj.log = null; +} + +function testErrorDuringWrite() { + const desc = "canTransfer, write=>error"; + const BASE = 200; + const obj = makeSerializable(BASE + 1); + const ab = new ArrayBuffer(100); + detachArrayBuffer(ab); + try { + serialize([obj, ab], [obj]); + } catch (e) { + } + gc(); + print(`${arguments.callee.name}: ${obj.log}`); + assertEq("" + obj.log, "" + [201, "?"], desc); + obj.log = null; +} + +function testErrorDuringTransfer() { + const desc = "canTransfer, write(ab), writeTransfer(obj), writeTransfer(ab)=>error, freeTransfer"; + const BASE = 300; + const obj = makeSerializable(BASE + 1); + const ab = new ArrayBuffer(100); + detachArrayBuffer(ab); + try { + serialize([obj, ab], [obj, ab]); + } catch (e) { + } + gc(); + print(`${arguments.callee.name}: ${obj.log}`); + assertEq("" + obj.log, "" + [ + // canTransfer(obj) then writeTransfer(obj) + 301, "?", 301, "W", + // error reading ab, freeTransfer(obj) + 301, "F" + ], desc); + obj.log = null; +} + +function testMultiOkHelper(g, BASE, desc) { + const obj = makeSerializable(BASE + 1); + const obj2 = makeSerializable(BASE + 2); + const obj3 = makeSerializable(BASE + 3); + serialize([obj, obj2, obj3], [obj, obj3]); + gc(); + print(`${arguments.callee.name}(${BASE}): ${obj.log}`); + assertEq("" + obj.log, "" + [ + // canTransfer(obj then obj3). + BASE + 1, "?", BASE + 3, "?", + // write(obj2), which happens before transferring. + BASE + 2, "w", + // writeTransfer(obj then obj3). + BASE + 1, "W", BASE + 3, "W", + // discard the clone buffer without deserializing, so freeTransfer(obj1+obj3). + BASE + 1, "F", BASE + 3, "F" + ], desc); + obj.log = null; +} + +function testMultiOk() { + const desc = "write 3 objects, transfer obj1 and obj3 only, write obj2"; + testMultiOkHelper(globalThis, 400, desc); +} + +function testMultiOkCrossRealm() { + const desc = "write 3 objects, transfer obj1 and obj2 only, cross-realm"; + testMultiOkHelper(newGlobal({ newCompartment: true }), 500, desc); +} + +function printTrace(callee, global, base, log, phase = undefined) { + phase = phase ? `${phase} ` : ""; + const test = callee.replace("Helper", "") + (global === globalThis ? "" : "CrossRealm"); + print(`${phase}${test}(${base}): ${log}`); +} + +function testMultiOkWithDeserializeHelper(g, BASE, desc) { + const obj = makeSerializable(BASE + 1); + const obj2 = makeSerializable(BASE + 2); + const obj3 = makeSerializable(BASE + 3); + let s = serialize([obj, obj2, obj3], [obj, obj3]); + gc(); + printTrace(arguments.callee.name, g, BASE, obj.log, "serialize"); + assertEq("" + obj.log, "" + [ + // canTransfer(obj+obj3). + BASE + 1, "?", BASE + 3, "?", + // write(obj2). + BASE + 2, "w", + // writeTransfer(obj1+obj3). All good. + BASE + 1, "W", BASE + 3, "W", + // Do not collect the clone buffer. It owns obj1+obj3 now. + ], "serialize " + desc); + obj.log = null; + + let clone = deserialize(s); + s = null; + gc(); + printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize"); + assertEq("" + obj.log, "" + [ + // readTransfer(obj1+obj3). + BASE + 1, "R", BASE + 3, "R", + // read(obj2). The full clone is successful. + BASE + 2, "r", + ], "deserialize " + desc); + obj.log = null; +} + +function testMultiOkWithDeserialize() { + const desc = "write 3 objects, transfer obj1 and obj3 only, write obj2, deserialize"; + testMultiOkWithDeserializeHelper(globalThis, 600, desc); +} + +function testMultiOkWithDeserializeCrossRealm() { + const desc = "write 3 objects, transfer obj1 and obj2 only, deserialize, cross-realm"; + testMultiOkWithDeserializeHelper(newGlobal({ newCompartment: true }), 700, desc); +} + +function testMultiWithDeserializeReadTransferErrorHelper(g, BASE, desc) { + const obj = makeSerializable(BASE + 1, 0); + const obj2 = makeSerializable(BASE + 2, 1); + const obj3 = makeSerializable(BASE + 3, 1); + let s = serialize([obj, obj2, obj3], [obj, obj3]); + gc(); + printTrace(arguments.callee.name, g, BASE, obj.log, "serialize"); + assertEq("" + obj.log, "" + [ + // Successful serialization. + BASE + 1, "?", BASE + 3, "?", + BASE + 2, "w", + BASE + 1, "W", BASE + 3, "W", + ], "serialize " + desc); + obj.log = null; + + try { + let clone = deserialize(s); + } catch (e) { + assertEq(e.message.includes("invalid transferable"), true); + } + s = null; + gc(); + printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize"); + assertEq("" + obj.log, "" + [ + // readTransfer(obj) then readTransfer(obj3) which fails. + BASE + 1, "R", BASE + 3, "R", + // obj2 has not been read at all because we errored out during readTransferMap(), + // which comes before the main reading. obj transfer data is now owned by its + // clone. obj3 transfer data was not successfully handed over to a new object, + // so it is still owned by the clone buffer and must be discarded with freeTransfer. + BASE + 3, "F", + ], "deserialize " + desc); + obj.log = null; +} + +function testMultiWithDeserializeReadTransferError() { + const desc = "write 3 objects, transfer obj1 and obj3 only, fail during readTransfer(obj3)"; + testMultiWithDeserializeReadTransferErrorHelper(globalThis, 800, desc); +} + +function testMultiWithDeserializeReadTransferErrorCrossRealm() { + const desc = "write 3 objects, transfer obj1 and obj2 only, fail during readTransfer(obj3), cross-realm"; + testMultiWithDeserializeReadTransferErrorHelper(newGlobal({ newCompartment: true }), 900, desc); +} + +function testMultiWithDeserializeReadErrorHelper(g, BASE, desc) { + const obj = makeSerializable(BASE + 1, 2); + const obj2 = makeSerializable(BASE + 2, 2); + const obj3 = makeSerializable(BASE + 3, 2); + let s = serialize([obj, obj2, obj3], [obj, obj3]); + gc(); + printTrace(arguments.callee.name, g, BASE, obj.log, "serialize"); + assertEq("" + obj.log, "" + [ + // Same as above. Everything is fine. + BASE + 1, "?", BASE + 3, "?", + BASE + 2, "w", + BASE + 1, "W", BASE + 3, "W", + ], "serialize " + desc); + obj.log = null; + + try { + let clone = deserialize(s); + } catch (e) { + assertEq(e.message.includes("Failed as requested"), true); + } + s = null; + gc(); + printTrace(arguments.callee.name, g, BASE, obj.log, "deserialize"); + assertEq("" + obj.log, "" + [ + // The transferred objects will get restored via the readTransfer() hook + // and thus will own their transferred data. They will get discarded during + // the GC, but that doesn't call any hooks. When the GC collects `s`, + // it will look for any transferrable data to release, but will + // find that it no longer owns any such data and will do nothing. + BASE + 1, "R", BASE + 3, "R", + BASE + 2, "r", + ], "deserialize " + desc); + obj.log = null; +} + +function testMultiWithDeserializeReadError() { + const desc = "write 3 objects, transfer obj1 and obj3 only, fail during read(obj2)"; + testMultiWithDeserializeReadErrorHelper(globalThis, 1000, desc); +} + +function testMultiWithDeserializeReadErrorCrossRealm() { + const desc = "write 3 objects, transfer obj1 and obj2 only, fail during read(obj2), cross-realm"; + testMultiWithDeserializeReadErrorHelper(newGlobal({ newCompartment: true }), 1100, desc); +} + +testBasic(); +testErrorDuringWrite(); +testErrorDuringTransfer(); +testMultiOk(); +testMultiOkCrossRealm(); +testMultiOkWithDeserialize(); +testMultiOkWithDeserializeCrossRealm(); +testMultiWithDeserializeReadTransferError(); +testMultiWithDeserializeReadTransferErrorCrossRealm(); +testMultiWithDeserializeReadError(); +testMultiWithDeserializeReadErrorCrossRealm(); |