Skip to content
Permalink
Browse files
Serialize and deserialize resizable ArrayBuffer
https://bugs.webkit.org/show_bug.cgi?id=248209
rdar://102601423

Reviewed by Ross Kirsling.

This patch adds serializing and deserializing of resizable ArrayBuffer and TypedArrays.
We add ResizableArrayBufferTag and add a feature serializing resizable ArrayBuffer.
But for growable SharedArrayBuffer, nothing is necessary since information is carried via
SharedArrayBufferContents already. For TypedArrays, we use UINT64_MAX byteLength marker
as a auto-length case. This works since byteLength cannot be UINT64_MAX since it exceeds
MAX_ARRAY_BUFFER_SIZE. The other things in TypedArrays are not changed much since these
TypedArrays should be resizable / growable ones when the subsequent backing serialized
ArrayBuffer is resizable.

We also add wrappedAs methods since normal tryCreate has more additional checks for construction.
But these checks can fail if the serialized TypedArrays are having ArrayBuffers which is resized
to be smaller after the construction. But this is OK since it just makes TypedArrays OOB. wrappedAs
methods do not have this check.

* Source/JavaScriptCore/runtime/ArrayBufferView.h:
(JSC::ArrayBufferView::byteOffsetRaw const):
(JSC::ArrayBufferView::byteOffset const):
(JSC::ArrayBufferView::byteLengthRaw const):
(JSC::ArrayBufferView::byteLength const):
* Source/WebCore/bindings/js/SerializedScriptValue.cpp:
(WebCore::CloneSerializer::dumpArrayBufferView):
(WebCore::CloneSerializer::dumpIfTerminal):
(WebCore::CloneDeserializer::readResizableNonSharedArrayBuffer):
(WebCore::CloneDeserializer::readArrayBufferViewImpl):
(WebCore::CloneDeserializer::readTerminal):

Canonical link: https://commits.webkit.org/256998@main
  • Loading branch information
