1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 18:34:58 +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)
{
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(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(print_value(print_context, JS::Value(length), seen_objects));
TRY(js_out(print_context, "\n byteLength: "));
TRY(print_value(print_context, JS::Value(typed_array_base.byte_length()), 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(print_value(print_context, JS::Value(JS::typed_array_byte_length(typed_array_record)), seen_objects));
TRY(js_out(print_context, "\n"));
// FIXME: This kinda sucks.
#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \

View file

@ -50,27 +50,46 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayIteratorPrototype::next)
size_t length;
// i. If array has a [[TypedArrayName]] internal slot, then
if (array.is_typed_array()) {
auto& typed_array = static_cast<TypedArrayBase&>(array);
if (typed_array.viewed_array_buffer()->is_detached())
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 1. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(array, seq-cst).
auto typed_array_record = make_typed_array_with_buffer_witness_record(typed_array, ArrayBuffer::SeqCst);
length = typed_array.array_length();
} else {
// 2. 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);
// 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));
}
// iii. If index ≥ len, return NormalCompletion(undefined).
if (index >= length) {
iterator->m_array = js_undefined();
return create_iterator_result_object(vm, js_undefined(), true);
}
iterator->m_index++;
if (iteration_kind == Object::PropertyKind::Key)
return create_iterator_result_object(vm, Value(static_cast<i32>(index)), false);
// iv. Let indexNumber be 𝔽(index).
auto value = TRY([&]() -> ThrowCompletionOr<Value> {
Value result;
// v. If kind is key, then
if (iteration_kind == Object::PropertyKind::Key) {
// 1. Let result be indexNumber.
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;
@ -78,13 +97,30 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayIteratorPrototype::next)
return value;
}
}
return array.get(index);
}());
if (iteration_kind == Object::PropertyKind::Value)
return create_iterator_result_object(vm, value, false);
// 3. If kind is value, then
if (iteration_kind == Object::PropertyKind::Value) {
// a. Let result be elementValue.
result = element_value;
}
// 4. Else,
else {
// a. Assert: kind is key+value.
VERIFY(iteration_kind == Object::PropertyKind::KeyAndValue);
return create_iterator_result_object(vm, Array::create_from(realm, { Value(static_cast<i32>(index)), value }), false);
// b. Let result be CreateArrayFromList(« indexNumber, elementValue »).
result = Array::create_from(realm, { Value(static_cast<i32>(index)), element_value });
}
}
// 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);
// 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)
// 25.4.2.1 ValidateIntegerTypedArray ( typedArray, waitable ), https://tc39.es/ecma262/#sec-validateintegertypedarray
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).
TRY(validate_typed_array(vm, typed_array));
// 3. Let buffer be typedArray.[[ViewedArrayBuffer]].
auto* buffer = typed_array.viewed_array_buffer();
// 2. NOTE: Bounds checking is not a synchronizing operation when typedArray's backing buffer is a growable SharedArrayBuffer.
auto const& type_name = typed_array.element_name();
// 4. If waitable is true, then
// 3. If waitable is true, then
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()))
return vm.throw_completion<TypeError>(ErrorType::TypedArrayTypeIsNot, type_name, "Int32 or BigInt64"sv);
}
// 5. Else,
// 4. Else,
else {
// a. Let type be TypedArrayElementType(typedArray).
// 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())
return vm.throw_completion<TypeError>(ErrorType::TypedArrayTypeIsNot, type_name, "an unclamped integer or BigInt"sv);
}
// 6. Return buffer.
return buffer;
// 5. Return taRecord.
return typed_array_record;
}
// 25.4.2.2 ValidateAtomicAccess ( typedArray, requestIndex ), https://tc39.es/ecma262/#sec-validateatomicaccess
static ThrowCompletionOr<size_t> validate_atomic_access(VM& vm, TypedArrayBase& typed_array, Value request_index)
// 25.4.2.2 ValidateAtomicAccess ( taRecord, requestIndex ), https://tc39.es/ecma262/#sec-validateatomicaccess
static ThrowCompletionOr<size_t> validate_atomic_access(VM& vm, TypedArrayWithBufferWitness const& typed_array_record, Value request_index)
{
// 1. Let length be typedArray.[[ArrayLength]].
auto length = typed_array.array_length();
// 1. Let length be TypedArrayLength(taRecord).
auto length = typed_array_length(typed_array_record);
// 2. Let accessIndex be ? ToIndex(requestIndex).
auto access_index = TRY(request_index.to_index(vm));
// 3. Assert: accessIndex ≥ 0.
auto access_index = TRY(request_index.to_index(vm));
// 4. If accessIndex ≥ length, throw a RangeError exception.
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();
// 6. Let offset be typedArray.[[ByteOffset]].
// 7. Let offset be typedArray.[[ByteOffset]].
auto offset = typed_array.byte_offset();
// 7. Return (accessIndex × elementSize) + offset.
// 8. Return (accessIndex × elementSize) + 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
static ThrowCompletionOr<Value> atomic_read_modify_write(VM& vm, TypedArrayBase& typed_array, Value index, Value value, ReadWriteModifyFunction operation)
{
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
auto* buffer = TRY(validate_integer_typed_array(vm, typed_array));
// 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
auto indexed_position = TRY(validate_atomic_access(vm, typed_array, index));
// 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, typed_array, index));
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)
value_to_set = TRY(value.to_bigint(vm));
// 4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
// 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
else
value_to_set = Value(TRY(value.to_integer_or_infinity(vm)));
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if (buffer->is_detached())
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
TRY(revalidate_atomic_access(vm, typed_array, byte_index_in_buffer));
// 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.
// 7. Let elementType be TypedArrayElementType(typedArray).
// 8. Return GetModifySetValueInBuffer(buffer, indexedPosition, elementType, v, op).
return typed_array.get_modify_set_value_in_buffer(indexed_position, value_to_set, move(operation));
// 5. Let buffer be typedArray.[[ViewedArrayBuffer]].
// 6. Let elementType be TypedArrayElementType(typedArray).
// 7. Return GetModifySetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, op).
return typed_array.get_modify_set_value_in_buffer(byte_index_in_buffer, value_to_set, move(operation));
}
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
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).
// 2. Let buffer be iieoRecord.[[Object]].[[ViewedArrayBuffer]].
// 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));
// 1. Let taRecord be ? ValidateIntegerTypedArray(typedArray, true).
auto typed_array_record = 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.
if (!buffer->is_shared_array_buffer())
return vm.throw_completion<TypeError>(ErrorType::NotASharedArrayBuffer);
// 4. Let i be ? ValidateAtomicAccess(iieoRecord, index).
auto index = TRY(validate_atomic_access(vm, typed_array, index_value));
// 4. Let i be ? ValidateAtomicAccess(taRecord, index).
auto index = TRY(validate_atomic_access(vm, typed_array_record, index_value));
// 5. Let arrayTypeName be typedArray.[[TypedArrayName]].
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
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).
auto* buffer = TRY(validate_integer_typed_array(vm, typed_array));
// 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. 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();
// 3. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
auto indexed_position = TRY(validate_atomic_access(vm, typed_array, vm.argument(1)));
Value expected;
Value replacement;
// 4. If typedArray.[[ContentType]] is BigInt, then
// 4. If typedArray.[[ContentType]] is bigint, then
if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt) {
// 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).
replacement = TRY(vm.argument(3).to_bigint(vm));
replacement = TRY(replacement_value.to_bigint(vm));
}
// 5. Else,
else {
// 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)).
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.
if (buffer->is_detached())
return vm.template throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 6. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
TRY(revalidate_atomic_access(vm, typed_array, byte_index_in_buffer));
// 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 elementSize be TypedArrayElementSize(typedArray).
// 9. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
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.
constexpr bool is_little_endian = AK::HostIsLittleEndian;
// 11. Let expectedBytes be NumericToRawBytes(elementType, expected, isLittleEndian).
// 10. Let expectedBytes be NumericToRawBytes(elementType, expected, isLittleEndian).
auto expected_bytes = MUST(ByteBuffer::create_uninitialized(sizeof(T)));
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)));
numeric_to_raw_bytes<T>(vm, replacement, is_little_endian, replacement_bytes);
// FIXME: Implement SharedArrayBuffer case.
// 13. If IsSharedArrayBuffer(buffer) is true, then
// a-i.
// 14. Else,
// 12. If IsSharedArrayBuffer(buffer) is true, then
// a. Let rawBytesRead be AtomicCompareExchangeInSharedBlock(block, byteIndexInBuffer, elementSize, expectedBytes, replacementBytes).
// 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.
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
// 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>) {
VERIFY_NOT_REACHED();
} else {
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* r = reinterpret_cast<U*>(replacement_bytes.data());
(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);
}
@ -335,10 +362,13 @@ static ThrowCompletionOr<Value> atomic_compare_exchange_impl(VM& vm, TypedArrayB
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::compare_exchange)
{
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) \
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
#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
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::load)
{
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
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)));
// 1. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
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.
if (typed_array->viewed_array_buffer()->is_detached())
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 2. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
TRY(revalidate_atomic_access(vm, *typed_array, byte_index_in_buffer));
// 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.
// 5. Let elementType be TypedArrayElementType(typedArray).
// 6. Return GetValueFromBuffer(buffer, indexedPosition, elementType, true, SeqCst).
return typed_array->get_value_from_buffer(indexed_position, ArrayBuffer::Order::SeqCst, true);
// 3. Let buffer be typedArray.[[ViewedArrayBuffer]].
// 4. Let elementType be TypedArrayElementType(typedArray).
// 5. Return GetValueFromBuffer(buffer, byteIndexInBuffer, elementType, true, seq-cst).
return typed_array->get_value_from_buffer(byte_index_in_buffer, ArrayBuffer::Order::SeqCst, true);
}
// 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
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::store)
{
// 1. Let buffer be ? ValidateIntegerTypedArray(typedArray).
auto* typed_array = TRY(typed_array_from(vm, vm.argument(0)));
TRY(validate_integer_typed_array(vm, *typed_array));
// 2. Let indexedPosition be ? ValidateAtomicAccess(typedArray, index).
auto indexed_position = TRY(validate_atomic_access(vm, *typed_array, vm.argument(1)));
auto index = vm.argument(1);
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)
value_to_set = TRY(value.to_bigint(vm));
// 4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
value = TRY(value.to_bigint(vm));
// 3. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
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.
if (typed_array->viewed_array_buffer()->is_detached())
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 4. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
TRY(revalidate_atomic_access(vm, *typed_array, byte_index_in_buffer));
// 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. Perform SetValueInBuffer(buffer, indexedPosition, elementType, v, true, SeqCst).
typed_array->set_value_in_buffer(indexed_position, value_to_set, ArrayBuffer::Order::SeqCst, true);
// 9. Return v.
return value_to_set;
// 8. Return v.
return value;
}
// 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);
// 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.
TRY(validate_integer_typed_array(vm, *typed_array, true));
auto byte_index_in_buffer = TRY(validate_atomic_access(vm, *typed_array, index));
auto byte_index_in_buffer = TRY(validate_atomic_access_on_integer_typed_array(vm, *typed_array, index, true));
// 2. If count is undefined, then
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());
}
// 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
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)
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()) {
// a. Let newLength be ? ToIndex(length).
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())
return vm.throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 6. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
auto buffer_byte_length = array_buffer.byte_length();
// 7. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst).
auto buffer_byte_length = array_buffer_byte_length(array_buffer, ArrayBuffer::Order::SeqCst);
Checked<size_t> new_byte_length;
// 7. If length is undefined, then
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.
// 8. If length is undefined and bufferIsFixedLength is false, then
if (length.is_undefined() && !buffer_is_fixed_length) {
// a. If offset > bufferByteLength, throw a RangeError exception.
if (offset > buffer_byte_length)
return vm.throw_completion<RangeError>(ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length);
// 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_());
}
// 9. Else,
else {
Checked<u32> new_byte_length;
// a. If length is undefined, then
if (length.is_undefined()) {
// 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);
// ii. Let newByteLength be bufferByteLength - offset.
new_byte_length = buffer_byte_length;
new_byte_length -= offset;
// iii. If newByteLength < 0, throw a RangeError exception.
if (new_byte_length.has_overflow())
return vm.throw_completion<RangeError>(ErrorType::TypedArrayOutOfRangeByteOffset, offset, buffer_byte_length);
}
// 8. Else,
// b. Else,
else {
// a. Let newByteLength be newLength × elementSize.
// i. Let newByteLength be newLength × elementSize.
new_byte_length = new_length;
new_byte_length *= element_size;
// b. If offset + newByteLength > bufferByteLength, throw a RangeError exception.
Checked<size_t> new_byte_end = new_byte_length;
new_byte_end += offset;
// 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);
}
if (new_byte_length.has_overflow())
return vm.throw_completion<RangeError>(ErrorType::InvalidLength, "typed array");
// 9. Set O.[[ViewedArrayBuffer]] to buffer.
typed_array.set_viewed_array_buffer(&array_buffer);
// 10. Set O.[[ByteLength]] to newByteLength.
// 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);
}
// 10. Set O.[[ViewedArrayBuffer]] to buffer.
typed_array.set_viewed_array_buffer(&array_buffer);
// 11. Set O.[[ByteOffset]] to offset.
typed_array.set_byte_offset(offset);
// 12. Set O.[[ArrayLength]] to newByteLength / elementSize.
typed_array.set_array_length(new_byte_length.value() / element_size);
// 13. Return unused.
// 12. Return unused.
return {};
}
// 23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray ), https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray
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();
// 1. Let srcData be srcArray.[[ViewedArrayBuffer]].
auto* src_data = src_array.viewed_array_buffer();
VERIFY(src_data);
auto* source_data = source_array.viewed_array_buffer();
VERIFY(source_data);
// 2. If IsDetachedBuffer(srcData) is true, throw a TypeError exception.
if (src_data->is_detached())
return vm.template throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// 2. Let elementType be TypedArrayElementType(O).
auto const& element_type = typed_array.element_name();
// 3. Let elementType be TypedArrayElementType(O).
// 4. Let elementSize be TypedArrayElementSize(O).
auto element_size = dest_array.element_size();
// 3. Let elementSize be TypedArrayElementSize(O).
auto element_size = typed_array.element_size();
// 5. Let srcType be TypedArrayElementType(srcArray).
// 6. Let srcElementSize be TypedArrayElementSize(srcArray).
auto src_element_size = src_array.element_size();
// 4. Let srcType be TypedArrayElementType(srcArray).
auto const& source_type = source_array.element_name();
// 7. Let srcByteOffset be srcArray.[[ByteOffset]].
auto src_byte_offset = src_array.byte_offset();
// 5. Let srcElementSize be TypedArrayElementSize(srcArray).
auto source_element_size = source_array.element_size();
// 8. Let elementLength be srcArray.[[ArrayLength]].
auto element_length = src_array.array_length();
// 6. Let srcByteOffset be srcArray.[[ByteOffset]].
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;
byte_length *= element_length;
if (byte_length.has_overflow())
@ -162,41 +164,37 @@ static ThrowCompletionOr<void> initialize_typed_array_from_typed_array(VM& vm, T
ArrayBuffer* data = nullptr;
// 10. If elementType is the same as srcType, then
if (dest_array.element_name() == src_array.element_name()) {
// 11. If elementType is srcType, then
if (element_type == source_type) {
// 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 {
// 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()));
// b. If IsDetachedBuffer(srcData) is true, throw a TypeError exception.
if (src_data->is_detached())
return vm.template throw_completion<TypeError>(ErrorType::DetachedArrayBuffer);
// b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception.
if (source_array.content_type() != typed_array.content_type())
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.
if (src_array.content_type() != dest_array.content_type())
return vm.template throw_completion<TypeError>(ErrorType::TypedArrayContentTypeMismatch, dest_array.class_name(), src_array.class_name());
// c. Let srcByteIndex be srcByteOffset.
u64 source_byte_index = source_byte_offset;
// d. Let srcByteIndex be srcByteOffset.
u64 src_byte_index = src_byte_offset;
// e. Let targetByteIndex be 0.
// d. Let targetByteIndex be 0.
u64 target_byte_index = 0;
// f. Let count be elementLength.
// g. Repeat, while count > 0,
// e. Let count be elementLength.
// f. Repeat, while count > 0,
for (u32 i = 0; i < element_length; ++i) {
// i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered).
auto value = src_array.get_value_from_buffer(src_byte_index, ArrayBuffer::Order::Unordered);
// i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, 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);
// iii. Set srcByteIndex to srcByteIndex + srcElementSize.
src_byte_index += src_element_size;
source_byte_index += source_element_size;
// iv. Set targetByteIndex to targetByteIndex + elementSize.
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.
dest_array.set_viewed_array_buffer(data);
// 13. Set O.[[ViewedArrayBuffer]] to data.
typed_array.set_viewed_array_buffer(data);
// 13. Set O.[[ByteLength]] to byteLength.
dest_array.set_byte_length(byte_length.value());
// 14. Set O.[[ByteLength]] to byteLength.
typed_array.set_byte_length(byte_length.value());
// 14. Set O.[[ByteOffset]] to 0.
dest_array.set_byte_offset(0);
// 15. Set O.[[ByteOffset]] to 0.
typed_array.set_byte_offset(0);
// 15. Set O.[[ArrayLength]] to elementLength.
dest_array.set_array_length(element_length);
// 16. Set O.[[ArrayLength]] to elementLength.
typed_array.set_array_length(element_length);
// 16. Return unused.
// 17. Return unused.
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
ThrowCompletionOr<TypedArrayBase*> typed_array_create(VM& vm, FunctionObject& constructor, MarkedVector<Value> arguments)
{
Optional<Value> first_argument;
if (!arguments.is_empty())
first_argument = arguments[0];
Optional<double> first_argument;
if (arguments.size() == 1 && arguments[0].is_number())
first_argument = arguments[0].as_double();
// 1. Let newTypedArray be ? Construct(constructor, argumentList).
auto new_typed_array = TRY(construct(vm, constructor, arguments.span()));
// 2. Perform ? ValidateTypedArray(newTypedArray).
if (!new_typed_array->is_typed_array())
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));
// 2. Let taRecord be ? ValidateTypedArray(newTypedArray, seq-cst).
auto typed_array_record = TRY(validate_typed_array(vm, *new_typed_array, ArrayBuffer::Order::SeqCst));
// 3. If argumentList is a List of a single Number, then
if (first_argument.has_value() && first_argument->is_number()) {
// a. If newTypedArray.[[ArrayLength]] < (argumentList[0]), throw a TypeError exception.
if (typed_array.array_length() < first_argument->as_double())
// 3. If the number of elements in argumentList is 1 and argumentList[0] is a Number, then
if (first_argument.has_value()) {
// a. 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);
// 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");
}
// 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
@ -358,6 +361,27 @@ ThrowCompletionOr<TypedArrayBase*> typed_array_create_same_type(VM& vm, TypedArr
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
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
#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/ArrayBuffer.h>
#include <LibJS/Runtime/ByteLength.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PropertyDescriptor.h>
@ -18,11 +19,6 @@
namespace JS {
class TypedArrayBase;
ThrowCompletionOr<TypedArrayBase*> typed_array_from(VM&, Value);
ThrowCompletionOr<void> validate_typed_array(VM&, TypedArrayBase&);
class TypedArrayBase : public Object {
JS_OBJECT(TypedArrayBase, Object);
@ -41,15 +37,15 @@ public:
using IntrinsicConstructor = NonnullGCPtr<TypedArrayConstructor> (Intrinsics::*)();
u32 array_length() const { return m_array_length; }
u32 byte_length() const { return m_byte_length; }
ByteLength const& array_length() const { return m_array_length; }
ByteLength const& byte_length() const { return m_byte_length; }
u32 byte_offset() const { return m_byte_offset; }
ContentType content_type() const { return m_content_type; }
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
IntrinsicConstructor intrinsic_constructor() const { return m_intrinsic_constructor; }
void set_array_length(u32 length) { m_array_length = length; }
void set_byte_length(u32 length) { m_byte_length = length; }
void set_array_length(ByteLength length) { m_array_length = move(length); }
void set_byte_length(ByteLength length) { m_byte_length = move(length); }
void set_byte_offset(u32 offset) { m_byte_offset = offset; }
void set_viewed_array_buffer(ArrayBuffer* array_buffer) { m_viewed_array_buffer = array_buffer; }
@ -78,8 +74,8 @@ protected:
set_is_typed_array();
}
u32 m_array_length { 0 };
u32 m_byte_length { 0 };
ByteLength m_array_length { 0 };
ByteLength m_byte_length { 0 };
u32 m_byte_offset { 0 };
ContentType m_content_type { ContentType::Number };
Kind m_kind {};
@ -90,25 +86,17 @@ private:
virtual void visit_edges(Visitor&) override;
};
// 10.4.5.14 IsValidIntegerIndex ( O, index ), https://tc39.es/ecma262/#sec-isvalidintegerindex
inline 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;
// 10.4.5.8 TypedArray With Buffer Witness Records, https://tc39.es/ecma262/#sec-typedarray-with-buffer-witness-records
struct TypedArrayWithBufferWitness {
NonnullGCPtr<TypedArrayBase const> object; // [[Object]]
ByteLength cached_buffer_byte_length; // [[CachedBufferByteLength]]
};
// 2. If IsIntegralNumber(index) is false, return false.
// 3. If index is -0𝔽, return false.
if (!property_index.is_index())
return false;
// 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;
}
TypedArrayWithBufferWitness make_typed_array_with_buffer_witness_record(TypedArrayBase const&, ArrayBuffer::Order);
u32 typed_array_byte_length(TypedArrayWithBufferWitness const&);
u32 typed_array_length(TypedArrayWithBufferWitness const&);
bool is_typed_array_out_of_bounds(TypedArrayWithBufferWitness const&);
bool is_valid_integer_index(TypedArrayBase const&, CanonicalIndex);
// 10.4.5.15 TypedArrayGetElement ( O, index ), https://tc39.es/ecma262/#sec-typedarraygetelement
template<typename T>
@ -394,45 +382,68 @@ public:
{
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() };
// 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then
if (!m_viewed_array_buffer->is_detached()) {
// a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do
for (size_t i = 0; i < m_array_length; ++i) {
// i. Add ! ToString(𝔽(i)) as the last element of keys.
keys.append(PrimitiveString::create(vm, ByteString::number(i)));
// 3. If IsTypedArrayOutOfBounds(taRecord) is false, then
if (!is_typed_array_out_of_bounds(typed_array_record)) {
// a. Let length be TypedArrayLength(taRecord).
auto length = typed_array_length(typed_array_record);
// 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()) {
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));
}
}
// 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()) {
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));
}
}
// 5. Return keys.
// 6. Return keys.
return { move(keys) };
}
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()
{
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); }
@ -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_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);
#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);
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", () => {

