瀏覽代碼

LibJS+LibWeb: Implement resizable ArrayBuffer support for DataView

This is (part of) a normative change in the ECMA-262 spec. See:
https://github.com/tc39/ecma262/commit/a9ae96e
Timothy Flynn 1 年之前
父節點
當前提交
c7fec9424c

+ 12 - 4
Userland/Libraries/LibJS/Print.cpp

@@ -480,14 +480,22 @@ ErrorOr<void> print_typed_array(JS::PrintContext& print_context, JS::TypedArrayB
 
 ErrorOr<void> print_data_view(JS::PrintContext& print_context, JS::DataView const& data_view, HashTable<JS::Object*>& seen_objects)
 {
+    auto view_record = JS::make_data_view_with_buffer_witness_record(data_view, JS::ArrayBuffer::Order::SeqCst);
     TRY(print_type(print_context, "DataView"sv));
-    TRY(js_out(print_context, "\n  byteLength: "));
-    TRY(print_value(print_context, JS::Value(data_view.byte_length()), seen_objects));
-    TRY(js_out(print_context, "\n  byteOffset: "));
-    TRY(print_value(print_context, JS::Value(data_view.byte_offset()), seen_objects));
+
     TRY(js_out(print_context, "\n  buffer: "));
     TRY(print_type(print_context, "ArrayBuffer"sv));
     TRY(js_out(print_context, " @ {:p}", data_view.viewed_array_buffer()));
+
+    if (JS::is_view_out_of_bounds(view_record)) {
+        TRY(js_out(print_context, "\n  <out of bounds>"));
+        return {};
+    }
+
+    TRY(js_out(print_context, "\n  byteLength: "));
+    TRY(print_value(print_context, JS::Value(JS::get_view_byte_length(view_record)), seen_objects));
+    TRY(js_out(print_context, "\n  byteOffset: "));
+    TRY(print_value(print_context, JS::Value(data_view.byte_offset()), seen_objects));
     return {};
 }
 

+ 47 - 0
Userland/Libraries/LibJS/Runtime/ByteLength.h

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+#include <AK/Variant.h>
+
+namespace JS {
+
+class ByteLength {
+public:
+    static ByteLength auto_() { return { Auto {} }; }
+    static ByteLength detached() { return { Detached {} }; }
+
+    ByteLength(u32 length)
+        : m_length(length)
+    {
+    }
+
+    bool is_auto() const { return m_length.has<Auto>(); }
+    bool is_detached() const { return m_length.has<Detached>(); }
+
+    u32 length() const
+    {
+        VERIFY(m_length.has<u32>());
+        return m_length.get<u32>();
+    }
+
+private:
+    struct Auto { };
+    struct Detached { };
+
+    using Length = Variant<Auto, Detached, u32>;
+
+    ByteLength(Length length)
+        : m_length(move(length))
+    {
+    }
+
+    Length m_length;
+};
+
+}

+ 96 - 4
Userland/Libraries/LibJS/Runtime/DataView.cpp

@@ -10,15 +10,15 @@ namespace JS {
 
 JS_DEFINE_ALLOCATOR(DataView);
 
-NonnullGCPtr<DataView> DataView::create(Realm& realm, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset)
+NonnullGCPtr<DataView> DataView::create(Realm& realm, ArrayBuffer* viewed_buffer, ByteLength byte_length, size_t byte_offset)
 {
-    return realm.heap().allocate<DataView>(realm, viewed_buffer, byte_length, byte_offset, realm.intrinsics().data_view_prototype());
+    return realm.heap().allocate<DataView>(realm, viewed_buffer, move(byte_length), byte_offset, realm.intrinsics().data_view_prototype());
 }
 
-DataView::DataView(ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset, Object& prototype)
+DataView::DataView(ArrayBuffer* viewed_buffer, ByteLength byte_length, size_t byte_offset, Object& prototype)
     : Object(ConstructWithPrototypeTag::Tag, prototype)
     , m_viewed_array_buffer(viewed_buffer)
-    , m_byte_length(byte_length)
+    , m_byte_length(move(byte_length))
     , m_byte_offset(byte_offset)
 {
 }
@@ -29,4 +29,96 @@ void DataView::visit_edges(Visitor& visitor)
     visitor.visit(m_viewed_array_buffer);
 }
 
+// 25.3.1.2 MakeDataViewWithBufferWitnessRecord ( obj, order ), https://tc39.es/ecma262/#sec-makedataviewwithbufferwitnessrecord
+DataViewWithBufferWitness make_data_view_with_buffer_witness_record(DataView const& data_view, ArrayBuffer::Order order)
+{
+    // 1. Let buffer be obj.[[ViewedArrayBuffer]].
+    auto* buffer = data_view.viewed_array_buffer();
+
+    ByteLength byte_length { 0 };
+
+    // 2. If IsDetachedBuffer(buffer) is true, then
+    if (buffer->is_detached()) {
+        // a. Let byteLength be detached.
+        byte_length = ByteLength::detached();
+    }
+    // 3. Else,
+    else {
+        // a. Let byteLength be ArrayBufferByteLength(buffer, order).
+        byte_length = array_buffer_byte_length(*buffer, order);
+    }
+
+    // 4. Return the DataView With Buffer Witness Record { [[Object]]: obj, [[CachedBufferByteLength]]: byteLength }.
+    return { .object = data_view, .cached_buffer_byte_length = move(byte_length) };
+}
+
+// 25.3.1.3 GetViewByteLength ( viewRecord ), https://tc39.es/ecma262/#sec-getviewbytelength
+u32 get_view_byte_length(DataViewWithBufferWitness const& view_record)
+{
+    // 1. Assert: IsViewOutOfBounds(viewRecord) is false.
+    VERIFY(!is_view_out_of_bounds(view_record));
+
+    // 2. Let view be viewRecord.[[Object]].
+    auto const& view = *view_record.object;
+
+    // 3. If view.[[ByteLength]] is not auto, return view.[[ByteLength]].
+    if (!view.byte_length().is_auto())
+        return view.byte_length().length();
+
+    // 4. Assert: IsFixedLengthArrayBuffer(view.[[ViewedArrayBuffer]]) is false.
+    VERIFY(!view.viewed_array_buffer()->is_fixed_length());
+
+    // 5. Let byteOffset be view.[[ByteOffset]].
+    auto byte_offset = view.byte_offset();
+
+    // 6. Let byteLength be viewRecord.[[CachedBufferByteLength]].
+    auto const& byte_length = view_record.cached_buffer_byte_length;
+
+    // 7. Assert: byteLength is not detached.
+    VERIFY(!byte_length.is_detached());
+
+    // 8. Return byteLength - byteOffset.
+    return byte_length.length() - byte_offset;
+}
+
+// 25.3.1.4 IsViewOutOfBounds ( viewRecord ), https://tc39.es/ecma262/#sec-isviewoutofbounds
+bool is_view_out_of_bounds(DataViewWithBufferWitness const& view_record)
+{
+    // 1. Let view be viewRecord.[[Object]].
+    auto const& view = *view_record.object;
+
+    // 2. Let bufferByteLength be viewRecord.[[CachedBufferByteLength]].
+    auto const& buffer_byte_length = view_record.cached_buffer_byte_length;
+
+    // 3. Assert: IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true if and only if bufferByteLength is detached.
+    VERIFY(view.viewed_array_buffer()->is_detached() == buffer_byte_length.is_detached());
+
+    // 4. If bufferByteLength is detached, return true.
+    if (buffer_byte_length.is_detached())
+        return true;
+
+    // 5. Let byteOffsetStart be view.[[ByteOffset]].
+    auto byte_offset_start = view.byte_offset();
+    u32 byte_offset_end = 0;
+
+    // 6. If view.[[ByteLength]] is auto, then
+    if (view.byte_length().is_auto()) {
+        // a. Let byteOffsetEnd be bufferByteLength.
+        byte_offset_end = buffer_byte_length.length();
+    }
+    // 7. Else,
+    else {
+        // a. Let byteOffsetEnd be byteOffsetStart + view.[[ByteLength]].
+        byte_offset_end = byte_offset_start + view.byte_length().length();
+    }
+
+    // 8. If byteOffsetStart > bufferByteLength or byteOffsetEnd > bufferByteLength, return true.
+    if ((byte_offset_start > buffer_byte_length.length()) || (byte_offset_end > buffer_byte_length.length()))
+        return true;
+
+    // 9. NOTE: 0-length DataViews are not considered out-of-bounds.
+    // 10. Return false.
+    return false;
+}
+
 }

