diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 3b9ca881fc..df17066a04 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1011,10 +1011,30 @@ Reference Identifier::to_reference(Interpreter& interpreter, GlobalObject&) cons environment = environment->outer_environment(); VERIFY(environment); VERIFY(environment->is_declarative_environment()); - if (!environment->is_permanently_screwed_by_eval()) + if (!environment->is_permanently_screwed_by_eval()) { + if (m_lexically_bound_function_argument.has_value()) { + return Reference { + *environment, + string(), + *m_lexically_bound_function_argument, + interpreter.vm().in_strict_mode(), + m_cached_environment_coordinate, + &interpreter.vm().running_execution_context(), + }; + } return Reference { *environment, string(), interpreter.vm().in_strict_mode(), m_cached_environment_coordinate }; + } m_cached_environment_coordinate = {}; } + if (m_lexically_bound_function_argument.has_value()) { + return Reference { + string(), + *m_lexically_bound_function_argument, + interpreter.vm().in_strict_mode(), + &interpreter.vm().running_execution_context(), + }; + } + auto reference = interpreter.vm().resolve_binding(string()); if (reference.environment_coordinate().has_value()) m_cached_environment_coordinate = reference.environment_coordinate(); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 3952c5a8db..27a1797871 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -982,6 +982,7 @@ public: } FlyString const& string() const { return m_string; } + void set_lexically_bound_function_argument_index(size_t index) { m_lexically_bound_function_argument = index; } virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; @@ -992,6 +993,7 @@ private: virtual bool is_identifier() const override { return true; } FlyString m_string; + Optional m_lexically_bound_function_argument; mutable Optional m_cached_environment_coordinate; }; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 0bd68ee8b9..bf33bfcb52 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -2,6 +2,7 @@ * Copyright (c) 2020, Stephan Unverwerth * Copyright (c) 2020-2021, Linus Groh * Copyright (c) 2021, David Tuin + * Copyright (c) 2021, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ @@ -43,6 +44,7 @@ public: static ScopePusher function_scope(Parser& parser, FunctionBody& function_body, Vector const& parameters) { ScopePusher scope_pusher(parser, &function_body, true); + scope_pusher.m_function_parameters = parameters; for (auto& parameter : parameters) { parameter.binding.visit( [&](FlyString const& name) { @@ -160,10 +162,46 @@ public: } } + 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(StringView 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; } + void set_contains_access_to_arguments_object() { m_contains_access_to_arguments_object = true; } + ~ScopePusher() { VERIFY(m_is_top_level || m_parent_scope); + if (!m_contains_access_to_arguments_object) { + for (auto& it : m_identifier_and_argument_index_associations) { + for (auto& identifier : it.value) { + if (!has_declaration(identifier.string())) + identifier.set_lexically_bound_function_argument_index(it.key); + } + } + } + for (size_t i = 0; i < m_functions_to_hoist.size(); i++) { auto const& function_declaration = m_functions_to_hoist[i]; if (m_lexical_names.contains(function_declaration.name()) || m_forbidden_var_names.contains(function_declaration.name())) @@ -174,10 +212,20 @@ public: m_parent_scope->m_functions_to_hoist.append(move(m_functions_to_hoist[i])); } + 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; + } + VERIFY(m_parser.m_state.current_scope_pusher == this); m_parser.m_state.current_scope_pusher = m_parent_scope; } + void associate_identifier_with_argument_index(NonnullRefPtr identifier, size_t index) + { + m_identifier_and_argument_index_associations.ensure(index).append(move(identifier)); + } + private: void throw_identifier_declared(FlyString const& name, NonnullRefPtr const& declaration) { @@ -198,6 +246,12 @@ private: HashTable m_forbidden_lexical_names; HashTable m_forbidden_var_names; NonnullRefPtrVector m_functions_to_hoist; + + Optional> m_function_parameters; + HashMap> m_identifier_and_argument_index_associations; + + bool m_contains_access_to_arguments_object { false }; + bool m_contains_direct_call_to_eval { false }; }; class OperatorPrecedenceTable { @@ -629,8 +683,9 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe // for arrow function bodies which are a single expression. // Esprima generates a single "ArrowFunctionExpression" // with a "body" property. - auto return_expression = parse_expression(2); auto return_block = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }); + ScopePusher function_scope = ScopePusher::function_scope(*this, return_block, parameters); + auto return_expression = parse_expression(2); return_block->append({ m_filename, rule_start.position(), position() }, move(return_expression)); if (m_state.strict_mode) return_block->set_strict_mode(); @@ -1460,6 +1515,48 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati syntax_error("Invalid property in object literal", range->start); } }; + if (is(*expression) && m_state.current_scope_pusher) { + auto identifier_instance = static_ptr_cast(expression); + auto function_scope = m_state.current_scope_pusher->last_function_scope(); + auto function_parent_scope = function_scope ? function_scope->parent_scope() : nullptr; + bool has_not_been_declared_as_variable = true; + for (auto scope = m_state.current_scope_pusher; scope != function_parent_scope; scope = scope->parent_scope()) { + if (scope->has_declaration(identifier_instance->string())) { + has_not_been_declared_as_variable = false; + break; + } + } + + if (has_not_been_declared_as_variable) { + if (identifier_instance->string() == "arguments"sv) + m_state.current_scope_pusher->set_contains_access_to_arguments_object(); + } + + if (function_scope && has_not_been_declared_as_variable) { + + auto& parameters = function_scope->function_parameters(); + Optional argument_index; + size_t index = 0; + for (auto& parameter : parameters) { + auto current_index = index++; + if (parameter.is_rest) + break; + if (auto name_ptr = parameter.binding.get_pointer()) { + // Need VM assistance for this, so let's just pretend it's not there. + if (parameter.default_value) + break; + if (identifier_instance->string() == *name_ptr) { + argument_index = current_index; + if (m_state.strict_mode) + break; + } + } + } + if (argument_index.has_value()) + m_state.current_scope_pusher->associate_identifier_with_argument_index(identifier_instance, *argument_index); + } + } + while (match(TokenType::TemplateLiteralStart)) { auto template_literal = parse_template_literal(true); expression = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(template_literal)); @@ -1487,6 +1584,24 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati check_for_invalid_object_property(expression); + if (is(*expression) && m_state.current_scope_pusher) { + auto& callee = static_ptr_cast(expression)->callee(); + if (is(callee)) { + auto& identifier_instance = static_cast(callee); + if (identifier_instance.string() == "eval"sv) { + bool has_not_been_declared_as_variable = true; + for (auto scope = m_state.current_scope_pusher; scope; scope = scope->parent_scope()) { + if (scope->has_declaration(identifier_instance.string())) { + has_not_been_declared_as_variable = false; + break; + } + } + if (has_not_been_declared_as_variable) + m_state.current_scope_pusher->set_contains_direct_call_to_eval(); + } + } + } + if (match(TokenType::Comma) && min_precedence <= 1) { NonnullRefPtrVector expressions; expressions.append(expression); @@ -3646,5 +3761,4 @@ NonnullRefPtr Parser::parse_export_statement(Program& program) return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(entries)); } - } diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp index 3af87c9826..c0f71a5fb6 100644 --- a/Userland/Libraries/LibJS/Runtime/Reference.cpp +++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp @@ -48,6 +48,31 @@ void Reference::put_value(GlobalObject& global_object, Value value) } VERIFY(m_base_type == BaseType::Environment); + + // Note: Optimisation, not from the spec. + if (m_function_argument_index.has_value()) { + // Note: Modifying this binding requires us to sync with the environment. + if (!m_base_environment) { + auto real_reference = global_object.vm().resolve_binding(m_name.as_string(), m_referenced_function_context->lexical_environment); + m_base_environment = real_reference.m_base_environment; + } + + if (!global_object.vm().execution_context_stack().is_empty() && m_referenced_function_context == &global_object.vm().running_execution_context()) { + auto& arguments = m_referenced_function_context->arguments; + auto index = m_function_argument_index.value(); + if (arguments.size() > index) { + arguments[index] = value; + } else { + arguments.ensure_capacity(index + 1); + for (size_t i = arguments.size(); i < index; ++i) + arguments.append(js_undefined()); + arguments.append(value); + } + m_base_environment->set_mutable_binding(global_object, name().as_string(), value, is_strict()); + return; + } + } + VERIFY(m_base_environment); if (m_environment_coordinate.has_value()) static_cast(m_base_environment)->set_mutable_binding_direct(global_object, m_environment_coordinate->index, value, m_strict); @@ -80,6 +105,17 @@ Value Reference::get_value(GlobalObject& global_object) const } VERIFY(m_base_type == BaseType::Environment); + + // Note: Optimisation, not from the spec. + if (m_function_argument_index.has_value()) { + if (!global_object.vm().execution_context_stack().is_empty() && m_referenced_function_context == &global_object.vm().running_execution_context()) + return global_object.vm().argument(m_function_argument_index.value()); + if (!m_base_environment) { + auto real_reference = global_object.vm().resolve_binding(m_name.as_string(), m_referenced_function_context->lexical_environment); + m_base_environment = real_reference.m_base_environment; + } + } + VERIFY(m_base_environment); if (m_environment_coordinate.has_value()) return static_cast(m_base_environment)->get_binding_value_direct(global_object, m_environment_coordinate->index, m_strict); @@ -141,6 +177,12 @@ bool Reference::delete_(GlobalObject& global_object) VERIFY(m_base_type == BaseType::Environment); + // Note: Optimisation, not from the spec. + if (m_function_argument_index.has_value()) { + // This is a direct reference to a function argument. + return false; + } + // c. Return ? base.DeleteBinding(ref.[[ReferencedName]]). return m_base_environment->delete_binding(global_object, m_name.as_string()); } diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h index 8749fe5dff..8d47fbaf09 100644 --- a/Userland/Libraries/LibJS/Runtime/Reference.h +++ b/Userland/Libraries/LibJS/Runtime/Reference.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,28 @@ public: { } + Reference(FlyString referenced_name, size_t function_argument_index, bool strict = false, ExecutionContext* function_context = nullptr) + : m_base_type(BaseType::Environment) + , m_base_environment(nullptr) + , m_name(move(referenced_name)) + , m_strict(strict) + , m_environment_coordinate({}) + , m_function_argument_index(function_argument_index) + , m_referenced_function_context(function_context) + { + } + + Reference(Environment& base, FlyString referenced_name, size_t function_argument_index, bool strict = false, Optional environment_coordinate = {}, ExecutionContext* function_context = nullptr) + : m_base_type(BaseType::Environment) + , m_base_environment(&base) + , m_name(move(referenced_name)) + , m_strict(strict) + , m_environment_coordinate(move(environment_coordinate)) + , m_function_argument_index(function_argument_index) + , m_referenced_function_context(function_context) + { + } + Value base() const { VERIFY(m_base_type == BaseType::Value); @@ -128,12 +151,14 @@ private: BaseType m_base_type { BaseType::Unresolvable }; union { Value m_base_value {}; - Environment* m_base_environment; + mutable Environment* m_base_environment; }; PropertyName m_name; Value m_this_value; bool m_strict { false }; Optional m_environment_coordinate; + Optional m_function_argument_index; + ExecutionContext* m_referenced_function_context { nullptr }; }; }