diff --git a/Userland/Libraries/LibJS/Print.cpp b/Userland/Libraries/LibJS/Print.cpp index be4b8907dc..957e4e5a47 100644 --- a/Userland/Libraries/LibJS/Print.cpp +++ b/Userland/Libraries/LibJS/Print.cpp @@ -480,14 +480,22 @@ ErrorOr print_typed_array(JS::PrintContext& print_context, JS::TypedArrayB ErrorOr print_data_view(JS::PrintContext& print_context, JS::DataView const& data_view, HashTable& 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 ")); + 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 {}; } diff --git a/Userland/Libraries/LibJS/Runtime/ByteLength.h b/Userland/Libraries/LibJS/Runtime/ByteLength.h new file mode 100644 index 0000000000..6b2812155f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/ByteLength.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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(); } + bool is_detached() const { return m_length.has(); } + + u32 length() const + { + VERIFY(m_length.has()); + return m_length.get(); + } + +private: + struct Auto { }; + struct Detached { }; + + using Length = Variant; + + ByteLength(Length length) + : m_length(move(length)) + { + } + + Length m_length; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/DataView.cpp b/Userland/Libraries/LibJS/Runtime/DataView.cpp index 0535a08cd3..760b054d3a 100644 --- a/Userland/Libraries/LibJS/Runtime/DataView.cpp +++ b/Userland/Libraries/LibJS/Runtime/DataView.cpp @@ -10,15 +10,15 @@ namespace JS { JS_DEFINE_ALLOCATOR(DataView); -NonnullGCPtr DataView::create(Realm& realm, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset) +NonnullGCPtr DataView::create(Realm& realm, ArrayBuffer* viewed_buffer, ByteLength byte_length, size_t byte_offset) { - return realm.heap().allocate(realm, viewed_buffer, byte_length, byte_offset, realm.intrinsics().data_view_prototype()); + return realm.heap().allocate(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; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/DataView.h b/Userland/Libraries/LibJS/Runtime/DataView.h index e97ab5eb0c..0e2a1ca2fc 100644 --- a/Userland/Libraries/LibJS/Runtime/DataView.h +++ b/Userland/Libraries/LibJS/Runtime/DataView.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -17,22 +18,32 @@ class DataView : public Object { JS_DECLARE_ALLOCATOR(DataView); public: - static NonnullGCPtr create(Realm&, ArrayBuffer*, size_t byte_length, size_t byte_offset); + static NonnullGCPtr 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 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 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&); + } diff --git a/Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp b/Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp index 891f188999..06731cf384 100644 --- a/Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp @@ -63,42 +63,71 @@ ThrowCompletionOr> DataViewConstructor::construct(FunctionO if (array_buffer.is_detached()) return vm.throw_completion(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(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(view_byte_length.length())); + if (checked_add.has_overflow() || checked_add.value() > buffer_byte_length) return vm.throw_completion(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(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(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(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(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(view_byte_length.length())); + + if (checked_add.has_overflow() || checked_add.value() > buffer_byte_length) + return vm.throw_completion(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; } diff --git a/Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp b/Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp index 141aeefa24..5ef52e3df7 100644 --- a/Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/DataViewPrototype.cpp @@ -67,35 +67,39 @@ static ThrowCompletionOr 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(); - - // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if (buffer->is_detached()) - return vm.template throw_completion(ErrorType::DetachedArrayBuffer); - - // 7. Let viewOffset be view.[[ByteOffset]]. + // 5. Let viewOffset be view.[[ByteOffset]]. auto view_offset = view->byte_offset(); - // 8. Let viewSize be view.[[ByteLength]]. - auto view_size = view->byte_length(); + // 6. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered). + auto view_record = make_data_view_with_buffer_witness_record(*view, ArrayBuffer::Order::Unordered); - // 9. Let elementSize be the Element Size value specified in Table 68 for Element Type type. + // 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(ErrorType::BufferOutOfBounds, "DataView"sv); + + // 9. Let viewSize be GetViewByteLength(viewRecord). + auto view_size = get_view_byte_length(view_record); + + // 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 buffer_index = get_index; - buffer_index += view_offset; - + // 11. If getIndex + elementSize > viewSize, throw a RangeError exception. Checked 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(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size); - // 12. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian). - return buffer->get_value(buffer_index.value(), false, ArrayBuffer::Order::Unordered, little_endian); + // 12. Let bufferIndex be getIndex + viewOffset. + Checked buffer_index = get_index; + buffer_index += view_offset; + + if (buffer_index.has_overflow()) + return vm.throw_completion(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size); + + // 13. Return GetValueFromBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, false, unordered, isLittleEndian). + return view->viewed_array_buffer()->get_value(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 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(); - - // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if (buffer->is_detached()) - return vm.throw_completion(ErrorType::DetachedArrayBuffer); - - // 9. Let viewOffset be view.[[ByteOffset]]. + // 7. Let viewOffset be view.[[ByteOffset]]. auto view_offset = view->byte_offset(); - // 10. Let viewSize be view.[[ByteLength]]. - auto view_size = view->byte_length(); + // 8. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered). + auto view_record = make_data_view_with_buffer_witness_record(*view, ArrayBuffer::Order::Unordered); - // 11. Let elementSize be the Element Size value specified in Table 68 for Element Type type. + // 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(ErrorType::BufferOutOfBounds, "DataView"sv); + + // 11. Let viewSize be GetViewByteLength(viewRecord). + auto view_size = get_view_byte_length(view_record); + + // 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 buffer_index = get_index; - buffer_index += view_offset; - + // 13. If getIndex + elementSize > viewSize, throw a RangeError exception. Checked 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(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size); - // 14. Perform SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian). - buffer->set_value(buffer_index.value(), number_value, false, ArrayBuffer::Order::Unordered, little_endian); + // 14. Let bufferIndex be getIndex + viewOffset. + Checked buffer_index = get_index; + buffer_index += view_offset; - // 15. Return undefined. + if (buffer_index.has_overflow()) + return vm.throw_completion(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size); + + // 15. Perform SetValueInBuffer(view.[[ViewedArrayBuffer]], bufferIndex, type, numberValue, false, unordered, isLittleEndian). + view->viewed_array_buffer()->set_value(buffer_index.value(), number_value, false, ArrayBuffer::Order::Unordered, little_endian); + + // 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(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(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(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(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 diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 873255f8fd..551698f27b 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/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 '{}'") \ diff --git a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js index b544badb84..21ab69a18e 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteLength.js +++ b/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); + }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js b/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js index 68c5d84716..7291beb9d3 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/DataView/DataView.prototype.byteOffset.js +++ b/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); + }); }); diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index bda0e4c3c0..cc0273dd50 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/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) { + 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) { + 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()); } diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 11fe74d4c2..3528a32994 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -312,7 +312,12 @@ JS::ThrowCompletionOr 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(buffer_object)) { auto& buffer = static_cast(*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::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("Not a BufferSource"sv); } diff --git a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp index c4625fc07e..47e3854d6a 100644 --- a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp @@ -49,6 +49,12 @@ ErrorOr get_buffer_source_copy(JS::Object const& buffer_source) } else if (is(buffer_source)) { auto const& es_buffer_source = static_cast(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 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(buffer_source); - es_array_buffer = &const_cast(es_buffer_source); + es_array_buffer = &const_cast(es_buffer_source); // 2. Set length to esBufferSource.[[ArrayBufferByteLength]]. length = es_buffer_source.byte_length();