Skip to content

Commit c7fec94

Browse files
trflynn89awesomekling
authored andcommitted
LibJS+LibWeb: Implement resizable ArrayBuffer support for DataView
This is (part of) a normative change in the ECMA-262 spec. See: tc39/ecma262@a9ae96e
1 parent 29ac6e3 commit c7fec94

File tree

12 files changed

+375
-92
lines changed

12 files changed

+375
-92
lines changed

Userland/Libraries/LibJS/Print.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -480,14 +480,22 @@ ErrorOr<void> print_typed_array(JS::PrintContext& print_context, JS::TypedArrayB
480480

481481
ErrorOr<void> print_data_view(JS::PrintContext& print_context, JS::DataView const& data_view, HashTable<JS::Object*>& seen_objects)
482482
{
483+
auto view_record = JS::make_data_view_with_buffer_witness_record(data_view, JS::ArrayBuffer::Order::SeqCst);
483484
TRY(print_type(print_context, "DataView"sv));
484-
TRY(js_out(print_context, "\n byteLength: "));
485-
TRY(print_value(print_context, JS::Value(data_view.byte_length()), seen_objects));
486-
TRY(js_out(print_context, "\n byteOffset: "));
487-
TRY(print_value(print_context, JS::Value(data_view.byte_offset()), seen_objects));
485+
488486
TRY(js_out(print_context, "\n buffer: "));
489487
TRY(print_type(print_context, "ArrayBuffer"sv));
490488
TRY(js_out(print_context, " @ {:p}", data_view.viewed_array_buffer()));
489+
490+
if (JS::is_view_out_of_bounds(view_record)) {
491+
TRY(js_out(print_context, "\n <out of bounds>"));
492+
return {};
493+
}
494+
495+
TRY(js_out(print_context, "\n byteLength: "));
496+
TRY(print_value(print_context, JS::Value(JS::get_view_byte_length(view_record)), seen_objects));
497+
TRY(js_out(print_context, "\n byteOffset: "));
498+
TRY(print_value(print_context, JS::Value(data_view.byte_offset()), seen_objects));
491499
return {};
492500
}
493501

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
#include <AK/Types.h>
10+
#include <AK/Variant.h>
11+
12+
namespace JS {
13+
14+
class ByteLength {
15+
public:
16+
static ByteLength auto_() { return { Auto {} }; }
17+
static ByteLength detached() { return { Detached {} }; }
18+
19+
ByteLength(u32 length)
20+
: m_length(length)
21+
{
22+
}
23+
24+
bool is_auto() const { return m_length.has<Auto>(); }
25+
bool is_detached() const { return m_length.has<Detached>(); }
26+
27+
u32 length() const
28+
{
29+
VERIFY(m_length.has<u32>());
30+
return m_length.get<u32>();
31+
}
32+
33+
private:
34+
struct Auto { };
35+
struct Detached { };
36+
37+
using Length = Variant<Auto, Detached, u32>;
38+
39+
ByteLength(Length length)
40+
: m_length(move(length))
41+
{
42+
}
43+
44+
Length m_length;
45+
};
46+
47+
}

Userland/Libraries/LibJS/Runtime/DataView.cpp

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ namespace JS {
1010

1111
JS_DEFINE_ALLOCATOR(DataView);
1212

13-
NonnullGCPtr<DataView> DataView::create(Realm& realm, ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset)
13+
NonnullGCPtr<DataView> DataView::create(Realm& realm, ArrayBuffer* viewed_buffer, ByteLength byte_length, size_t byte_offset)
1414
{
15-
return realm.heap().allocate<DataView>(realm, viewed_buffer, byte_length, byte_offset, realm.intrinsics().data_view_prototype());
15+
return realm.heap().allocate<DataView>(realm, viewed_buffer, move(byte_length), byte_offset, realm.intrinsics().data_view_prototype());
1616
}
1717

18-
DataView::DataView(ArrayBuffer* viewed_buffer, size_t byte_length, size_t byte_offset, Object& prototype)
18+
DataView::DataView(ArrayBuffer* viewed_buffer, ByteLength byte_length, size_t byte_offset, Object& prototype)
1919
: Object(ConstructWithPrototypeTag::Tag, prototype)
2020
, m_viewed_array_buffer(viewed_buffer)
21-
, m_byte_length(byte_length)
21+
, m_byte_length(move(byte_length))
2222
, m_byte_offset(byte_offset)
2323
{
2424
}
@@ -29,4 +29,96 @@ void DataView::visit_edges(Visitor& visitor)
2929
visitor.visit(m_viewed_array_buffer);
3030
}
3131

32+
// 25.3.1.2 MakeDataViewWithBufferWitnessRecord ( obj, order ), https://tc39.es/ecma262/#sec-makedataviewwithbufferwitnessrecord
33+
DataViewWithBufferWitness make_data_view_with_buffer_witness_record(DataView const& data_view, ArrayBuffer::Order order)
34+
{
35+
// 1. Let buffer be obj.[[ViewedArrayBuffer]].
36+
auto* buffer = data_view.viewed_array_buffer();
37+
38+
ByteLength byte_length { 0 };
39+
40+
// 2. If IsDetachedBuffer(buffer) is true, then
41+
if (buffer->is_detached()) {
42+
// a. Let byteLength be detached.
43+
byte_length = ByteLength::detached();
44+
}
45+
// 3. Else,
46+
else {
47+
// a. Let byteLength be ArrayBufferByteLength(buffer, order).
48+
byte_length = array_buffer_byte_length(*buffer, order);
49+
}
50+
51+
// 4. Return the DataView With Buffer Witness Record { [[Object]]: obj, [[CachedBufferByteLength]]: byteLength }.
52+
return { .object = data_view, .cached_buffer_byte_length = move(byte_length) };
53+
}
54+
55+
// 25.3.1.3 GetViewByteLength ( viewRecord ), https://tc39.es/ecma262/#sec-getviewbytelength
56+
u32 get_view_byte_length(DataViewWithBufferWitness const& view_record)
57+
{
58+
// 1. Assert: IsViewOutOfBounds(viewRecord) is false.
59+
VERIFY(!is_view_out_of_bounds(view_record));
60+
61+
// 2. Let view be viewRecord.[[Object]].
62+
auto const& view = *view_record.object;
63+
64+
// 3. If view.[[ByteLength]] is not auto, return view.[[ByteLength]].
65+
if (!view.byte_length().is_auto())
66+
return view.byte_length().length();
67+
68+
// 4. Assert: IsFixedLengthArrayBuffer(view.[[ViewedArrayBuffer]]) is false.
69+
VERIFY(!view.viewed_array_buffer()->is_fixed_length());
70+
71+
// 5. Let byteOffset be view.[[ByteOffset]].
72+
auto byte_offset = view.byte_offset();
73+
74+
// 6. Let byteLength be viewRecord.[[CachedBufferByteLength]].
75+
auto const& byte_length = view_record.cached_buffer_byte_length;
76+
77+
// 7. Assert: byteLength is not detached.
78+
VERIFY(!byte_length.is_detached());
79+
80+
// 8. Return byteLength - byteOffset.
81+
return byte_length.length() - byte_offset;
82+
}
83+
84+
// 25.3.1.4 IsViewOutOfBounds ( viewRecord ), https://tc39.es/ecma262/#sec-isviewoutofbounds
85+
bool is_view_out_of_bounds(DataViewWithBufferWitness const& view_record)
86+
{
87+
// 1. Let view be viewRecord.[[Object]].
88+
auto const& view = *view_record.object;
89+
90+
// 2. Let bufferByteLength be viewRecord.[[CachedBufferByteLength]].
91+
auto const& buffer_byte_length = view_record.cached_buffer_byte_length;
92+
93+
// 3. Assert: IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true if and only if bufferByteLength is detached.
94+
VERIFY(view.viewed_array_buffer()->is_detached() == buffer_byte_length.is_detached());
95+
96+
// 4. If bufferByteLength is detached, return true.
97+
if (buffer_byte_length.is_detached())
98+
return true;
99+
100+
// 5. Let byteOffsetStart be view.[[ByteOffset]].
101+
auto byte_offset_start = view.byte_offset();
102+
u32 byte_offset_end = 0;
103+
104+
// 6. If view.[[ByteLength]] is auto, then
105+
if (view.byte_length().is_auto()) {
106+
// a. Let byteOffsetEnd be bufferByteLength.
107+
byte_offset_end = buffer_byte_length.length();
108+
}
109+
// 7. Else,
110+
else {
111+
// a. Let byteOffsetEnd be byteOffsetStart + view.[[ByteLength]].
112+
byte_offset_end = byte_offset_start + view.byte_length().length();
113+
}
114+
115+
// 8. If byteOffsetStart > bufferByteLength or byteOffsetEnd > bufferByteLength, return true.
116+
if ((byte_offset_start > buffer_byte_length.length()) || (byte_offset_end > buffer_byte_length.length()))
117+
return true;
118+
119+
// 9. NOTE: 0-length DataViews are not considered out-of-bounds.
120+
// 10. Return false.
121+
return false;
122+
}
123+
32124
}

