/* * Copyright (c) 2025, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include namespace JS { GC_DEFINE_ALLOCATOR(SharedFunctionInstanceData); SharedFunctionInstanceData::SharedFunctionInstanceData( VM& vm, FunctionKind kind, Utf16FlyString name, i32 function_length, NonnullRefPtr formal_parameters, NonnullRefPtr ecmascript_code, ByteString source_text, bool strict, bool is_arrow_function, FunctionParsingInsights const& parsing_insights, Vector local_variables_names) : m_formal_parameters(move(formal_parameters)) , m_ecmascript_code(move(ecmascript_code)) , m_name(move(name)) , m_source_text(move(source_text)) , m_local_variables_names(move(local_variables_names)) , m_function_length(function_length) , m_kind(kind) , m_strict(strict) , m_might_need_arguments_object(parsing_insights.might_need_arguments_object) , m_contains_direct_call_to_eval(parsing_insights.contains_direct_call_to_eval) , m_is_arrow_function(is_arrow_function) , m_uses_this(parsing_insights.uses_this) { if (m_is_arrow_function) m_this_mode = ThisMode::Lexical; else if (m_strict) m_this_mode = ThisMode::Strict; else m_this_mode = ThisMode::Global; // 15.1.3 Static Semantics: IsSimpleParameterList, https://tc39.es/ecma262/#sec-static-semantics-issimpleparameterlist m_has_simple_parameter_list = all_of(m_formal_parameters->parameters(), [&](auto& parameter) { if (parameter.is_rest) return false; if (parameter.default_value) return false; if (!parameter.binding.template has>()) 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. size_t parameters_in_environment = 0; // NOTE: This loop performs step 5, 6, and 8. for (auto const& parameter : formals.parameters()) { if (parameter.default_value) m_has_parameter_expressions = true; parameter.binding.visit( [&](Identifier const& identifier) { if (m_parameter_names.set(identifier.string(), identifier.is_local() ? ParameterIsLocal::Yes : ParameterIsLocal::No) != AK::HashSetResult::InsertedNewEntry) m_has_duplicates = true; else if (!identifier.is_local()) ++parameters_in_environment; }, [&](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(), identifier.is_local() ? ParameterIsLocal::Yes : ParameterIsLocal::No) != AK::HashSetResult::InsertedNewEntry) m_has_duplicates = true; else if (!identifier.is_local()) ++parameters_in_environment; })); }); } // 15. Let argumentsObjectNeeded be true. m_arguments_object_needed = m_might_need_arguments_object; // 16. If func.[[ThisMode]] is lexical, then if (m_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; } size_t* environment_size = nullptr; size_t parameter_environment_bindings_count = 0; // 19. If strict is true or hasParameterExpressions is false, then if (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 // NOTE: Here we are only interested in the size of the environment. environment_size = &m_function_environment_bindings_count; } // 20. Else, else { // a. NOTE: A separate Environment Record is needed to ensure that bindings created by direct eval calls in the formal parameter list are outside the environment where parameters are declared. // b. Let calleeEnv be the LexicalEnvironment of calleeContext. // c. Let env be NewDeclarativeEnvironment(calleeEnv). environment_size = ¶meter_environment_bindings_count; } *environment_size += parameters_in_environment; HashMap parameter_bindings; auto arguments_object_needs_binding = m_arguments_object_needed && !m_local_variables_names.first_matching([](auto const& local) { return local.declaration_kind == LocalVariable::DeclarationKind::ArgumentsObject; }).has_value(); // 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(), ParameterIsLocal::No); if (arguments_object_needs_binding) (*environment_size)++; } else { parameter_bindings = m_parameter_names; // a. Let parameterBindings be parameterNames. } HashMap instantiated_var_names; size_t* var_environment_size = nullptr; // 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([&](Identifier const& id) { // i. If instantiatedVarNames does not contain n, then if (instantiated_var_names.set(id.string(), id.is_local() ? ParameterIsLocal::Yes : ParameterIsLocal::No) == 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, // NOTE: We don't have to set parameter_binding or function_name here // since those are only relevant in the hasParameterExpressions==true path. }); if (!id.is_local()) (*environment_size)++; } })); } // d. Let varEnv be env var_environment_size = environment_size; } else { // a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body. // b. Let varEnv be NewDeclarativeEnvironment(env). // NOTE: Here we are only interested in the size of the environment. var_environment_size = &m_var_environment_bindings_count; // 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([&](Identifier const& id) { auto const& name = id.string(); // 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(name, id.is_local() ? ParameterIsLocal::Yes : ParameterIsLocal::No) == AK::HashSetResult::InsertedNewEntry) { m_var_names_to_initialize_binding.append({ .identifier = id, .parameter_binding = parameter_bindings.contains(name), .function_name = function_names.contains(name), }); if (!id.is_local()) (*var_environment_size)++; } })); } } // 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 (!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, ParameterIsLocal::No); (*var_environment_size)++; } function_declaration.set_should_do_additional_annexB_steps(); })); } size_t* lex_environment_size = nullptr; // 30. If strict is false, then if (!m_strict) { bool can_elide_declarative_environment = !m_contains_direct_call_to_eval && (!scope_body || !scope_body->has_non_local_lexical_declarations()); if (can_elide_declarative_environment) { lex_environment_size = var_environment_size; } else { // a. Let lexEnv be NewDeclarativeEnvironment(varEnv). lex_environment_size = &m_lex_environment_bindings_count; } } else { // a. let lexEnv be varEnv. // NOTE: Here we are only interested in the size of the environment. lex_environment_size = var_environment_size; } if (scope_body) { MUST(scope_body->for_each_lexically_declared_identifier([&](auto const& id) { if (!id.is_local()) (*lex_environment_size)++; })); } m_function_environment_needed = arguments_object_needs_binding || m_function_environment_bindings_count > 0 || m_var_environment_bindings_count > 0 || m_lex_environment_bindings_count > 0 || parsing_insights.uses_this_from_environment || m_contains_direct_call_to_eval; } void SharedFunctionInstanceData::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_executable); } SharedFunctionInstanceData::~SharedFunctionInstanceData() = default; }