View file

@ -11,7 +11,10 @@ describe("errors", () => {
detachArrayBuffer(typedArray.buffer);
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", () => {

View file

@ -11,7 +11,10 @@ describe("errors", () => {
detachArrayBuffer(typedArray.buffer);
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", () => {

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.at).toHaveLength(1);

View file

@ -24,3 +24,17 @@ test("basic functionality", () => {
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);
});
});
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];
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.copyWithin).toHaveLength(2);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.entries).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.every).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.fill).toHaveLength(1);

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().filter(undefined);
}).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));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().find(undefined);
}).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));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().findIndex(undefined);
}).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));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().findLast(undefined);
}).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));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().findLastIndex(undefined);
}).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));

View file

@ -38,6 +38,22 @@ describe("errors", () => {
new T().forEach(undefined);
}).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));

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.includes).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.indexOf).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.keys).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.lastIndexOf).toHaveLength(1);

View file

@ -19,3 +19,17 @@ test("basic functionality", () => {
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);
}).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));

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.reduce).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.reduceRight).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.reverse).toHaveLength(0);

View file

@ -15,6 +15,33 @@ const BIGINT_TYPED_ARRAYS = [
{ 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.
describe("normal behavior", () => {
// 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];
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.slice).toHaveLength(2);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.some).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.sort).toHaveLength(1);

View file

@ -43,3 +43,17 @@ test("basic functionality", () => {
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];
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.toLocaleString).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.toReversed).toHaveLength(0);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.toSorted).toHaveLength(1);