Userland/Libraries/LibJS/Runtime/DataView.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#pragma once
88

99
#include <LibJS/Runtime/ArrayBuffer.h>
10+
#include <LibJS/Runtime/ByteLength.h>
1011
#include <LibJS/Runtime/GlobalObject.h>
1112
#include <LibJS/Runtime/Object.h>
1213

@@ -17,22 +18,32 @@ class DataView : public Object {
1718
JS_DECLARE_ALLOCATOR(DataView);
1819

1920
public:
20-
static NonnullGCPtr<DataView> create(Realm&, ArrayBuffer*, size_t byte_length, size_t byte_offset);
21+
static NonnullGCPtr<DataView> create(Realm&, ArrayBuffer*, ByteLength byte_length, size_t byte_offset);
2122

2223
virtual ~DataView() override = default;
2324

2425
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
25-
size_t byte_length() const { return m_byte_length; }
26+
ByteLength const& byte_length() const { return m_byte_length; }
2627
size_t byte_offset() const { return m_byte_offset; }
2728

2829
private:
29-
DataView(ArrayBuffer*, size_t byte_length, size_t byte_offset, Object& prototype);
30+
DataView(ArrayBuffer*, ByteLength byte_length, size_t byte_offset, Object& prototype);
3031

3132
virtual void visit_edges(Visitor& visitor) override;
3233

3334
GCPtr<ArrayBuffer> m_viewed_array_buffer;
34-
size_t m_byte_length { 0 };
35+
ByteLength m_byte_length { 0 };
3536
size_t m_byte_offset { 0 };
3637
};
3738