Constellation committed Nov 24, 2022
1 parent da5d5fa commit a9510aedf5179f92aa036b67aea7f5f9e8b0a24f
Show file tree
Hide file tree
Showing 27 changed files with 704 additions and 55 deletions.
@@ -0,0 +1,19 @@
Resizable ArrayBuffers should be serializable

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS cloned.resizable is true
PASS cloned.byteLength is 36
PASS cloned.maxByteLength is 128
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS cloned.byteLength is 128
PASS cloned.maxByteLength is 128
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,26 @@
<!DOCTYPE HTML><!-- webkit-test-runner [ jscOptions=--useResizableArrayBuffer=true,--useSharedArrayBuffer=true ] -->
<html>
<head>
<title>Resizable ArrayBuffers serialization</title>
<script src="../../resources/js-test-pre.js"></script>
</head>
<body>
<script>
description("Resizable ArrayBuffers should be serializable");
var arrayBuffer = new ArrayBuffer(36, { maxByteLength: 128 });
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
var cloned = structuredClone(arrayBuffer);
shouldBeTrue(`cloned.resizable`);
shouldBe(`cloned.byteLength`, `36`);
shouldBe(`cloned.maxByteLength`, `128`);
cloned.resize(128);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`cloned.byteLength`, `128`);
shouldBe(`cloned.maxByteLength`, `128`);
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
@@ -0,0 +1,22 @@
Resizable ArrayBuffers should be serializable

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS int32AutoArray.length is 8
PASS int32AutoArray.byteOffset is 4
PASS cloned.buffer.resizable is true
PASS cloned.buffer.byteLength is 36
PASS cloned.buffer.maxByteLength is 128
PASS cloned.length is 8
PASS cloned.byteOffset is 4
PASS cloned.buffer.byteLength is 128
PASS cloned.buffer.maxByteLength is 128
PASS cloned.length is 31
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,24 @@
Resizable ArrayBuffers should be serializable OOB

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS int32AutoArray.length is 8
PASS int32AutoArray.byteOffset is 4
PASS cloned.buffer.resizable is true
PASS cloned.buffer.byteLength is 0
PASS cloned.buffer.maxByteLength is 128
PASS cloned.length is 0
PASS cloned.byteOffset is 0
PASS cloned[0] is undefined
PASS cloned.buffer.byteLength is 128
PASS cloned.buffer.maxByteLength is 128
PASS cloned.length is 31
PASS cloned.byteOffset is 4
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,24 @@
Resizable ArrayBuffers should be serializable OOB explicit length

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS int32Array.length is 4
PASS int32Array.byteOffset is 4
PASS cloned.buffer.resizable is true
PASS cloned.buffer.byteLength is 0
PASS cloned.buffer.maxByteLength is 128
PASS cloned.length is 0
PASS cloned.byteOffset is 0
PASS cloned[0] is undefined
PASS cloned.buffer.byteLength is 128
PASS cloned.buffer.maxByteLength is 128
PASS cloned.length is 4
PASS cloned.byteOffset is 4
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,33 @@
<!DOCTYPE HTML><!-- webkit-test-runner [ jscOptions=--useResizableArrayBuffer=true,--useSharedArrayBuffer=true ] -->
<html>
<head>
<title>Resizable ArrayBuffers serialization</title>
<script src="../../resources/js-test-pre.js"></script>
</head>
<body>
<script>
description("Resizable ArrayBuffers should be serializable OOB explicit length");
var arrayBuffer = new ArrayBuffer(36, { maxByteLength: 128 });
var int32Array = new Int32Array(arrayBuffer, 4, 4);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`int32Array.length`, `4`);
shouldBe(`int32Array.byteOffset`, `4`);
arrayBuffer.resize(0);
var cloned = structuredClone(int32Array);
shouldBeTrue(`cloned.buffer.resizable`);
shouldBe(`cloned.buffer.byteLength`, `0`);
shouldBe(`cloned.buffer.maxByteLength`, `128`);
shouldBe(`cloned.length`, `0`);
shouldBe(`cloned.byteOffset`, `0`);
shouldBe(`cloned[0]`, `undefined`);
cloned.buffer.resize(128);
shouldBe(`cloned.buffer.byteLength`, `128`);
shouldBe(`cloned.buffer.maxByteLength`, `128`);
shouldBe(`cloned.length`, `4`);
shouldBe(`cloned.byteOffset`, `4`);
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
@@ -0,0 +1,33 @@
<!DOCTYPE HTML><!-- webkit-test-runner [ jscOptions=--useResizableArrayBuffer=true,--useSharedArrayBuffer=true ] -->
<html>
<head>
<title>Resizable ArrayBuffers serialization</title>
<script src="../../resources/js-test-pre.js"></script>
</head>
<body>
<script>
description("Resizable ArrayBuffers should be serializable OOB");
var arrayBuffer = new ArrayBuffer(36, { maxByteLength: 128 });
var int32AutoArray = new Int32Array(arrayBuffer, 4);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`int32AutoArray.length`, `8`);
shouldBe(`int32AutoArray.byteOffset`, `4`);
arrayBuffer.resize(0);
var cloned = structuredClone(int32AutoArray);
shouldBeTrue(`cloned.buffer.resizable`);
shouldBe(`cloned.buffer.byteLength`, `0`);
shouldBe(`cloned.buffer.maxByteLength`, `128`);
shouldBe(`cloned.length`, `0`);
shouldBe(`cloned.byteOffset`, `0`);
shouldBe(`cloned[0]`, `undefined`);
cloned.buffer.resize(128);
shouldBe(`cloned.buffer.byteLength`, `128`);
shouldBe(`cloned.buffer.maxByteLength`, `128`);
shouldBe(`cloned.length`, `31`);
shouldBe(`cloned.byteOffset`, `4`);
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
@@ -0,0 +1,30 @@
<!DOCTYPE HTML><!-- webkit-test-runner [ jscOptions=--useResizableArrayBuffer=true,--useSharedArrayBuffer=true ] -->
<html>
<head>
<title>Resizable ArrayBuffers serialization</title>
<script src="../../resources/js-test-pre.js"></script>
</head>
<body>
<script>
description("Resizable ArrayBuffers should be serializable");
var arrayBuffer = new ArrayBuffer(36, { maxByteLength: 128 });
var int32AutoArray = new Int32Array(arrayBuffer, 4);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`int32AutoArray.length`, `8`);
shouldBe(`int32AutoArray.byteOffset`, `4`);
var cloned = structuredClone(int32AutoArray);
shouldBeTrue(`cloned.buffer.resizable`);
shouldBe(`cloned.buffer.byteLength`, `36`);
shouldBe(`cloned.buffer.maxByteLength`, `128`);
shouldBe(`cloned.length`, `8`);
shouldBe(`cloned.byteOffset`, `4`);
cloned.buffer.resize(128);
shouldBe(`cloned.buffer.byteLength`, `128`);
shouldBe(`cloned.buffer.maxByteLength`, `128`);
shouldBe(`cloned.length`, `31`);
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
@@ -0,0 +1,21 @@
Growable SharedArrayBuffers should be serializable

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS arrayBuffer.growable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS result.growable is true
PASS result.byteLength is 36
PASS result.maxByteLength is 128
PASS result.growable is true
PASS result.byteLength is 128
PASS result.maxByteLength is 128
PASS arrayBuffer.growable is true
PASS arrayBuffer.byteLength is 128
PASS arrayBuffer.maxByteLength is 128
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,58 @@
<!DOCTYPE HTML><!-- webkit-test-runner [ jscOptions=--useResizableArrayBuffer=true,--useSharedArrayBuffer=true ] -->
<html>
<head>
<title>Growable SharedArrayBuffers should be serializable</title>
<script src="../../resources/js-test-pre.js"></script>
</head>
<body>
<script>
description("Growable SharedArrayBuffers should be serializable");
window.jsTestIsAsync = true;