+ 15 - 4
Userland/Libraries/LibJS/Runtime/DataView.h

@@ -7,6 +7,7 @@
 #pragma once
 
 #include <LibJS/Runtime/ArrayBuffer.h>
+#include <LibJS/Runtime/ByteLength.h>
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Object.h>
 
@@ -17,22 +18,32 @@ class DataView : public Object {
     JS_DECLARE_ALLOCATOR(DataView);
 
 public:
-    static NonnullGCPtr<DataView> create(Realm&, ArrayBuffer*, size_t byte_length, size_t byte_offset);
+    static NonnullGCPtr<DataView> create(Realm&, ArrayBuffer*, ByteLength byte_length, size_t byte_offset);
 
     virtual ~DataView() override = default;
 
     ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
-    size_t byte_length() const { return m_byte_length; }
+    ByteLength const& byte_length() const { return m_byte_length; }
     size_t byte_offset() const { return m_byte_offset; }
 
 private:
-    DataView(ArrayBuffer*, size_t byte_length, size_t byte_offset, Object& prototype);
+    DataView(ArrayBuffer*, ByteLength byte_length, size_t byte_offset, Object& prototype);
 
     virtual void visit_edges(Visitor& visitor) override;
 
     GCPtr<ArrayBuffer> m_viewed_array_buffer;
-    size_t m_byte_length { 0 };
+    ByteLength m_byte_length { 0 };
     size_t m_byte_offset { 0 };
 };
 
