Skip to content
Permalink
Browse files
[JSC] Update resizable ArrayBuffer based on spec update
https://bugs.webkit.org/show_bug.cgi?id=248511
rdar://102793557

Reviewed by Ross Kirsling.

* LayoutTests/js/dom/resizable-array-buffer-view-serialization-out-of-bounds-expected.txt:
* LayoutTests/js/dom/resizable-array-buffer-view-serialization-out-of-bounds-explicit-length-expected.txt:
* LayoutTests/js/dom/resizable-array-buffer-view-serialization-out-of-bounds-explicit-length.html:
* LayoutTests/js/dom/resizable-array-buffer-view-serialization-out-of-bounds.html:
* LayoutTests/js/dom/resizable-data-view-serialization-out-of-bounds-expected.txt: Added.
* LayoutTests/js/dom/resizable-data-view-serialization-out-of-bounds-explicit-length-expected.txt: Added.
* LayoutTests/js/dom/resizable-data-view-serialization-out-of-bounds-explicit-length.html: Copied from LayoutTests/js/dom/resizable-array-buffer-view-serialization-out-of-bounds-explicit-length.html.
* LayoutTests/js/dom/resizable-data-view-serialization-out-of-bounds.html: Copied from LayoutTests/js/dom/resizable-array-buffer-view-serialization-out-of-bounds.html.
* Source/JavaScriptCore/runtime/JSArrayBufferView.h:
(JSC::JSArrayBufferView::isDetached const):
(JSC::JSArrayBufferView::byteOffset const):
(JSC::JSArrayBufferView::isOutOfBounds const): Add isOutOfBounds, function aligned to spec's IsArrayBufferViewOutOfBounds. Used in SerializedScriptValue.
(JSC::JSArrayBufferView::isDetached): Deleted.
* Source/JavaScriptCore/runtime/JSArrayBufferViewInlines.h:
(JSC::isArrayBufferViewOutOfBounds):
(JSC::isIntegerIndexedObjectOutOfBounds):
* Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeFunctions.h:
(JSC::genericTypedArrayViewProtoFuncSet): This is not actually changing the behavior, but more aligned to the latest spec's change.
* Source/WebCore/bindings/js/SerializedScriptValue.cpp:
(WebCore::CloneSerializer::dumpArrayBufferView): We reject OOB ArrayBuffer based on discussion in [1].

[1]: whatwg/html#8559

Canonical link: https://commits.webkit.org/257178@main
  • Loading branch information
Constellation committed Nov 30, 2022
1 parent 464a308 commit ba8ba25ae962e03ccfaa78f078741374667e3475
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 57 deletions.
@@ -8,16 +8,17 @@ 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 structuredClone(int32AutoArray) threw exception DataCloneError: The object can not be cloned..
PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 0
PASS arrayBuffer.maxByteLength is 128
PASS int32AutoArray.length is 0
PASS int32AutoArray.byteOffset is 0
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 successfullyParsed is true

TEST COMPLETE
@@ -8,16 +8,17 @@ 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 structuredClone(int32Array) threw exception DataCloneError: The object can not be cloned..
PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 0
PASS arrayBuffer.maxByteLength is 128
PASS int32Array.length is 0
PASS int32Array.byteOffset is 0
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 successfullyParsed is true

TEST COMPLETE
@@ -15,18 +15,18 @@
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`);
shouldThrow(`structuredClone(int32Array)`);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `0`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`int32Array.length`, `0`);
shouldBe(`int32Array.byteOffset`, `0`);
arrayBuffer.resize(36);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`int32Array.length`, `4`);
shouldBe(`int32Array.byteOffset`, `4`);
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
@@ -15,18 +15,18 @@
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`);
shouldThrow(`structuredClone(int32AutoArray)`);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `0`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`int32AutoArray.length`, `0`);
shouldBe(`int32AutoArray.byteOffset`, `0`);
arrayBuffer.resize(36);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`int32AutoArray.length`, `8`);
shouldBe(`int32AutoArray.byteOffset`, `4`);
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
@@ -0,0 +1,25 @@
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 view.byteLength is 32
PASS view.byteOffset is 4
PASS structuredClone(view) threw exception DataCloneError: The object can not be cloned..
PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 0
PASS arrayBuffer.maxByteLength is 128
PASS view.byteLength threw exception TypeError: Underlying ArrayBuffer has been detached from the view or out-of-bounds.
PASS view.byteOffset threw exception TypeError: Underlying ArrayBuffer has been detached from the view or out-of-bounds.
PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS view.byteLength is 32
PASS view.byteOffset is 4
PASS successfullyParsed is true

