1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 18:24:57 +00:00

LibJS+LibWeb: Implement resizable ArrayBuffer support for TypedArray

This is (part of) a normative change in the ECMA-262 spec. See:
a9ae96e
This commit is contained in:
Timothy Flynn 2023-12-24 14:55:10 -05:00 committed by Andreas Kling
parent c7fec9424c
commit 9258d7b98a
47 changed files with 2059 additions and 884 deletions

View file

@ -445,19 +445,26 @@ ErrorOr<void> print_number(JS::PrintContext& print_context, T number)
ErrorOr<void> print_typed_array(JS::PrintContext& print_context, JS::TypedArrayBase const& typed_array_base, HashTable<JS::Object*>& seen_objects) ErrorOr<void> print_typed_array(JS::PrintContext& print_context, JS::TypedArrayBase const& typed_array_base, HashTable<JS::Object*>& seen_objects)
{ {
auto& array_buffer = *typed_array_base.viewed_array_buffer(); auto& array_buffer = *typed_array_base.viewed_array_buffer();
auto length = typed_array_base.array_length();
auto typed_array_record = JS::make_typed_array_with_buffer_witness_record(typed_array_base, JS::ArrayBuffer::Order::SeqCst);
TRY(print_type(print_context, typed_array_base.class_name())); TRY(print_type(print_context, typed_array_base.class_name()));
TRY(js_out(print_context, "\n buffer: "));
TRY(print_type(print_context, "ArrayBuffer"sv));
TRY(js_out(print_context, " @ {:p}", &array_buffer));
if (JS::is_typed_array_out_of_bounds(typed_array_record)) {
TRY(js_out(print_context, "\n <out of bounds>"));
return {};
}
auto length = JS::typed_array_length(typed_array_record);
TRY(js_out(print_context, "\n length: ")); TRY(js_out(print_context, "\n length: "));
TRY(print_value(print_context, JS::Value(length), seen_objects)); TRY(print_value(print_context, JS::Value(length), seen_objects));
TRY(js_out(print_context, "\n byteLength: ")); TRY(js_out(print_context, "\n byteLength: "));
TRY(print_value(print_context, JS::Value(typed_array_base.byte_length()), seen_objects)); TRY(print_value(print_context, JS::Value(JS::typed_array_byte_length(typed_array_record)), seen_objects));
TRY(js_out(print_context, "\n buffer: "));
TRY(print_type(print_context, "ArrayBuffer"sv));
if (array_buffer.is_detached())
TRY(js_out(print_context, " (detached)"));
TRY(js_out(print_context, " @ {:p}", &array_buffer));
if (length == 0 || array_buffer.is_detached())
return {};
TRY(js_out(print_context, "\n")); TRY(js_out(print_context, "\n"));
// FIXME: This kinda sucks. // FIXME: This kinda sucks.
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \

View file

@ -50,41 +50,77 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayIteratorPrototype::next)
size_t length; size_t length;
// i. If array has a [[TypedArrayName]] internal slot, then
if (array.is_typed_array()) { if (array.is_typed_array()) {
auto& typed_array = static_cast<TypedArrayBase&>(array); auto& typed_array = static_cast<TypedArrayBase&>(array);
if (typed_array.viewed_array_buffer()->is_detached()) // 1. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(array, seq-cst).
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer); auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, ArrayBuffer::SeqCst);
length = typed_array.array_length(); // 2. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
} else { if (is_typed_array_out_of_bounds(typed_array_record))
return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
// 3. Let len be TypedArrayLength(taRecord).
length = typed_array_length(typed_array_record);
}
// ii. Else,
else {
// 1. Let len be ? LengthOfArrayLike(array).
length = TRY(length_of_array_like(vm, array)); length = TRY(length_of_array_like(vm, array));
} }
// iii. If index ≥ len, return NormalCompletion(undefined).
if (index >= length) { if (index >= length) {
iterator->m_array = js_undefined(); iterator->m_array = js_undefined();
return create_iterator_result_object(vm, js_undefined(), true); return create_iterator_result_object(vm, js_undefined(), true);
} }
iterator->m_index++; // iv. Let indexNumber be 𝔽(index).
if (iteration_kind == Object::PropertyKind::Key)
return create_iterator_result_object(vm, Value(static_cast<i32>(index)), false);
auto value = TRY([&]() -> ThrowCompletionOr<Value> { Value result;
// OPTIMIZATION: For objects that don't interfere with indexed property access, we try looking directly at storage.
if (!array.may_interfere_with_indexed_property_access() && array.indexed_properties().has_index(index)) { // v. If kind is key, then
auto value = array.indexed_properties().get(index)->value; if (iteration_kind == Object::PropertyKind::Key) {
if (!value.is_accessor()) { // 1. Let result be indexNumber.
return value; result = Value(static_cast<i32>(index));
}
// vi. Else,
else {
// 1. Let elementKey be ! ToString(indexNumber).
// 2. Let elementValue be ? Get(array, elementKey).
auto element_value = TRY([&]() -> ThrowCompletionOr<Value> {
// OPTIMIZATION: For objects that don't interfere with indexed property access, we try looking directly at storage.
if (!array.may_interfere_with_indexed_property_access() && array.indexed_properties().has_index(index)) {
auto value = array.indexed_properties().get(index)->value;
if (!value.is_accessor()) {
return value;
}
} }
return array.get(index);
}());
// 3. If kind is value, then
if (iteration_kind == Object::PropertyKind::Value) {
// a. Let result be elementValue.
result = element_value;
} }
return array.get(index); // 4. Else,
}()); else {
// a. Assert: kind is key+value.
VERIFY(iteration_kind == Object::PropertyKind::KeyAndValue);
if (iteration_kind == Object::PropertyKind::Value) // b. Let result be CreateArrayFromList(« indexNumber, elementValue »).
return create_iterator_result_object(vm, value, false); result = Array::create_from(realm, { Value(static_cast<i32>(index)), element_value });
}
}
return create_iterator_result_object(vm, Array::create_from(realm, { Value(static_cast<i32>(index)), value }), false); // viii. Set index to index + 1.
++iterator->m_index;
// vii. Perform ? GeneratorYield(CreateIterResultObject(result, false)).
return create_iterator_result_object(vm, result, false);
} }
} }

View file