+// 25.3.1.1 DataView With Buffer Witness Records, https://tc39.es/ecma262/#sec-dataview-with-buffer-witness-records
+struct DataViewWithBufferWitness {
+    NonnullGCPtr<DataView const> object;  // [[Object]]
+    ByteLength cached_buffer_byte_length; // [[CachedBufferByteLength]]
+};
+
+DataViewWithBufferWitness make_data_view_with_buffer_witness_record(DataView const&, ArrayBuffer::Order);
+u32 get_view_byte_length(DataViewWithBufferWitness const&);
+bool is_view_out_of_bounds(DataViewWithBufferWitness const&);
+
 }

+ 44 - 15
Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp

@@ -63,42 +63,71 @@ ThrowCompletionOr<NonnullGCPtr<Object>> DataViewConstructor::construct(FunctionO
     if (array_buffer.is_detached())
         return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
 
-    // 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
-    auto buffer_byte_length = array_buffer.byte_length();
+    // 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst).
+    auto buffer_byte_length = array_buffer_byte_length(array_buffer, ArrayBuffer::Order::SeqCst);
 
     // 6. If offset > bufferByteLength, throw a RangeError exception.
     if (offset > buffer_byte_length)
         return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, offset, buffer_byte_length);
 
-    size_t view_byte_length;
+    // 7. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer).
+    auto buffer_is_fixed_length = array_buffer.is_fixed_length();
 
-    // 7. If byteLength is undefined, then
+    ByteLength view_byte_length { 0 };
+
+    // 8. If byteLength is undefined, then
     if (byte_length.is_undefined()) {
-        // a. Let viewByteLength be bufferByteLength - offset.
-        view_byte_length = buffer_byte_length - offset;
+        // a. If bufferIsFixedLength is true, then
+        if (buffer_is_fixed_length) {
+            // i. Let viewByteLength be bufferByteLength - offset.
+            view_byte_length = buffer_byte_length - offset;
+        }
+        // b. Else,
+        else {
+            // i. Let viewByteLength be auto.
+            view_byte_length = ByteLength::auto_();
+        }
     }
-    // 8. Else,
+    // 9. Else,
     else {
         // a. Let viewByteLength be ? ToIndex(byteLength).
         view_byte_length = TRY(byte_length.to_index(vm));
 
         // b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
-        auto const checked_add = AK::make_checked(view_byte_length) + AK::make_checked(offset);
+        auto checked_add = AK::make_checked(offset) + AK::make_checked(static_cast<size_t>(view_byte_length.length()));
+
         if (checked_add.has_overflow() || checked_add.value() > buffer_byte_length)
             return vm.throw_completion<RangeError>(ErrorType::InvalidLength, vm.names.DataView);
     }
 
-    // 9. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
-    // 11. Set O.[[ViewedArrayBuffer]] to buffer.
-    // 12. Set O.[[ByteLength]] to viewByteLength.
-    // 13. Set O.[[ByteOffset]] to offset.
-    auto data_view = TRY(ordinary_create_from_constructor<DataView>(vm, new_target, &Intrinsics::data_view_prototype, &array_buffer, view_byte_length, offset));
+    // 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
+    auto data_view = TRY(ordinary_create_from_constructor<DataView>(vm, new_target, &Intrinsics::data_view_prototype, &array_buffer, move(view_byte_length), offset));
 
