/* * 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 "Parser.h" #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, RefPtr const& pattern, DeprecatedFlyString const& parameter) { ScopePusher scope_pusher(parser, nullptr, ScopeLevel::NotTopLevel, ScopeType::Catch); if (pattern) { // NOTE: Nothing in the callback throws an exception. MUST(pattern->for_each_bound_identifier([&](auto const& identifier) { scope_pusher.m_forbidden_var_names.set(identifier.string()); scope_pusher.m_bound_names.set(identifier.string()); })); } else if (!parameter.is_empty()) { scope_pusher.m_var_names.set(parameter); scope_pusher.m_bound_names.set(parameter); } return scope_pusher; } 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); 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()); })); m_node->add_var_scoped_declaration(move(declaration)); } else { VERIFY(is(*declaration)); auto& function_declaration = static_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); if (!m_lexical_names.contains(function_name)) m_functions_to_hoist.append(static_ptr_cast(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_function_parameters.has_value()) return scope_ptr; } return nullptr; } Vector 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(DeprecatedFlyString const& name) const { return m_lexical_names.contains(name) || m_var_names.contains(name) || !m_functions_to_hoist.find_if([&name](auto& function) { return function->name() == name; }).is_end(); } bool contains_direct_call_to_eval() const { return m_contains_direct_call_to_eval; } bool contains_access_to_arguments_object() const { return m_contains_access_to_arguments_object; } void set_contains_direct_call_to_eval() { m_contains_direct_call_to_eval = true; m_screwed_by_eval_in_scope_chain = true; } void set_contains_access_to_arguments_object() { m_contains_access_to_arguments_object = true; } void set_scope_node(ScopeNode* node) { m_node = node; } void set_function_parameters(Vector const& parameters) { m_function_parameters = parameters; for (auto& parameter : parameters) { parameter.binding.visit( [&](Identifier const& identifier) { register_identifier(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.has_value()) { m_parent_scope->m_contains_access_to_arguments_object |= m_contains_access_to_arguments_object; 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_parent_scope->m_screwed_by_eval_in_scope_chain = true; } for (auto& it : m_identifier_groups) { auto const& identifier_group_name = it.key; auto& identifier_group = it.value; if (m_parser.m_state.in_catch_parameter_context) { // NOTE: The parser currently cannot determine if an identifier captured by a function belongs to the environment created by a catch parameter. // As a result, any identifiers used inside the catch parameter are not considered as candidates for optimization in local or global variable access. continue; } bool scope_has_declaration = false; if (is_top_level() && m_var_names.contains(identifier_group_name)) scope_has_declaration = true; else if (m_lexical_names.contains(identifier_group_name) || m_function_names.contains(identifier_group_name)) scope_has_declaration = true; if (m_type == ScopeType::Function && !m_is_arrow_function && identifier_group_name == "arguments"sv) scope_has_declaration = true; bool hoistable_function_declaration = false; for (auto const& function_declaration : m_functions_to_hoist) { if (function_declaration->name() == identifier_group_name) hoistable_function_declaration = true; } if ((m_type == ScopeType::ClassDeclaration || m_type == ScopeType::Catch) && 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_bound_names.contains(identifier_group_name)) { // NOTE: Currently parser can't determine that named function expression assigment creates scope with binding for funciton name so function names are not considered as candidates to be optmized in global variables access identifier_group.might_be_variable_in_lexical_scope_in_named_function_assignment = true; } if (m_type == ScopeType::ClassDeclaration || m_type == ScopeType::Catch) { // NOTE: Class declaration and catch scopes do not have own ScopeNode hence can't contain declaration of any variable scope_has_declaration = false; } if (m_type == ScopeType::Function) { if (!m_contains_access_to_arguments_object && m_function_parameters_candidates_for_local_variables.contains(identifier_group_name)) { scope_has_declaration = 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 || identifier_group.might_be_variable_in_lexical_scope_in_named_function_assignment || identifier_group.used_inside_scope_with_eval || m_parser.m_state.initiated_by_eval); if (can_use_global_for_identifier) { for (auto& identifier : identifier_group.identifiers) identifier->set_is_global(); } } else if (scope_has_declaration) { 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