@ -27,89 +27,118 @@ namespace JS {
JS_DEFINE_ALLOCATOR(AtomicsObject); JS_DEFINE_ALLOCATOR(AtomicsObject);
// 25.4.2.1 ValidateIntegerTypedArray ( typedArray [ , waitable ] ), https://tc39.es/ecma262/#sec-validateintegertypedarray // 25.4.2.1 ValidateIntegerTypedArray ( typedArray, waitable ), https://tc39.es/ecma262/#sec-validateintegertypedarray
static ThrowCompletionOr<ArrayBuffer*> validate_integer_typed_array(VM& vm, TypedArrayBase& typed_array, bool waitable = false) static ThrowCompletionOr<TypedArrayWithBufferWitness> validate_integer_typed_array(VM& vm, TypedArrayBase const& typed_array, bool waitable)
{ {
// 1. If waitable is not present, set waitable to false. // 1. Let taRecord be ? ValidateTypedArray(typedArray, unordered).
auto typed_array_record = TRY(validate_typed_array(vm, typed_array, ArrayBuffer::Order::Unordered));
// 2. Perform ? ValidateTypedArray(typedArray). // 2. NOTE: Bounds checking is not a synchronizing operation when typedArray's backing buffer is a growable SharedArrayBuffer.
TRY(validate_typed_array(vm, typed_array));
// 3. Let buffer be typedArray.[[ViewedArrayBuffer]].
auto* buffer = typed_array.viewed_array_buffer();
auto const& type_name = typed_array.element_name(); auto const& type_name = typed_array.element_name();
// 4. If waitable is true, then // 3. If waitable is true, then
if (waitable) { if (waitable) {
// a. If typedArray.[[TypedArrayName]] is not "Int32Array" or "BigInt64Array", throw a TypeError exception. // a. If typedArray.[[TypedArrayName]] is neither "Int32Array" nor "BigInt64Array", throw a TypeError exception.
if ((type_name != vm.names.Int32Array.as_string()) && (type_name != vm.names.BigInt64Array.as_string())) if ((type_name != vm.names.Int32Array.as_string()) && (type_name != vm.names.BigInt64Array.as_string()))
return vm.throw_completion<TypeError>(ErrorType::TypedArrayTypeIsNot, type_name, "Int32 or BigInt64"sv); return vm.throw_completion<TypeError>(ErrorType::TypedArrayTypeIsNot, type_name, "Int32 or BigInt64"sv);
} }
// 5. Else, // 4. Else,
else { else {
// a. Let type be TypedArrayElementType(typedArray). // a. Let type be TypedArrayElementType(typedArray).
// b. If IsUnclampedIntegerElementType(type) is false and IsBigIntElementType(type) is false, throw a TypeError exception. // b. If IsUnclampedIntegerElementType(type) is false and IsBigIntElementType(type) is false, throw a TypeError exception.
if (!typed_array.is_unclamped_integer_element_type() && !typed_array.is_bigint_element_type()) if (!typed_array.is_unclamped_integer_element_type() && !typed_array.is_bigint_element_type())
return vm.throw_completion<TypeError>(ErrorType::TypedArrayTypeIsNot, type_name, "an unclamped integer or BigInt"sv); return vm.throw_completion<TypeError>(ErrorType::TypedArrayTypeIsNot, type_name, "an unclamped integer or BigInt"sv);
} }
// 6. Return buffer. // 5. Return taRecord.
return buffer; return typed_array_record;
} }
// 25.4.2.2 ValidateAtomicAccess ( typedArray, requestIndex ), https://tc39.es/ecma262/#sec-validateatomicaccess // 25.4.2.2 ValidateAtomicAccess ( taRecord, requestIndex ), https://tc39.es/ecma262/#sec-validateatomicaccess
static ThrowCompletionOr<size_t> validate_atomic_access(VM& vm, TypedArrayBase& typed_array, Value request_index) static ThrowCompletionOr<size_t> validate_atomic_access(VM& vm, TypedArrayWithBufferWitness const& typed_array_record, Value request_index)
{ {
// 1. Let length be typedArray.[[ArrayLength]]. // 1. Let length be TypedArrayLength(taRecord).
auto length = typed_array.array_length(); auto length = typed_array_length(typed_array_record);
// 2. Let accessIndex be ? ToIndex(requestIndex). // 2. Let accessIndex be ? ToIndex(requestIndex).
auto access_index = TRY(request_index.to_index(vm));
// 3. Assert: accessIndex ≥ 0. // 3. Assert: accessIndex ≥ 0.
auto access_index = TRY(request_index.to_index(vm));
// 4. If accessIndex ≥ length, throw a RangeError exception. // 4. If accessIndex ≥ length, throw a RangeError exception.
if (access_index >= length) if (access_index >= length)
return vm.throw_completion<RangeError>(ErrorType::IndexOutOfRange, access_index, typed_array.array_length()); return vm.throw_completion<RangeError>(ErrorType::IndexOutOfRange, access_index, length);
// 5. Let elementSize be TypedArrayElementSize(typedArray). // 5. Let typedArray be taRecord.[[Object]].
auto const& typed_array = *typed_array_record.object;
// 6. Let elementSize be TypedArrayElementSize(typedArray).
auto element_size = typed_array.element_size(); auto element_size = typed_array.element_size();
// 6. Let offset be typedArray.[[ByteOffset]]. // 7. Let offset be typedArray.[[ByteOffset]].
auto offset = typed_array.byte_offset(); auto offset = typed_array.byte_offset();
// 7. Return (accessIndex × elementSize) + offset. // 8. Return (accessIndex × elementSize) + offset.
return (access_index * element_size) + offset; return (access_index * element_size) + offset;
} }
// 25.4.3.3 ValidateAtomicAccessOnIntegerTypedArray ( typedArray, requestIndex [ , waitable ] ), https://tc39.es/ecma262/#sec-validateatomicaccessonintegertypedarray
static ThrowCompletionOr<size_t> validate_atomic_access_on_integer_typed_array(VM& vm, TypedArrayBase const& typed_array, Value request_index, bool waitable = false)
{
// 1. If waitable is not present, set waitable to false.
// 2. Let taRecord be ? ValidateIntegerTypedArray(typedArray, waitable).
auto typed_array_record = TRY(validate_integer_typed_array(vm, typed_array, waitable));
// 3. Return ? ValidateAtomicAccess(taRecord, requestIndex).
return TRY(validate_atomic_access(vm, typed_array_record, request_index));
}
// 25.4.3.4 RevalidateAtomicAccess ( typedArray, byteIndexInBuffer ), https://tc39.es/ecma262/#sec-revalidateatomicaccess
static ThrowCompletionOr<void> revalidate_atomic_access(VM& vm, TypedArrayBase const& typed_array, size_t byte_index_in_buffer)
{
// 1. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(typedArray, unordered).
auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, ArrayBuffer::Order::Unordered);
// 2. NOTE: Bounds checking is not a synchronizing operation when typedArray's backing buffer is a growable SharedArrayBuffer.
// 3. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
if (is_typed_array_out_of_bounds(typed_array_record))
return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
// 4. Assert: byteIndexInBuffer ≥ typedArray.[[ByteOffset]].
VERIFY(byte_index_in_buffer >= typed_array.byte_offset());
// 5. If byteIndexInBuffer ≥ taRecord.[[CachedBufferByteLength]], throw a RangeError exception.
if (byte_index_in_buffer >= typed_array_record.cached_buffer_byte_length.length())
return vm.throw_completion<RangeError>(ErrorType::IndexOutOfRange, byte_index_in_buffer, typed_array_record.cached_buffer_byte_length.length());
// 6. Return unused.
return {};
}
// 25.4.2.17 AtomicReadModifyWrite ( typedArray, index, value, op ), https://tc39.es/ecma262/#sec-atomicreadmodifywrite // 25.4.2.17 AtomicReadModifyWrite ( typedArray, index, value, op ), https://tc39.es/ecma262/#sec-atomicreadmodifywrite
static ThrowCompletionOr<Value> atomic_read_modify_write(VM& vm, TypedArrayBase& typed_array, Value index, Value value, ReadWriteModifyFunction operation) static ThrowCompletionOr<Value> atomic_read_modify_write(VM& vm, TypedArrayBase& typed_array, Value index, Value value, ReadWriteModifyFunction operation)
{ {
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray). // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
auto* buffer = TRY(validate_integer_typed_array(vm, typed_array)); auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, typed_array, index));
// 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
auto indexed_position = TRY(validate_atomic_access(vm, typed_array, index));
Value value_to_set; Value value_to_set;
// 3. If typedArray.[[ContentType]] is BigInt, let v be ? ToBigInt(value). // 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value).
if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt) if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt)
value_to_set = TRY(value.to_bigint(vm)); value_to_set = TRY(value.to_bigint(vm));
// 4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
else else
value_to_set = Value(TRY(value.to_integer_or_infinity(vm))); value_to_set = Value(TRY(value.to_integer_or_infinity(vm)));
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
if (buffer->is_detached()) TRY(revalidate_atomic_access(vm, typed_array, byte_index_in_buffer));
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 6. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could cause the buffer to become detached. // 5. Let buffer be typedArray.[[ViewedArrayBuffer]].
// 6. Let elementType be TypedArrayElementType(typedArray).
// 7. Let elementType be TypedArrayElementType(typedArray). // 7. Return GetModifySetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, op).
// 8. Return GetModifySetValueInBuffer(buffer, indexedPosition, elementType, v, op). return typed_array.get_modify_set_value_in_buffer(byte_index_in_buffer, value_to_set, move(operation));
return typed_array.get_modify_set_value_in_buffer(indexed_position, value_to_set, move(operation));
} }
enum class WaitMode { enum class WaitMode {
@ -120,17 +149,18 @@ enum class WaitMode {
// 25.4.3.14 DoWait ( mode, typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-dowait // 25.4.3.14 DoWait ( mode, typedArray, index, value, timeout ), https://tc39.es/ecma262/#sec-dowait
static ThrowCompletionOr<Value> do_wait(VM& vm, WaitMode mode, TypedArrayBase& typed_array, Value index_value, Value expected_value, Value timeout_value) static ThrowCompletionOr<Value> do_wait(VM& vm, WaitMode mode, TypedArrayBase& typed_array, Value index_value, Value expected_value, Value timeout_value)
{ {
// 1. Let iieoRecord be ? ValidateIntegerTypedArray(typedArray, true). // 1. Let taRecord be ? ValidateIntegerTypedArray(typedArray, true).
// 2. Let buffer be iieoRecord.[[Object]].[[ViewedArrayBuffer]]. auto typed_array_record = TRY(validate_integer_typed_array(vm, typed_array, true));
// FIXME: An IIEO record is a new structure from the resizable array buffer proposal. Use it when the proposal is implemented.
auto* buffer = TRY(validate_integer_typed_array(vm, typed_array, true)); // 2. Let buffer be taRecord.[[Object]].[[ViewedArrayBuffer]].
auto* buffer = typed_array_record.object->viewed_array_buffer();
// 3. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception. // 3. If IsSharedArrayBuffer(buffer) is false, throw a TypeError exception.
if (!buffer->is_shared_array_buffer()) if (!buffer->is_shared_array_buffer())
return vm.throw_completion<TypeError>(ErrorType::NotASharedArrayBuffer); return vm.throw_completion<TypeError>(ErrorType::NotASharedArrayBuffer);
// 4. Let i be ? ValidateAtomicAccess(iieoRecord, index). // 4. Let i be ? ValidateAtomicAccess(taRecord, index).
auto index = TRY(validate_atomic_access(vm, typed_array, index_value)); auto index = TRY(validate_atomic_access(vm, typed_array_record, index_value));
// 5. Let arrayTypeName be typedArray.[[TypedArrayName]]. // 5. Let arrayTypeName be typedArray.[[TypedArrayName]].
auto const& array_type_name = typed_array.element_name(); auto const& array_type_name = typed_array.element_name();
@ -254,80 +284,77 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::and_)
// 25.4.6 Atomics.compareExchange ( typedArray, index, expectedValue, replacementValue ), https://tc39.es/ecma262/#sec-atomics.compareexchange // 25.4.6 Atomics.compareExchange ( typedArray, index, expectedValue, replacementValue ), https://tc39.es/ecma262/#sec-atomics.compareexchange
template<typename T> template<typename T>
static ThrowCompletionOr<Value> atomic_compare_exchange_impl(VM& vm, TypedArrayBase& typed_array) static ThrowCompletionOr<Value> atomic_compare_exchange_impl(VM& vm, TypedArrayBase& typed_array, Value index, Value expected_value, Value replacement_value)
{ {
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray). // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
auto* buffer = TRY(validate_integer_typed_array(vm, typed_array)); auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, typed_array, index));
// 2. Let block be buffer.[[ArrayBufferData]]. // 2. Let buffer be typedArray.[[ViewedArrayBuffer]].
auto* buffer = typed_array.viewed_array_buffer();
// 3. Let block be buffer.[[ArrayBufferData]].
auto& block = buffer->buffer(); auto& block = buffer->buffer();
// 3. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
auto indexed_position = TRY(validate_atomic_access(vm, typed_array, vm.argument(1)));
Value expected; Value expected;
Value replacement; Value replacement;
// 4. If typedArray.[[ContentType]] is BigInt, then // 4. If typedArray.[[ContentType]] is bigint, then
if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt) { if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt) {
// a. Let expected be ? ToBigInt(expectedValue). // a. Let expected be ? ToBigInt(expectedValue).
expected = TRY(vm.argument(2).to_bigint(vm)); expected = TRY(expected_value.to_bigint(vm));
// b. Let replacement be ? ToBigInt(replacementValue). // b. Let replacement be ? ToBigInt(replacementValue).
replacement = TRY(vm.argument(3).to_bigint(vm)); replacement = TRY(replacement_value.to_bigint(vm));
} }
// 5. Else, // 5. Else,
else { else {
// a. Let expected be 𝔽(? ToIntegerOrInfinity(expectedValue)). // a. Let expected be 𝔽(? ToIntegerOrInfinity(expectedValue)).
expected = Value(TRY(vm.argument(2).to_integer_or_infinity(vm))); expected = Value(TRY(expected_value.to_integer_or_infinity(vm)));
// b. Let replacement be 𝔽(? ToIntegerOrInfinity(replacementValue)). // b. Let replacement be 𝔽(? ToIntegerOrInfinity(replacementValue)).
replacement = Value(TRY(vm.argument(3).to_integer_or_infinity(vm))); replacement = Value(TRY(replacement_value.to_integer_or_infinity(vm)));
} }
// 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 6. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
if (buffer->is_detached()) TRY(revalidate_atomic_access(vm, typed_array, byte_index_in_buffer));
return vm.template throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 7. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could cause the buffer to become detached. // 7. Let elementType be TypedArrayElementType(typedArray).
// 8. Let elementSize be TypedArrayElementSize(typedArray).
// 8. Let elementType be TypedArrayElementType(typedArray). // 9. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
// 9. Let elementSize be TypedArrayElementSize(typedArray). static constexpr bool is_little_endian = AK::HostIsLittleEndian;
// 10. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record. // 10. Let expectedBytes be NumericToRawBytes(elementType, expected, isLittleEndian).
constexpr bool is_little_endian = AK::HostIsLittleEndian;
// 11. Let expectedBytes be NumericToRawBytes(elementType, expected, isLittleEndian).
auto expected_bytes = MUST(ByteBuffer::create_uninitialized(sizeof(T))); auto expected_bytes = MUST(ByteBuffer::create_uninitialized(sizeof(T)));
numeric_to_raw_bytes<T>(vm, expected, is_little_endian, expected_bytes); numeric_to_raw_bytes<T>(vm, expected, is_little_endian, expected_bytes);
// 12. Let replacementBytes be NumericToRawBytes(elementType, replacement, isLittleEndian). // 11. Let replacementBytes be NumericToRawBytes(elementType, replacement, isLittleEndian).
auto replacement_bytes = MUST(ByteBuffer::create_uninitialized(sizeof(T))); auto replacement_bytes = MUST(ByteBuffer::create_uninitialized(sizeof(T)));
numeric_to_raw_bytes<T>(vm, replacement, is_little_endian, replacement_bytes); numeric_to_raw_bytes<T>(vm, replacement, is_little_endian, replacement_bytes);
// FIXME: Implement SharedArrayBuffer case. // FIXME: Implement SharedArrayBuffer case.
// 13. If IsSharedArrayBuffer(buffer) is true, then // 12. If IsSharedArrayBuffer(buffer) is true, then
// a-i. // a. Let rawBytesRead be AtomicCompareExchangeInSharedBlock(block, byteIndexInBuffer, elementSize, expectedBytes, replacementBytes).
// 14. Else, // 13. Else,
// a. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[indexedPosition]. // a. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[byteIndexInBuffer].
// FIXME: Propagate errors. // FIXME: Propagate errors.
auto raw_bytes_read = MUST(block.slice(indexed_position, sizeof(T))); auto raw_bytes_read = MUST(block.slice(byte_index_in_buffer, sizeof(T)));
// b. If ByteListEqual(rawBytesRead, expectedBytes) is true, then // b. If ByteListEqual(rawBytesRead, expectedBytes) is true, then
// i. Store the individual bytes of replacementBytes into block, starting at block[indexedPosition]. // i. Store the individual bytes of replacementBytes into block, starting at block[byteIndexInBuffer].
if constexpr (IsFloatingPoint<T>) { if constexpr (IsFloatingPoint<T>) {
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} else { } else {
using U = Conditional<IsSame<ClampedU8, T>, u8, T>; using U = Conditional<IsSame<ClampedU8, T>, u8, T>;
auto* v = reinterpret_cast<U*>(block.span().slice(indexed_position).data()); auto* v = reinterpret_cast<U*>(block.span().slice(byte_index_in_buffer).data());
auto* e = reinterpret_cast<U*>(expected_bytes.data()); auto* e = reinterpret_cast<U*>(expected_bytes.data());
auto* r = reinterpret_cast<U*>(replacement_bytes.data()); auto* r = reinterpret_cast<U*>(replacement_bytes.data());
(void)AK::atomic_compare_exchange_strong(v, *e, *r); (void)AK::atomic_compare_exchange_strong(v, *e, *r);
} }
// 15. Return RawBytesToNumeric(elementType, rawBytesRead, isLittleEndian). // 14. Return RawBytesToNumeric(elementType, rawBytesRead, isLittleEndian).
return raw_bytes_to_numeric<T>(vm, raw_bytes_read, is_little_endian); return raw_bytes_to_numeric<T>(vm, raw_bytes_read, is_little_endian);
} }
@ -335,10 +362,13 @@ static ThrowCompletionOr<Value> atomic_compare_exchange_impl(VM& vm, TypedArrayB
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::compare_exchange) JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::compare_exchange)
{ {
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0))); auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
auto index = vm.argument(1);
auto expected_value = vm.argument(2);
auto replacement_value = vm.argument(3);
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
if (is<ClassName>(typed_array)) \ if (is<ClassName>(typed_array)) \
return TRY(atomic_compare_exchange_impl<Type>(vm, *typed_array)); return TRY(atomic_compare_exchange_impl<Type>(vm, *typed_array, index, expected_value, replacement_value));
JS_ENUMERATE_TYPED_ARRAYS JS_ENUMERATE_TYPED_ARRAYS
#undef __JS_ENUMERATE #undef __JS_ENUMERATE
@ -379,22 +409,19 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::is_lock_free)
// 25.4.9 Atomics.load ( typedArray, index ), https://tc39.es/ecma262/#sec-atomics.load // 25.4.9 Atomics.load ( typedArray, index ), https://tc39.es/ecma262/#sec-atomics.load
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::load) JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::load)
{ {
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0))); auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
TRY(validate_integer_typed_array(vm, *typed_array)); auto index = vm.argument(1);
// 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index). // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
auto indexed_position = TRY(validate_atomic_access(vm, *typed_array, vm.argument(1))); auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, *typed_array, index));
// 3. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 2. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
if (typed_array->viewed_array_buffer()->is_detached()) TRY(revalidate_atomic_access(vm, *typed_array, byte_index_in_buffer));
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 4. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ValidateAtomicAccess on the preceding line can have arbitrary side effects, which could cause the buffer to become detached. // 3. Let buffer be typedArray.[[ViewedArrayBuffer]].
// 4. Let elementType be TypedArrayElementType(typedArray).
// 5. Let elementType be TypedArrayElementType(typedArray). // 5. Return GetValueFromBuffer(buffer, byteIndexInBuffer, elementType, true, seq-cst).
// 6. Return GetValueFromBuffer(buffer, indexedPosition, elementType, true, SeqCst). return typed_array->get_value_from_buffer(byte_index_in_buffer, ArrayBuffer::Order::SeqCst, true);
return typed_array->get_value_from_buffer(indexed_position, ArrayBuffer::Order::SeqCst, true);
} }
// 25.4.10 Atomics.or ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.or // 25.4.10 Atomics.or ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.or
@ -416,35 +443,30 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::or_)
// 25.4.11 Atomics.store ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.store // 25.4.11 Atomics.store ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.store
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::store) JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::store)
{ {
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0))); auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
TRY(validate_integer_typed_array(vm, *typed_array)); auto index = vm.argument(1);
// 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
auto indexed_position = TRY(validate_atomic_access(vm, *typed_array, vm.argument(1)));
auto value = vm.argument(2); auto value = vm.argument(2);
Value value_to_set;
// 3. If typedArray.[[ContentType]] is BigInt, let v be ? ToBigInt(value). // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, *typed_array, index));
// 2. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value).
if (typed_array->content_type() == TypedArrayBase::ContentType::BigInt) if (typed_array->content_type() == TypedArrayBase::ContentType::BigInt)
value_to_set = TRY(value.to_bigint(vm)); value = TRY(value.to_bigint(vm));
// 4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)). // 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
else else
value_to_set = Value(TRY(value.to_integer_or_infinity(vm))); value = Value(TRY(value.to_integer_or_infinity(vm)));
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
if (typed_array->viewed_array_buffer()->is_detached()) TRY(revalidate_atomic_access(vm, *typed_array, byte_index_in_buffer));
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 6. NOTE: The above check is not redundant with the check in ValidateIntegerTypedArray because the call to ToBigInt or ToIntegerOrInfinity on the preceding lines can have arbitrary side effects, which could cause the buffer to become detached. // 5. Let buffer be typedArray.[[ViewedArrayBuffer]].
// 6. Let elementType be TypedArrayElementType(typedArray).
// 7. Perform SetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, true, seq-cst).
typed_array->set_value_in_buffer(byte_index_in_buffer, value, ArrayBuffer::Order::SeqCst, true);
// 7. Let elementType be TypedArrayElementType(typedArray). // 8. Return v.
// 8. Perform SetValueInBuffer(buffer, indexedPosition, elementType, v, true, SeqCst). return value;
typed_array->set_value_in_buffer(indexed_position, value_to_set, ArrayBuffer::Order::SeqCst, true);
// 9. Return v.
return value_to_set;
} }
// 25.4.12 Atomics.sub ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.sub // 25.4.12 Atomics.sub ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.sub
@ -495,9 +517,7 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::notify)
auto count_value = vm.argument(2); auto count_value = vm.argument(2);
// 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index, true). // 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index, true).
// FIXME: ValidateAtomicAccessOnIntegerTypedArray is a new AO from the resizable array buffer proposal. Use it when the proposal is implemented. auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, *typed_array, index, true));
TRY(validate_integer_typed_array(vm, *typed_array, true));
auto byte_index_in_buffer = TRY(validate_atomic_access(vm, *typed_array, index));
// 2. If count is undefined, then // 2. If count is undefined, then
double count = 0.0; double count = 0.0;

