/* * Copyright (c) 2020, Stephan Unverwerth * Copyright (c) 2020-2022, Linus Groh * Copyright (c) 2021-2022, David Tuin * Copyright (c) 2021, Ali Mohammad Pur * Copyright (c) 2021, Idan Horowitz * Copyright (c) 2023, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include namespace JS { class ScopePusher { // NOTE: We really only need ModuleTopLevel and NotModuleTopLevel as the only // difference seems to be in https://tc39.es/ecma262/#sec-static-semantics-varscopeddeclarations // where ModuleItemList only does the VarScopedDeclaration and not the // TopLevelVarScopedDeclarations. enum class ScopeLevel { NotTopLevel, ScriptTopLevel, ModuleTopLevel, FunctionTopLevel, StaticInitTopLevel }; public: enum class ScopeType { Function, Program, Block, ForLoop, With, Catch, ClassStaticInit, ClassField, ClassDeclaration, }; private: ScopePusher(Parser& parser, ScopeNode* node, ScopeLevel scope_level, ScopeType type) : m_parser(parser) , m_scope_level(scope_level) , m_type(type) { m_parent_scope = exchange(m_parser.m_state.current_scope_pusher, this); if (type != ScopeType::Function) { VERIFY(node || (m_parent_scope && scope_level == ScopeLevel::NotTopLevel)); if (!node) m_node = m_parent_scope->m_node; else m_node = node; } if (!is_top_level()) m_top_level_scope = m_parent_scope->m_top_level_scope; else m_top_level_scope = this; } bool is_top_level() { return m_scope_level != ScopeLevel::NotTopLevel; } public: static ScopePusher function_scope(Parser& parser, RefPtr function_name = nullptr) { ScopePusher scope_pusher(parser, nullptr, ScopeLevel::FunctionTopLevel, ScopeType::Function); if (function_name) { scope_pusher.m_bound_names.set(function_name->string()); } return scope_pusher; } static ScopePusher program_scope(Parser& parser, Program& program) { return ScopePusher(parser, &program, program.type() == Program::Type::Script ? ScopeLevel::ScriptTopLevel : ScopeLevel::ModuleTopLevel, ScopeType::Program); } static ScopePusher block_scope(Parser& parser, ScopeNode& node) { return ScopePusher(parser, &node, ScopeLevel::NotTopLevel, ScopeType::Block); } static ScopePusher for_loop_scope(Parser& parser, ScopeNode& node) { return ScopePusher(parser, &node, ScopeLevel::NotTopLevel, ScopeType::ForLoop); } static ScopePusher with_scope(Parser& parser, ScopeNode& node) { ScopePusher scope_pusher(parser, &node, ScopeLevel::NotTopLevel, ScopeType::With); return scope_pusher; } static ScopePusher catch_scope(Parser& parser) { return ScopePusher(parser, nullptr, ScopeLevel::NotTopLevel, ScopeType::Catch); } void add_catch_parameter(RefPtr const& pattern, RefPtr const& parameter) { if (pattern) { // NOTE: Nothing in the callback throws an exception. MUST(pattern->for_each_bound_identifier([&](auto const& identifier) { m_forbidden_var_names.set(identifier.string()); m_bound_names.set(identifier.string()); m_catch_parameter_names.set(identifier.string()); })); } else if (parameter) { m_var_names.set(parameter->string(), parameter.ptr()); m_bound_names.set(parameter->string()); m_catch_parameter_names.set(parameter->string()); } } static ScopePusher static_init_block_scope(Parser& parser, ScopeNode& node) { ScopePusher scope_pusher(parser, &node, ScopeLevel::StaticInitTopLevel, ScopeType::ClassStaticInit); return scope_pusher; } static ScopePusher class_field_scope(Parser& parser, ScopeNode& node) { ScopePusher scope_pusher(parser, &node, ScopeLevel::NotTopLevel, ScopeType::ClassField); return scope_pusher; } static ScopePusher class_declaration_scope(Parser& parser, RefPtr class_name) { ScopePusher scope_pusher(parser, nullptr, ScopeLevel::NotTopLevel, ScopeType::ClassDeclaration); if (class_name) { scope_pusher.m_bound_names.set(class_name->string()); } return scope_pusher; } ScopeType type() const { return m_type; } void add_declaration(NonnullRefPtr declaration) { if (declaration->is_lexical_declaration()) { // NOTE: Nothing in the callback throws an exception. MUST(declaration->for_each_bound_identifier([&](auto const& identifier) { auto const& name = identifier.string(); if (m_var_names.contains(name) || m_forbidden_lexical_names.contains(name) || m_function_names.contains(name)) throw_identifier_declared(name, declaration); if (m_lexical_names.set(name) != AK::HashSetResult::InsertedNewEntry) throw_identifier_declared(name, declaration); })); m_node->add_lexical_declaration(move(declaration)); } else if (!declaration->is_function_declaration()) { // NOTE: Nothing in the callback throws an exception. MUST(declaration->for_each_bound_identifier([&](auto const& identifier) { auto const& name = identifier.string(); ScopePusher* pusher = this; while (true) { if (pusher->m_lexical_names.contains(name) || pusher->m_function_names.contains(name) || pusher->m_forbidden_var_names.contains(name)) throw_identifier_declared(name, declaration); pusher->m_var_names.set(name, &identifier); if (pusher->is_top_level()) break; VERIFY(pusher->m_parent_scope != nullptr); pusher = pusher->m_parent_scope; } VERIFY(pusher->is_top_level() && pusher->m_node); pusher->m_node->add_var_scoped_declaration(declaration); })); VERIFY(m_top_level_scope); m_top_level_scope->m_node->add_var_scoped_declaration(move(declaration)); } else { if (m_scope_level != ScopeLevel::NotTopLevel && m_scope_level != ScopeLevel::ModuleTopLevel) { // Only non-top levels and Module don't var declare the top functions // NOTE: Nothing in the callback throws an exception. MUST(declaration->for_each_bound_identifier([&](auto const& identifier) { m_var_names.set(identifier.string(), &identifier); })); m_node->add_var_scoped_declaration(move(declaration)); } else { VERIFY(is(*declaration)); auto function_declaration = static_ptr_cast(declaration); auto function_name = function_declaration->name(); if (m_var_names.contains(function_name) || m_lexical_names.contains(function_name)) throw_identifier_declared(function_name, declaration); if (function_declaration->kind() != FunctionKind::Normal || m_parser.m_state.strict_mode) { if (m_function_names.contains(function_name)) throw_identifier_declared(function_name, declaration); m_lexical_names.set(function_name); m_node->add_lexical_declaration(move(declaration)); return; } m_function_names.set(function_name, function_declaration); if (!m_lexical_names.contains(function_name)) m_functions_to_hoist.append(function_declaration); m_node->add_lexical_declaration(move(declaration)); } } } ScopePusher const* last_function_scope() const { for (auto scope_ptr = this; scope_ptr; scope_ptr = scope_ptr->m_parent_scope) { if (scope_ptr->m_type == ScopeType::Function || scope_ptr->m_type == ScopeType::ClassStaticInit) return scope_ptr; } return nullptr; } auto const& function_parameters() const { return *m_function_parameters; } ScopePusher* parent_scope() { return m_parent_scope; } ScopePusher const* parent_scope() const { return m_parent_scope; } [[nodiscard]] bool has_declaration(Utf16FlyString const& name) const { return m_lexical_names.contains(name) || m_var_names.contains(name) || m_functions_to_hoist.contains([&name](auto& function) { return function->name() == name; }); } bool contains_direct_call_to_eval() const { return m_contains_direct_call_to_eval; } void set_contains_direct_call_to_eval() { m_contains_direct_call_to_eval = true; m_screwed_by_eval_in_scope_chain = true; m_eval_in_current_function = true; } void set_contains_access_to_arguments_object_in_non_strict_mode() { m_contains_access_to_arguments_object_in_non_strict_mode = true; } void set_scope_node(ScopeNode* node) { m_node = node; } void set_function_parameters(NonnullRefPtr parameters) { m_function_parameters = move(parameters); for (auto& parameter : m_function_parameters->parameters()) { parameter.binding.visit( [&](Identifier const& identifier) { register_identifier(fixme_launder_const_through_pointer_cast(identifier)); m_function_parameters_candidates_for_local_variables.set(identifier.string()); m_forbidden_lexical_names.set(identifier.string()); }, [&](NonnullRefPtr const& binding_pattern) { // NOTE: Nothing in the callback throws an exception. MUST(binding_pattern->for_each_bound_identifier([&](auto const& identifier) { m_forbidden_lexical_names.set(identifier.string()); })); }); } } ~ScopePusher() { VERIFY(is_top_level() || m_parent_scope); if (m_parent_scope && !m_function_parameters) { m_parent_scope->m_contains_access_to_arguments_object_in_non_strict_mode |= m_contains_access_to_arguments_object_in_non_strict_mode; m_parent_scope->m_contains_direct_call_to_eval |= m_contains_direct_call_to_eval; m_parent_scope->m_contains_await_expression |= m_contains_await_expression; } if (!m_node) { m_parser.m_state.current_scope_pusher = m_parent_scope; return; } if (m_parent_scope && (m_contains_direct_call_to_eval || m_screwed_by_eval_in_scope_chain)) { m_parent_scope->m_screwed_by_eval_in_scope_chain = true; } // Propagate eval-in-current-function only through block scopes, not across function boundaries. // This is used for global identifier marking - eval can only inject vars into its containing // function's scope, not into parent function scopes. if (m_parent_scope && m_eval_in_current_function && m_type != ScopeType::Function) { m_parent_scope->m_eval_in_current_function = true; } for (auto& it : m_identifier_groups) { auto const& identifier_group_name = it.key; auto& identifier_group = it.value; if (identifier_group.declaration_kind.has_value()) { for (auto& identifier : identifier_group.identifiers) { identifier->set_declaration_kind(identifier_group.declaration_kind.value()); } } Optional local_variable_declaration_kind; if (is_top_level() && m_var_names.contains(identifier_group_name)) { local_variable_declaration_kind = LocalVariable::DeclarationKind::Var; } else if (m_lexical_names.contains(identifier_group_name)) { local_variable_declaration_kind = LocalVariable::DeclarationKind::LetOrConst; } else if (m_function_names.contains(identifier_group_name)) { local_variable_declaration_kind = LocalVariable::DeclarationKind::Function; } if (m_type == ScopeType::Function && !m_is_arrow_function && identifier_group_name == "arguments"sv) { local_variable_declaration_kind = LocalVariable::DeclarationKind::ArgumentsObject; } if (m_type == ScopeType::Catch && m_catch_parameter_names.contains(identifier_group_name)) { local_variable_declaration_kind = LocalVariable::DeclarationKind::CatchClauseParameter; } bool hoistable_function_declaration = m_functions_to_hoist.contains([&](auto const& function_declaration) { return function_declaration->name() == identifier_group_name; }); if (m_type == ScopeType::ClassDeclaration && m_bound_names.contains(identifier_group_name)) { // NOTE: Currently, the parser cannot recognize that assigning a named function expression creates a scope with a binding for the function name. // As a result, function names are not considered as candidates for optimization in global variable access. continue; } if (m_type == ScopeType::Function && !m_is_function_declaration && m_bound_names.contains(identifier_group_name)) { // Named function expression: identifiers with this name inside the function may refer // to the function's immutable name binding, so they cannot be optimized as globals. for (auto& identifier : identifier_group.identifiers) identifier->set_is_inside_scope_with_eval(); } if (m_type == ScopeType::ClassDeclaration) { // NOTE: Class declaration doesn't not have own ScopeNode hence can't contain declaration of any variable local_variable_declaration_kind.clear(); } bool is_function_parameter = false; if (m_type == ScopeType::Function) { if (!m_contains_access_to_arguments_object_in_non_strict_mode && m_function_parameters_candidates_for_local_variables.contains(identifier_group_name)) { is_function_parameter = true; } else if (m_forbidden_lexical_names.contains(identifier_group_name)) { // NOTE: If an identifier is used as a function parameter that cannot be optimized locally or globally, it is simply ignored. continue; } } if (m_type == ScopeType::Function && hoistable_function_declaration) { // NOTE: Hoistable function declarations are currently not optimized into global or local variables, but future improvements may change that. continue; } if (m_type == ScopeType::Program) { auto can_use_global_for_identifier = !(identifier_group.used_inside_with_statement || m_parser.m_state.initiated_by_eval); if (can_use_global_for_identifier) { for (auto& identifier : identifier_group.identifiers) { // Only mark identifiers as global if they are not inside a function scope // that contains eval() or has eval in its scope chain. if (!identifier->is_inside_scope_with_eval()) identifier->set_is_global(); } } } else if (local_variable_declaration_kind.has_value() || is_function_parameter) { if (hoistable_function_declaration) continue; if (!identifier_group.captured_by_nested_function && !identifier_group.used_inside_with_statement) { if (m_screwed_by_eval_in_scope_chain) continue; auto local_scope = last_function_scope(); if (!local_scope) { // NOTE: If there is no function scope, we are in a *descendant* of the global program scope. // While we cannot make `let` and `const` into locals in the topmost program scope, // as that would break expected web behavior where subsequent