Serenity Operating System
1/*
2 * Copyright (c) 2020-2022, Linus Groh <linusg@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#pragma once
8
9#include <AK/ByteBuffer.h>
10#include <AK/Function.h>
11#include <AK/Variant.h>
12#include <LibJS/Runtime/BigInt.h>
13#include <LibJS/Runtime/Completion.h>
14#include <LibJS/Runtime/GlobalObject.h>
15#include <LibJS/Runtime/Object.h>
16
17namespace JS {
18
19struct ClampedU8 {
20};
21
22// 25.1.1 Notation (read-modify-write modification function), https://tc39.es/ecma262/#sec-arraybuffer-notation
23using ReadWriteModifyFunction = Function<ByteBuffer(ByteBuffer, ByteBuffer)>;
24
25class ArrayBuffer : public Object {
26 JS_OBJECT(ArrayBuffer, Object);
27
28public:
29 static ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> create(Realm&, size_t);
30 static NonnullGCPtr<ArrayBuffer> create(Realm&, ByteBuffer);
31 static NonnullGCPtr<ArrayBuffer> create(Realm&, ByteBuffer*);
32
33 virtual ~ArrayBuffer() override = default;
34
35 size_t byte_length() const { return buffer_impl().size(); }
36 ByteBuffer& buffer() { return buffer_impl(); }
37 ByteBuffer const& buffer() const { return buffer_impl(); }
38
39 // Used by allocate_array_buffer() to attach the data block after construction
40 void set_buffer(ByteBuffer buffer) { m_buffer = move(buffer); }
41
42 Value detach_key() const { return m_detach_key; }
43 void set_detach_key(Value detach_key) { m_detach_key = detach_key; }
44
45 void detach_buffer() { m_buffer = Empty {}; }
46 bool is_detached() const { return m_buffer.has<Empty>(); }
47
48 enum Order {
49 SeqCst,
50 Unordered
51 };
52 template<typename type>
53 Value get_value(size_t byte_index, bool is_typed_array, Order, bool is_little_endian = true);
54 template<typename type>
55 void set_value(size_t byte_index, Value value, bool is_typed_array, Order, bool is_little_endian = true);
56 template<typename T>
57 Value get_modify_set_value(size_t byte_index, Value value, ReadWriteModifyFunction operation, bool is_little_endian = true);
58
59private:
60 ArrayBuffer(ByteBuffer buffer, Object& prototype);
61 ArrayBuffer(ByteBuffer* buffer, Object& prototype);
62
63 virtual void visit_edges(Visitor&) override;
64
65 ByteBuffer& buffer_impl()
66 {
67 ByteBuffer* ptr { nullptr };
68 m_buffer.visit([&](Empty) { VERIFY_NOT_REACHED(); }, [&](auto* pointer) { ptr = pointer; }, [&](auto& value) { ptr = &value; });
69 return *ptr;
70 }
71
72 ByteBuffer const& buffer_impl() const { return const_cast<ArrayBuffer*>(this)->buffer_impl(); }
73
74 Variant<Empty, ByteBuffer, ByteBuffer*> m_buffer;
75 // The various detach related members of ArrayBuffer are not used by any ECMA262 functionality,
76 // but are required to be available for the use of various harnesses like the Test262 test runner.
77 Value m_detach_key;
78};
79
80ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM&, FunctionObject& constructor, size_t byte_length);
81ThrowCompletionOr<void> detach_array_buffer(VM&, ArrayBuffer& array_buffer, Optional<Value> key = {});
82ThrowCompletionOr<ArrayBuffer*> clone_array_buffer(VM&, ArrayBuffer& source_buffer, size_t source_byte_offset, size_t source_length);
83
84// 25.1.2.9 RawBytesToNumeric ( type, rawBytes, isLittleEndian ), https://tc39.es/ecma262/#sec-rawbytestonumeric
85template<typename T>
86static Value raw_bytes_to_numeric(VM& vm, ByteBuffer raw_value, bool is_little_endian)
87{
88 if (!is_little_endian) {
89 VERIFY(raw_value.size() % 2 == 0);
90 for (size_t i = 0; i < raw_value.size() / 2; ++i)
91 swap(raw_value[i], raw_value[raw_value.size() - 1 - i]);
92 }
93 using UnderlyingBufferDataType = Conditional<IsSame<ClampedU8, T>, u8, T>;
94 if constexpr (IsSame<UnderlyingBufferDataType, float>) {
95 float value;
96 raw_value.span().copy_to({ &value, sizeof(float) });
97 if (isnan(value))
98 return js_nan();
99 return Value(value);
100 }
101 if constexpr (IsSame<UnderlyingBufferDataType, double>) {
102 double value;
103 raw_value.span().copy_to({ &value, sizeof(double) });
104 if (isnan(value))
105 return js_nan();
106 return Value(value);
107 }
108 if constexpr (!IsIntegral<UnderlyingBufferDataType>)
109 VERIFY_NOT_REACHED();
110 UnderlyingBufferDataType int_value = 0;
111 raw_value.span().copy_to({ &int_value, sizeof(UnderlyingBufferDataType) });
112 if constexpr (sizeof(UnderlyingBufferDataType) == 8) {
113 if constexpr (IsSigned<UnderlyingBufferDataType>) {
114 static_assert(IsSame<UnderlyingBufferDataType, i64>);
115 return BigInt::create(vm, Crypto::SignedBigInteger { int_value });
116 } else {
117 static_assert(IsOneOf<UnderlyingBufferDataType, u64, double>);
118 return BigInt::create(vm, Crypto::SignedBigInteger { Crypto::UnsignedBigInteger { int_value } });
119 }
120 } else {
121 return Value(int_value);
122 }
123}
124
125// Implementation for 25.1.2.10 GetValueFromBuffer, used in TypedArray<T>::get_value_from_buffer().
126template<typename T>
127Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_array, Order, bool is_little_endian)
128{
129 auto& vm = this->vm();
130
131 auto element_size = sizeof(T);
132
133 // FIXME: Check for shared buffer
134
135 // FIXME: Propagate errors.
136 auto raw_value = MUST(buffer_impl().slice(byte_index, element_size));
137 return raw_bytes_to_numeric<T>(vm, move(raw_value), is_little_endian);
138}
139
140// 25.1.2.11 NumericToRawBytes ( type, value, isLittleEndian ), https://tc39.es/ecma262/#sec-numerictorawbytes
141template<typename T>
142static ByteBuffer numeric_to_raw_bytes(VM& vm, Value value, bool is_little_endian)
143{
144 VERIFY(value.is_number() || value.is_bigint());
145 using UnderlyingBufferDataType = Conditional<IsSame<ClampedU8, T>, u8, T>;
146 ByteBuffer raw_bytes = ByteBuffer::create_uninitialized(sizeof(UnderlyingBufferDataType)).release_value_but_fixme_should_propagate_errors(); // FIXME: Handle possible OOM situation.
147 auto flip_if_needed = [&]() {
148 if (is_little_endian)
149 return;
150 VERIFY(sizeof(UnderlyingBufferDataType) % 2 == 0);
151 for (size_t i = 0; i < sizeof(UnderlyingBufferDataType) / 2; ++i)
152 swap(raw_bytes[i], raw_bytes[sizeof(UnderlyingBufferDataType) - 1 - i]);
153 };
154 if constexpr (IsSame<UnderlyingBufferDataType, float>) {
155 float raw_value = MUST(value.to_double(vm));
156 ReadonlyBytes { &raw_value, sizeof(float) }.copy_to(raw_bytes);
157 flip_if_needed();
158 return raw_bytes;
159 }
160 if constexpr (IsSame<UnderlyingBufferDataType, double>) {
161 double raw_value = MUST(value.to_double(vm));
162 ReadonlyBytes { &raw_value, sizeof(double) }.copy_to(raw_bytes);
163 flip_if_needed();
164 return raw_bytes;
165 }
166 if constexpr (!IsIntegral<UnderlyingBufferDataType>)
167 VERIFY_NOT_REACHED();
168 if constexpr (sizeof(UnderlyingBufferDataType) == 8) {
169 UnderlyingBufferDataType int_value;
170
171 if constexpr (IsSigned<UnderlyingBufferDataType>)
172 int_value = MUST(value.to_bigint_int64(vm));
173 else
174 int_value = MUST(value.to_bigint_uint64(vm));
175
176 ReadonlyBytes { &int_value, sizeof(UnderlyingBufferDataType) }.copy_to(raw_bytes);
177 flip_if_needed();
178 return raw_bytes;
179 } else {
180 UnderlyingBufferDataType int_value;
181 if constexpr (IsSigned<UnderlyingBufferDataType>) {
182 if constexpr (sizeof(UnderlyingBufferDataType) == 4)
183 int_value = MUST(value.to_i32(vm));
184 else if constexpr (sizeof(UnderlyingBufferDataType) == 2)
185 int_value = MUST(value.to_i16(vm));
186 else
187 int_value = MUST(value.to_i8(vm));
188 } else {
189 if constexpr (sizeof(UnderlyingBufferDataType) == 4)
190 int_value = MUST(value.to_u32(vm));
191 else if constexpr (sizeof(UnderlyingBufferDataType) == 2)
192 int_value = MUST(value.to_u16(vm));
193 else if constexpr (!IsSame<T, ClampedU8>)
194 int_value = MUST(value.to_u8(vm));
195 else
196 int_value = MUST(value.to_u8_clamp(vm));
197 }
198 ReadonlyBytes { &int_value, sizeof(UnderlyingBufferDataType) }.copy_to(raw_bytes);
199 if constexpr (sizeof(UnderlyingBufferDataType) % 2 == 0)
200 flip_if_needed();
201 return raw_bytes;
202 }
203}
204
205// 25.1.2.12 SetValueInBuffer ( arrayBuffer, byteIndex, type, value, isTypedArray, order [ , isLittleEndian ] ), https://tc39.es/ecma262/#sec-setvalueinbuffer
206template<typename T>
207void ArrayBuffer::set_value(size_t byte_index, Value value, [[maybe_unused]] bool is_typed_array, Order, bool is_little_endian)
208{
209 auto& vm = this->vm();
210
211 auto raw_bytes = numeric_to_raw_bytes<T>(vm, value, is_little_endian);
212
213 // FIXME: Check for shared buffer
214
215 raw_bytes.span().copy_to(buffer_impl().span().slice(byte_index));
216}
217
218// 25.1.2.13 GetModifySetValueInBuffer ( arrayBuffer, byteIndex, type, value, op [ , isLittleEndian ] ), https://tc39.es/ecma262/#sec-getmodifysetvalueinbuffer
219template<typename T>
220Value ArrayBuffer::get_modify_set_value(size_t byte_index, Value value, ReadWriteModifyFunction operation, bool is_little_endian)
221{
222 auto& vm = this->vm();
223
224 auto raw_bytes = numeric_to_raw_bytes<T>(vm, value, is_little_endian);
225
226 // FIXME: Check for shared buffer
227
228 // FIXME: Propagate errors.
229 auto raw_bytes_read = MUST(buffer_impl().slice(byte_index, sizeof(T)));
230 auto raw_bytes_modified = operation(raw_bytes_read, raw_bytes);
231 raw_bytes_modified.span().copy_to(buffer_impl().span().slice(byte_index));
232
233 return raw_bytes_to_numeric<T>(vm, raw_bytes_read, is_little_endian);
234}
235
236}