View file

@ -28,25 +28,6 @@ ThrowCompletionOr<TypedArrayBase*> typed_array_from(VM& vm, Value typed_array_va
return static_cast<TypedArrayBase*>(this_object.ptr()); return static_cast<TypedArrayBase*>(this_object.ptr());
} }
// 23.2.4.4 ValidateTypedArray ( O ), https://tc39.es/ecma262/#sec-validatetypedarray
ThrowCompletionOr<void> validate_typed_array(VM& vm, TypedArrayBase& typed_array)
{
// 1. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
if (!typed_array.is_typed_array())
return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "TypedArray");
// 2. Assert: O has a [[ViewedArrayBuffer]] internal slot.
// 3. Let buffer be O.[[ViewedArrayBuffer]].
auto* buffer = typed_array.viewed_array_buffer();
// 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if (buffer->is_detached())
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
return {};
}
// 22.2.5.1.3 InitializeTypedArrayFromArrayBuffer, https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer // 22.2.5.1.3 InitializeTypedArrayFromArrayBuffer, https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer
static ThrowCompletionOr<void> initialize_typed_array_from_array_buffer(VM& vm, TypedArrayBase& typed_array, ArrayBuffer& array_buffer, Value byte_offset, Value length) static ThrowCompletionOr<void> initialize_typed_array_from_array_buffer(VM& vm, TypedArrayBase& typed_array, ArrayBuffer& array_buffer, Value byte_offset, Value length)
{ {
@ -60,101 +41,122 @@ static ThrowCompletionOr<void> initialize_typed_array_from_array_buffer(VM& vm,
if (offset % element_size != 0) if (offset % element_size != 0)
return vm.throw_completion<RangeError>(ErrorType::TypedArrayInvalidByteOffset, typed_array.class_name(), element_size, offset); return vm.throw_completion<RangeError>(ErrorType::TypedArrayInvalidByteOffset, typed_array.class_name(), element_size, offset);
size_t new_length { 0 }; // 4. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer).
auto buffer_is_fixed_length = array_buffer.is_fixed_length();
// 4. If length is not undefined, then size_t new_length { 0 };
// 5. If length is not undefined, then
if (!length.is_undefined()) { if (!length.is_undefined()) {
// a. Let newLength be ? ToIndex(length). // a. Let newLength be ? ToIndex(length).
new_length = TRY(length.to_index(vm)); new_length = TRY(length.to_index(vm));
} }
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 6. 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);
// 6. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. // 7. 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);
Checked<size_t> new_byte_length; // 8. If length is undefined and bufferIsFixedLength is false, then
if (length.is_undefined() && !buffer_is_fixed_length) {
// 7. If length is undefined, then // a. If offset > bufferByteLength, throw a RangeError exception.
if (length.is_undefined()) {
// a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception.
if (buffer_byte_length % element_size != 0)
return vm.throw_completion<RangeError>(ErrorType::TypedArrayInvalidBufferLength, typed_array.class_name(), element_size, buffer_byte_length);
// b. Let newByteLength be bufferByteLength - offset.
// c. If newByteLength < 0, throw a RangeError exception.
if (offset > buffer_byte_length) if (offset > buffer_byte_length)
return vm.throw_completion<RangeError>(ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length); return vm.throw_completion<RangeError>(ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length);
new_byte_length = buffer_byte_length;
new_byte_length -= offset; // b. Set O.[[ByteLength]] to auto.
typed_array.set_byte_length(ByteLength::auto_());
// c. Set O.[[ArrayLength]] to auto.
typed_array.set_array_length(ByteLength::auto_());
} }
// 8. Else, // 9. Else,
else { else {
// a. Let newByteLength be newLength × elementSize. Checked<u32> new_byte_length;
new_byte_length = new_length;
new_byte_length *= element_size;
// b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. // a. If length is undefined, then
Checked<size_t> new_byte_end = new_byte_length; if (length.is_undefined()) {
new_byte_end += offset; // i. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError exception.
if (modulo(buffer_byte_length, element_size) != 0)
return vm.throw_completion<RangeError>(ErrorType::TypedArrayInvalidBufferLength, typed_array.class_name(), element_size, buffer_byte_length);
if (new_byte_end.has_overflow()) // ii. Let newByteLength be bufferByteLength - offset.
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "typed array"); new_byte_length = buffer_byte_length;
new_byte_length -= offset;
if (new_byte_end.value() > buffer_byte_length) // iii. If newByteLength < 0, throw a RangeError exception.
return vm.throw_completion<RangeError>(ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, new_byte_end.value(), buffer_byte_length); if (new_byte_length.has_overflow())
return vm.throw_completion<RangeError>(ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length);
}
// b. Else,
else {
// i. Let newByteLength be newLength × elementSize.
new_byte_length = new_length;
new_byte_length *= element_size;
// ii. If offset + newByteLength > bufferByteLength, throw a RangeError exception.
Checked<u32> new_byte_end = offset;
new_byte_end += new_byte_length;
if (new_byte_end.has_overflow())
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "typed array");
if (new_byte_end.value() > buffer_byte_length)
return vm.throw_completion<RangeError>(ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, new_byte_end.value(), buffer_byte_length);
}
// c. Set O.[[ByteLength]] to newByteLength.
typed_array.set_byte_length(new_byte_length.value());
// d. Set O.[[ArrayLength]] to newByteLength / elementSize.
typed_array.set_array_length(new_byte_length.value() / element_size);
} }
if (new_byte_length.has_overflow()) // 10. Set O.[[ViewedArrayBuffer]] to buffer.
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "typed array");
// 9. Set O.[[ViewedArrayBuffer]] to buffer.
typed_array.set_viewed_array_buffer(&array_buffer); typed_array.set_viewed_array_buffer(&array_buffer);
// 10. Set O.[[ByteLength]] to newByteLength.
typed_array.set_byte_length(new_byte_length.value());
// 11. Set O.[[ByteOffset]] to offset. // 11. Set O.[[ByteOffset]] to offset.
typed_array.set_byte_offset(offset); typed_array.set_byte_offset(offset);
// 12. Set O.[[ArrayLength]] to newByteLength / elementSize. // 12. Return unused.
typed_array.set_array_length(new_byte_length.value() / element_size);
// 13. Return unused.
return {}; return {};
} }
// 23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray ), https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray // 23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray ), https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray
template<typename T> template<typename T>
static ThrowCompletionOr<void> initialize_typed_array_from_typed_array(VM& vm, TypedArray<T>& dest_array, TypedArrayBase& src_array) static ThrowCompletionOr<void> initialize_typed_array_from_typed_array(VM& vm, TypedArray<T>& typed_array, TypedArrayBase& source_array)
{ {
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
// 1. Let srcData be srcArray.[[ViewedArrayBuffer]]. // 1. Let srcData be srcArray.[[ViewedArrayBuffer]].
auto* src_data = src_array.viewed_array_buffer(); auto* source_data = source_array.viewed_array_buffer();
VERIFY(src_data); VERIFY(source_data);
// 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. // 2. Let elementType be TypedArrayElementType(O).
if (src_data->is_detached()) auto const& element_type = typed_array.element_name();
return vm.template throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 3. Let elementType be TypedArrayElementType(O). // 3. Let elementSize be TypedArrayElementSize(O).
// 4. Let elementSize be TypedArrayElementSize(O). auto element_size = typed_array.element_size();
auto element_size = dest_array.element_size();
// 5. Let srcType be TypedArrayElementType(srcArray). // 4. Let srcType be TypedArrayElementType(srcArray).
// 6. Let srcElementSize be TypedArrayElementSize(srcArray). auto const& source_type = source_array.element_name();
auto src_element_size = src_array.element_size();
// 7. Let srcByteOffset be srcArray.[[ByteOffset]]. // 5. Let srcElementSize be TypedArrayElementSize(srcArray).
auto src_byte_offset = src_array.byte_offset(); auto source_element_size = source_array.element_size();
// 8. Let elementLength be srcArray.[[ArrayLength]]. // 6. Let srcByteOffset be srcArray.[[ByteOffset]].
auto element_length = src_array.array_length(); auto source_byte_offset = source_array.byte_offset();
// 9. Let byteLength be elementSize × elementLength. // 7. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(srcArray, seq-cst).
auto source_record = make_typed_array_with_buffer_witness_record(source_array, ArrayBuffer::Order::SeqCst);
// 8. If IsTypedArrayOutOfBounds(srcRecord) is true, throw a TypeError exception.
if (is_typed_array_out_of_bounds(source_record))
return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
// 9. Let elementLength be TypedArrayLength(srcRecord).
auto element_length = typed_array_length(source_record);
// 10. Let byteLength be elementSize × elementLength.
Checked<size_t> byte_length = element_size; Checked<size_t> byte_length = element_size;
byte_length *= element_length; byte_length *= element_length;
if (byte_length.has_overflow()) if (byte_length.has_overflow())
@ -162,41 +164,37 @@ static ThrowCompletionOr<void> initialize_typed_array_from_typed_array(VM& vm, T
ArrayBuffer* data = nullptr; ArrayBuffer* data = nullptr;
// 10. If elementType is the same as srcType, then // 11. If elementType is srcType, then
if (dest_array.element_name() == src_array.element_name()) { if (element_type == source_type) {
// a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength). // a. Let data be ? CloneArrayBuffer(srcData, srcByteOffset, byteLength).
data = TRY(clone_array_buffer(vm, *src_data, src_byte_offset, byte_length.value())); data = TRY(clone_array_buffer(vm, *source_data, source_byte_offset, byte_length.value()));
} }
// 11. Else, // 12. Else,
else { else {
// a. Let data be ? AllocateArrayBuffer(bufferConstructor, byteLength). // a. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength).
data = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), byte_length.value())); data = TRY(allocate_array_buffer(vm, realm.intrinsics().array_buffer_constructor(), byte_length.value()));
// b. If IsDetachedBuffer(srcData) is true, throw a TypeError exception. // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception.
if (src_data->is_detached()) if (source_array.content_type() != typed_array.content_type())
return vm.template throw_completion<TypeError>(ErrorType::DetachedArrayBuffer); return vm.template throw_completion<TypeError>(ErrorType::TypedArrayContentTypeMismatch, typed_array.class_name(), source_array.class_name());
// c. If srcArray.[[ContentType]] ≠ O.[[ContentType]], throw a TypeError exception. // c. Let srcByteIndex be srcByteOffset.
if (src_array.content_type() != dest_array.content_type()) u64 source_byte_index = source_byte_offset;
return vm.template throw_completion<TypeError>(ErrorType::TypedArrayContentTypeMismatch, dest_array.class_name(), src_array.class_name());
// d. Let srcByteIndex be srcByteOffset. // d. Let targetByteIndex be 0.
u64 src_byte_index = src_byte_offset;
// e. Let targetByteIndex be 0.
u64 target_byte_index = 0; u64 target_byte_index = 0;
// f. Let count be elementLength. // e. Let count be elementLength.
// g. Repeat, while count > 0, // f. Repeat, while count > 0,
for (u32 i = 0; i < element_length; ++i) { for (u32 i = 0; i < element_length; ++i) {
// i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, unordered).
auto value = src_array.get_value_from_buffer(src_byte_index, ArrayBuffer::Order::Unordered); auto value = source_array.get_value_from_buffer(source_byte_index, ArrayBuffer::Order::Unordered);
// ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, unordered).
data->template set_value<T>(target_byte_index, value, true, ArrayBuffer::Order::Unordered); data->template set_value<T>(target_byte_index, value, true, ArrayBuffer::Order::Unordered);
// iii. Set srcByteIndex to srcByteIndex + srcElementSize. // iii. Set srcByteIndex to srcByteIndex + srcElementSize.
src_byte_index += src_element_size; source_byte_index += source_element_size;
// iv. Set targetByteIndex to targetByteIndex + elementSize. // iv. Set targetByteIndex to targetByteIndex + elementSize.
target_byte_index += element_size; target_byte_index += element_size;
@ -205,19 +203,19 @@ static ThrowCompletionOr<void> initialize_typed_array_from_typed_array(VM& vm, T
} }
} }
// 12. Set O.[[ViewedArrayBuffer]] to data. // 13. Set O.[[ViewedArrayBuffer]] to data.
dest_array.set_viewed_array_buffer(data); typed_array.set_viewed_array_buffer(data);
// 13. Set O.[[ByteLength]] to byteLength. // 14. Set O.[[ByteLength]] to byteLength.
dest_array.set_byte_length(byte_length.value()); typed_array.set_byte_length(byte_length.value());
// 14. Set O.[[ByteOffset]] to 0. // 15. Set O.[[ByteOffset]] to 0.
dest_array.set_byte_offset(0); typed_array.set_byte_offset(0);
// 15. Set O.[[ArrayLength]] to elementLength. // 16. Set O.[[ArrayLength]] to elementLength.
dest_array.set_array_length(element_length); typed_array.set_array_length(element_length);
// 16. Return unused. // 17. Return unused.
return {}; return {};
} }
@ -318,27 +316,32 @@ static ThrowCompletionOr<void> initialize_typed_array_from_list(VM& vm, TypedArr
// 23.2.4.2 TypedArrayCreate ( constructor, argumentList ), https://tc39.es/ecma262/#typedarray-create // 23.2.4.2 TypedArrayCreate ( constructor, argumentList ), https://tc39.es/ecma262/#typedarray-create
ThrowCompletionOr<TypedArrayBase*> typed_array_create(VM& vm, FunctionObject& constructor, MarkedVector<Value> arguments) ThrowCompletionOr<TypedArrayBase*> typed_array_create(VM& vm, FunctionObject& constructor, MarkedVector<Value> arguments)
{ {
Optional<Value> first_argument; Optional<double> first_argument;
if (!arguments.is_empty()) if (arguments.size() == 1 && arguments[0].is_number())
first_argument = arguments[0]; first_argument = arguments[0].as_double();
// 1. Let newTypedArray be ? Construct(constructor, argumentList). // 1. Let newTypedArray be ? Construct(constructor, argumentList).
auto new_typed_array = TRY(construct(vm, constructor, arguments.span())); auto new_typed_array = TRY(construct(vm, constructor, arguments.span()));
// 2. Perform ? ValidateTypedArray(newTypedArray). // 2. Let taRecord be ? ValidateTypedArray(newTypedArray, seq-cst).
if (!new_typed_array->is_typed_array()) auto typed_array_record = TRY(validate_typed_array(vm, *new_typed_array, ArrayBuffer::Order::SeqCst));
return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "TypedArray");
auto& typed_array = *static_cast<TypedArrayBase*>(new_typed_array.ptr());
TRY(validate_typed_array(vm, typed_array));
// 3. If argumentList is a List of a single Number, then // 3. If the number of elements in argumentList is 1 and argumentList[0] is a Number, then
if (first_argument.has_value() && first_argument->is_number()) { if (first_argument.has_value()) {
// a. If newTypedArray.[[ArrayLength]] < (argumentList[0]), throw a TypeError exception. // a. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
if (typed_array.array_length() < first_argument->as_double()) if (is_typed_array_out_of_bounds(typed_array_record))
return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
// b. Let length be TypedArrayLength(taRecord).
auto length = typed_array_length(typed_array_record);
// c. If length < (argumentList[0]), throw a TypeError exception.
if (length < *first_argument)
return vm.throw_completion<TypeError>(ErrorType::InvalidLength, "typed array"); return vm.throw_completion<TypeError>(ErrorType::InvalidLength, "typed array");
} }
// 4. Return newTypedArray. // 4. Return newTypedArray.
return &typed_array; return static_cast<TypedArrayBase*>(new_typed_array.ptr());
} }
// 23.2.4.3 TypedArrayCreateSameType ( exemplar, argumentList ), https://tc39.es/ecma262/#sec-typedarray-create-same-type // 23.2.4.3 TypedArrayCreateSameType ( exemplar, argumentList ), https://tc39.es/ecma262/#sec-typedarray-create-same-type
@ -358,6 +361,27 @@ ThrowCompletionOr<TypedArrayBase*> typed_array_create_same_type(VM& vm, TypedArr
return result; return result;
} }
// 23.2.4.4 ValidateTypedArray ( O ), https://tc39.es/ecma262/#sec-validatetypedarray
ThrowCompletionOr<TypedArrayWithBufferWitness> validate_typed_array(VM& vm, Object const& object, ArrayBuffer::Order order)
{
// 1. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
if (!object.is_typed_array())
return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "TypedArray");
// 2. Assert: O has a [[ViewedArrayBuffer]] internal slot.
auto const& typed_array = static_cast<TypedArrayBase const&>(object);
// 3. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, order).
auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, order);
// 4. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception.
if (is_typed_array_out_of_bounds(typed_array_record))
return vm.throw_completion<TypeError>(ErrorType::BufferOutOfBounds, "TypedArray"sv);
// 5. Return taRecord.
return typed_array_record;
}
// 23.2.4.7 CompareTypedArrayElements ( x, y, comparefn ), https://tc39.es/ecma262/#sec-typedarray-create-same-type // 23.2.4.7 CompareTypedArrayElements ( x, y, comparefn ), https://tc39.es/ecma262/#sec-typedarray-create-same-type
ThrowCompletionOr<double> compare_typed_array_elements(VM& vm, Value x, Value y, FunctionObject* comparefn) ThrowCompletionOr<double> compare_typed_array_elements(VM& vm, Value x, Value y, FunctionObject* comparefn)
{ {
@ -563,4 +587,162 @@ void TypedArrayBase::visit_edges(Visitor& visitor)
JS_ENUMERATE_TYPED_ARRAYS JS_ENUMERATE_TYPED_ARRAYS
#undef __JS_ENUMERATE #undef __JS_ENUMERATE
// 10.4.5.9 MakeTypedArrayWithBufferWitnessRecord ( obj, order ), https://tc39.es/ecma262/#sec-maketypedarraywithbufferwitnessrecord
TypedArrayWithBufferWitness make_typed_array_with_buffer_witness_record(TypedArrayBase const& typed_array, ArrayBuffer::Order order)
{
// 1. Let buffer be obj.[[ViewedArrayBuffer]].
auto* buffer = typed_array.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 TypedArray With Buffer Witness Record { [[Object]]: obj, [[CachedBufferByteLength]]: byteLength }.
return { .object = typed_array, .cached_buffer_byte_length = move(byte_length) };
}
// 10.4.5.11 TypedArrayByteLength ( taRecord ), https://tc39.es/ecma262/#sec-typedarraybytelength
u32 typed_array_byte_length(TypedArrayWithBufferWitness const& typed_array_record)
{
// 1. If IsTypedArrayOutOfBounds(taRecord) is true, return 0.
if (is_typed_array_out_of_bounds(typed_array_record))
return 0;
// 2. Let length be TypedArrayLength(taRecord).
auto length = typed_array_length(typed_array_record);
// 3. If length = 0, return 0.
if (length == 0)
return 0;
// 4. Let O be taRecord.[[Object]].
auto object = typed_array_record.object;
// 5. If O.[[ByteLength]] is not auto, return O.[[ByteLength]].
if (!object->byte_length().is_auto())
return object->byte_length().length();
// 6. Let elementSize be TypedArrayElementSize(O).
auto element_size = object->element_size();
// 7. Return length × elementSize.
return length * element_size;
}
// 10.4.5.12 TypedArrayLength ( taRecord ), https://tc39.es/ecma262/#sec-typedarraylength
u32 typed_array_length(TypedArrayWithBufferWitness const& typed_array_record)
{
// 1. Assert: IsTypedArrayOutOfBounds(taRecord) is false.
VERIFY(!is_typed_array_out_of_bounds(typed_array_record));
// 2. Let O be taRecord.[[Object]].
auto object = typed_array_record.object;
// 3. If O.[[ArrayLength]] is not auto, return O.[[ArrayLength]].
if (!object->array_length().is_auto())
return object->array_length().length();
// 4. Assert: IsFixedLengthArrayBuffer(O.[[ViewedArrayBuffer]]) is false.
VERIFY(!object->viewed_array_buffer()->is_fixed_length());
// 5. Let byteOffset be O.[[ByteOffset]].
auto byte_offset = object->byte_offset();
// 6. Let elementSize be TypedArrayElementSize(O).
auto element_size = object->element_size();
// 7. Let byteLength be taRecord.[[CachedBufferByteLength]].
auto const& byte_length = typed_array_record.cached_buffer_byte_length;
// 8. Assert: byteLength is not detached.
VERIFY(!byte_length.is_detached());
// 9. Return floor((byteLength - byteOffset) / elementSize).
return (byte_length.length() - byte_offset) / element_size;
}
// 10.4.5.13 IsTypedArrayOutOfBounds ( taRecord ), https://tc39.es/ecma262/#sec-istypedarrayoutofbounds
bool is_typed_array_out_of_bounds(TypedArrayWithBufferWitness const& typed_array_record)
{
// 1. Let O be taRecord.[[Object]].
auto object = typed_array_record.object;
// 2. Let bufferByteLength be taRecord.[[CachedBufferByteLength]].
auto const& buffer_byte_length = typed_array_record.cached_buffer_byte_length;
// 3. Assert: IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true if and only if bufferByteLength is detached.
VERIFY(object->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 O.[[ByteOffset]].
auto byte_offset_start = object->byte_offset();
u32 byte_offset_end = 0;
// 6. If O.[[ArrayLength]] is auto, then
if (object->array_length().is_auto()) {
// a. Let byteOffsetEnd be bufferByteLength.
byte_offset_end = buffer_byte_length.length();
}
// 7. Else,
else {
// a. Let elementSize be TypedArrayElementSize(O).
auto element_size = object->element_size();
// b. Let byteOffsetEnd be byteOffsetStart + O.[[ArrayLength]] × elementSize.
byte_offset_end = byte_offset_start + object->array_length().length() * element_size;
}
// 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 TypedArrays are not considered out-of-bounds.
// 10. Return false.
return false;
}
// 10.4.5.14 IsValidIntegerIndex ( O, index ), https://tc39.es/ecma262/#sec-isvalidintegerindex
bool is_valid_integer_index(TypedArrayBase const& typed_array, CanonicalIndex property_index)
{
// 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false.
if (typed_array.viewed_array_buffer()->is_detached())
return false;
// 2. If IsIntegralNumber(index) is false, return false.
// 3. If index is -0𝔽, return false.
if (!property_index.is_index())
return false;
// 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, unordered).
auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, ArrayBuffer::Unordered);
// NOTE: Bounds checking is not a synchronizing operation when O's backing buffer is a growable SharedArrayBuffer.
// 6. If IsTypedArrayOutOfBounds(taRecord) is true, return false.
if (is_typed_array_out_of_bounds(typed_array_record))
return false;
// 7. Let length be TypedArrayLength(taRecord).
auto length = typed_array_length(typed_array_record);
// 8. If (index) < 0 or (index) ≥ length, return false.
if (property_index.as_index() >= length)
return false;
// 9. Return true.
return true;
}
} }

