diff --git a/Libraries/LibJS/Bytecode/Bytecode.def b/Libraries/LibJS/Bytecode/Bytecode.def index c7156d2f4e6..f90c41b6231 100644 --- a/Libraries/LibJS/Bytecode/Bytecode.def +++ b/Libraries/LibJS/Bytecode/Bytecode.def @@ -153,6 +153,20 @@ op CreateArguments < Instruction m_is_immutable: bool endop +op CreateAsyncFromSyncIterator < Instruction + @nothrow + m_dst: Operand + m_iterator: Operand + m_next_method: Operand + m_done: Operand +endop + +op CreateDataPropertyOrThrow < Instruction + m_object: Operand + m_property: Operand + m_value: Operand +endop + op CreateLexicalEnvironment < Instruction @nothrow m_dst: Optional @@ -422,6 +436,18 @@ op InstanceOf < Instruction m_rhs: Operand endop +op IsCallable < Instruction + @nothrow + m_dst: Operand + m_value: Operand +endop + +op IsConstructor < Instruction + @nothrow + m_dst: Operand + m_value: Operand +endop + op IteratorClose < Instruction m_iterator_object: Operand m_iterator_next: Operand @@ -640,6 +666,11 @@ op NewArray < Instruction m_elements: Operand[] endop +op NewArrayWithLength < Instruction + m_dst: Operand + m_array_length: Operand +endop + op NewClass < Instruction m_length: u32 m_dst: Operand @@ -663,6 +694,11 @@ op NewObject < Instruction m_dst: Operand endop +op NewObjectWithNoPrototype < Instruction + @nothrow + m_dst: Operand +endop + op NewPrimitiveArray < Instruction @nothrow m_length: u32 @@ -1041,6 +1077,22 @@ op ThrowIfTDZ < Instruction m_src: Operand endop +op ToBoolean < Instruction + @nothrow + m_dst: Operand + m_value: Operand +endop + +op ToLength < Instruction + m_dst: Operand + m_value: Operand +endop + +op ToObject < Instruction + m_dst: Operand + m_value: Operand +endop + op Typeof < Instruction m_dst: Operand m_src: Operand diff --git a/Libraries/LibJS/Bytecode/Generator.cpp b/Libraries/LibJS/Bytecode/Generator.cpp index ccdf4cb19f6..1ba774f3394 100644 --- a/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Libraries/LibJS/Bytecode/Generator.cpp @@ -1472,7 +1472,7 @@ ScopedOperand Generator::add_constant(Value value) return append_new_constant(); } -CodeGenerationErrorOr Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan arguments, ScopedOperand const&) +CodeGenerationErrorOr Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan arguments, ScopedOperand const& dst) { VERIFY(m_builtin_abstract_operations_enabled); for (auto const& argument : arguments) { @@ -1486,6 +1486,249 @@ CodeGenerationErrorOr Generator::generate_builtin_abstract_operation(Ident auto const& operation_name = builtin_identifier.string(); + if (operation_name == "IsCallable"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "IsCallable only accepts one argument"sv, + }; + } + + auto source = TRY(arguments[0].value->generate_bytecode(*this)).value(); + emit(dst, source); + return {}; + } + + if (operation_name == "IsConstructor"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "IsConstructor only accepts one argument"sv, + }; + } + + auto source = TRY(arguments[0].value->generate_bytecode(*this)).value(); + emit(dst, source); + return {}; + } + + if (operation_name == "ToBoolean"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "ToBoolean only accepts one argument"sv, + }; + } + + auto source = TRY(arguments[0].value->generate_bytecode(*this)).value(); + emit(dst, source); + return {}; + } + + if (operation_name == "ToObject"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "ToObject only accepts one argument"sv, + }; + } + + auto source = TRY(arguments[0].value->generate_bytecode(*this)).value(); + emit(dst, source); + return {}; + } + + if (operation_name == "ThrowTypeError"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "throw_type_error only accepts one argument"sv, + }; + } + + auto const* message = as_if(*arguments[0].value); + if (!message) { + return CodeGenerationError { + &builtin_identifier, + "ThrowTypeError's message must be a string literal"sv, + }; + } + + auto message_string = intern_string(message->value()); + auto type_error_register = allocate_register(); + emit(type_error_register, message_string); + perform_needed_unwinds(); + emit(type_error_register); + return {}; + } + + if (operation_name == "ThrowIfNotObject"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "ThrowIfNotObject only accepts one argument"sv, + }; + } + + auto source = TRY(arguments[0].value->generate_bytecode(*this)).value(); + emit(source); + return {}; + } + + if (operation_name == "Call"sv) { + if (arguments.size() < 2) { + return CodeGenerationError { + &builtin_identifier, + "Call must have at least two arguments"sv, + }; + } + + auto const& callee_argument = arguments[0].value; + auto callee = TRY(callee_argument->generate_bytecode(*this)).value(); + auto this_value = TRY(arguments[1].value->generate_bytecode(*this)).value(); + auto arguments_to_call_with = arguments.slice(2); + + Vector argument_operands; + argument_operands.ensure_capacity(arguments_to_call_with.size()); + for (auto const& argument : arguments_to_call_with) { + auto argument_value = TRY(argument.value->generate_bytecode(*this)).value(); + argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value)); + } + + auto expression_string = ([&callee_argument] -> Optional { + if (auto const* identifier = as_if(*callee_argument)) + return identifier->string().to_utf16_string(); + + if (auto const* member_expression = as_if(*callee_argument)) + return member_expression->to_string_approximation(); + + return {}; + })(); + + Optional expression_string_index; + if (expression_string.has_value()) + expression_string_index = intern_string(expression_string.release_value()); + + emit_with_extra_operand_slots( + argument_operands.size(), + dst, + callee, + this_value, + expression_string_index, + argument_operands); + return {}; + } + + if (operation_name == "NewObjectWithNoPrototype"sv) { + if (!arguments.is_empty()) { + return CodeGenerationError { + &builtin_identifier, + "NewObjectWithNoPrototype does not take any arguments"sv, + }; + } + + emit(dst); + return {}; + } + + if (operation_name == "CreateAsyncFromSyncIterator"sv) { + if (arguments.size() != 3) { + return CodeGenerationError { + &builtin_identifier, + "CreateAsyncFromSyncIterator only accepts exactly three arguments"sv, + }; + } + + auto iterator = TRY(arguments[0].value->generate_bytecode(*this)).value(); + auto next_method = TRY(arguments[1].value->generate_bytecode(*this)).value(); + auto done = TRY(arguments[2].value->generate_bytecode(*this)).value(); + + emit(dst, iterator, next_method, done); + return {}; + } + + if (operation_name == "ToLength"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "ToLength only accepts exactly one argument"sv, + }; + } + + auto value = TRY(arguments[0].value->generate_bytecode(*this)).value(); + emit(dst, value); + return {}; + } + + if (operation_name == "NewTypeError"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "NewTypeError only accepts one argument"sv, + }; + } + + auto const* message = as_if(*arguments[0].value); + if (!message) { + return CodeGenerationError { + &builtin_identifier, + "new_type_error's message must be a string literal"sv, + }; + } + + auto message_string = intern_string(message->value()); + emit(dst, message_string); + return {}; + } + + if (operation_name == "NewArrayWithLength"sv) { + if (arguments.size() != 1) { + return CodeGenerationError { + &builtin_identifier, + "NewArrayWithLength only accepts one argument"sv, + }; + } + + auto length = TRY(arguments[0].value->generate_bytecode(*this)).value(); + emit(dst, length); + return {}; + } + + if (operation_name == "CreateDataPropertyOrThrow"sv) { + if (arguments.size() != 3) { + return CodeGenerationError { + &builtin_identifier, + "CreateDataPropertyOrThrow only accepts three arguments"sv, + }; + } + + auto object = TRY(arguments[0].value->generate_bytecode(*this)).value(); + auto property = TRY(arguments[1].value->generate_bytecode(*this)).value(); + auto value = TRY(arguments[2].value->generate_bytecode(*this)).value(); + emit(object, property, value); + return {}; + } + +#define __JS_ENUMERATE(snake_name, functionName, length) \ + if (operation_name == #functionName##sv) { \ + Vector argument_operands; \ + argument_operands.ensure_capacity(arguments.size()); \ + for (auto const& argument : arguments) { \ + auto argument_value = TRY(argument.value->generate_bytecode(*this)).value(); \ + argument_operands.unchecked_append(copy_if_needed_to_preserve_evaluation_order(argument_value)); \ + } \ + emit_with_extra_operand_slots( \ + argument_operands.size(), \ + dst, \ + add_constant(m_vm.current_realm()->intrinsics().snake_name##_abstract_operation_function()), \ + add_constant(js_undefined()), \ + intern_string(builtin_identifier.string().to_utf16_string()), \ + argument_operands); \ + return {}; \ + } + JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS +#undef __JS_ENUMERATE + dbgln("Unknown builtin abstract operation: '{}'", operation_name); return CodeGenerationError { &builtin_identifier, @@ -1504,6 +1747,18 @@ CodeGenerationErrorOr> Generator::maybe_generate_builtin if (!m_builtin_abstract_operations_enabled) return OptionalNone {}; + if (constant_name == "SYMBOL_ITERATOR"sv) { + return add_constant(vm().well_known_symbol_iterator()); + } + + if (constant_name == "SYMBOL_ASYNC_ITERATOR"sv) { + return add_constant(vm().well_known_symbol_async_iterator()); + } + + if (constant_name == "MAX_ARRAY_LIKE_INDEX"sv) { + return add_constant(Value(MAX_ARRAY_LIKE_INDEX)); + } + dbgln("Unknown builtin constant: '{}'", constant_name); return CodeGenerationError { &builtin_identifier, diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index 4c421655861..ee0a68389cc 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -450,6 +452,18 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) goto start; } + handle_IsCallable: { + auto& instruction = *reinterpret_cast(&bytecode[program_counter]); + set(instruction.dst(), Value(get(instruction.value()).is_function())); + DISPATCH_NEXT(IsCallable); + } + + handle_IsConstructor: { + auto& instruction = *reinterpret_cast(&bytecode[program_counter]); + set(instruction.dst(), Value(get(instruction.value()).is_constructor())); + DISPATCH_NEXT(IsConstructor); + } + #define HANDLE_INSTRUCTION(name) \ handle_##name: \ { \ @@ -491,6 +505,8 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Catch); HANDLE_INSTRUCTION(ConcatString); HANDLE_INSTRUCTION(CopyObjectExcludingProperties); + HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateAsyncFromSyncIterator); + HANDLE_INSTRUCTION(CreateDataPropertyOrThrow); HANDLE_INSTRUCTION(CreateImmutableBinding); HANDLE_INSTRUCTION(CreateMutableBinding); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(CreateLexicalEnvironment); @@ -550,9 +566,11 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) HANDLE_INSTRUCTION(Mod); HANDLE_INSTRUCTION(Mul); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewArray); + HANDLE_INSTRUCTION(NewArrayWithLength); HANDLE_INSTRUCTION(NewClass); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewFunction); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewObject); + HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewObjectWithNoPrototype); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewPrimitiveArray); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewRegExp); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(NewTypeError); @@ -593,6 +611,9 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) HANDLE_INSTRUCTION(ThrowIfNotObject); HANDLE_INSTRUCTION(ThrowIfNullish); HANDLE_INSTRUCTION(ThrowIfTDZ); + HANDLE_INSTRUCTION(ToLength); + HANDLE_INSTRUCTION(ToObject); + HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(ToBoolean); HANDLE_INSTRUCTION(Typeof); HANDLE_INSTRUCTION(TypeofBinding); HANDLE_INSTRUCTION(UnaryMinus); @@ -2030,6 +2051,14 @@ void NewPrimitiveArray::execute_impl(Bytecode::Interpreter& interpreter) const interpreter.set(dst(), array); } +ThrowCompletionOr NewArrayWithLength::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto length = static_cast(interpreter.get(m_array_length).as_double()); + auto array = TRY(Array::create(interpreter.realm(), length)); + interpreter.set(m_dst, array); + return {}; +} + void AddPrivateName::execute_impl(Bytecode::Interpreter& interpreter) const { auto const& name = interpreter.get_identifier(m_name); @@ -2081,6 +2110,13 @@ void NewObject::execute_impl(Bytecode::Interpreter& interpreter) const interpreter.set(dst(), Object::create(realm, realm.intrinsics().object_prototype())); } +void NewObjectWithNoPrototype::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto& realm = *vm.current_realm(); + interpreter.set(dst(), Object::create(realm, nullptr)); +} + void NewRegExp::execute_impl(Bytecode::Interpreter& interpreter) const { interpreter.set(dst(), @@ -3299,4 +3335,51 @@ ThrowCompletionOr CreateMutableBinding::execute_impl(Bytecode::Interpreter return environment.create_mutable_binding(interpreter.vm(), interpreter.get_identifier(m_identifier), m_can_be_deleted); } +ThrowCompletionOr ToObject::execute_impl(Bytecode::Interpreter& interpreter) const +{ + interpreter.set(m_dst, TRY(interpreter.get(m_value).to_object(interpreter.vm()))); + return {}; +} + +void ToBoolean::execute_impl(Bytecode::Interpreter& interpreter) const +{ + interpreter.set(m_dst, Value(interpreter.get(m_value).to_boolean())); +} + +ThrowCompletionOr ToLength::execute_impl(Bytecode::Interpreter& interpreter) const +{ + interpreter.set(m_dst, Value { TRY(interpreter.get(m_value).to_length(interpreter.vm())) }); + return {}; +} + +void CreateAsyncFromSyncIterator::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto& realm = interpreter.realm(); + + auto& iterator = interpreter.get(m_iterator).as_object(); + auto next_method = interpreter.get(m_next_method); + auto done = interpreter.get(m_done).as_bool(); + + auto iterator_record = realm.create(iterator, next_method, done); + auto async_from_sync_iterator = create_async_from_sync_iterator(vm, iterator_record); + + auto iterator_object = Object::create(realm, nullptr); + iterator_object->define_direct_property(vm.names.iterator, async_from_sync_iterator.iterator, default_attributes); + iterator_object->define_direct_property(vm.names.nextMethod, async_from_sync_iterator.next_method, default_attributes); + iterator_object->define_direct_property(vm.names.done, Value { async_from_sync_iterator.done }, default_attributes); + + interpreter.set(m_dst, iterator_object); +} + +ThrowCompletionOr CreateDataPropertyOrThrow::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto& object = interpreter.get(m_object).as_object(); + auto property = TRY(interpreter.get(m_property).to_property_key(vm)); + auto value = interpreter.get(m_value); + TRY(object.create_data_property_or_throw(property, value)); + return {}; +} + } diff --git a/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Libraries/LibJS/Runtime/ArrayConstructor.cpp index cce05c65076..8f754141950 100644 --- a/Libraries/LibJS/Runtime/ArrayConstructor.cpp +++ b/Libraries/LibJS/Runtime/ArrayConstructor.cpp @@ -40,7 +40,7 @@ void ArrayConstructor::initialize(Realm& realm) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(realm, vm.names.from, from, 1, attr); - define_native_function(realm, vm.names.fromAsync, from_async, 1, attr); + define_native_javascript_backed_function(vm.names.fromAsync, realm.intrinsics().from_async_array_constructor_function(), 1, attr); define_native_function(realm, vm.names.isArray, is_array, 1, attr); define_native_function(realm, vm.names.of, of, 0, attr); @@ -284,236 +284,6 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from) return array; } -// 2.1.1.1 Array.fromAsync ( asyncItems [ , mapfn [ , thisArg ] ] ), https://tc39.es/proposal-array-from-async/#sec-array.fromAsync -JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from_async) -{ - auto& realm = *vm.current_realm(); - - auto async_items = vm.argument(0); - auto mapfn = vm.argument(1); - auto this_arg = vm.argument(2); - - // 1. Let C be the this value. - auto constructor = vm.this_value(); - - // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). - auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor())); - - // 3. Let fromAsyncClosure be a new Abstract Closure with no parameters that captures C, mapfn, and thisArg and performs the following steps when called: - auto from_async_closure = GC::create_function(realm.heap(), [constructor, mapfn, this_arg, &vm, &realm, async_items]() mutable -> Completion { - bool mapping; - - // a. If mapfn is undefined, let mapping be false. - if (mapfn.is_undefined()) { - mapping = false; - } - // b. Else, - else { - // i. If IsCallable(mapfn) is false, throw a TypeError exception. - if (!mapfn.is_function()) - return vm.throw_completion(ErrorType::NotAFunction, mapfn.to_string_without_side_effects()); - - // ii. Let mapping be true. - mapping = true; - } - - // c. Let usingAsyncIterator be ? GetMethod(asyncItems, @@asyncIterator). - auto using_async_iterator = TRY(async_items.get_method(vm, vm.well_known_symbol_async_iterator())); - - GC::Ptr using_sync_iterator; - - // d. If usingAsyncIterator is undefined, then - if (!using_async_iterator) { - // i. Let usingSyncIterator be ? GetMethod(asyncItems, @@iterator). - using_sync_iterator = TRY(async_items.get_method(vm, vm.well_known_symbol_iterator())); - } - - // e. Let iteratorRecord be undefined. - GC::Ptr iterator_record; - - // f. If usingAsyncIterator is not undefined, then - if (using_async_iterator) { - // i. Set iteratorRecord to ? GetIterator(asyncItems, async, usingAsyncIterator). - // FIXME: The Array.from proposal is out of date - it should be using GetIteratorFromMethod. - iterator_record = TRY(get_iterator_from_method(vm, async_items, *using_async_iterator)); - } - // g. Else if usingSyncIterator is not undefined, then - else if (using_sync_iterator) { - // i. Set iteratorRecord to ? CreateAsyncFromSyncIterator(GetIterator(asyncItems, sync, usingSyncIterator)). - // FIXME: The Array.from proposal is out of date - it should be using GetIteratorFromMethod. - auto iterator_record_impl = create_async_from_sync_iterator(vm, TRY(get_iterator_from_method(vm, async_items, *using_sync_iterator))); - iterator_record = vm.heap().allocate(iterator_record_impl.iterator, iterator_record_impl.next_method, iterator_record_impl.done); - } - - // h. If iteratorRecord is not undefined, then - if (iterator_record) { - GC::Ptr array; - - // i. If IsConstructor(C) is true, then - if (constructor.is_constructor()) { - // 1. Let A be ? Construct(C). - array = TRY(JS::construct(vm, constructor.as_function())); - } - // ii. Else, - else { - // i. Let A be ! ArrayCreate(0). - array = MUST(Array::create(realm, 0)); - } - - // iii. Let k be 0. - // iv. Repeat, - for (size_t k = 0;; ++k) { - // 1. If k ≥ 2^53 - 1, then - if (k >= MAX_ARRAY_LIKE_INDEX) { - // a. Let error be ThrowCompletion(a newly created TypeError object). - auto error = vm.throw_completion(ErrorType::ArrayMaxSize); - - // b. Return ? AsyncIteratorClose(iteratorRecord, error). - return TRY(async_iterator_close(vm, *iterator_record, move(error))); - } - - // 2. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // FIXME: There seems to be a bug here where we are not respecting array mutation. After resolving the first entry, the - // iterator should also take into account any other changes which are made to async_items (which does not seem to - // be happening). - - // 3. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). - auto next_result = TRY(JS::call(vm, iterator_record->next_method, iterator_record->iterator)); - - // 4. Set nextResult to ? Await(nextResult). - next_result = TRY(await(vm, next_result)); - - // 5. If nextResult is not an Object, throw a TypeError exception. - if (!next_result.is_object()) - return vm.throw_completion(ErrorType::IterableNextBadReturn); - - // 6. Let done be ? IteratorComplete(nextResult). - auto done = TRY(JS::iterator_complete(vm, next_result.as_object())); - - // 7. If done is true, - if (done) { - // a. Perform ? Set(A, "length", 𝔽(k), true). - TRY(array->set(vm.names.length, Value(k), Object::ShouldThrowExceptions::Yes)); - - // b. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }. - return Completion { Completion::Type::Return, array }; - } - - // 8. Let nextValue be ? IteratorValue(nextResult). - auto next_value = TRY(iterator_value(vm, next_result.as_object())); - - Value mapped_value; - - // 9. If mapping is true, then - if (mapping) { - // a. Let mappedValue be Call(mapfn, thisArg, « nextValue, 𝔽(k) »). - auto mapped_value_or_error = JS::call(vm, mapfn, this_arg, next_value, Value(k)); - - // b. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). - if (mapped_value_or_error.is_error()) { - TRY(async_iterator_close(vm, *iterator_record, mapped_value_or_error)); - return mapped_value_or_error; - } - - // c. Set mappedValue to Await(mappedValue). - mapped_value_or_error = await(vm, mapped_value_or_error.value()); - - // d. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). - if (mapped_value_or_error.is_error()) { - TRY(async_iterator_close(vm, *iterator_record, mapped_value_or_error)); - return mapped_value_or_error; - } - - mapped_value = mapped_value_or_error.value(); - } - // 10. Else, let mappedValue be nextValue. - else { - mapped_value = next_value; - } - - // 11. Let defineStatus be CreateDataPropertyOrThrow(A, Pk, mappedValue). - auto define_status = array->create_data_property_or_throw(property_key, mapped_value); - - // 12. If defineStatus is an abrupt completion, return ? AsyncIteratorClose(iteratorRecord, defineStatus). - if (define_status.is_error()) - return TRY(iterator_close(vm, *iterator_record, define_status.release_error())); - - // 13. Set k to k + 1. - } - } - // k. Else, - else { - // i. NOTE: asyncItems is neither an AsyncIterable nor an Iterable so assume it is an array-like object. - - // ii. Let arrayLike be ! ToObject(asyncItems). - auto array_like = MUST(async_items.to_object(vm)); - - // iii. Let len be ? LengthOfArrayLike(arrayLike). - auto length = TRY(length_of_array_like(vm, array_like)); - - GC::Ptr array; - - // iv. If IsConstructor(C) is true, then - if (constructor.is_constructor()) { - // 1. Let A be ? Construct(C, « 𝔽(len) »). - array = TRY(JS::construct(vm, constructor.as_function(), Value(length))); - } - // v. Else, - else { - // 1. Let A be ? ArrayCreate(len). - array = TRY(Array::create(realm, length)); - } - - // vi. Let k be 0. - // vii. Repeat, while k < len, - for (size_t k = 0; k < length; ++k) { - // 1. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // 2. Let kValue be ? Get(arrayLike, Pk). - auto k_value = TRY(array_like->get(property_key)); - - // 3. Set kValue to ? Await(kValue). - k_value = TRY(await(vm, k_value)); - - Value mapped_value; - - // 4. If mapping is true, then - if (mapping) { - // a. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »). - mapped_value = TRY(JS::call(vm, mapfn, this_arg, k_value, Value(k))); - - // b. Set mappedValue to ? Await(mappedValue). - mapped_value = TRY(await(vm, mapped_value)); - } - // 5. Else, let mappedValue be kValue. - else { - mapped_value = k_value; - } - - // 6. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). - TRY(array->create_data_property_or_throw(property_key, mapped_value)); - - // 7. Set k to k + 1. - } - - // viii. Perform ? Set(A, "length", 𝔽(len), true). - TRY(array->set(vm.names.length, Value(length), Object::ShouldThrowExceptions::Yes)); - - // ix. Return Completion Record { [[Type]]: return, [[Value]]: A, [[Target]]: empty }. - return Completion { Completion::Type::Return, array }; - } - }); - - // 4. Perform AsyncFunctionStart(promiseCapability, fromAsyncClosure). - async_function_start(vm, promise_capability, *from_async_closure); - - // 5. Return promiseCapability.[[Promise]]. - return promise_capability->promise(); -} - // 23.1.2.2 Array.isArray ( arg ), https://tc39.es/ecma262/#sec-array.isarray JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::is_array) { diff --git a/Libraries/LibJS/Runtime/ArrayConstructor.h b/Libraries/LibJS/Runtime/ArrayConstructor.h index 6d31b080606..fbe35237627 100644 --- a/Libraries/LibJS/Runtime/ArrayConstructor.h +++ b/Libraries/LibJS/Runtime/ArrayConstructor.h @@ -27,7 +27,6 @@ private: virtual bool has_constructor() const override { return true; } JS_DECLARE_NATIVE_FUNCTION(from); - JS_DECLARE_NATIVE_FUNCTION(from_async); JS_DECLARE_NATIVE_FUNCTION(is_array); JS_DECLARE_NATIVE_FUNCTION(of); diff --git a/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Libraries/LibJS/Runtime/CommonPropertyNames.h index 4761d938092..60b9d03f064 100644 --- a/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -366,6 +366,7 @@ namespace JS { P(negated) \ P(NEGATIVE_INFINITY) \ P(next) \ + P(nextMethod) \ P(normalize) \ P(notation) \ P(notify) \ diff --git a/Libraries/LibJS/Runtime/Intrinsics.cpp b/Libraries/LibJS/Runtime/Intrinsics.cpp index 29268f4f0a9..d01a340cd64 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -5,6 +5,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include @@ -77,6 +79,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +135,32 @@ #include #include +// FIXME: Remove this asm hack when we upgrade to GCC 15. +#define INCLUDE_FILE_WITH_ASSEMBLY(name, file_path) \ + asm(".global " #name "\n" #name ":\n" \ + ".incbin " #file_path "\n" \ + ".global " #name "_END\n" #name "_END:\n" \ + ".byte 0\n"); \ + extern unsigned char const name[]; + +#if defined(AK_COMPILER_CLANG) || (defined(AK_COMPILER_GCC) && (__GNUC__ > 14)) +static constexpr unsigned char ABSTRACT_OPERATIONS[] = { +# embed "JavaScriptImplementations/AbstractOperations.js" suffix(, ) + 0 // null terminator +}; +#else +INCLUDE_FILE_WITH_ASSEMBLY(ABSTRACT_OPERATIONS, "LibJS/Runtime/JavaScriptImplementations/AbstractOperations.js") +#endif + +#if defined(AK_COMPILER_CLANG) || (defined(AK_COMPILER_GCC) && (__GNUC__ > 14)) +static constexpr unsigned char ARRAY_CONSTRUCTOR[] = { +# embed "JavaScriptImplementations/ArrayConstructor.js" suffix(, ) + 0 // null terminator +}; +#else +INCLUDE_FILE_WITH_ASSEMBLY(ARRAY_CONSTRUCTOR, "LibJS/Runtime/JavaScriptImplementations/ArrayConstructor.js") +#endif + namespace JS { GC_DEFINE_ALLOCATOR(Intrinsics); @@ -175,6 +204,24 @@ GC::Ref Intrinsics::create(Realm& realm) return *intrinsics; } +static Vector> parse_builtin_file(unsigned char const* script_text) +{ + auto script_text_as_utf16 = Utf16String::from_utf8_without_validation({ script_text, strlen(reinterpret_cast(script_text)) }); + auto code = SourceCode::create("BuiltinFile"_string, move(script_text_as_utf16)); + auto lexer = Lexer { move(code) }; + auto parser = Parser { move(lexer) }; + VERIFY(!parser.has_errors()); + auto program = parser.parse_program(true); + + Vector> declarations; + for (auto const& child : program->children()) { + if (auto const* function_declaration = as_if(*child)) + declarations.append(*function_declaration); + } + + return declarations; +} + void Intrinsics::initialize_intrinsics(Realm& realm) { auto& vm = this->vm(); @@ -480,6 +527,16 @@ void Intrinsics::visit_edges(Visitor& visitor) #undef __JS_ENUMERATE visitor.visit(m_default_collator); + +#define __JS_ENUMERATE(snake_name, functionName, length) \ + visitor.visit(m_##snake_name##_abstract_operation_function); + JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(snake_name, functionName, length) \ + visitor.visit(m_##snake_name##_array_constructor_function); + JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS +#undef __JS_ENUMERATE } GC::Ref Intrinsics::default_collator() @@ -490,6 +547,38 @@ GC::Ref Intrinsics::default_collator() return *m_default_collator; } +#define __JS_ENUMERATE(snake_name, functionName, length) \ + GC::Ref Intrinsics::snake_name##_abstract_operation_function() \ + { \ + if (!m_##snake_name##_abstract_operation_function) { \ + static auto abstract_operations_function_declarations = parse_builtin_file(ABSTRACT_OPERATIONS); \ + auto snake_name##_function_declaration = abstract_operations_function_declarations.find_if([](auto const& function_declaration) { \ + return function_declaration->name() == #functionName##sv; \ + }); \ + VERIFY(!snake_name##_function_declaration.is_end()); \ + m_##snake_name##_abstract_operation_function = NativeJavaScriptBackedFunction::create(m_realm, *snake_name##_function_declaration, PropertyKey { #functionName##_utf16_fly_string, PropertyKey::StringMayBeNumber::No }, length); \ + } \ + return *m_##snake_name##_abstract_operation_function; \ + } +JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(snake_name, functionName, length) \ + GC::Ref Intrinsics::snake_name##_array_constructor_function() \ + { \ + if (!m_##snake_name##_array_constructor_function) { \ + static auto intrinsics_function_declarations = parse_builtin_file(ARRAY_CONSTRUCTOR); \ + auto snake_name##_function_declaration = intrinsics_function_declarations.find_if([](auto const& function_declaration) { \ + return function_declaration->name() == #functionName##sv; \ + }); \ + VERIFY(!snake_name##_function_declaration.is_end()); \ + m_##snake_name##_array_constructor_function = NativeJavaScriptBackedFunction::create(m_realm, *snake_name##_function_declaration, PropertyKey { #functionName##_utf16_fly_string, PropertyKey::StringMayBeNumber::No }, length); \ + } \ + return *m_##snake_name##_array_constructor_function; \ + } +JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS +#undef __JS_ENUMERATE + // 10.2.4 AddRestrictedFunctionProperties ( F, realm ), https://tc39.es/ecma262/#sec-addrestrictedfunctionproperties void add_restricted_function_properties(FunctionObject& function, Realm& realm) { diff --git a/Libraries/LibJS/Runtime/Intrinsics.h b/Libraries/LibJS/Runtime/Intrinsics.h index 135d6c10f52..9793b55bef1 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.h +++ b/Libraries/LibJS/Runtime/Intrinsics.h @@ -14,6 +14,16 @@ namespace JS { +#define JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS \ + __JS_ENUMERATE(async_iterator_close, AsyncIteratorClose, 3) \ + __JS_ENUMERATE(get_method, GetMethod, 2) \ + __JS_ENUMERATE(get_iterator_direct, GetIteratorDirect, 1) \ + __JS_ENUMERATE(get_iterator_from_method, GetIteratorFromMethod, 2) \ + __JS_ENUMERATE(iterator_complete, IteratorComplete, 1) + +#define JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS \ + __JS_ENUMERATE(from_async, fromAsync, 1) + class JS_API Intrinsics final : public Cell { GC_CELL(Intrinsics, Cell); GC_DECLARE_ALLOCATOR(Intrinsics); @@ -127,6 +137,16 @@ public: [[nodiscard]] GC::Ref default_collator(); +#define __JS_ENUMERATE(snake_name, functionName, length) \ + GC::Ref snake_name##_abstract_operation_function(); + JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(snake_name, functionName, length) \ + GC::Ref snake_name##_array_constructor_function(); + JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS +#undef __JS_ENUMERATE + private: Intrinsics(Realm& realm) : m_realm(realm) @@ -249,6 +269,16 @@ private: JS_ENUMERATE_ITERATOR_PROTOTYPES #undef __JS_ENUMERATE +#define __JS_ENUMERATE(snake_name, functionName, length) \ + GC::Ptr m_##snake_name##_abstract_operation_function; + JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ABSTRACT_OPERATIONS +#undef __JS_ENUMERATE + +#define __JS_ENUMERATE(snake_name, functionName, length) \ + GC::Ptr m_##snake_name##_array_constructor_function; + JS_ENUMERATE_NATIVE_JAVASCRIPT_BACKED_ARRAY_CONSTRUCTOR_FUNCTIONS +#undef __JS_ENUMERATE + GC::Ptr m_default_collator; }; diff --git a/Libraries/LibJS/Runtime/JavaScriptImplementations/AbstractOperations.js b/Libraries/LibJS/Runtime/JavaScriptImplementations/AbstractOperations.js new file mode 100644 index 00000000000..e85cf74aab1 --- /dev/null +++ b/Libraries/LibJS/Runtime/JavaScriptImplementations/AbstractOperations.js @@ -0,0 +1,95 @@ +/** + * 7.3.10 GetMethod ( V, P ), https://tc39.es/ecma262/#sec-getmethod + */ +function GetMethod(value, property) { + // 1. Let func be ? GetV(V, P). + const function_ = value[property]; + + // 2. If func is either undefined or null, return undefined. + if (function_ === undefined || function_ === null) return undefined; + + // 3. If IsCallable(func) is false, throw a TypeError exception. + if (!IsCallable(function_)) ThrowTypeError("Not a function"); + + // 4. Return func. + return function_; +} + +/** + * 7.4.2 GetIteratorDirect ( obj ), https://tc39.es/ecma262/#sec-getiteratordirect + */ +function GetIteratorDirect(object) { + // 1. Let nextMethod be ? Get(obj, "next"). + const nextMethod = object.next; + + // 2. Let iteratorRecord be the Iterator Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }. + const iteratorRecord = NewObjectWithNoPrototype(); + iteratorRecord.iterator = object; + iteratorRecord.nextMethod = nextMethod; + iteratorRecord.done = false; + + // 3. Return iteratorRecord. + return iteratorRecord; +} + +/** + * 7.4.3 GetIteratorFromMethod ( obj, method ), https://tc39.es/ecma262/#sec-getiteratorfrommethod + */ +function GetIteratorFromMethod(object, method) { + // 1. Let iterator be ? Call(method, obj). + const iterator = Call(method, object); + + // 2. If iterator is not an Object, throw a TypeError exception. + ThrowIfNotObject(iterator); + + // 3. Return ? GetIteratorDirect(iterator). + return GetIteratorDirect(iterator); +} + +/** + * 7.4.7 IteratorComplete ( iteratorResult ) - https://tc39.es/ecma262/#sec-iteratorcomplete + */ +function IteratorComplete(iteratorResult) { + // 1. Return ToBoolean(? Get(iteratorResult, "done")). + return ToBoolean(iteratorResult.done); +} + +/** + * 7.4.13 AsyncIteratorClose ( iteratorRecord, completion ) - https://tc39.es/ecma262/#sec-asynciteratorclose + */ +async function AsyncIteratorClose(iteratorRecord, completionValue, isThrowCompletion) { + // FIXME: 1. Assert: iteratorRecord.[[Iterator]] is an Object. + + // 2. Let iterator be iteratorRecord.[[Iterator]]. + const iterator = iteratorRecord.iterator; + + let innerResult; + + try { + // 3. Let innerResult be Completion(GetMethod(iterator, "return")). + innerResult = GetMethod(iterator, "return"); + + // 4. If innerResult is a normal completion, then + // a. Let return be innerResult.[[Value]]. + // b. If return is undefined, return ? completion. + // NOTE: If isThrowCompletion is true, it will override this return in the finally block. + if (innerResult === undefined) return completionValue; + + // c. Set innerResult to Completion(Call(return, iterator)). + // d. If innerResult is a normal completion, set innerResult to Completion(Await(innerResult.[[Value]])). + innerResult = await Call(innerResult, iterator); + } finally { + // 5. If completion is a throw completion, return ? completion. + if (isThrowCompletion) throw completionValue; + + // 6. If innerResult is a throw completion, return ? innerResult. + // NOTE: If the try block threw, it will rethrow when leaving this finally block. + } + + // 7. If innerResult.[[Value]] is not an Object, throw a TypeError exception. + ThrowIfNotObject(innerResult); + + // 8. Return ? completion. + // NOTE: Because of step 5, this will not be a throw completion. + return completionValue; +} diff --git a/Libraries/LibJS/Runtime/JavaScriptImplementations/ArrayConstructor.js b/Libraries/LibJS/Runtime/JavaScriptImplementations/ArrayConstructor.js new file mode 100644 index 00000000000..89df705c2ab --- /dev/null +++ b/Libraries/LibJS/Runtime/JavaScriptImplementations/ArrayConstructor.js @@ -0,0 +1,189 @@ +/** + * 2.1.1.1 Array.fromAsync ( asyncItems [ , mapper [ , thisArg ] ] ), https://tc39.es/proposal-array-from-async/#sec-array.fromAsync + */ +async function fromAsync(asyncItems, mapper, thisArg) { + // 1. Let C be the this value. + const constructor = this; + + let mapping; + + // 2. If mapper is undefined, then + if (mapper === undefined) { + // a. Let mapping be false. + mapping = false; + } + // 3. Else, + else { + // a. If IsCallable(mapper) is false, throw a TypeError exception. + if (!IsCallable(mapper)) { + ThrowTypeError("mapper must be a function"); + } + + // b. Let mapping be true. + mapping = true; + } + + // 4. Let usingAsyncIterator be ? GetMethod(asyncItems, %Symbol.asyncIterator%). + const usingAsyncIterator = GetMethod(asyncItems, SYMBOL_ASYNC_ITERATOR); + + let usingSyncIterator = undefined; + + // 5. If usingAsyncIterator is undefined, then + if (usingAsyncIterator === undefined) { + // a. Let usingSyncIterator be ? GetMethod(asyncItems, %Symbol.iterator%). + usingSyncIterator = GetMethod(asyncItems, SYMBOL_ITERATOR); + } + + // 6. Let iteratorRecord be undefined. + let iteratorRecord = undefined; + + // 7. If usingAsyncIterator is not undefined, then + if (usingAsyncIterator !== undefined) { + // a. Set iteratorRecord to ? GetIteratorFromMethod(asyncItems, usingAsyncIterator). + iteratorRecord = GetIteratorFromMethod(asyncItems, usingAsyncIterator); + } + // 8. Else if usingSyncIterator is not undefined, then + else if (usingSyncIterator !== undefined) { + // a. Set iteratorRecord to CreateAsyncFromSyncIterator(? GetIteratorFromMethod(asyncItems, usingSyncIterator)). + const iteratorFromMethod = GetIteratorFromMethod(asyncItems, usingSyncIterator); + iteratorRecord = CreateAsyncFromSyncIterator( + iteratorFromMethod.iterator, + iteratorFromMethod.nextMethod, + iteratorFromMethod.done + ); + } + + // 9. If iteratorRecord is not undefined, then + if (iteratorRecord !== undefined) { + let array; + + // a. If IsConstructor(C) is true, then + if (IsConstructor(constructor)) { + // i. Let A be ? Construct(C). + array = new constructor(); + } + // b. Else, + else { + // i. Let A be ! ArrayCreate(0). + array = []; + } + + // c. Let k be 0. + // d. Repeat, + for (let k = 0; ; ++k) { + // i. If k ≥ 2**53 - 1, then + if (k >= MAX_ARRAY_LIKE_INDEX) { + // 1. Let error be ThrowCompletion(a newly created TypeError object). + const error = NewTypeError("Maximum array size exceeded"); + + // 2. Return ? AsyncIteratorClose(iteratorRecord, error). + return AsyncIteratorClose(iteratorRecord, error, true); + } + + // ii. Let Pk be ! ToString(𝔽(k)). + // iii. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). + // iv. Set nextResult to ? Await(nextResult). + const nextResult = await Call(iteratorRecord.nextMethod, iteratorRecord.iterator); + + // v. If nextResult is not an Object, throw a TypeError exception. + ThrowIfNotObject(nextResult); + + // vi. Let done be ? IteratorComplete(nextResult). + const done = IteratorComplete(nextResult); + + // vii. If done is true, then + if (done) { + // 1. Perform ? Set(A, "length", 𝔽(k), true). + array.length = k; + + // 2. Return A. + return array; + } + + // viii. Let nextValue be ? IteratorValue(nextResult). + const nextValue = nextResult.value; + + try { + let mappedValue; + + // ix. If mapping is true, then + if (mapping) { + // 1. Let mappedValue be Completion(Call(mapper, thisArg, « nextValue, 𝔽(k) »)). + // 2. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). + // 3. Set mappedValue to Completion(Await(mappedValue)). + // 4. IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord). + mappedValue = await Call(mapper, thisArg, nextValue, k); + } + // x. Else, + else { + // 1. Let mappedValue be nextValue. + mappedValue = nextValue; + } + + // xi. Let defineStatus be Completion(CreateDataPropertyOrThrow(A, Pk, mappedValue)). + // xii. IfAbruptCloseAsyncIterator(defineStatus, iteratorRecord). + CreateDataPropertyOrThrow(array, k, mappedValue); + } catch (exception) { + return AsyncIteratorClose(iteratorRecord, exception, true); + } + + // xiii. Set k to k + 1. + } + } + // 10. Else, + else { + // a. NOTE: asyncItems is neither an AsyncIterable nor an Iterable so assume it is an array-like object. + // b. Let arrayLike be ! ToObject(asyncItems). + const arrayLike = ToObject(asyncItems); + + // c. Let len be ? LengthOfArrayLike(arrayLike). + const length = ToLength(arrayLike.length); + + let array; + + // d. If IsConstructor(C) is true, then + if (IsConstructor(constructor)) { + // i. Let A be ? Construct(C, « 𝔽(len) »). + array = new constructor(length); + } + // e. Else, + else { + // i. Let A be ? ArrayCreate(len). + array = NewArrayWithLength(length); + } + + // f. Let k be 0. + // g. Repeat, while k < len, + for (let k = 0; k < length; ++k) { + // i. Let Pk be ! ToString(𝔽(k)). + // ii. Let kValue be ? Get(arrayLike, Pk). + // iii. Set kValue to ? Await(kValue). + const kValue = await arrayLike[k]; + + let mappedValue; + + // iv. If mapping is true, then + if (mapping) { + // 1. Let mappedValue be ? Call(mapper, thisArg, « kValue, 𝔽(k) »). + // 2. Set mappedValue to ? Await(mappedValue). + mappedValue = await Call(mapper, thisArg, kValue, k); + } + // v. Else, + else { + // 1. Let mappedValue be kValue. + mappedValue = kValue; + } + + // vi. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). + CreateDataPropertyOrThrow(array, k, mappedValue); + + // vii. Set k to k + 1. + } + + // h. Perform ? Set(A, "length", 𝔽(len), true). + array.length = length; + + // i. Return A. + return array; + } +} diff --git a/Libraries/LibJS/Runtime/JavaScriptImplementations/Builtins.d.ts b/Libraries/LibJS/Runtime/JavaScriptImplementations/Builtins.d.ts new file mode 100644 index 00000000000..ae26be3a98a --- /dev/null +++ b/Libraries/LibJS/Runtime/JavaScriptImplementations/Builtins.d.ts @@ -0,0 +1,98 @@ +/** + * Returns if the given value is callable (i.e. is a function) + * @param value {any} Value to check is callable + * @returns true if callable, false otherwise + */ +declare function IsCallable(value: any): boolean; + +/** + * Returns if the given value is a constructor + * @param value {any} Value to check is a constructor + * @returns true if is a constructor, false otherwise + */ +declare function IsConstructor(value: any): boolean; + +/** + * Converts the given value to its object form if it's not already an object. + * @param value {any} Value to convert to an object + * @returns {@link value} in its object form + * @throws {TypeError} If {@link value} is null or undefined + */ +declare function ToObject(value: any): object; + +/** + * Converts the given value to a boolean. + * @param value {any} Value to convert to a boolean + * @returns {@link value} Value converted to a boolean + */ +declare function ToBoolean(value: any): boolean; + +/** + * Converts the given value to a length. + * @param value {any} Value to convert to a length + * @returns {@link value} Value converted to a length + */ +declare function ToLength(value: any): number; + +/** + * Throws a {@link TypeError} with the given message. + * @param message The reason the error was thrown. + * @throws {TypeError} With the given message. + */ +declare function ThrowTypeError(message: string): never; + +/** + * Throws if the given value is an object. + * @param value {any} Value to check is an object + * @throws {TypeError} If value is not an object + */ +declare function ThrowIfNotObject(value: any): void; + +/** + * Creates a new object with no properties and no prototype. + */ +declare function NewObjectWithNoPrototype(): object; + +/** + * Creates a new TypeError with the given message. + */ +declare function NewTypeError(message: string): TypeError; + +/** + * Creates a new array with a starting length. + */ +declare function NewArrayWithLength(length: number): Array; + +/** + * Creates an AsyncFromSyncIterator object. + */ +declare function CreateAsyncFromSyncIterator(iterator: object, nextMethod: any, done: boolean): object; + +/** + * Creates the given property on the given object with the given value. + */ +declare function CreateDataPropertyOrThrow(object: object, property: string | number, value: any): undefined; + +/** + * Calls the given callee with `this` set to `thisValue` and with the passed in arguments. + * @param callee The function to call + * @param thisValue The value of `this` to use + * @param args The arguments to pass to the function + * @returns The result of calling the function + */ +declare function Call(callee: any, thisValue: any, ...args: any[]): any; + +/** + * @defaultValue {@link Symbol.iterator} + */ +declare const SYMBOL_ITERATOR: symbol; + +/** + * @defaultValue {@link Symbol.asyncIterator} + */ +declare const SYMBOL_ASYNC_ITERATOR: symbol; + +/** + * @defaultValue 2 ** 53 - 1 + */ +declare const MAX_ARRAY_LIKE_INDEX: number; diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index 1b3a44267e3..ae7bc5eeb38 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -1422,6 +1423,11 @@ void Object::define_native_function(Realm& realm, PropertyKey const& property_ke realm.define_builtin(builtin.value(), function); } +void Object::define_native_javascript_backed_function(PropertyKey const& property_key, GC::Ref function, i32, PropertyAttributes attributes) +{ + define_direct_property(property_key, function, attributes); +} + // 20.1.2.3.1 ObjectDefineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-objectdefineproperties ThrowCompletionOr Object::define_properties(Value properties) { diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index bd2b2fe5ff4..1fe51b26e50 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -204,6 +204,7 @@ public: void define_native_function(Realm&, PropertyKey const&, ESCAPING Function(VM&)>, i32 length, PropertyAttributes attributes, Optional builtin = {}); void define_native_accessor(Realm&, PropertyKey const&, ESCAPING Function(VM&)> getter, ESCAPING Function(VM&)> setter, PropertyAttributes attributes); + void define_native_javascript_backed_function(PropertyKey const&, GC::Ref function, i32 length, PropertyAttributes attributes); virtual bool is_dom_node() const { return false; } virtual bool is_dom_element() const { return false; } diff --git a/Libraries/LibJS/Tests/builtins/Array/Array.fromAsync.js b/Libraries/LibJS/Tests/builtins/Array/Array.fromAsync.js index b0135e6b94e..d9183271e3c 100644 --- a/Libraries/LibJS/Tests/builtins/Array/Array.fromAsync.js +++ b/Libraries/LibJS/Tests/builtins/Array/Array.fromAsync.js @@ -101,4 +101,36 @@ describe("normal behavior", () => { expect(counter).toBe(1); }); + + asyncTest("mapper exception takes priority over iterator closure exception", async () => { + let exceptionOrder = []; + + async function* iterator() { + try { + yield 1; + yield 2; + } finally { + exceptionOrder.push("iterator"); + throw "iterator exception"; + } + } + + let exception = null; + + try { + await Array.fromAsync(iterator(), value => { + if (value === 1) { + exceptionOrder.push("mapper"); + throw "mapper exception"; + } + + return value; + }); + } catch (e) { + exception = e; + } + + expect(exceptionOrder).toEqual(["mapper", "iterator"]); + expect(exception).toBe("mapper exception"); + }); }); diff --git a/Meta/CMake/common_compile_options.cmake b/Meta/CMake/common_compile_options.cmake index d8cca50c408..db0ca07c47a 100644 --- a/Meta/CMake/common_compile_options.cmake +++ b/Meta/CMake/common_compile_options.cmake @@ -135,6 +135,10 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_CXX_SIMULATE_ID MATCHES add_cxx_compile_options(-Wno-implicit-const-int-float-conversion) add_cxx_compile_options(-Wno-user-defined-literals) add_cxx_compile_options(-Wno-unqualified-std-cast-call) + + # Used for the #embed directive. + # FIXME: Remove this once #embed is no longer an extension. + add_cxx_compile_options(-Wno-c23-extensions) elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") # Only ignore expansion-to-defined for g++, clang's implementation doesn't complain about function-like macros add_cxx_compile_options(-Wno-expansion-to-defined) @@ -154,6 +158,11 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang$" AND CMAKE_CXX_SIMULATE_ID MATCHES add_cxx_compile_options(-Wno-reserved-identifier) add_cxx_compile_options(-Wno-user-defined-literals) add_cxx_compile_options(-Wno-unqualified-std-cast-call) + + # Used for the #embed directive. + # FIXME: Remove this once #embed is no longer an extension. + add_cxx_compile_options(-Wno-c23-extensions) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) # TODO: this seems wrong, but we use this kind of code too much