1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 02:58: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:
Timothy Flynn 2023-10-15 09:46:09 -04:00 committed by Andreas Kling
parent 29ac6e3689
commit c7fec9424c
12 changed files with 377 additions and 94 deletions

View file

@ -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 {};
}

View 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;
};
}

View file

@ -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;
}
}

View file

@ -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&);
}

View file

@ -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.
// a. If bufferIsFixedLength is true, then
if (buffer_is_fixed_length) {
// i. Let viewByteLength be bufferByteLength - offset.
view_byte_length = buffer_byte_length - offset;
}
// 8. Else,
// b. Else,
else {
// i. Let viewByteLength be auto.
view_byte_length = ByteLength::auto_();
}
}
// 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;
}

View file

@ -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

View file

@ -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 '{}'") \

View file

@ -1,7 +1,36 @@
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", () => {
const buffer = new ArrayBuffer(124);
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);
});
});

View file

@ -1,3 +1,25 @@
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);
@ -5,3 +27,4 @@ test("basic functionality", () => {
expect(new DataView(buffer, 64).byteOffset).toBe(64);
expect(new DataView(buffer, 123).byteOffset).toBe(123);
});
});

View file

@ -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());
}

View file

@ -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);
}

View file

@ -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,7 +62,7 @@ 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 {