View file

@ -9,6 +9,7 @@
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/ArrayBuffer.h> #include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/ByteLength.h>
#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PropertyDescriptor.h> #include <LibJS/Runtime/PropertyDescriptor.h>
@ -18,11 +19,6 @@
namespace JS { namespace JS {
class TypedArrayBase;
ThrowCompletionOr<TypedArrayBase*> typed_array_from(VM&, Value);
ThrowCompletionOr<void> validate_typed_array(VM&, TypedArrayBase&);
class TypedArrayBase : public Object { class TypedArrayBase : public Object {
JS_OBJECT(TypedArrayBase, Object); JS_OBJECT(TypedArrayBase, Object);
@ -41,15 +37,15 @@ public:
using IntrinsicConstructor = NonnullGCPtr<TypedArrayConstructor> (Intrinsics::*)(); using IntrinsicConstructor = NonnullGCPtr<TypedArrayConstructor> (Intrinsics::*)();
u32 array_length() const { return m_array_length; } ByteLength const& array_length() const { return m_array_length; }
u32 byte_length() const { return m_byte_length; } ByteLength const& byte_length() const { return m_byte_length; }
u32 byte_offset() const { return m_byte_offset; } u32 byte_offset() const { return m_byte_offset; }
ContentType content_type() const { return m_content_type; } ContentType content_type() const { return m_content_type; }
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; } ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
IntrinsicConstructor intrinsic_constructor() const { return m_intrinsic_constructor; } IntrinsicConstructor intrinsic_constructor() const { return m_intrinsic_constructor; }
void set_array_length(u32 length) { m_array_length = length; } void set_array_length(ByteLength length) { m_array_length = move(length); }
void set_byte_length(u32 length) { m_byte_length = length; } void set_byte_length(ByteLength length) { m_byte_length = move(length); }
void set_byte_offset(u32 offset) { m_byte_offset = offset; } void set_byte_offset(u32 offset) { m_byte_offset = offset; }
void set_viewed_array_buffer(ArrayBuffer* array_buffer) { m_viewed_array_buffer = array_buffer; } void set_viewed_array_buffer(ArrayBuffer* array_buffer) { m_viewed_array_buffer = array_buffer; }
@ -78,8 +74,8 @@ protected:
set_is_typed_array(); set_is_typed_array();
} }
u32 m_array_length { 0 }; ByteLength m_array_length { 0 };
u32 m_byte_length { 0 }; ByteLength m_byte_length { 0 };
u32 m_byte_offset { 0 }; u32 m_byte_offset { 0 };
ContentType m_content_type { ContentType::Number }; ContentType m_content_type { ContentType::Number };
Kind m_kind {}; Kind m_kind {};
@ -90,25 +86,17 @@ private:
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;
}; };
// 10.4.5.14 IsValidIntegerIndex ( O, index ), https://tc39.es/ecma262/#sec-isvalidintegerindex // 10.4.5.8 TypedArray With Buffer Witness Records, https://tc39.es/ecma262/#sec-typedarray-with-buffer-witness-records
inline bool is_valid_integer_index(TypedArrayBase const& typed_array, CanonicalIndex property_index) struct TypedArrayWithBufferWitness {
{ NonnullGCPtr<TypedArrayBase const> object; // [[Object]]
// 1. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, return false. ByteLength cached_buffer_byte_length; // [[CachedBufferByteLength]]
if (typed_array.viewed_array_buffer()->is_detached()) };
return false;
// 2. If IsIntegralNumber(index) is false, return false. TypedArrayWithBufferWitness make_typed_array_with_buffer_witness_record(TypedArrayBase const&, ArrayBuffer::Order);
// 3. If index is -0𝔽, return false. u32 typed_array_byte_length(TypedArrayWithBufferWitness const&);
if (!property_index.is_index()) u32 typed_array_length(TypedArrayWithBufferWitness const&);
return false; bool is_typed_array_out_of_bounds(TypedArrayWithBufferWitness const&);
bool is_valid_integer_index(TypedArrayBase const&, CanonicalIndex);
// 4. If (index) < 0 or (index) ≥ O.[[ArrayLength]], return false.
if (property_index.as_index() >= typed_array.array_length())
return false;
// 5. Return true.
return true;
}
// 10.4.5.15 TypedArrayGetElement ( O, index ), https://tc39.es/ecma262/#sec-typedarraygetelement // 10.4.5.15 TypedArrayGetElement ( O, index ), https://tc39.es/ecma262/#sec-typedarraygetelement
template<typename T> template<typename T>
@ -394,45 +382,68 @@ public:
{ {
auto& vm = this->vm(); auto& vm = this->vm();
// 1. Let keys be a new empty List. // 1. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
auto typed_array_record = make_typed_array_with_buffer_witness_record(*this, ArrayBuffer::Order::SeqCst);
// 2. Let keys be a new empty List.
auto keys = MarkedVector<Value> { heap() }; auto keys = MarkedVector<Value> { heap() };
// 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then // 3. If IsTypedArrayOutOfBounds(taRecord) is false, then
if (!m_viewed_array_buffer->is_detached()) { if (!is_typed_array_out_of_bounds(typed_array_record)) {
// a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do // a. Let length be TypedArrayLength(taRecord).
for (size_t i = 0; i < m_array_length; ++i) { auto length = typed_array_length(typed_array_record);
// i. Add ! ToString(𝔽(i)) as the last element of keys.
keys.append(PrimitiveString::create(vm, ByteString::number(i))); // b. For each integer i such that 0 ≤ i < length, in ascending order, do
for (size_t i = 0; i < length; ++i) {
// i. Append ! ToString(𝔽(i)) to keys.
keys.append(PrimitiveString::create(vm, MUST(String::number(i))));
} }
} }
// 3. For each own property key P of O such that Type(P) is String and P is not an integer index, in ascending chronological order of property creation, do // 4. For each own property key P of O such that P is a String and P is not an integer index, in ascending chronological order of property creation, do
for (auto& it : shape().property_table()) { for (auto& it : shape().property_table()) {
if (it.key.is_string()) { if (it.key.is_string()) {
// a. Add P as the last element of keys. // a. Append P to keys.
keys.append(it.key.to_value(vm)); keys.append(it.key.to_value(vm));
} }
} }
// 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do // 5. For each own property key P of O such that P is a Symbol, in ascending chronological order of property creation, do
for (auto& it : shape().property_table()) { for (auto& it : shape().property_table()) {
if (it.key.is_symbol()) { if (it.key.is_symbol()) {
// a. Add P as the last element of keys. // a. Append P to keys.
keys.append(it.key.to_value(vm)); keys.append(it.key.to_value(vm));
} }
} }
// 5. Return keys. // 6. Return keys.
return { move(keys) }; return { move(keys) };
} }
ReadonlySpan<UnderlyingBufferDataType> data() const ReadonlySpan<UnderlyingBufferDataType> data() const
{ {
return { reinterpret_cast<UnderlyingBufferDataType const*>(m_viewed_array_buffer->buffer().data() + m_byte_offset), m_array_length }; auto typed_array_record = make_typed_array_with_buffer_witness_record(*this, ArrayBuffer::Order::SeqCst);
if (is_typed_array_out_of_bounds(typed_array_record)) {
// FIXME: Propagate this as an error?
return {};
}
auto length = typed_array_length(typed_array_record);
return { reinterpret_cast<UnderlyingBufferDataType const*>(m_viewed_array_buffer->buffer().data() + m_byte_offset), length };
} }
Span<UnderlyingBufferDataType> data() Span<UnderlyingBufferDataType> data()
{ {
return { reinterpret_cast<UnderlyingBufferDataType*>(m_viewed_array_buffer->buffer().data() + m_byte_offset), m_array_length }; auto typed_array_record = make_typed_array_with_buffer_witness_record(*this, ArrayBuffer::Order::SeqCst);
if (is_typed_array_out_of_bounds(typed_array_record)) {
// FIXME: Propagate this as an error?
return {};
}
auto length = typed_array_length(typed_array_record);
return { reinterpret_cast<UnderlyingBufferDataType*>(m_viewed_array_buffer->buffer().data() + m_byte_offset), length };
} }
virtual size_t element_size() const override { return sizeof(UnderlyingBufferDataType); } virtual size_t element_size() const override { return sizeof(UnderlyingBufferDataType); }
@ -466,8 +477,10 @@ protected:
} }
}; };
ThrowCompletionOr<TypedArrayBase*> typed_array_from(VM&, Value);
ThrowCompletionOr<TypedArrayBase*> typed_array_create(VM&, FunctionObject& constructor, MarkedVector<Value> arguments); ThrowCompletionOr<TypedArrayBase*> typed_array_create(VM&, FunctionObject& constructor, MarkedVector<Value> arguments);
ThrowCompletionOr<TypedArrayBase*> typed_array_create_same_type(VM&, TypedArrayBase const& exemplar, MarkedVector<Value> arguments); ThrowCompletionOr<TypedArrayBase*> typed_array_create_same_type(VM&, TypedArrayBase const& exemplar, MarkedVector<Value> arguments);
ThrowCompletionOr<TypedArrayWithBufferWitness> validate_typed_array(VM&, Object const&, ArrayBuffer::Order);
ThrowCompletionOr<double> compare_typed_array_elements(VM&, Value x, Value y, FunctionObject* comparefn); ThrowCompletionOr<double> compare_typed_array_elements(VM&, Value x, Value y, FunctionObject* comparefn);
#define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ #define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,10 @@ describe("errors", () => {
detachArrayBuffer(typedArray.buffer); detachArrayBuffer(typedArray.buffer);
Atomics.notify(typedArray, 0, 0); Atomics.notify(typedArray, 0, 0);
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached"); }).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
}); });
test("invalid TypedArray type", () => { test("invalid TypedArray type", () => {

View file

@ -11,7 +11,10 @@ describe("errors", () => {
detachArrayBuffer(typedArray.buffer); detachArrayBuffer(typedArray.buffer);
Atomics.wait(typedArray, 0, 0, 0); Atomics.wait(typedArray, 0, 0, 0);
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached"); }).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
}); });
test("invalid TypedArray type", () => { test("invalid TypedArray type", () => {

View file

@ -11,7 +11,10 @@ describe("errors", () => {
detachArrayBuffer(typedArray.buffer); detachArrayBuffer(typedArray.buffer);
Atomics.waitAsync(typedArray, 0, 0, 0); Atomics.waitAsync(typedArray, 0, 0, 0);
}).toThrowWithMessage(TypeError, "ArrayBuffer is detached"); }).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
}); });
test("invalid TypedArray type", () => { test("invalid TypedArray type", () => {

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.at(0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.at).toHaveLength(1); expect(T.prototype.at).toHaveLength(1);

View file

@ -24,3 +24,17 @@ test("basic functionality", () => {
expect(typedArray.byteLength).toBe(T.expected); expect(typedArray.byteLength).toBe(T.expected);
}); });
}); });
test("resizable ArrayBuffer", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.array.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.array.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T.array(arrayBuffer, T.array.BYTES_PER_ELEMENT, 1);
expect(typedArray.byteLength).toBe(T.array.BYTES_PER_ELEMENT);
arrayBuffer.resize(T.array.BYTES_PER_ELEMENT);
expect(typedArray.byteLength).toBe(0);
});
});

