From dcf5ff517890c05a5c8f59091bf56bcdeed13ef5 Mon Sep 17 00:00:00 2001 From: Kenneth Myhra Date: Fri, 23 Feb 2024 18:30:50 +0100 Subject: [PATCH] LibWeb: Move serialize_* methods outside scope of Serializer class These methods are useful independent of the class Serializer, so let's move their declarations to the header file and and outside the scope of the Serializer class. --- .../LibWeb/HTML/StructuredSerialize.cpp | 322 +++++++++--------- .../LibWeb/HTML/StructuredSerialize.h | 11 + 2 files changed, 173 insertions(+), 160 deletions(-) diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 9b823ed519..5eb084058e 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2022, Daniel Ehrenberg * Copyright (c) 2022, Andrew Kaster - * Copyright (c) 2023, Kenneth Myhra + * Copyright (c) 2023-2024, Kenneth Myhra * Copyright (c) 2023, Idan Horowitz * * SPDX-License-Identifier: BSD-2-Clause @@ -170,10 +170,10 @@ public: } else if (value.is_bigint()) { m_serialized.append(ValueTag::BigIntPrimitive); auto& val = value.as_bigint(); - TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, val.to_string()))); + TRY(serialize_string(m_vm, m_serialized, TRY_OR_THROW_OOM(m_vm, val.to_string()))); } else if (value.is_string()) { m_serialized.append(ValueTag::StringPrimitive); - TRY(serialize_string(m_serialized, value.as_string())); + TRY(serialize_string(m_vm, m_serialized, value.as_string())); } else { return_primitive_type = false; } @@ -206,14 +206,14 @@ public: else if (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::BigIntObject); auto& bigint_object = static_cast(value.as_object()); - TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, bigint_object.bigint().to_string()))); + TRY(serialize_string(m_vm, m_serialized, TRY_OR_THROW_OOM(m_vm, bigint_object.bigint().to_string()))); } // 10. Otherwise, if value has a [[StringData]] internal slot, then set serialized to { [[Type]]: "String", [[StringData]]: value.[[StringData]] }. else if (value.is_object() && is(value.as_object())) { m_serialized.append(ValueTag::StringObject); auto& string_object = static_cast(value.as_object()); - TRY(serialize_string(m_serialized, string_object.primitive_string())); + TRY(serialize_string(m_vm, m_serialized, string_object.primitive_string())); } // 11. Otherwise, if value has a [[DateValue]] internal slot, then set serialized to { [[Type]]: "Date", [[DateValue]]: value.[[DateValue]] }. @@ -233,20 +233,20 @@ public: // Note: A Regex object is perfectly happy to be reconstructed with just the source+flags // In the future, we could optimize the work being done on the deserialize step by serializing // more of the internal state (the [[RegExpMatcher]] internal slot) - TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, String::from_byte_string(regexp_object.pattern())))); - TRY(serialize_string(m_serialized, TRY_OR_THROW_OOM(m_vm, String::from_byte_string(regexp_object.flags())))); + TRY(serialize_string(m_vm, m_serialized, TRY_OR_THROW_OOM(m_vm, String::from_byte_string(regexp_object.pattern())))); + TRY(serialize_string(m_vm, m_serialized, TRY_OR_THROW_OOM(m_vm, String::from_byte_string(regexp_object.flags())))); } // 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then: else if (value.is_object() && is(value.as_object())) { - TRY(serialize_array_buffer(m_serialized, static_cast(value.as_object()))); + TRY(serialize_array_buffer(m_vm, m_serialized, static_cast(value.as_object()), m_for_storage)); } // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: else if (value.is_object() && is(value.as_object())) { - TRY(serialize_viewed_array_buffer(m_serialized, static_cast(value.as_object()))); + TRY(serialize_viewed_array_buffer(m_vm, m_serialized, static_cast(value.as_object()), m_for_storage, m_memory)); } else if (value.is_object() && is(value.as_object())) { - TRY(serialize_viewed_array_buffer(m_serialized, static_cast(value.as_object()))); + TRY(serialize_viewed_array_buffer(m_vm, m_serialized, static_cast(value.as_object()), m_for_storage, m_memory)); } // 15. Otherwise, if value has [[MapData]] internal slot, then: @@ -291,7 +291,7 @@ public: m_serialized.append(type); m_serialized.append(message.has_value()); if (message.has_value()) - TRY(serialize_string(m_serialized, *message)); + TRY(serialize_string(m_vm, m_serialized, *message)); } // 18. Otherwise, if value is an Array exotic object, then: @@ -407,7 +407,7 @@ public: auto output_value = TRY(structured_serialize_internal(m_vm, input_value, m_for_storage, m_memory)); // 3. Append { [[Key]]: key, [[Value]]: outputValue } to serialized.[[Properties]]. - TRY(serialize_string(m_serialized, key.as_string())); + TRY(serialize_string(m_vm, m_serialized, key.as_string())); m_serialized.extend(output_value); property_count++; @@ -427,156 +427,158 @@ private: u32 m_next_id { 0 }; SerializationRecord m_serialized; bool m_for_storage { false }; - - WebIDL::ExceptionOr serialize_bytes(Vector& vector, ReadonlyBytes bytes) - { - // Append size of the buffer to the serialized structure. - u64 const size = bytes.size(); - TRY_OR_THROW_OOM(m_vm, vector.try_append(bit_cast(&size), 2)); - // Append the bytes of the buffer to the serialized structure. - u64 byte_position = 0; - while (byte_position < size) { - u32 combined_value = 0; - for (u8 i = 0; i < 4; ++i) { - u8 const byte = bytes[byte_position]; - combined_value |= byte << (i * 8); - byte_position++; - if (byte_position == size) - break; - } - TRY_OR_THROW_OOM(m_vm, vector.try_append(combined_value)); - } - return {}; - } - - WebIDL::ExceptionOr serialize_string(Vector& vector, DeprecatedFlyString const& string) - { - return serialize_bytes(vector, string.view().bytes()); - } - - WebIDL::ExceptionOr serialize_string(Vector& vector, String const& string) - { - return serialize_bytes(vector, { string.code_points().bytes(), string.code_points().byte_length() }); - } - - WebIDL::ExceptionOr serialize_string(Vector& vector, JS::PrimitiveString const& primitive_string) - { - auto string = primitive_string.utf8_string(); - TRY(serialize_string(vector, string)); - return {}; - } - - WebIDL::ExceptionOr serialize_array_buffer(Vector& vector, JS::ArrayBuffer const& array_buffer) - { - // 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then: - - // FIXME: 1. If IsSharedArrayBuffer(value) is true, then: - if (false) { - // 1. If the current settings object's cross-origin isolated capability is false, then throw a "DataCloneError" DOMException. - // NOTE: This check is only needed when serializing (and not when deserializing) as the cross-origin isolated capability cannot change - // over time and a SharedArrayBuffer cannot leave an agent cluster. - if (current_settings_object().cross_origin_isolated_capability() == CanUseCrossOriginIsolatedAPIs::No) - return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize SharedArrayBuffer when cross-origin isolated"_fly_string); - - // 2. If forStorage is true, then throw a "DataCloneError" DOMException. - if (m_for_storage) - return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize SharedArrayBuffer for storage"_fly_string); - - // FIXME: 3. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "GrowableSharedArrayBuffer", - // [[ArrayBufferData]]: value.[[ArrayBufferData]], [[ArrayBufferByteLengthData]]: value.[[ArrayBufferByteLengthData]], - // [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }. - // FIXME: 4. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value.[[ArrayBufferData]], - // [[ArrayBufferByteLength]]: value.[[ArrayBufferByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }. - } - // 2. Otherwise: - else { - // 1. If IsDetachedBuffer(value) is true, then throw a "DataCloneError" DOMException. - if (array_buffer.is_detached()) - return WebIDL::DataCloneError::create(*m_vm.current_realm(), "Cannot serialize detached ArrayBuffer"_fly_string); - - // 2. Let size be value.[[ArrayBufferByteLength]]. - auto size = array_buffer.byte_length(); - - // 3. Let dataCopy be ? CreateByteDataBlock(size). - // NOTE: This can throw a RangeError exception upon allocation failure. - auto data_copy = TRY(JS::create_byte_data_block(m_vm, size)); - - // 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size). - JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size); - - // FIXME: 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer", - // [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }. - if (false) { - } - // 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }. - else { - vector.append(ValueTag::ArrayBuffer); - TRY(serialize_bytes(vector, data_copy.buffer().bytes())); - } - } - return {}; - } - - template ViewType> - WebIDL::ExceptionOr serialize_viewed_array_buffer(Vector& vector, ViewType const& view) - { - // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: - - auto view_record = [&]() { - if constexpr (IsSame) { - return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst); - } else { - return JS::make_typed_array_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst); - } - }(); - - // 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException. - if constexpr (IsSame) { - if (JS::is_view_out_of_bounds(view_record)) - return WebIDL::DataCloneError::create(*m_vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "DataView"sv))); - } else { - if (JS::is_typed_array_out_of_bounds(view_record)) - return WebIDL::DataCloneError::create(*m_vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "TypedArray"sv))); - } - - // 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) { - vector.append(ValueTag::ArrayBufferView); - vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]] - TRY(serialize_string(vector, "DataView"_string)); // [[Constructor]] - vector.append(JS::get_view_byte_length(view_record)); - 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); - vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]] - TRY(serialize_string(vector, view.element_name())); // [[Constructor]] - vector.append(JS::typed_array_byte_length(view_record)); - vector.append(view.byte_offset()); - vector.append(JS::typed_array_length(view_record)); - } - return {}; - } }; +WebIDL::ExceptionOr serialize_bytes(JS::VM& vm, Vector& vector, ReadonlyBytes bytes) +{ + // Append size of the buffer to the serialized structure. + u64 const size = bytes.size(); + TRY_OR_THROW_OOM(vm, vector.try_append(bit_cast(&size), 2)); + // Append the bytes of the buffer to the serialized structure. + u64 byte_position = 0; + while (byte_position < size) { + u32 combined_value = 0; + for (u8 i = 0; i < 4; ++i) { + u8 const byte = bytes[byte_position]; + combined_value |= byte << (i * 8); + byte_position++; + if (byte_position == size) + break; + } + TRY_OR_THROW_OOM(vm, vector.try_append(combined_value)); + } + return {}; +} + +WebIDL::ExceptionOr serialize_string(JS::VM& vm, Vector& vector, DeprecatedFlyString const& string) +{ + return serialize_bytes(vm, vector, string.view().bytes()); +} + +WebIDL::ExceptionOr serialize_string(JS::VM& vm, Vector& vector, String const& string) +{ + return serialize_bytes(vm, vector, { string.code_points().bytes(), string.code_points().byte_length() }); +} + +WebIDL::ExceptionOr serialize_string(JS::VM& vm, Vector& vector, JS::PrimitiveString const& primitive_string) +{ + auto string = primitive_string.utf8_string(); + TRY(serialize_string(vm, vector, string)); + return {}; +} + +WebIDL::ExceptionOr serialize_array_buffer(JS::VM& vm, Vector& vector, JS::ArrayBuffer const& array_buffer, bool for_storage) +{ + // 13. Otherwise, if value has an [[ArrayBufferData]] internal slot, then: + + // FIXME: 1. If IsSharedArrayBuffer(value) is true, then: + if (false) { + // 1. If the current settings object's cross-origin isolated capability is false, then throw a "DataCloneError" DOMException. + // NOTE: This check is only needed when serializing (and not when deserializing) as the cross-origin isolated capability cannot change + // over time and a SharedArrayBuffer cannot leave an agent cluster. + if (current_settings_object().cross_origin_isolated_capability() == CanUseCrossOriginIsolatedAPIs::No) + return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize SharedArrayBuffer when cross-origin isolated"_fly_string); + + // 2. If forStorage is true, then throw a "DataCloneError" DOMException. + if (for_storage) + return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize SharedArrayBuffer for storage"_fly_string); + + // FIXME: 3. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "GrowableSharedArrayBuffer", + // [[ArrayBufferData]]: value.[[ArrayBufferData]], [[ArrayBufferByteLengthData]]: value.[[ArrayBufferByteLengthData]], + // [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }. + // FIXME: 4. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value.[[ArrayBufferData]], + // [[ArrayBufferByteLength]]: value.[[ArrayBufferByteLength]], [[AgentCluster]]: the surrounding agent's agent cluster }. + } + // 2. Otherwise: + else { + // 1. If IsDetachedBuffer(value) is true, then throw a "DataCloneError" DOMException. + if (array_buffer.is_detached()) + return WebIDL::DataCloneError::create(*vm.current_realm(), "Cannot serialize detached ArrayBuffer"_fly_string); + + // 2. Let size be value.[[ArrayBufferByteLength]]. + auto size = array_buffer.byte_length(); + + // 3. Let dataCopy be ? CreateByteDataBlock(size). + // NOTE: This can throw a RangeError exception upon allocation failure. + auto data_copy = TRY(JS::create_byte_data_block(vm, size)); + + // 4. Perform CopyDataBlockBytes(dataCopy, 0, value.[[ArrayBufferData]], 0, size). + JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size); + + // FIXME: 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer", + // [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }. + if (false) { + } + // 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }. + else { + vector.append(ValueTag::ArrayBuffer); + TRY(serialize_bytes(vm, vector, data_copy.buffer().bytes())); + } + } + return {}; +} + +template ViewType> +WebIDL::ExceptionOr serialize_viewed_array_buffer(JS::VM& vm, Vector& vector, ViewType const& view, bool for_storage, SerializationMemory& memory) +{ + // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then: + + auto view_record = [&]() { + if constexpr (IsSame) { + return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst); + } else { + return JS::make_typed_array_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst); + } + }(); + + // 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException. + if constexpr (IsSame) { + if (JS::is_view_out_of_bounds(view_record)) + return WebIDL::DataCloneError::create(*vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "DataView"sv))); + } else { + if (JS::is_typed_array_out_of_bounds(view_record)) + return WebIDL::DataCloneError::create(*vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "TypedArray"sv))); + } + + // 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(vm, JS::Value(buffer), for_storage, 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) { + vector.append(ValueTag::ArrayBufferView); + vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]] + TRY(serialize_string(vm, vector, "DataView"_string)); // [[Constructor]] + vector.append(JS::get_view_byte_length(view_record)); + 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); + vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]] + TRY(serialize_string(vm, vector, view.element_name())); // [[Constructor]] + vector.append(JS::typed_array_byte_length(view_record)); + vector.append(view.byte_offset()); + vector.append(JS::typed_array_length(view_record)); + } + return {}; +} +template WebIDL::ExceptionOr serialize_viewed_array_buffer(JS::VM& vm, Vector& vector, JS::TypedArrayBase const& view, bool for_storage, SerializationMemory& memory); +template WebIDL::ExceptionOr serialize_viewed_array_buffer(JS::VM& vm, Vector& vector, JS::DataView const& view, bool for_storage, SerializationMemory& memory); + class Deserializer { public: Deserializer(JS::VM& vm, JS::Realm& target_realm, ReadonlySpan serialized, DeserializationMemory& memory) diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h index bdf48c342d..0ffb13eb6e 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2022, Daniel Ehrenberg * Copyright (c) 2022, Andrew Kaster + * Copyright (c) 2024, Kenneth Myhra * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,6 +13,8 @@ #include #include #include +#include +#include #include // Structured serialize is an entirely different format from IPC because: @@ -50,6 +53,14 @@ WebIDL::ExceptionOr structured_serialize_internal(JS::VM& v WebIDL::ExceptionOr structured_deserialize(JS::VM& vm, SerializationRecord const& serialized, JS::Realm& target_realm, Optional); +WebIDL::ExceptionOr serialize_bytes(JS::VM& vm, Vector& vector, ReadonlyBytes bytes); +WebIDL::ExceptionOr serialize_string(JS::VM& vm, Vector& vector, DeprecatedFlyString const& string); +WebIDL::ExceptionOr serialize_string(JS::VM& vm, Vector& vector, String const& string); +WebIDL::ExceptionOr serialize_string(JS::VM& vm, Vector& vector, JS::PrimitiveString const& primitive_string); +WebIDL::ExceptionOr serialize_array_buffer(JS::VM& vm, Vector& vector, JS::ArrayBuffer const& array_buffer, bool for_storage); +template ViewType> +WebIDL::ExceptionOr serialize_viewed_array_buffer(JS::VM& vm, Vector& vector, ViewType const& view, bool for_storage, SerializationMemory& memory); + WebIDL::ExceptionOr deserialize_bytes(JS::VM& vm, ReadonlySpan vector, size_t& position); WebIDL::ExceptionOr deserialize_string(JS::VM& vm, ReadonlySpan vector, size_t& position); WebIDL::ExceptionOr> deserialize_string_primitive(JS::VM& vm, ReadonlySpan vector, size_t& position);