TEST COMPLETE

@@ -0,0 +1,25 @@
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 view.byteLength is 4
PASS view.byteOffset is 4
PASS structuredClone(view) threw exception DataCloneError: The object can not be cloned..
PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 0
PASS arrayBuffer.maxByteLength is 128
PASS view.byteLength threw exception TypeError: Underlying ArrayBuffer has been detached from the view or out-of-bounds.
PASS view.byteOffset threw exception TypeError: Underlying ArrayBuffer has been detached from the view or out-of-bounds.
PASS arrayBuffer.resizable is true
PASS arrayBuffer.byteLength is 36
PASS arrayBuffer.maxByteLength is 128
PASS view.byteLength is 4
PASS view.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 view = new DataView(arrayBuffer, 4, 4);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`view.byteLength`, `4`);
shouldBe(`view.byteOffset`, `4`);
arrayBuffer.resize(0);
shouldThrow(`structuredClone(view)`);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `0`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldThrow(`view.byteLength`);
shouldThrow(`view.byteOffset`);
arrayBuffer.resize(36);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`view.byteLength`, `4`);
shouldBe(`view.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 view = new DataView(arrayBuffer, 4);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`view.byteLength`, `32`);
shouldBe(`view.byteOffset`, `4`);
arrayBuffer.resize(0);
shouldThrow(`structuredClone(view)`);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `0`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldThrow(`view.byteLength`);
shouldThrow(`view.byteOffset`);
arrayBuffer.resize(36);
shouldBeTrue(`arrayBuffer.resizable`);
shouldBe(`arrayBuffer.byteLength`, `36`);
shouldBe(`arrayBuffer.maxByteLength`, `128`);
shouldBe(`view.byteLength`, `32`);
shouldBe(`view.byteOffset`, `4`);
</script>
<script src="../../resources/js-test-post.js"></script>
</body>
</html>
@@ -157,6 +157,8 @@ inline bool canUseArrayBufferViewRawFieldsDirectly(TypedArrayMode mode)
return (static_cast<uint8_t>(mode) & resizabilityAndAutoLengthMask) <= isGrowableSharedMode;
}

template<typename Getter> bool isArrayBufferViewOutOfBounds(JSArrayBufferView*, Getter&);

template<typename Getter> std::optional<size_t> integerIndexedObjectLength(JSArrayBufferView*, Getter&);
template<typename Getter> size_t integerIndexedObjectByteLength(JSArrayBufferView*, Getter&);
template<typename Getter> bool isIntegerIndexedObjectOutOfBounds(JSArrayBufferView*, Getter&);
@@ -264,7 +266,7 @@ class JSArrayBufferView : public JSNonFinalObject {
JSArrayBuffer* possiblySharedJSBuffer(JSGlobalObject* globalObject);
RefPtr<ArrayBufferView> unsharedImpl();
JS_EXPORT_PRIVATE RefPtr<ArrayBufferView> possiblySharedImpl();
bool isDetached() { return hasArrayBuffer() && !hasVector(); }
bool isDetached() const { return hasArrayBuffer() && !hasVector(); }
bool isResizableOrGrowableShared() const { return JSC::isResizableOrGrowableShared(m_mode); }
bool isGrowableShared() const { return JSC::isGrowableShared(m_mode); };
bool isResizableNonShared() const { return JSC::isResizableNonShared(m_mode); };
@@ -288,7 +290,7 @@ class JSArrayBufferView : public JSNonFinalObject {
return byteOffsetRaw();

IdempotentArrayBufferByteLengthGetter<std::memory_order_seq_cst> getter;
if (UNLIKELY(isIntegerIndexedObjectOutOfBounds(const_cast<JSArrayBufferView*>(this), getter)))
if (UNLIKELY(isArrayBufferViewOutOfBounds(const_cast<JSArrayBufferView*>(this), getter)))
return 0;
return byteOffsetRaw();
}
@@ -328,6 +330,17 @@ class JSArrayBufferView : public JSNonFinalObject {
#endif
}

bool isOutOfBounds() const
{
// https://tc39.es/proposal-resizablearraybuffer/#sec-isarraybufferviewoutofbounds
if (UNLIKELY(isDetached()))
return true;
if (LIKELY(!isResizableNonShared()))
return false;
IdempotentArrayBufferByteLengthGetter<std::memory_order_seq_cst> getter;
return isArrayBufferViewOutOfBounds(const_cast<JSArrayBufferView*>(this), getter);
}

DECLARE_EXPORT_INFO;

static ptrdiff_t offsetOfVector() { return OBJECT_OFFSETOF(JSArrayBufferView, m_vector); }
@@ -110,32 +110,41 @@ inline RefPtr<ArrayBufferView> JSArrayBufferView::toWrappedAllowShared(VM&, JSVa
}

template<typename Getter>
bool isIntegerIndexedObjectOutOfBounds(JSArrayBufferView* typedArray, Getter& getter)
bool isArrayBufferViewOutOfBounds(JSArrayBufferView* view, Getter& getter)
{
// https://tc39.es/proposal-resizablearraybuffer/#sec-isintegerindexedobjectoutofbounds
// https://tc39.es/proposal-resizablearraybuffer/#sec-isarraybufferviewoutofbounds
//
// This function should work with DataView too.

if (UNLIKELY(typedArray->isDetached()))
if (UNLIKELY(view->isDetached()))
return true;

if (LIKELY(!typedArray->isResizableOrGrowableShared()))
if (LIKELY(!view->isResizableOrGrowableShared()))
return false;

ASSERT(isWastefulTypedArray(typedArray->mode()) && isResizableOrGrowableShared(typedArray->mode()));
RefPtr<ArrayBuffer> buffer = typedArray->possiblySharedBuffer();
ASSERT(isWastefulTypedArray(view->mode()) && isResizableOrGrowableShared(view->mode()));
RefPtr<ArrayBuffer> buffer = view->possiblySharedBuffer();
if (!buffer)
return true;

size_t bufferByteLength = getter(*buffer);
size_t byteOffsetStart = typedArray->byteOffsetRaw();
size_t byteOffsetStart = view->byteOffsetRaw();
size_t byteOffsetEnd = 0;
if (typedArray->isAutoLength())
if (view->isAutoLength())
byteOffsetEnd = bufferByteLength;
else
byteOffsetEnd = byteOffsetStart + typedArray->byteLengthRaw();
byteOffsetEnd = byteOffsetStart + view->byteLengthRaw();

return byteOffsetStart > bufferByteLength || byteOffsetEnd > bufferByteLength;
}

template<typename Getter>
bool isIntegerIndexedObjectOutOfBounds(JSArrayBufferView* typedArray, Getter& getter)
{
return isArrayBufferViewOutOfBounds(typedArray, getter);
}

template<typename Getter>
std::optional<size_t> integerIndexedObjectLength(JSArrayBufferView* typedArray, Getter& getter)
{
@@ -228,11 +228,11 @@ ALWAYS_INLINE EncodedJSValue genericTypedArrayViewProtoFuncSet(VM& vm, JSGlobalO
if (source.isObject() && isTypedView(asObject(source)->type())) {
JSArrayBufferView* sourceView = jsCast<JSArrayBufferView*>(source);
IdempotentArrayBufferByteLengthGetter<std::memory_order_seq_cst> getter;
size_t length = integerIndexedObjectLength(sourceView, getter).value_or(0);
if (isIntegerIndexedObjectOutOfBounds(sourceView, getter))
auto lengthValue = integerIndexedObjectLength(sourceView, getter);
if (UNLIKELY(!lengthValue))
return throwVMTypeError(globalObject, scope, typedArrayBufferHasBeenDetachedErrorMessage);
scope.release();
thisObject->setFromTypedArray(globalObject, offset, sourceView, 0, length, CopyType::Unobservable);
thisObject->setFromTypedArray(globalObject, offset, sourceView, 0, lengthValue.value(), CopyType::Unobservable);
return JSValue::encode(jsUndefined());
}

@@ -1037,6 +1037,11 @@ class CloneSerializer : CloneBase {
else
return false;

if (UNLIKELY(jsCast<JSArrayBufferView*>(obj)->isOutOfBounds())) {
code = SerializationReturnCode::DataCloneError;
return true;
}

RefPtr<ArrayBufferView> arrayBufferView = toPossiblySharedArrayBufferView(vm, obj);
if (arrayBufferView->isResizableOrGrowableShared()) {
uint64_t byteOffset = arrayBufferView->byteOffsetRaw();

0 comments on commit ba8ba25

Please sign in to comment.