-    // 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
+    // 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
     if (array_buffer.is_detached())
         return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
 
-    // 14. Return O.
+    // 12. Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst).
+    buffer_byte_length = array_buffer_byte_length(array_buffer, ArrayBuffer::Order::SeqCst);
+
+    // 13. If offset > bufferByteLength, throw a RangeError exception.
+    if (offset > buffer_byte_length)
+        return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, offset, buffer_byte_length);
+
+    // 14. If byteLength is not undefined, then
+    if (!byte_length.is_undefined()) {
+        // a. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
+        auto checked_add = AK::make_checked(offset) + AK::make_checked(static_cast<size_t>(view_byte_length.length()));
+
+        if (checked_add.has_overflow() || checked_add.value() > buffer_byte_length)
+            return vm.throw_completion<RangeError>(ErrorType::InvalidLength, vm.names.DataView);
+    }
+
+    // 15. Set O.[[ViewedArrayBuffer]] to buffer.
+    // 16. Set O.[[ByteLength]] to viewByteLength.
+    // 17. Set O.[[ByteOffset]] to offset.
+
+    // 18. Return O.
     return data_view;
 }
 

+ 64 - 48
Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp

@@ -67,35 +67,39 @@ static ThrowCompletionOr<Value> get_view_value(VM& vm, Value request_index, Valu
     // 4. Set isLittleEndian to ToBoolean(isLittleEndian).
     auto little_endian = is_little_endian.to_boolean();
 
-    // 5. Let buffer be view.[[ViewedArrayBuffer]].
-    auto buffer = view->viewed_array_buffer();
+    // 5. Let viewOffset be view.[[ByteOffset]].
+    auto view_offset = view->byte_offset();
 
-    // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
-    if (buffer->is_detached())
-        return vm.template throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
+    // 6. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered).
+    auto view_record = make_data_view_with_buffer_witness_record(*view, ArrayBuffer::Order::Unordered);
 
-    // 7. Let viewOffset be view.[[ByteOffset]].
-    auto view_offset = view->byte_offset();
+    // 7. NOTE: Bounds checking is not a synchronizing operation when view's backing buffer is a growable SharedArrayBuffer.
+    // 8. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception.
+    if (is_view_out_of_bounds(view_record))
+        return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "DataView"sv);
 
-    // 8. Let viewSize be view.[[ByteLength]].
-    auto view_size = view->byte_length();
+    // 9. Let viewSize be GetViewByteLength(viewRecord).
+    auto view_size = get_view_byte_length(view_record);
 
-    // 9. Let elementSize be the Element Size value specified in Table 68 for Element Type type.
+    // 10. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
     auto element_size = sizeof(T);
 
-    // 11. Let bufferIndex be getIndex + viewOffset.
-    Checked<size_t> buffer_index = get_index;
-    buffer_index += view_offset;
-
+    // 11. If getIndex + elementSize > viewSize, throw a RangeError exception.
     Checked<size_t> end_index = get_index;
     end_index += element_size;
 
-    // 10. If getIndex + elementSize > viewSize, throw a RangeError exception.
-    if (buffer_index.has_overflow() || end_index.has_overflow() || end_index.value() > view_size)
+    if (end_index.has_overflow() || end_index.value() > view_size)
+        return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
+
+    // 12. Let bufferIndex be getIndex + viewOffset.
+    Checked<size_t> buffer_index = get_index;
+    buffer_index += view_offset;
+
+    if (buffer_index.has_overflow())
         return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
 
-    // 12. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian).
-    return buffer->get_value<T>(buffer_index.value(), false, ArrayBuffer::Order::Unordered, little_endian);
+    // 13. Return GetValueFromBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, false, unordered, isLittleEndian).
+    return view->viewed_array_buffer()->get_value<T>(buffer_index.value(), false, ArrayBuffer::Order::Unordered, little_endian);
 }
 
 // 25.3.1.6 SetViewValue ( view, requestIndex, isLittleEndian, type, value ), https://tc39.es/ecma262/#sec-setviewvalue