View file

@ -33,3 +33,17 @@ test("basic functionality", () => {
expect(arrayFromOffset[1]).toBe(!isBigIntArray ? 3 : 3n); expect(arrayFromOffset[1]).toBe(!isBigIntArray ? 3 : 3n);
}); });
}); });
test("resizable ArrayBuffer", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
expect(typedArray.byteOffset).toBe(T.BYTES_PER_ELEMENT);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(typedArray.byteOffset).toBe(0);
});
});

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.copyWithin(0, 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 2", () => { test("length is 2", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.copyWithin).toHaveLength(2); expect(T.prototype.copyWithin).toHaveLength(2);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.entries();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 0", () => { test("length is 0", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.entries).toHaveLength(0); expect(T.prototype.entries).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.every(value => value === 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 1", () => { test("length is 1", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.every).toHaveLength(1); expect(T.prototype.every).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.fill(0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.fill).toHaveLength(1); expect(T.prototype.fill).toHaveLength(1);

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().filter(undefined); new T().filter(undefined);
}).toThrowWithMessage(TypeError, "undefined is not a function"); }).toThrowWithMessage(TypeError, "undefined is not a function");
}); });
test(`ArrayBuffer out of bounds (${T.name})`, () => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.filter(value => value === 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
} }
TYPED_ARRAYS.forEach(T => argumentErrorTests(T)); TYPED_ARRAYS.forEach(T => argumentErrorTests(T));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().find(undefined); new T().find(undefined);
}).toThrowWithMessage(TypeError, "undefined is not a function"); }).toThrowWithMessage(TypeError, "undefined is not a function");
}); });
test(`ArrayBuffer out of bounds (${T.name})`, () => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.find(value => value === 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
} }
TYPED_ARRAYS.forEach(T => errorTests(T)); TYPED_ARRAYS.forEach(T => errorTests(T));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().findIndex(undefined); new T().findIndex(undefined);
}).toThrowWithMessage(TypeError, "undefined is not a function"); }).toThrowWithMessage(TypeError, "undefined is not a function");
}); });
test(`ArrayBuffer out of bounds (${T.name})`, () => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.findIndex(value => value === 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
} }
TYPED_ARRAYS.forEach(T => errorTests(T)); TYPED_ARRAYS.forEach(T => errorTests(T));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().findLast(undefined); new T().findLast(undefined);
}).toThrowWithMessage(TypeError, "undefined is not a function"); }).toThrowWithMessage(TypeError, "undefined is not a function");
}); });
test(`ArrayBuffer out of bounds (${T.name})`, () => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.findLast(value => value === 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
} }
TYPED_ARRAYS.forEach(T => errorTests(T)); TYPED_ARRAYS.forEach(T => errorTests(T));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().findLastIndex(undefined); new T().findLastIndex(undefined);
}).toThrowWithMessage(TypeError, "undefined is not a function"); }).toThrowWithMessage(TypeError, "undefined is not a function");
}); });
test(`ArrayBuffer out of bounds (${T.name})`, () => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.findLastIndex(value => value === 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
} }
TYPED_ARRAYS.forEach(T => errorTests(T)); TYPED_ARRAYS.forEach(T => errorTests(T));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().forEach(undefined); new T().forEach(undefined);
}).toThrowWithMessage(TypeError, "undefined is not a function"); }).toThrowWithMessage(TypeError, "undefined is not a function");
}); });
test(`ArrayBuffer out of bounds (${T.name})`, () => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.forEach(() => {});
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
} }
TYPED_ARRAYS.forEach(T => errorTests(T)); TYPED_ARRAYS.forEach(T => errorTests(T));

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.includes(0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.includes).toHaveLength(1); expect(T.prototype.includes).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.indexOf(0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.indexOf).toHaveLength(1); expect(T.prototype.indexOf).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.keys();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 0", () => { test("length is 0", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.keys).toHaveLength(0); expect(T.prototype.keys).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.lastIndexOf(0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.lastIndexOf).toHaveLength(1); expect(T.prototype.lastIndexOf).toHaveLength(1);