39+
// 25.3.1.1 DataView With Buffer Witness Records, https://tc39.es/ecma262/#sec-dataview-with-buffer-witness-records
40+
struct DataViewWithBufferWitness {
41+
NonnullGCPtr<DataView const> object; // [[Object]]
42+
ByteLength cached_buffer_byte_length; // [[CachedBufferByteLength]]
43+
};
44+
45+
DataViewWithBufferWitness make_data_view_with_buffer_witness_record(DataView const&, ArrayBuffer::Order);
46+
u32 get_view_byte_length(DataViewWithBufferWitness const&);
47+
bool is_view_out_of_bounds(DataViewWithBufferWitness const&);
48+
3849
}

Userland/Libraries/LibJS/Runtime/DataViewConstructor.cpp

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,42 +63,71 @@ ThrowCompletionOr<NonnullGCPtr<Object>> DataViewConstructor::construct(FunctionO
6363
if (array_buffer.is_detached())
6464
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
6565

66-
// 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
67-
auto buffer_byte_length = array_buffer.byte_length();
66+
// 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst).
67+
auto buffer_byte_length = array_buffer_byte_length(array_buffer, ArrayBuffer::Order::SeqCst);
6868

6969
// 6. If offset > bufferByteLength, throw a RangeError exception.
7070
if (offset > buffer_byte_length)
7171
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, offset, buffer_byte_length);
7272

