diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jit-test/tests/structured-clone | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit-test/tests/structured-clone')
12 files changed, 818 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/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/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/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"); |