diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 2847c3e6a9..f5f19a67ce 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -99,6 +99,160 @@ ECMAScriptFunctionObject::ECMAScriptFunctionObject(DeprecatedFlyString name, Dep return false; return true; }); + + // NOTE: The following steps are from FunctionDeclarationInstantiation that could be executed once + // and then reused in all subsequent function instantiations. + + // 2. Let code be func.[[ECMAScriptCode]]. + ScopeNode const* scope_body = nullptr; + if (is(*m_ecmascript_code)) + scope_body = static_cast(m_ecmascript_code.ptr()); + + // 3. Let strict be func.[[Strict]]. + + // 4. Let formals be func.[[FormalParameters]]. + auto const& formals = m_formal_parameters; + + // 5. Let parameterNames be the BoundNames of formals. + // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. + + // NOTE: This loop performs step 5, 6, and 8. + for (auto const& parameter : formals) { + if (parameter.default_value) + m_has_parameter_expressions = true; + + parameter.binding.visit( + [&](Identifier const& identifier) { + if (m_parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry) + m_has_duplicates = true; + }, + [&](NonnullRefPtr const& pattern) { + if (pattern->contains_expression()) + m_has_parameter_expressions = true; + + // NOTE: Nothing in the callback throws an exception. + MUST(pattern->for_each_bound_identifier([&](auto& identifier) { + if (m_parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry) + m_has_duplicates = true; + })); + }); + } + + // 15. Let argumentsObjectNeeded be true. + m_arguments_object_needed = m_might_need_arguments_object; + + // 16. If func.[[ThisMode]] is lexical, then + if (this_mode() == ThisMode::Lexical) { + // a. NOTE: Arrow functions never have an arguments object. + // b. Set argumentsObjectNeeded to false. + m_arguments_object_needed = false; + } + // 17. Else if parameterNames contains "arguments", then + else if (m_parameter_names.contains(vm().names.arguments.as_string())) { + // a. Set argumentsObjectNeeded to false. + m_arguments_object_needed = false; + } + + HashTable function_names; + + // 18. Else if hasParameterExpressions is false, then + // a. If functionNames contains "arguments" or lexicalNames contains "arguments", then + // i. Set argumentsObjectNeeded to false. + // NOTE: The block below is a combination of step 14 and step 18. + if (scope_body) { + // NOTE: Nothing in the callback throws an exception. + MUST(scope_body->for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) { + if (function_names.set(function.name()) == AK::HashSetResult::InsertedNewEntry) + m_functions_to_initialize.append(function); + })); + + auto const& arguments_name = vm().names.arguments.as_string(); + + if (!m_has_parameter_expressions && function_names.contains(arguments_name)) + m_arguments_object_needed = false; + + if (!m_has_parameter_expressions && m_arguments_object_needed) { + // NOTE: Nothing in the callback throws an exception. + MUST(scope_body->for_each_lexically_declared_identifier([&](auto const& identifier) { + if (identifier.string() == arguments_name) + m_arguments_object_needed = false; + })); + } + } else { + m_arguments_object_needed = false; + } + + HashTable parameter_bindings; + + // 22. If argumentsObjectNeeded is true, then + if (m_arguments_object_needed) { + // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ». + parameter_bindings = m_parameter_names; + parameter_bindings.set(vm().names.arguments.as_string()); + } else { + parameter_bindings = m_parameter_names; + // a. Let parameterBindings be parameterNames. + } + + HashTable instantiated_var_names; + + // 27. If hasParameterExpressions is false, then + if (!m_has_parameter_expressions) { + // b. Let instantiatedVarNames be a copy of the List parameterBindings. + instantiated_var_names = parameter_bindings; + + if (scope_body) { + // c. For each element n of varNames, do + MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) { + // i. If instantiatedVarNames does not contain n, then + if (instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) { + // 1. Append n to instantiatedVarNames. + // Following steps will be executed in function_declaration_instantiation: + // 2. Perform ! env.CreateMutableBinding(n, false). + // 3. Perform ! env.InitializeBinding(n, undefined). + m_var_names_to_initialize_binding.append({ + .identifier = id, + .parameter_binding = parameter_bindings.contains(id.string()), + .function_name = function_names.contains(id.string()), + }); + } + })); + } + } else { + // 28. Else, + // NOTE: Steps a, b, c and d are executed in function_declaration_instantiation. + // e. For each element n of varNames, do + if (scope_body) { + MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) { + // 1. Append n to instantiatedVarNames. + // Following steps will be executed in function_declaration_instantiation: + // 2. Perform ! env.CreateMutableBinding(n, false). + // 3. Perform ! env.InitializeBinding(n, undefined). + if (instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) { + m_var_names_to_initialize_binding.append({ + .identifier = id, + .parameter_binding = parameter_bindings.contains(id.string()), + .function_name = function_names.contains(id.string()), + }); + } + })); + } + } + + if (!m_strict && scope_body) { + MUST(scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { + auto function_name = function_declaration.name(); + if (parameter_bindings.contains(function_name)) + return; + + if (!instantiated_var_names.contains(function_name) && function_name != vm().names.arguments.as_string()) { + m_function_names_to_initialize_binding.append(function_name); + instantiated_var_names.set(function_name); + } + + function_declaration.set_should_do_additional_annexB_steps(); + })); + } } void ECMAScriptFunctionObject::initialize(Realm& realm) @@ -340,115 +494,32 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia if (is(*m_ecmascript_code)) scope_body = static_cast(m_ecmascript_code.ptr()); - // 3. Let strict be func.[[Strict]]. - bool const strict = is_strict_mode(); - - bool has_parameter_expressions = false; - - // 4. Let formals be func.[[FormalParameters]]. - auto const& formals = m_formal_parameters; - - // FIXME: Maybe compute has duplicates at parse time? (We need to anyway since it's an error in some cases) - // 5. Let parameterNames be the BoundNames of formals. - // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. - bool has_duplicates = false; - HashTable parameter_names; - - // NOTE: This loop performs step 5, 6, and 8. - for (auto const& parameter : formals) { - if (parameter.default_value) - has_parameter_expressions = true; - - parameter.binding.visit( - [&](Identifier const& identifier) { - if (parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry) - has_duplicates = true; - }, - [&](NonnullRefPtr const& pattern) { - if (pattern->contains_expression()) - has_parameter_expressions = true; - - // NOTE: Nothing in the callback throws an exception. - MUST(pattern->for_each_bound_identifier([&](auto& identifier) { - if (parameter_names.set(identifier.string()) != AK::HashSetResult::InsertedNewEntry) - has_duplicates = true; - })); - }); - } + // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. + // 3. Let strict be func.[[Strict]]. + // 4. Let formals be func.[[FormalParameters]]. + // 5. Let parameterNames be the BoundNames of formals. + // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. // 7. Let simpleParameterList be IsSimpleParameterList of formals. bool const simple_parameter_list = has_simple_parameter_list(); - // 8. Let hasParameterExpressions be ContainsExpression of formals. - // NOTE: Already set above. - - // 9. Let varNames be the VarDeclaredNames of code. - // 10. Let varDeclarations be the VarScopedDeclarations of code. - // 11. Let lexicalNames be the LexicallyDeclaredNames of code. - // NOTE: Not needed as we use iteration helpers for this instead. - - // 12. Let functionNames be a new empty List. - HashTable function_names; - - // 13. Let functionsToInitialize be a new empty List. - Vector functions_to_initialize; - - // 14. For each element d of varDeclarations, in reverse List order, do - // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then - // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. - // ii. Let fn be the sole element of the BoundNames of d. - // iii. If functionNames does not contain fn, then - // 1. Insert fn as the first element of functionNames. - // 2. NOTE: If there are multiple function declarations for the same name, the last declaration is used. - // 3. Insert d as the first element of functionsToInitialize. - // NOTE: This block is done in step 18 below. - - // 15. Let argumentsObjectNeeded be true. - auto arguments_object_needed = m_might_need_arguments_object; - - // 16. If func.[[ThisMode]] is lexical, then - if (this_mode() == ThisMode::Lexical) { - // a. NOTE: Arrow functions never have an arguments object. - // b. Set argumentsObjectNeeded to false. - arguments_object_needed = false; - } - // 17. Else if parameterNames contains "arguments", then - else if (parameter_names.contains(vm.names.arguments.as_string())) { - // a. Set argumentsObjectNeeded to false. - arguments_object_needed = false; - } - - // 18. Else if hasParameterExpressions is false, then - // a. If functionNames contains "arguments" or lexicalNames contains "arguments", then - // i. Set argumentsObjectNeeded to false. - // NOTE: The block below is a combination of step 14 and step 18. - if (scope_body) { - // NOTE: Nothing in the callback throws an exception. - MUST(scope_body->for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) { - if (function_names.set(function.name()) == AK::HashSetResult::InsertedNewEntry) - functions_to_initialize.append(function); - })); - - auto const& arguments_name = vm.names.arguments.as_string(); - - if (!has_parameter_expressions && function_names.contains(arguments_name)) - arguments_object_needed = false; - - if (!has_parameter_expressions && arguments_object_needed) { - // NOTE: Nothing in the callback throws an exception. - MUST(scope_body->for_each_lexically_declared_identifier([&](auto const& identifier) { - if (identifier.string() == arguments_name) - arguments_object_needed = false; - })); - } - } else { - arguments_object_needed = false; - } + // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. + // 8. Let hasParameterExpressions be ContainsExpression of formals. + // 9. Let varNames be the VarDeclaredNames of code. + // 10. Let varDeclarations be the VarScopedDeclarations of code. + // 11. Let lexicalNames be the LexicallyDeclaredNames of code. + // 12. Let functionNames be a new empty List. + // 13. Let functionsToInitialize be a new empty List. + // 14. For each element d of varDeclarations, in reverse List order, do + // 15. Let argumentsObjectNeeded be true. + // 16. If func.[[ThisMode]] is lexical, then + // 17. Else if parameterNames contains "arguments", then + // 18. Else if hasParameterExpressions is false, then GCPtr environment; // 19. If strict is true or hasParameterExpressions is false, then - if (strict || !has_parameter_expressions) { + if (m_strict || !m_has_parameter_expressions) { // a. NOTE: Only a single Environment Record is needed for the parameters, since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval. // b. Let env be the LexicalEnvironment of calleeContext. environment = callee_context.lexical_environment; @@ -471,7 +542,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia } // 21. For each String paramName of parameterNames, do - for (auto const& parameter_name : parameter_names) { + for (auto const& parameter_name : m_parameter_names) { // a. Let alreadyDeclared be ! env.HasBinding(paramName). auto already_declared = MUST(environment->has_binding(parameter_name)); @@ -483,7 +554,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia MUST(environment->create_mutable_binding(vm, parameter_name, false)); // ii. If hasDuplicates is true, then - if (has_duplicates) { + if (m_has_duplicates) { // 1. Perform ! env.InitializeBinding(paramName, undefined). MUST(environment->initialize_binding(vm, parameter_name, js_undefined(), Environment::InitializeBindingHint::Normal)); } @@ -491,11 +562,11 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia } // 22. If argumentsObjectNeeded is true, then - if (arguments_object_needed) { + if (m_arguments_object_needed) { Object* arguments_object; // a. If strict is true or simpleParameterList is false, then - if (strict || !simple_parameter_list) { + if (m_strict || !simple_parameter_list) { // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). arguments_object = create_unmapped_arguments_object(vm, vm.running_execution_context().arguments); } @@ -508,7 +579,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia } // c. If strict is true, then - if (strict) { + if (m_strict) { // i. Perform ! env.CreateImmutableBinding("arguments", false). MUST(environment->create_immutable_binding(vm, vm.names.arguments.as_string(), false)); @@ -524,7 +595,6 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia MUST(environment->initialize_binding(vm, vm.names.arguments.as_string(), arguments_object, Environment::InitializeBindingHint::Normal)); // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ». - parameter_names.set(vm.names.arguments.as_string()); } // 23. Else, else { @@ -567,7 +637,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia argument_value = js_undefined(); } - Environment* used_environment = has_duplicates ? nullptr : environment; + Environment* used_environment = m_has_duplicates ? nullptr : environment; if constexpr (IsSame const&, decltype(param)>) { if (param->is_local()) { @@ -576,7 +646,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia } Reference reference = TRY(vm.resolve_binding(param->string(), used_environment)); // Here the difference from hasDuplicates is important - if (has_duplicates) + if (m_has_duplicates) return reference.put_value(vm, argument_value); return reference.initialize_referenced_binding(vm, argument_value); } @@ -594,7 +664,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia instantiated_var_names.ensure_capacity(scope_body->var_declaration_count()); // 27. If hasParameterExpressions is false, then - if (!has_parameter_expressions) { + if (!m_has_parameter_expressions) { // a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars. // b. Let instantiatedVarNames be a copy of the List parameterBindings. @@ -605,21 +675,20 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia // an exception should not result from `for_each_var_declared_name`. // c. For each element n of varNames, do - MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) { - // i. If instantiatedVarNames does not contain n, then - if (!parameter_names.contains(id.string()) && instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) { - // 1. Append n to instantiatedVarNames. - + for (auto const& variable_to_initialize : m_var_names_to_initialize_binding) { + auto const& id = variable_to_initialize.identifier; + // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. + // i. If instantiatedVarNames does not contain n, then + // 1. Append n to instantiatedVarNames. + if (id.is_local()) { + callee_context.local_variables[id.local_variable_index()] = js_undefined(); + } else { // 2. Perform ! env.CreateMutableBinding(n, false). // 3. Perform ! env.InitializeBinding(n, undefined). - if (id.is_local()) { - callee_context.local_variables[id.local_variable_index()] = js_undefined(); - } else { - MUST(environment->create_mutable_binding(vm, id.string(), false)); - MUST(environment->initialize_binding(vm, id.string(), js_undefined(), Environment::InitializeBindingHint::Normal)); - } + MUST(environment->create_mutable_binding(vm, id.string(), false)); + MUST(environment->initialize_binding(vm, id.string(), js_undefined(), Environment::InitializeBindingHint::Normal)); } - })); + } } // d.Let varEnv be env @@ -643,69 +712,61 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia // an exception should not result from `for_each_var_declared_name`. // e. For each element n of varNames, do - MUST(scope_body->for_each_var_declared_identifier([&](auto const& id) { - // i. If instantiatedVarNames does not contain n, then - if (instantiated_var_names.set(id.string()) == AK::HashSetResult::InsertedNewEntry) { - // 1. Append n to instantiatedVarNames. + for (auto const& variable_to_initialize : m_var_names_to_initialize_binding) { + auto const& id = variable_to_initialize.identifier; - // 2. Perform ! varEnv.CreateMutableBinding(n, false). - MUST(var_environment->create_mutable_binding(vm, id.string(), false)); + // NOTE: Following steps were executed in ECMAScriptFunctionObject constructor. + // i. If instantiatedVarNames does not contain n, then + // 1. Append n to instantiatedVarNames. - Value initial_value; + // 2. Perform ! varEnv.CreateMutableBinding(n, false). + MUST(var_environment->create_mutable_binding(vm, id.string(), false)); - // 3. If parameterBindings does not contain n, or if functionNames contains n, then - if (!parameter_names.contains(id.string()) || function_names.contains(id.string())) { - // a. Let initialValue be undefined. - initial_value = js_undefined(); - } - // 4. Else, - else { - // a. Let initialValue be ! env.GetBindingValue(n, false). - if (id.is_local()) { - initial_value = callee_context.local_variables[id.local_variable_index()]; - } else { - initial_value = MUST(environment->get_binding_value(vm, id.string(), false)); - } - } + Value initial_value; - // 5. Perform ! varEnv.InitializeBinding(n, initialValue). - if (id.is_local()) { - // NOTE: Local variables are supported only in bytecode interpreter - callee_context.local_variables[id.local_variable_index()] = initial_value; - } else { - MUST(var_environment->initialize_binding(vm, id.string(), initial_value, Environment::InitializeBindingHint::Normal)); - } - - // 6. NOTE: A var with the same name as a formal parameter initially has the same value as the corresponding initialized parameter. + // 3. If parameterBindings does not contain n, or if functionNames contains n, then + if (!variable_to_initialize.parameter_binding || variable_to_initialize.function_name) { + // a. Let initialValue be undefined. + initial_value = js_undefined(); } - })); + // 4. Else, + else { + // a. Let initialValue be ! env.GetBindingValue(n, false). + if (id.is_local()) { + initial_value = callee_context.local_variables[id.local_variable_index()]; + } else { + initial_value = MUST(environment->get_binding_value(vm, id.string(), false)); + } + } + + // 5. Perform ! varEnv.InitializeBinding(n, initialValue). + if (id.is_local()) { + // NOTE: Local variables are supported only in bytecode interpreter + callee_context.local_variables[id.local_variable_index()] = initial_value; + } else { + MUST(var_environment->initialize_binding(vm, id.string(), initial_value, Environment::InitializeBindingHint::Normal)); + } + + // 6. NOTE: A var with the same name as a formal parameter initially has the same value as the corresponding initialized parameter. + } } } // 29. NOTE: Annex B.3.2.1 adds additional steps at this point. // B.3.2.1 Changes to FunctionDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation - if (!strict && scope_body) { + if (!m_strict && scope_body) { // NOTE: Due to the use of MUST with `create_mutable_binding` and `initialize_binding` below, // an exception should not result from `for_each_function_hoistable_with_annexB_extension`. - MUST(scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { - auto function_name = function_declaration.name(); - if (parameter_names.contains(function_name)) - return; - // The spec says 'initializedBindings' here but that does not exist and it then adds it to 'instantiatedVarNames' so it probably means 'instantiatedVarNames'. - if (!instantiated_var_names.contains(function_name) && function_name != vm.names.arguments.as_string()) { - MUST(var_environment->create_mutable_binding(vm, function_name, false)); - MUST(var_environment->initialize_binding(vm, function_name, js_undefined(), Environment::InitializeBindingHint::Normal)); - instantiated_var_names.set(function_name); - } - - function_declaration.set_should_do_additional_annexB_steps(); - })); + for (auto const& function_name : m_function_names_to_initialize_binding) { + MUST(var_environment->create_mutable_binding(vm, function_name, false)); + MUST(var_environment->initialize_binding(vm, function_name, js_undefined(), Environment::InitializeBindingHint::Normal)); + } } GCPtr lex_environment; // 30. If strict is false, then - if (!strict) { + if (!m_strict) { // Optimization: We avoid creating empty top-level declarative environments in non-strict mode, if both of these conditions are true: // 1. there is no direct call to eval() within this function // 2. there are no lexical declarations that would go into the environment @@ -766,7 +827,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia auto private_environment = callee_context.private_environment; // 36. For each Parse Node f of functionsToInitialize, do - for (auto& declaration : functions_to_initialize) { + for (auto& declaration : m_functions_to_initialize) { // a. Let fn be the sole element of the BoundNames of f. // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. auto function = ECMAScriptFunctionObject::create(realm, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), declaration.local_variables_names(), lex_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval()); diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index afc72f9765..f0897de2c4 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -138,6 +138,20 @@ private: bool m_is_arrow_function : 1 { false }; bool m_has_simple_parameter_list : 1 { false }; FunctionKind m_kind : 3 { FunctionKind::Normal }; + + struct VariableNameToInitianlize { + Identifier const& identifier; + bool parameter_binding { false }; + bool function_name { false }; + }; + + bool m_has_parameter_expressions { false }; + bool m_has_duplicates { false }; + HashTable m_parameter_names; + Vector m_functions_to_initialize; + bool m_arguments_object_needed { false }; + Vector m_var_names_to_initialize_binding; + Vector m_function_names_to_initialize_binding; }; template<>