/src/serenity/Userland/Libraries/LibJS/Runtime/ArrayBuffer.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibJS/Runtime/AbstractOperations.h> |
8 | | #include <LibJS/Runtime/ArrayBuffer.h> |
9 | | #include <LibJS/Runtime/ArrayBufferConstructor.h> |
10 | | #include <LibJS/Runtime/GlobalObject.h> |
11 | | |
12 | | namespace JS { |
13 | | |
14 | | JS_DEFINE_ALLOCATOR(ArrayBuffer); |
15 | | |
16 | | ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> ArrayBuffer::create(Realm& realm, size_t byte_length) |
17 | 0 | { |
18 | 0 | auto buffer = ByteBuffer::create_zeroed(byte_length); |
19 | 0 | if (buffer.is_error()) |
20 | 0 | return realm.vm().throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, byte_length); |
21 | | |
22 | 0 | return realm.heap().allocate<ArrayBuffer>(realm, buffer.release_value(), realm.intrinsics().array_buffer_prototype()); |
23 | 0 | } |
24 | | |
25 | | NonnullGCPtr<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer buffer) |
26 | 0 | { |
27 | 0 | return realm.heap().allocate<ArrayBuffer>(realm, move(buffer), realm.intrinsics().array_buffer_prototype()); |
28 | 0 | } |
29 | | |
30 | | NonnullGCPtr<ArrayBuffer> ArrayBuffer::create(Realm& realm, ByteBuffer* buffer) |
31 | 0 | { |
32 | 0 | return realm.heap().allocate<ArrayBuffer>(realm, buffer, realm.intrinsics().array_buffer_prototype()); |
33 | 0 | } |
34 | | |
35 | | ArrayBuffer::ArrayBuffer(ByteBuffer buffer, Object& prototype) |
36 | 0 | : Object(ConstructWithPrototypeTag::Tag, prototype) |
37 | 0 | , m_data_block(DataBlock { move(buffer), DataBlock::Shared::No }) |
38 | 0 | , m_detach_key(js_undefined()) |
39 | 0 | { |
40 | 0 | } |
41 | | |
42 | | ArrayBuffer::ArrayBuffer(ByteBuffer* buffer, Object& prototype) |
43 | 0 | : Object(ConstructWithPrototypeTag::Tag, prototype) |
44 | 0 | , m_data_block(DataBlock { buffer, DataBlock::Shared::No }) |
45 | 0 | , m_detach_key(js_undefined()) |
46 | 0 | { |
47 | 0 | } |
48 | | |
49 | | void ArrayBuffer::visit_edges(Cell::Visitor& visitor) |
50 | 0 | { |
51 | 0 | Base::visit_edges(visitor); |
52 | 0 | visitor.visit(m_detach_key); |
53 | 0 | } |
54 | | |
55 | | // 6.2.9.1 CreateByteDataBlock ( size ), https://tc39.es/ecma262/#sec-createbytedatablock |
56 | | ThrowCompletionOr<DataBlock> create_byte_data_block(VM& vm, size_t size) |
57 | 0 | { |
58 | | // 1. If size > 2^53 - 1, throw a RangeError exception. |
59 | 0 | if (size > MAX_ARRAY_LIKE_INDEX) |
60 | 0 | return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "array buffer"); |
61 | | |
62 | | // 2. Let db be a new Data Block value consisting of size bytes. If it is impossible to create such a Data Block, throw a RangeError exception. |
63 | | // 3. Set all of the bytes of db to 0. |
64 | 0 | auto data_block = ByteBuffer::create_zeroed(size); |
65 | 0 | if (data_block.is_error()) |
66 | 0 | return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, size); |
67 | | |
68 | | // 4. Return db. |
69 | 0 | return DataBlock { data_block.release_value(), DataBlock::Shared::No }; |
70 | 0 | } |
71 | | |
72 | | // FIXME: The returned DataBlock is not shared in the sense that the standard specifies it. |
73 | | // 6.2.9.2 CreateSharedByteDataBlock ( size ), https://tc39.es/ecma262/#sec-createsharedbytedatablock |
74 | | static ThrowCompletionOr<DataBlock> create_shared_byte_data_block(VM& vm, size_t size) |
75 | 0 | { |
76 | | // 1. Let db be a new Shared Data Block value consisting of size bytes. If it is impossible to create such a Shared Data Block, throw a RangeError exception. |
77 | 0 | auto data_block = ByteBuffer::create_zeroed(size); |
78 | 0 | if (data_block.is_error()) |
79 | 0 | return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, size); |
80 | | |
81 | | // 2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. |
82 | | // 3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier(). |
83 | | // 4. Let zero be « 0 ». |
84 | | // 5. For each index i of db, do |
85 | | // a. Append WriteSharedMemory { [[Order]]: init, [[NoTear]]: true, [[Block]]: db, [[ByteIndex]]: i, [[ElementSize]]: 1, [[Payload]]: zero } to eventsRecord.[[EventList]]. |
86 | | // 6. Return db. |
87 | 0 | return DataBlock { data_block.release_value(), DataBlock::Shared::Yes }; |
88 | 0 | } |
89 | | |
90 | | // 6.2.9.3 CopyDataBlockBytes ( toBlock, toIndex, fromBlock, fromIndex, count ), https://tc39.es/ecma262/#sec-copydatablockbytes |
91 | | void copy_data_block_bytes(ByteBuffer& to_block, u64 to_index, ByteBuffer const& from_block, u64 from_index, u64 count) |
92 | 0 | { |
93 | | // 1. Assert: fromBlock and toBlock are distinct values. |
94 | 0 | VERIFY(&to_block != &from_block); |
95 | | |
96 | | // 2. Let fromSize be the number of bytes in fromBlock. |
97 | 0 | auto from_size = from_block.size(); |
98 | | |
99 | | // 3. Assert: fromIndex + count ≤ fromSize. |
100 | 0 | VERIFY(from_index + count <= from_size); |
101 | | |
102 | | // 4. Let toSize be the number of bytes in toBlock. |
103 | 0 | auto to_size = to_block.size(); |
104 | | |
105 | | // 5. Assert: toIndex + count ≤ toSize. |
106 | 0 | VERIFY(to_index + count <= to_size); |
107 | | |
108 | | // 6. Repeat, while count > 0, |
109 | 0 | while (count > 0) { |
110 | | // FIXME: a. If fromBlock is a Shared Data Block, then |
111 | | // FIXME: i. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record. |
112 | | // FIXME: ii. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier(). |
113 | | // FIXME: iii. Let bytes be a List whose sole element is a nondeterministically chosen byte value. |
114 | | // FIXME: iv. NOTE: In implementations, bytes is the result of a non-atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency. |
115 | | // FIXME: v. Let readEvent be ReadSharedMemory { [[Order]]: Unordered, [[NoTear]]: true, [[Block]]: fromBlock, [[ByteIndex]]: fromIndex, [[ElementSize]]: 1 }. |
116 | | // FIXME: vi. Append readEvent to eventsRecord.[[EventList]]. |
117 | | // FIXME: vii. Append Chosen Value Record { [[Event]]: readEvent, [[ChosenValue]]: bytes } to execution.[[ChosenValues]]. |
118 | | // FIXME: viii. If toBlock is a Shared Data Block, then |
119 | | // FIXME: 1. Append WriteSharedMemory { [[Order]]: Unordered, [[NoTear]]: true, [[Block]]: toBlock, [[ByteIndex]]: toIndex, [[ElementSize]]: 1, [[Payload]]: bytes } to eventsRecord.[[EventList]]. |
120 | | // FIXME: ix. Else, |
121 | | // FIXME: 1. Set toBlock[toIndex] to bytes[0]. |
122 | | // FIXME: b. Else, |
123 | | // FIXME: i. Assert: toBlock is not a Shared Data Block. |
124 | | |
125 | | // ii. Set toBlock[toIndex] to fromBlock[fromIndex]. |
126 | 0 | to_block[to_index] = from_block[from_index]; |
127 | | |
128 | | // c. Set toIndex to toIndex + 1. |
129 | 0 | ++to_index; |
130 | | |
131 | | // d. Set fromIndex to fromIndex + 1. |
132 | 0 | ++from_index; |
133 | | |
134 | | // e. Set count to count - 1. |
135 | 0 | --count; |
136 | 0 | } |
137 | | |
138 | | // 7. Return unused. |
139 | 0 | } |
140 | | |
141 | | // 25.1.3.1 AllocateArrayBuffer ( constructor, byteLength [ , maxByteLength ] ), https://tc39.es/ecma262/#sec-allocatearraybuffer |
142 | | ThrowCompletionOr<ArrayBuffer*> allocate_array_buffer(VM& vm, FunctionObject& constructor, size_t byte_length, Optional<size_t> const& max_byte_length) |
143 | 0 | { |
144 | | // 1. Let slots be « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] ». |
145 | | |
146 | | // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingResizableBuffer be true; otherwise let allocatingResizableBuffer be false. |
147 | 0 | auto allocating_resizable_buffer = max_byte_length.has_value(); |
148 | | |
149 | | // 3. If allocatingResizableBuffer is true, then |
150 | 0 | if (allocating_resizable_buffer) { |
151 | | // a. If byteLength > maxByteLength, throw a RangeError exception. |
152 | 0 | if (byte_length > *max_byte_length) |
153 | 0 | return vm.throw_completion<RangeError>(ErrorType::ByteLengthExceedsMaxByteLength, byte_length, *max_byte_length); |
154 | | |
155 | | // b. Append [[ArrayBufferMaxByteLength]] to slots. |
156 | 0 | } |
157 | | |
158 | | // 4. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", slots). |
159 | 0 | auto obj = TRY(ordinary_create_from_constructor<ArrayBuffer>(vm, constructor, &Intrinsics::array_buffer_prototype, nullptr)); |
160 | | |
161 | | // 5. Let block be ? CreateByteDataBlock(byteLength). |
162 | 0 | auto block = TRY(create_byte_data_block(vm, byte_length)); |
163 | | |
164 | | // 6. Set obj.[[ArrayBufferData]] to block. |
165 | 0 | obj->set_data_block(move(block)); |
166 | | |
167 | | // 7. Set obj.[[ArrayBufferByteLength]] to byteLength. |
168 | | |
169 | | // 8. If allocatingResizableBuffer is true, then |
170 | 0 | if (allocating_resizable_buffer) { |
171 | | // a. If it is not possible to create a Data Block block consisting of maxByteLength bytes, throw a RangeError exception. |
172 | | // b. NOTE: Resizable ArrayBuffers are designed to be implementable with in-place growth. Implementations may throw if, for example, virtual memory cannot be reserved up front. |
173 | 0 | if (auto result = obj->buffer().try_ensure_capacity(*max_byte_length); result.is_error()) |
174 | 0 | return vm.throw_completion<RangeError>(ErrorType::NotEnoughMemoryToAllocate, *max_byte_length); |
175 | | |
176 | | // c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. |
177 | 0 | obj->set_max_byte_length(*max_byte_length); |
178 | 0 | } |
179 | | |
180 | | // 9. Return obj. |
181 | 0 | return obj.ptr(); |
182 | 0 | } |
183 | | |
184 | | // 25.1.3.3 ArrayBufferCopyAndDetach ( arrayBuffer, newLength, preserveResizability ), https://tc39.es/ecma262/#sec-arraybuffercopyanddetach |
185 | | ThrowCompletionOr<ArrayBuffer*> array_buffer_copy_and_detach(VM& vm, ArrayBuffer& array_buffer, Value new_length, PreserveResizability preserve_resizability) |
186 | 0 | { |
187 | 0 | auto& realm = *vm.current_realm(); |
188 | | |
189 | | // 1. Perform ? RequireInternalSlot(arrayBuffer, [[ArrayBufferData]]). |
190 | | |
191 | | // 2. If IsSharedArrayBuffer(arrayBuffer) is true, throw a TypeError exception. |
192 | 0 | if (array_buffer.is_shared_array_buffer()) |
193 | 0 | return vm.throw_completion<TypeError>(ErrorType::SharedArrayBuffer); |
194 | | |
195 | | // 3. If newLength is undefined, then |
196 | | // a. Let newByteLength be arrayBuffer.[[ArrayBufferByteLength]]. |
197 | | // 4. Else, |
198 | | // a. Let newByteLength be ? ToIndex(newLength). |
199 | 0 | auto new_byte_length = new_length.is_undefined() ? array_buffer.byte_length() : TRY(new_length.to_index(vm)); |
200 | | |
201 | | // 5. If IsDetachedBuffer(arrayBuffer) is true, throw a TypeError exception. |
202 | 0 | if (array_buffer.is_detached()) |
203 | 0 | return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer); |
204 | | |
205 | 0 | Optional<size_t> new_max_byte_length; |
206 | | |
207 | | // 6. If preserveResizability is PRESERVE-RESIZABILITY and IsFixedLengthArrayBuffer(arrayBuffer) is false, then |
208 | 0 | if (preserve_resizability == PreserveResizability::PreserveResizability && !array_buffer.is_fixed_length()) { |
209 | | // a. Let newMaxByteLength be arrayBuffer.[[ArrayBufferMaxByteLength]]. |
210 | 0 | new_max_byte_length = array_buffer.max_byte_length(); |
211 | 0 | } |
212 | | // 7. Else, |
213 | 0 | else { |
214 | | // a. Let newMaxByteLength be EMPTY. |
215 | 0 | } |
216 | | |
217 | | // 8. If arrayBuffer.[[ArrayBufferDetachKey]] is not undefined, throw a TypeError exception. |
218 | 0 | if (!array_buffer.detach_key().is_undefined()) |
219 | 0 | return vm.throw_completion<TypeError>(ErrorType::DetachKeyMismatch, array_buffer.detach_key(), js_undefined()); |
220 | | |
221 | | // 9. Let newBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, newByteLength, newMaxByteLength). |
222 | 0 | auto* new_buffer = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), new_byte_length, new_max_byte_length)); |
223 | | |
224 | | // 10. Let copyLength be min(newByteLength, arrayBuffer.[[ArrayBufferByteLength]]). |
225 | 0 | auto copy_length = min(new_byte_length, array_buffer.byte_length()); |
226 | | |
227 | | // 11. Let fromBlock be arrayBuffer.[[ArrayBufferData]]. |
228 | | // 12. Let toBlock be newBuffer.[[ArrayBufferData]]. |
229 | | // 13. Perform CopyDataBlockBytes(toBlock, 0, fromBlock, 0, copyLength). |
230 | | // 14. NOTE: Neither creation of the new Data Block nor copying from the old Data Block are observable. Implementations may implement this method as a zero-copy move or a realloc. |
231 | 0 | copy_data_block_bytes(new_buffer->buffer(), 0, array_buffer.buffer(), 0, copy_length); |
232 | | |
233 | | // 15. Perform ! DetachArrayBuffer(arrayBuffer). |
234 | 0 | MUST(detach_array_buffer(vm, array_buffer)); |
235 | | |
236 | | // 16. Return newBuffer. |
237 | 0 | return new_buffer; |
238 | 0 | } |
239 | | |
240 | | // 25.1.3.5 DetachArrayBuffer ( arrayBuffer [ , key ] ), https://tc39.es/ecma262/#sec-detacharraybuffer |
241 | | ThrowCompletionOr<void> detach_array_buffer(VM& vm, ArrayBuffer& array_buffer, Optional<Value> key) |
242 | 0 | { |
243 | | // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. |
244 | 0 | VERIFY(!array_buffer.is_shared_array_buffer()); |
245 | | |
246 | | // 2. If key is not present, set key to undefined. |
247 | 0 | if (!key.has_value()) |
248 | 0 | key = js_undefined(); |
249 | | |
250 | | // 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. |
251 | 0 | if (!same_value(array_buffer.detach_key(), *key)) |
252 | 0 | return vm.throw_completion<TypeError>(ErrorType::DetachKeyMismatch, *key, array_buffer.detach_key()); |
253 | | |
254 | | // 4. Set arrayBuffer.[[ArrayBufferData]] to null. |
255 | | // 5. Set arrayBuffer.[[ArrayBufferByteLength]] to 0. |
256 | 0 | array_buffer.detach_buffer(); |
257 | | |
258 | | // 6. Return unused. |
259 | 0 | return {}; |
260 | 0 | } |
261 | | |
262 | | // 25.1.3.6 CloneArrayBuffer ( srcBuffer, srcByteOffset, srcLength, cloneConstructor ), https://tc39.es/ecma262/#sec-clonearraybuffer |
263 | | ThrowCompletionOr<ArrayBuffer*> clone_array_buffer(VM& vm, ArrayBuffer& source_buffer, size_t source_byte_offset, size_t source_length) |
264 | 0 | { |
265 | 0 | auto& realm = *vm.current_realm(); |
266 | | |
267 | | // 1. Assert: IsDetachedBuffer(srcBuffer) is false. |
268 | 0 | VERIFY(!source_buffer.is_detached()); |
269 | | |
270 | | // 2. Let targetBuffer be ? AllocateArrayBuffer(%ArrayBuffer%, srcLength). |
271 | 0 | auto* target_buffer = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), source_length)); |
272 | | |
273 | | // 3. Let srcBlock be srcBuffer.[[ArrayBufferData]]. |
274 | 0 | auto& source_block = source_buffer.buffer(); |
275 | | |
276 | | // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. |
277 | 0 | auto& target_block = target_buffer->buffer(); |
278 | | |
279 | | // 5. Perform CopyDataBlockBytes(targetBlock, 0, srcBlock, srcByteOffset, srcLength). |
280 | 0 | copy_data_block_bytes(target_block, 0, source_block, source_byte_offset, source_length); |
281 | | |
282 | | // 6. Return targetBuffer. |
283 | 0 | return target_buffer; |
284 | 0 | } |
285 | | |
286 | | // 25.1.3.7 GetArrayBufferMaxByteLengthOption ( options ), https://tc39.es/ecma262/#sec-getarraybuffermaxbytelengthoption |
287 | | ThrowCompletionOr<Optional<size_t>> get_array_buffer_max_byte_length_option(VM& vm, Value options) |
288 | 0 | { |
289 | | // 1. If options is not an Object, return empty. |
290 | 0 | if (!options.is_object()) |
291 | 0 | return OptionalNone {}; |
292 | | |
293 | | // 2. Let maxByteLength be ? Get(options, "maxByteLength"). |
294 | 0 | auto max_byte_length = TRY(options.as_object().get(vm.names.maxByteLength)); |
295 | | |
296 | | // 3. If maxByteLength is undefined, return empty. |
297 | 0 | if (max_byte_length.is_undefined()) |
298 | 0 | return OptionalNone {}; |
299 | | |
300 | | // 4. Return ? ToIndex(maxByteLength). |
301 | 0 | return TRY(max_byte_length.to_index(vm)); |
302 | 0 | } |
303 | | |
304 | | // 25.2.2.1 AllocateSharedArrayBuffer ( constructor, byteLength [ , maxByteLength ] ), https://tc39.es/ecma262/#sec-allocatesharedarraybuffer |
305 | | ThrowCompletionOr<NonnullGCPtr<ArrayBuffer>> allocate_shared_array_buffer(VM& vm, FunctionObject& constructor, size_t byte_length) |
306 | 0 | { |
307 | | // 1. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%SharedArrayBuffer.prototype%", « [[ArrayBufferData]], [[ArrayBufferByteLength]] »). |
308 | 0 | auto obj = TRY(ordinary_create_from_constructor<ArrayBuffer>(vm, constructor, &Intrinsics::shared_array_buffer_prototype, nullptr)); |
309 | | |
310 | | // 2. Let block be ? CreateSharedByteDataBlock(byteLength). |
311 | 0 | auto block = TRY(create_shared_byte_data_block(vm, byte_length)); |
312 | | |
313 | | // 3. Set obj.[[ArrayBufferData]] to block. |
314 | | // 4. Set obj.[[ArrayBufferByteLength]] to byteLength. |
315 | 0 | obj->set_data_block(move(block)); |
316 | | |
317 | | // 5. Return obj. |
318 | 0 | return obj; |
319 | 0 | } |
320 | | |
321 | | } |