summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/structured-clone/transferable-cleanup.js
diff options
context:
space:
mode:
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.js243
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();