@@ -121,37 +125,41 @@ static ThrowCompletionOr<Value> set_view_value(VM& vm, Value request_index, Valu
     // 6. Set isLittleEndian to ToBoolean(isLittleEndian).
     auto little_endian = is_little_endian.to_boolean();
 
-    // 7. Let buffer be view.[[ViewedArrayBuffer]].
-    auto buffer = view->viewed_array_buffer();
+    // 7. Let viewOffset be view.[[ByteOffset]].
+    auto view_offset = view->byte_offset();
 
-    // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
-    if (buffer->is_detached())
-        return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
+    // 8. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered).
+    auto view_record = make_data_view_with_buffer_witness_record(*view, ArrayBuffer::Order::Unordered);
 
-    // 9. Let viewOffset be view.[[ByteOffset]].
-    auto view_offset = view->byte_offset();
+    // 9. NOTE: Bounds checking is not a synchronizing operation when view's backing buffer is a growable SharedArrayBuffer.
+    // 10. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception.
+    if (is_view_out_of_bounds(view_record))
+        return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "DataView"sv);
 
-    // 10. Let viewSize be view.[[ByteLength]].
-    auto view_size = view->byte_length();
+    // 11. Let viewSize be GetViewByteLength(viewRecord).
+    auto view_size = get_view_byte_length(view_record);
 
-    // 11. Let elementSize be the Element Size value specified in Table 68 for Element Type type.
+    // 12. Let elementSize be the Element Size value specified in Table 71 for Element Type type.
     auto element_size = sizeof(T);
 
-    // 13. Let bufferIndex be getIndex + viewOffset.
-    Checked<size_t> buffer_index = get_index;
-    buffer_index += view_offset;
-
+    // 13. If getIndex + elementSize > viewSize, throw a RangeError exception.
     Checked<size_t> end_index = get_index;
     end_index += element_size;
 
-    // 12. If getIndex + elementSize > viewSize, throw a RangeError exception.
-    if (buffer_index.has_overflow() || end_index.has_overflow() || end_index.value() > view_size)
+    if (end_index.has_overflow() || end_index.value() > view_size)
+        return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
+
+    // 14. Let bufferIndex be getIndex + viewOffset.
+    Checked<size_t> buffer_index = get_index;
+    buffer_index += view_offset;
+
+    if (buffer_index.has_overflow())
         return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
 
-    // 14. Perform SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian).
-    buffer->set_value<T>(buffer_index.value(), number_value, false, ArrayBuffer::Order::Unordered, little_endian);
+    // 15. Perform SetValueInBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, numberValue, false, unordered, isLittleEndian).
+    view->viewed_array_buffer()->set_value<T>(buffer_index.value(), number_value, false, ArrayBuffer::Order::Unordered, little_endian);
 
-    // 15. Return undefined.
+    // 16. Return undefined.
     return js_undefined();
 }
 
@@ -176,14 +184,18 @@ JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::byte_length_getter)
     // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
     auto data_view = TRY(typed_this_value(vm));
 
-    // 4. Let buffer be O.[[ViewedArrayBuffer]].
-    // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
-    if (data_view->viewed_array_buffer()->is_detached())
-        return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
+    // 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst).
+    auto view_record = make_data_view_with_buffer_witness_record(data_view, ArrayBuffer::Order::SeqCst);
+
+    // 5. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception.
+    if (is_view_out_of_bounds(view_record))
+        return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "DataView"sv);
+
+    // 6. Let size be GetViewByteLength(viewRecord).
+    auto size = get_view_byte_length(view_record);
 
-    // 6. Let size be O.[[ByteLength]].
     // 7. Return 𝔽(size).
-    return Value(data_view->byte_length());
+    return Value { size };
 }
 
 // 25.3.4.3 get DataView.prototype.byteOffset, https://tc39.es/ecma262/#sec-get-dataview.prototype.byteoffset
@@ -194,14 +206,18 @@ JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::byte_offset_getter)
     // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
     auto data_view = TRY(typed_this_value(vm));
 
-    // 4. Let buffer be O.[[ViewedArrayBuffer]].
-    // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
-    if (data_view->viewed_array_buffer()->is_detached())
-        return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
+    // 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst).
+    auto view_record = make_data_view_with_buffer_witness_record(data_view, ArrayBuffer::Order::SeqCst);
+
+    // 5. If IsViewOutOfBounds(viewRecord) is true, throw a TypeError exception.
+    if (is_view_out_of_bounds(view_record))
+        return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "DataView"sv);
 
     // 6. Let offset be O.[[ByteOffset]].