var arrayBuffer = new SharedArrayBuffer(36, { maxByteLength: 128 });

shouldBeTrue(`arrayBuffer.growable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);

async function createWorker(script)
{
script += "self.postMessage('ready');";
const blob = new Blob([script], { type: 'text/javascript' });
const url = URL.createObjectURL(blob);
const worker = new Worker(URL.createObjectURL(blob));
await new Promise(resolve => worker.onmessage = () => {
resolve();
});
URL.revokeObjectURL(url);
return worker;
}

(async () => {
const worker = await createWorker(`
self.onmessage = (event) => {
let data = event.data;
self.postMessage(data);
}
`);

const promise = new Promise(resolve => worker.onmessage = event => resolve(event.data));
worker.postMessage(arrayBuffer);

globalThis.result = await promise;
shouldBeTrue(`result.growable`);
shouldBe(`result.byteLength`, `36`);
shouldBe(`result.maxByteLength`, `128`);
result.grow(128);
shouldBeTrue(`result.growable`);
shouldBe(`result.byteLength`, `128`);
shouldBe(`result.maxByteLength`, `128`);
shouldBeTrue(`arrayBuffer.growable`);
shouldBe(`arrayBuffer.byteLength`, `128`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
finishJSTest();
})();
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
@@ -0,0 +1,26 @@
Growable SharedArrayBuffers typed array should be serializable

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS arrayBuffer.growable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS int32AutoArray.length is 8
PASS int32AutoArray.byteOffset is 4
PASS result.buffer.growable is true
PASS result.buffer.byteLength is 36
PASS result.buffer.maxByteLength is 128
PASS result.length is 8
PASS result.byteOffset is 4
PASS int32AutoArray[0] is 42
PASS result.buffer.byteLength is 128
PASS result.buffer.maxByteLength is 128
PASS int32AutoArray.length is 31
PASS int32AutoArray.byteOffset is 4
PASS result.length is 31
PASS result.byteOffset is 4
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,26 @@
Growable SharedArrayBuffers typed array should be serializable explicit length

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS arrayBuffer.growable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS int32AutoArray.length is 4
PASS int32AutoArray.byteOffset is 4
PASS result.buffer.growable is true
PASS result.buffer.byteLength is 36
PASS result.buffer.maxByteLength is 128
PASS result.length is 4
PASS result.byteOffset is 4
PASS int32AutoArray[0] is 42
PASS result.buffer.byteLength is 128
PASS result.buffer.maxByteLength is 128
PASS int32AutoArray.length is 4
PASS int32AutoArray.byteOffset is 4
PASS result.length is 4
PASS result.byteOffset is 4
PASS successfullyParsed is true

TEST COMPLETE

0 comments on commit a9510ae

Please sign in to comment.