73-
size_t view_byte_length;
73+
// 7. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer).
74+
auto buffer_is_fixed_length = array_buffer.is_fixed_length();
7475

75-
// 7. If byteLength is undefined, then
76+
ByteLength view_byte_length { 0 };
77+
78+
// 8. If byteLength is undefined, then
7679
if (byte_length.is_undefined()) {
77-
// a. Let viewByteLength be bufferByteLength - offset.
78-
view_byte_length = buffer_byte_length - offset;
80+
// a. If bufferIsFixedLength is true, then
81+
if (buffer_is_fixed_length) {
82+
// i. Let viewByteLength be bufferByteLength - offset.
83+
view_byte_length = buffer_byte_length - offset;
84+
}
85+
// b. Else,
86+
else {
87+
// i. Let viewByteLength be auto.
88+
view_byte_length = ByteLength::auto_();
89+
}
7990
}
80-
// 8. Else,
91+
// 9. Else,
8192
else {
8293
// a. Let viewByteLength be ? ToIndex(byteLength).
8394
view_byte_length = TRY(byte_length.to_index(vm));
8495

8596
// b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
86-
auto const checked_add = AK::make_checked(view_byte_length) + AK::make_checked(offset);
97+
auto checked_add = AK::make_checked(offset) + AK::make_checked(static_cast<size_t>(view_byte_length.length()));
98+
8799
if (checked_add.has_overflow() || checked_add.value() > buffer_byte_length)
88100
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, vm.names.DataView);
89101
}
90102

91-
// 9. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
92-
// 11. Set O.[[ViewedArrayBuffer]] to buffer.
93-
// 12. Set O.[[ByteLength]] to viewByteLength.
94-
// 13. Set O.[[ByteOffset]] to offset.
95-
auto data_view = TRY(ordinary_create_from_constructor<DataView>(vm, new_target, &Intrinsics::data_view_prototype, &array_buffer, view_byte_length, offset));
103+
// 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
104+
auto data_view = TRY(ordinary_create_from_constructor<DataView>(vm, new_target, &Intrinsics::data_view_prototype, &array_buffer, move(view_byte_length), offset));
96105

97-
// 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
106+
// 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
98107
if (array_buffer.is_detached())
99108
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
100109

101-
// 14. Return O.
110+
// 12. Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst).
111+
buffer_byte_length = array_buffer_byte_length(array_buffer, ArrayBuffer::Order::SeqCst);
112+
113+
// 13. If offset > bufferByteLength, throw a RangeError exception.
114+
if (offset > buffer_byte_length)
115+
return vm.throw_completion<RangeError>(ErrorType::DataViewOutOfRangeByteOffset, offset, buffer_byte_length);
116+
117+
// 14. If byteLength is not undefined, then
118+
if (!byte_length.is_undefined()) {
119+
// a. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
120+
auto checked_add = AK::make_checked(offset) + AK::make_checked(static_cast<size_t>(view_byte_length.length()));
121+
122+
if (checked_add.has_overflow() || checked_add.value() > buffer_byte_length)
123+
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, vm.names.DataView);
124+
}
125+
126+
// 15. Set O.[[ViewedArrayBuffer]] to buffer.
127+
// 16. Set O.[[ByteLength]] to viewByteLength.
128+
// 17. Set O.[[ByteOffset]] to offset.
129+
130+
// 18. Return O.
102131
return data_view;
103132
}
104133

0 commit comments

Comments
 (0)