From 0bb987e809ff02ca4bc7776533a69394fb1bb932 Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Sat, 4 Apr 2026 19:25:48 +0200 Subject: [PATCH] LibJS+LibWeb: Allow instantiating DataBlock with an external buffer This requires the minimal API exposed by ByteBuffer, allowing external users to implement them as needed instead of being forced to use a ByteBuffer. --- .../AsmInterpreter/AsmInterpreter.cpp | 4 +- Libraries/LibJS/Bytecode/Interpreter.cpp | 4 +- Libraries/LibJS/Print.cpp | 4 +- Libraries/LibJS/Runtime/ArrayBuffer.cpp | 35 +++++--- Libraries/LibJS/Runtime/ArrayBuffer.h | 90 ++++++++++++++++--- .../LibJS/Runtime/ArrayBufferPrototype.cpp | 16 ++-- Libraries/LibJS/Runtime/TypedArray.h | 6 +- .../LibJS/Runtime/TypedArrayPrototype.cpp | 12 +-- Libraries/LibJS/Runtime/Uint8Array.cpp | 4 +- Libraries/LibWeb/Crypto/Crypto.cpp | 2 +- Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 4 +- Libraries/LibWeb/Crypto/KeyAlgorithms.cpp | 2 +- Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 7 +- Libraries/LibWeb/FileAPI/Blob.cpp | 8 +- Libraries/LibWeb/FileAPI/FileReaderSync.cpp | 2 +- Libraries/LibWeb/HTML/StructuredSerialize.cpp | 13 ++- .../LibWeb/IndexedDB/Internal/Algorithms.cpp | 2 +- .../Streams/ReadableStreamDefaultReader.cpp | 2 +- .../Streams/ReadableStreamOperations.cpp | 6 +- .../LibWeb/WebGL/WebGLRenderingContextBase.h | 4 +- Libraries/LibWeb/WebIDL/Buffers.cpp | 2 +- Libraries/LibWeb/WebSockets/WebSocket.cpp | 2 +- 22 files changed, 163 insertions(+), 68 deletions(-) diff --git a/Libraries/LibJS/Bytecode/AsmInterpreter/AsmInterpreter.cpp b/Libraries/LibJS/Bytecode/AsmInterpreter/AsmInterpreter.cpp index 1aaf732471f..100d6adfe45 100644 --- a/Libraries/LibJS/Bytecode/AsmInterpreter/AsmInterpreter.cpp +++ b/Libraries/LibJS/Bytecode/AsmInterpreter/AsmInterpreter.cpp @@ -1063,7 +1063,7 @@ i64 asm_try_get_by_value_typed_array(VM* vm, u32 pc) } auto* buffer = typed_array.viewed_array_buffer(); - auto const* data = buffer->buffer().data() + typed_array.byte_offset(); + auto const* data = buffer->data() + typed_array.byte_offset(); Value result; switch (typed_array.kind()) { @@ -1133,7 +1133,7 @@ i64 asm_try_put_by_value_typed_array(VM* vm, u32 pc) return 0; auto* buffer = typed_array.viewed_array_buffer(); - auto* data = buffer->buffer().data() + typed_array.byte_offset(); + auto* data = buffer->data() + typed_array.byte_offset(); auto value = vm->get(insn.src()); if (value.is_int32()) { diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index 3e420dbd189..bb3e8593dcf 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -893,7 +893,7 @@ inline Value fast_typed_array_get_element(TypedArrayBase& typed_array, u32 index } auto const& array_buffer = *typed_array.viewed_array_buffer(); - auto const* slot = reinterpret_cast(array_buffer.buffer().offset_pointer(offset_into_array_buffer.value())); + auto const* slot = reinterpret_cast(array_buffer.span().offset_pointer(offset_into_array_buffer.value())); return Value { *slot }; } @@ -911,7 +911,7 @@ inline void fast_typed_array_set_element(TypedArrayBase& typed_array, u32 index, } auto& array_buffer = *typed_array.viewed_array_buffer(); - auto* slot = reinterpret_cast(array_buffer.buffer().offset_pointer(offset_into_array_buffer.value())); + auto* slot = reinterpret_cast(array_buffer.span().offset_pointer(offset_into_array_buffer.value())); *slot = value; } diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index c73bed5fbf5..8849b6f45de 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -401,10 +401,10 @@ ErrorOr print_array_buffer(JS::PrintContext& print_context, JS::ArrayBuffe if (byte_length == 0) return {}; - auto& buffer = array_buffer.buffer(); + auto const* buffer_data = array_buffer.data(); TRY(js_out(print_context, "\n")); for (size_t i = 0; i < byte_length; ++i) { - TRY(js_out(print_context, "{:02x}", buffer[i])); + TRY(js_out(print_context, "{:02x}", buffer_data[i])); if (i + 1 < byte_length) { if ((i + 1) % 32 == 0) TRY(js_out(print_context, "\n")); diff --git a/Libraries/LibJS/Runtime/ArrayBuffer.cpp b/Libraries/LibJS/Runtime/ArrayBuffer.cpp index d98cb6ad22e..fdfaa8bf6b7 100644 --- a/Libraries/LibJS/Runtime/ArrayBuffer.cpp +++ b/Libraries/LibJS/Runtime/ArrayBuffer.cpp @@ -43,6 +43,11 @@ GC::Ref ArrayBuffer::create(Realm& realm, ByteBuffer* buffer, DataB return realm.create(buffer, is_shared, prototype_for_shared_state(realm, is_shared)); } +GC::Ref ArrayBuffer::create(Realm& realm, DataBlock::UnownedExternalBuffer buffer, DataBlock::Shared is_shared) +{ + return realm.create(buffer, is_shared, prototype_for_shared_state(realm, is_shared)); +} + ArrayBuffer::ArrayBuffer(ByteBuffer buffer, DataBlock::Shared is_shared, Object& prototype) : Object(ConstructWithPrototypeTag::Tag, prototype) , m_data_block(DataBlock { move(buffer), is_shared }) @@ -57,6 +62,13 @@ ArrayBuffer::ArrayBuffer(ByteBuffer* buffer, DataBlock::Shared is_shared, Object { } +ArrayBuffer::ArrayBuffer(DataBlock::UnownedExternalBuffer buffer, DataBlock::Shared is_shared, Object& prototype) + : Object(ConstructWithPrototypeTag::Tag, prototype) + , m_data_block(DataBlock { move(buffer), is_shared }) + , m_detach_key(js_undefined()) +{ +} + void ArrayBuffer::account_external_memory_change(size_t old_external_memory_size, size_t new_external_memory_size) { if (new_external_memory_size > old_external_memory_size) { @@ -83,6 +95,8 @@ void ArrayBuffer::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_detach_key); + if (auto* external = m_data_block.byte_buffer.get_pointer()) + visitor.visit(external->owner); } // 6.2.9.1 CreateByteDataBlock ( size ), https://tc39.es/ecma262/#sec-createbytedatablock @@ -195,7 +209,7 @@ ThrowCompletionOr allocate_array_buffer(VM& vm, FunctionObject& co } // 4. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", slots). - auto obj = TRY(ordinary_create_from_constructor(vm, constructor, &Intrinsics::array_buffer_prototype, nullptr, DataBlock::Shared::No)); + auto obj = TRY(ordinary_create_from_constructor(vm, constructor, &Intrinsics::array_buffer_prototype, static_cast(nullptr), DataBlock::Shared::No)); // 5. Let block be ? CreateByteDataBlock(byteLength). auto block = TRY(create_byte_data_block(vm, byte_length)); @@ -269,7 +283,7 @@ ThrowCompletionOr array_buffer_copy_and_detach(VM& vm, ArrayBuffer // 12. Let toBlock be newBuffer.[[ArrayBufferData]]. // 13. Perform CopyDataBlockBytes(toBlock, 0, fromBlock, 0, copyLength). // 14. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. Implementations may implement this method as a zero-copy move or a realloc. - copy_data_block_bytes(new_buffer->buffer(), 0, array_buffer.buffer(), 0, copy_length); + new_buffer->overwrite(0, array_buffer.data(), copy_length); // 15. Perform ! DetachArrayBuffer(arrayBuffer). MUST(detach_array_buffer(vm, array_buffer)); @@ -299,10 +313,11 @@ ThrowCompletionOr ArrayBuffer::detach_and_take_bytes(VM& vm) return vm.throw_completion(ErrorType::DetachKeyMismatch, js_undefined(), detach_key()); auto old_external_memory_size = external_memory_size(); - auto bytes = m_data_block.byte_buffer.visit( - [](Empty) -> ByteBuffer { VERIFY_NOT_REACHED(); }, - [](ByteBuffer& value) -> ByteBuffer { return move(value); }, - [](DataBlock::UnownedFixedLengthByteBuffer& value) -> ByteBuffer { return MUST(ByteBuffer::copy(value.buffer->span())); }); + ByteBuffer bytes; + if (auto* buffer = m_data_block.byte_buffer.get_pointer()) + bytes = move(*buffer); + else + bytes = MUST(ByteBuffer::copy(span())); for (auto& cached_view : m_cached_views) { auto& view = static_cast(cached_view); if (view.viewed_array_buffer() == this) @@ -353,13 +368,11 @@ ThrowCompletionOr clone_array_buffer(VM& vm, ArrayBuffer& source_b auto* target_buffer = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), source_length)); // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. - auto& source_block = source_buffer.buffer(); + auto source_block = source_buffer.bytes().slice(source_byte_offset, source_length); // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. - auto& target_block = target_buffer->buffer(); - // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). - copy_data_block_bytes(target_block, 0, source_block, source_byte_offset, source_length); + target_buffer->overwrite(0, source_block.data(), source_length); // 6. Return targetBuffer. return target_buffer; @@ -404,7 +417,7 @@ ThrowCompletionOr> allocate_shared_array_buffer(VM& vm, Fun // a. Append [[ArrayBufferByteLength]] to slots. // 5. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", slots). - auto obj = TRY(ordinary_create_from_constructor(vm, constructor, &Intrinsics::shared_array_buffer_prototype, nullptr, DataBlock::Shared::Yes)); + auto obj = TRY(ordinary_create_from_constructor(vm, constructor, &Intrinsics::shared_array_buffer_prototype, static_cast(nullptr), DataBlock::Shared::Yes)); // 6. If allocatingGrowableBuffer is true, let allocLength be maxByteLength; otherwise let allocLength be byteLength. auto alloc_length = allocating_growable_buffer ? *max_byte_length : byte_length; diff --git a/Libraries/LibJS/Runtime/ArrayBuffer.h b/Libraries/LibJS/Runtime/ArrayBuffer.h index 808f2caeeac..afd872c2db4 100644 --- a/Libraries/LibJS/Runtime/ArrayBuffer.h +++ b/Libraries/LibJS/Runtime/ArrayBuffer.h @@ -63,21 +63,74 @@ struct DataBlock { size_t size = 0; }; + // AD-HOC: ECMA-262 models ArrayBuffer backing storage as a Data Block. We additionally allow + // host code to provide an external byte store via callbacks so engine-independent + // consumers like LibWeb can project spec-defined host objects onto ArrayBuffer without + // teaching LibJS about those hosts. + struct UnownedExternalBuffer { + using DataFunction = u8* (*)(void*); + using SizeFunction = size_t (*)(void*); + + explicit UnownedExternalBuffer(GC::Ref owner, void* context, DataFunction data, SizeFunction size) + : context(context) + , data(data) + , size(size) + , owner(owner) + { + } + + explicit UnownedExternalBuffer(GC::Ref owner, void* context, DataFunction data, size_t fixed_size) + : context(context) + , data(data) + , size(fixed_size) + , owner(owner) + { + } + + void* context { nullptr }; + DataFunction data { nullptr }; + Variant size; + GC::Ref owner; + }; + ByteBuffer& buffer() { return byte_buffer.visit( [&](Empty) -> ByteBuffer& { VERIFY_NOT_REACHED(); }, [&](ByteBuffer& value) -> ByteBuffer& { return value; }, - [&](UnownedFixedLengthByteBuffer& value) -> ByteBuffer& { return *value.buffer; }); + [&](UnownedFixedLengthByteBuffer& value) -> ByteBuffer& { return *value.buffer; }, + [&](UnownedExternalBuffer&) -> ByteBuffer& { VERIFY_NOT_REACHED(); }); } ByteBuffer const& buffer() const { return const_cast(this)->buffer(); } + u8* data() + { + return byte_buffer.visit( + [](Empty) -> u8* { VERIFY_NOT_REACHED(); }, + [](ByteBuffer& value) -> u8* { return value.data(); }, + [](UnownedFixedLengthByteBuffer& value) -> u8* { return value.buffer->data(); }, + [](UnownedExternalBuffer& value) -> u8* { return value.data ? value.data(value.context) : nullptr; }); + } + u8 const* data() const { return const_cast(this)->data(); } + + Bytes span() { return { data(), size() }; } + ReadonlyBytes span() const { return { data(), size() }; } + Bytes bytes() { return { data(), size() }; } + ReadonlyBytes bytes() const { return { data(), size() }; } + void overwrite(size_t offset, void const* source, size_t count) + { + VERIFY(offset <= size()); + VERIFY(count <= size() - offset); + __builtin_memcpy(data() + offset, source, count); + } + size_t size() const { return byte_buffer.visit( [](Empty) -> size_t { return 0u; }, [](ByteBuffer const& buffer) { return buffer.size(); }, - [](UnownedFixedLengthByteBuffer const& value) { return value.size; }); + [](UnownedFixedLengthByteBuffer const& value) { return value.size; }, + [](UnownedExternalBuffer const& value) { return value.size ? value.size(value.context) : 0; }); } size_t external_memory_size() const @@ -85,10 +138,13 @@ struct DataBlock { return byte_buffer.visit( [](Empty) -> size_t { return 0; }, [](ByteBuffer const& buffer) { return buffer.is_inline() ? 0 : buffer.capacity(); }, - [](UnownedFixedLengthByteBuffer const&) -> size_t { return 0; }); + [](UnownedFixedLengthByteBuffer const&) -> size_t { return 0; }, + [](UnownedExternalBuffer const&) -> size_t { return 0; }); } - Variant byte_buffer; + bool is_external() const { return byte_buffer.has(); } + + Variant byte_buffer; Shared is_shared = { Shared::No }; }; @@ -100,6 +156,7 @@ public: static ThrowCompletionOr> create(Realm&, size_t, DataBlock::Shared = DataBlock::Shared::No); static GC::Ref create(Realm&, ByteBuffer, DataBlock::Shared = DataBlock::Shared::No); static GC::Ref create(Realm&, ByteBuffer*, DataBlock::Shared = DataBlock::Shared::No); + static GC::Ref create(Realm&, DataBlock::UnownedExternalBuffer, DataBlock::Shared = DataBlock::Shared::No); virtual ~ArrayBuffer() override = default; @@ -109,6 +166,14 @@ public: // [[ArrayBufferData]] ByteBuffer& buffer() { return m_data_block.buffer(); } ByteBuffer const& buffer() const { return m_data_block.buffer(); } + u8* data() { return m_data_block.data(); } + u8 const* data() const { return m_data_block.data(); } + Bytes span() { return m_data_block.span(); } + ReadonlyBytes span() const { return m_data_block.span(); } + Bytes bytes() { return m_data_block.bytes(); } + ReadonlyBytes bytes() const { return m_data_block.bytes(); } + void overwrite(size_t offset, void const* source, size_t count) { m_data_block.overwrite(offset, source, count); } + bool is_external() const { return m_data_block.is_external(); } // Detaches this ArrayBuffer and returns its underlying bytes as a ByteBuffer for use in a TransferArrayBuffer-like // operation. Moves the storage when we own it and copies it for externally-owned buffers (e.g. Wasm memory). @@ -185,6 +250,7 @@ public: private: ArrayBuffer(ByteBuffer buffer, DataBlock::Shared, Object& prototype); ArrayBuffer(ByteBuffer* buffer, DataBlock::Shared, Object& prototype); + ArrayBuffer(DataBlock::UnownedExternalBuffer buffer, DataBlock::Shared, Object& prototype); virtual bool is_array_buffer() const final { return true; } @@ -325,10 +391,10 @@ Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_a VERIFY(!is_detached()); // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. - VERIFY(m_data_block.buffer().bytes().slice(byte_index).size() >= sizeof(T)); + VERIFY(m_data_block.bytes().slice(byte_index).size() >= sizeof(T)); // 3. Let block be arrayBuffer.[[ArrayBufferData]]. - auto& block = m_data_block.buffer(); + auto block = m_data_block.bytes(); // 4. Let elementSize be the Element Size value specified in Table 70 for Element Type type. auto element_size = sizeof(T); @@ -349,7 +415,7 @@ Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_a // 6. Else, else { // a. Let rawValue be a List whose elements are bytes from block at indices in the interval from byteIndex (inclusive) to byteIndex + elementSize (exclusive). - block.bytes().slice(byte_index, element_size).copy_to(raw_value); + block.slice(byte_index, element_size).copy_to(raw_value); } // 7. Assert: The number of elements in rawValue is elementSize. @@ -443,7 +509,7 @@ void ArrayBuffer::set_value(size_t byte_index, Value value, [[maybe_unused]] boo VERIFY(!is_detached()); // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. - VERIFY(m_data_block.buffer().bytes().slice(byte_index).size() >= sizeof(T)); + VERIFY(m_data_block.bytes().slice(byte_index).size() >= sizeof(T)); // 3. Assert: value is a BigInt if IsBigIntElementType(type) is true; otherwise, value is a Number. if constexpr (IsIntegral && sizeof(T) == 8) @@ -452,7 +518,7 @@ void ArrayBuffer::set_value(size_t byte_index, Value value, [[maybe_unused]] boo VERIFY(value.is_number()); // 4. Let block be arrayBuffer.[[ArrayBufferData]]. - auto& block = m_data_block.buffer(); + auto block = m_data_block.span(); // FIXME: 5. Let elementSize be the Element Size value specified in Table 70 for Element Type type. @@ -473,7 +539,7 @@ void ArrayBuffer::set_value(size_t byte_index, Value value, [[maybe_unused]] boo // 9. Else, else { // a. Store the individual bytes of rawBytes into block, starting at block[byteIndex]. - raw_bytes.span().copy_to(block.span().slice(byte_index)); + raw_bytes.span().copy_to(block.slice(byte_index)); } // 10. Return unused. @@ -491,9 +557,9 @@ Value ArrayBuffer::get_modify_set_value(size_t byte_index, Value value, ReadWrit // FIXME: Check for shared buffer auto raw_bytes_read = MUST(ByteBuffer::create_uninitialized(sizeof(T))); - m_data_block.buffer().bytes().slice(byte_index, sizeof(T)).copy_to(raw_bytes_read); + m_data_block.bytes().slice(byte_index, sizeof(T)).copy_to(raw_bytes_read); auto raw_bytes_modified = operation(raw_bytes_read, raw_bytes); - raw_bytes_modified.span().copy_to(m_data_block.buffer().span().slice(byte_index)); + raw_bytes_modified.span().copy_to(m_data_block.span().slice(byte_index)); return raw_bytes_to_numeric(vm, raw_bytes_read, is_little_endian); } diff --git a/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp b/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp index a531805450b..3d3437fb068 100644 --- a/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp +++ b/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp @@ -154,7 +154,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::resize) return js_undefined(); // 9. Let oldBlock be O.[[ArrayBufferData]]. - auto const& old_block = array_buffer_object->buffer(); + // NOTE: oldBlock is read directly in step 12. // 10. Let newBlock be ? CreateByteDataBlock(newByteLength). auto new_block = TRY(create_byte_data_block(vm, new_byte_length)); @@ -163,7 +163,10 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::resize) auto copy_length = min(new_byte_length, array_buffer_object->byte_length()); // 12. Perform CopyDataBlockBytes(newBlock, 0, oldBlock, 0, copyLength). - copy_data_block_bytes(new_block.buffer(), 0, old_block, 0, copy_length); + if (array_buffer_object->is_external()) + new_block.overwrite(0, array_buffer_object->data(), copy_length); + else + copy_data_block_bytes(new_block.buffer(), 0, array_buffer_object->buffer(), 0, copy_length); // 13. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. Implementations may implement this method as in-place growth or shrinkage. @@ -264,10 +267,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice) return vm.throw_completion(ErrorType::DetachedArrayBuffer); // 24. Let fromBuf be O.[[ArrayBufferData]]. - auto& from_buf = array_buffer_object->buffer(); - // 25. Let toBuf be new.[[ArrayBufferData]]. - auto& to_buf = new_array_buffer_object->buffer(); + // NOTE: fromBuf and toBuf are read directly in step 27b. // 26. Let currentLen be O.[[ArrayBufferByteLength]]. auto current_length = array_buffer_object->byte_length(); @@ -278,7 +279,10 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::slice) auto count = min(new_length, current_length - first); // b. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, count). - copy_data_block_bytes(to_buf, 0, from_buf, first, count); + if (array_buffer_object->is_external()) + new_array_buffer_object->overwrite(0, array_buffer_object->data() + (size_t)first, (size_t)count); + else + copy_data_block_bytes(new_array_buffer_object->buffer(), 0, array_buffer_object->buffer(), first, count); } // 28. Return new. diff --git a/Libraries/LibJS/Runtime/TypedArray.h b/Libraries/LibJS/Runtime/TypedArray.h index deeef283f60..142933abbaa 100644 --- a/Libraries/LibJS/Runtime/TypedArray.h +++ b/Libraries/LibJS/Runtime/TypedArray.h @@ -107,7 +107,7 @@ protected: } m_viewed_array_buffer->register_cached_typed_array_view(*this); - m_data = m_viewed_array_buffer->buffer().data() + m_byte_offset; + m_data = m_viewed_array_buffer->data() + m_byte_offset; } u32 m_element_size { 0 }; @@ -506,7 +506,7 @@ public: } auto length = typed_array_length(typed_array_record); - return { reinterpret_cast(m_viewed_array_buffer->buffer().data() + m_byte_offset), length }; + return { reinterpret_cast(m_viewed_array_buffer->data() + m_byte_offset), length }; } Span data() @@ -519,7 +519,7 @@ public: } auto length = typed_array_length(typed_array_record); - return { reinterpret_cast(m_viewed_array_buffer->buffer().data() + m_byte_offset), length }; + return { reinterpret_cast(m_viewed_array_buffer->data() + m_byte_offset), length }; } bool is_unclamped_integer_element_type() const override diff --git a/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp b/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp index ebfb859578e..d9c8b1325f6 100644 --- a/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp +++ b/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp @@ -376,7 +376,7 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within) to_end += count_bytes; if (!from_end.has_overflow() && !to_end.has_overflow()) { - auto* base = buffer->buffer().data(); + auto* base = buffer->data(); void const* src = base + from_byte_index; void* dst = base + to_byte_index; memmove(dst, src, count_bytes); @@ -511,7 +511,7 @@ inline void fast_typed_array_fill(TypedArrayBase& typed_array, u32 begin, u32 en } auto& array_buffer = *typed_array.viewed_array_buffer(); - auto* slot = reinterpret_cast(array_buffer.buffer().offset_pointer(computed_begin.value())); + auto* slot = reinterpret_cast(array_buffer.data() + computed_begin.value()); for (auto i = begin; i < end; ++i) *(slot++) = value; } @@ -1469,7 +1469,7 @@ static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArr auto same_shared_array_buffer = false; // 18. If IsSharedArrayBuffer(srcBuffer) is true, IsSharedArrayBuffer(targetBuffer) is true, and srcBuffer.[[ArrayBufferData]] is targetBuffer.[[ArrayBufferData]], let sameSharedArrayBuffer be true; otherwise, let sameSharedArrayBuffer be false. - if (source_buffer->is_shared_array_buffer() && target_buffer->is_shared_array_buffer() && (&source_buffer->buffer() == &target_buffer->buffer())) + if (source_buffer->is_shared_array_buffer() && target_buffer->is_shared_array_buffer() && (source_buffer->data() == target_buffer->data())) same_shared_array_buffer = true; size_t source_byte_index = 0; @@ -1517,7 +1517,7 @@ static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArr // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). // iii. Set srcByteIndex to srcByteIndex + 1. // iv. Set targetByteIndex to targetByteIndex + 1. - target_buffer->buffer().overwrite(target_byte_index, source_buffer->buffer().data() + source_byte_index, limit - target_byte_index); + target_buffer->overwrite(target_byte_index, source_buffer->data() + source_byte_index, limit - target_byte_index); } // 24. Else, else { @@ -1757,8 +1757,8 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::slice) // OPTIMIZATION: If the buffers are not detached and not shared, we can do a single bulk copy. if (!target_buffer.is_detached() && !target_buffer.is_shared_array_buffer() && !source_buffer.is_detached() && !source_buffer.is_shared_array_buffer() - && &target_buffer.buffer() != &source_buffer.buffer()) { - target_buffer.buffer().overwrite(target_byte_index, source_buffer.buffer().data() + source_byte_index.value(), limit.value() - target_byte_index); + && target_buffer.data() != source_buffer.data()) { + target_buffer.overwrite(target_byte_index, source_buffer.data() + source_byte_index.value(), limit.value() - target_byte_index); } else { // ix. Repeat, while targetByteIndex < limit, while (target_byte_index < limit) { diff --git a/Libraries/LibJS/Runtime/Uint8Array.cpp b/Libraries/LibJS/Runtime/Uint8Array.cpp index 499d21e3de7..a5469ce1b0d 100644 --- a/Libraries/LibJS/Runtime/Uint8Array.cpp +++ b/Libraries/LibJS/Runtime/Uint8Array.cpp @@ -163,7 +163,7 @@ JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayConstructorHelpers::from_hex) // 7. Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding // index of result.[[Bytes]]. - auto& array_buffer_data = typed_array->viewed_array_buffer()->buffer(); + auto* array_buffer_data = typed_array->viewed_array_buffer()->data(); for (size_t index = 0; index < result_length; ++index) array_buffer_data[index] = result.bytes[index]; @@ -464,7 +464,7 @@ ThrowCompletionOr get_uint8_array_bytes_view(VM& vm, TypedArrayBa auto byte_offset = typed_array.byte_offset(); return ReadonlyBytes { - typed_array.viewed_array_buffer()->buffer().data() + byte_offset, + typed_array.viewed_array_buffer()->data() + byte_offset, length }; } diff --git a/Libraries/LibWeb/Crypto/Crypto.cpp b/Libraries/LibWeb/Crypto/Crypto.cpp index b6dde7dd79f..2c6207adc33 100644 --- a/Libraries/LibWeb/Crypto/Crypto.cpp +++ b/Libraries/LibWeb/Crypto/Crypto.cpp @@ -70,7 +70,7 @@ WebIDL::ExceptionOr> Crypto::get_random_values return WebIDL::QuotaExceededError::create(realm(), "array's byteLength may not be greater than 65536"_utf16); // 3. Overwrite all elements of array with cryptographically strong random values of the appropriate type. - fill_with_random(array->viewed_array_buffer()->buffer().bytes().slice(array->byte_offset(), array->byte_length())); + fill_with_random(array->viewed_array_buffer()->bytes().slice(array->byte_offset(), array->byte_length())); // 4. Return array. return array; diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 4903e78ea45..bf3d1af2542 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -92,9 +92,9 @@ static ::Crypto::UnsignedBigInteger big_integer_from_api_big_integer(GC::Ptrviewed_array_buffer()->buffer(); + auto buffer = big_integer->viewed_array_buffer()->bytes(); - if (buffer.size() > 0) + if (!buffer.is_empty()) return ::Crypto::UnsignedBigInteger::import_data(buffer); return ::Crypto::UnsignedBigInteger(0); } diff --git a/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp b/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp index c4c944554cf..30b62eb91c3 100644 --- a/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp @@ -104,7 +104,7 @@ WebIDL::ExceptionOr RsaKeyAlgorithm::set_public_exponent(::Crypto::Unsigne // The BigInteger typedef from the WebCrypto spec requires the bytes in the Uint8Array be ordered in Big Endian m_public_exponent = TRY(JS::Uint8Array::create(realm, result.size())); - m_public_exponent->viewed_array_buffer()->buffer().overwrite(0, result.data(), result.size()); + m_public_exponent->viewed_array_buffer()->overwrite(0, result.data(), result.size()); return {}; } diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 8db7f9def85..09e1a724386 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -859,7 +859,8 @@ GC::Ref SubtleCrypto::derive_key(AlgorithmIdentifier algorithm, } // 15. Let result be the result of performing the import key operation specified by normalizedDerivedKeyAlgorithmImport using "raw" as format, secret as keyData, derivedKeyType as algorithm and using extractable and usages. - auto result_or_error = normalized_derived_key_algorithm_import.methods->import_key(*normalized_derived_key_algorithm_import.parameter, Bindings::KeyFormat::Raw, secret.release_value()->buffer(), extractable, key_usages); + auto secret_bytes = MUST(ByteBuffer::copy(secret.release_value()->bytes())); + auto result_or_error = normalized_derived_key_algorithm_import.methods->import_key(*normalized_derived_key_algorithm_import.parameter, Bindings::KeyFormat::Raw, move(secret_bytes), extractable, key_usages); if (result_or_error.is_error()) { WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value()); return; @@ -1134,7 +1135,7 @@ GC::Ref SubtleCrypto::unwrap_key(Bindings::KeyFormat format, Ke // 15. If format is equal to the string "jwk": if (format == Bindings::KeyFormat::Jwk) { // Let key be the result of executing the parse a JWK algorithm, with bytes as the data to be parsed. - auto maybe_parsed = JsonWebKey::parse(realm, bytes->buffer()); + auto maybe_parsed = JsonWebKey::parse(realm, bytes->bytes()); if (maybe_parsed.is_error()) { WebIDL::reject_promise(realm, promise, maybe_parsed.release_error().release_value()); return; @@ -1466,7 +1467,7 @@ GC::Ref SubtleCrypto::decapsulate_key(AlgorithmIdentifier decap auto maybe_shared_key = normalized_shared_key_algorithm.methods->import_key( *normalized_shared_key_algorithm.parameter, Bindings::KeyFormat::RawSecret, - decapsulated_bits->buffer(), + MUST(ByteBuffer::copy(decapsulated_bits->bytes())), extractable, usages); if (maybe_shared_key.is_error()) { diff --git a/Libraries/LibWeb/FileAPI/Blob.cpp b/Libraries/LibWeb/FileAPI/Blob.cpp index d53a8e9c74c..f20fef2ba25 100644 --- a/Libraries/LibWeb/FileAPI/Blob.cpp +++ b/Libraries/LibWeb/FileAPI/Blob.cpp @@ -393,10 +393,10 @@ GC::Ref Blob::text() return WebIDL::upon_fulfillment(*promise, GC::create_function(heap(), [&vm](JS::Value first_argument) -> WebIDL::ExceptionOr { auto const& object = first_argument.as_object(); VERIFY(is(object)); - auto const& buffer = static_cast(object).buffer(); + auto const& array_buffer = static_cast(object); auto decoder = TextCodec::decoder_for("UTF-8"sv); - auto utf8_text = TRY_OR_THROW_OOM(vm, TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, buffer)); + auto utf8_text = TRY_OR_THROW_OOM(vm, TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, array_buffer.bytes())); return JS::PrimitiveString::create(vm, move(utf8_text)); })); } @@ -422,9 +422,9 @@ GC::Ref Blob::array_buffer() return WebIDL::upon_fulfillment(*promise, GC::create_function(heap(), [&realm](JS::Value first_argument) -> WebIDL::ExceptionOr { auto const& object = first_argument.as_object(); VERIFY(is(object)); - auto const& buffer = static_cast(object).buffer(); + auto const& array_buffer = static_cast(object); - return JS::ArrayBuffer::create(realm, buffer); + return JS::ArrayBuffer::create(realm, MUST(ByteBuffer::copy(array_buffer.bytes()))); })); } diff --git a/Libraries/LibWeb/FileAPI/FileReaderSync.cpp b/Libraries/LibWeb/FileAPI/FileReaderSync.cpp index 9815b1f1ea6..cdb0c5f60e0 100644 --- a/Libraries/LibWeb/FileAPI/FileReaderSync.cpp +++ b/Libraries/LibWeb/FileAPI/FileReaderSync.cpp @@ -94,7 +94,7 @@ WebIDL::ExceptionOr FileReaderSync::read_as(Blob& blob, FileReader::Type if (promise->state() == JS::Promise::State::Fulfilled && array_buffer) { // AD-HOC: This diverges from the spec as wrritten, where the type argument is specified explicitly for each caller. // 1. Return the result of package data given bytes, type, blob’s type, and encoding. - auto result = TRY(FileReader::blob_package_data(realm(), array_buffer->buffer(), type, blob.type(), encoding)); + auto result = TRY(FileReader::blob_package_data(realm(), MUST(ByteBuffer::copy(array_buffer->bytes())), type, blob.type(), encoding)); return result.get(); } diff --git a/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Libraries/LibWeb/HTML/StructuredSerialize.cpp index ef2198eaf7f..bfd87911ce9 100644 --- a/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -167,7 +167,10 @@ static WebIDL::ExceptionOr serialize_array_buffer(JS::VM& vm, TransferData 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); + if (array_buffer.is_external()) + data_copy.overwrite(0, array_buffer.data(), size); + else + JS::copy_data_block_bytes(data_copy.buffer(), 0, array_buffer.buffer(), 0, size); // 5. If value has an [[ArrayBufferMaxByteLength]] internal slot, then set serialized to { [[Type]]: "ResizableArrayBuffer", // [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size, [[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }. @@ -1040,13 +1043,17 @@ WebIDL::ExceptionOr structured_serialize_with_transfer // 4. If transferable has an [[ArrayBufferData]] internal slot, then: if (array_buffer) { // 1. If transferable has an [[ArrayBufferMaxByteLength]] internal slot, then: + auto buffer_data = array_buffer->is_external() + ? MUST(ByteBuffer::copy(array_buffer->bytes())) + : ByteBuffer(array_buffer->buffer()); + if (!array_buffer->is_fixed_length()) { // 1. Set dataHolder.[[Type]] to "ResizableArrayBuffer". data_holder.encode(TransferType::ResizableArrayBuffer); // 2. Set dataHolder.[[ArrayBufferData]] to transferable.[[ArrayBufferData]]. // 3. Set dataHolder.[[ArrayBufferByteLength]] to transferable.[[ArrayBufferByteLength]]. - data_holder.encode(array_buffer->buffer()); + data_holder.encode(buffer_data); // 4. Set dataHolder.[[ArrayBufferMaxByteLength]] to transferable.[[ArrayBufferMaxByteLength]]. data_holder.encode(array_buffer->max_byte_length()); @@ -1058,7 +1065,7 @@ WebIDL::ExceptionOr structured_serialize_with_transfer // 2. Set dataHolder.[[ArrayBufferData]] to transferable.[[ArrayBufferData]]. // 3. Set dataHolder.[[ArrayBufferByteLength]] to transferable.[[ArrayBufferByteLength]]. - data_holder.encode(array_buffer->buffer()); + data_holder.encode(buffer_data); } // 3. Perform ? DetachArrayBuffer(transferable). diff --git a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp index b29621fd37f..ff0247b39fc 100644 --- a/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp +++ b/Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp @@ -763,7 +763,7 @@ JS::Value convert_a_key_to_a_value(JS::Realm& realm, GC::Ref key) auto array_buffer = MUST(JS::ArrayBuffer::create(realm, len)); // 4. Set the entries in buffer’s [[ArrayBufferData]] internal slot to the entries in value. - buffer.span().copy_to(array_buffer->buffer()); + array_buffer->overwrite(0, buffer.data(), buffer.size()); // 5. Return buffer. return array_buffer; diff --git a/Libraries/LibWeb/Streams/ReadableStreamDefaultReader.cpp b/Libraries/LibWeb/Streams/ReadableStreamDefaultReader.cpp index 156f2b6b5b9..10bd2fcf8b9 100644 --- a/Libraries/LibWeb/Streams/ReadableStreamDefaultReader.cpp +++ b/Libraries/LibWeb/Streams/ReadableStreamDefaultReader.cpp @@ -89,7 +89,7 @@ void ReadLoopReadRequest::on_chunk(JS::Value chunk) } auto const& array = static_cast(chunk.as_object()); - auto const& buffer = array.viewed_array_buffer()->buffer(); + auto buffer = array.viewed_array_buffer()->bytes().slice(array.byte_offset(), array.byte_length().length()); // 2. Append the bytes represented by chunk to bytes. m_bytes.append(buffer); diff --git a/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp b/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp index 10af4d86325..254ca3940b6 100644 --- a/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp +++ b/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp @@ -2077,7 +2077,11 @@ bool readable_byte_stream_controller_fill_pull_into_descriptor_from_queue(Readab VERIFY(can_copy_data_block_bytes_buffer(descriptor_buffer, dest_start, queue_buffer, queue_byte_offset, bytes_to_copy)); // 8. Perform ! CopyDataBlockBytes(pullIntoDescriptor’s buffer.[[ArrayBufferData]], destStart, headOfQueue’s buffer.[[ArrayBufferData]], headOfQueue’s byte offset, bytesToCopy). - JS::copy_data_block_bytes(pull_into_descriptor.buffer->buffer(), dest_start, head_of_queue.buffer->buffer(), head_of_queue.byte_offset, bytes_to_copy); + // NOTE: Stream buffers are never externally backed, so copy_data_block_bytes is safe here. + if (pull_into_descriptor.buffer->is_external() || head_of_queue.buffer->is_external()) + pull_into_descriptor.buffer->overwrite(dest_start, head_of_queue.buffer->data() + head_of_queue.byte_offset, bytes_to_copy); + else + JS::copy_data_block_bytes(pull_into_descriptor.buffer->buffer(), dest_start, head_of_queue.buffer->buffer(), head_of_queue.byte_offset, bytes_to_copy); // 9. If headOfQueue’s byte length is bytesToCopy, if (head_of_queue.byte_length == bytes_to_copy) { diff --git a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h index a694ec35880..d534541b9e4 100644 --- a/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h +++ b/Libraries/LibWeb/WebGL/WebGLRenderingContextBase.h @@ -93,11 +93,11 @@ protected: auto raw_object = src_data->raw_object(); if (auto* array_buffer = as_if(*raw_object)) { - return TRY(get_offset_span(array_buffer->buffer().span(), src_offset, src_length_override)).reinterpret(); + return TRY(get_offset_span(array_buffer->span(), src_offset, src_length_override)).reinterpret(); } if (auto* data_view = as_if(*raw_object)) { - return TRY(get_offset_span(data_view->viewed_array_buffer()->buffer().span(), src_offset, src_length_override)).reinterpret(); + return TRY(get_offset_span(data_view->viewed_array_buffer()->span(), src_offset, src_length_override)).reinterpret(); } // NOTE: This has to be done because src_offset is the number of elements to offset by, not the number of bytes. diff --git a/Libraries/LibWeb/WebIDL/Buffers.cpp b/Libraries/LibWeb/WebIDL/Buffers.cpp index 6f056173192..c7cbe3e25bd 100644 --- a/Libraries/LibWeb/WebIDL/Buffers.cpp +++ b/Libraries/LibWeb/WebIDL/Buffers.cpp @@ -122,7 +122,7 @@ void ArrayBufferView::write(ReadonlyBytes bytes, u32 starting_offset) auto array_buffer = viewed_array_buffer(); // 5. Write bytes into arrayBuffer with startingOffset set to jsView.[[ByteOffset]] + startingOffset. - array_buffer->buffer().overwrite(byte_offset() + starting_offset, bytes.data(), bytes.size()); + array_buffer->overwrite(byte_offset() + starting_offset, bytes.data(), bytes.size()); } BufferSource::~BufferSource() = default; diff --git a/Libraries/LibWeb/WebSockets/WebSocket.cpp b/Libraries/LibWeb/WebSockets/WebSocket.cpp index bb8d915a55b..80d181f9c13 100644 --- a/Libraries/LibWeb/WebSockets/WebSocket.cpp +++ b/Libraries/LibWeb/WebSockets/WebSocket.cpp @@ -317,7 +317,7 @@ WebIDL::ExceptionOr WebSocket::send(Variant ReadonlyBytes buffer; if (auto array_buffer = buffer_source->viewed_array_buffer(); array_buffer && !array_buffer->is_detached()) - buffer = array_buffer->buffer(); + buffer = array_buffer->bytes(); m_websocket->send(buffer, false); },