View file

@ -12,6 +12,26 @@ const TYPED_ARRAYS = [
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", () => {
TYPED_ARRAYS.forEach(T => {
expect(T.prototype.values).toHaveLength(0);

View file

@ -48,6 +48,24 @@ describe("errors", () => {
}).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", () => {

View file

@ -49,13 +49,17 @@ WebIDL::ExceptionOr<JS::Handle<WebIDL::ArrayBufferView>> Crypto::get_random_valu
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);
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.
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);
// 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
// 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>) {
return JS::make_data_view_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
} else {
// FIXME: Create a TypedArray record when TypedArray supports resizable ArrayBuffer objects.
TODO();
return 0;
return JS::make_typed_array_with_buffer_witness_record(view, JS::ArrayBuffer::Order::SeqCst);
}
}();
@ -537,7 +535,8 @@ private:
if (JS::is_view_out_of_bounds(view_record))
return WebIDL::DataCloneError::create(*m_vm.current_realm(), MUST(String::formatted(JS::ErrorType::BufferOutOfBounds.message(), "DataView"sv)));
} else {
// FIXME: Check TypedArray bounds when TypedArray supports resizable ArrayBuffer objects.
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.
@ -570,9 +569,9 @@ private:
vector.append(ValueTag::ArrayBufferView);
vector.extend(move(buffer_serialized)); // [[ArrayBufferSerialized]]
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.array_length());
vector.append(JS::typed_array_length(view_record));
}
return {};
}

View file

@ -2041,14 +2041,17 @@ WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteSt
// 4. Let byteOffset be chunk.[[ByteOffset]].
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.
if (buffer->is_detached()) {
auto error = JS::TypeError::create(realm, "Buffer is detached"sv);
return JS::throw_completion(error);
}
// FIXME: The streams spec has not been updated for resizable ArrayBuffer objects. We must perform step 6 before
// invoking TypedArrayByteLength in step 5. We also must check if the array is out-of-bounds, rather than
// 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).
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();
} else if (is<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)) {
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)) {
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]].
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();
// 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)) {
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
{
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()