From 9267e2474127426816aaed7887418675acbded77 Mon Sep 17 00:00:00 2001 From: Tobias Soppa Date: Mon, 11 Sep 2023 10:35:03 +0200 Subject: [PATCH] LibJS+LibWeb: Track SharedArrayBuffers' shared state ArrayBuffer no longer stores a plain ByteBuffer internally, but a DataBlock instead, which encapsulated the ByteBuffer together with information if it is shared or not. --- .../Libraries/LibJS/Runtime/ArrayBuffer.cpp | 34 ++++++--- .../Libraries/LibJS/Runtime/ArrayBuffer.h | 72 +++++++++++++------ .../LibJS/Runtime/ArrayBufferPrototype.cpp | 3 +- Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../Runtime/SharedArrayBufferPrototype.cpp | 2 +- .../LibWeb/HTML/StructuredSerialize.cpp | 4 +- 6 files changed, 81 insertions(+), 35 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp index 2f65f7b6ca..3d12c26bd3 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp @@ -32,14 +32,14 @@ NonnullGCPtr ArrayBuffer::create(Realm& realm, ByteBuffer* buffer) ArrayBuffer::ArrayBuffer(ByteBuffer buffer, Object& prototype) : Object(ConstructWithPrototypeTag::Tag, prototype) - , m_buffer(move(buffer)) + , m_data_block(DataBlock { move(buffer), DataBlock::Shared::No }) , m_detach_key(js_undefined()) { } ArrayBuffer::ArrayBuffer(ByteBuffer* buffer, Object& prototype) : Object(ConstructWithPrototypeTag::Tag, prototype) - , m_buffer(buffer) + , m_data_block(DataBlock { buffer, DataBlock::Shared::No }) , m_detach_key(js_undefined()) { } @@ -51,7 +51,7 @@ void ArrayBuffer::visit_edges(Cell::Visitor& visitor) } // 6.2.9.1 CreateByteDataBlock ( size ), https://tc39.es/ecma262/#sec-createbytedatablock -ThrowCompletionOr create_byte_data_block(VM& vm, size_t size) +ThrowCompletionOr create_byte_data_block(VM& vm, size_t size) { // 1. If size > 2^53 - 1, throw a RangeError exception. if (size > MAX_ARRAY_LIKE_INDEX) @@ -64,7 +64,25 @@ ThrowCompletionOr create_byte_data_block(VM& vm, size_t size) return vm.throw_completion(ErrorType::NotEnoughMemoryToAllocate, size); // 4. Return db. - return data_block.release_value(); + return DataBlock { data_block.release_value(), DataBlock::Shared::No }; +} + +// FIXME: The returned DataBlock is not shared in the sense that the standard specifies it. +// 6.2.9.2 CreateSharedByteDataBlock ( size ), https://tc39.es/ecma262/#sec-createsharedbytedatablock +static ThrowCompletionOr create_shared_byte_data_block(VM& vm, size_t size) +{ + // 1. Let db be a new Shared Data Block value consisting of size bytes. If it is impossible to create such a Shared Data Block, throw a RangeError exception. + auto data_block = ByteBuffer::create_zeroed(size); + if (data_block.is_error()) + return vm.throw_completion(ErrorType::NotEnoughMemoryToAllocate, size); + + // 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. + // 3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier(). + // 4. Let zero be « 0 ». + // 5. For each index i of db, do + // a. Append WriteSharedMemory { [[Order]]: init, [[NoTear]]: true, [[Block]]: db, [[ByteIndex]]: i, [[ElementSize]]: 1, [[Payload]]: zero } to eventsRecord.[[EventList]]. + // 6. Return db. + return DataBlock { data_block.release_value(), DataBlock::Shared::Yes }; } // 6.2.9.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count ), https://tc39.es/ecma262/#sec-copydatablockbytes @@ -128,7 +146,7 @@ ThrowCompletionOr allocate_array_buffer(VM& vm, FunctionObject& co auto block = TRY(create_byte_data_block(vm, byte_length)); // 3. Set obj.[[ArrayBufferData]] to block. - obj->set_buffer(move(block)); + obj->set_data_block(move(block)); // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. @@ -235,12 +253,12 @@ ThrowCompletionOr> allocate_shared_array_buffer(VM& vm // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]] »). auto obj = TRY(ordinary_create_from_constructor(vm, constructor, &Intrinsics::shared_array_buffer_prototype, nullptr)); - // FIXME: 2. Let block be ? CreateSharedByteDataBlock(byteLength). - auto block = TRY(create_byte_data_block(vm, byte_length)); + // 2. Let block be ? CreateSharedByteDataBlock(byteLength). + auto block = TRY(create_shared_byte_data_block(vm, byte_length)); // 3. Set obj.[[ArrayBufferData]] to block. // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. - obj->set_buffer(move(block)); + obj->set_data_block(move(block)); // 5. Return obj. return obj; diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h index 71806c267b..f9d74b2353 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h @@ -27,6 +27,25 @@ enum class PreserveResizability { PreserveResizability }; +// 6.2.9 Data Blocks, https://tc39.es/ecma262/#sec-data-blocks +struct DataBlock { + enum class Shared { + No, + Yes, + }; + + ByteBuffer& buffer() + { + ByteBuffer* ptr { nullptr }; + byte_buffer.visit([&](Empty) { VERIFY_NOT_REACHED(); }, [&](auto* pointer) { ptr = pointer; }, [&](auto& value) { ptr = &value; }); + return *ptr; + } + ByteBuffer const& buffer() const { return const_cast(this)->buffer(); } + + Variant byte_buffer; + Shared is_shared = { Shared::No }; +}; + class ArrayBuffer : public Object { JS_OBJECT(ArrayBuffer, Object); @@ -42,31 +61,47 @@ public: if (is_detached()) return 0; - return buffer_impl().size(); + return m_data_block.buffer().size(); } // [[ArrayBufferData]] - ByteBuffer& buffer() { return buffer_impl(); } - ByteBuffer const& buffer() const { return buffer_impl(); } + ByteBuffer& buffer() { return m_data_block.buffer(); } + ByteBuffer const& buffer() const { return m_data_block.buffer(); } // Used by allocate_array_buffer() to attach the data block after construction - void set_buffer(ByteBuffer buffer) { m_buffer = move(buffer); } + void set_data_block(DataBlock block) { m_data_block = move(block); } Value detach_key() const { return m_detach_key; } void set_detach_key(Value detach_key) { m_detach_key = detach_key; } - void detach_buffer() { m_buffer = Empty {}; } + void detach_buffer() { m_data_block.byte_buffer = Empty {}; } // 25.1.2.2 IsDetachedBuffer ( arrayBuffer ), https://tc39.es/ecma262/#sec-isdetachedbuffer bool is_detached() const { // 1. If arrayBuffer.[[ArrayBufferData]] is null, return true. - if (m_buffer.has()) + if (m_data_block.byte_buffer.has()) return true; // 2. Return false. return false; } + // 25.2.1.2 IsSharedArrayBuffer ( obj ), https://tc39.es/ecma262/#sec-issharedarraybuffer + bool is_shared_array_buffer() const + { + // 1. Let bufferData be obj.[[ArrayBufferData]]. + // 2. If bufferData is null, return false. + if (m_data_block.byte_buffer.has()) + return false; + // 3. If bufferData is a Data Block, return false. + if (m_data_block.is_shared == DataBlock::Shared::No) + return false; + // 4. Assert: bufferData is a Shared Data Block. + VERIFY(m_data_block.is_shared == DataBlock::Shared::Yes); + // 5. Return true. + return true; + } + enum Order { SeqCst, Unordered @@ -84,22 +119,13 @@ private: virtual void visit_edges(Visitor&) override; - ByteBuffer& buffer_impl() - { - ByteBuffer* ptr { nullptr }; - m_buffer.visit([&](Empty) { VERIFY_NOT_REACHED(); }, [&](auto* pointer) { ptr = pointer; }, [&](auto& value) { ptr = &value; }); - return *ptr; - } - - ByteBuffer const& buffer_impl() const { return const_cast(this)->buffer_impl(); } - - Variant m_buffer; + DataBlock m_data_block; // The various detach related members of ArrayBuffer are not used by any ECMA262 functionality, // but are required to be available for the use of various harnesses like the Test262 test runner. Value m_detach_key; }; -ThrowCompletionOr create_byte_data_block(VM& vm, size_t size); +ThrowCompletionOr create_byte_data_block(VM& vm, size_t size); void copy_data_block_bytes(ByteBuffer& to_block, u64 to_index, ByteBuffer const& from_block, u64 from_index, u64 count); ThrowCompletionOr allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length); ThrowCompletionOr detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional key = {}); @@ -188,10 +214,10 @@ ThrowCompletionOr ArrayBuffer::get_value(size_t byte_index, [[maybe_unuse VERIFY(!is_detached()); // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. - VERIFY(buffer().bytes().slice(byte_index).size() >= sizeof(T)); + VERIFY(m_data_block.buffer().bytes().slice(byte_index).size() >= sizeof(T)); // 3. Let block be arrayBuffer.[[ArrayBufferData]]. - auto& block = buffer(); + auto& block = m_data_block.buffer(); // 4. Let elementSize be the Element Size value specified in Table 70 for Element Type type. auto element_size = sizeof(T); @@ -300,7 +326,7 @@ ThrowCompletionOr ArrayBuffer::set_value(size_t byte_index, Value value, [ VERIFY(!is_detached()); // 2. Assert: There are sufficient bytes in arrayBuffer starting at byteIndex to represent a value of type. - VERIFY(buffer().bytes().slice(byte_index).size() >= sizeof(T)); + VERIFY(m_data_block.buffer().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) @@ -309,7 +335,7 @@ ThrowCompletionOr ArrayBuffer::set_value(size_t byte_index, Value value, [ VERIFY(value.is_number()); // 4. Let block be arrayBuffer.[[ArrayBufferData]]. - auto& block = buffer(); + auto& block = m_data_block.buffer(); // FIXME: 5. Let elementSize be the Element Size value specified in Table 70 for Element Type type. @@ -346,9 +372,9 @@ ThrowCompletionOr ArrayBuffer::get_modify_set_value(size_t byte_index, Va // FIXME: Check for shared buffer - auto raw_bytes_read = TRY_OR_THROW_OOM(vm, buffer_impl().slice(byte_index, sizeof(T))); + auto raw_bytes_read = TRY_OR_THROW_OOM(vm, m_data_block.buffer().slice(byte_index, sizeof(T))); auto raw_bytes_modified = operation(raw_bytes_read, raw_bytes); - raw_bytes_modified.span().copy_to(buffer_impl().span().slice(byte_index)); + raw_bytes_modified.span().copy_to(m_data_block.buffer().span().slice(byte_index)); return raw_bytes_to_numeric(vm, raw_bytes_read, is_little_endian); } diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp index 8041587d8e..567026973f 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayBufferPrototype.cpp @@ -139,7 +139,8 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayBufferPrototype::byte_length_getter) auto array_buffer_object = TRY(typed_this_value(vm)); // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - // FIXME: Check for shared buffer + if (array_buffer_object->is_shared_array_buffer()) + return vm.throw_completion(ErrorType::ThisCannotBeSharedArrayBuffer); // NOTE: These steps are done in byte_length() // 4. If IsDetachedBuffer(O) is true, return +0𝔽. diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index ac14c73c52..8fbb3fce6c 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -293,6 +293,7 @@ M(TemporalUnknownCriticalAnnotation, "Unknown annotation key in critical annotation: '{}'") \ M(TemporalZonedDateTimeRoundZeroOrNegativeLengthDay, "Cannot round a ZonedDateTime in a calendar or time zone that has zero or " \ "negative length days") \ + M(ThisCannotBeSharedArrayBuffer, "|this| cannot be a SharedArrayBuffer") \ M(ThisHasNotBeenInitialized, "|this| has not been initialized") \ M(ThisIsAlreadyInitialized, "|this| is already initialized") \ M(ToObjectNullOrUndefined, "ToObject on null or undefined") \ diff --git a/Userland/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp b/Userland/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp index 4b91553503..0556e22bae 100644 --- a/Userland/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/SharedArrayBufferPrototype.cpp @@ -37,7 +37,7 @@ JS_DEFINE_NATIVE_FUNCTION(SharedArrayBufferPrototype::byte_length_getter) // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). auto array_buffer_object = TRY(typed_this_value(vm)); - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. // FIXME: Check for shared buffer // 4. Let length be O.[[ArrayBufferByteLength]]. diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index 610a96ce5a..829a1d7ecd 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -309,7 +309,7 @@ private: 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, 0, array_buffer.buffer(), 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]] }. @@ -318,7 +318,7 @@ private: // 6. Otherwise, set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size }. else { vector.append(ValueTag::ArrayBuffer); - TRY(serialize_bytes(vector, data_copy.bytes())); + TRY(serialize_bytes(vector, data_copy.buffer().bytes())); } } return {};