View file

@ -19,3 +19,17 @@ test("basic functionality", () => {
expect(typedArray.length).toBe(42); expect(typedArray.length).toBe(42);
}); });
}); });
test("resizable ArrayBuffer", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
expect(typedArray.length).toBe(1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(typedArray.length).toBe(0);
});
});

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().map(undefined); new T().map(undefined);
}).toThrowWithMessage(TypeError, "undefined is not a function"); }).toThrowWithMessage(TypeError, "undefined is not a function");
}); });
test(`ArrayBuffer out of bounds (${T.name})`, () => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.map(value => value);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
} }
TYPED_ARRAYS.forEach(T => argumentErrorTests(T)); TYPED_ARRAYS.forEach(T => argumentErrorTests(T));

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.reduce((accumulator, value) => accumulator + value);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.reduce).toHaveLength(1); expect(T.prototype.reduce).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.reduceRight((accumulator, value) => accumulator + value);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.reduceRight).toHaveLength(1); expect(T.prototype.reduceRight).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.reverse();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 0", () => { test("length is 0", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.reverse).toHaveLength(0); expect(T.prototype.reverse).toHaveLength(0);

View file

@ -15,6 +15,33 @@ const BIGINT_TYPED_ARRAYS = [
{ array: BigInt64Array, maxUnsignedInteger: 2n ** 63n - 1n }, { array: BigInt64Array, maxUnsignedInteger: 2n ** 63n - 1n },
]; ];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.array.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.array.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T.array(arrayBuffer, T.array.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.array.BYTES_PER_ELEMENT);
expect(() => {
typedArray.set([0]);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
expect(() => {
typedArray.set(new T.array());
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
// FIXME: Write out a full test suite for this function. This currently only performs a single regression test. // FIXME: Write out a full test suite for this function. This currently only performs a single regression test.
describe("normal behavior", () => { describe("normal behavior", () => {
// Previously, we didn't apply source's byte offset on the code path for setting a typed array // Previously, we didn't apply source's byte offset on the code path for setting a typed array

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.slice(0, 1);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.slice).toHaveLength(2); expect(T.prototype.slice).toHaveLength(2);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.some(value => value === 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 1", () => { test("length is 1", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.some).toHaveLength(1); expect(T.prototype.some).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.sort();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.sort).toHaveLength(1); expect(T.prototype.sort).toHaveLength(1);

View file

@ -43,3 +43,17 @@ test("basic functionality", () => {
expect(typedArray[1]).toBe(4n); expect(typedArray[1]).toBe(4n);
}); });
}); });
test("resizable ArrayBuffer", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
expect(typedArray.subarray(0, 1).byteLength).toBe(T.BYTES_PER_ELEMENT);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(typedArray.subarray(0, 1).byteLength).toBe(0);
});
});

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.toLocaleString();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.toLocaleString).toHaveLength(0); expect(T.prototype.toLocaleString).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.toReversed();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 0", () => { test("length is 0", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.toReversed).toHaveLength(0); expect(T.prototype.toReversed).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.toSorted();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("basic functionality", () => { test("basic functionality", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.toSorted).toHaveLength(1); expect(T.prototype.toSorted).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array];
describe("errors", () => {
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.values();
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
});
test("length is 0", () => { test("length is 0", () => {
TYPED_ARRAYS.forEach(T => { TYPED_ARRAYS.forEach(T => {
expect(T.prototype.values).toHaveLength(0); expect(T.prototype.values).toHaveLength(0);

View file

@ -48,6 +48,24 @@ describe("errors", () => {
}).toThrowWithMessage(RangeError, "Invalid integer index: -inf"); }).toThrowWithMessage(RangeError, "Invalid integer index: -inf");
}); });
}); });
test("ArrayBuffer out of bounds", () => {
TYPED_ARRAYS.forEach(T => {
let arrayBuffer = new ArrayBuffer(T.BYTES_PER_ELEMENT * 2, {
maxByteLength: T.BYTES_PER_ELEMENT * 4,
});
let typedArray = new T(arrayBuffer, T.BYTES_PER_ELEMENT, 1);
arrayBuffer.resize(T.BYTES_PER_ELEMENT);
expect(() => {
typedArray.with(0, 0);
}).toThrowWithMessage(
TypeError,
"TypedArray contains a property which references a value at an index not contained within its buffer's bounds"
);
});
});
}); });
describe("normal behavior", () => { describe("normal behavior", () => {

View file

@ -49,13 +49,17 @@ WebIDL::ExceptionOr<JS::Handle<WebIDL::ArrayBufferView>> Crypto::get_random_valu
if (!array->is_typed_array_base()) if (!array->is_typed_array_base())
return WebIDL::TypeMismatchError::create(realm(), "array must be one of Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, BigInt64Array, or BigUint64Array"_fly_string); return WebIDL::TypeMismatchError::create(realm(), "array must be one of Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, BigInt64Array, or BigUint64Array"_fly_string);
auto const& typed_array = *array->bufferable_object().get<JS::NonnullGCPtr<JS::TypedArrayBase>>();
auto typed_array_record = JS::make_typed_array_with_buffer_witness_record(typed_array, JS::ArrayBuffer::Order::SeqCst);
// IMPLEMENTATION DEFINED: If the viewed array buffer is out-of-bounds, throw a InvalidStateError and terminate the algorithm.
if (JS::is_typed_array_out_of_bounds(typed_array_record))
return WebIDL::InvalidStateError::create(realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "TypedArray"sv)));
// 2. If the byteLength of array is greater than 65536, throw a QuotaExceededError and terminate the algorithm. // 2. If the byteLength of array is greater than 65536, throw a QuotaExceededError and terminate the algorithm.
if (array->viewed_array_buffer()->byte_length() > 65536) if (JS::typed_array_byte_length(typed_array_record) > 65536)
return WebIDL::QuotaExceededError::create(realm(), "array's byteLength may not be greater than 65536"_fly_string); return WebIDL::QuotaExceededError::create(realm(), "array's byteLength may not be greater than 65536"_fly_string);
// IMPLEMENTATION DEFINED: If the viewed array buffer is detached, throw a InvalidStateError and terminate the algorithm.
if (array->viewed_array_buffer()->is_detached())
return WebIDL::InvalidStateError::create(realm(), "array is detached"_fly_string);
// FIXME: Handle SharedArrayBuffers // FIXME: Handle SharedArrayBuffers
// 3. Overwrite all elements of array with cryptographically strong random values of the appropriate type. // 3. Overwrite all elements of array with cryptographically strong random values of the appropriate type.

View file

@ -526,9 +526,7 @@ private:
if constexpr (IsSame<ViewType, JS::DataView>) { if constexpr (IsSame<ViewType, JS::DataView>) {
return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst); return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
} else { } else {
// FIXME: Create a TypedArray record when TypedArray supports resizable ArrayBuffer objects. return JS::make_typed_array_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
TODO();
return 0;
} }
}(); }();
@ -537,7 +535,8 @@ private:
if (JS::is_view_out_of_bounds(view_record)) 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))); return WebIDL::DataCloneError::create(*m_vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "DataView"sv)));
} else { } else {
// FIXME: Check TypedArray bounds when TypedArray supports resizable ArrayBuffer objects. if (JS::is_typed_array_out_of_bounds(view_record))
return WebIDL::DataCloneError::create(*m_vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "TypedArray"sv)));
} }
// 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot. // 2. Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.
@ -570,9 +569,9 @@ 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, view.element_name())); // [[Constructor]] TRY(serialize_string(vector, view.element_name())); // [[Constructor]]
vector.append(view.byte_length()); vector.append(JS::typed_array_byte_length(view_record));
vector.append(view.byte_offset()); vector.append(view.byte_offset());
vector.append(view.array_length()); vector.append(JS::typed_array_length(view_record));
} }
return {}; return {};
} }

View file

@ -2041,14 +2041,17 @@ WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteSt
// 4. Let byteOffset be chunk.[[ByteOffset]]. // 4. Let byteOffset be chunk.[[ByteOffset]].
auto byte_offset = typed_array->byte_offset(); auto byte_offset = typed_array->byte_offset();
// 5. Let byteLength be chunk.[[ByteLength]].
auto byte_length = typed_array->byte_length();
// 6. If ! IsDetachedBuffer(buffer) is true, throw a TypeError exception. // 6. If ! IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if (buffer->is_detached()) { // FIXME: The streams spec has not been updated for resizable ArrayBuffer objects. We must perform step 6 before
auto error = JS::TypeError::create(realm, "Buffer is detached"sv); // invoking TypedArrayByteLength in step 5. We also must check if the array is out-of-bounds, rather than
return JS::throw_completion(error); // just detached.
} auto typed_array_record = JS::make_typed_array_with_buffer_witness_record(*typed_array, JS::ArrayBuffer::Order::SeqCst);
if (JS::is_typed_array_out_of_bounds(typed_array_record))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BufferOutOfBounds, "TypedArray"sv);
// 5. Let byteLength be chunk.[[ByteLength]].
auto byte_length = JS::typed_array_byte_length(typed_array_record);
// 7. Let transferredBuffer be ? TransferArrayBuffer(buffer). // 7. Let transferredBuffer be ? TransferArrayBuffer(buffer).
auto transferred_buffer = TRY(transfer_array_buffer(realm, *buffer)); auto transferred_buffer = TRY(transfer_array_buffer(realm, *buffer));

View file

@ -309,7 +309,12 @@ JS::ThrowCompletionOr<size_t> parse_module(JS::VM& vm, JS::Object* buffer_object
data = buffer.buffer(); data = buffer.buffer();
} else if (is<JS::TypedArrayBase>(buffer_object)) { } else if (is<JS::TypedArrayBase>(buffer_object)) {
auto& buffer = static_cast<JS::TypedArrayBase&>(*buffer_object); auto& buffer = static_cast<JS::TypedArrayBase&>(*buffer_object);
data = buffer.viewed_array_buffer()->buffer().span().slice(buffer.byte_offset(), buffer.byte_length());
auto typed_array_record = JS::make_typed_array_with_buffer_witness_record(buffer, JS::ArrayBuffer::Order::SeqCst);
if (JS::is_typed_array_out_of_bounds(typed_array_record))
return vm.throw_completion<JS::TypeError>(JS::ErrorType::BufferOutOfBounds, "TypedArray"sv);
data = buffer.viewed_array_buffer()->buffer().span().slice(buffer.byte_offset(), JS::typed_array_byte_length(typed_array_record));
} 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);

View file

@ -38,6 +38,12 @@ ErrorOr<ByteBuffer> get_buffer_source_copy(JS::Object const& buffer_source)
if (is<JS::TypedArrayBase>(buffer_source)) { if (is<JS::TypedArrayBase>(buffer_source)) {
auto const& es_buffer_source = static_cast<JS::TypedArrayBase const&>(buffer_source); auto const& es_buffer_source = static_cast<JS::TypedArrayBase const&>(buffer_source);
auto typed_array_record = JS::make_typed_array_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_typed_array_out_of_bounds(typed_array_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();
@ -45,7 +51,7 @@ 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::typed_array_byte_length(typed_array_record);
} 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);

View file

@ -13,7 +13,16 @@ namespace Web::WebIDL {
u32 BufferableObjectBase::byte_length() const u32 BufferableObjectBase::byte_length() const
{ {
return m_bufferable_object.visit([](auto& obj) { return static_cast<u32>(obj->byte_length()); }); return m_bufferable_object.visit(
[](JS::NonnullGCPtr<JS::TypedArrayBase> typed_array) {
auto typed_array_record = JS::make_typed_array_with_buffer_witness_record(typed_array, JS::ArrayBuffer::Order::SeqCst);
return JS::typed_array_byte_length(typed_array_record);
},
[](JS::NonnullGCPtr<JS::DataView> data_view) {
auto view_record = JS::make_data_view_with_buffer_witness_record(data_view, JS::ArrayBuffer::Order::SeqCst);
return JS::get_view_byte_length(view_record);
},
[](JS::NonnullGCPtr<JS::ArrayBuffer> array_buffer) { return static_cast<u32>(array_buffer->byte_length()); });
} }
JS::NonnullGCPtr<JS::Object> BufferableObjectBase::raw_object() JS::NonnullGCPtr<JS::Object> BufferableObjectBase::raw_object()