mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 05:18:12 +00:00
LibJS+LibWeb: Implement resizable ArrayBuffer support for DataView
This is (part of) a normative change in the ECMA-262 spec. See:
a9ae96e
This commit is contained in:
parent
29ac6e3689
commit
c7fec9424c
12 changed files with 377 additions and 94 deletions
|
@ -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
Userland/Libraries/LibJS/Runtime/ByteLength.h
Normal file
47
Userland/Libraries/LibJS/Runtime/ByteLength.h
Normal file
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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&);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
// 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (buffer->is_detached())
|
||||
return vm.template throw_completion<TypeError>(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<TypeError>(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<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. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian).
|
||||
return buffer->get_value<T>(buffer_index.value(), false, ArrayBuffer::Order::Unordered, little_endian);
|
||||
// 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);
|
||||
|
||||
// 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();
|
||||
|
||||
// 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (buffer->is_detached())
|
||||
return vm.throw_completion<TypeError>(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<TypeError>(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<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. Perform SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian).
|
||||
buffer->set_value<T>(buffer_index.value(), number_value, false, ArrayBuffer::Order::Unordered, little_endian);
|
||||
// 14. Let bufferIndex be getIndex + viewOffset.
|
||||
Checked<size_t> buffer_index = get_index;
|
||||
buffer_index += view_offset;
|
||||
|
||||
// 15. Return undefined.
|
||||
if (buffer_index.has_overflow())
|
||||
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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 '{}'") \
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue