summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/structured-clone
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/jit-test/tests/structured-clone
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/structured-clone')
-rw-r--r--js/src/jit-test/tests/structured-clone/Map-Set-cross-compartment.js11
-rw-r--r--js/src/jit-test/tests/structured-clone/Map.js112
-rw-r--r--js/src/jit-test/tests/structured-clone/Set.js84
-rw-r--r--js/src/jit-test/tests/structured-clone/array-buffers.js80
-rw-r--r--js/src/jit-test/tests/structured-clone/bug1687243.js30
-rw-r--r--js/src/jit-test/tests/structured-clone/bug1875797.js2
-rw-r--r--js/src/jit-test/tests/structured-clone/errors.js139
-rw-r--r--js/src/jit-test/tests/structured-clone/growable-shared-array-buffers.js126
-rw-r--r--js/src/jit-test/tests/structured-clone/resizable-array-buffers-transferable.js37
-rw-r--r--js/src/jit-test/tests/structured-clone/resizable-array-buffers.js122
-rw-r--r--js/src/jit-test/tests/structured-clone/roundtrip.js30
-rw-r--r--js/src/jit-test/tests/structured-clone/sab-errMsg.js26
-rw-r--r--js/src/jit-test/tests/structured-clone/saved-stack.js37
-rw-r--r--js/src/jit-test/tests/structured-clone/tenuring.js92
-rw-r--r--js/src/jit-test/tests/structured-clone/transferable-across-segments.js13
-rw-r--r--js/src/jit-test/tests/structured-clone/transferable-cleanup.js243
-rw-r--r--js/src/jit-test/tests/structured-clone/version3.js13
17 files changed, 1197 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/structured-clone/Map-Set-cross-compartment.js b/js/src/jit-test/tests/structured-clone/Map-Set-cross-compartment.js
new file mode 100644
index 0000000000..8ce792562e
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/Map-Set-cross-compartment.js
@@ -0,0 +1,11 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+// Don't crash
+serialize(evalcx("new Set(['x', 'y'])"));
+serialize(evalcx("new Map([['x', 1]])"));
+
+assertEq(deserialize(serialize(evalcx("new Set([1, 2, 3])"))).has(1), true);
+assertEq(deserialize(serialize(evalcx("new Map([['x', 1]])"))).get('x'), 1);
diff --git a/js/src/jit-test/tests/structured-clone/Map.js b/js/src/jit-test/tests/structured-clone/Map.js
new file mode 100644
index 0000000000..51ac240961
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/Map.js
@@ -0,0 +1,112 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+load(libdir + "asserts.js");
+
+var map = new Map();
+map.set("self", map);
+
+var magic = deserialize(serialize(map));
+assertEq(magic.get("self"), magic);
+assertEq(magic.size, 1);
+
+map = new Map();
+map.set(map, "self");
+
+magic = deserialize(serialize(map));
+assertEq(magic.get(magic), "self");
+assertEq(magic.size, 1);
+
+var values = [
+ "a", "\uDEFF", undefined, null, -3.5, true, false, NaN, 155, -2
+]
+
+map = new Map();
+for (var value of values) {
+ map.set(value, value);
+}
+
+magic = deserialize(serialize(map));
+var i = 0;
+for (value of magic) {
+ assertEq(value[0], value[1]);
+ assertEq(value[0], values[i++]);
+}
+
+assertDeepEq([...map.keys()], [...magic.keys()]);
+assertDeepEq([...map.values()], [...magic.values()]);
+
+var obj = {a: 1};
+obj.map = new Map();
+obj.map.set("obj", obj);
+
+magic = deserialize(serialize(obj));
+
+assertEq(magic.map.get("obj"), magic);
+assertEq(magic.a, 1);
+
+map = new Map();
+map.set("a", new Number(1));
+map.set("b", new String("aaaa"));
+map.set("c", new Date(NaN));
+
+magic = deserialize(serialize(map));
+
+assertEq(magic.get("a").valueOf(), 1);
+assertEq(magic.get("b").valueOf(), "aaaa");
+assertEq(magic.get("c").valueOf(), NaN);
+
+assertDeepEq([...map.keys()], ["a", "b", "c"]);
+
+map = new Map();
+map.set("x", new Map());
+map.get("x").set("x", map);
+map.get("x").set("b", null);
+
+magic = deserialize(serialize(map));
+
+assertEq(magic.get("x").get("x"), magic);
+assertEq(magic.get("x").get("b"), null);
+
+map = new Map()
+map.set({a: 1}, "b");
+
+magic = deserialize(serialize(map));
+
+obj = [...magic.keys()][0];
+assertEq(obj.a, 1);
+assertEq(magic.get(obj), "b");
+
+// Make sure expandos aren't cloned (Bug 1041172)
+map = new Map();
+map.a = "aaaaa";
+magic = deserialize(serialize(map));
+assertEq("a" in magic, false);
+assertEq(Object.keys(magic).length, 0);
+
+// Busted [[Prototype]] shouldn't matter
+map = new Map();
+Object.setPrototypeOf(map, null);
+Map.prototype.set.call(map, "self", map);
+magic = deserialize(serialize(map));
+assertEq(magic.get("self"), magic);
+assertEq(magic.size, 1);
+
+// Can't fuzz around with Map after it is cloned
+obj = {
+ a: new Map(),
+ get b() {
+ obj.a.delete("test");
+ return "invoked";
+ }
+}
+obj.a.set("test", "hello");
+assertEq(obj.a.has("test"), true);
+magic = deserialize(serialize(obj));
+assertEq(obj.a.has("test"), false);
+assertEq(magic.a.size, 1);
+assertEq(magic.a.get("test"), "hello");
+assertEq([...magic.a.keys()].toString(), "test");
+assertEq(magic.b, "invoked"); \ No newline at end of file
diff --git a/js/src/jit-test/tests/structured-clone/Set.js b/js/src/jit-test/tests/structured-clone/Set.js
new file mode 100644
index 0000000000..76c1651715
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/Set.js
@@ -0,0 +1,84 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+load(libdir + "asserts.js");
+
+var set = new Set();
+set.add(set);
+
+var magic = deserialize(serialize(set));
+assertEq(magic.size, 1);
+assertEq(magic.values().next().value, magic);
+
+var values = [
+ "a", "\uDEFF", undefined, null, -3.5, true, false, NaN, 155, -2
+]
+
+set = new Set();
+for (var value of values) {
+ set.add(value)
+}
+
+magic = deserialize(serialize(set));
+var i = 0;
+for (value of magic) {
+ assertEq(value, values[i++]);
+}
+
+assertDeepEq([...set.keys()], [...magic.keys()]);
+assertDeepEq([...set.values()], [...magic.values()]);
+
+var obj = {a: 1};
+obj.set = new Set();
+obj.set.add(obj);
+
+magic = deserialize(serialize(obj));
+
+assertEq(magic.set.values().next().value, magic);
+assertEq(magic.a, 1);
+
+set = new Set();
+set.add(new Number(1));
+set.add(new String("aaaa"));
+set.add(new Date(NaN));
+
+magic = deserialize(serialize(set));
+
+values = magic.values();
+assertEq(values.next().value.valueOf(), 1);
+assertEq(values.next().value.valueOf(), "aaaa");
+assertEq(values.next().value.valueOf(), NaN);
+assertEq(values.next().done, true);
+
+// Make sure expandos aren't cloned (Bug 1041172)
+set = new Set();
+set.a = "aaaaa";
+magic = deserialize(serialize(set));
+assertEq("a" in magic, false);
+assertEq(Object.keys(magic).length, 0);
+
+// Busted [[Prototype]] shouldn't matter
+set = new Set();
+Object.setPrototypeOf(set, null);
+Set.prototype.add.call(set, "aaa");
+magic = deserialize(serialize(set));
+assertEq(magic.has("aaa"), true);
+assertEq(magic.size, 1);
+
+// Can't fuzz around with Set after it is cloned
+obj = {
+ a: new Set(),
+ get b() {
+ obj.a.delete("test");
+ return "invoked";
+ }
+}
+obj.a.add("test");
+assertEq(obj.a.has("test"), true);
+magic = deserialize(serialize(obj));
+assertEq(obj.a.has("test"), false);
+assertEq(magic.a.size, 1);
+assertEq([...magic.a.keys()].toString(), "test");
+assertEq(magic.b, "invoked"); \ No newline at end of file
diff --git a/js/src/jit-test/tests/structured-clone/array-buffers.js b/js/src/jit-test/tests/structured-clone/array-buffers.js
new file mode 100644
index 0000000000..fcb61f6eca
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/array-buffers.js
@@ -0,0 +1,80 @@
+// Tests for ArrayBuffer, TypedArray, and DataView encoding/decoding.
+
+var clonebuffer = serialize("dummy");
+
+// ========= V2 =========
+
+function testV2Int32Array() {
+ var buf = new Uint8Array([3,0,0,0,0,0,241,255,3,0,0,0,16,0,255,255,4,0,0,0,0,0,0,0,12,0,0,0,9,0,255,255,1,0,0,0,177,127,57,5,133,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0]);
+ clonebuffer.clonebuffer = buf.buffer;
+ var ta = deserialize(clonebuffer);
+ assertEq(ta instanceof Int32Array, true);
+ assertEq(ta.toString(), "1,87654321,-123");
+}
+testV2Int32Array();
+
+function testV2Float64Array() {
+ var buf = new Uint8Array([3,0,0,0,0,0,241,255,4,0,0,0,16,0,255,255,7,0,0,0,0,0,0,0,32,0,0,0,9,0,255,255,0,0,0,0,0,0,248,127,31,133,235,81,184,30,9,64,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]);
+ clonebuffer.clonebuffer = buf.buffer;
+ var ta = deserialize(clonebuffer);
+ assertEq(ta instanceof Float64Array, true);
+ assertEq(ta.toString(), "NaN,3.14,0,0");
+}
+testV2Float64Array();
+
+function testV2DataView() {
+ var buf = new Uint8Array([3,0,0,0,0,0,241,255,3,0,0,0,21,0,255,255,3,0,0,0,9,0,255,255,5,0,255,0,0,0,0,0,0,0,0,0,0,0,0,0]);
+ clonebuffer.clonebuffer = buf.buffer;
+ var dv = deserialize(clonebuffer);
+ assertEq(dv instanceof DataView, true);
+ assertEq(new Uint8Array(dv.buffer).toString(), "5,0,255");
+}
+testV2DataView();
+
+function testV2ArrayBuffer() {
+ var buf = new Uint8Array([3,0,0,0,0,0,241,255,4,0,0,0,9,0,255,255,33,44,55,66,0,0,0,0]);
+ clonebuffer.clonebuffer = buf.buffer;
+ var ab = deserialize(clonebuffer);
+ assertEq(ab instanceof ArrayBuffer, true);
+ assertEq(new Uint8Array(ab).toString(), "33,44,55,66");
+}
+testV2ArrayBuffer();
+
+// ========= Current =========
+
+function testInt32Array() {
+ var ta1 = new Int32Array([1, 87654321, -123]);
+ var clonebuf = serialize(ta1, undefined, {scope: "DifferentProcessForIndexedDB"});
+ var ta2 = deserialize(clonebuf);
+ assertEq(ta2 instanceof Int32Array, true);
+ assertEq(ta2.toString(), "1,87654321,-123");
+}
+testInt32Array();
+
+function testFloat64Array() {
+ var ta1 = new Float64Array([NaN, 3.14, 0, 0]);
+ var clonebuf = serialize(ta1, undefined, {scope: "DifferentProcessForIndexedDB"});
+ var ta2 = deserialize(clonebuf);
+ assertEq(ta2 instanceof Float64Array, true);
+ assertEq(ta2.toString(), "NaN,3.14,0,0");
+}
+testFloat64Array();
+
+function testDataView() {
+ var ta = new Uint8Array([5, 0, 255]);
+ var dv1 = new DataView(ta.buffer);
+ var clonebuf = serialize(dv1, undefined, {scope: "DifferentProcessForIndexedDB"});
+ var dv2 = deserialize(clonebuf);
+ assertEq(dv2 instanceof DataView, true);
+ assertEq(new Uint8Array(dv2.buffer).toString(), "5,0,255");
+}
+testDataView();
+
+function testArrayBuffer() {
+ var ta = new Uint8Array([33, 44, 55, 66]);
+ var clonebuf = serialize(ta.buffer, undefined, {scope: "DifferentProcessForIndexedDB"});
+ var ab = deserialize(clonebuf);
+ assertEq(ab instanceof ArrayBuffer, true);
+ assertEq(new Uint8Array(ab).toString(), "33,44,55,66");
+}
+testArrayBuffer();
diff --git a/js/src/jit-test/tests/structured-clone/bug1687243.js b/js/src/jit-test/tests/structured-clone/bug1687243.js
new file mode 100644
index 0000000000..13b7b83a07
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/bug1687243.js
@@ -0,0 +1,30 @@
+let invalidTypedArrays = [
+ // Uint8Array with invalid length.
+ [3,0,0,0,0,0,241,255,0,0,0,0,32,0,255,255,3,0,0,0,0,0,0,255,0,0,0,0,31,0,255,255,3,0,0,0,0,0,0,0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0],
+ // Uint8Array with invalid byteOffset.
+ [3,0,0,0,0,0,241,255,0,0,0,0,32,0,255,255,3,0,0,0,0,0,0,0,0,0,0,0,31,0,255,255,3,0,0,0,0,0,0,0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,1],
+];
+let invalidDataViews = [
+ // DataView with invalid length.
+ [3,0,0,0,0,0,241,255,0,0,0,0,33,0,255,255,3,0,0,0,0,0,0,255,0,0,0,0,31,0,255,255,3,0,0,0,0,0,0,0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0],
+ // DataView with invalid byteOffset.
+ [3,0,0,0,0,0,241,255,0,0,0,0,33,0,255,255,3,0,0,0,0,0,0,0,0,0,0,0,31,0,255,255,3,0,0,0,0,0,0,0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,1],
+];
+function checkError(data, message) {
+ let clonebuffer = serialize("dummy");
+ let buf = new Uint8Array(data);
+ clonebuffer.clonebuffer = buf.buffer;
+ let ex = null;
+ try {
+ deserialize(clonebuffer);
+ } catch (e) {
+ ex = e;
+ }
+ assertEq(ex.toString().includes(message), true);
+}
+for (let data of invalidTypedArrays) {
+ checkError(data, "invalid typed array length or offset");
+}
+for (let data of invalidDataViews) {
+ checkError(data, "invalid DataView length or offset");
+}
diff --git a/js/src/jit-test/tests/structured-clone/bug1875797.js b/js/src/jit-test/tests/structured-clone/bug1875797.js
new file mode 100644
index 0000000000..b512ff36df
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/bug1875797.js
@@ -0,0 +1,2 @@
+a = makeSerializable();
+serialize(a, [a]);
diff --git a/js/src/jit-test/tests/structured-clone/errors.js b/js/src/jit-test/tests/structured-clone/errors.js
new file mode 100644
index 0000000000..6e72d46b7c
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/errors.js
@@ -0,0 +1,139 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+load(libdir + "asserts.js");
+
+function roundtrip(error) {
+ return deserialize(serialize(error, []));
+}
+
+// Basic
+{
+ let error = new Error("hello world");
+ let cloned = roundtrip(error);
+
+ assertDeepEq(cloned, error);
+ assertEq(cloned.name, "Error");
+ assertEq(cloned.message, "hello world");
+ assertEq(cloned.stack, error.stack);
+}
+
+let constructors = [Error, EvalError, RangeError, ReferenceError,
+ SyntaxError, TypeError, URIError];
+for (let constructor of constructors) {
+ // With message
+ let error = new constructor("hello");
+ let cloned = roundtrip(error);
+ assertDeepEq(cloned, error);
+ assertEq(cloned.hasOwnProperty('message'), true);
+ assertEq(cloned instanceof constructor, true);
+
+ // Without message
+ error = new constructor();
+ cloned = roundtrip(error);
+ assertDeepEq(cloned, error);
+ assertEq(cloned.hasOwnProperty('message'), false);
+ assertEq(cloned instanceof constructor, true);
+
+ // Custom name
+ error = new constructor("hello");
+ error.name = "MyError";
+ cloned = roundtrip(error);
+ assertEq(cloned.name, "Error");
+ assertEq(cloned.message, "hello");
+ assertEq(cloned.stack, error.stack);
+ if (constructor !== Error) {
+ assertEq(cloned instanceof constructor, false);
+ }
+
+ // |cause| property
+ error = new constructor("hello", { cause: new Error("foobar") });
+ cloned = roundtrip(error);
+ assertDeepEq(cloned, error);
+ assertEq(cloned.hasOwnProperty('message'), true);
+ assertEq(cloned instanceof constructor, true);
+ assertEq(cloned.stack, error.stack);
+ assertEq(cloned.stack === undefined, false);
+
+ // |cause| property, manually added after construction.
+ error = new constructor("hello");
+ error.cause = new Error("foobar");
+ assertDeepEq(Object.getOwnPropertyDescriptor(error, "cause"), {
+ value: error.cause,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ });
+ cloned = roundtrip(error);
+ assertDeepEq(Object.getOwnPropertyDescriptor(cloned, "cause"), {
+ value: cloned.cause,
+ writable: true,
+ enumerable: false, // Non-enumerable in the cloned object!
+ configurable: true,
+ });
+ assertEq(cloned.hasOwnProperty('message'), true);
+ assertEq(cloned instanceof constructor, true);
+ assertEq(cloned.stack, error.stack);
+ assertEq(cloned.stack === undefined, false);
+
+ // Subclassing
+ error = new (class MyError extends constructor {});
+ cloned = roundtrip(error);
+ assertEq(cloned.name, constructor.name);
+ assertEq(cloned.hasOwnProperty('message'), false);
+ assertEq(cloned.stack, error.stack);
+ assertEq(cloned instanceof Error, true);
+
+ // Cross-compartment
+ error = evalcx(`new ${constructor.name}("hello")`);
+ cloned = roundtrip(error);
+ assertEq(cloned.name, constructor.name);
+ assertEq(cloned.message, "hello");
+ assertEq(cloned.stack, error.stack);
+ assertEq(cloned instanceof constructor, true);
+}
+
+// Non-string message
+{
+ let error = new Error("hello world");
+ error.message = 123;
+ let cloned = roundtrip(error);
+ assertEq(cloned.message, "123");
+ assertEq(cloned.hasOwnProperty('message'), true);
+
+ error = new Error();
+ Object.defineProperty(error, 'message', { get: () => {} });
+ cloned = roundtrip(error);
+ assertEq(cloned.message, "");
+ assertEq(cloned.hasOwnProperty('message'), false);
+}
+
+// AggregateError
+{
+ // With message
+ let error = new AggregateError([{a: 1}, {b: 2}], "hello");
+ let cloned = roundtrip(error);
+ assertDeepEq(cloned, error);
+ assertEq(cloned.hasOwnProperty('message'), true);
+ assertEq(cloned instanceof AggregateError, true);
+
+ // Without message
+ error = new AggregateError([{a: 1}, {b: 2}]);
+ cloned = roundtrip(error);
+ assertDeepEq(cloned, error);
+ assertEq(cloned.hasOwnProperty('message'), false);
+ assertEq(cloned instanceof AggregateError, true);
+
+ // Custom name breaks this!
+ error = new AggregateError([{a: 1}, {b: 2}]);
+ error.name = "MyError";
+ cloned = roundtrip(error);
+ assertEq(cloned.name, "Error");
+ assertEq(cloned.message, "");
+ assertEq(cloned.stack, error.stack);
+ assertEq(cloned instanceof AggregateError, false);
+ assertEq(cloned.errors, undefined);
+ assertEq(cloned.hasOwnProperty('errors'), false);
+}
diff --git a/js/src/jit-test/tests/structured-clone/growable-shared-array-buffers.js b/js/src/jit-test/tests/structured-clone/growable-shared-array-buffers.js
new file mode 100644
index 0000000000..f4eba68231
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/growable-shared-array-buffers.js
@@ -0,0 +1,126 @@
+// |jit-test| --enable-arraybuffer-resizable; skip-if: !SharedArrayBuffer.prototype.grow
+
+const scopes = [
+ "SameProcess",
+];
+
+const g = newGlobal({enableCoopAndCoep: true});
+
+const options = {
+ SharedArrayBuffer: "allow",
+};
+
+function testInt32Array(scope) {
+ var length = 4;
+ var byteLength = length * Int32Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new SharedArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.growable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta1 = new Int32Array(ab);
+ assertEq(ta1.byteLength, byteLength);
+ ta1.set([1, 87654321, -123]);
+ assertEq(ta1.toString(), "1,87654321,-123,0");
+
+ var clonebuf = g.serialize(ta1, undefined, {scope, ...options});
+ var ta2 = g.deserialize(clonebuf, {...options});
+ assertEq(ta2 instanceof g.Int32Array, true);
+ assertEq(ta2.byteLength, byteLength);
+ assertEq(ta2.toString(), "1,87654321,-123,0");
+ assertEq(ta2.buffer.growable, true);
+ assertEq(ta2.buffer.byteLength, byteLength);
+ assertEq(ta2.buffer.maxByteLength, maxByteLength);
+
+ ta2.buffer.grow(maxByteLength);
+ assertEq(ta2.byteLength, maxByteLength);
+}
+scopes.forEach(testInt32Array);
+
+function testFloat64Array(scope) {
+ var length = 4;
+ var byteLength = length * Float64Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new SharedArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.growable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta1 = new Float64Array(ab);
+ assertEq(ta1.byteLength, byteLength);
+ ta1.set([NaN, 3.14, 0, 0]);
+ assertEq(ta1.toString(), "NaN,3.14,0,0");
+
+ var clonebuf = g.serialize(ta1, undefined, {scope, ...options});
+ var ta2 = g.deserialize(clonebuf, {...options});
+ assertEq(ta2 instanceof g.Float64Array, true);
+ assertEq(ta2.byteLength, byteLength);
+ assertEq(ta2.toString(), "NaN,3.14,0,0");
+ assertEq(ta2.buffer.growable, true);
+ assertEq(ta2.buffer.byteLength, byteLength);
+ assertEq(ta2.buffer.maxByteLength, maxByteLength);
+
+ ta2.buffer.grow(maxByteLength);
+ assertEq(ta2.byteLength, maxByteLength);
+}
+scopes.forEach(testFloat64Array);
+
+function testDataView(scope) {
+ var length = 4;
+ var byteLength = length * Uint8Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new SharedArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.growable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta = new Uint8Array(ab);
+ ta.set([5, 0, 255]);
+ assertEq(ta.toString(), "5,0,255,0");
+ var dv1 = new DataView(ab);
+ assertEq(dv1.byteLength, byteLength);
+
+ var clonebuf = g.serialize(dv1, undefined, {scope, ...options});
+ var dv2 = g.deserialize(clonebuf, {...options});
+ assertEq(dv2 instanceof g.DataView, true);
+ assertEq(dv2.byteLength, byteLength);
+ assertEq(new Uint8Array(dv2.buffer).toString(), "5,0,255,0");
+ assertEq(dv2.buffer.growable, true);
+ assertEq(dv2.buffer.byteLength, byteLength);
+ assertEq(dv2.buffer.maxByteLength, maxByteLength);
+
+ dv2.buffer.grow(maxByteLength);
+ assertEq(dv2.byteLength, maxByteLength);
+}
+scopes.forEach(testDataView);
+
+function testArrayBuffer(scope) {
+ var length = 4;
+ var byteLength = length * Uint8Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new SharedArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.growable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta = new Uint8Array(ab);
+ ta.set([33, 44, 55, 66]);
+ assertEq(ta.toString(), "33,44,55,66");
+
+ var clonebuf = g.serialize(ab, undefined, {scope, ...options});
+ var ab2 = g.deserialize(clonebuf, {...options});
+ assertEq(ab2 instanceof g.SharedArrayBuffer, true);
+ assertEq(new Uint8Array(ab2).toString(), "33,44,55,66");
+ assertEq(ab2.growable, true);
+ assertEq(ab2.byteLength, byteLength);
+ assertEq(ab2.maxByteLength, maxByteLength);
+
+ ab2.grow(maxByteLength);
+ assertEq(ab2.byteLength, maxByteLength);
+}
+scopes.forEach(testArrayBuffer);
diff --git a/js/src/jit-test/tests/structured-clone/resizable-array-buffers-transferable.js b/js/src/jit-test/tests/structured-clone/resizable-array-buffers-transferable.js
new file mode 100644
index 0000000000..dd7c7fb075
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/resizable-array-buffers-transferable.js
@@ -0,0 +1,37 @@
+// |jit-test| --enable-arraybuffer-resizable; skip-if: !ArrayBuffer.prototype.resize
+
+const scopes = [
+ "SameProcess",
+ "DifferentProcess",
+ "DifferentProcessForIndexedDB",
+];
+
+function testArrayBufferTransferable(scope) {
+ var length = 4;
+ var byteLength = length * Uint8Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new ArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.resizable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta = new Uint8Array(ab);
+ ta.set([33, 44, 55, 66]);
+ assertEq(ta.toString(), "33,44,55,66");
+
+ var clonebuf = serialize(ab, [ab], {scope});
+ var ab2 = deserialize(clonebuf);
+ assertEq(ab2 instanceof ArrayBuffer, true);
+ assertEq(new Uint8Array(ab2).toString(), "33,44,55,66");
+ assertEq(ab2.resizable, true);
+ assertEq(ab2.byteLength, byteLength);
+ assertEq(ab2.maxByteLength, maxByteLength);
+
+ assertEq(ab.detached, true);
+ assertEq(ab2.detached, false);
+
+ ab2.resize(maxByteLength);
+ assertEq(ab2.byteLength, maxByteLength);
+}
+scopes.forEach(testArrayBufferTransferable);
diff --git a/js/src/jit-test/tests/structured-clone/resizable-array-buffers.js b/js/src/jit-test/tests/structured-clone/resizable-array-buffers.js
new file mode 100644
index 0000000000..bc435f92a0
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/resizable-array-buffers.js
@@ -0,0 +1,122 @@
+// |jit-test| --enable-arraybuffer-resizable; skip-if: !ArrayBuffer.prototype.resize
+
+const scopes = [
+ "SameProcess",
+ "DifferentProcess",
+ "DifferentProcessForIndexedDB",
+];
+
+function testInt32Array(scope) {
+ var length = 4;
+ var byteLength = length * Int32Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new ArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.resizable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta1 = new Int32Array(ab);
+ assertEq(ta1.byteLength, byteLength);
+ ta1.set([1, 87654321, -123]);
+ assertEq(ta1.toString(), "1,87654321,-123,0");
+
+ var clonebuf = serialize(ta1, undefined, {scope});
+ var ta2 = deserialize(clonebuf);
+ assertEq(ta2 instanceof Int32Array, true);
+ assertEq(ta2.byteLength, byteLength);
+ assertEq(ta2.toString(), "1,87654321,-123,0");
+ assertEq(ta2.buffer.resizable, true);
+ assertEq(ta2.buffer.byteLength, byteLength);
+ assertEq(ta2.buffer.maxByteLength, maxByteLength);
+
+ ta2.buffer.resize(maxByteLength);
+ assertEq(ta2.byteLength, maxByteLength);
+}
+scopes.forEach(testInt32Array);
+
+function testFloat64Array(scope) {
+ var length = 4;
+ var byteLength = length * Float64Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new ArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.resizable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta1 = new Float64Array(ab);
+ assertEq(ta1.byteLength, byteLength);
+ ta1.set([NaN, 3.14, 0, 0]);
+ assertEq(ta1.toString(), "NaN,3.14,0,0");
+
+ var clonebuf = serialize(ta1, undefined, {scope});
+ var ta2 = deserialize(clonebuf);
+ assertEq(ta2 instanceof Float64Array, true);
+ assertEq(ta2.byteLength, byteLength);
+ assertEq(ta2.toString(), "NaN,3.14,0,0");
+ assertEq(ta2.buffer.resizable, true);
+ assertEq(ta2.buffer.byteLength, byteLength);
+ assertEq(ta2.buffer.maxByteLength, maxByteLength);
+
+ ta2.buffer.resize(maxByteLength);
+ assertEq(ta2.byteLength, maxByteLength);
+}
+scopes.forEach(testFloat64Array);
+
+function testDataView(scope) {
+ var length = 4;
+ var byteLength = length * Uint8Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new ArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.resizable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta = new Uint8Array(ab);
+ ta.set([5, 0, 255]);
+ assertEq(ta.toString(), "5,0,255,0");
+ var dv1 = new DataView(ab);
+ assertEq(dv1.byteLength, byteLength);
+
+ var clonebuf = serialize(dv1, undefined, {scope});
+ var dv2 = deserialize(clonebuf);
+ assertEq(dv2 instanceof DataView, true);
+ assertEq(dv2.byteLength, byteLength);
+ assertEq(new Uint8Array(dv2.buffer).toString(), "5,0,255,0");
+ assertEq(dv2.buffer.resizable, true);
+ assertEq(dv2.buffer.byteLength, byteLength);
+ assertEq(dv2.buffer.maxByteLength, maxByteLength);
+
+ dv2.buffer.resize(maxByteLength);
+ assertEq(dv2.byteLength, maxByteLength);
+}
+scopes.forEach(testDataView);
+
+function testArrayBuffer(scope) {
+ var length = 4;
+ var byteLength = length * Uint8Array.BYTES_PER_ELEMENT;
+ var maxByteLength = 2 * byteLength;
+
+ var ab = new ArrayBuffer(byteLength, {maxByteLength});
+ assertEq(ab.resizable, true);
+ assertEq(ab.byteLength, byteLength);
+ assertEq(ab.maxByteLength, maxByteLength);
+
+ var ta = new Uint8Array(ab);
+ ta.set([33, 44, 55, 66]);
+ assertEq(ta.toString(), "33,44,55,66");
+
+ var clonebuf = serialize(ab, undefined, {scope});
+ var ab2 = deserialize(clonebuf);
+ assertEq(ab2 instanceof ArrayBuffer, true);
+ assertEq(new Uint8Array(ab2).toString(), "33,44,55,66");
+ assertEq(ab2.resizable, true);
+ assertEq(ab2.byteLength, byteLength);
+ assertEq(ab2.maxByteLength, maxByteLength);
+
+ ab2.resize(maxByteLength);
+ assertEq(ab2.byteLength, maxByteLength);
+}
+scopes.forEach(testArrayBuffer);
diff --git a/js/src/jit-test/tests/structured-clone/roundtrip.js b/js/src/jit-test/tests/structured-clone/roundtrip.js
new file mode 100644
index 0000000000..ef7732ea07
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/roundtrip.js
@@ -0,0 +1,30 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+load(libdir + "asserts.js");
+
+const objects = [
+ {},
+ {a: 1, b: 2},
+ {0: 1, 1: 2},
+ {0: 1, 1: 2, a: 1},
+ {0: 1, 1: 2, a: 1, b: 2},
+ {1000000: 0, 1000001: 1},
+ {0: 0, 1: 0, 1000000: 0, 1000001: 1},
+
+ [],
+ [0, 1, 2],
+ [0, 15, 16],
+ [{a: 0, b: 0}, {b: 0, a: 0}],
+ [0, , , 1, 2],
+ [, 1],
+ [0,,],
+ [,,],
+]
+
+for (const obj of objects) {
+ assertDeepEq(deserialize(serialize(obj)), obj);
+ assertDeepEq(deserialize(serialize(wrapWithProto(obj, null))), obj);
+}
diff --git a/js/src/jit-test/tests/structured-clone/sab-errMsg.js b/js/src/jit-test/tests/structured-clone/sab-errMsg.js
new file mode 100644
index 0000000000..ea8c7de0fd
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/sab-errMsg.js
@@ -0,0 +1,26 @@
+// |jit-test| skip-if: !sharedMemoryEnabled()
+
+// Check the error mssage when the prefs for COOP/COEP are both enable or not.
+var g = newGlobal();
+var ex;
+const sab = new SharedArrayBuffer();
+try {
+ g.serialize(sab);
+} catch (e) {
+ ex = e;
+}
+assertEq(ex.toString(),
+ `TypeError: The SharedArrayBuffer object cannot be serialized. The ` +
+ `Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP ` +
+ `headers will enable this in the future.`);
+
+var h = newGlobal({enableCoopAndCoep: true});
+try {
+ h.serialize(sab);
+} catch (e) {
+ ex = e;
+}
+assertEq(ex.toString(),
+ `TypeError: The SharedArrayBuffer object cannot be serialized. The ` +
+ `Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy HTTP ` +
+ `headers can be used to enable this.`);
diff --git a/js/src/jit-test/tests/structured-clone/saved-stack.js b/js/src/jit-test/tests/structured-clone/saved-stack.js
new file mode 100644
index 0000000000..dd2d4a3240
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/saved-stack.js
@@ -0,0 +1,37 @@
+// The following binary data was created with:
+// JS_STRUCTURED_CLONE_VERSION = 8
+//
+// ```
+// function f() {
+// return saveStack();
+// }
+// function g() {
+// return f();
+// }
+//
+// let stack = g();
+// print(valueToSource(serialize(stack, undefined, {scope: "DifferentProcess"}).clonebuffer))
+// ```
+
+function checkStack(stack) {
+ print(stack.toString());
+
+ assertEq(stack.functionDisplayName, "f");
+ assertEq(stack.parent.functionDisplayName, "g");
+ assertEq(stack.parent.parent.functionDisplayName, null);
+ assertEq(stack.parent.parent.parent, null);
+}
+
+var clonebuffer = serialize("dummy");
+clonebuffer.clonebuffer = "\x02\x00\x00\x00\x00\x00\xF1\xFF\x18\x00\xFF\xFF\x16\x00\xFF\xFF \x00\x00\x80\x04\x00\xFF\xFF/home/tom/Desktop/saved-stack.js\x11\x00\x00\x00\x03\x00\xFF\xFF\t\x00\x00\x00\x03\x00\xFF\xFF\x01\x00\x00\x80\x04\x00\xFF\xFFf\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x18\x00\xFF\xFF\x16\x00\xFF\xFF \x00\x00\x80\x04\x00\xFF\xFF/home/tom/Desktop/saved-stack.js\x14\x00\x00\x00\x03\x00\xFF\xFF\t\x00\x00\x00\x03\x00\xFF\xFF\x01\x00\x00\x80\x04\x00\xFF\xFFg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\x18\x00\xFF\xFF\x16\x00\xFF\xFF \x00\x00\x80\x04\x00\xFF\xFF/home/tom/Desktop/saved-stack.js\x17\x00\x00\x00\x03\x00\xFF\xFF\r\x00\x00\x00\x03\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\xFF\xFF\x00\x00\x00\x00\x13\x00\xFF\xFF\x00\x00\x00\x00\x13\x00\xFF\xFF\x00\x00\x00\x00\x13\x00\xFF\xFF";
+var stack = deserialize(clonebuffer);
+checkStack(stack);
+
+function f() {
+ return saveStack();
+}
+function g() {
+ return f();
+}
+stack = deserialize(serialize(g()));
+checkStack(stack);
diff --git a/js/src/jit-test/tests/structured-clone/tenuring.js b/js/src/jit-test/tests/structured-clone/tenuring.js
new file mode 100644
index 0000000000..0fffa064fa
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/tenuring.js
@@ -0,0 +1,92 @@
+// Check that we switch to allocating in the tenure heap after the first
+// nursery collection.
+
+function buildObjectTree(depth) {
+ if (depth === 0) {
+ return "leaf";
+ }
+
+ return {
+ left: buildObjectTree(depth - 1),
+ right: buildObjectTree(depth - 1)
+ };
+}
+
+function buildArrayTree(depth) {
+ if (depth === 0) {
+ return [];
+ }
+
+ return [
+ buildArrayTree(depth - 1),
+ buildArrayTree(depth - 1)
+ ];
+}
+
+function testRoundTrip(depth, objectTree, expectedNurseryAllocated) {
+ const input = objectTree ? buildObjectTree(depth) : buildArrayTree(depth);
+
+ gc();
+ const initialMinorNumber = gcparam('minorGCNumber');
+
+ const output = deserialize(serialize(input));
+ checkHeap(output, depth, objectTree, expectedNurseryAllocated);
+
+ const minorCollections = gcparam('minorGCNumber') - initialMinorNumber;
+ const expectedMinorCollections = expectedNurseryAllocated ? 0 : 1;
+ assertEq(minorCollections, expectedMinorCollections);
+}
+
+function checkHeap(tree, depth, objectTree, expectedNurseryAllocated) {
+ const counts = countHeapLocations(tree, objectTree);
+
+ const total = counts.nursery + counts.tenured;
+ assertEq(total, (2 ** (depth + 1)) - 1);
+
+ if (expectedNurseryAllocated) {
+ assertEq(counts.tenured, 0);
+ assertEq(counts.nursery >= 1, true);
+ } else {
+ assertEq(counts.tenured >= 1, true);
+ // We get a single nursery allocation when we trigger minor GC.
+ assertEq(counts.nursery <= 1, true);
+ }
+}
+
+function countHeapLocations(tree, objectTree, counts) {
+ if (!counts) {
+ counts = {nursery: 0, tenured: 0};
+ }
+
+ if (isNurseryAllocated(tree)) {
+ counts.nursery++;
+ } else {
+ counts.tenured++;
+ }
+
+ if (objectTree) {
+ if (tree !== "leaf") {
+ countHeapLocations(tree.left, objectTree, counts);
+ countHeapLocations(tree.right, objectTree, counts);
+ }
+ } else {
+ if (tree.length !== 0) {
+ countHeapLocations(tree[0], objectTree, counts);
+ countHeapLocations(tree[1], objectTree, counts);
+ }
+ }
+
+ return counts;
+}
+
+gczeal(0);
+gcparam('minNurseryBytes', 1024 * 1024);
+gcparam('maxNurseryBytes', 1024 * 1024);
+gc();
+
+testRoundTrip(1, true, true);
+testRoundTrip(1, false, true);
+testRoundTrip(4, true, true);
+testRoundTrip(4, false, true);
+testRoundTrip(15, true, false);
+testRoundTrip(15, false, false);
diff --git a/js/src/jit-test/tests/structured-clone/transferable-across-segments.js b/js/src/jit-test/tests/structured-clone/transferable-across-segments.js
new file mode 100644
index 0000000000..9b31260ae7
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/transferable-across-segments.js
@@ -0,0 +1,13 @@
+// Default capacity is 4096 bytes and each entry requires 24 bytes, so when
+// the transferables list contains >170 entries, more than one segment is
+// used, because 171 * 24 = 4104 and 4104 > 4096.
+
+const transferables = [];
+for (let i = 0; i < 170 + 1; ++i) {
+ transferables.push(new ArrayBuffer(1));
+}
+
+// Just don't crash.
+serialize([], transferables, {
+ scope: "DifferentProcess",
+});
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();
diff --git a/js/src/jit-test/tests/structured-clone/version3.js b/js/src/jit-test/tests/structured-clone/version3.js
new file mode 100644
index 0000000000..c3b3041ad6
--- /dev/null
+++ b/js/src/jit-test/tests/structured-clone/version3.js
@@ -0,0 +1,13 @@
+// Created with JS_STRUCTURED_CLONE_VERSION = 3
+// var x = {
+// "ab": 1,
+// 12: 2,
+// };
+// print(uneval(serialize(x).clonebuffer));
+
+var clonebuffer = serialize("abc");
+clonebuffer.clonebuffer = "\x00\x00\x00\x00\b\x00\xFF\xFF\f\x00\x00\x00\x03\x00\xFF\xFF\x00\x00\x00\x00\x00\x00\x00@\x02\x00\x00\x00\x04\x00\xFF\xFFa\x00b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF0?\x00\x00\x00\x00\x00\x00\xFF\xFF"
+var obj = deserialize(clonebuffer)
+assertEq(obj.ab, 1);
+assertEq(obj[12], 2);
+assertEq(Object.keys(obj).toString(), "12,ab");