mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 02:57:42 +00:00
LibWeb: Add {de}serialization steps for TypedArrayBuffers and DataViews
This commit is contained in:
parent
bddf3fbf2d
commit
5ae9b2fdaf
3 changed files with 161 additions and 1 deletions
|
@ -0,0 +1,12 @@
|
||||||
|
Uint8Array 30,40,50,60
|
||||||
|
Uint8ClampedArray 30,40,50,60
|
||||||
|
Uint16Array 30,40,50,60
|
||||||
|
Uint32Array 30,40,50,60
|
||||||
|
Int8Array 30,40,50,60
|
||||||
|
Int16Array 30,40,50,60
|
||||||
|
Int32Array 30,40,50,60
|
||||||
|
Float32Array 30,40,50,60
|
||||||
|
Float64Array 30,40,50,60
|
||||||
|
BigUint64Array 30,40,50,60
|
||||||
|
BigInt64Array 30,40,50,60
|
||||||
|
DataView [object DataView]
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
const TYPED_ARRAYS = [
|
||||||
|
Uint8Array,
|
||||||
|
Uint8ClampedArray,
|
||||||
|
Uint16Array,
|
||||||
|
Uint32Array,
|
||||||
|
Int8Array,
|
||||||
|
Int16Array,
|
||||||
|
Int32Array,
|
||||||
|
Float32Array,
|
||||||
|
Float64Array,
|
||||||
|
];
|
||||||
|
|
||||||
|
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
|
||||||
|
|
||||||
|
test(() => {
|
||||||
|
TYPED_ARRAYS.forEach(T => {
|
||||||
|
const a = new T([30, 40, 50, 60]);
|
||||||
|
let b = structuredClone(a)
|
||||||
|
if (!a.every((value, index) => value === b[index]))
|
||||||
|
println("FAIL")
|
||||||
|
println(`${b[Symbol.toStringTag]} ${b}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
BIGINT_TYPED_ARRAYS.forEach(T => {
|
||||||
|
const a = new T([30n, 40n, 50n, 60n]);
|
||||||
|
let b = structuredClone(a)
|
||||||
|
if (!a.every((value, index) => value === b[index]))
|
||||||
|
println("FAIL")
|
||||||
|
|
||||||
|
println(`${b[Symbol.toStringTag]} ${b}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test DataView clone
|
||||||
|
let a = new ArrayBuffer(64);
|
||||||
|
for(let i = 0; i < a.byteLength; ++i) {
|
||||||
|
a[i] = i;
|
||||||
|
}
|
||||||
|
let view = new DataView(a);
|
||||||
|
let view2 = structuredClone(view);
|
||||||
|
if (view.buffer === view2.buffer)
|
||||||
|
println("FAIL: Buffer not deep copied");
|
||||||
|
|
||||||
|
for (let i = 0; i < view.byteLength; ++i) {
|
||||||
|
if (!view[i] === view2[i])
|
||||||
|
println("FAIL: Data not copied")
|
||||||
|
}
|
||||||
|
println(`${view2[Symbol.toStringTag]} ${view2}`)
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -14,11 +14,13 @@
|
||||||
#include <LibJS/Runtime/BigInt.h>
|
#include <LibJS/Runtime/BigInt.h>
|
||||||
#include <LibJS/Runtime/BigIntObject.h>
|
#include <LibJS/Runtime/BigIntObject.h>
|
||||||
#include <LibJS/Runtime/BooleanObject.h>
|
#include <LibJS/Runtime/BooleanObject.h>
|
||||||
|
#include <LibJS/Runtime/DataView.h>
|
||||||
#include <LibJS/Runtime/Date.h>
|
#include <LibJS/Runtime/Date.h>
|
||||||
#include <LibJS/Runtime/NumberObject.h>
|
#include <LibJS/Runtime/NumberObject.h>
|
||||||
#include <LibJS/Runtime/PrimitiveString.h>
|
#include <LibJS/Runtime/PrimitiveString.h>
|
||||||
#include <LibJS/Runtime/RegExpObject.h>
|
#include <LibJS/Runtime/RegExpObject.h>
|
||||||
#include <LibJS/Runtime/StringObject.h>
|
#include <LibJS/Runtime/StringObject.h>
|
||||||
|
#include <LibJS/Runtime/TypedArray.h>
|
||||||
#include <LibJS/Runtime/VM.h>
|
#include <LibJS/Runtime/VM.h>
|
||||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||||
#include <LibWeb/HTML/StructuredSerialize.h>
|
#include <LibWeb/HTML/StructuredSerialize.h>
|
||||||
|
@ -81,6 +83,8 @@ enum ValueTag {
|
||||||
|
|
||||||
ArrayBuffer,
|
ArrayBuffer,
|
||||||
|
|
||||||
|
ArrayBufferView,
|
||||||
|
|
||||||
// TODO: Define many more types
|
// TODO: Define many more types
|
||||||
|
|
||||||
// This tag or higher are understood to be errors
|
// This tag or higher are understood to be errors
|
||||||
|
@ -204,7 +208,14 @@ public:
|
||||||
TRY(serialize_array_buffer(m_serialized, static_cast<JS::ArrayBuffer&>(value.as_object())));
|
TRY(serialize_array_buffer(m_serialized, static_cast<JS::ArrayBuffer&>(value.as_object())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 14 - 24: FIXME: Serialize other data types
|
// 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
|
||||||
|
else if (value.is_object() && is<JS::TypedArrayBase>(value.as_object())) {
|
||||||
|
TRY(serialize_viewed_array_buffer(m_serialized, static_cast<JS::TypedArrayBase&>(value.as_object())));
|
||||||
|
} else if (value.is_object() && is<JS::DataView>(value.as_object())) {
|
||||||
|
TRY(serialize_viewed_array_buffer(m_serialized, static_cast<JS::DataView&>(value.as_object())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 15 - 24: FIXME: Serialize other data types
|
||||||
else {
|
else {
|
||||||
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string));
|
return throw_completion(WebIDL::DataCloneError::create(*m_vm.current_realm(), "Unsupported type"_fly_string));
|
||||||
}
|
}
|
||||||
|
@ -245,6 +256,11 @@ private:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebIDL::ExceptionOr<void> serialize_string(Vector<u32>& vector, DeprecatedFlyString const& string)
|
||||||
|
{
|
||||||
|
return serialize_bytes(vector, string.view().bytes());
|
||||||
|
}
|
||||||
|
|
||||||
WebIDL::ExceptionOr<void> serialize_string(Vector<u32>& vector, String const& string)
|
WebIDL::ExceptionOr<void> serialize_string(Vector<u32>& vector, String const& string)
|
||||||
{
|
{
|
||||||
return serialize_bytes(vector, { string.code_points().bytes(), string.code_points().byte_length() });
|
return serialize_bytes(vector, { string.code_points().bytes(), string.code_points().byte_length() });
|
||||||
|
@ -307,6 +323,54 @@ private:
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<OneOf<JS::TypedArrayBase, JS::DataView> ViewType>
|
||||||
|
WebIDL::ExceptionOr<void> serialize_viewed_array_buffer(Vector<u32>& vector, ViewType const& view)
|
||||||
|
{
|
||||||
|
// 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
|
||||||
|
|
||||||
|
// FIXME: 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException.
|
||||||
|
|
||||||
|
// 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.
|
||||||
|
auto* buffer = view.viewed_array_buffer();
|
||||||
|
|
||||||
|
// 3. Let bufferSerialized be ? StructuredSerializeInternal(buffer, forStorage, memory).
|
||||||
|
auto buffer_serialized = TRY(structured_serialize_internal(m_vm, JS::Value(buffer), m_for_storage, m_memory));
|
||||||
|
|
||||||
|
// 4. Assert: bufferSerialized.[[Type]] is "ArrayBuffer", "ResizableArrayBuffer", "SharedArrayBuffer", or "GrowableSharedArrayBuffer".
|
||||||
|
// NOTE: We currently only implement this for ArrayBuffer
|
||||||
|
VERIFY(buffer_serialized[0] == ValueTag::ArrayBuffer);
|
||||||
|
|
||||||
|
// 5. If value has a [[DataView]] internal slot, then set serialized to { [[Type]]: "ArrayBufferView", [[Constructor]]: "DataView",
|
||||||
|
// [[ArrayBufferSerialized]]: bufferSerialized, [[ByteLength]]: value.[[ByteLength]], [[ByteOffset]]: value.[[ByteOffset]] }.
|
||||||
|
if constexpr (IsSame<ViewType, JS::DataView>) {
|
||||||
|
vector.append(ValueTag::ArrayBufferView);
|
||||||
|
u64 const serialized_buffer_size = buffer_serialized.size();
|
||||||
|
vector.append(bit_cast<u32*>(&serialized_buffer_size), 2);
|
||||||
|
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
||||||
|
TRY(serialize_string(vector, "DataView"_string)); // [[Constructor]]
|
||||||
|
vector.append(view.byte_length());
|
||||||
|
vector.append(view.byte_offset());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Otherwise:
|
||||||
|
else {
|
||||||
|
// 1. Assert: value has a [[TypedArrayName]] internal slot.
|
||||||
|
// NOTE: Handled by constexpr check and template constraints
|
||||||
|
// 2. Set serialized to { [[Type]]: "ArrayBufferView", [[Constructor]]: value.[[TypedArrayName]],
|
||||||
|
// [[ArrayBufferSerialized]]: bufferSerialized, [[ByteLength]]: value.[[ByteLength]],
|
||||||
|
// [[ByteOffset]]: value.[[ByteOffset]], [[ArrayLength]]: value.[[ArrayLength]] }.
|
||||||
|
vector.append(ValueTag::ArrayBufferView);
|
||||||
|
u64 const serialized_buffer_size = buffer_serialized.size();
|
||||||
|
vector.append(bit_cast<u32*>(&serialized_buffer_size), 2);
|
||||||
|
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
||||||
|
TRY(serialize_string(vector, view.element_name())); // [[Constructor]]
|
||||||
|
vector.append(view.byte_length());
|
||||||
|
vector.append(view.byte_offset());
|
||||||
|
vector.append(view.array_length());
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Deserializer {
|
class Deserializer {
|
||||||
|
@ -404,6 +468,39 @@ public:
|
||||||
m_memory.append(JS::ArrayBuffer::create(*realm, move(bytes)));
|
m_memory.append(JS::ArrayBuffer::create(*realm, move(bytes)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ValueTag::ArrayBufferView: {
|
||||||
|
auto* realm = m_vm.current_realm();
|
||||||
|
u32 size_bits[2];
|
||||||
|
size_bits[0] = m_vector[position++];
|
||||||
|
size_bits[1] = m_vector[position++];
|
||||||
|
u64 const array_buffer_size = *bit_cast<u64*>(&size_bits);
|
||||||
|
auto array_buffer_value = TRY(structured_deserialize_impl(m_vm, m_vector.slice(position, array_buffer_size), *realm, m_serialization_memory));
|
||||||
|
auto& array_buffer = verify_cast<JS::ArrayBuffer>(array_buffer_value.as_object());
|
||||||
|
position += array_buffer_size;
|
||||||
|
auto constructor_name = TRY(deserialize_string(m_vm, m_vector, position));
|
||||||
|
u32 byte_length = m_vector[position++];
|
||||||
|
u32 byte_offset = m_vector[position++];
|
||||||
|
|
||||||
|
if (constructor_name == "DataView"sv) {
|
||||||
|
m_memory.append(JS::DataView::create(*realm, &array_buffer, byte_length, byte_offset));
|
||||||
|
} else {
|
||||||
|
u32 array_length = m_vector[position++];
|
||||||
|
JS::GCPtr<JS::TypedArrayBase> typed_array_ptr;
|
||||||
|
#define CREATE_TYPED_ARRAY(ClassName) \
|
||||||
|
if (constructor_name == #ClassName##sv) \
|
||||||
|
typed_array_ptr = JS::ClassName::create(*realm, array_length, array_buffer);
|
||||||
|
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
|
||||||
|
CREATE_TYPED_ARRAY(ClassName)
|
||||||
|
JS_ENUMERATE_TYPED_ARRAYS
|
||||||
|
#undef __JS_ENUMERATE
|
||||||
|
#undef CREATE_TYPED_ARRAY
|
||||||
|
VERIFY(typed_array_ptr != nullptr); // FIXME: Handle errors better here? Can a fuzzer put weird stuff in the buffer?
|
||||||
|
typed_array_ptr->set_byte_length(byte_length);
|
||||||
|
typed_array_ptr->set_byte_offset(byte_offset);
|
||||||
|
m_memory.append(typed_array_ptr);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
m_error = "Unsupported type"_fly_string;
|
m_error = "Unsupported type"_fly_string;
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue