2020-09-20 19:24:44 +02:00
/*
2023-02-19 22:07:52 +01:00
* Copyright ( c ) 2020 - 2023 , Andreas Kling < kling @ serenityos . org >
2023-02-11 16:14:41 +00:00
* Copyright ( c ) 2020 - 2023 , Linus Groh < linusg @ serenityos . org >
2022-01-19 01:22:58 +01:00
* Copyright ( c ) 2021 - 2022 , David Tuin < davidot @ serenityos . org >
2020-09-20 19:24:44 +02:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-09-20 19:24:44 +02:00
*/
2023-01-21 12:36:47 -05:00
# include <AK/Array.h>
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
# include <AK/Debug.h>
2022-01-19 01:22:58 +01:00
# include <AK/LexicalPath.h>
2020-09-27 15:18:55 +02:00
# include <AK/ScopeGuard.h>
2023-02-11 16:14:41 +00:00
# include <AK/String.h>
2020-09-27 17:24:14 +02:00
# include <AK/StringBuilder.h>
2023-03-22 02:35:30 +11:00
# include <LibFileSystem/FileSystem.h>
2022-11-23 13:41:50 +01:00
# include <LibJS/AST.h>
2023-06-22 15:59:18 +02:00
# include <LibJS/Bytecode/Interpreter.h>
2020-09-20 19:24:44 +02:00
# include <LibJS/Interpreter.h>
2021-06-22 13:30:48 +02:00
# include <LibJS/Runtime/AbstractOperations.h>
2020-12-05 16:38:29 +01:00
# include <LibJS/Runtime/Array.h>
2021-08-09 15:21:15 +02:00
# include <LibJS/Runtime/BoundFunction.h>
LibJS: Implement [[Call]] and [[Construct]] internal slots properly
This patch implements:
- Spec compliant [[Call]] and [[Construct]] internal slots, as virtual
FunctionObject::internal_{call,construct}(). These effectively replace
the old virtual FunctionObject::{call,construct}(), but with several
advantages:
- Clear and consistent naming, following the object internal methods
- Use of completions
- internal_construct() returns an Object, and not Value! This has been
a source of confusion for a long time, since in the spec there's
always an Object returned but the Value return type in LibJS meant
that this could not be fully trusted and something could screw you
over.
- Arguments are passed explicitly in form of a MarkedValueList,
allowing manipulation (BoundFunction). We still put them on the
execution context as a lot of code depends on it (VM::arguments()),
but not from the Call() / Construct() AOs anymore, which now allows
for bypassing them and invoking [[Call]] / [[Construct]] directly.
Nothing but Call() / Construct() themselves do that at the moment,
but future additions to ECMA262 or already existing web specs might.
- Spec compliant, standalone Call() and Construct() AOs: currently the
closest we have is VM::{call,construct}(), but those try to cater to
all the different function object subclasses at once, resulting in a
horrible mess and calling AOs with functions they should never be
called with; most prominently PrepareForOrdinaryCall and
OrdinaryCallBindThis, which are only for ECMAScriptFunctionObject.
As a result this also contains an implicit optimization: we no longer
need to create a new function environment for NativeFunctions - which,
worth mentioning, is what started this whole crusade in the first place
:^)
2021-10-08 20:37:21 +01:00
# include <LibJS/Runtime/Completion.h>
2021-09-24 22:40:38 +02:00
# include <LibJS/Runtime/ECMAScriptFunctionObject.h>
2020-09-27 15:18:55 +02:00
# include <LibJS/Runtime/Error.h>
2021-06-15 22:16:17 +03:00
# include <LibJS/Runtime/FinalizationRegistry.h>
2021-07-01 12:24:46 +02:00
# include <LibJS/Runtime/FunctionEnvironment.h>
2023-06-24 09:30:46 -04:00
# include <LibJS/Runtime/Iterator.h>
2021-05-29 16:03:19 +04:30
# include <LibJS/Runtime/IteratorOperations.h>
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
# include <LibJS/Runtime/NativeFunction.h>
2022-10-02 10:59:22 +01:00
# include <LibJS/Runtime/PromiseCapability.h>
2020-09-27 15:18:55 +02:00
# include <LibJS/Runtime/Reference.h>
2020-09-22 16:18:51 +02:00
# include <LibJS/Runtime/Symbol.h>
2020-09-20 19:24:44 +02:00
# include <LibJS/Runtime/VM.h>
2022-01-19 01:22:58 +01:00
# include <LibJS/SourceTextModule.h>
2022-01-27 14:51:21 +01:00
# include <LibJS/SyntheticModule.h>
2020-09-20 19:24:44 +02:00
namespace JS {
2023-03-17 10:44:47 -04:00
ErrorOr < NonnullRefPtr < VM > > VM : : create ( OwnPtr < CustomData > custom_data )
2020-09-20 19:24:44 +02:00
{
2023-03-17 10:21:51 -04:00
ErrorMessages error_messages { } ;
2023-03-17 10:44:47 -04:00
error_messages [ to_underlying ( ErrorMessage : : OutOfMemory ) ] = TRY ( String : : from_utf8 ( ErrorType : : OutOfMemory . message ( ) ) ) ;
2023-03-17 10:21:51 -04:00
auto vm = adopt_ref ( * new VM ( move ( custom_data ) , move ( error_messages ) ) ) ;
WellKnownSymbols well_known_symbols {
# define __JS_ENUMERATE(SymbolName, snake_name) \
2023-03-17 10:44:47 -04:00
Symbol : : create ( * vm , TRY ( " Symbol. " # SymbolName # # _string ) , false ) ,
2023-03-17 10:21:51 -04:00
JS_ENUMERATE_WELL_KNOWN_SYMBOLS
# undef __JS_ENUMERATE
} ;
vm - > set_well_known_symbols ( move ( well_known_symbols ) ) ;
return vm ;
2020-09-20 19:24:44 +02:00
}
2023-01-21 12:36:47 -05:00
template < u32 . . . code_points >
static constexpr auto make_single_ascii_character_strings ( IndexSequence < code_points . . . > )
{
return AK : : Array { ( String : : from_code_point ( code_points ) ) . . . } ;
}
static constexpr auto single_ascii_character_strings = make_single_ascii_character_strings ( MakeIndexSequence < 128 > ( ) ) ;
2023-03-17 10:21:51 -04:00
VM : : VM ( OwnPtr < CustomData > custom_data , ErrorMessages error_messages )
2020-09-20 19:24:44 +02:00
: m_heap ( * this )
2023-03-17 10:21:51 -04:00
, m_error_messages ( move ( error_messages ) )
2021-09-08 22:58:36 +02:00
, m_custom_data ( move ( custom_data ) )
2020-09-20 19:24:44 +02:00
{
2023-06-22 15:59:18 +02:00
m_bytecode_interpreter = make < Bytecode : : Interpreter > ( * this ) ;
2023-01-21 10:42:46 -05:00
m_empty_string = m_heap . allocate_without_realm < PrimitiveString > ( String { } ) ;
2023-01-21 12:36:47 -05:00
for ( size_t i = 0 ; i < single_ascii_character_strings . size ( ) ; + + i )
m_single_ascii_character_strings [ i ] = m_heap . allocate_without_realm < PrimitiveString > ( single_ascii_character_strings [ i ] ) ;
2020-10-22 17:43:48 +02:00
2022-02-06 03:46:45 +00:00
// Default hook implementations. These can be overridden by the host, for example, LibWeb overrides the default hooks to place promise jobs on the microtask queue.
host_promise_rejection_tracker = [ this ] ( Promise & promise , Promise : : RejectionOperation operation ) {
promise_rejection_tracker ( promise , operation ) ;
} ;
2022-08-21 16:09:38 +01:00
host_call_job_callback = [ this ] ( JobCallback & job_callback , Value this_value , MarkedVector < Value > arguments ) {
return call_job_callback ( * this , job_callback , this_value , move ( arguments ) ) ;
2022-02-06 03:46:45 +00:00
} ;
host_enqueue_finalization_registry_cleanup_job = [ this ] ( FinalizationRegistry & finalization_registry ) {
enqueue_finalization_registry_cleanup_job ( finalization_registry ) ;
} ;
host_enqueue_promise_job = [ this ] ( Function < ThrowCompletionOr < Value > ( ) > job , Realm * realm ) {
enqueue_promise_job ( move ( job ) , realm ) ;
} ;
host_make_job_callback = [ ] ( FunctionObject & function_object ) {
return make_job_callback ( function_object ) ;
} ;
2022-01-19 01:22:58 +01:00
host_resolve_imported_module = [ & ] ( ScriptOrModule referencing_script_or_module , ModuleRequest const & specifier ) {
return resolve_imported_module ( move ( referencing_script_or_module ) , specifier ) ;
} ;
2023-02-17 07:30:42 -05:00
host_import_module_dynamically = [ & ] ( ScriptOrModule , ModuleRequest const & , PromiseCapability const & promise_capability ) - > ThrowCompletionOr < void > {
2022-01-18 19:39:36 +01:00
// By default, we throw on dynamic imports this is to prevent arbitrary file access by scripts.
VERIFY ( current_realm ( ) ) ;
2022-08-16 00:20:49 +01:00
auto & realm = * current_realm ( ) ;
2022-12-13 20:49:50 +00:00
auto promise = Promise : : create ( realm ) ;
2022-01-18 19:39:36 +01:00
// If you are here because you want to enable dynamic module importing make sure it won't be a security problem
// by checking the default implementation of HostImportModuleDynamically and creating your own hook or calling
// vm.enable_default_host_import_module_dynamically_hook().
2023-02-17 07:30:42 -05:00
promise - > reject ( MUST_OR_THROW_OOM ( Error : : create ( realm , ErrorType : : DynamicImportNotAllowed . message ( ) ) ) ) ;
2022-01-18 19:39:36 +01:00
promise - > perform_then (
2022-08-22 11:48:08 +01:00
NativeFunction : : create ( realm , " " , [ ] ( auto & ) - > ThrowCompletionOr < Value > {
2022-01-18 19:39:36 +01:00
VERIFY_NOT_REACHED ( ) ;
} ) ,
2022-10-02 12:11:30 +01:00
NativeFunction : : create ( realm , " " , [ & promise_capability ] ( auto & vm ) - > ThrowCompletionOr < Value > {
2022-01-18 19:39:36 +01:00
auto error = vm . argument ( 0 ) ;
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »).
2022-10-02 12:11:30 +01:00
MUST ( call ( vm , * promise_capability . reject ( ) , js_undefined ( ) , error ) ) ;
2022-01-18 19:39:36 +01:00
// b. Return undefined.
return js_undefined ( ) ;
} ) ,
{ } ) ;
2023-02-17 07:30:42 -05:00
return { } ;
2022-01-18 19:39:36 +01:00
} ;
2022-10-02 12:11:30 +01:00
host_finish_dynamic_import = [ & ] ( ScriptOrModule referencing_script_or_module , ModuleRequest const & specifier , PromiseCapability const & promise_capability , Promise * promise ) {
2022-01-18 19:39:36 +01:00
return finish_dynamic_import ( move ( referencing_script_or_module ) , specifier , promise_capability , promise ) ;
} ;
2022-01-18 19:40:43 +01:00
host_get_import_meta_properties = [ & ] ( SourceTextModule const & ) - > HashMap < PropertyKey , Value > {
return { } ;
} ;
host_finalize_import_meta = [ & ] ( Object * , SourceTextModule const & ) {
} ;
2022-01-27 02:44:03 +01:00
host_get_supported_import_assertions = [ & ] {
2022-12-04 18:02:33 +00:00
return Vector < DeprecatedString > { " type " } ;
2022-01-27 02:44:03 +01:00
} ;
2022-04-10 00:55:45 +01:00
// 19.2.1.2 HostEnsureCanCompileStrings ( callerRealm, calleeRealm ), https://tc39.es/ecma262/#sec-hostensurecancompilestrings
2022-04-22 19:10:27 +01:00
host_ensure_can_compile_strings = [ ] ( Realm & ) - > ThrowCompletionOr < void > {
// The host-defined abstract operation HostEnsureCanCompileStrings takes argument calleeRealm (a Realm Record)
// and returns either a normal completion containing unused or a throw completion.
2022-04-10 00:55:45 +01:00
// It allows host environments to block certain ECMAScript functions which allow developers to compile strings into ECMAScript code.
// An implementation of HostEnsureCanCompileStrings must conform to the following requirements:
// - If the returned Completion Record is a normal completion, it must be a normal completion containing unused.
// The default implementation of HostEnsureCanCompileStrings is to return NormalCompletion(unused).
return { } ;
} ;
2022-08-14 00:51:32 +02:00
host_ensure_can_add_private_element = [ ] ( Object & ) - > ThrowCompletionOr < void > {
// The host-defined abstract operation HostEnsureCanAddPrivateElement takes argument O (an Object)
// and returns either a normal completion containing unused or a throw completion.
// It allows host environments to prevent the addition of private elements to particular host-defined exotic objects.
// An implementation of HostEnsureCanAddPrivateElement must conform to the following requirements:
// - If O is not a host-defined exotic object, this abstract operation must return NormalCompletion(unused) and perform no other steps.
// - Any two calls of this abstract operation with the same argument must return the same kind of Completion Record.
// The default implementation of HostEnsureCanAddPrivateElement is to return NormalCompletion(unused).
return { } ;
// This abstract operation is only invoked by ECMAScript hosts that are web browsers.
// NOTE: Since LibJS has no way of knowing whether the current environment is a browser we always
// call HostEnsureCanAddPrivateElement when needed.
} ;
2023-02-16 12:55:22 -05:00
}
2023-06-22 15:59:18 +02:00
VM : : ~ VM ( ) = default ;
2023-02-16 14:09:11 -05:00
String const & VM : : error_message ( ErrorMessage type ) const
2023-02-16 12:55:22 -05:00
{
VERIFY ( type < ErrorMessage : : __Count ) ;
auto const & message = m_error_messages [ to_underlying ( type ) ] ;
VERIFY ( ! message . is_empty ( ) ) ;
return message ;
2020-09-20 19:24:44 +02:00
}
2022-01-18 19:39:36 +01:00
void VM : : enable_default_host_import_module_dynamically_hook ( )
{
2022-10-02 12:11:30 +01:00
host_import_module_dynamically = [ & ] ( ScriptOrModule referencing_script_or_module , ModuleRequest const & specifier , PromiseCapability const & promise_capability ) {
2022-01-18 19:39:36 +01:00
return import_module_dynamically ( move ( referencing_script_or_module ) , specifier , promise_capability ) ;
} ;
}
2020-09-20 19:24:44 +02:00
Interpreter & VM : : interpreter ( )
{
2021-02-23 20:42:32 +01:00
VERIFY ( ! m_interpreters . is_empty ( ) ) ;
2020-09-20 19:24:44 +02:00
return * m_interpreters . last ( ) ;
}
Interpreter * VM : : interpreter_if_exists ( )
{
if ( m_interpreters . is_empty ( ) )
return nullptr ;
return m_interpreters . last ( ) ;
}
2023-06-22 15:59:18 +02:00
Bytecode : : Interpreter & VM : : bytecode_interpreter ( )
{
return * m_bytecode_interpreter ;
}
Bytecode : : Interpreter * VM : : bytecode_interpreter_if_exists ( )
{
if ( ! Bytecode : : Interpreter : : enabled ( ) )
return nullptr ;
return m_bytecode_interpreter ;
}
2020-09-20 19:24:44 +02:00
void VM : : push_interpreter ( Interpreter & interpreter )
{
m_interpreters . append ( & interpreter ) ;
}
void VM : : pop_interpreter ( Interpreter & interpreter )
{
2021-02-23 20:42:32 +01:00
VERIFY ( ! m_interpreters . is_empty ( ) ) ;
2020-09-20 19:24:44 +02:00
auto * popped_interpreter = m_interpreters . take_last ( ) ;
2021-02-23 20:42:32 +01:00
VERIFY ( popped_interpreter = = & interpreter ) ;
2020-09-20 19:24:44 +02:00
}
2020-09-21 13:36:32 +02:00
VM : : InterpreterExecutionScope : : InterpreterExecutionScope ( Interpreter & interpreter )
2020-09-20 19:24:44 +02:00
: m_interpreter ( interpreter )
{
m_interpreter . vm ( ) . push_interpreter ( m_interpreter ) ;
}
2020-09-21 13:36:32 +02:00
VM : : InterpreterExecutionScope : : ~ InterpreterExecutionScope ( )
2020-09-20 19:24:44 +02:00
{
m_interpreter . vm ( ) . pop_interpreter ( m_interpreter ) ;
}
2020-09-21 13:47:33 +02:00
void VM : : gather_roots ( HashTable < Cell * > & roots )
{
2020-09-22 16:36:33 +02:00
roots . set ( m_empty_string ) ;
2023-02-26 16:09:02 -07:00
for ( auto string : m_single_ascii_character_strings )
2020-10-22 17:43:48 +02:00
roots . set ( string ) ;
2021-10-03 14:52:53 +02:00
auto gather_roots_from_execution_context_stack = [ & roots ] ( Vector < ExecutionContext * > const & stack ) {
for ( auto & execution_context : stack ) {
if ( execution_context - > this_value . is_cell ( ) )
roots . set ( & execution_context - > this_value . as_cell ( ) ) ;
for ( auto & argument : execution_context - > arguments ) {
if ( argument . is_cell ( ) )
roots . set ( & argument . as_cell ( ) ) ;
}
roots . set ( execution_context - > lexical_environment ) ;
roots . set ( execution_context - > variable_environment ) ;
2021-12-08 09:57:55 +01:00
roots . set ( execution_context - > private_environment ) ;
2023-02-26 16:09:02 -07:00
if ( auto context_owner = execution_context - > context_owner )
2022-11-21 11:18:15 +01:00
roots . set ( context_owner ) ;
2022-09-05 14:31:25 +02:00
execution_context - > script_or_module . visit (
[ ] ( Empty ) { } ,
[ & ] ( auto & script_or_module ) {
roots . set ( script_or_module . ptr ( ) ) ;
} ) ;
2020-09-27 15:18:55 +02:00
}
2021-10-03 14:52:53 +02:00
} ;
gather_roots_from_execution_context_stack ( m_execution_context_stack ) ;
for ( auto & saved_stack : m_saved_execution_context_stacks )
gather_roots_from_execution_context_stack ( saved_stack ) ;
2020-09-22 16:18:51 +02:00
# define __JS_ENUMERATE(SymbolName, snake_name) \
2023-05-21 13:13:53 +02:00
roots . set ( m_well_known_symbols . snake_name ) ;
2020-09-22 16:18:51 +02:00
JS_ENUMERATE_WELL_KNOWN_SYMBOLS
# undef __JS_ENUMERATE
2022-12-06 20:58:15 +00:00
for ( auto & symbol : m_global_symbol_registry )
2020-09-22 16:18:51 +02:00
roots . set ( symbol . value ) ;
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
2023-02-26 16:09:02 -07:00
for ( auto finalization_registry : m_finalization_registry_cleanup_jobs )
2021-08-15 00:04:00 +03:00
roots . set ( finalization_registry ) ;
2020-09-22 16:18:51 +02:00
}
2023-01-08 19:23:00 -05:00
ThrowCompletionOr < Value > VM : : named_evaluation_if_anonymous_function ( ASTNode const & expression , DeprecatedFlyString const & name )
2020-09-27 15:18:55 +02:00
{
2021-09-22 12:44:56 +02:00
// 8.3.3 Static Semantics: IsAnonymousFunctionDefinition ( expr ), https://tc39.es/ecma262/#sec-isanonymousfunctiondefinition
// And 8.3.5 Runtime Semantics: NamedEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-namedevaluation
if ( is < FunctionExpression > ( expression ) ) {
auto & function = static_cast < FunctionExpression const & > ( expression ) ;
if ( ! function . has_name ( ) ) {
2023-06-23 14:27:42 +02:00
return function . instantiate_ordinary_function_expression ( * this , name ) ;
2021-05-29 16:03:19 +04:30
}
2021-09-22 12:44:56 +02:00
} else if ( is < ClassExpression > ( expression ) ) {
auto & class_expression = static_cast < ClassExpression const & > ( expression ) ;
if ( ! class_expression . has_name ( ) ) {
2023-06-25 17:33:17 +02:00
return TRY ( class_expression . class_definition_evaluation ( * this , { } , name ) ) ;
2021-05-29 16:03:19 +04:30
}
}
2023-06-24 06:44:01 +02:00
return execute_ast_node ( expression ) ;
2021-05-29 16:03:19 +04:30
}
2021-09-22 12:44:56 +02:00
// 13.15.5.2 Runtime Semantics: DestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-destructuringassignmentevaluation
2023-02-19 22:07:52 +01:00
ThrowCompletionOr < void > VM : : destructuring_assignment_evaluation ( NonnullRefPtr < BindingPattern const > const & target , Value value )
2021-05-29 16:03:19 +04:30
{
2021-09-22 12:44:56 +02:00
// Note: DestructuringAssignmentEvaluation is just like BindingInitialization without an environment
// And it allows member expressions. We thus trust the parser to disallow member expressions
// in any non assignment binding and just call BindingInitialization with a nullptr environment
2022-08-21 20:38:35 +01:00
return binding_initialization ( target , value , nullptr ) ;
2021-05-29 16:03:19 +04:30
}
2021-09-22 12:44:56 +02:00
// 8.5.2 Runtime Semantics: BindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
2023-01-08 19:23:00 -05:00
ThrowCompletionOr < void > VM : : binding_initialization ( DeprecatedFlyString const & target , Value value , Environment * environment )
2021-05-29 16:03:19 +04:30
{
2021-12-29 10:21:48 +01:00
// 1. Let name be StringValue of Identifier.
// 2. Return ? InitializeBoundName(name, value, environment).
2022-08-21 19:24:32 +01:00
return initialize_bound_name ( * this , target , value , environment ) ;
2021-05-29 16:03:19 +04:30
}
2021-09-22 12:44:56 +02:00
// 8.5.2 Runtime Semantics: BindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization
2023-02-19 22:07:52 +01:00
ThrowCompletionOr < void > VM : : binding_initialization ( NonnullRefPtr < BindingPattern const > const & target , Value value , Environment * environment )
2021-05-29 16:03:19 +04:30
{
2022-08-21 15:56:27 +01:00
auto & vm = * this ;
2021-12-29 10:21:48 +01:00
// BindingPattern : ObjectBindingPattern
2021-09-22 12:44:56 +02:00
if ( target - > kind = = BindingPattern : : Kind : : Object ) {
2021-12-29 10:21:48 +01:00
// 1. Perform ? RequireObjectCoercible(value).
2022-08-21 19:24:32 +01:00
TRY ( require_object_coercible ( vm , value ) ) ;
2021-12-29 10:21:48 +01:00
2022-05-02 20:54:39 +02:00
// 2. Return ? BindingInitialization of ObjectBindingPattern with arguments value and environment.
2021-12-29 10:21:48 +01:00
// BindingInitialization of ObjectBindingPattern
2022-05-01 01:21:22 +02:00
// 1. Perform ? PropertyBindingInitialization of BindingPropertyList with arguments value and environment.
2022-08-21 20:38:35 +01:00
TRY ( property_binding_initialization ( * target , value , environment ) ) ;
2021-12-29 10:21:48 +01:00
2022-05-02 20:54:39 +02:00
// 2. Return unused.
2021-09-22 12:44:56 +02:00
return { } ;
2021-12-29 10:21:48 +01:00
}
// BindingPattern : ArrayBindingPattern
else {
// 1. Let iteratorRecord be ? GetIterator(value).
2022-08-21 15:56:27 +01:00
auto iterator_record = TRY ( get_iterator ( vm , value ) ) ;
2021-05-29 16:03:19 +04:30
2022-05-02 20:54:39 +02:00
// 2. Let result be Completion(IteratorBindingInitialization of ArrayBindingPattern with arguments iteratorRecord and environment).
2022-08-21 20:38:35 +01:00
auto result = iterator_binding_initialization ( * target , iterator_record , environment ) ;
2021-05-29 16:03:19 +04:30
2021-12-29 10:21:48 +01:00
// 3. If iteratorRecord.[[Done]] is false, return ? IteratorClose(iteratorRecord, result).
2022-01-09 19:12:24 +01:00
if ( ! iterator_record . done ) {
2021-10-20 13:36:14 -04:00
// iterator_close() always returns a Completion, which ThrowCompletionOr will interpret as a throw
// completion. So only return the result of iterator_close() if it is indeed a throw completion.
auto completion = result . is_throw_completion ( ) ? result . release_error ( ) : normal_completion ( { } ) ;
2022-08-21 15:56:27 +01:00
if ( completion = iterator_close ( vm , iterator_record , move ( completion ) ) ; completion . is_error ( ) )
2021-10-20 13:36:14 -04:00
return completion . release_error ( ) ;
2021-09-22 12:44:56 +02:00
}
2021-10-20 13:36:14 -04:00
2022-05-02 20:54:39 +02:00
// 4. Return ? result.
2021-09-22 12:44:56 +02:00
return result ;
}
}
2023-06-24 06:44:01 +02:00
ThrowCompletionOr < Value > VM : : execute_ast_node ( ASTNode const & node )
{
if ( auto * bytecode_interpreter = bytecode_interpreter_if_exists ( ) ) {
auto executable = TRY ( Bytecode : : compile ( * this , node , FunctionKind : : Normal , " " sv ) ) ;
auto result_or_error = bytecode_interpreter - > run_and_return_frame ( * current_realm ( ) , * executable , nullptr ) ;
if ( result_or_error . value . is_error ( ) )
return result_or_error . value . release_error ( ) ;
return result_or_error . frame - > registers [ 0 ] ;
}
return TRY ( node . execute ( interpreter ( ) ) ) . value ( ) ;
}
2021-09-22 12:44:56 +02:00
// 13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-propertydestructuringassignmentevaluation
// 14.3.3.1 Runtime Semantics: PropertyBindingInitialization, https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-propertybindinginitialization
2022-08-21 20:38:35 +01:00
ThrowCompletionOr < void > VM : : property_binding_initialization ( BindingPattern const & binding , Value value , Environment * environment )
2021-09-22 12:44:56 +02:00
{
2022-08-21 14:00:56 +01:00
auto & vm = * this ;
2022-08-21 20:38:35 +01:00
auto & realm = * vm . current_realm ( ) ;
2023-04-13 15:26:41 +02:00
auto object = TRY ( value . to_object ( vm ) ) ;
2021-05-29 16:03:19 +04:30
2021-10-24 16:19:28 +02:00
HashTable < PropertyKey > seen_names ;
2021-09-22 12:44:56 +02:00
for ( auto & property : binding . entries ) {
2021-09-18 01:11:32 +02:00
2021-09-22 12:44:56 +02:00
VERIFY ( ! property . is_elision ( ) ) ;
2021-09-18 01:11:32 +02:00
2021-09-22 12:44:56 +02:00
if ( property . is_rest ) {
Reference assignment_target ;
2023-02-19 22:07:52 +01:00
if ( auto identifier_ptr = property . name . get_pointer < NonnullRefPtr < Identifier const > > ( ) ) {
2021-12-30 14:13:20 +01:00
assignment_target = TRY ( resolve_binding ( ( * identifier_ptr ) - > string ( ) , environment ) ) ;
2023-02-19 22:07:52 +01:00
} else if ( auto member_ptr = property . alias . get_pointer < NonnullRefPtr < MemberExpression const > > ( ) ) {
2022-08-16 19:28:17 +01:00
assignment_target = TRY ( ( * member_ptr ) - > to_reference ( interpreter ( ) ) ) ;
2021-09-22 12:44:56 +02:00
} else {
VERIFY_NOT_REACHED ( ) ;
}
2021-05-29 16:03:19 +04:30
2022-12-13 20:49:50 +00:00
auto rest_object = Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
2021-09-22 12:44:56 +02:00
VERIFY ( rest_object ) ;
2021-05-29 16:03:19 +04:30
2022-08-21 14:00:56 +01:00
TRY ( rest_object - > copy_data_properties ( vm , object , seen_names ) ) ;
2021-09-22 12:44:56 +02:00
if ( ! environment )
2022-08-21 15:39:13 +01:00
return assignment_target . put_value ( vm , rest_object ) ;
2021-09-22 12:44:56 +02:00
else
2022-08-21 15:39:13 +01:00
return assignment_target . initialize_referenced_binding ( vm , rest_object ) ;
2021-09-22 12:44:56 +02:00
}
2021-05-29 16:03:19 +04:30
2022-01-02 21:37:50 +01:00
auto name = TRY ( property . name . visit (
[ & ] ( Empty ) - > ThrowCompletionOr < PropertyKey > { VERIFY_NOT_REACHED ( ) ; } ,
2023-02-19 22:07:52 +01:00
[ & ] ( NonnullRefPtr < Identifier const > const & identifier ) - > ThrowCompletionOr < PropertyKey > {
2022-01-02 21:37:50 +01:00
return identifier - > string ( ) ;
2021-09-22 12:44:56 +02:00
} ,
2023-02-19 22:07:52 +01:00
[ & ] ( NonnullRefPtr < Expression const > const & expression ) - > ThrowCompletionOr < PropertyKey > {
2023-06-24 06:44:01 +02:00
auto result = TRY ( execute_ast_node ( * expression ) ) ;
2022-08-21 14:00:56 +01:00
return result . to_property_key ( vm ) ;
2022-01-02 21:37:50 +01:00
} ) ) ;
2021-05-29 16:03:19 +04:30
2021-09-22 12:44:56 +02:00
seen_names . set ( name ) ;
2023-02-19 22:07:52 +01:00
if ( property . name . has < NonnullRefPtr < Identifier const > > ( ) & & property . alias . has < Empty > ( ) ) {
2021-09-22 12:44:56 +02:00
// FIXME: this branch and not taking this have a lot in common we might want to unify it more (like it was before).
2023-02-19 22:07:52 +01:00
auto & identifier = * property . name . get < NonnullRefPtr < Identifier const > > ( ) ;
2021-12-30 14:13:20 +01:00
auto reference = TRY ( resolve_binding ( identifier . string ( ) , environment ) ) ;
2021-09-22 12:44:56 +02:00
2021-10-02 23:52:27 +01:00
auto value_to_assign = TRY ( object - > get ( name ) ) ;
2021-09-22 12:44:56 +02:00
if ( property . initializer & & value_to_assign . is_undefined ( ) ) {
2022-08-16 19:28:17 +01:00
value_to_assign = TRY ( named_evaluation_if_anonymous_function ( * property . initializer , identifier . string ( ) ) ) ;
2021-05-29 16:03:19 +04:30
}
2021-09-22 12:44:56 +02:00
if ( ! environment )
2022-08-21 15:39:13 +01:00
TRY ( reference . put_value ( vm , value_to_assign ) ) ;
2021-09-22 12:44:56 +02:00
else
2022-08-21 15:39:13 +01:00
TRY ( reference . initialize_referenced_binding ( vm , value_to_assign ) ) ;
2021-09-22 12:44:56 +02:00
continue ;
}
2021-05-29 16:03:19 +04:30
2021-12-30 14:13:20 +01:00
auto reference_to_assign_to = TRY ( property . alias . visit (
[ & ] ( Empty ) - > ThrowCompletionOr < Optional < Reference > > { return Optional < Reference > { } ; } ,
2023-02-19 22:07:52 +01:00
[ & ] ( NonnullRefPtr < Identifier const > const & identifier ) - > ThrowCompletionOr < Optional < Reference > > {
2021-12-30 14:13:20 +01:00
return TRY ( resolve_binding ( identifier - > string ( ) , environment ) ) ;
2021-09-22 12:44:56 +02:00
} ,
2023-02-19 22:07:52 +01:00
[ & ] ( NonnullRefPtr < BindingPattern const > const & ) - > ThrowCompletionOr < Optional < Reference > > { return Optional < Reference > { } ; } ,
[ & ] ( NonnullRefPtr < MemberExpression const > const & member_expression ) - > ThrowCompletionOr < Optional < Reference > > {
2022-08-16 19:28:17 +01:00
return TRY ( member_expression - > to_reference ( interpreter ( ) ) ) ;
2021-12-30 14:13:20 +01:00
} ) ) ;
2021-09-22 12:44:56 +02:00
2021-10-02 23:52:27 +01:00
auto value_to_assign = TRY ( object - > get ( name ) ) ;
2021-09-22 12:44:56 +02:00
if ( property . initializer & & value_to_assign . is_undefined ( ) ) {
2023-02-19 22:07:52 +01:00
if ( auto * identifier_ptr = property . alias . get_pointer < NonnullRefPtr < Identifier const > > ( ) )
2022-08-16 19:28:17 +01:00
value_to_assign = TRY ( named_evaluation_if_anonymous_function ( * property . initializer , ( * identifier_ptr ) - > string ( ) ) ) ;
2021-09-22 12:44:56 +02:00
else
2023-06-24 06:44:01 +02:00
value_to_assign = TRY ( execute_ast_node ( * property . initializer ) ) ;
2020-09-27 15:18:55 +02:00
}
2021-06-12 18:04:28 -07:00
2023-02-19 22:07:52 +01:00
if ( auto * binding_ptr = property . alias . get_pointer < NonnullRefPtr < BindingPattern const > > ( ) ) {
2022-08-21 20:38:35 +01:00
TRY ( binding_initialization ( * binding_ptr , value_to_assign , environment ) ) ;
2021-09-22 12:44:56 +02:00
} else {
VERIFY ( reference_to_assign_to . has_value ( ) ) ;
if ( ! environment )
2022-08-21 15:39:13 +01:00
TRY ( reference_to_assign_to - > put_value ( vm , value_to_assign ) ) ;
2021-09-22 12:44:56 +02:00
else
2022-08-21 15:39:13 +01:00
TRY ( reference_to_assign_to - > initialize_referenced_binding ( vm , value_to_assign ) ) ;
2021-09-22 12:44:56 +02:00
}
2020-09-27 15:18:55 +02:00
}
2021-06-12 18:04:28 -07:00
2021-09-22 12:44:56 +02:00
return { } ;
}
2020-09-27 15:18:55 +02:00
2021-09-22 12:44:56 +02:00
// 13.15.5.5 Runtime Semantics: IteratorDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-iteratordestructuringassignmentevaluation
// 8.5.3 Runtime Semantics: IteratorBindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-iteratorbindinginitialization
2023-06-23 15:35:19 -04:00
ThrowCompletionOr < void > VM : : iterator_binding_initialization ( BindingPattern const & binding , IteratorRecord & iterator_record , Environment * environment )
2021-09-22 12:44:56 +02:00
{
2022-08-21 15:39:13 +01:00
auto & vm = * this ;
2022-08-21 20:38:35 +01:00
auto & realm = * vm . current_realm ( ) ;
2022-08-16 00:20:49 +01:00
2021-09-22 12:44:56 +02:00
// FIXME: this method is nearly identical to destructuring assignment!
for ( size_t i = 0 ; i < binding . entries . size ( ) ; i + + ) {
auto & entry = binding . entries [ i ] ;
Value value ;
2021-06-12 18:04:28 -07:00
2021-12-30 14:13:20 +01:00
auto assignment_target = TRY ( entry . alias . visit (
[ & ] ( Empty ) - > ThrowCompletionOr < Optional < Reference > > { return Optional < Reference > { } ; } ,
2023-02-19 22:07:52 +01:00
[ & ] ( NonnullRefPtr < Identifier const > const & identifier ) - > ThrowCompletionOr < Optional < Reference > > {
2021-12-30 14:13:20 +01:00
return TRY ( resolve_binding ( identifier - > string ( ) , environment ) ) ;
2021-09-22 12:44:56 +02:00
} ,
2023-02-19 22:07:52 +01:00
[ & ] ( NonnullRefPtr < BindingPattern const > const & ) - > ThrowCompletionOr < Optional < Reference > > { return Optional < Reference > { } ; } ,
[ & ] ( NonnullRefPtr < MemberExpression const > const & member_expression ) - > ThrowCompletionOr < Optional < Reference > > {
2022-08-16 19:28:17 +01:00
return TRY ( member_expression - > to_reference ( interpreter ( ) ) ) ;
2021-12-30 14:13:20 +01:00
} ) ) ;
2021-05-29 16:03:19 +04:30
2022-01-09 19:12:24 +01:00
// BindingRestElement : ... BindingIdentifier
// BindingRestElement : ... BindingPattern
2021-09-22 12:44:56 +02:00
if ( entry . is_rest ) {
VERIFY ( i = = binding . entries . size ( ) - 1 ) ;
2021-05-29 16:03:19 +04:30
2022-01-09 19:12:24 +01:00
// 2. Let A be ! ArrayCreate(0).
2022-12-13 20:49:49 +00:00
auto array = MUST ( Array : : create ( realm , 0 ) ) ;
2022-01-09 19:12:24 +01:00
// 3. Let n be 0.
// 4. Repeat,
while ( true ) {
2023-04-15 16:23:03 +02:00
ThrowCompletionOr < GCPtr < Object > > next { nullptr } ;
2022-01-09 19:12:24 +01:00
// a. If iteratorRecord.[[Done]] is false, then
if ( ! iterator_record . done ) {
2022-05-02 20:54:39 +02:00
// i. Let next be Completion(IteratorStep(iteratorRecord)).
2022-08-21 15:56:27 +01:00
next = iterator_step ( vm , iterator_record ) ;
2022-01-09 19:12:24 +01:00
// ii. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(next).
if ( next . is_error ( ) ) {
iterator_record . done = true ;
return next . release_error ( ) ;
}
// iv. If next is false, set iteratorRecord.[[Done]] to true.
if ( ! next . value ( ) )
iterator_record . done = true ;
2021-09-22 12:44:56 +02:00
}
2021-05-29 16:03:19 +04:30
2022-01-09 19:12:24 +01:00
// b. If iteratorRecord.[[Done]] is true, then
if ( iterator_record . done ) {
// NOTE: Step i. and ii. are handled below.
2021-09-22 12:44:56 +02:00
break ;
}
2021-09-18 01:11:32 +02:00
2022-05-02 20:54:39 +02:00
// c. Let nextValue be Completion(IteratorValue(next)).
2022-08-21 15:56:27 +01:00
auto next_value = iterator_value ( vm , * next . value ( ) ) ;
2022-01-09 19:12:24 +01:00
// d. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
// e. ReturnIfAbrupt(nextValue).
if ( next_value . is_error ( ) ) {
iterator_record . done = true ;
return next_value . release_error ( ) ;
}
// f. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽 (n)), nextValue).
array - > indexed_properties ( ) . append ( next_value . value ( ) ) ;
// g. Set n to n + 1.
2021-09-22 12:44:56 +02:00
}
value = array ;
2022-01-09 19:12:24 +01:00
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
// BindingElement : BindingPattern Initializer[opt]
else {
// 1. Let v be undefined.
value = js_undefined ( ) ;
2021-09-22 12:44:56 +02:00
2022-01-09 19:12:24 +01:00
// 2. If iteratorRecord.[[Done]] is false, then
if ( ! iterator_record . done ) {
2022-05-02 20:54:39 +02:00
// a. Let next be Completion(IteratorStep(iteratorRecord)).
2022-08-21 15:56:27 +01:00
auto next = iterator_step ( vm , iterator_record ) ;
2021-09-22 12:44:56 +02:00
2022-01-09 19:12:24 +01:00
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
// c. ReturnIfAbrupt(next).
if ( next . is_error ( ) ) {
iterator_record . done = true ;
return next . release_error ( ) ;
}
// d. If next is false, set iteratorRecord.[[Done]] to true.
if ( ! next . value ( ) ) {
iterator_record . done = true ;
}
// e. Else,
else {
2022-05-02 20:54:39 +02:00
// i. Set v to Completion(IteratorValue(next)).
2022-08-21 15:56:27 +01:00
auto value_or_error = iterator_value ( vm , * next . value ( ) ) ;
2022-01-09 19:12:24 +01:00
// ii. If v is an abrupt completion, set iteratorRecord.[[Done]] to true.
// iii. ReturnIfAbrupt(v).
if ( value_or_error . is_throw_completion ( ) ) {
iterator_record . done = true ;
return value_or_error . release_error ( ) ;
}
value = value_or_error . release_value ( ) ;
2020-12-05 16:38:29 +01:00
}
}
2022-01-09 19:12:24 +01:00
// NOTE: Step 3. and 4. are handled below.
2020-12-05 16:38:29 +01:00
}
2021-09-22 12:44:56 +02:00
if ( value . is_undefined ( ) & & entry . initializer ) {
VERIFY ( ! entry . is_rest ) ;
2023-02-19 22:07:52 +01:00
if ( auto * identifier_ptr = entry . alias . get_pointer < NonnullRefPtr < Identifier const > > ( ) )
2022-08-16 19:28:17 +01:00
value = TRY ( named_evaluation_if_anonymous_function ( * entry . initializer , ( * identifier_ptr ) - > string ( ) ) ) ;
2021-09-22 12:44:56 +02:00
else
2023-06-24 06:44:01 +02:00
value = TRY ( execute_ast_node ( * entry . initializer ) ) ;
2020-09-27 15:18:55 +02:00
}
LibJS: Rewrite most of Object for spec compliance :^)
This is a huge patch, I know. In hindsight this perhaps could've been
done slightly more incremental, but I started and then fixed everything
until it worked, and here we are. I tried splitting of some completely
unrelated changes into separate commits, however. Anyway.
This is a rewrite of most of Object, and by extension large parts of
Array, Proxy, Reflect, String, TypedArray, and some other things.
What we already had worked fine for about 90% of things, but getting the
last 10% right proved to be increasingly difficult with the current code
that sort of grew organically and is only very loosely based on the
spec - this became especially obvious when we started fixing a large
number of test262 failures.
Key changes include:
- 1:1 matching function names and parameters of all object-related
functions, to avoid ambiguity. Previously we had things like put(),
which the spec doesn't have - as a result it wasn't always clear which
need to be used.
- Better separation between object abstract operations and internal
methods - the former are always the same, the latter can be overridden
(and are therefore virtual). The internal methods (i.e. [[Foo]] in the
spec) are now prefixed with 'internal_' for clarity - again, it was
previously not always clear which AO a certain method represents,
get() could've been both Get and [[Get]] (I don't know which one it
was closer to right now).
Note that some of the old names have been kept until all code relying
on them is updated, but they are now simple wrappers around the
closest matching standard abstract operation.
- Simplifications of the storage layer: functions that write values to
storage are now prefixed with 'storage_' to make their purpose clear,
and as they are not part of the spec they should not contain any steps
specified by it. Much functionality is now covered by the layers above
it and was removed (e.g. handling of accessors, attribute checks).
- PropertyAttributes has been greatly simplified, and is being replaced
by PropertyDescriptor - a concept similar to the current
implementation, but more aligned with the actual spec. See the commit
message of the previous commit where it was introduced for details.
- As a bonus, and since I had to look at the spec a whole lot anyway, I
introduced more inline comments with the exact steps from the spec -
this makes it super easy to verify correctness.
- East-const all the things.
As a result of all of this, things are much more correct but a bit
slower now. Retaining speed wasn't a consideration at all, I have done
no profiling of the new code - there might be low hanging fruits, which
we can then harvest separately.
Special thanks to Idan for helping me with this by tracking down bugs,
updating everything outside of LibJS to work with these changes (LibWeb,
Spreadsheet, HackStudio), as well as providing countless patches to fix
regressions I introduced - there still are very few (we got it down to
5), but we also get many new passing test262 tests in return. :^)
Co-authored-by: Idan Horowitz <idan.horowitz@gmail.com>
2021-07-04 18:14:16 +01:00
2023-02-19 22:07:52 +01:00
if ( auto * binding_ptr = entry . alias . get_pointer < NonnullRefPtr < BindingPattern const > > ( ) ) {
2022-08-21 20:38:35 +01:00
TRY ( binding_initialization ( * binding_ptr , value , environment ) ) ;
2021-09-22 12:44:56 +02:00
} else if ( ! entry . alias . has < Empty > ( ) ) {
VERIFY ( assignment_target . has_value ( ) ) ;
if ( ! environment )
2022-08-21 15:39:13 +01:00
TRY ( assignment_target - > put_value ( vm , value ) ) ;
2021-09-22 12:44:56 +02:00
else
2022-08-21 15:39:13 +01:00
TRY ( assignment_target - > initialize_referenced_binding ( vm , value ) ) ;
2021-09-22 12:44:56 +02:00
}
LibJS: Rewrite most of Object for spec compliance :^)
This is a huge patch, I know. In hindsight this perhaps could've been
done slightly more incremental, but I started and then fixed everything
until it worked, and here we are. I tried splitting of some completely
unrelated changes into separate commits, however. Anyway.
This is a rewrite of most of Object, and by extension large parts of
Array, Proxy, Reflect, String, TypedArray, and some other things.
What we already had worked fine for about 90% of things, but getting the
last 10% right proved to be increasingly difficult with the current code
that sort of grew organically and is only very loosely based on the
spec - this became especially obvious when we started fixing a large
number of test262 failures.
Key changes include:
- 1:1 matching function names and parameters of all object-related
functions, to avoid ambiguity. Previously we had things like put(),
which the spec doesn't have - as a result it wasn't always clear which
need to be used.
- Better separation between object abstract operations and internal
methods - the former are always the same, the latter can be overridden
(and are therefore virtual). The internal methods (i.e. [[Foo]] in the
spec) are now prefixed with 'internal_' for clarity - again, it was
previously not always clear which AO a certain method represents,
get() could've been both Get and [[Get]] (I don't know which one it
was closer to right now).
Note that some of the old names have been kept until all code relying
on them is updated, but they are now simple wrappers around the
closest matching standard abstract operation.
- Simplifications of the storage layer: functions that write values to
storage are now prefixed with 'storage_' to make their purpose clear,
and as they are not part of the spec they should not contain any steps
specified by it. Much functionality is now covered by the layers above
it and was removed (e.g. handling of accessors, attribute checks).
- PropertyAttributes has been greatly simplified, and is being replaced
by PropertyDescriptor - a concept similar to the current
implementation, but more aligned with the actual spec. See the commit
message of the previous commit where it was introduced for details.
- As a bonus, and since I had to look at the spec a whole lot anyway, I
introduced more inline comments with the exact steps from the spec -
this makes it super easy to verify correctness.
- East-const all the things.
As a result of all of this, things are much more correct but a bit
slower now. Retaining speed wasn't a consideration at all, I have done
no profiling of the new code - there might be low hanging fruits, which
we can then harvest separately.
Special thanks to Idan for helping me with this by tracking down bugs,
updating everything outside of LibJS to work with these changes (LibWeb,
Spreadsheet, HackStudio), as well as providing countless patches to fix
regressions I introduced - there still are very few (we got it down to
5), but we also get many new passing test262 tests in return. :^)
Co-authored-by: Idan Horowitz <idan.horowitz@gmail.com>
2021-07-04 18:14:16 +01:00
}
2021-09-22 12:44:56 +02:00
return { } ;
2020-09-27 15:18:55 +02:00
}
2021-07-02 21:54:56 +02:00
// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference
2023-01-08 19:23:00 -05:00
ThrowCompletionOr < Reference > VM : : get_identifier_reference ( Environment * environment , DeprecatedFlyString name , bool strict , size_t hops )
2020-09-27 15:18:55 +02:00
{
2021-07-02 21:54:56 +02:00
// 1. If env is the value null, then
if ( ! environment ) {
// a. Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }.
2021-09-11 19:03:38 +02:00
return Reference { Reference : : BaseType : : Unresolvable , move ( name ) , strict } ;
2021-07-02 21:54:56 +02:00
}
2021-10-07 00:16:37 +02:00
2021-12-30 13:19:05 +01:00
// 2. Let exists be ? env.HasBinding(name).
2021-10-07 00:16:37 +02:00
Optional < size_t > index ;
2021-12-30 13:19:05 +01:00
auto exists = TRY ( environment - > has_binding ( name , & index ) ) ;
2021-07-02 21:54:56 +02:00
2021-12-30 13:19:05 +01:00
// Note: This is an optimization for looking up the same reference.
2021-10-07 01:06:21 +02:00
Optional < EnvironmentCoordinate > environment_coordinate ;
2022-11-21 18:01:22 +01:00
if ( index . has_value ( ) ) {
VERIFY ( hops < = NumericLimits < u32 > : : max ( ) ) ;
VERIFY ( index . value ( ) < = NumericLimits < u32 > : : max ( ) ) ;
environment_coordinate = EnvironmentCoordinate { . hops = static_cast < u32 > ( hops ) , . index = static_cast < u32 > ( index . value ( ) ) } ;
}
2021-10-07 01:06:21 +02:00
2021-12-30 13:19:05 +01:00
// 3. If exists is true, then
if ( exists ) {
// a. Return the Reference Record { [[Base]]: env, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }.
2021-10-07 01:06:21 +02:00
return Reference { * environment , move ( name ) , strict , environment_coordinate } ;
2021-12-30 13:19:05 +01:00
}
// 4. Else,
else {
// a. Let outer be env.[[OuterEnv]].
// b. Return ? GetIdentifierReference(outer, name, strict).
2021-10-07 01:06:21 +02:00
return get_identifier_reference ( environment - > outer_environment ( ) , move ( name ) , strict , hops + 1 ) ;
2021-12-30 13:19:05 +01:00
}
2021-07-02 21:54:56 +02:00
}
// 9.4.2 ResolveBinding ( name [ , env ] ), https://tc39.es/ecma262/#sec-resolvebinding
2023-01-08 19:23:00 -05:00
ThrowCompletionOr < Reference > VM : : resolve_binding ( DeprecatedFlyString const & name , Environment * environment )
2021-07-02 21:54:56 +02:00
{
// 1. If env is not present or if env is undefined, then
if ( ! environment ) {
// a. Set env to the running execution context's LexicalEnvironment.
environment = running_execution_context ( ) . lexical_environment ;
}
// 2. Assert: env is an Environment Record.
VERIFY ( environment ) ;
2022-04-30 22:22:52 +02:00
// 3. If the source text matched by the syntactic production that is being evaluated is contained in strict mode code, let strict be true; else let strict be false.
2021-07-02 21:54:56 +02:00
bool strict = in_strict_mode ( ) ;
// 4. Return ? GetIdentifierReference(env, name, strict).
return get_identifier_reference ( environment , name , strict ) ;
2021-12-30 14:13:20 +01:00
// NOTE: The spec says:
// Note: The result of ResolveBinding is always a Reference Record whose [[ReferencedName]] field is name.
// But this is not actually correct as GetIdentifierReference (or really the methods it calls) can throw.
2020-09-27 15:18:55 +02:00
}
2021-06-25 11:40:03 +02:00
// 9.4.4 ResolveThisBinding ( ), https://tc39.es/ecma262/#sec-resolvethisbinding
2022-08-21 15:12:43 +01:00
ThrowCompletionOr < Value > VM : : resolve_this_binding ( )
2021-06-25 11:40:03 +02:00
{
2022-08-21 15:12:43 +01:00
auto & vm = * this ;
2021-12-30 23:00:37 +01:00
// 1. Let envRec be GetThisEnvironment().
2022-12-15 20:07:13 +00:00
auto environment = get_this_environment ( vm ) ;
2022-08-21 15:12:43 +01:00
2021-12-30 23:00:37 +01:00
// 2. Return ? envRec.GetThisBinding().
2022-12-15 20:07:13 +00:00
return TRY ( environment - > get_this_binding ( vm ) ) ;
2021-06-25 11:40:03 +02:00
}
2021-12-28 23:50:23 +01:00
// 9.4.5 GetNewTarget ( ), https://tc39.es/ecma262/#sec-getnewtarget
2021-06-22 13:30:48 +02:00
Value VM : : get_new_target ( )
2020-09-27 15:18:55 +02:00
{
2021-12-28 23:50:23 +01:00
// 1. Let envRec be GetThisEnvironment().
2022-12-15 20:07:13 +00:00
auto env = get_this_environment ( * this ) ;
2021-12-28 23:50:23 +01:00
// 2. Assert: envRec has a [[NewTarget]] field.
// 3. Return envRec.[[NewTarget]].
2022-12-15 20:07:13 +00:00
return verify_cast < FunctionEnvironment > ( * env ) . new_target ( ) ;
2020-09-27 15:18:55 +02:00
}
2022-08-21 15:39:13 +01:00
// 9.4.5 GetGlobalObject ( ), https://tc39.es/ecma262/#sec-getglobalobject
2022-08-28 15:03:45 +01:00
Object & VM : : get_global_object ( )
2022-08-21 15:39:13 +01:00
{
// 1. Let currentRealm be the current Realm Record.
auto & current_realm = * this - > current_realm ( ) ;
// 2. Return currentRealm.[[GlobalObject]].
return current_realm . global_object ( ) ;
}
2020-10-04 13:54:44 +02:00
bool VM : : in_strict_mode ( ) const
{
2021-06-24 19:17:45 +02:00
if ( execution_context_stack ( ) . is_empty ( ) )
2020-10-04 13:54:44 +02:00
return false ;
2021-06-24 19:17:45 +02:00
return running_execution_context ( ) . is_strict_mode ;
2020-10-04 13:54:44 +02:00
}
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
void VM : : run_queued_promise_jobs ( )
{
dbgln_if ( PROMISE_DEBUG , " Running queued promise jobs " ) ;
2022-02-07 15:12:41 +01:00
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
while ( ! m_promise_jobs . is_empty ( ) ) {
2022-02-06 03:46:45 +00:00
auto job = m_promise_jobs . take_first ( ) ;
dbgln_if ( PROMISE_DEBUG , " Calling promise job function " ) ;
LibJS: Clear exception after running each queued Promise job
It's not what the spec tells us to do. In fact, the spec tells us the
exact opposite:
9.5 Jobs and Host Operations to Enqueue Jobs
https://tc39.es/ecma262/#sec-jobs
A Job is an Abstract Closure with no parameters that initiates an
ECMAScript computation when no other ECMAScript computation is
currently in progress.
...
Their implementations must conform to the following requirements:
- ...
- The Abstract Closure must return a normal completion, implementing
its own handling of errors.
However, this turned out to not be true in all cases. More specifically,
the NewPromiseReactionJob AO returns the completion result of calling a
user-provided function (PromiseCapability's [[Resolve]] / [[Reject]]),
which may be an abrupt completion:
27.2.2.1 NewPromiseReactionJob ( reaction, argument )
https://tc39.es/ecma262/#sec-newpromisereactionjob
1. Let job be a new Job Abstract Closure with no parameters that
captures reaction and argument and performs the following steps
when called:
...
h. If handlerResult is an abrupt completion, then
i. Let status be Call(promiseCapability.[[Reject]],
undefined, « handlerResult.[[Value]] »).
i. Else,
i. Let status be Call(promiseCapability.[[Resolve]],
undefined, « handlerResult.[[Value]] »).
j. Return Completion(status).
Interestingly, this case is explicitly handled in the HTML spec's
implementation of jobs as microtasks:
8.1.5.3.3 HostEnqueuePromiseJob(job, realm)
https://html.spec.whatwg.org/webappapis.html#hostenqueuepromisejob
2. Queue a microtask on the surrounding agent's event loop to
perform the following steps:
...
5. If result is an abrupt completion, then report the exception
given by result.[[Value]].
This is precisely what all the major engines do - but not only in
browsers; the provided code snippet in the test added in this commit
works just fine in Node.js, for example.
SpiderMonkey:
https://searchfox.org/mozilla-central/rev/25997ce8267ec9e3ea4b727e0973bd9ef02bba79/js/src/builtin/Promise.cpp#6292
https://searchfox.org/mozilla-central/rev/25997ce8267ec9e3ea4b727e0973bd9ef02bba79/js/src/builtin/Promise.cpp#1277
https://searchfox.org/mozilla-central/rev/25997ce8267ec9e3ea4b727e0973bd9ef02bba79/js/src/vm/JSContext.cpp#845
JavaScriptCore:
https://trac.webkit.org/browser/webkit/trunk/Source/JavaScriptCore/builtins/PromiseOperations.js?rev=273718#L562
https://trac.webkit.org/browser/webkit/trunk/Source/JavaScriptCore/runtime/JSMicrotask.cpp?rev=273718#L94
V8:
https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/promise-abstract-operations.tq;l=481;drc=a760f03a6e99bf4863d8d21c5f7896a74a0a39ea
https://source.chromium.org/chromium/chromium/src/+/main:v8/src/builtins/builtins-microtask-queue-gen.cc;l=331;drc=65c9257f1777731d6d0669598f6fe6fe65fa61d3
This should probably be fixed in the ECMAScript spec to relax the rule
that Jobs may not return an abrupt completion, just like in the HTML
spec. The important bit is that those are not surfaced to user code in
any way.
2021-11-11 23:13:58 +00:00
2022-02-06 03:46:45 +00:00
[[maybe_unused]] auto result = job ( ) ;
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
}
}
2021-06-13 00:22:35 +01:00
// 9.5.4 HostEnqueuePromiseJob ( job, realm ), https://tc39.es/ecma262/#sec-hostenqueuepromisejob
2022-02-06 03:46:45 +00:00
void VM : : enqueue_promise_job ( Function < ThrowCompletionOr < Value > ( ) > job , Realm * )
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
{
2022-02-06 03:46:45 +00:00
// An implementation of HostEnqueuePromiseJob must conform to the requirements in 9.5 as well as the following:
// - FIXME: If realm is not null, each time job is invoked the implementation must perform implementation-defined steps such that execution is prepared to evaluate ECMAScript code at the time of job's invocation.
// - FIXME: Let scriptOrModule be GetActiveScriptOrModule() at the time HostEnqueuePromiseJob is invoked. If realm is not null, each time job is invoked the implementation must perform implementation-defined steps
// such that scriptOrModule is the active script or module at the time of job's invocation.
// - Jobs must run in the same order as the HostEnqueuePromiseJob invocations that scheduled them.
m_promise_jobs . append ( move ( job ) ) ;
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
}
2021-06-15 22:16:17 +03:00
void VM : : run_queued_finalization_registry_cleanup_jobs ( )
{
while ( ! m_finalization_registry_cleanup_jobs . is_empty ( ) ) {
2023-02-26 16:09:02 -07:00
auto registry = m_finalization_registry_cleanup_jobs . take_first ( ) ;
2022-02-07 15:12:41 +01:00
// FIXME: Handle any uncatched exceptions here.
( void ) registry - > cleanup ( ) ;
2021-06-15 22:16:17 +03:00
}
}
// 9.10.4.1 HostEnqueueFinalizationRegistryCleanupJob ( finalizationRegistry ), https://tc39.es/ecma262/#sec-host-cleanup-finalization-registry
void VM : : enqueue_finalization_registry_cleanup_job ( FinalizationRegistry & registry )
{
m_finalization_registry_cleanup_jobs . append ( & registry ) ;
}
2021-06-13 00:22:35 +01:00
// 27.2.1.9 HostPromiseRejectionTracker ( promise, operation ), https://tc39.es/ecma262/#sec-host-promise-rejection-tracker
2022-02-06 03:46:45 +00:00
void VM : : promise_rejection_tracker ( Promise & promise , Promise : : RejectionOperation operation ) const
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 22:13:29 +02:00
{
switch ( operation ) {
case Promise : : RejectionOperation : : Reject :
// A promise was rejected without any handlers
if ( on_promise_unhandled_rejection )
on_promise_unhandled_rejection ( promise ) ;
break ;
case Promise : : RejectionOperation : : Handle :
// A handler was added to an already rejected promise
if ( on_promise_rejection_handled )
on_promise_rejection_handled ( promise ) ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2021-06-06 22:53:44 +02:00
void VM : : dump_backtrace ( ) const
{
2021-09-11 17:01:19 +02:00
for ( ssize_t i = m_execution_context_stack . size ( ) - 1 ; i > = 0 ; - - i ) {
auto & frame = m_execution_context_stack [ i ] ;
if ( frame - > current_node ) {
LibJS: Reduce AST memory usage by shrink-wrapping source range info
Before this change, each AST node had a 64-byte SourceRange member.
This SourceRange had the following layout:
filename: StringView (16 bytes)
start: Position (24 bytes)
end: Position (24 bytes)
The Position structs have { line, column, offset }, all members size_t.
To reduce memory consumption, AST nodes now only store the following:
source_code: NonnullRefPtr<SourceCode> (8 bytes)
start_offset: u32 (4 bytes)
end_offset: u32 (4 bytes)
SourceCode is a new ref-counted data structure that keeps the filename
and original parsed source code in a single location, and all AST nodes
have a pointer to it.
The start_offset and end_offset can be turned into (line, column) when
necessary by calling SourceCode::range_from_offsets(). This will walk
the source code string and compute line/column numbers on the fly, so
it's not necessarily fast, but it should be rare since this information
is primarily used for diagnostics and exception stack traces.
With this, ASTNode shrinks from 80 bytes to 32 bytes. This gives us a
~23% reduction in memory usage when loading twitter.com/awesomekling
(330 MiB before, 253 MiB after!) :^)
2022-11-21 17:37:38 +01:00
auto source_range = frame - > current_node - > source_range ( ) ;
dbgln ( " -> {} @ {}:{},{} " , frame - > function_name , source_range . filename ( ) , source_range . start . line , source_range . start . column ) ;
2021-09-11 17:01:19 +02:00
} else {
dbgln ( " -> {} " , frame - > function_name ) ;
}
}
2021-06-06 22:53:44 +02:00
}
2021-10-03 14:52:53 +02:00
void VM : : save_execution_context_stack ( )
{
m_saved_execution_context_stacks . append ( move ( m_execution_context_stack ) ) ;
}
void VM : : restore_execution_context_stack ( )
{
m_execution_context_stack = m_saved_execution_context_stacks . take_last ( ) ;
}
2022-01-17 14:48:22 +01:00
// 9.4.1 GetActiveScriptOrModule ( ), https://tc39.es/ecma262/#sec-getactivescriptormodule
ScriptOrModule VM : : get_active_script_or_module ( ) const
{
// 1. If the execution context stack is empty, return null.
if ( m_execution_context_stack . is_empty ( ) )
return Empty { } ;
// 2. Let ec be the topmost execution context on the execution context stack whose ScriptOrModule component is not null.
for ( auto i = m_execution_context_stack . size ( ) - 1 ; i > 0 ; i - - ) {
if ( ! m_execution_context_stack [ i ] - > script_or_module . has < Empty > ( ) )
return m_execution_context_stack [ i ] - > script_or_module ;
}
// 3. If no such execution context exists, return null. Otherwise, return ec's ScriptOrModule.
// Note: Since it is not empty we have 0 and since we got here all the
// above contexts don't have a non-null ScriptOrModule
return m_execution_context_stack [ 0 ] - > script_or_module ;
}
2022-12-04 18:02:33 +00:00
VM : : StoredModule * VM : : get_stored_module ( ScriptOrModule const & , DeprecatedString const & filename , DeprecatedString const & )
2022-01-19 01:22:58 +01:00
{
// Note the spec says:
// Each time this operation is called with a specific referencingScriptOrModule, specifier pair as arguments
// it must return the same Module Record instance if it completes normally.
// Currently, we ignore the referencing script or module but this might not be correct in all cases.
2022-01-27 14:51:21 +01:00
// Editor's Note from https://tc39.es/proposal-json-modules/#sec-hostresolveimportedmodule
// The above text implies that is recommended but not required that hosts do not use moduleRequest.[[Assertions]]
// as part of the module cache key. In either case, an exception thrown from an import with a given assertion list
// does not rule out success of another import with the same specifier but a different assertion list.
2022-01-19 01:22:58 +01:00
auto end_or_module = m_loaded_modules . find_if ( [ & ] ( StoredModule const & stored_module ) {
2022-06-26 20:58:32 +01:00
return stored_module . filename = = filename ;
2022-01-19 01:22:58 +01:00
} ) ;
if ( end_or_module . is_end ( ) )
return nullptr ;
return & ( * end_or_module ) ;
}
ThrowCompletionOr < void > VM : : link_and_eval_module ( Badge < Interpreter > , SourceTextModule & module )
{
return link_and_eval_module ( module ) ;
}
2023-06-15 12:36:57 +02:00
ThrowCompletionOr < void > VM : : link_and_eval_module ( Badge < Bytecode : : Interpreter > , SourceTextModule & module )
{
return link_and_eval_module ( module ) ;
}
2022-01-27 14:51:21 +01:00
ThrowCompletionOr < void > VM : : link_and_eval_module ( Module & module )
2022-01-19 01:22:58 +01:00
{
2022-06-26 20:58:32 +01:00
auto filename = module . filename ( ) ;
2022-01-19 01:22:58 +01:00
auto module_or_end = m_loaded_modules . find_if ( [ & ] ( StoredModule const & stored_module ) {
return stored_module . module . ptr ( ) = = & module ;
} ) ;
StoredModule * stored_module ;
if ( module_or_end . is_end ( ) ) {
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] Warning introducing module via link_and_eval_module {} " , module . filename ( ) ) ;
2022-02-04 23:47:35 +01:00
if ( m_loaded_modules . size ( ) > 0 )
dbgln ( " Warning: Using multiple modules as entry point can lead to unexpected results " ) ;
2022-01-19 01:22:58 +01:00
m_loaded_modules . empend (
2022-09-05 14:31:25 +02:00
NonnullGCPtr ( module ) ,
2022-01-19 01:22:58 +01:00
module . filename ( ) ,
2022-12-04 18:02:33 +00:00
DeprecatedString { } , // Null type
2022-01-19 01:22:58 +01:00
module ,
true ) ;
stored_module = & m_loaded_modules . last ( ) ;
} else {
stored_module = module_or_end . operator - > ( ) ;
if ( stored_module - > has_once_started_linking ) {
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] Module already has started linking once {} " , module . filename ( ) ) ;
return { } ;
}
stored_module - > has_once_started_linking = true ;
}
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] Linking module {} " , filename ) ;
2022-01-19 01:22:58 +01:00
auto linked_or_error = module . link ( * this ) ;
if ( linked_or_error . is_error ( ) )
return linked_or_error . throw_completion ( ) ;
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] Linking passed, now evaluating module {} " , filename ) ;
2022-01-19 01:22:58 +01:00
auto evaluated_or_error = module . evaluate ( * this ) ;
if ( evaluated_or_error . is_error ( ) )
return evaluated_or_error . throw_completion ( ) ;
auto * evaluated_value = evaluated_or_error . value ( ) ;
run_queued_promise_jobs ( ) ;
VERIFY ( m_promise_jobs . is_empty ( ) ) ;
// FIXME: This will break if we start doing promises actually asynchronously.
VERIFY ( evaluated_value - > state ( ) ! = Promise : : State : : Pending ) ;
2022-02-07 15:12:41 +01:00
if ( evaluated_value - > state ( ) = = Promise : : State : : Rejected )
2022-01-19 01:22:58 +01:00
return JS : : throw_completion ( evaluated_value - > result ( ) ) ;
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] Evaluating passed for module {} " , module . filename ( ) ) ;
return { } ;
}
2022-12-04 18:02:33 +00:00
static DeprecatedString resolve_module_filename ( StringView filename , StringView module_type )
2022-06-26 21:00:55 +01:00
{
auto extensions = Vector < StringView , 2 > { " js " sv , " mjs " sv } ;
if ( module_type = = " json " sv )
extensions = { " json " sv } ;
2023-03-22 02:35:30 +11:00
if ( ! FileSystem : : exists ( filename ) ) {
2022-06-26 21:00:55 +01:00
for ( auto extension : extensions ) {
// import "./foo" -> import "./foo.ext"
2022-12-04 18:02:33 +00:00
auto resolved_filepath = DeprecatedString : : formatted ( " {}.{} " , filename , extension ) ;
2023-03-22 02:35:30 +11:00
if ( FileSystem : : exists ( resolved_filepath ) )
2022-06-26 21:00:55 +01:00
return resolved_filepath ;
}
2023-03-22 02:35:30 +11:00
} else if ( FileSystem : : is_directory ( filename ) ) {
2022-06-26 21:00:55 +01:00
for ( auto extension : extensions ) {
// import "./foo" -> import "./foo/index.ext"
2022-12-04 18:02:33 +00:00
auto resolved_filepath = LexicalPath : : join ( filename , DeprecatedString : : formatted ( " index.{} " , extension ) ) . string ( ) ;
2023-03-22 02:35:30 +11:00
if ( FileSystem : : exists ( resolved_filepath ) )
2022-06-26 21:00:55 +01:00
return resolved_filepath ;
}
}
return filename ;
}
2022-01-19 01:22:58 +01:00
// 16.2.1.7 HostResolveImportedModule ( referencingScriptOrModule, specifier ), https://tc39.es/ecma262/#sec-hostresolveimportedmodule
2022-09-05 14:31:25 +02:00
ThrowCompletionOr < NonnullGCPtr < Module > > VM : : resolve_imported_module ( ScriptOrModule referencing_script_or_module , ModuleRequest const & module_request )
2022-01-19 01:22:58 +01:00
{
// An implementation of HostResolveImportedModule must conform to the following requirements:
// - If it completes normally, the [[Value]] slot of the completion must contain an instance of a concrete subclass of Module Record.
2022-01-27 02:44:03 +01:00
// - If a Module Record corresponding to the pair referencingScriptOrModule, moduleRequest does not exist or cannot be created, an exception must be thrown.
// - Each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]], moduleRequest.[[Assertions]] triple
// as arguments it must return the same Module Record instance if it completes normally.
// * It is recommended but not required that implementations additionally conform to the following stronger constraint:
// each time this operation is called with a specific referencingScriptOrModule, moduleRequest.[[Specifier]] pair as arguments it must return the same Module Record instance if it completes normally.
// - moduleRequest.[[Assertions]] must not influence the interpretation of the module or the module specifier;
// instead, it may be used to determine whether the algorithm completes normally or with an abrupt completion.
// Multiple different referencingScriptOrModule, moduleRequest.[[Specifier]] pairs may map to the same Module Record instance.
// The actual mapping semantic is host-defined but typically a normalization process is applied to specifier as part of the mapping process.
// A typical normalization process would include actions such as alphabetic case folding and expansion of relative and abbreviated path specifiers.
2022-01-19 01:22:58 +01:00
2022-06-26 21:00:55 +01:00
// We only allow "type" as a supported assertion so it is the only valid key that should ever arrive here.
VERIFY ( module_request . assertions . is_empty ( ) | | ( module_request . assertions . size ( ) = = 1 & & module_request . assertions . first ( ) . key = = " type " ) ) ;
2022-12-04 18:02:33 +00:00
auto module_type = module_request . assertions . is_empty ( ) ? DeprecatedString { } : module_request . assertions . first ( ) . value ;
2022-06-26 21:00:55 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] module at {} has type {} [is_null={}] " , module_request . module_specifier , module_type , module_type . is_null ( ) ) ;
2022-01-19 01:22:58 +01:00
StringView base_filename = referencing_script_or_module . visit (
[ & ] ( Empty ) {
return " . " sv ;
} ,
2022-02-07 16:28:39 +01:00
[ & ] ( auto & script_or_module ) {
2022-01-19 01:22:58 +01:00
return script_or_module - > filename ( ) ;
} ) ;
LexicalPath base_path { base_filename } ;
2022-06-26 20:58:32 +01:00
auto filename = LexicalPath : : absolute_path ( base_path . dirname ( ) , module_request . module_specifier ) ;
2022-01-19 01:22:58 +01:00
2022-06-26 21:00:55 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] base path: '{}' " , base_path ) ;
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] initial filename: '{}' " , filename ) ;
filename = resolve_module_filename ( filename , module_type ) ;
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] resolved filename: '{}' " , filename ) ;
2022-01-19 01:22:58 +01:00
# if JS_MODULE_DEBUG
2022-12-04 18:02:33 +00:00
DeprecatedString referencing_module_string = referencing_script_or_module . visit (
[ & ] ( Empty ) - > DeprecatedString {
2022-01-19 01:22:58 +01:00
return " . " ;
} ,
2022-02-07 19:58:50 +01:00
[ & ] ( auto & script_or_module ) {
2022-01-19 01:22:58 +01:00
if constexpr ( IsSame < Script * , decltype ( script_or_module ) > ) {
2022-12-04 18:02:33 +00:00
return DeprecatedString : : formatted ( " Script @ {} " , script_or_module . ptr ( ) ) ;
2022-01-19 01:22:58 +01:00
}
2022-12-04 18:02:33 +00:00
return DeprecatedString : : formatted ( " Module @ {} " , script_or_module . ptr ( ) ) ;
2022-01-19 01:22:58 +01:00
} ) ;
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] resolve_imported_module({}, {}) " , referencing_module_string , filename ) ;
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] resolved {} + {} -> {} " , base_path , module_request . module_specifier , filename ) ;
2022-01-19 01:22:58 +01:00
# endif
2022-06-26 20:58:32 +01:00
auto * loaded_module_or_end = get_stored_module ( referencing_script_or_module , filename , module_type ) ;
2022-01-19 01:22:58 +01:00
if ( loaded_module_or_end ! = nullptr ) {
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] resolve_imported_module({}) already loaded at {} " , filename , loaded_module_or_end - > module . ptr ( ) ) ;
2022-09-05 14:31:25 +02:00
return NonnullGCPtr ( * loaded_module_or_end - > module ) ;
2022-01-19 01:22:58 +01:00
}
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] reading and parsing module {} " , filename ) ;
2022-01-19 01:22:58 +01:00
2023-04-26 20:44:50 -04:00
auto file_or_error = Core : : File : : open ( filename , Core : : File : : OpenMode : : Read ) ;
2022-01-19 01:22:58 +01:00
if ( file_or_error . is_error ( ) ) {
2022-08-16 20:33:17 +01:00
return throw_completion < SyntaxError > ( ErrorType : : ModuleNotFound , module_request . module_specifier ) ;
2022-01-19 01:22:58 +01:00
}
// FIXME: Don't read the file in one go.
2023-04-26 20:44:50 -04:00
auto file_content_or_error = file_or_error . value ( ) - > read_until_eof ( ) ;
if ( file_content_or_error . is_error ( ) ) {
if ( file_content_or_error . error ( ) . code ( ) = = ENOMEM )
return throw_completion < JS : : InternalError > ( error_message ( : : JS : : VM : : ErrorMessage : : OutOfMemory ) ) ;
return throw_completion < SyntaxError > ( ErrorType : : ModuleNotFound , module_request . module_specifier ) ;
}
StringView const content_view { file_content_or_error . value ( ) . bytes ( ) } ;
2022-01-19 01:22:58 +01:00
2022-09-05 14:31:25 +02:00
auto module = TRY ( [ & ] ( ) - > ThrowCompletionOr < NonnullGCPtr < Module > > {
2022-01-27 14:51:21 +01:00
// If assertions has an entry entry such that entry.[[Key]] is "type", let type be entry.[[Value]]. The following requirements apply:
// If type is "json", then this algorithm must either invoke ParseJSONModule and return the resulting Completion Record, or throw an exception.
if ( module_type = = " json " sv ) {
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] reading and parsing JSON module {} " , filename ) ;
return parse_json_module ( content_view , * current_realm ( ) , filename ) ;
2022-01-27 14:51:21 +01:00
}
2022-01-19 01:22:58 +01:00
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] reading and parsing as SourceTextModule module {} " , filename ) ;
2022-01-27 14:51:21 +01:00
// Note: We treat all files as module, so if a script does not have exports it just runs it.
2022-06-26 20:58:32 +01:00
auto module_or_errors = SourceTextModule : : parse ( content_view , * current_realm ( ) , filename ) ;
2022-01-27 14:51:21 +01:00
if ( module_or_errors . is_error ( ) ) {
VERIFY ( module_or_errors . error ( ) . size ( ) > 0 ) ;
2022-12-06 01:12:49 +00:00
return throw_completion < SyntaxError > ( module_or_errors . error ( ) . first ( ) . to_deprecated_string ( ) ) ;
2022-01-27 14:51:21 +01:00
}
return module_or_errors . release_value ( ) ;
} ( ) ) ;
2022-01-19 01:22:58 +01:00
2022-06-26 20:58:32 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] resolve_imported_module(...) parsed {} to {} " , filename , module . ptr ( ) ) ;
2022-01-19 01:22:58 +01:00
// We have to set it here already in case it references itself.
m_loaded_modules . empend (
referencing_script_or_module ,
2022-06-26 20:58:32 +01:00
filename ,
2022-01-27 14:51:21 +01:00
module_type ,
2022-09-05 14:31:25 +02:00
* module ,
2022-01-19 01:22:58 +01:00
false ) ;
return module ;
}
2022-01-18 19:39:36 +01:00
// 16.2.1.8 HostImportModuleDynamically ( referencingScriptOrModule, specifier, promiseCapability ), https://tc39.es/ecma262/#sec-hostimportmoduledynamically
2023-02-17 07:30:42 -05:00
ThrowCompletionOr < void > VM : : import_module_dynamically ( ScriptOrModule referencing_script_or_module , ModuleRequest module_request , PromiseCapability const & promise_capability )
2022-01-18 19:39:36 +01:00
{
2022-08-16 00:20:49 +01:00
auto & realm = * current_realm ( ) ;
2022-01-18 19:39:36 +01:00
// Success path:
2022-01-27 02:44:03 +01:00
// - At some future time, the host environment must perform FinishDynamicImport(referencingScriptOrModule, moduleRequest, promiseCapability, promise),
2022-01-18 19:39:36 +01:00
// where promise is a Promise resolved with undefined.
// - Any subsequent call to HostResolveImportedModule after FinishDynamicImport has completed,
2022-05-03 21:23:12 +02:00
// given the arguments referencingScriptOrModule and specifier, must return a normal completion
// containing a module which has already been evaluated, i.e. whose Evaluate concrete method has
// already been called and returned a normal completion.
2022-01-18 19:39:36 +01:00
// Failure path:
// - At some future time, the host environment must perform
2022-01-27 02:44:03 +01:00
// FinishDynamicImport(referencingScriptOrModule, moduleRequest, promiseCapability, promise),
2022-01-18 19:39:36 +01:00
// where promise is a Promise rejected with an error representing the cause of failure.
2022-12-13 20:49:50 +00:00
auto promise = Promise : : create ( realm ) ;
2022-01-18 19:39:36 +01:00
ScopeGuard finish_dynamic_import = [ & ] {
2022-10-02 12:11:30 +01:00
host_finish_dynamic_import ( referencing_script_or_module , module_request , promise_capability , promise ) ;
2022-01-18 19:39:36 +01:00
} ;
// Generally within ECMA262 we always get a referencing_script_or_moulde. However, ShadowRealm gives an explicit null.
// To get around this is we attempt to get the active script_or_module otherwise we might start loading "random" files from the working directory.
if ( referencing_script_or_module . has < Empty > ( ) ) {
referencing_script_or_module = get_active_script_or_module ( ) ;
// If there is no ScriptOrModule in any of the execution contexts
if ( referencing_script_or_module . has < Empty > ( ) ) {
// Throw an error for now
2023-02-17 07:30:42 -05:00
promise - > reject ( InternalError : : create ( realm , TRY_OR_THROW_OOM ( * this , String : : formatted ( ErrorType : : ModuleNotFoundNoReferencingScript . message ( ) , module_request . module_specifier ) ) ) ) ;
return { } ;
2022-01-18 19:39:36 +01:00
}
}
// Note: If host_resolve_imported_module returns a module it has been loaded successfully and the next call in finish_dynamic_import will retrieve it again.
2022-01-27 02:44:03 +01:00
auto module_or_error = host_resolve_imported_module ( referencing_script_or_module , module_request ) ;
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] HostImportModuleDynamically(..., {}) -> {} " , module_request . module_specifier , module_or_error . is_error ( ) ? " failed " : " passed " ) ;
2022-01-18 19:39:36 +01:00
if ( module_or_error . is_throw_completion ( ) ) {
promise - > reject ( * module_or_error . throw_completion ( ) . value ( ) ) ;
} else {
auto module = module_or_error . release_value ( ) ;
2022-01-27 14:51:21 +01:00
auto & source_text_module = static_cast < Module & > ( * module ) ;
2022-01-18 19:39:36 +01:00
auto evaluated_or_error = link_and_eval_module ( source_text_module ) ;
if ( evaluated_or_error . is_throw_completion ( ) ) {
promise - > reject ( * evaluated_or_error . throw_completion ( ) . value ( ) ) ;
} else {
promise - > fulfill ( js_undefined ( ) ) ;
}
}
2022-05-02 20:54:39 +02:00
// It must return unused.
2022-01-18 19:39:36 +01:00
// Note: Just return void always since the resulting value cannot be accessed by user code.
2023-02-17 07:30:42 -05:00
return { } ;
2022-01-18 19:39:36 +01:00
}
// 16.2.1.9 FinishDynamicImport ( referencingScriptOrModule, specifier, promiseCapability, innerPromise ), https://tc39.es/ecma262/#sec-finishdynamicimport
2022-10-02 12:11:30 +01:00
void VM : : finish_dynamic_import ( ScriptOrModule referencing_script_or_module , ModuleRequest module_request , PromiseCapability const & promise_capability , Promise * inner_promise )
2022-01-18 19:39:36 +01:00
{
2022-01-27 02:44:03 +01:00
dbgln_if ( JS_MODULE_DEBUG , " [JS MODULE] finish_dynamic_import on {} " , module_request . module_specifier ) ;
2022-01-18 19:39:36 +01:00
2022-08-16 00:20:49 +01:00
auto & realm = * current_realm ( ) ;
2022-01-18 19:39:36 +01:00
// 1. Let fulfilledClosure be a new Abstract Closure with parameters (result) that captures referencingScriptOrModule, specifier, and promiseCapability and performs the following steps when called:
2022-10-02 12:11:30 +01:00
auto fulfilled_closure = [ referencing_script_or_module = move ( referencing_script_or_module ) , module_request = move ( module_request ) , & promise_capability ] ( VM & vm ) - > ThrowCompletionOr < Value > {
2022-01-18 19:39:36 +01:00
auto result = vm . argument ( 0 ) ;
// a. Assert: result is undefined.
VERIFY ( result . is_undefined ( ) ) ;
// b. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier).
2022-01-27 02:44:03 +01:00
auto module_record = MUST ( vm . host_resolve_imported_module ( referencing_script_or_module , module_request ) ) ;
2022-01-18 19:39:36 +01:00
// c. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
// Note: If HostResolveImportedModule returns a module evaluate will have been called on it.
2022-05-02 20:54:39 +02:00
// d. Let namespace be Completion(GetModuleNamespace(moduleRecord)).
2022-01-18 19:39:36 +01:00
auto namespace_ = module_record - > get_module_namespace ( vm ) ;
// e. If namespace is an abrupt completion, then
if ( namespace_ . is_throw_completion ( ) ) {
// i. Perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
2022-10-02 12:11:30 +01:00
MUST ( call ( vm , * promise_capability . reject ( ) , js_undefined ( ) , * namespace_ . throw_completion ( ) . value ( ) ) ) ;
2022-01-18 19:39:36 +01:00
}
// f. Else,
else {
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »).
2022-10-02 12:11:30 +01:00
MUST ( call ( vm , * promise_capability . resolve ( ) , js_undefined ( ) , namespace_ . release_value ( ) ) ) ;
2022-01-18 19:39:36 +01:00
}
2022-05-02 20:54:39 +02:00
// g. Return unused.
// NOTE: We don't support returning an empty/optional/unused value here.
2022-01-18 19:39:36 +01:00
return js_undefined ( ) ;
} ;
2022-05-02 20:54:39 +02:00
// 2. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
2022-12-13 20:49:50 +00:00
auto on_fulfilled = NativeFunction : : create ( realm , move ( fulfilled_closure ) , 0 , " " ) ;
2022-01-18 19:39:36 +01:00
// 3. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures promiseCapability and performs the following steps when called:
2022-10-02 12:11:30 +01:00
auto rejected_closure = [ & promise_capability ] ( VM & vm ) - > ThrowCompletionOr < Value > {
2022-01-18 19:39:36 +01:00
auto error = vm . argument ( 0 ) ;
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »).
2022-10-02 12:11:30 +01:00
MUST ( call ( vm , * promise_capability . reject ( ) , js_undefined ( ) , error ) ) ;
2022-05-02 20:54:39 +02:00
// b. Return unused.
// NOTE: We don't support returning an empty/optional/unused value here.
2022-01-18 19:39:36 +01:00
return js_undefined ( ) ;
} ;
2022-05-02 20:54:39 +02:00
// 4. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »).
2022-12-13 20:49:50 +00:00
auto on_rejected = NativeFunction : : create ( realm , move ( rejected_closure ) , 0 , " " ) ;
2022-01-18 19:39:36 +01:00
2022-05-02 20:54:39 +02:00
// 5. Perform PerformPromiseThen(innerPromise, onFulfilled, onRejected).
2022-01-18 19:39:36 +01:00
inner_promise - > perform_then ( on_fulfilled , on_rejected , { } ) ;
2022-05-02 20:54:39 +02:00
// 6. Return unused.
2022-01-18 19:39:36 +01:00
}
2020-09-20 19:24:44 +02:00
}