+    auto offset = data_view->byte_offset();
+
     // 7. Return 𝔽(offset).
-    return Value(data_view->byte_offset());
+    return Value { offset };
 }
 
 // 25.3.4.5 DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] ), https://tc39.es/ecma262/#sec-dataview.prototype.getbigint64

+ 1 - 0
Userland/Libraries/LibJS/Runtime/ErrorTypes.h

@@ -18,6 +18,7 @@
     M(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt")                                                            \
     M(BigIntInvalidValue, "Invalid value for BigInt: {}")                                                                               \
     M(BindingNotInitialized, "Binding {} is not initialized")                                                                           \
+    M(BufferOutOfBounds, "{} contains a property which references a value at an index not contained within its buffer's bounds")        \
     M(ByteLengthExceedsMaxByteLength, "ArrayBuffer byte length of {} exceeds the max byte length of {}")                                \
     M(CallStackSizeExceeded, "Call stack size limit exceeded")                                                                          \
     M(CannotDeclareGlobalFunction, "Cannot declare global function of name '{}'")                                                       \

+ 35 - 6
Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js

@@ -1,7 +1,36 @@
-test("basic functionality", () => {
-    const buffer = new ArrayBuffer(124);
-    expect(new DataView(buffer).byteLength).toBe(124);
-    expect(new DataView(buffer, 0, 1).byteLength).toBe(1);
-    expect(new DataView(buffer, 0, 64).byteLength).toBe(64);
-    expect(new DataView(buffer, 0, 123).byteLength).toBe(123);
+describe("errors", () => {
+    test("called on non-DataView object", () => {
+        expect(() => {
+            DataView.prototype.byteLength;
+        }).toThrowWithMessage(TypeError, "Not an object of type DataView");
+    });
+
+    test("detached buffer", () => {
+        let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
+        let view = new DataView(buffer);
+        detachArrayBuffer(buffer);
+
+        expect(() => {
+            view.byteLength;
+        }).toThrowWithMessage(
+            TypeError,
+            "DataView contains a property which references a value at an index not contained within its buffer's bounds"
+        );
+    });
+});
+
+describe("normal behavior", () => {
+    test("basic functionality", () => {
+        let buffer = new ArrayBuffer(124);
+        expect(new DataView(buffer).byteLength).toBe(124);
+        expect(new DataView(buffer, 0, 1).byteLength).toBe(1);
+        expect(new DataView(buffer, 0, 64).byteLength).toBe(64);
+        expect(new DataView(buffer, 0, 123).byteLength).toBe(123);
+
+        buffer = new ArrayBuffer(5, { maxByteLength: 10 });
+        expect(new DataView(buffer).byteLength).toBe(5);
+        expect(new DataView(buffer, 0).byteLength).toBe(5);
+        expect(new DataView(buffer, 3).byteLength).toBe(2);
+        expect(new DataView(buffer, 5).byteLength).toBe(0);
+    });
 });

+ 29 - 6
Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js

@@ -1,7 +1,30 @@
-test("basic functionality", () => {
-    const buffer = new ArrayBuffer(124);
-    expect(new DataView(buffer).byteOffset).toBe(0);
-    expect(new DataView(buffer, 1).byteOffset).toBe(1);
-    expect(new DataView(buffer, 64).byteOffset).toBe(64);
-    expect(new DataView(buffer, 123).byteOffset).toBe(123);
+describe("errors", () => {
+    test("called on non-DataView object", () => {
+        expect(() => {
+            DataView.prototype.byteOffset;
+        }).toThrowWithMessage(TypeError, "Not an object of type DataView");
+    });
+
+    test("detached buffer", () => {
+        let buffer = new ArrayBuffer(5, { maxByteLength: 10 });
+        let view = new DataView(buffer);
+        detachArrayBuffer(buffer);
+
+        expect(() => {
+            view.byteOffset;
+        }).toThrowWithMessage(
+            TypeError,
+            "DataView contains a property which references a value at an index not contained within its buffer's bounds"
+        );
+    });
+});
+
+describe("normal behavior", () => {
+    test("basic functionality", () => {
+        const buffer = new ArrayBuffer(124);
+        expect(new DataView(buffer).byteOffset).toBe(0);
+        expect(new DataView(buffer, 1).byteOffset).toBe(1);
+        expect(new DataView(buffer, 64).byteOffset).toBe(64);
+        expect(new DataView(buffer, 123).byteOffset).toBe(123);
+    });
 });

+ 18 - 2
Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp

@@ -522,7 +522,23 @@ private:
     {
         // 14. Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
 
-        // FIXME: 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException.
+        auto view_record = [&]() {
+            if constexpr (IsSame<ViewType, JS::DataView>) {
+                return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
+            } else {
+                // FIXME: Create a TypedArray record when TypedArray supports resizable ArrayBuffer objects.
+                TODO();
+                return 0;
+            }
+        }();
+
+        // 1. If IsArrayBufferViewOutOfBounds(value) is true, then throw a "DataCloneError" DOMException.
+        if constexpr (IsSame<ViewType, JS::DataView>) {
+            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 {
+            // FIXME: Check TypedArray bounds when TypedArray supports resizable ArrayBuffer objects.
+        }
 
         // 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.
         auto* buffer = view.viewed_array_buffer();
@@ -540,7 +556,7 @@ private:
             vector.append(ValueTag::ArrayBufferView);
             vector.extend(move(buffer_serialized));           // [[ArrayBufferSerialized]]
             TRY(serialize_string(vector, "DataView"_string)); // [[Constructor]]
-            vector.append(view.byte_length());
+            vector.append(JS::get_view_byte_length(view_record));
             vector.append(view.byte_offset());
         }
 

+ 6 - 1
Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp

@@ -312,7 +312,12 @@ JS::ThrowCompletionOr<size_t> parse_module(JS::VM& vm, JS::Object* buffer_object
         data = buffer.viewed_array_buffer()->buffer().span().slice(buffer.byte_offset(), buffer.byte_length());
     } else if (is<JS::DataView>(buffer_object)) {
         auto& buffer = static_cast<JS::DataView&>(*buffer_object);
-        data = buffer.viewed_array_buffer()->buffer().span().slice(buffer.byte_offset(), buffer.byte_length());
+
+        auto view_record = JS::make_data_view_with_buffer_witness_record(buffer, JS::ArrayBuffer::Order::SeqCst);
+        if (JS::is_view_out_of_bounds(view_record))
+            return vm.throw_completion<JS::TypeError>(JS::ErrorType::BufferOutOfBounds, "DataView"sv);
+
+        data = buffer.viewed_array_buffer()->buffer().span().slice(buffer.byte_offset(), JS::get_view_byte_length(view_record));
     } else {
         return vm.throw_completion<JS::TypeError>("Not a BufferSource"sv);
     }

+ 8 - 2
Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp

@@ -49,6 +49,12 @@ ErrorOr<ByteBuffer> get_buffer_source_copy(JS::Object const& buffer_source)
     } else if (is<JS::DataView>(buffer_source)) {
         auto const& es_buffer_source = static_cast<JS::DataView const&>(buffer_source);
 
+        auto view_record = JS::make_data_view_with_buffer_witness_record(es_buffer_source, JS::ArrayBuffer::Order::SeqCst);
+
+        // AD-HOC: The WebIDL spec has not been updated for resizable ArrayBuffer objects. This check follows the behavior of step 7.
+        if (JS::is_view_out_of_bounds(view_record))
+            return ByteBuffer {};
+
         // 1. Set esArrayBuffer to esBufferSource.[[ViewedArrayBuffer]].
         es_array_buffer = es_buffer_source.viewed_array_buffer();
 
@@ -56,13 +62,13 @@ ErrorOr<ByteBuffer> get_buffer_source_copy(JS::Object const& buffer_source)
         offset = es_buffer_source.byte_offset();
 
         // 3. Set length to esBufferSource.[[ByteLength]].
-        length = es_buffer_source.byte_length();
+        length = JS::get_view_byte_length(view_record);
     }
     // 6. Otherwise:
     else {
         // 1. Assert: esBufferSource is an ArrayBuffer or SharedArrayBuffer object.
         auto const& es_buffer_source = static_cast<JS::ArrayBuffer const&>(buffer_source);
-        es_array_buffer = &const_cast<JS ::ArrayBuffer&>(es_buffer_source);
+        es_array_buffer = &const_cast<JS::ArrayBuffer&>(es_buffer_source);
 
         // 2. Set length to esBufferSource.[[ArrayBufferByteLength]].
         length = es_buffer_source.byte_length();