diff --git a/Userland/Libraries/LibJS/Print.cpp b/Userland/Libraries/LibJS/Print.cpp index 957e4e5a47..bc01bea40b 100644 --- a/Userland/Libraries/LibJS/Print.cpp +++ b/Userland/Libraries/LibJS/Print.cpp @@ -445,19 +445,26 @@ ErrorOr print_number(JS::PrintContext& print_context, T number) ErrorOr print_typed_array(JS::PrintContext& print_context, JS::TypedArrayBase const& typed_array_base, HashTable& 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 ")); + 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) \ diff --git a/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp index 470c3033ce..c3f9dea450 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayIteratorPrototype.cpp @@ -50,41 +50,77 @@ 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(array); - if (typed_array.viewed_array_buffer()->is_detached()) - return vm.throw_completion(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(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(index)), false); + // iv. Let indexNumber be 𝔽(index). - auto value = TRY([&]() -> ThrowCompletionOr { - // 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; + Value result; + + // v. If kind is key, then + if (iteration_kind == Object::PropertyKind::Key) { + // 1. Let result be indexNumber. + result = Value(static_cast(index)); + } + // vi. Else, + else { + // 1. Let elementKey be ! ToString(indexNumber). + // 2. Let elementValue be ? Get(array, elementKey). + auto element_value = TRY([&]() -> ThrowCompletionOr { + // 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) - return create_iterator_result_object(vm, value, false); + // b. Let result be CreateArrayFromList(« indexNumber, elementValue »). + result = Array::create_from(realm, { Value(static_cast(index)), element_value }); + } + } - return create_iterator_result_object(vm, Array::create_from(realm, { Value(static_cast(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); } } diff --git a/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp b/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp index c590d2edf6..bf4eee7ad1 100644 --- a/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp @@ -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 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 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(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(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 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 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(ErrorType::IndexOutOfRange, access_index, typed_array.array_length()); + return vm.throw_completion(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 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 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(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(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 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(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 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(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 -static ThrowCompletionOr atomic_compare_exchange_impl(VM& vm, TypedArrayBase& typed_array) +static ThrowCompletionOr 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(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(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(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) { VERIFY_NOT_REACHED(); } else { using U = Conditional, u8, T>; - auto* v = reinterpret_cast(block.span().slice(indexed_position).data()); + auto* v = reinterpret_cast(block.span().slice(byte_index_in_buffer).data()); auto* e = reinterpret_cast(expected_bytes.data()); auto* r = reinterpret_cast(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(vm, raw_bytes_read, is_little_endian); } @@ -335,10 +362,13 @@ static ThrowCompletionOr 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(typed_array)) \ - return TRY(atomic_compare_exchange_impl(vm, *typed_array)); + return TRY(atomic_compare_exchange_impl(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(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(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; diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp index ba5153d86a..0275e347f3 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp +++ b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp @@ -28,25 +28,6 @@ ThrowCompletionOr typed_array_from(VM& vm, Value typed_array_va return static_cast(this_object.ptr()); } -// 23.2.4.4 ValidateTypedArray ( O ), https://tc39.es/ecma262/#sec-validatetypedarray -ThrowCompletionOr validate_typed_array(VM& vm, TypedArrayBase& typed_array) -{ - // 1. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). - if (!typed_array.is_typed_array()) - return vm.throw_completion(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(ErrorType::DetachedArrayBuffer); - - return {}; -} - // 22.2.5.1.3 InitializeTypedArrayFromArrayBuffer, https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer static ThrowCompletionOr 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 initialize_typed_array_from_array_buffer(VM& vm, if (offset % element_size != 0) return vm.throw_completion(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(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 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(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(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 { - // a. Let newByteLength be newLength × elementSize. - new_byte_length = new_length; - new_byte_length *= element_size; + Checked new_byte_length; - // b. If offset + newByteLength > bufferByteLength, throw a RangeError exception. - Checked new_byte_end = new_byte_length; - new_byte_end += offset; + // 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(ErrorType::TypedArrayInvalidBufferLength, typed_array.class_name(), element_size, buffer_byte_length); - if (new_byte_end.has_overflow()) - return vm.throw_completion(ErrorType::InvalidLength, "typed array"); + // ii. Let newByteLength be bufferByteLength - offset. + new_byte_length = buffer_byte_length; + new_byte_length -= offset; - if (new_byte_end.value() > buffer_byte_length) - return vm.throw_completion(ErrorType::TypedArrayOutOfRangeByteOffsetOrLength, offset, new_byte_end.value(), buffer_byte_length); + // iii. If newByteLength < 0, throw a RangeError exception. + if (new_byte_length.has_overflow()) + return vm.throw_completion(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 new_byte_end = offset; + new_byte_end += new_byte_length; + + if (new_byte_end.has_overflow()) + return vm.throw_completion(ErrorType::InvalidLength, "typed array"); + if (new_byte_end.value() > buffer_byte_length) + return vm.throw_completion(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()) - return vm.throw_completion(ErrorType::InvalidLength, "typed array"); - - // 9. Set O.[[ViewedArrayBuffer]] to buffer. + // 10. Set O.[[ViewedArrayBuffer]] to 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. 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 -static ThrowCompletionOr initialize_typed_array_from_typed_array(VM& vm, TypedArray& dest_array, TypedArrayBase& src_array) +static ThrowCompletionOr initialize_typed_array_from_typed_array(VM& vm, TypedArray& 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(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(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 byte_length = element_size; byte_length *= element_length; if (byte_length.has_overflow()) @@ -162,41 +164,37 @@ static ThrowCompletionOr 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(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(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(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(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 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 initialize_typed_array_from_list(VM& vm, TypedArr // 23.2.4.2 TypedArrayCreate ( constructor, argumentList ), https://tc39.es/ecma262/#typedarray-create ThrowCompletionOr typed_array_create(VM& vm, FunctionObject& constructor, MarkedVector arguments) { - Optional first_argument; - if (!arguments.is_empty()) - first_argument = arguments[0]; + Optional 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(ErrorType::NotAnObjectOfType, "TypedArray"); - auto& typed_array = *static_cast(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(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(ErrorType::InvalidLength, "typed array"); } // 4. Return newTypedArray. - return &typed_array; + return static_cast(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 typed_array_create_same_type(VM& vm, TypedArr return result; } +// 23.2.4.4 ValidateTypedArray ( O ), https://tc39.es/ecma262/#sec-validatetypedarray +ThrowCompletionOr 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(ErrorType::NotAnObjectOfType, "TypedArray"); + + // 2. Assert: O has a [[ViewedArrayBuffer]] internal slot. + auto const& typed_array = static_cast(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(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 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; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.h b/Userland/Libraries/LibJS/Runtime/TypedArray.h index be3074b5fe..72b0c562b7 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArray.h +++ b/Userland/Libraries/LibJS/Runtime/TypedArray.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -18,11 +19,6 @@ namespace JS { -class TypedArrayBase; - -ThrowCompletionOr typed_array_from(VM&, Value); -ThrowCompletionOr validate_typed_array(VM&, TypedArrayBase&); - class TypedArrayBase : public Object { JS_OBJECT(TypedArrayBase, Object); @@ -41,15 +37,15 @@ public: using IntrinsicConstructor = NonnullGCPtr (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 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 @@ -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 { 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 data() const { - return { reinterpret_cast(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(m_viewed_array_buffer->buffer().data() + m_byte_offset), length }; } + Span data() { - return { reinterpret_cast(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(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 typed_array_from(VM&, Value); ThrowCompletionOr typed_array_create(VM&, FunctionObject& constructor, MarkedVector arguments); ThrowCompletionOr typed_array_create_same_type(VM&, TypedArrayBase const& exemplar, MarkedVector arguments); +ThrowCompletionOr validate_typed_array(VM&, Object const&, ArrayBuffer::Order); ThrowCompletionOr compare_typed_array_elements(VM&, Value x, Value y, FunctionObject* comparefn); #define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp index ffdcb1f84b..5e0f201369 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp @@ -81,67 +81,14 @@ static ThrowCompletionOr typed_array_from_this(VM& vm) return typed_array_from(vm, this_value); } -static ThrowCompletionOr validate_typed_array_from_this(VM& vm) -{ - auto* typed_array = TRY(typed_array_from_this(vm)); - - TRY(validate_typed_array(vm, *typed_array)); - - return typed_array; -} - -static ThrowCompletionOr callback_from_args(VM& vm, ByteString const& name) +static ThrowCompletionOr> callback_from_args(VM& vm, StringView prototype_name) { if (vm.argument_count() < 1) - return vm.throw_completion(ErrorType::TypedArrayPrototypeOneArg, name); + return vm.throw_completion(ErrorType::TypedArrayPrototypeOneArg, prototype_name); auto callback = vm.argument(0); if (!callback.is_function()) - return vm.throw_completion(ErrorType::NotAFunction, callback.to_string_without_side_effects()); - return &callback.as_function(); -} - -static ThrowCompletionOr for_each_item(VM& vm, ByteString const& name, Function callback) -{ - auto* typed_array = TRY(validate_typed_array_from_this(vm)); - - auto initial_length = typed_array->array_length(); - - auto* callback_function = TRY(callback_from_args(vm, name)); - - auto this_value = vm.argument(1); - - for (size_t i = 0; i < initial_length; ++i) { - auto value = TRY(typed_array->get(i)); - - auto callback_result = TRY(call(vm, *callback_function, this_value, value, Value((i32)i), typed_array)); - - if (callback(i, value, callback_result) == IterationDecision::Break) - break; - } - - return {}; -} - -static ThrowCompletionOr for_each_item_from_last(VM& vm, ByteString const& name, Function callback) -{ - auto* typed_array = TRY(validate_typed_array_from_this(vm)); - - auto initial_length = typed_array->array_length(); - - auto* callback_function = TRY(callback_from_args(vm, name)); - - auto this_value = vm.argument(1); - - for (ssize_t i = (ssize_t)initial_length - 1; i >= 0; --i) { - auto value = TRY(typed_array->get(i)); - - auto callback_result = TRY(call(vm, *callback_function, this_value, value, Value((i32)i), typed_array)); - - if (callback(i, value, callback_result) == IterationDecision::Break) - break; - } - - return {}; + return vm.throw_completion(ErrorType::NotAFunction, callback); + return callback.as_function(); } // 23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList ), https://tc39.es/ecma262/#typedarray-species-create @@ -170,39 +117,43 @@ static ThrowCompletionOr typed_array_species_create(VM& vm, Typ // 23.2.3.1 %TypedArray%.prototype.at ( index ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.at JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::at) { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto index = vm.argument(0); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). - auto relative_index = TRY(vm.argument(0).to_integer_or_infinity(vm)); + auto relative_index = TRY(index.to_integer_or_infinity(vm)); - if (Value(relative_index).is_infinity()) + if (Value { relative_index }.is_infinity()) return js_undefined(); - Checked index { 0 }; + Checked k_checked { 0 }; // 5. If relativeIndex ≥ 0, then if (relative_index >= 0) { // a. Let k be relativeIndex. - index += relative_index; + k_checked += relative_index; } // 6. Else, else { // a. Let k be len + relativeIndex. - index += length; - index -= -relative_index; + k_checked += length; + k_checked -= -relative_index; } // 7. If k < 0 or k ≥ len, return undefined. - if (index.has_overflow() || index.value() >= length) + if (k_checked.has_overflow() || k_checked.value() >= length) return js_undefined(); // 8. Return ! Get(O, ! ToString(𝔽(k))). - return MUST(typed_array->get(index.value())); + return MUST(typed_array->get(k_checked.value())); } // 23.2.3.2 get %TypedArray%.prototype.buffer, https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer @@ -210,9 +161,9 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::buffer_getter) { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. auto* buffer = typed_array->viewed_array_buffer(); VERIFY(buffer); @@ -226,22 +177,17 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::byte_length_getter) { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - auto* buffer = typed_array->viewed_array_buffer(); - VERIFY(buffer); + // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + auto typed_array_record = make_typed_array_with_buffer_witness_record(*typed_array, ArrayBuffer::Order::SeqCst); - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - if (buffer->is_detached()) - return Value(0); + // 5. Let size be TypedArrayByteLength(taRecord). + auto size = typed_array_byte_length(typed_array_record); - // 6. Let size be O.[[ByteLength]]. - auto size = typed_array->byte_length(); - - // 7. Return 𝔽(size). - return Value(size); + // 6. Return 𝔽(size). + return Value { size }; } // 23.2.3.4 get %TypedArray%.prototype.byteOffset, https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset @@ -249,81 +195,91 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::byte_offset_getter) { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - auto* buffer = typed_array->viewed_array_buffer(); - VERIFY(buffer); + // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + auto typed_array_record = make_typed_array_with_buffer_witness_record(*typed_array, ArrayBuffer::Order::SeqCst); - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - if (buffer->is_detached()) - return Value(0); + // 5. If IsTypedArrayOutOfBounds(taRecord) is true, return +0𝔽. + if (is_typed_array_out_of_bounds(typed_array_record)) + return Value { 0 }; // 6. Let offset be O.[[ByteOffset]]. auto offset = typed_array->byte_offset(); // 7. Return 𝔽(offset). - return Value(offset); + return Value { offset }; } // 23.2.3.6 %TypedArray%.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within) { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto target = vm.argument(0); + auto start = vm.argument(1); + auto end = vm.argument(2); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. Let relativeTarget be ? ToIntegerOrInfinity(target). - auto relative_target = TRY(vm.argument(0).to_integer_or_infinity(vm)); + auto relative_target = TRY(target.to_integer_or_infinity(vm)); double to; - if (Value(relative_target).is_negative_infinity()) { - // 5. If relativeTarget is -∞, let to be 0. + // 5. If relativeTarget = -∞, let to be 0. + if (Value { relative_target }.is_negative_infinity()) { to = 0.0; - } else if (relative_target < 0) { - // 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0) + } + // 6. Else if relativeTarget < 0, let to be max(len + relativeTarget, 0). + else if (relative_target < 0) { to = max(length + relative_target, 0.0); - } else { - // 7. Else, let to be min(relativeTarget, len). + } + // 7. Else, let to be min(relativeTarget, len). + else { to = min(relative_target, (double)length); } // 8. Let relativeStart be ? ToIntegerOrInfinity(start). - auto relative_start = TRY(vm.argument(1).to_integer_or_infinity(vm)); + auto relative_start = TRY(start.to_integer_or_infinity(vm)); double from; - if (Value(relative_start).is_negative_infinity()) { - // 9. If relativeStart is -∞, let from be 0. + // 9. If relativeStart = -∞, let from be 0. + if (Value { relative_start }.is_negative_infinity()) { from = 0.0; - } else if (relative_start < 0) { - // 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + } + // 10. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + else if (relative_start < 0) { from = max(length + relative_start, 0.0); - } else { - // 11. Else, let from be min(relativeStart, len). + } + // 11. Else, let from be min(relativeStart, len). + else { from = min(relative_start, (double)length); } double relative_end; - // 12. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - if (vm.argument(2).is_undefined()) + if (end.is_undefined()) relative_end = length; else - relative_end = TRY(vm.argument(2).to_integer_or_infinity(vm)); + relative_end = TRY(end.to_integer_or_infinity(vm)); double final; - if (Value(relative_end).is_negative_infinity()) { - // 13. If relativeEnd is -∞, let final be 0. + // 13. If relativeEnd = -∞, let final be 0. + if (Value { relative_end }.is_negative_infinity()) { final = 0.0; - } else if (relative_end < 0) { - // 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + } + // 14. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + else if (relative_end < 0) { final = max(length + relative_end, 0.0); - } else { - // 15. Else, let final be min(relativeEnd, len). + } + // 15. Else, let final be min(relativeEnd, len). + else { final = min(relative_end, (double)length); } @@ -335,22 +291,36 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within) // a. NOTE: The copying must be performed in a manner that preserves the bit-level encoding of the source data. // b. Let buffer be O.[[ViewedArrayBuffer]]. - auto buffer = typed_array->viewed_array_buffer(); + auto* buffer = typed_array->viewed_array_buffer(); - // c. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - if (buffer->is_detached()) - return vm.throw_completion(ErrorType::DetachedArrayBuffer); + // c. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + typed_array_record = make_typed_array_with_buffer_witness_record(*typed_array, ArrayBuffer::Order::SeqCst); - // d. Let elementSize be TypedArrayElementSize(O). + // d. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + if (is_typed_array_out_of_bounds(typed_array_record)) + return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); + + // e. Set len to TypedArrayLength(taRecord). + length = typed_array_length(typed_array_record); + + // f. Let elementSize be TypedArrayElementSize(O). auto element_size = typed_array->element_size(); - // e. Let byteOffset be O.[[ByteOffset]]. + // g. Let byteOffset be O.[[ByteOffset]]. auto byte_offset = typed_array->byte_offset(); - // FIXME: Not exactly sure what we should do when overflow occurs. - // Just return as if succeeded for now. (This goes for steps g to j) + // FIXME: Not exactly sure what we should do when overflow occurs. Just return as if succeeded for now. - // f. Let toByteIndex be to × elementSize + byteOffset. + // h. Let bufferByteLimit be len × elementSize + byteOffset. + Checked buffer_byte_limit_checked = static_cast(length); + buffer_byte_limit_checked *= element_size; + buffer_byte_limit_checked += byte_offset; + if (buffer_byte_limit_checked.has_overflow()) { + dbgln("TypedArrayPrototype::copy_within: buffer_byte_limit overflowed, returning as if succeeded."); + return typed_array; + } + + // i. Let toByteIndex be to × elementSize + byteOffset. Checked to_byte_index_checked = static_cast(to); to_byte_index_checked *= element_size; to_byte_index_checked += byte_offset; @@ -359,23 +329,24 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within) return typed_array; } - // g. Let fromByteIndex be from × elementSize + byteOffset. + // j. Let fromByteIndex be from × elementSize + byteOffset. Checked from_byte_index_checked = static_cast(from); from_byte_index_checked *= element_size; from_byte_index_checked += byte_offset; if (from_byte_index_checked.has_overflow()) { - dbgln("TypedArrayPrototype::copy_within: from_byte_index_checked overflowed, returning as if succeeded."); + dbgln("TypedArrayPrototype::copy_within: from_byte_index overflowed, returning as if succeeded."); return typed_array; } - // h. Let countBytes be count × elementSize. + // k. Let countBytes be count × elementSize. Checked count_bytes_checked = static_cast(count); count_bytes_checked *= element_size; if (count_bytes_checked.has_overflow()) { - dbgln("TypedArrayPrototype::copy_within: count_bytes_checked overflowed, returning as if succeeded."); + dbgln("TypedArrayPrototype::copy_within: count_bytes overflowed, returning as if succeeded."); return typed_array; } + auto buffer_byte_limit = buffer_byte_limit_checked.value(); auto to_byte_index = to_byte_index_checked.value(); auto from_byte_index = from_byte_index_checked.value(); auto count_bytes = count_bytes_checked.value(); @@ -389,11 +360,10 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within) i8 direction; - // i. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then + // l. If fromByteIndex < toByteIndex and toByteIndex < fromByteIndex + countBytes, then if (from_byte_index < to_byte_index && to_byte_index < from_plus_count.value()) { // i. Let direction be -1. direction = -1; - // ii. Set fromByteIndex to fromByteIndex + countBytes - 1. from_byte_index = from_plus_count.value() - 1; @@ -407,27 +377,36 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within) // iii. Set toByteIndex to toByteIndex + countBytes - 1. to_byte_index = to_plus_count.value() - 1; } - // j. Else, + // m. Else, else { // i. Let direction be 1. direction = 1; } - // k. Repeat, while countBytes > 0, - for (; count_bytes > 0; --count_bytes) { - // i. Let value be GetValueFromBuffer(buffer, fromByteIndex, Uint8, true, Unordered). - auto value = buffer->get_value(from_byte_index, true, ArrayBuffer::Order::Unordered); + // n. Repeat, while countBytes > 0, + while (count_bytes > 0) { + // i. If fromByteIndex < bufferByteLimit and toByteIndex < bufferByteLimit, then + if (from_byte_index < buffer_byte_limit && to_byte_index < buffer_byte_limit) { + // 1. Let value be GetValueFromBuffer(buffer, fromByteIndex, uint8, true, unordered). + auto value = buffer->get_value(from_byte_index, true, ArrayBuffer::Order::Unordered); - // ii. Perform SetValueInBuffer(buffer, toByteIndex, Uint8, value, true, Unordered). - buffer->set_value(to_byte_index, value, true, ArrayBuffer::Order::Unordered); + // 2. Perform SetValueInBuffer(buffer, toByteIndex, uint8, value, true, unordered). + buffer->set_value(to_byte_index, value, true, ArrayBuffer::Order::Unordered); - // iii. Set fromByteIndex to fromByteIndex + direction. - from_byte_index += direction; + // 3. Set fromByteIndex to fromByteIndex + direction. + from_byte_index += direction; - // iv. Set toByteIndex to toByteIndex + direction. - to_byte_index += direction; + // 4. Set toByteIndex to toByteIndex + direction. + to_byte_index += direction; - // v. Set countBytes to countBytes - 1. + // 5. Set countBytes to countBytes - 1. + --count_bytes; + } + // ii. Else, + else { + // 1. Set countBytes to 0. + count_bytes = 0; + } } } @@ -441,8 +420,10 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::entries) auto& realm = *vm.current_realm(); // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Perform ? ValidateTypedArray(O, seq-cst). + (void)TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); // 3. Return CreateArrayIterator(O, key+value). return ArrayIterator::create(realm, typed_array, Object::PropertyKind::KeyAndValue); @@ -451,41 +432,72 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::entries) // 23.2.3.8 %TypedArray%.prototype.every ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.every JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::every) { - auto result = true; - TRY(for_each_item(vm, "every", [&](auto, auto, auto callback_result) { - if (!callback_result.to_boolean()) { - result = false; - return IterationDecision::Break; - } - return IterationDecision::Continue; - })); - return Value(result); + auto this_arg = vm.argument(1); + + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + auto callback_function = TRY(callback_from_args(vm, "every"sv)); + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + + // b. Let kValue be ! Get(O, Pk). + auto value = MUST(typed_array->get(property_key)); + + // c. Let testResult be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(vm, *callback_function, this_arg, value, Value { k }, typed_array)).to_boolean(); + + // d. If testResult is false, return false. + if (!test_result) + return false; + + // e. Set k to k + 1. + } + + // 7. Return true. + return true; } // 23.2.3.9 %TypedArray%.prototype.fill ( value [ , start [ , end ] ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::fill) { + auto value = vm.argument(0); + auto start = vm.argument(1); + auto end = vm.argument(2); + // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); - Value value; // 4. If O.[[ContentType]] is BigInt, set value to ? ToBigInt(value). if (typed_array->content_type() == TypedArrayBase::ContentType::BigInt) - value = TRY(vm.argument(0).to_bigint(vm)); + value = TRY(value.to_bigint(vm)); // 5. Otherwise, set value to ? ToNumber(value). else - value = TRY(vm.argument(0).to_number(vm)); + value = TRY(value.to_number(vm)); // 6. Let relativeStart be ? ToIntegerOrInfinity(start). - auto relative_start = TRY(vm.argument(1).to_integer_or_infinity(vm)); + auto relative_start = TRY(start.to_integer_or_infinity(vm)); u32 k; - // 7. If relativeStart is -∞, let k be 0. - if (Value(relative_start).is_negative_infinity()) + // 7. If relativeStart = -∞, let k be 0. + if (Value { relative_start }.is_negative_infinity()) k = 0; // 8. Else if relativeStart < 0, let k be max(len + relativeStart, 0). else if (relative_start < 0) @@ -494,16 +506,16 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::fill) else k = min(relative_start, length); - // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). double relative_end; - if (vm.argument(2).is_undefined()) + // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + if (end.is_undefined()) relative_end = length; else - relative_end = TRY(vm.argument(2).to_integer_or_infinity(vm)); + relative_end = TRY(end.to_integer_or_infinity(vm)); u32 final; - // 11. If relativeEnd is -∞, let final be 0. - if (Value(relative_end).is_negative_infinity()) + // 11. If relativeEnd = -∞, let final be 0. + if (Value { relative_end }.is_negative_infinity()) final = 0; // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). else if (relative_end < 0) @@ -512,57 +524,73 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::fill) else final = min(relative_end, length); - // 14. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. - if (typed_array->viewed_array_buffer()->is_detached()) - return vm.throw_completion(ErrorType::DetachedArrayBuffer); + // 14. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); - // 15. Repeat, while k < final, - for (; k < final; ++k) { + // 15. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + if (is_typed_array_out_of_bounds(typed_array_record)) + return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); + + // 16. Set len to TypedArrayLength(taRecord). + length = typed_array_length(typed_array_record); + + // 17. Set final to min(final, len). + final = min(final, length); + + // 18. Repeat, while k < final, + while (k < final) { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Perform ! Set(O, Pk, value, true). - MUST(typed_array->set(k, value, Object::ShouldThrowExceptions::Yes)); + MUST(typed_array->set(property_key, value, Object::ShouldThrowExceptions::Yes)); // c. Set k to k + 1. + ++k; } - // 16. Return O. + // 19. Return O. return typed_array; } // 23.2.3.10 %TypedArray%.prototype.filter ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::filter) { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto this_arg = vm.argument(1); - // 3. Let len be O.[[ArrayLength]]. - auto initial_length = typed_array->array_length(); + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - auto* callback_function = TRY(callback_from_args(vm, "filter")); + auto callback_function = TRY(callback_from_args(vm, "filter"sv)); // 5. Let kept be a new empty List. - MarkedVector kept(vm.heap()); + MarkedVector kept { vm.heap() }; - // 7. Let captured be 0. + // 6. Let captured be 0. size_t captured = 0; - auto this_value = vm.argument(1); - - // 5. Let k be 0. + // 7. Let k be 0. // 8. Repeat, while k < len, - for (size_t i = 0; i < initial_length; ++i) { + for (size_t k = 0; k < length; ++k) { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Let kValue be ! Get(O, Pk). - auto value = MUST(typed_array->get(i)); + auto value = MUST(typed_array->get(property_key)); // c. Let selected be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - auto callback_result = TRY(call(vm, *callback_function, this_value, value, Value((i32)i), typed_array)).to_boolean(); + auto selected = TRY(call(vm, *callback_function, this_arg, value, Value { k }, typed_array)).to_boolean(); // d. If selected is true, then - if (callback_result) { - // i. Append kValue to the end of kept. + if (selected) { + // i. Append kValue to kept. kept.append(value); // ii. Set captured to captured + 1. @@ -593,97 +621,218 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::filter) return filter_array; } +enum class Direction { + Ascending, + Descending, +}; + +struct FoundValue { + Value index_to_value() const + { + if (!index.has_value()) + return Value { -1 }; + return Value { *index }; + } + + Optional index; // [[Index]] + Value value; // [[Value]] +}; + +// 23.1.3.12.1 FindViaPredicate ( O, len, direction, predicate, thisArg ), https://tc39.es/ecma262/#sec-findviapredicate +static ThrowCompletionOr find_via_predicate(VM& vm, TypedArrayBase const& typed_array, u32 length, Direction direction, Value this_arg, StringView prototype_name) +{ + // 1. If IsCallable(predicate) is false, throw a TypeError exception. + auto predicate = TRY(callback_from_args(vm, prototype_name)); + + Vector indices; + indices.ensure_capacity(length); + + // 2. If direction is ascending, then + if (direction == Direction::Ascending) { + // a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in ascending order. + for (u32 i = 0; i < length; ++i) + indices.unchecked_append(i); + } + // 3. Else, + else { + // a. Let indices be a List of the integers in the interval from 0 (inclusive) to len (exclusive), in descending order. + for (u32 i = length; i > 0; --i) + indices.unchecked_append(i - 1); + } + + // 4. For each integer k of indices, do + for (auto k : indices) { + // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + + // b. NOTE: If O is a TypedArray, the following invocation of Get will return a normal completion. + + // c. Let kValue be ? Get(O, Pk). + auto value = TRY(typed_array.get(property_key)); + + // d. Let testResult be ? Call(predicate, thisArg, « kValue, 𝔽(k), O »). + auto test_result = TRY(call(vm, *predicate, this_arg, value, Value { k }, &typed_array)); + + // e. If ToBoolean(testResult) is true, return the Record { [[Index]]: 𝔽(k), [[Value]]: kValue }. + if (test_result.to_boolean()) + return FoundValue { k, value }; + } + + // 5. Return the Record { [[Index]]: -1𝔽, [[Value]]: undefined }. + return FoundValue { {}, js_undefined() }; +} + // 23.2.3.11 %TypedArray%.prototype.find ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.find JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find) { - auto result = js_undefined(); - TRY(for_each_item(vm, "find", [&](auto, auto value, auto callback_result) { - if (callback_result.to_boolean()) { - result = value; - return IterationDecision::Break; - } - return IterationDecision::Continue; - })); - return result; + auto this_arg = vm.argument(1); + + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). + auto find_record = TRY(find_via_predicate(vm, *typed_array, length, Direction::Ascending, this_arg, "find"sv)); + + // 5. Return findRec.[[Value]]. + return find_record.value; } // 23.2.3.12 %TypedArray%.prototype.findIndex ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_index) { - auto result_index = -1; - TRY(for_each_item(vm, "findIndex", [&](auto index, auto, auto callback_result) { - if (callback_result.to_boolean()) { - result_index = index; - return IterationDecision::Break; - } - return IterationDecision::Continue; - })); - return Value(result_index); + auto this_arg = vm.argument(1); + + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. Let findRec be ? FindViaPredicate(O, len, ascending, predicate, thisArg). + auto find_record = TRY(find_via_predicate(vm, *typed_array, length, Direction::Ascending, this_arg, "findIndex"sv)); + + // 5. Return findRec.[[Index]]. + return find_record.index_to_value(); } // 23.2.3.13 %TypedArray%.prototype.findLast ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlast JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_last) { - auto result = js_undefined(); - TRY(for_each_item_from_last(vm, "findLast", [&](auto, auto value, auto callback_result) { - if (callback_result.to_boolean()) { - result = value; - return IterationDecision::Break; - } - return IterationDecision::Continue; - })); - return result; + auto this_arg = vm.argument(1); + + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). + auto find_record = TRY(find_via_predicate(vm, *typed_array, length, Direction::Descending, this_arg, "findLast"sv)); + + // 5. Return findRec.[[Value]]. + return find_record.value; } // 23.2.3.14 %TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlastindex JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_last_index) { - auto result_index = -1; - TRY(for_each_item_from_last(vm, "findLastIndex", [&](auto index, auto, auto callback_result) { - if (callback_result.to_boolean()) { - result_index = index; - return IterationDecision::Break; - } - return IterationDecision::Continue; - })); - return Value(result_index); + auto this_arg = vm.argument(1); + + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. Let findRec be ? FindViaPredicate(O, len, descending, predicate, thisArg). + auto find_record = TRY(find_via_predicate(vm, *typed_array, length, Direction::Descending, this_arg, "findLastIndex"sv)); + + // 5. Return findRec.[[Index]]. + return find_record.index_to_value(); } // 23.2.3.15 %TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::for_each) { - TRY(for_each_item(vm, "forEach", [](auto, auto, auto) { - return IterationDecision::Continue; - })); + auto this_arg = vm.argument(1); + + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + auto callback_function = TRY(callback_from_args(vm, "forEach"sv)); + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + + // b. Let kValue be ! Get(O, Pk). + auto value = MUST(typed_array->get(property_key)); + + // c. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + TRY(call(vm, *callback_function, this_arg, value, Value { k }, typed_array)); + + // d. Set k to k + 1. + } + + // 7. Return undefined. return js_undefined(); } // 23.2.3.16 %TypedArray%.prototype.includes ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::includes) { + auto search_element = vm.argument(0); + auto from_index = vm.argument(1); + // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); - // 4. If len is 0, return false. + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. If len = 0, return false. if (length == 0) - return Value(false); + return Value { false }; // 5. Let n be ? ToIntegerOrInfinity(fromIndex). - auto n = TRY(vm.argument(1).to_integer_or_infinity(vm)); + auto n = TRY(from_index.to_integer_or_infinity(vm)); // 6. Assert: If fromIndex is undefined, then n is 0. - if (vm.argument(1).is_undefined()) + if (from_index.is_undefined()) VERIFY(n == 0); - auto value_n = Value(n); - // 7. If n is +∞, return false. + Value value_n { n }; + // 7. If n = +∞, return false. if (value_n.is_positive_infinity()) - return Value(false); - // 8. Else if n is -∞, set n to 0. + return Value { false }; + // 8. Else if n = -∞, set n to 0. else if (value_n.is_negative_infinity()) n = 0; @@ -696,57 +845,63 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::includes) // 10. Else, else { // a. Let k be len + n. - auto relative_k = length + n; + auto relative_k = length + n; // Ensures we dont overflow `k`. // b. If k < 0, set k to 0. if (relative_k < 0) relative_k = 0; + k = relative_k; } - auto search_element = vm.argument(0); // 11. Repeat, while k < len, - for (; k < length; ++k) { + while (k < length) { // a. Let elementK be ! Get(O, ! ToString(𝔽(k))). auto element_k = MUST(typed_array->get(k)); // b. If SameValueZero(searchElement, elementK) is true, return true. if (same_value_zero(search_element, element_k)) - return Value(true); + return Value { true }; // c. Set k to k + 1. + ++k; } // 12. Return false. - return Value(false); + return Value { false }; } // 23.2.3.17 %TypedArray%.prototype.indexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::index_of) { + auto search_element = vm.argument(0); + auto from_index = vm.argument(1); + // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); - // 4. If len is 0, return -1𝔽. + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. If len = 0, return -1𝔽. if (length == 0) - return Value(-1); + return Value { -1 }; // 5. Let n be ? ToIntegerOrInfinity(fromIndex). - auto n = TRY(vm.argument(1).to_integer_or_infinity(vm)); + auto n = TRY(from_index.to_integer_or_infinity(vm)); // 6. Assert: If fromIndex is undefined, then n is 0. - if (vm.argument(1).is_undefined()) + if (from_index.is_undefined()) VERIFY(n == 0); - auto value_n = Value(n); - // 7. If n is +∞, return -1𝔽. + Value value_n { n }; + // 7. If n = +∞, return -1𝔽. if (value_n.is_positive_infinity()) - return Value(-1); - // 8. Else if n is -∞, set n to 0. + return Value { -1 }; + // 8. Else if n = -∞, set n to 0. else if (value_n.is_negative_infinity()) n = 0; @@ -764,12 +919,12 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::index_of) // b. If k < 0, set k to 0. if (relative_k < 0) relative_k = 0; + k = relative_k; } // 11. Repeat, while k < len, - auto search_element = vm.argument(0); - for (; k < length; ++k) { + while (k < length) { // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). auto k_present = MUST(typed_array->has_property(k)); @@ -778,52 +933,60 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::index_of) // i. Let elementK be ! Get(O, ! ToString(𝔽(k))). auto element_k = MUST(typed_array->get(k)); - // ii. Let same be IsStrictlyEqual(searchElement, elementK). - // iii. If same is true, return 𝔽(k). + // ii. If IsStrictlyEqual(searchElement, elementK) is true, return 𝔽(k). if (is_strictly_equal(search_element, element_k)) - return Value(k); + return Value { k }; } // c. Set k to k + 1. + ++k; } // 12. Return -1𝔽. - return Value(-1); + return Value { -1 }; } // 23.2.3.18 %TypedArray%.prototype.join ( separator ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.join JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::join) { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto separator = vm.argument(0); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + String sep {}; // 4. If separator is undefined, let sep be ",". + if (separator.is_undefined()) + sep = String::from_code_point(','); // 5. Else, let sep be ? ToString(separator). - ByteString separator = ","; - if (!vm.argument(0).is_undefined()) - separator = TRY(vm.argument(0).to_byte_string(vm)); + else + sep = TRY(separator.to_string(vm)); // 6. Let R be the empty String. StringBuilder builder; // 7. Let k be 0. // 8. Repeat, while k < len, - for (size_t i = 0; i < length; ++i) { + for (size_t k = 0; k < length; ++k) { // a. If k > 0, set R to the string-concatenation of R and sep. - if (i > 0) - builder.append(separator); + if (k > 0) + builder.append(sep); // b. Let element be ! Get(O, ! ToString(𝔽(k))). - auto element = MUST(typed_array->get(i)); + auto element = MUST(typed_array->get(k)); + + String next {}; // c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element). - if (element.is_undefined()) - continue; - auto next = MUST(element.to_byte_string(vm)); + if (!element.is_undefined()) + next = MUST(element.to_string(vm)); // d. Set R to the string-concatenation of R and next. builder.append(next); @@ -832,7 +995,7 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::join) } // 9. Return R. - return PrimitiveString::create(vm, builder.to_byte_string()); + return PrimitiveString::create(vm, MUST(builder.to_string())); } // 23.2.3.19 %TypedArray%.prototype.keys ( ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys @@ -841,8 +1004,10 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::keys) auto& realm = *vm.current_realm(); // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Perform ? ValidateTypedArray(O, seq-cst). + (void)TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); // 3. Return CreateArrayIterator(O, key). return ArrayIterator::create(realm, typed_array, Object::PropertyKind::Key); @@ -855,18 +1020,19 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::last_index_of) auto from_index = vm.argument(1); // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. If len = 0, return -1𝔽. if (length == 0) - return Value(-1); + return Value { -1 }; double n; - // 5. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. if (vm.argument_count() > 1) n = TRY(from_index.to_integer_or_infinity(vm)); @@ -874,11 +1040,10 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::last_index_of) n = length - 1; // 6. If n = -∞, return -1𝔽. - if (Value(n).is_negative_infinity()) - return Value(-1); + if (Value { n }.is_negative_infinity()) + return Value { -1 }; i32 k; - // 7. If n ≥ 0, then if (n >= 0) { // a. Let k be min(n, len - 1). @@ -887,14 +1052,16 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::last_index_of) // 8. Else, else { // a. Let k be len + n. - auto relative_k = length + n; - if (relative_k < 0) // ensures we dont underflow `k` + auto relative_k = length + n; // Ensures we dont overflow `k`. + + if (relative_k < 0) relative_k = -1; + k = relative_k; } // 9. Repeat, while k ≥ 0, - for (; k >= 0; --k) { + while (k >= 0) { // a. Let kPresent be ! HasProperty(O, ! ToString(𝔽(k))). auto k_present = MUST(typed_array->has_property(k)); @@ -905,14 +1072,15 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::last_index_of) // ii. If IsStrictlyEqual(searchElement, elementK) is true, return 𝔽(k). if (is_strictly_equal(search_element, element_k)) - return Value(k); + return Value { k }; } // c. Set k to k - 1. + --k; } // 10. Return -1𝔽. - return Value(-1); + return Value { -1 }; } // 23.2.3.21 get %TypedArray%.prototype.length, https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length @@ -920,76 +1088,83 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::length_getter) { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Assert: O has [[ViewedArrayBuffer]] and [[ArrayLength]] internal slots. - // 4. Let buffer be O.[[ViewedArrayBuffer]]. - auto* buffer = typed_array->viewed_array_buffer(); - VERIFY(buffer); + // 4. Let taRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + auto typed_array_record = make_typed_array_with_buffer_witness_record(*typed_array, ArrayBuffer::Order::SeqCst); - // 5. If IsDetachedBuffer(buffer) is true, return +0𝔽. - if (buffer->is_detached()) - return Value(0); + // 5. If IsTypedArrayOutOfBounds(taRecord) is true, return +0𝔽. + if (is_typed_array_out_of_bounds(typed_array_record)) + return Value { 0 }; - // 6. Let length be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 6. Let length be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 7. Return 𝔽(length). - return Value(length); + return Value { length }; } // 23.2.3.22 %TypedArray%.prototype.map ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.map JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::map) { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto this_arg = vm.argument(1); - // 3. Let len be O.[[ArrayLength]]. - auto initial_length = typed_array->array_length(); + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - auto* callback_function = TRY(callback_from_args(vm, "map")); + auto callback_function = TRY(callback_from_args(vm, "map"sv)); // 5. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(len) »). MarkedVector arguments(vm.heap()); - arguments.empend(initial_length); - auto* return_array = TRY(typed_array_species_create(vm, *typed_array, move(arguments))); - - auto this_value = vm.argument(1); + arguments.empend(length); + auto* array = TRY(typed_array_species_create(vm, *typed_array, move(arguments))); // 6. Let k be 0. // 7. Repeat, while k < len, - for (size_t i = 0; i < initial_length; ++i) { + for (size_t k = 0; k < length; ++k) { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Let kValue be ! Get(O, Pk). - auto value = MUST(typed_array->get(i)); + auto value = MUST(typed_array->get(property_key)); // c. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). - auto mapped_value = TRY(call(vm, *callback_function, this_value, value, Value((i32)i), typed_array)); + auto mapped_value = TRY(call(vm, *callback_function, this_arg, value, Value { k }, typed_array)); // d. Perform ? Set(A, Pk, mappedValue, true). - TRY(return_array->set(i, mapped_value, Object::ShouldThrowExceptions::Yes)); + TRY(array->set(property_key, mapped_value, Object::ShouldThrowExceptions::Yes)); // e. Set k to k + 1. } // 8. Return A. - return return_array; + return array; } // 23.2.3.23 %TypedArray%.prototype.reduce ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::reduce) { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto initial_value = vm.argument(1); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - auto* callback_function = TRY(callback_from_args(vm, vm.names.reduce.as_string())); + auto callback_function = TRY(callback_from_args(vm, "reduce"sv)); // 5. If len = 0 and initialValue is not present, throw a TypeError exception. if (length == 0 && vm.argument_count() <= 1) @@ -999,87 +1174,101 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::reduce) u32 k = 0; // 7. Let accumulator be undefined. - Value accumulator; + auto accumulator = js_undefined(); // 8. If initialValue is present, then if (vm.argument_count() > 1) { // a. Set accumulator to initialValue. - accumulator = vm.argument(1); + accumulator = initial_value; } // 9. Else, else { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Set accumulator to ! Get(O, Pk). - accumulator = MUST(typed_array->get(k)); + accumulator = MUST(typed_array->get(property_key)); // c. Set k to k + 1. ++k; } // 10. Repeat, while k < len, - for (; k < length; ++k) { + while (k < length) { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Let kValue be ! Get(O, Pk). - auto k_value = MUST(typed_array->get(k)); + auto value = MUST(typed_array->get(k)); // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). - accumulator = TRY(call(vm, *callback_function, js_undefined(), accumulator, k_value, Value(k), typed_array)); + accumulator = TRY(call(vm, *callback_function, js_undefined(), accumulator, value, Value { k }, typed_array)); // d. Set k to k + 1. + ++k; } // 11. Return accumulator. return accumulator; } -// 23.2.3.24 %TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce +// 23.2.3.24 %TypedArray%.prototype.reduceRight ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::reduce_right) { - // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto initial_value = vm.argument(1); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. - auto* callback_function = TRY(callback_from_args(vm, vm.names.reduce.as_string())); + auto callback_function = TRY(callback_from_args(vm, "reduceRight"sv)); - // 5. If len is 0 and initialValue is not present, throw a TypeError exception. + // 5. If len = 0 and initialValue is not present, throw a TypeError exception. if (length == 0 && vm.argument_count() <= 1) return vm.throw_completion(ErrorType::ReduceNoInitial); // 6. Let k be len - 1. - i32 k = (i32)length - 1; + auto k = static_cast(length) - 1; // 7. Let accumulator be undefined. - Value accumulator; + auto accumulator = js_undefined(); // 8. If initialValue is present, then if (vm.argument_count() > 1) { // a. Set accumulator to initialValue. - accumulator = vm.argument(1); + accumulator = initial_value; } // 9. Else, else { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Set accumulator to ! Get(O, Pk). - accumulator = MUST(typed_array->get(k)); + accumulator = MUST(typed_array->get(property_key)); // c. Set k to k - 1. --k; } // 10. Repeat, while k ≥ 0, - for (; k >= 0; --k) { + while (k >= 0) { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Let kValue be ! Get(O, Pk). - auto k_value = MUST(typed_array->get(k)); + auto value = MUST(typed_array->get(k)); // c. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). - accumulator = TRY(call(vm, *callback_function, js_undefined(), accumulator, k_value, Value(k), typed_array)); + accumulator = TRY(call(vm, *callback_function, js_undefined(), accumulator, value, Value { k }, typed_array)); // d. Set k to k - 1. + --k; } // 11. Return accumulator. @@ -1090,34 +1279,40 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::reduce_right) JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::reverse) { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. Let middle be floor(len / 2). auto middle = length / 2; // 5. Let lower be 0. // 6. Repeat, while lower ≠ middle, - for (size_t lower = 0; lower != middle; ++lower) { + for (u32 lower = 0; lower != middle; ++lower) { // a. Let upper be len - lower - 1. auto upper = length - lower - 1; // b. Let upperP be ! ToString(𝔽(upper)). - // d. Let lowerValue be ! Get(O, lowerP). - auto lower_value = MUST(typed_array->get(lower)); + PropertyKey upper_property_key { upper }; // c. Let lowerP be ! ToString(𝔽(lower)). + PropertyKey lower_property_key { lower }; + + // d. Let lowerValue be ! Get(O, lowerP). + auto lower_value = MUST(typed_array->get(lower_property_key)); + // e. Let upperValue be ! Get(O, upperP). - auto upper_value = MUST(typed_array->get(upper)); + auto upper_value = MUST(typed_array->get(upper_property_key)); // f. Perform ! Set(O, lowerP, upperValue, true). - MUST(typed_array->set(lower, upper_value, Object::ShouldThrowExceptions::Yes)); + MUST(typed_array->set(lower_property_key, upper_value, Object::ShouldThrowExceptions::Yes)); // g. Perform ! Set(O, upperP, lowerValue, true). - MUST(typed_array->set(upper, lower_value, Object::ShouldThrowExceptions::Yes)); + MUST(typed_array->set(upper_property_key, lower_value, Object::ShouldThrowExceptions::Yes)); // h. Set lower to lower + 1. } @@ -1127,68 +1322,74 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::reverse) } // 23.2.3.26.1 SetTypedArrayFromTypedArray ( target, targetOffset, source ), https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray -static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArrayBase& target, double target_offset, TypedArrayBase& source) +static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArrayBase& target, double target_offset, TypedArrayBase const& source) { // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. auto* target_buffer = target.viewed_array_buffer(); - // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. - if (target_buffer->is_detached()) - return vm.throw_completion(ErrorType::DetachedArrayBuffer); + // 2. Let targetRecord be MakeTypedArrayWithBufferWitnessRecord(target, seq-cst) + auto target_record = make_typed_array_with_buffer_witness_record(target, ArrayBuffer::Order::SeqCst); - // 3. Let targetLength be target.[[ArrayLength]]. - auto target_length = target.array_length(); + // 3. If IsTypedArrayOutOfBounds(targetRecord) is true, throw a TypeError exception. + if (is_typed_array_out_of_bounds(target_record)) + return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); - // 4. Let srcBuffer be source.[[ViewedArrayBuffer]]. + // 4. Let targetLength be TypedArrayLength(targetRecord). + auto target_length = typed_array_length(target_record); + + // 5. Let srcBuffer be source.[[ViewedArrayBuffer]]. auto* source_buffer = source.viewed_array_buffer(); - // 5. If IsDetachedBuffer(srcBuffer) is true, throw a TypeError exception. - if (source_buffer->is_detached()) - return vm.throw_completion(ErrorType::DetachedArrayBuffer); + // 6. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(source, seq-cst). + auto source_record = make_typed_array_with_buffer_witness_record(source, ArrayBuffer::Order::SeqCst); - // 6. Let targetType be TypedArrayElementType(target). - // 7. Let targetElementSize be TypedArrayElementSize(target). + // 7. If IsTypedArrayOutOfBounds(srcRecord) is true, throw a TypeError exception. + if (is_typed_array_out_of_bounds(source_record)) + return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); + + // 8. Let srcLength be TypedArrayLength(srcRecord). + auto source_length = typed_array_length(source_record); + + // 9. Let targetType be TypedArrayElementType(target). + // 10. Let targetElementSize be TypedArrayElementSize(target). auto target_element_size = target.element_size(); - // 8. Let targetByteOffset be target.[[ByteOffset]]. + // 11. Let targetByteOffset be target.[[ByteOffset]]. auto target_byte_offset = target.byte_offset(); - // 9. Let srcType be TypedArrayElementType(source). - // 10. Let srcElementSize be TypedArrayElementSize(source). + // 12. Let srcType be TypedArrayElementType(source). + // 13. Let srcElementSize be TypedArrayElementSize(source). auto source_element_size = source.element_size(); - // 11. Let srcLength be source.[[ArrayLength]]. - auto source_length = source.array_length(); - - // 12. Let srcByteOffset be source.[[ByteOffset]]. + // 14. Let srcByteOffset be source.[[ByteOffset]]. auto source_byte_offset = source.byte_offset(); - // 13. If targetOffset is +∞, throw a RangeError exception. - if (isinf(target_offset)) + // 15. If targetOffset = +∞, throw a RangeError exception. + if (Value { target_offset }.is_positive_infinity()) return vm.throw_completion(ErrorType::TypedArrayInvalidTargetOffset, "finite"); - // 14. If srcLength + targetOffset > targetLength, throw a RangeError exception. + // 16. If srcLength + targetOffset > targetLength, throw a RangeError exception. Checked checked = source_length; checked += static_cast(target_offset); if (checked.has_overflow() || checked.value() > target_length) return vm.throw_completion(ErrorType::TypedArrayOverflowOrOutOfBounds, "target length"); - // 15. If target.[[ContentType]] ≠ source.[[ContentType]], throw a TypeError exception. + // 17. If target.[[ContentType]] is not source.[[ContentType]], throw a TypeError exception. if (target.content_type() != source.content_type()) return vm.throw_completion(ErrorType::TypedArrayInvalidCopy, target.class_name(), source.class_name()); - // FIXME: 16. If both IsSharedArrayBuffer(srcBuffer) and IsSharedArrayBuffer(targetBuffer) are true, then - // FIXME: a. If srcBuffer.[[ArrayBufferData]] and targetBuffer.[[ArrayBufferData]] are the same Shared Data Block values, let same be true; else let same be false. + auto same_shared_array_buffer = false; - // 17. Else, let same be SameValue(srcBuffer, targetBuffer). - auto same = same_value(source_buffer, target_buffer); + // 18. If IsSharedArrayBuffer(srcBuffer) is true, IsSharedArrayBuffer(targetBuffer) is true, and srcBuffer.[[ArrayBufferData]] is targetBuffer.[[ArrayBufferData]], let sameSharedArrayBuffer be true; otherwise, let sameSharedArrayBuffer be false. + if (source_buffer->is_shared_array_buffer() && target_buffer->is_shared_array_buffer() && (&source_buffer->buffer() == &target_buffer->buffer())) + same_shared_array_buffer = true; size_t source_byte_index = 0; - // 18. If same is true, then - if (same) { - // a. Let srcByteLength be source.[[ByteLength]]. - auto source_byte_length = source.byte_length(); + // 19. If SameValue(srcBuffer, targetBuffer) is true or sameSharedArrayBuffer is true, then + if (same_shared_array_buffer || same_value(source_buffer, target_buffer)) { + // a. Let srcByteLength be TypedArrayByteLength(srcRecord). + auto source_byte_length = typed_array_byte_length(source_record); // b. Set srcBuffer to ? CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength). source_buffer = TRY(clone_array_buffer(vm, *source_buffer, source_byte_offset, source_byte_length)); @@ -1196,12 +1397,13 @@ static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArr // c. Let srcByteIndex be 0. source_byte_index = 0; } - // 19. Else, let srcByteIndex be srcByteOffset. + // 20. Else, else { + // a. Let srcByteIndex be srcByteOffset. source_byte_index = source_byte_offset; } - // 20. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. + // 21. Let targetByteIndex be targetOffset × targetElementSize + targetByteOffset. Checked checked_target_byte_index(static_cast(target_offset)); checked_target_byte_index *= target_element_size; checked_target_byte_index += target_byte_offset; @@ -1209,7 +1411,7 @@ static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArr return vm.throw_completion(ErrorType::TypedArrayOverflow, "target byte index"); auto target_byte_index = checked_target_byte_index.value(); - // 21. Let limit be targetByteIndex + targetElementSize × srcLength. + // 22. Let limit be targetByteIndex + targetElementSize × srcLength. Checked checked_limit(source_length); checked_limit *= target_element_size; checked_limit += target_byte_index; @@ -1217,9 +1419,9 @@ static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArr return vm.throw_completion(ErrorType::TypedArrayOverflow, "target limit"); auto limit = checked_limit.value(); - // 22. If srcType is the same as targetType, then + // 23. If srcType is targetType, then if (source.element_name() == target.element_name()) { - // a. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. + // a. NOTE: The transfer must be performed in a manner that preserves the bit-level encoding of the source data. // b. Repeat, while targetByteIndex < limit, // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). @@ -1227,46 +1429,49 @@ static ThrowCompletionOr set_typed_array_from_typed_array(VM& vm, TypedArr // iv. Set targetByteIndex to targetByteIndex + 1. target_buffer->buffer().overwrite(target_byte_index, source_buffer->buffer().data() + source_byte_index, limit - target_byte_index); } - // 23. Else, + // 24. Else, else { // a. Repeat, while targetByteIndex < limit, while (target_byte_index < limit) { // i. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, Unordered). auto value = source.get_value_from_buffer(source_byte_index, ArrayBuffer::Unordered); + // ii. Perform SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, Unordered). target.set_value_in_buffer(target_byte_index, value, ArrayBuffer::Unordered); + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. source_byte_index += source_element_size; + // iv. Set targetByteIndex to targetByteIndex + targetElementSize. - target_byte_index += target.element_size(); + target_byte_index += target_element_size; } } - // 24. Return unused. + // 25. Return unused. return {}; } // 23.2.3.26.2 SetTypedArrayFromArrayLike ( target, targetOffset, source ), https://tc39.es/ecma262/#sec-settypedarrayfromarraylike static ThrowCompletionOr set_typed_array_from_array_like(VM& vm, TypedArrayBase& target, double target_offset, Value source) { - // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. - auto* target_buffer = target.viewed_array_buffer(); + // 1. Let targetRecord be MakeTypedArrayWithBufferWitnessRecord(target, seq-cst) + auto target_record = make_typed_array_with_buffer_witness_record(target, ArrayBuffer::Order::SeqCst); - // 2. If IsDetachedBuffer(targetBuffer) is true, throw a TypeError exception. - if (target_buffer->is_detached()) - return vm.throw_completion(ErrorType::DetachedArrayBuffer); + // 2. If IsTypedArrayOutOfBounds(targetRecord) is true, throw a TypeError exception. + if (is_typed_array_out_of_bounds(target_record)) + return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); - // 3. Let targetLength be target.[[ArrayLength]]. - auto target_length = target.array_length(); + // 3. Let targetLength be TypedArrayLength(targetRecord). + auto target_length = typed_array_length(target_record); // 4. Let src be ? ToObject(source). - auto src = TRY(source.to_object(vm)); + auto source_object = TRY(source.to_object(vm)); // 5. Let srcLength be ? LengthOfArrayLike(src). - auto source_length = TRY(length_of_array_like(vm, src)); + auto source_length = TRY(length_of_array_like(vm, source_object)); - // 6. If targetOffset is +∞, throw a RangeError exception. - if (isinf(target_offset)) + // 6. If targetOffset = +∞, throw a RangeError exception. + if (Value { target_offset }.is_positive_infinity()) return vm.throw_completion(ErrorType::TypedArrayInvalidTargetOffset, "finite"); // 7. If srcLength + targetOffset > targetLength, throw a RangeError exception. @@ -1281,8 +1486,10 @@ static ThrowCompletionOr set_typed_array_from_array_like(VM& vm, TypedArra // 9. Repeat, while k < srcLength, while (k < source_length) { // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + // b. Let value be ? Get(src, Pk). - auto value = TRY(src->get(k)); + auto value = TRY(source_object->get(property_key)); // c. Let targetIndex be 𝔽(targetOffset + k). // NOTE: We verify above that target_offset + source_length is valid, so this cannot fail. @@ -1308,15 +1515,15 @@ static ThrowCompletionOr set_typed_array_from_array_like(VM& vm, TypedArra JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::set) { auto source = vm.argument(0); + auto offset = vm.argument(1); // 1. Let target be the this value. // 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]). + // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Assert: target has a [[ViewedArrayBuffer]] internal slot. - // 4. Let targetOffset be ? ToIntegerOrInfinity(offset). - auto target_offset = TRY(vm.argument(1).to_integer_or_infinity(vm)); + auto target_offset = TRY(offset.to_integer_or_infinity(vm)); // 5. If targetOffset < 0, throw a RangeError exception. if (target_offset < 0) @@ -1346,19 +1553,20 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::slice) auto end = vm.argument(1); // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. Let relativeStart be ? ToIntegerOrInfinity(start). auto relative_start = TRY(start.to_integer_or_infinity(vm)); i32 k = 0; - - // 5. If relativeStart is -∞, let k be 0. - if (Value(relative_start).is_negative_infinity()) + // 5. If relativeStart = -∞, let k be 0. + if (Value { relative_start }.is_negative_infinity()) k = 0; // 6. Else if relativeStart < 0, let k be max(len + relativeStart, 0). else if (relative_start < 0) @@ -1368,7 +1576,6 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::slice) k = min(relative_start, length); double relative_end = 0; - // 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). if (end.is_undefined()) relative_end = length; @@ -1376,9 +1583,8 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::slice) relative_end = TRY(end.to_integer_or_infinity(vm)); i32 final = 0; - // 9. If relativeEnd is -∞, let final be 0. - if (Value(relative_end).is_negative_infinity()) + if (Value { relative_end }.is_negative_infinity()) final = 0; // 10. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). else if (relative_end < 0) @@ -1393,122 +1599,174 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::slice) // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). MarkedVector arguments(vm.heap()); arguments.empend(count); - auto* new_array = TRY(typed_array_species_create(vm, *typed_array, move(arguments))); + auto* array = TRY(typed_array_species_create(vm, *typed_array, move(arguments))); // 14. If count > 0, then if (count > 0) { - // a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. - if (typed_array->viewed_array_buffer()->is_detached()) - return vm.throw_completion(ErrorType::DetachedArrayBuffer); + // a. Set taRecord to MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + typed_array_record = make_typed_array_with_buffer_witness_record(*typed_array, ArrayBuffer::Order::SeqCst); - // b. Let srcType be TypedArrayElementType(O). - // c. Let targetType be TypedArrayElementType(A). + // b. If IsTypedArrayOutOfBounds(taRecord) is true, throw a TypeError exception. + if (is_typed_array_out_of_bounds(typed_array_record)) + return vm.throw_completion(ErrorType::BufferOutOfBounds, "TypedArray"sv); - // d. If srcType is different from targetType, then - if (typed_array->element_name() != new_array->element_name()) { - // i. Let n be 0. - // ii. Repeat, while k < final, - for (i32 n = 0; k < final; ++k, ++n) { - // 1. Let Pk be ! ToString(𝔽(k)). - // 2. Let kValue be ! Get(O, Pk). - auto k_value = MUST(typed_array->get(k)); + // c. Set len to TypedArrayLength(taRecord). + length = typed_array_length(typed_array_record); - // 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true). - MUST(new_array->set(n, k_value, Object::ShouldThrowExceptions::Yes)); + // d. Set final to min(final, len). + final = min(final, length); - // 4. Set k to k + 1. - // 5. Set n to n + 1. - } - } - // e. Else, - else { - // i. Let srcBuffer be O.[[ViewedArrayBuffer]]. + // FIXME: Spec issue: If the TypedArray length changed, the count must also be updated. + // https://github.com/tc39/ecma262/issues/3248 + count = max(final - k, 0); + + // e. Let srcType be TypedArrayElementType(O). + // f. Let targetType be TypedArrayElementType(A). + + // g. If srcType is targetType, then + if (typed_array->element_name() == array->element_name()) { + // i. NOTE: The transfer must be performed in a manner that preserves the bit-level encoding of the source data. + + // ii. Let srcBuffer be O.[[ViewedArrayBuffer]]. auto& source_buffer = *typed_array->viewed_array_buffer(); - // ii. Let targetBuffer be A.[[ViewedArrayBuffer]]. - auto& target_buffer = *new_array->viewed_array_buffer(); + // iii. Let targetBuffer be A.[[ViewedArrayBuffer]]. + auto& target_buffer = *array->viewed_array_buffer(); - // iii. Let elementSize be TypedArrayElementSize(O). + // iv. Let elementSize be TypedArrayElementSize(O). auto element_size = typed_array->element_size(); - // iv. NOTE: If srcType and targetType are the same, the transfer must be performed in a manner that preserves the bit-level encoding of the source data. - // v. Let srcByteOffset be O.[[ByteOffset]]. auto source_byte_offset = typed_array->byte_offset(); - // vi. Let targetByteIndex be A.[[ByteOffset]]. - auto target_byte_index = new_array->byte_offset(); - - // vii. Let srcByteIndex be (k × elementSize) + srcByteOffset. + // vi. Let srcByteIndex be (k × elementSize) + srcByteOffset. Checked source_byte_index = k; source_byte_index *= element_size; source_byte_index += source_byte_offset; if (source_byte_index.has_overflow()) { dbgln("TypedArrayPrototype::slice: source_byte_index overflowed, returning as if succeeded."); - return new_array; + return array; } - // viii. Let limit be targetByteIndex + count × elementSize. - Checked limit = count; + // vii. Let targetByteIndex be A.[[ByteOffset]]. + auto target_byte_index = array->byte_offset(); + + // viii. Let limit be targetByteIndex + min(count, len) × elementSize. + Checked limit = min(count, length); limit *= element_size; limit += target_byte_index; if (limit.has_overflow()) { dbgln("TypedArrayPrototype::slice: limit overflowed, returning as if succeeded."); - return new_array; + return array; } // ix. Repeat, while targetByteIndex < limit, - for (; target_byte_index < limit.value(); ++source_byte_index, ++target_byte_index) { - // 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, Uint8, true, Unordered). + while (target_byte_index < limit) { + // 1. Let value be GetValueFromBuffer(srcBuffer, srcByteIndex, uint8, true, unordered). auto value = source_buffer.get_value(source_byte_index.value(), true, ArrayBuffer::Unordered); - // 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, Uint8, value, true, Unordered). + // 2. Perform SetValueInBuffer(targetBuffer, targetByteIndex, uint8, value, true, unordered). target_buffer.set_value(target_byte_index, value, true, ArrayBuffer::Unordered); // 3. Set srcByteIndex to srcByteIndex + 1. + ++source_byte_index; + // 4. Set targetByteIndex to targetByteIndex + 1. + ++target_byte_index; + } + } + // h. Else, + else { + // i. Let n be 0. + u32 n = 0; + + // ii. Repeat, while k < final, + while (k < final) { + // 1. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + + // 2. Let kValue be ! Get(O, Pk). + auto value = MUST(typed_array->get(property_key)); + + // 3. Perform ! Set(A, ! ToString(𝔽(n)), kValue, true). + MUST(array->set(n, value, Object::ShouldThrowExceptions::Yes)); + + // 4. Set k to k + 1. + ++k; + + // 5. Set n to n + 1. + ++n; } } } // 15. Return A. - return new_array; + return array; } // 23.2.3.28 %TypedArray%.prototype.some ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.some JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::some) { - auto result = false; - TRY(for_each_item(vm, "some", [&](auto, auto, auto callback_result) { - if (callback_result.to_boolean()) { - result = true; - return IterationDecision::Break; - } - return IterationDecision::Continue; - })); - return Value(result); + auto this_arg = vm.argument(1); + + // 1. Let O be the this value. + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + auto callback_function = TRY(callback_from_args(vm, "some"sv)); + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + PropertyKey property_key { k }; + + // b. Let kValue be ! Get(O, Pk). + auto value = MUST(typed_array->get(property_key)); + + // c. Let testResult be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(vm, *callback_function, this_arg, value, Value { k }, typed_array)).to_boolean(); + + // d. If testResult is true, return true. + if (test_result) + return true; + + // e. Set k to k + 1. + } + + // 7. Return false. + return false; } // 23.2.3.29 %TypedArray%.prototype.sort ( comparefn ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::sort) { + auto compare_function = vm.argument(0); + // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - auto compare_fn = vm.argument(0); - if (!compare_fn.is_undefined() && !compare_fn.is_function()) - return vm.throw_completion(ErrorType::NotAFunction, compare_fn.to_string_without_side_effects()); + if (!compare_function.is_undefined() && !compare_function.is_function()) + return vm.throw_completion(ErrorType::NotAFunction, compare_function); // 2. Let obj be the this value. - // 3. Perform ? ValidateTypedArray(obj). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 4. Let len be obj.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 3. Let taRecord be ? ValidateTypedArray(obj, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 4. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 5. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.30. // 6. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: Function(Value, Value)> sort_compare = [&](auto x, auto y) -> ThrowCompletionOr { // a. Return ? CompareTypedArrayElements(x, y, comparefn). - return TRY(compare_typed_array_elements(vm, x, y, compare_fn.is_undefined() ? nullptr : &compare_fn.as_function())); + return TRY(compare_typed_array_elements(vm, x, y, compare_function.is_undefined() ? nullptr : &compare_function.as_function())); }; // 7. Let sortedList be ? SortIndexedProperties(obj, len, SortCompare, read-through-holes). @@ -1519,6 +1777,7 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::sort) for (size_t j = 0; j < length; j++) { // a. Perform ! Set(obj, ! ToString(𝔽(j)), sortedList[j], true). MUST(typed_array->set(j, sorted_list[j], Object::ShouldThrowExceptions::Yes)); + // b. Set j to j + 1. } @@ -1534,21 +1793,32 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::subarray) // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]). + // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. auto* buffer = typed_array->viewed_array_buffer(); - // 5. Let srcLength be O.[[ArrayLength]]. - auto source_length = typed_array->array_length(); + // 5. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst). + auto source_record = make_typed_array_with_buffer_witness_record(*typed_array, ArrayBuffer::Order::SeqCst); - // 6. Let relativeBegin be ? ToIntegerOrInfinity(begin). + u32 source_length = 0; + // 6. If IsTypedArrayOutOfBounds(srcRecord) is true, then + if (is_typed_array_out_of_bounds(source_record)) { + // a. Let srcLength be 0. + source_length = 0; + } + // 7. Else, + else { + // a. Let srcLength be TypedArrayLength(srcRecord). + source_length = typed_array_length(source_record); + } + + // 8. Let relativeBegin be ? ToIntegerOrInfinity(begin). auto relative_begin = TRY(begin.to_integer_or_infinity(vm)); i32 begin_index = 0; - - // 7. If relativeBegin is -∞, let beginIndex be 0. + // 7. If relativeBegin = -∞, let beginIndex be 0. if (Value(relative_begin).is_negative_infinity()) begin_index = 0; // 8. Else if relativeBegin < 0, let beginIndex be max(srcLength + relativeBegin, 0). @@ -1558,47 +1828,59 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::subarray) else begin_index = min(relative_begin, source_length); - double relative_end = 0; + // 12. Let elementSize be TypedArrayElementSize(O). + auto element_size = typed_array->element_size(); - // 10. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end). - if (end.is_undefined()) - relative_end = source_length; - else - relative_end = TRY(end.to_integer_or_infinity(vm)); + // 13. Let srcByteOffset be O.[[ByteOffset]]. + auto source_byte_offset = typed_array->byte_offset(); - i32 end_index = 0; - - // 11. If relativeEnd is -∞, let endIndex be 0. - if (Value(relative_end).is_negative_infinity()) - end_index = 0; - // 12. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0). - else if (relative_end < 0) - end_index = max(source_length + relative_end, 0); - // 13. Else, let endIndex be min(relativeEnd, srcLength). - else - end_index = min(relative_end, source_length); - - // 14. Let newLength be max(endIndex - beginIndex, 0). - auto new_length = max(end_index - begin_index, 0); - - // 15. Let elementSize be TypedArrayElementSize(O). - // 16. Let srcByteOffset be O.[[ByteOffset]]. - // 17. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. + // 14. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. Checked begin_byte_offset = begin_index; - begin_byte_offset *= typed_array->element_size(); - begin_byte_offset += typed_array->byte_offset(); + begin_byte_offset *= element_size; + begin_byte_offset += source_byte_offset; if (begin_byte_offset.has_overflow()) { dbgln("TypedArrayPrototype::begin_byte_offset: limit overflowed, returning as if succeeded."); return typed_array; } - // 18. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». MarkedVector arguments(vm.heap()); - arguments.empend(buffer); - arguments.empend(begin_byte_offset.value()); - arguments.empend(new_length); - // 19. Return ? TypedArraySpeciesCreate(O, argumentsList). + // 15. If O.[[ArrayLength]] is auto and end is undefined, then + if (typed_array->array_length().is_auto()) { + // a. Let argumentsList be « buffer, 𝔽(beginByteOffset) ». + arguments.empend(buffer); + arguments.empend(begin_byte_offset.value()); + } + // 16. Else, + else { + double relative_end = 0; + // a. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end). + if (end.is_undefined()) + relative_end = source_length; + else + relative_end = TRY(end.to_integer_or_infinity(vm)); + + i32 end_index = 0; + // 11. If relativeEnd = -∞, let endIndex be 0. + if (Value(relative_end).is_negative_infinity()) + end_index = 0; + // 12. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0). + else if (relative_end < 0) + end_index = max(source_length + relative_end, 0); + // 13. Else, let endIndex be min(relativeEnd, srcLength). + else + end_index = min(relative_end, source_length); + + // e. Let newLength be max(endIndex - beginIndex, 0). + auto new_length = max(end_index - begin_index, 0); + + // f. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ». + arguments.empend(buffer); + arguments.empend(begin_byte_offset.value()); + arguments.empend(new_length); + } + + // 17. Return ? TypedArraySpeciesCreate(O, argumentsList). return TRY(typed_array_species_create(vm, *typed_array, move(arguments))); } @@ -1611,15 +1893,15 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string) // This function is not generic. ValidateTypedArray is applied to the this value prior to evaluating the algorithm. // If its result is an abrupt completion that exception is thrown instead of evaluating the algorithm. - auto* typed_array = TRY(validate_typed_array_from_this(vm)); // 1. Let array be ? ToObject(this value). - // NOTE: Handled by ValidateTypedArray + auto* typed_array = TRY(typed_array_from_this(vm)); // 2. Let len be ? ToLength(? Get(array, "length")). // The implementation of the algorithm may be optimized with the knowledge that the this value is an object that // has a fixed length and whose integer-indexed properties are not sparse. - auto length = typed_array->array_length(); + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + auto length = typed_array_length(typed_array_record); // 3. Let separator be the implementation-defined list-separator String value appropriate for the host environment's current locale (such as ", "). constexpr auto separator = ','; @@ -1649,7 +1931,7 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string) builder.append(locale_string); } - // d. Increase k by 1. + // d. Set k to k + 1. } // 7. Return R. @@ -1660,82 +1942,84 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string) JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_reversed) { // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let length be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let length be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. Let A be ? TypedArrayCreateSameType(O, « 𝔽(length) »). MarkedVector arguments(vm.heap()); arguments.empend(length); - auto* return_array = TRY(typed_array_create_same_type(vm, *typed_array, move(arguments))); + auto* array = TRY(typed_array_create_same_type(vm, *typed_array, move(arguments))); // 5. Let k be 0. - // 6. Repeat, while k < length - for (size_t k = 0; k < length; k++) { + // 6. Repeat, while k < length, + for (size_t k = 0; k < length; ++k) { // a. Let from be ! ToString(𝔽(length - k - 1)). - auto from = PropertyKey { length - k - 1 }; + PropertyKey from { length - k - 1 }; // b. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; + PropertyKey property_key { k }; // c. Let fromValue be ! Get(O, from). auto from_value = MUST(typed_array->get(from)); // d. Perform ! Set(A, Pk, fromValue, true). - MUST(return_array->set(property_key, from_value, Object::ShouldThrowExceptions::Yes)); + MUST(array->set(property_key, from_value, Object::ShouldThrowExceptions::Yes)); // e. Set k to k + 1. } // 7. Return A. - return return_array; + return array; } // 23.2.3.33 %TypedArray%.prototype.toSorted ( comparefn ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.tosorted JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_sorted) { - auto comparefn = vm.argument(0); + auto compare_function = vm.argument(0); // 1. If comparefn is not undefined and IsCallable(comparefn) is false, throw a TypeError exception. - if (!comparefn.is_undefined() && !comparefn.is_function()) - return vm.throw_completion(ErrorType::NotAFunction, comparefn); + if (!compare_function.is_undefined() && !compare_function.is_function()) + return vm.throw_completion(ErrorType::NotAFunction, compare_function); // 2. Let O be the this value. - auto object = TRY(vm.this_value().to_object(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + // 3. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); - // 4. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 4. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 5. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »). MarkedVector arguments(vm.heap()); arguments.empend(length); - auto* return_array = TRY(typed_array_create_same_type(vm, *typed_array, move(arguments))); + auto* array = TRY(typed_array_create_same_type(vm, *typed_array, move(arguments))); - // 6. NOTE: The following closure performs a numeric comparison rather than the string comparison used in Array.prototype.toSorted - // 7. Let SortCompare be a new Abstract Closure with parameters (x, y) that captures comparefn and performs the following steps when called: + // 6. NOTE: The following closure performs a numeric comparison rather than the string comparison used in 23.1.3.34. Function(Value, Value)> sort_compare = [&](auto x, auto y) -> ThrowCompletionOr { // a. Return ? CompareTypedArrayElements(x, y, comparefn). - return TRY(compare_typed_array_elements(vm, x, y, comparefn.is_undefined() ? nullptr : &comparefn.as_function())); + return TRY(compare_typed_array_elements(vm, x, y, compare_function.is_undefined() ? nullptr : &compare_function.as_function())); }; // 8. Let sortedList be ? SortIndexedProperties(O, len, SortCompare, read-through-holes). - auto sorted_list = TRY(sort_indexed_properties(vm, object, length, sort_compare, Holes::ReadThroughHoles)); + auto sorted_list = TRY(sort_indexed_properties(vm, *typed_array, length, sort_compare, Holes::ReadThroughHoles)); // 9. Let j be 0. // 10. Repeat, while j < len, for (size_t j = 0; j < length; j++) { // a. Perform ! Set(A, ! ToString(𝔽(j)), sortedList[j], true). - MUST(return_array->set(j, sorted_list[j], Object::ShouldThrowExceptions::Yes)); + MUST(array->set(j, sorted_list[j], Object::ShouldThrowExceptions::Yes)); + // b. Set j to j + 1. } // 11. Return A. - return return_array; + return array; } // 23.2.3.35 %TypedArray%.prototype.values ( ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.values @@ -1744,8 +2028,10 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::values) auto& realm = *vm.current_realm(); // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); + + // 2. Perform ? ValidateTypedArray(O, seq-cst). + (void)TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); // 3. Return CreateArrayIterator(O, value). return ArrayIterator::create(realm, typed_array, Object::PropertyKind::Value); @@ -1758,11 +2044,13 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::with) auto value = vm.argument(1); // 1. Let O be the this value. - // 2. Perform ? ValidateTypedArray(O). - auto* typed_array = TRY(validate_typed_array_from_this(vm)); + auto* typed_array = TRY(typed_array_from_this(vm)); - // 3. Let len be O.[[ArrayLength]]. - auto length = typed_array->array_length(); + // 2. Let taRecord be ? ValidateTypedArray(O, seq-cst). + auto typed_array_record = TRY(validate_typed_array(vm, *typed_array, ArrayBuffer::Order::SeqCst)); + + // 3. Let len be TypedArrayLength(taRecord). + auto length = typed_array_length(typed_array_record); // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). auto relative_index = TRY(index.to_integer_or_infinity(vm)); @@ -1771,6 +2059,7 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::with) // 5. If relativeIndex ≥ 0, let actualIndex be relativeIndex. if (relative_index >= 0) actual_index = relative_index; + // 6. Else, let actualIndex be len + relativeIndex. else actual_index = length + relative_index; @@ -1789,13 +2078,13 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::with) // 10. Let A be ? TypedArrayCreateSameType(O, « 𝔽(len) »). MarkedVector arguments(vm.heap()); arguments.empend(length); - auto* return_array = TRY(typed_array_create_same_type(vm, *typed_array, move(arguments))); + auto* array = TRY(typed_array_create_same_type(vm, *typed_array, move(arguments))); // 11. Let k be 0. // 12. Repeat, while k < len, for (size_t k = 0; k < length; k++) { // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; + PropertyKey property_key { k }; Value from_value; // b. If k is actualIndex, let fromValue be numericValue. @@ -1806,13 +2095,13 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::with) from_value = MUST(typed_array->get(property_key)); // d. Perform ! Set(A, Pk, fromValue, true). - MUST(return_array->set(property_key, from_value, Object::ShouldThrowExceptions::Yes)); + MUST(array->set(property_key, from_value, Object::ShouldThrowExceptions::Yes)); // e. Set k to k + 1. } // 13. Return A. - return return_array; + return array; } // 23.2.3.38 get %TypedArray%.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag diff --git a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.notify.js b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.notify.js index 75ecbaba70..4a2584e040 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.notify.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.notify.js @@ -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", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.wait.js b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.wait.js index 39378ad6c7..45e7a7ebb0 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.wait.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.wait.js @@ -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", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.waitAsync.js b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.waitAsync.js index cc5ebc3006..e8799065e5 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.waitAsync.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.waitAsync.js @@ -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", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.at.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.at.js index d961de1e8d..fecc099149 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.at.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.at.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteLength.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteLength.js index 46d6ed898d..0bbbbf85c5 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteLength.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteLength.js @@ -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); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteOffset.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteOffset.js index 5eac5d5c44..4ca9e0ca49 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteOffset.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.byteOffset.js @@ -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); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.copyWithin.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.copyWithin.js index 6995d8c6ab..f1d255c8b3 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.copyWithin.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.copyWithin.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.entries.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.entries.js index 9b5ffa192e..b396e948f8 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.entries.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.entries.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.every.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.every.js index 527431dc9e..58bcea08c7 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.every.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.every.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.fill.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.fill.js index 4260cc45e8..af97b5daa3 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.fill.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.fill.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js index b33ace787b..01e9a2c629 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js @@ -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)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.find.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.find.js index ead1500d0a..139a7653f0 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.find.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.find.js @@ -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)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findIndex.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findIndex.js index 7ecaafb509..74ab4932ef 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findIndex.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findIndex.js @@ -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)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js index 4f44f13646..6cc1541871 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js @@ -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)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js index 8139487ce8..545dea582b 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js @@ -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)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.forEach.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.forEach.js index 84dcbed433..c860c92211 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.forEach.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.forEach.js @@ -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)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.includes.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.includes.js index f1a1eda27e..dc36fd834c 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.includes.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.includes.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.indexOf.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.indexOf.js index 9cfc741f32..d9cb8b67e3 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.indexOf.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.indexOf.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.keys.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.keys.js index a7e66676fe..fc5169bccb 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.keys.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.keys.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.lastIndexOf.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.lastIndexOf.js index c45b105ce4..eea71f3146 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.lastIndexOf.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.lastIndexOf.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js index ecaa946a08..3868a74fca 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.length.js @@ -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); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.map.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.map.js index 622d3a3224..cd4bd2ae54 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.map.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.map.js @@ -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)); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduce.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduce.js index b338f16575..66441d17f0 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduce.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduce.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduceRight.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduceRight.js index 6bc0df3fde..f4d804b0d1 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduceRight.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reduceRight.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reverse.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reverse.js index 89169c2b6e..c7143d40f9 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reverse.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.reverse.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.set.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.set.js index dabc0f5d14..1833282712 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.set.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.set.js @@ -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 diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.slice.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.slice.js index 92a8eaa8d5..dc37d980be 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.slice.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.slice.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.some.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.some.js index 99ea1e1e3e..63a1f25650 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.some.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.some.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.sort.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.sort.js index 09dc6c9fc8..9cb7e537c8 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.sort.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.sort.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.subarray.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.subarray.js index aa60b769d0..253dc2a138 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.subarray.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.subarray.js @@ -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); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toLocaleString.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toLocaleString.js index 9114c1b079..21c27ad030 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toLocaleString.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toLocaleString.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toReversed.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toReversed.js index 89db5ee653..0ecb43e9e5 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toReversed.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toReversed.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toSorted.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toSorted.js index 410c4aa082..3e13c97922 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toSorted.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.toSorted.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.values.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.values.js index 5de95ed31d..18c702e4e2 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.values.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.values.js @@ -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); diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.with.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.with.js index 8c501331f9..0613d424ee 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.with.js +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.with.js @@ -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", () => { diff --git a/Userland/Libraries/LibWeb/Crypto/Crypto.cpp b/Userland/Libraries/LibWeb/Crypto/Crypto.cpp index 6a07f775a8..21a99a3f7f 100644 --- a/Userland/Libraries/LibWeb/Crypto/Crypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/Crypto.cpp @@ -49,13 +49,17 @@ WebIDL::ExceptionOr> 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>(); + 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. diff --git a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp index cc0273dd50..4febb9e293 100644 --- a/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp +++ b/Userland/Libraries/LibWeb/HTML/StructuredSerialize.cpp @@ -526,9 +526,7 @@ private: if constexpr (IsSame) { 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 {}; } diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp index 8ba23d02d5..f8bda1e1e9 100644 --- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -2041,14 +2041,17 @@ WebIDL::ExceptionOr 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::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)); diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 3528a32994..e2fdbae970 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -309,7 +309,12 @@ JS::ThrowCompletionOr parse_module(JS::VM& vm, JS::Object* buffer_object data = buffer.buffer(); } else if (is(buffer_object)) { auto& buffer = static_cast(*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::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(buffer_object)) { auto& buffer = static_cast(*buffer_object); diff --git a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp index 47e3854d6a..0da1d8d1d3 100644 --- a/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/WebIDL/AbstractOperations.cpp @@ -38,6 +38,12 @@ ErrorOr get_buffer_source_copy(JS::Object const& buffer_source) if (is(buffer_source)) { auto const& es_buffer_source = static_cast(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 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(buffer_source)) { auto const& es_buffer_source = static_cast(buffer_source); diff --git a/Userland/Libraries/LibWeb/WebIDL/Buffers.cpp b/Userland/Libraries/LibWeb/WebIDL/Buffers.cpp index 7ed150b802..de7fa1a150 100644 --- a/Userland/Libraries/LibWeb/WebIDL/Buffers.cpp +++ b/Userland/Libraries/LibWeb/WebIDL/Buffers.cpp @@ -13,7 +13,16 @@ namespace Web::WebIDL { u32 BufferableObjectBase::byte_length() const { - return m_bufferable_object.visit([](auto& obj) { return static_cast(obj->byte_length()); }); + return m_bufferable_object.visit( + [](JS::NonnullGCPtr 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 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 array_buffer) { return static_cast(array_buffer->byte_length()); }); } JS::NonnullGCPtr BufferableObjectBase::raw_object()