mirror of
https://github.com/RGBCube/serenity
synced 2025-06-01 08:58:11 +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)
|
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(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(js_out(print_context, "\n buffer: "));
|
||||||
TRY(print_type(print_context, "ArrayBuffer"sv));
|
TRY(print_type(print_context, "ArrayBuffer"sv));
|
||||||
TRY(js_out(print_context, " @ {:p}", data_view.viewed_array_buffer()));
|
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 {};
|
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);
|
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)
|
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||||
, m_viewed_array_buffer(viewed_buffer)
|
, m_viewed_array_buffer(viewed_buffer)
|
||||||
, m_byte_length(byte_length)
|
, m_byte_length(move(byte_length))
|
||||||
, m_byte_offset(byte_offset)
|
, m_byte_offset(byte_offset)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -29,4 +29,96 @@ void DataView::visit_edges(Visitor& visitor)
|
||||||
visitor.visit(m_viewed_array_buffer);
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <LibJS/Runtime/ArrayBuffer.h>
|
#include <LibJS/Runtime/ArrayBuffer.h>
|
||||||
|
#include <LibJS/Runtime/ByteLength.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/Object.h>
|
#include <LibJS/Runtime/Object.h>
|
||||||
|
|
||||||
|
@ -17,22 +18,32 @@ class DataView : public Object {
|
||||||
JS_DECLARE_ALLOCATOR(DataView);
|
JS_DECLARE_ALLOCATOR(DataView);
|
||||||
|
|
||||||
public:
|
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;
|
virtual ~DataView() override = default;
|
||||||
|
|
||||||
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
|
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; }
|
size_t byte_offset() const { return m_byte_offset; }
|
||||||
|
|
||||||
private:
|
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;
|
virtual void visit_edges(Visitor& visitor) override;
|
||||||
|
|
||||||
GCPtr<ArrayBuffer> m_viewed_array_buffer;
|
GCPtr<ArrayBuffer> m_viewed_array_buffer;
|
||||||
size_t m_byte_length { 0 };
|
ByteLength m_byte_length { 0 };
|
||||||
size_t m_byte_offset { 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())
|
if (array_buffer.is_detached())
|
||||||
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
||||||
|
|
||||||
// 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
|
// 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst).
|
||||||
auto buffer_byte_length = array_buffer.byte_length();
|
auto buffer_byte_length = array_buffer_byte_length(array_buffer, ArrayBuffer::Order::SeqCst);
|
||||||
|
|
||||||
// 6. If offset > bufferByteLength, throw a RangeError exception.
|
// 6. If offset > bufferByteLength, throw a RangeError exception.
|
||||||
if (offset > buffer_byte_length)
|
if (offset > buffer_byte_length)
|
||||||
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, 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()) {
|
if (byte_length.is_undefined()) {
|
||||||
// a. Let viewByteLength be bufferByteLength - offset.
|
// a. If bufferIsFixedLength is true, then
|
||||||
view_byte_length = buffer_byte_length - offset;
|
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 {
|
else {
|
||||||
// a. Let viewByteLength be ? ToIndex(byteLength).
|
// a. Let viewByteLength be ? ToIndex(byteLength).
|
||||||
view_byte_length = TRY(byte_length.to_index(vm));
|
view_byte_length = TRY(byte_length.to_index(vm));
|
||||||
|
|
||||||
// b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
|
// 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)
|
if (checked_add.has_overflow() || checked_add.value() > buffer_byte_length)
|
||||||
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, vm.names.DataView);
|
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, vm.names.DataView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
|
// 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
|
||||||
// 11. Set O.[[ViewedArrayBuffer]] to buffer.
|
auto data_view = TRY(ordinary_create_from_constructor<DataView>(vm, new_target, &Intrinsics::data_view_prototype, &array_buffer, move(view_byte_length), offset));
|
||||||
// 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. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
// 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||||
if (array_buffer.is_detached())
|
if (array_buffer.is_detached())
|
||||||
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
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;
|
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).
|
// 4. Set isLittleEndian to ToBoolean(isLittleEndian).
|
||||||
auto little_endian = is_little_endian.to_boolean();
|
auto little_endian = is_little_endian.to_boolean();
|
||||||
|
|
||||||
// 5. Let buffer be view.[[ViewedArrayBuffer]].
|
// 5. Let viewOffset be view.[[ByteOffset]].
|
||||||
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]].
|
|
||||||
auto view_offset = view->byte_offset();
|
auto view_offset = view->byte_offset();
|
||||||
|
|
||||||
// 8. Let viewSize be view.[[ByteLength]].
|
// 6. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered).
|
||||||
auto view_size = view->byte_length();
|
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);
|
auto element_size = sizeof(T);
|
||||||
|
|
||||||
// 11. Let bufferIndex be getIndex + viewOffset.
|
// 11. If getIndex + elementSize > viewSize, throw a RangeError exception.
|
||||||
Checked<size_t> buffer_index = get_index;
|
|
||||||
buffer_index += view_offset;
|
|
||||||
|
|
||||||
Checked<size_t> end_index = get_index;
|
Checked<size_t> end_index = get_index;
|
||||||
end_index += element_size;
|
end_index += element_size;
|
||||||
|
|
||||||
// 10. If getIndex + elementSize > viewSize, throw a RangeError exception.
|
if (end_index.has_overflow() || end_index.value() > view_size)
|
||||||
if (buffer_index.has_overflow() || end_index.has_overflow() || end_index.value() > view_size)
|
|
||||||
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
|
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
|
||||||
|
|
||||||
// 12. Return GetValueFromBuffer(buffer, bufferIndex, type, false, Unordered, isLittleEndian).
|
// 12. Let bufferIndex be getIndex + viewOffset.
|
||||||
return buffer->get_value<T>(buffer_index.value(), false, ArrayBuffer::Order::Unordered, little_endian);
|
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
|
// 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).
|
// 6. Set isLittleEndian to ToBoolean(isLittleEndian).
|
||||||
auto little_endian = is_little_endian.to_boolean();
|
auto little_endian = is_little_endian.to_boolean();
|
||||||
|
|
||||||
// 7. Let buffer be view.[[ViewedArrayBuffer]].
|
// 7. Let viewOffset be view.[[ByteOffset]].
|
||||||
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]].
|
|
||||||
auto view_offset = view->byte_offset();
|
auto view_offset = view->byte_offset();
|
||||||
|
|
||||||
// 10. Let viewSize be view.[[ByteLength]].
|
// 8. Let viewRecord be MakeDataViewWithBufferWitnessRecord(view, unordered).
|
||||||
auto view_size = view->byte_length();
|
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);
|
auto element_size = sizeof(T);
|
||||||
|
|
||||||
// 13. Let bufferIndex be getIndex + viewOffset.
|
// 13. If getIndex + elementSize > viewSize, throw a RangeError exception.
|
||||||
Checked<size_t> buffer_index = get_index;
|
|
||||||
buffer_index += view_offset;
|
|
||||||
|
|
||||||
Checked<size_t> end_index = get_index;
|
Checked<size_t> end_index = get_index;
|
||||||
end_index += element_size;
|
end_index += element_size;
|
||||||
|
|
||||||
// 12. If getIndex + elementSize > viewSize, throw a RangeError exception.
|
if (end_index.has_overflow() || end_index.value() > view_size)
|
||||||
if (buffer_index.has_overflow() || end_index.has_overflow() || end_index.value() > view_size)
|
|
||||||
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
|
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, get_index, view_size);
|
||||||
|
|
||||||
// 14. Perform SetValueInBuffer(buffer, bufferIndex, type, numberValue, false, Unordered, isLittleEndian).
|
// 14. Let bufferIndex be getIndex + viewOffset.
|
||||||
buffer->set_value<T>(buffer_index.value(), number_value, false, ArrayBuffer::Order::Unordered, little_endian);
|
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();
|
return js_undefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,14 +184,18 @@ JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::byte_length_getter)
|
||||||
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||||
auto data_view = TRY(typed_this_value(vm));
|
auto data_view = TRY(typed_this_value(vm));
|
||||||
|
|
||||||
// 4. Let buffer be O.[[ViewedArrayBuffer]].
|
// 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst).
|
||||||
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
auto view_record = make_data_view_with_buffer_witness_record(data_view, ArrayBuffer::Order::SeqCst);
|
||||||
if (data_view->viewed_array_buffer()->is_detached())
|
|
||||||
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
// 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).
|
// 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
|
// 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.
|
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||||
auto data_view = TRY(typed_this_value(vm));
|
auto data_view = TRY(typed_this_value(vm));
|
||||||
|
|
||||||
// 4. Let buffer be O.[[ViewedArrayBuffer]].
|
// 4. Let viewRecord be MakeDataViewWithBufferWitnessRecord(O, seq-cst).
|
||||||
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
auto view_record = make_data_view_with_buffer_witness_record(data_view, ArrayBuffer::Order::SeqCst);
|
||||||
if (data_view->viewed_array_buffer()->is_detached())
|
|
||||||
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
|
// 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]].
|
// 6. Let offset be O.[[ByteOffset]].
|
||||||
|
auto offset = data_view->byte_offset();
|
||||||
|
|
||||||
// 7. Return 𝔽(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
|
// 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(BigIntFromNonIntegral, "Cannot convert non-integral number to BigInt") \
|
||||||
M(BigIntInvalidValue, "Invalid value for BigInt: {}") \
|
M(BigIntInvalidValue, "Invalid value for BigInt: {}") \
|
||||||
M(BindingNotInitialized, "Binding {} is not initialized") \
|
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(ByteLengthExceedsMaxByteLength, "ArrayBuffer byte length of {} exceeds the max byte length of {}") \
|
||||||
M(CallStackSizeExceeded, "Call stack size limit exceeded") \
|
M(CallStackSizeExceeded, "Call stack size limit exceeded") \
|
||||||
M(CannotDeclareGlobalFunction, "Cannot declare global function of name '{}'") \
|
M(CannotDeclareGlobalFunction, "Cannot declare global function of name '{}'") \
|
||||||
|
|
|
@ -1,7 +1,36 @@
|
||||||
test("basic functionality", () => {
|
describe("errors", () => {
|
||||||
const buffer = new ArrayBuffer(124);
|
test("called on non-DataView object", () => {
|
||||||
expect(new DataView(buffer).byteLength).toBe(124);
|
expect(() => {
|
||||||
expect(new DataView(buffer, 0, 1).byteLength).toBe(1);
|
DataView.prototype.byteLength;
|
||||||
expect(new DataView(buffer, 0, 64).byteLength).toBe(64);
|
}).toThrowWithMessage(TypeError, "Not an object of type DataView");
|
||||||
expect(new DataView(buffer, 0, 123).byteLength).toBe(123);
|
});
|
||||||
|
|
||||||
|
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", () => {
|
describe("errors", () => {
|
||||||
const buffer = new ArrayBuffer(124);
|
test("called on non-DataView object", () => {
|
||||||
expect(new DataView(buffer).byteOffset).toBe(0);
|
expect(() => {
|
||||||
expect(new DataView(buffer, 1).byteOffset).toBe(1);
|
DataView.prototype.byteOffset;
|
||||||
expect(new DataView(buffer, 64).byteOffset).toBe(64);
|
}).toThrowWithMessage(TypeError, "Not an object of type DataView");
|
||||||
expect(new DataView(buffer, 123).byteOffset).toBe(123);
|
});
|
||||||
|
|
||||||
|
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:
|
// 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.
|
// 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.
|
||||||
auto* buffer = view.viewed_array_buffer();
|
auto* buffer = view.viewed_array_buffer();
|
||||||
|
@ -540,7 +556,7 @@ private:
|
||||||
vector.append(ValueTag::ArrayBufferView);
|
vector.append(ValueTag::ArrayBufferView);
|
||||||
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
|
||||||
TRY(serialize_string(vector, "DataView"_string)); // [[Constructor]]
|
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());
|
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());
|
data = buffer.viewed_array_buffer()->buffer().span().slice(buffer.byte_offset(), buffer.byte_length());
|
||||||
} else if (is<JS::DataView>(buffer_object)) {
|
} else if (is<JS::DataView>(buffer_object)) {
|
||||||
auto& buffer = static_cast<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 {
|
} else {
|
||||||
return vm.throw_completion<JS::TypeError>("Not a BufferSource"sv);
|
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)) {
|
} else if (is<JS::DataView>(buffer_source)) {
|
||||||
auto const& es_buffer_source = static_cast<JS::DataView const&>(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]].
|
// 1. Set esArrayBuffer to esBufferSource.[[ViewedArrayBuffer]].
|
||||||
es_array_buffer = es_buffer_source.viewed_array_buffer();
|
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();
|
offset = es_buffer_source.byte_offset();
|
||||||
|
|
||||||
// 3. Set length to esBufferSource.[[ByteLength]].
|
// 3. Set length to esBufferSource.[[ByteLength]].
|
||||||
length = es_buffer_source.byte_length();
|
length = JS::get_view_byte_length(view_record);
|
||||||
}
|
}
|
||||||
// 6. Otherwise:
|
// 6. Otherwise:
|
||||||
else {
|
else {
|
||||||
// 1. Assert: esBufferSource is an ArrayBuffer or SharedArrayBuffer object.
|
// 1. Assert: esBufferSource is an ArrayBuffer or SharedArrayBuffer object.
|
||||||
auto const& es_buffer_source = static_cast<JS::ArrayBuffer const&>(buffer_source);
|
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]].
|
// 2. Set length to esBufferSource.[[ArrayBufferByteLength]].
|
||||||
length = es_buffer_source.byte_length();
|
length = es_buffer_source.byte_length();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue