From 830ea0414cc6e4686514ff7de337787dbb6c3b71 Mon Sep 17 00:00:00 2001 From: davidot Date: Wed, 22 Sep 2021 12:44:56 +0200 Subject: [PATCH] LibJS: Make scoping follow the spec Before this we used an ad-hoc combination of references and 'variables' stored in a hashmap. This worked in most cases but is not spec like. Additionally hoisting, dynamically naming functions and scope analysis was not done properly. This patch fixes all of that by: - Implement BindingInitialization for destructuring assignment. - Implementing a new ScopePusher which tracks the lexical and var scoped declarations. This hoists functions to the top level if no lexical declaration name overlaps. Furthermore we do checking of redeclarations in the ScopePusher now requiring less checks all over the place. - Add methods for parsing the directives and statement lists instead of having that code duplicated in multiple places. This allows declarations to pushed to the appropriate scope more easily. - Remove the non spec way of storing 'variables' in DeclarativeEnvironment and make Reference follow the spec instead of checking both the bindings and 'variables'. - Remove all scoping related things from the Interpreter. And instead use environments as specified by the spec. This also includes fixing that NativeFunctions did not produce a valid FunctionEnvironment which could cause issues with callbacks and eval. All FunctionObjects now have a valid NewFunctionEnvironment implementation. - Remove execute_statements from Interpreter and instead use ASTNode::execute everywhere this simplifies AST.cpp as you no longer need to worry about which method to call. - Make ScopeNodes setup their own environment. This uses four different methods specified by the spec {Block, Function, Eval, Global}DeclarationInstantiation with the annexB extensions. - Implement and use NamedEvaluation where specified. Additionally there are fixes to things exposed by these changes to eval, {for, for-in, for-of} loops and assignment. Finally it also fixes some tests in test-js which where passing before but not now that we have correct behavior :^). --- Userland/Libraries/LibJS/AST.cpp | 1358 ++++++++++++----- Userland/Libraries/LibJS/AST.h | 163 +- .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 43 +- Userland/Libraries/LibJS/Bytecode/Op.cpp | 19 +- Userland/Libraries/LibJS/Interpreter.cpp | 136 +- Userland/Libraries/LibJS/Interpreter.h | 12 - Userland/Libraries/LibJS/Parser.cpp | 746 ++++----- Userland/Libraries/LibJS/Parser.h | 35 +- .../LibJS/Runtime/AbstractOperations.cpp | 262 +++- .../LibJS/Runtime/AbstractOperations.h | 2 + .../Libraries/LibJS/Runtime/BoundFunction.cpp | 4 +- .../Libraries/LibJS/Runtime/BoundFunction.h | 2 +- .../LibJS/Runtime/DeclarativeEnvironment.cpp | 34 +- .../LibJS/Runtime/DeclarativeEnvironment.h | 12 +- .../Runtime/ECMAScriptFunctionObject.cpp | 314 +++- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 3 +- .../Libraries/LibJS/Runtime/Environment.h | 4 - Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 2 + .../LibJS/Runtime/FunctionEnvironment.cpp | 4 +- .../LibJS/Runtime/FunctionEnvironment.h | 2 +- .../Libraries/LibJS/Runtime/FunctionObject.h | 2 +- .../LibJS/Runtime/GlobalEnvironment.cpp | 20 +- .../LibJS/Runtime/GlobalEnvironment.h | 3 - .../LibJS/Runtime/NativeFunction.cpp | 15 +- .../Libraries/LibJS/Runtime/NativeFunction.h | 2 +- Userland/Libraries/LibJS/Runtime/Object.cpp | 28 + Userland/Libraries/LibJS/Runtime/Object.h | 1 + .../LibJS/Runtime/ObjectEnvironment.cpp | 18 - .../LibJS/Runtime/ObjectEnvironment.h | 4 - .../Libraries/LibJS/Runtime/ProxyObject.cpp | 4 +- .../Libraries/LibJS/Runtime/ProxyObject.h | 2 +- .../Libraries/LibJS/Runtime/Reference.cpp | 57 +- Userland/Libraries/LibJS/Runtime/Reference.h | 9 +- Userland/Libraries/LibJS/Runtime/VM.cpp | 633 ++++---- Userland/Libraries/LibJS/Runtime/VM.h | 25 +- .../LibJS/Tests/functions/function-name.js | 36 +- .../LibJS/Tests/loops/for-in-basic.js | 21 +- .../LibJS/Tests/loops/for-of-basic.js | 17 +- .../Tests/operators/delete-local-variable.js | 14 +- .../LibJS/Tests/strict-mode-errors.js | 7 +- .../Libraries/LibJS/Tests/string-escapes.js | 4 +- .../Tests/syntax/destructuring-assignment.js | 5 + .../LibJS/Tests/syntax/switch-as-statement.js | 69 + Userland/Utilities/js.cpp | 17 +- 44 files changed, 2622 insertions(+), 1548 deletions(-) create mode 100644 Userland/Libraries/LibJS/Tests/syntax/switch-as-statement.js diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 2a5d338f80..073b2dacb3 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -81,40 +81,118 @@ static String get_function_name(GlobalObject& global_object, Value value) return value.to_string(global_object); } -Value ScopeNode::execute(Interpreter& interpreter, GlobalObject& global_object) const +Value ScopeNode::evaluate_statements(Interpreter& interpreter, GlobalObject& global_object) const +{ + // FIXME: This should use completions but for now we just use the vm to communicate things. + auto& vm = interpreter.vm(); + Value last_value; + for (auto& node : children()) { + auto value = node.execute(interpreter, global_object); + if (!value.is_empty()) + last_value = value; + if (vm.should_unwind()) { + break; + } + } + return last_value; +} + +Value FunctionBody::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; - return interpreter.execute_statement(global_object, *this); + + // Note: Scoping should have already been setup by whoever is calling this FunctionBody. + auto function_result = evaluate_statements(interpreter, global_object); + + if (interpreter.exception()) + return {}; + + if (interpreter.vm().unwind_until() != ScopeType::Function) + function_result = js_undefined(); + else + interpreter.vm().stop_unwind(); + + return function_result; +} + +// 14.2.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation +Value BlockStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const +{ + InterpreterNodeScope node_scope { interpreter, *this }; + + auto& vm = interpreter.vm(); + Environment* old_environment = vm.running_execution_context().lexical_environment; + ScopeGuard restore_environment = [&] { + vm.running_execution_context().lexical_environment = old_environment; + }; + auto* block_environment = new_declarative_environment(*old_environment); + block_declaration_instantiation(global_object, block_environment); + vm.running_execution_context().lexical_environment = block_environment; + + auto block_value = evaluate_statements(interpreter, global_object); + if (!labels().is_empty() && vm.should_unwind_until(ScopeType::Breakable, labels())) + vm.stop_unwind(); + + if (vm.exception()) + return {}; + + return block_value; } Value Program::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; - return interpreter.execute_statement(global_object, *this, ScopeType::Block); + + VERIFY(interpreter.lexical_environment() && interpreter.lexical_environment()->is_global_environment()); + auto& global_env = static_cast(*interpreter.lexical_environment()); + + TRY_OR_DISCARD(global_declaration_instantiation(interpreter, global_object, global_env)); + + return evaluate_statements(interpreter, global_object); } -Value FunctionDeclaration::execute(Interpreter& interpreter, GlobalObject&) const +Value FunctionDeclaration::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; + + if (m_is_hoisted) { + // Perform special annexB steps see step 3 of: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation + auto function_object = interpreter.vm().running_execution_context().lexical_environment->get_binding_value(global_object, name(), false); + interpreter.vm().running_execution_context().variable_environment->set_mutable_binding(global_object, name(), function_object, false); + VERIFY(!interpreter.exception()); + } + return {}; } -// 15.2.5 Runtime Semantics: InstantiateOrdinaryFunctionExpression, https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression Value FunctionExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; - auto* func_env = interpreter.lexical_environment(); - bool has_identifier = !name().is_empty() && !is_auto_renamed(); + return instantiate_ordinary_function_expression(interpreter, global_object, name()); +} - if (has_identifier) { - func_env = interpreter.heap().allocate(global_object, func_env); - func_env->create_immutable_binding(global_object, name(), false); +// 15.2.5 Runtime Semantics: InstantiateOrdinaryFunctionExpression, https://tc39.es/ecma262/#sec-runtime-semantics-instantiateordinaryfunctionexpression +Value FunctionExpression::instantiate_ordinary_function_expression(Interpreter& interpreter, GlobalObject& global_object, FlyString given_name) const +{ + if (given_name.is_empty()) + given_name = ""; + auto has_own_name = !name().is_empty(); + + auto const& used_name = has_own_name ? name() : given_name; + auto* scope = interpreter.lexical_environment(); + if (has_own_name) { + VERIFY(scope); + scope = new_declarative_environment(*scope); + scope->create_immutable_binding(global_object, name(), false); } - auto closure = ECMAScriptFunctionObject::create(global_object, name(), body(), parameters(), function_length(), func_env, kind(), is_strict_mode(), is_arrow_function()); + auto closure = ECMAScriptFunctionObject::create(global_object, used_name, body(), parameters(), function_length(), scope, kind(), is_strict_mode(), is_arrow_function()); - if (has_identifier) - func_env->initialize_binding(global_object, name(), closure); + // FIXME: 6. Perform SetFunctionName(closure, name). + // FIXME: 7. Perform MakeConstructor(closure). + + if (has_own_name) + scope->initialize_binding(global_object, name(), closure); return closure; } @@ -275,14 +353,14 @@ Value SuperCall::execute(Interpreter& interpreter, GlobalObject& global_object) return {}; // 5. If IsConstructor(func) is false, throw a TypeError exception. - // FIXME: This check is non-conforming. - if (!func || !func->is_function()) { + if (!func || !func->value_of().is_constructor()) { vm.throw_exception(global_object, ErrorType::NotAConstructor, "Super constructor"); return {}; } // 6. Let result be ? Construct(func, argList, newTarget). auto& function = new_target.as_function(); + auto result = vm.construct(static_cast(*func), function, move(arg_list)); if (vm.exception()) return {}; @@ -332,10 +410,10 @@ Value IfStatement::execute(Interpreter& interpreter, GlobalObject& global_object return {}; if (predicate_result.to_boolean()) - return interpreter.execute_statement(global_object, *m_consequent); + return m_consequent->execute(interpreter, global_object); if (m_alternate) - return interpreter.execute_statement(global_object, *m_alternate); + return m_alternate->execute(interpreter, global_object); return js_undefined(); } @@ -368,7 +446,7 @@ Value WithStatement::execute(Interpreter& interpreter, GlobalObject& global_obje interpreter.vm().running_execution_context().lexical_environment = new_environment; // 6. Let C be the result of evaluating Statement. - auto result = interpreter.execute_statement(global_object, m_body).value_or(js_undefined()); + auto result = m_body->execute(interpreter, global_object).value_or(js_undefined()); // 7. Set the running execution context's LexicalEnvironment to oldEnv. interpreter.vm().running_execution_context().lexical_environment = old_environment; @@ -391,7 +469,7 @@ Value WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_obj return {}; if (!test_result.to_boolean()) break; - last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); + last_value = m_body->execute(interpreter, global_object).value_or(last_value); if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { @@ -417,7 +495,7 @@ Value DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& global_o for (;;) { if (interpreter.exception()) return {}; - last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); + last_value = m_body->execute(interpreter, global_object).value_or(last_value); if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { @@ -444,129 +522,313 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec { InterpreterNodeScope node_scope { interpreter, *this }; - RefPtr wrapper; + // Note we don't always set a new environment but to use RAII we must do this here. + auto* old_environment = interpreter.lexical_environment(); + ScopeGuard restore_old_environment = [&] { + interpreter.vm().running_execution_context().lexical_environment = old_environment; + }; - if (m_init && is(*m_init) && static_cast(*m_init).declaration_kind() != DeclarationKind::Var) { - wrapper = create_ast_node(source_range()); - NonnullRefPtrVector decls; - decls.append(*static_cast(m_init.ptr())); - wrapper->add_variables(decls); - interpreter.enter_scope(*wrapper, ScopeType::Block, global_object); - } + Vector let_declarations; - auto wrapper_cleanup = ScopeGuard([&] { - if (wrapper) - interpreter.exit_scope(*wrapper); - }); - - auto last_value = js_undefined(); if (m_init) { + if (is(*m_init) && static_cast(*m_init).declaration_kind() != DeclarationKind::Var) { + auto* loop_environment = new_declarative_environment(*old_environment); + auto& declaration = static_cast(*m_init); + declaration.for_each_bound_name([&](auto const& name) { + if (declaration.declaration_kind() == DeclarationKind::Const) { + loop_environment->create_immutable_binding(global_object, name, true); + } else { + loop_environment->create_mutable_binding(global_object, name, false); + let_declarations.append(name); + } + return IterationDecision::Continue; + }); + + interpreter.vm().running_execution_context().lexical_environment = loop_environment; + } + m_init->execute(interpreter, global_object); if (interpreter.exception()) return {}; } - if (m_test) { - while (true) { - auto test_result = m_test->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; - if (!test_result.to_boolean()) + auto last_value = js_undefined(); + + // 14.7.4.4 CreatePerIterationEnvironment ( perIterationBindings ), https://tc39.es/ecma262/#sec-createperiterationenvironment + auto create_per_iteration_environment = [&]() -> ThrowCompletionOr { + if (let_declarations.is_empty()) + return {}; + + auto* last_iteration_env = interpreter.lexical_environment(); + auto* outer = last_iteration_env->outer_environment(); + VERIFY(outer); + auto* this_iteration_env = new_declarative_environment(*outer); + for (auto& name : let_declarations) { + this_iteration_env->create_mutable_binding(global_object, name, false); + auto last_value = last_iteration_env->get_binding_value(global_object, name, true); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + VERIFY(!last_value.is_empty()); + this_iteration_env->initialize_binding(global_object, name, last_value); + } + interpreter.vm().running_execution_context().lexical_environment = this_iteration_env; + + return {}; + }; + + TRY_OR_DISCARD(create_per_iteration_environment()); + + auto test_empty_or_true = [&] { + if (!m_test) + return true; + + auto test_result = m_test->execute(interpreter, global_object); + if (interpreter.exception()) + return false; + return test_result.to_boolean(); + }; + + while (true) { + if (!test_empty_or_true()) + break; + + last_value = m_body->execute(interpreter, global_object).value_or(last_value); + if (interpreter.exception()) + return {}; + if (interpreter.vm().should_unwind()) { + if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { + interpreter.vm().stop_unwind(); + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { + interpreter.vm().stop_unwind(); break; - last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); - if (interpreter.exception()) - return {}; - if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { - interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { - interpreter.vm().stop_unwind(); - break; - } else { - return last_value; - } - } - if (m_update) { - m_update->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; + } else { + return last_value; } } - } else { - while (true) { - last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); + + TRY_OR_DISCARD(create_per_iteration_environment()); + + if (m_update) { + m_update->execute(interpreter, global_object); if (interpreter.exception()) return {}; - if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { - interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { - interpreter.vm().stop_unwind(); - break; - } else { - return last_value; - } - } - if (m_update) { - m_update->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; - } } } - + if (interpreter.exception()) + return {}; return last_value; } -static Variant, NonnullRefPtr> variable_from_for_declaration(Interpreter& interpreter, GlobalObject& global_object, ASTNode const& node, RefPtr wrapper) -{ - if (is(node)) { - auto& variable_declaration = static_cast(node); - VERIFY(!variable_declaration.declarations().is_empty()); - if (variable_declaration.declaration_kind() != DeclarationKind::Var) { - wrapper = create_ast_node(node.source_range()); - interpreter.enter_scope(*wrapper, ScopeType::Block, global_object); +struct ForInOfHeadState { + explicit ForInOfHeadState(Variant, NonnullRefPtr> lhs) + { + lhs.visit( + [&](NonnullRefPtr& ast_node) { + expression_lhs = ast_node.ptr(); + }, + [&](NonnullRefPtr& pattern) { + pattern_lhs = pattern.ptr(); + destructuring = true; + lhs_kind = Assignment; + }); + } + + ASTNode* expression_lhs = nullptr; + BindingPattern* pattern_lhs = nullptr; + enum LhsKind { + Assignment, + VarBinding, + LexicalBinding + }; + LhsKind lhs_kind = Assignment; + bool destructuring = false; + + Value rhs_value; + + // 14.7.5.7 ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ), https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset + // Note: This is only steps 6.g through 6.j of the method because we currently implement for-in without an iterator so to prevent duplicated code we do this part here. + ThrowCompletionOr execute_head(Interpreter& interpreter, GlobalObject& global_object, Value next_value) const + { + VERIFY(!next_value.is_empty()); + + Optional lhs_reference; + Environment* iteration_environment = nullptr; + + // g. If lhsKind is either assignment or varBinding, then + if (lhs_kind == Assignment || lhs_kind == VarBinding) { + if (!destructuring) { + VERIFY(expression_lhs); + if (is(*expression_lhs)) { + auto& declaration = static_cast(*expression_lhs); + VERIFY(declaration.declarations().first().target().has>()); + lhs_reference = declaration.declarations().first().target().get>()->to_reference(interpreter, global_object); + } else { + VERIFY(is(*expression_lhs) || is(*expression_lhs)); + auto& expression = static_cast(*expression_lhs); + lhs_reference = expression.to_reference(interpreter, global_object); + } + } } - variable_declaration.execute(interpreter, global_object); - return variable_declaration.declarations().first().target(); + // h. Else, + else { + VERIFY(expression_lhs && is(*expression_lhs)); + iteration_environment = new_declarative_environment(*interpreter.lexical_environment()); + auto& for_declaration = static_cast(*expression_lhs); + for_declaration.for_each_bound_name([&](auto const& name) { + if (for_declaration.declaration_kind() == DeclarationKind::Const) + iteration_environment->create_immutable_binding(global_object, name, false); + else + iteration_environment->create_mutable_binding(global_object, name, true); + }); + interpreter.vm().running_execution_context().lexical_environment = iteration_environment; + + if (!destructuring) { + VERIFY(for_declaration.declarations().first().target().has>()); + lhs_reference = interpreter.vm().resolve_binding(for_declaration.declarations().first().target().get>()->string()); + } + } + + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + // i. If destructuring is false, then + if (!destructuring) { + VERIFY(lhs_reference.has_value()); + if (lhs_kind == LexicalBinding) + lhs_reference->initialize_referenced_binding(global_object, next_value); + else + lhs_reference->put_value(global_object, next_value); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + return {}; + } + + // j. Else, + if (lhs_kind == Assignment) { + VERIFY(pattern_lhs); + return interpreter.vm().destructuring_assignment_evaluation(*pattern_lhs, next_value, global_object); + } + VERIFY(expression_lhs && is(*expression_lhs)); + auto& for_declaration = static_cast(*expression_lhs); + auto& binding_pattern = for_declaration.declarations().first().target().get>(); + VERIFY(lhs_kind == VarBinding || iteration_environment); + + // At this point iteration_environment is undefined if lhs_kind == VarBinding which means this does both + // branch j.ii and j.iii because ForBindingInitialization is just a forwarding call to BindingInitialization. + return interpreter.vm().binding_initialization(binding_pattern, next_value, iteration_environment, global_object); + } +}; + +// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +// 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation +// This method combines ForInOfLoopEvaluation and ForIn/OfHeadEvaluation for similar reason as ForIn/OfBodyEvaluation, to prevent code duplication. +// For the same reason we also skip step 6 and 7 of ForIn/OfHeadEvaluation as this is done by the appropriate for loop type. +static ThrowCompletionOr for_in_of_head_execute(Interpreter& interpreter, GlobalObject& global_object, Variant, NonnullRefPtr> lhs, Expression const& rhs) +{ + ForInOfHeadState state(lhs); + if (auto* ast_ptr = lhs.get_pointer>(); ast_ptr && is(*(*ast_ptr))) { + // Runtime Semantics: ForInOfLoopEvaluation, for any of: + // ForInOfStatement : for ( var ForBinding in Expression ) Statement + // ForInOfStatement : for ( ForDeclaration in Expression ) Statement + // ForInOfStatement : for ( var ForBinding of AssignmentExpression ) Statement + // ForInOfStatement : for ( ForDeclaration of AssignmentExpression ) Statement + + // 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation + Environment* new_environment = nullptr; + + auto& variable_declaration = static_cast(*(*ast_ptr)); + VERIFY(variable_declaration.declarations().size() == 1); + state.destructuring = variable_declaration.declarations().first().target().has>(); + if (variable_declaration.declaration_kind() == DeclarationKind::Var) { + state.lhs_kind = ForInOfHeadState::VarBinding; + auto& variable = variable_declaration.declarations().first(); + // B.3.5 Initializers in ForIn Statement Heads, https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads + if (variable.init()) { + VERIFY(variable.target().has>()); + auto& binding_id = variable.target().get>()->string(); + auto reference = interpreter.vm().resolve_binding(binding_id); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + auto result = TRY(interpreter.vm().named_evaluation_if_anonymous_function(global_object, *variable.init(), binding_id)); + reference.put_value(global_object, result); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + } + } else { + state.lhs_kind = ForInOfHeadState::LexicalBinding; + new_environment = new_declarative_environment(*interpreter.lexical_environment()); + variable_declaration.for_each_bound_name([&](auto const& name) { + new_environment->create_mutable_binding(global_object, name, false); + }); + } + + if (new_environment) { + // 2.d Set the running execution context's LexicalEnvironment to newEnv. + TemporaryChange scope_change(interpreter.vm().running_execution_context().lexical_environment, new_environment); + + // 3. Let exprRef be the result of evaluating expr. + // 5. Let exprValue be ? GetValue(exprRef). + state.rhs_value = rhs.execute(interpreter, global_object); + + // Note that since a reference stores it's environment it doesn't matter we only reset + // this after step 5. (Also we have no way of separating these steps at this point) + // 4. Set the running execution context's LexicalEnvironment to oldEnv. + } else { + // 3. Let exprRef be the result of evaluating expr. + // 5. Let exprValue be ? GetValue(exprRef). + state.rhs_value = rhs.execute(interpreter, global_object); + } + + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + return state; } - if (is(node)) { - return NonnullRefPtr(static_cast(node)); - } + // Runtime Semantics: ForInOfLoopEvaluation, for any of: + // ForInOfStatement : for ( LeftHandSideExpression in Expression ) Statement + // ForInOfStatement : for ( LeftHandSideExpression of AssignmentExpression ) Statement - VERIFY_NOT_REACHED(); + // 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation + + // We can skip step 1, 2 and 4 here (on top of already skipping step 6 and 7). + // 3. Let exprRef be the result of evaluating expr. + // 5. Let exprValue be ? GetValue(exprRef). + state.rhs_value = rhs.execute(interpreter, global_object); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + return state; } Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; - bool has_declaration = is(*m_lhs); - if (!has_declaration && !is(*m_lhs)) { - // FIXME: Implement "for (foo.bar in baz)", "for (foo[0] in bar)" - VERIFY_NOT_REACHED(); - } - RefPtr wrapper; - auto target = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper); - auto wrapper_cleanup = ScopeGuard([&] { - if (wrapper) - interpreter.exit_scope(*wrapper); - }); - auto last_value = js_undefined(); - auto rhs_result = m_rhs->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; + auto for_in_head_state = TRY_OR_DISCARD(for_in_of_head_execute(interpreter, global_object, m_lhs, *m_rhs)); + + auto rhs_result = for_in_head_state.rhs_value; + + // 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation if (rhs_result.is_nullish()) - return {}; + return js_undefined(); + auto* object = rhs_result.to_object(global_object); + VERIFY(object); + + // 14.7.5.7 ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ), https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset + Environment* old_environment = interpreter.lexical_environment(); + auto restore_scope = ScopeGuard([&] { + interpreter.vm().running_execution_context().lexical_environment = old_environment; + }); + + auto last_value = js_undefined(); while (object) { auto property_names = object->enumerable_own_property_names(Object::PropertyKind::Key); for (auto& value : property_names) { - interpreter.vm().assign(target, value, global_object, has_declaration); - if (interpreter.exception()) - return {}; - last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); + TRY_OR_DISCARD(for_in_head_state.execute_head(interpreter, global_object, value)); + last_value = m_body->execute(interpreter, global_object).value_or(last_value); + interpreter.vm().running_execution_context().lexical_environment = old_environment; if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { @@ -589,25 +851,24 @@ Value ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_obj { InterpreterNodeScope node_scope { interpreter, *this }; - bool has_declaration = is(*m_lhs); - if (!has_declaration && !is(*m_lhs)) { - // FIXME: Implement "for (foo.bar of baz)", "for (foo[0] of bar)" - VERIFY_NOT_REACHED(); - } - RefPtr wrapper; - auto target = variable_from_for_declaration(interpreter, global_object, m_lhs, wrapper); - auto wrapper_cleanup = ScopeGuard([&] { - if (wrapper) - interpreter.exit_scope(*wrapper); - }); + auto for_of_head_state = TRY_OR_DISCARD(for_in_of_head_execute(interpreter, global_object, m_lhs, m_rhs)); + + auto rhs_result = for_of_head_state.rhs_value; auto last_value = js_undefined(); - auto rhs_result = m_rhs->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; + + // 14.7.5.7 ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ), https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset + // We use get_iterator_values which behaves like ForIn/OfBodyEvaluation with iteratorKind iterate. + Environment* old_environment = interpreter.lexical_environment(); + auto restore_scope = ScopeGuard([&] { + interpreter.vm().running_execution_context().lexical_environment = old_environment; + }); get_iterator_values(global_object, rhs_result, [&](Value value) { - interpreter.vm().assign(target, value, global_object, has_declaration); - last_value = interpreter.execute_statement(global_object, *m_body).value_or(last_value); + auto result = for_of_head_state.execute_head(interpreter, global_object, value); + if (result.is_error()) + return IterationDecision::Break; + last_value = m_body->execute(interpreter, global_object).value_or(last_value); + interpreter.vm().running_execution_context().lexical_environment = old_environment; if (interpreter.exception()) return IterationDecision::Break; if (interpreter.vm().should_unwind()) { @@ -777,21 +1038,45 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& return TRY_OR_DISCARD(make_super_property_reference(global_object, actual_this, property_key, strict)); } - auto object_value = m_object->execute(interpreter, global_object); + auto base_reference = m_object->to_reference(interpreter, global_object); if (interpreter.exception()) return {}; + Value base_value; + + if (base_reference.is_valid_reference()) + base_value = base_reference.get_value(global_object); + else + base_value = m_object->execute(interpreter, global_object); + + if (interpreter.exception()) + return {}; + VERIFY(!base_value.is_empty()); + // From here on equivalent to // 13.3.4 EvaluatePropertyAccessWithIdentifierKey ( baseValue, identifierName, strict ), https://tc39.es/ecma262/#sec-evaluate-property-access-with-identifier-key + PropertyName property_name; + if (is_computed()) { + // Weird order which I can't quite find from the specs. + auto value = m_property->execute(interpreter, global_object); + if (interpreter.exception()) + return Reference {}; - object_value = TRY_OR_DISCARD(require_object_coercible(global_object, object_value)); + TRY_OR_DISCARD(require_object_coercible(global_object, base_value)); - auto property_name = computed_property_name(interpreter, global_object); + VERIFY(!value.is_empty()); + property_name = PropertyName::from_value(global_object, value); + if (interpreter.exception()) + return Reference {}; + } else { + property_name = verify_cast(*m_property).string(); + TRY_OR_DISCARD(require_object_coercible(global_object, base_value)); + } if (!property_name.is_valid()) return Reference {}; auto strict = interpreter.vm().in_strict_mode(); - return Reference { object_value, move(property_name), {}, strict }; + return Reference { base_value, move(property_name), {}, strict }; } Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -809,14 +1094,17 @@ Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_ob Value lhs_result; if (m_op == UnaryOp::Typeof && is(*m_lhs)) { auto reference = m_lhs->to_reference(interpreter, global_object); - if (interpreter.exception()) { + if (interpreter.exception()) return {}; - } + if (reference.is_unresolvable()) { lhs_result = js_undefined(); } else { - lhs_result = reference.get_value(global_object, false); + lhs_result = reference.get_value(global_object); + if (interpreter.exception()) + return {}; } + VERIFY(!lhs_result.is_empty()); } else { lhs_result = m_lhs->execute(interpreter, global_object); if (interpreter.exception()) @@ -865,12 +1153,51 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob { InterpreterNodeScope node_scope { interpreter, *this }; - auto& vm = interpreter.vm(); - Value class_constructor_value = m_constructor->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; + // FIXME: Set value.[[SourceText]] to the source text matched by ClassExpression. + return TRY_OR_DISCARD(class_definition_evaluation(interpreter, global_object, m_name, m_name.is_null() ? "" : m_name)); +} - update_function_name(class_constructor_value, m_name); +Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_object) const +{ + InterpreterNodeScope node_scope { interpreter, *this }; + + auto name = m_class_expression->name(); + VERIFY(!name.is_empty()); + auto class_constructor = TRY_OR_DISCARD(m_class_expression->class_definition_evaluation(interpreter, global_object, name, name)); + + if (interpreter.lexical_environment()) { + interpreter.lexical_environment()->initialize_binding(global_object, name, class_constructor); + } else { + auto reference = interpreter.vm().resolve_binding(name); + + reference.put_value(global_object, class_constructor); + if (interpreter.exception()) + return {}; + } + + return {}; +} + +// 15.7.14 Runtime Semantics: ClassDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation +ThrowCompletionOr ClassExpression::class_definition_evaluation(Interpreter& interpreter, GlobalObject& global_object, FlyString const& binding_name, FlyString const& class_name) const +{ + auto& vm = interpreter.vm(); + auto* environment = vm.lexical_environment(); + VERIFY(environment); + auto* class_scope = new_declarative_environment(*environment); + if (!binding_name.is_null()) + class_scope->create_immutable_binding(global_object, binding_name, true); + + ArmedScopeGuard restore_environment = [&] { + vm.running_execution_context().lexical_environment = environment; + }; + vm.running_execution_context().lexical_environment = class_scope; + + Value class_constructor_value = m_constructor->execute(interpreter, global_object); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + update_function_name(class_constructor_value, class_name); VERIFY(class_constructor_value.is_function() && is(class_constructor_value.as_function())); auto* class_constructor = static_cast(&class_constructor_value.as_function()); @@ -878,59 +1205,58 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob Value super_constructor = js_undefined(); if (!m_super_class.is_null()) { super_constructor = m_super_class->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; - if (!super_constructor.is_function() && !super_constructor.is_null()) { - interpreter.vm().throw_exception(global_object, ErrorType::ClassExtendsValueNotAConstructorOrNull, super_constructor.to_string_without_side_effects()); - return {}; - } + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + if (!super_constructor.is_function() && !super_constructor.is_null()) + return interpreter.vm().throw_completion(global_object, ErrorType::ClassExtendsValueNotAConstructorOrNull, super_constructor.to_string_without_side_effects()); + class_constructor->set_constructor_kind(ECMAScriptFunctionObject::ConstructorKind::Derived); Object* super_constructor_prototype = nullptr; if (!super_constructor.is_null()) { auto super_constructor_prototype_value = super_constructor.as_object().get(vm.names.prototype); - if (interpreter.exception()) - return {}; - if (!super_constructor_prototype_value.is_object() && !super_constructor_prototype_value.is_null()) { - interpreter.vm().throw_exception(global_object, ErrorType::ClassExtendsValueInvalidPrototype, super_constructor_prototype_value.to_string_without_side_effects()); - return {}; - } + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + if (!super_constructor_prototype_value.is_object() && !super_constructor_prototype_value.is_null()) + return interpreter.vm().throw_completion(global_object, ErrorType::ClassExtendsValueInvalidPrototype, super_constructor_prototype_value.to_string_without_side_effects()); + if (super_constructor_prototype_value.is_object()) super_constructor_prototype = &super_constructor_prototype_value.as_object(); } auto* prototype = Object::create(global_object, super_constructor_prototype); prototype->define_direct_property(vm.names.constructor, class_constructor, 0); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); class_constructor->define_direct_property(vm.names.prototype, prototype, Attribute::Writable); - if (interpreter.exception()) - return {}; - TRY_OR_DISCARD(class_constructor->internal_set_prototype_of(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object())); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + TRY(class_constructor->internal_set_prototype_of(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object())); } auto class_prototype = class_constructor->get(vm.names.prototype); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + if (!class_prototype.is_object()) + return interpreter.vm().throw_completion(global_object, ErrorType::NotAnObject, "Class prototype"); - if (!class_prototype.is_object()) { - interpreter.vm().throw_exception(global_object, ErrorType::NotAnObject, "Class prototype"); - return {}; - } for (auto const& method : m_methods) { auto method_value = method.execute(interpreter, global_object); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); auto& method_function = static_cast(method_value.as_function()); auto key = method.key().execute(interpreter, global_object); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); auto property_key = key.to_property_key(global_object); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); auto& target = method.is_static() ? *class_constructor : class_prototype.as_object(); method_function.set_home_object(&target); @@ -950,18 +1276,18 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob default: VERIFY_NOT_REACHED(); } - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); } for (auto& field : m_fields) { auto key = field.key().execute(interpreter, global_object); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); auto property_key = key.to_property_key(global_object); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); ECMAScriptFunctionObject* initializer = nullptr; if (field.initializer()) { @@ -975,30 +1301,22 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob if (field.is_static()) { Value field_value = js_undefined(); if (initializer) - field_value = TRY_OR_DISCARD(interpreter.vm().call(*initializer, class_constructor_value)); + field_value = TRY(interpreter.vm().call(*initializer, class_constructor_value)); class_constructor->create_data_property_or_throw(property_key, field_value); - if (interpreter.exception()) - return {}; + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); } else { class_constructor->add_field(property_key, initializer); } } - return class_constructor; -} + vm.running_execution_context().lexical_environment = environment; + restore_environment.disarm(); + if (!binding_name.is_null()) + class_scope->initialize_binding(global_object, binding_name, class_constructor); -Value ClassDeclaration::execute(Interpreter& interpreter, GlobalObject& global_object) const -{ - InterpreterNodeScope node_scope { interpreter, *this }; - - Value class_constructor = m_class_expression->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; - - interpreter.lexical_environment()->put_into_environment(m_class_expression->name(), { class_constructor, DeclarationKind::Let }); - - return {}; + return Value(class_constructor); } static void print_indent(int indent) @@ -1015,12 +1333,27 @@ void ASTNode::dump(int indent) const void ScopeNode::dump(int indent) const { ASTNode::dump(indent); - if (!m_variables.is_empty()) { + if (!m_lexical_declarations.is_empty()) { print_indent(indent + 1); - outln("(Variables)"); - for (auto& variable : m_variables) - variable.dump(indent + 2); + outln("(Lexical declarations)"); + for (auto& declaration : m_lexical_declarations) + declaration.dump(indent + 2); } + + if (!m_var_declarations.is_empty()) { + print_indent(indent + 1); + outln("(Variable declarations)"); + for (auto& declaration : m_var_declarations) + declaration.dump(indent + 2); + } + + if (!m_functions_hoistable_with_annexB_extension.is_empty()) { + print_indent(indent + 1); + outln("(Hoisted functions via annexB extension)"); + for (auto& declaration : m_functions_hoistable_with_annexB_extension) + declaration.dump(indent + 2); + } + if (!m_children.is_empty()) { print_indent(indent + 1); outln("(Children)"); @@ -1192,6 +1525,12 @@ void ClassDeclaration::dump(int indent) const m_class_expression->dump(indent + 1); } +void ClassDeclaration::for_each_bound_name(IteratorOrVoidFunction callback) const +{ + if (!m_class_expression->name().is_empty()) + callback(m_class_expression->name()); +} + void ClassExpression::dump(int indent) const { print_indent(indent); @@ -1302,6 +1641,17 @@ void NullLiteral::dump(int indent) const outln("null"); } +bool BindingPattern::contains_expression() const +{ + for (auto& entry : entries) { + if (entry.initializer) + return true; + if (auto binding_ptr = entry.alias.get_pointer>(); binding_ptr && (*binding_ptr)->contains_expression()) + return true; + } + return false; +} + void BindingPattern::dump(int indent) const { print_indent(indent); @@ -1379,6 +1729,12 @@ void FunctionDeclaration::dump(int indent) const FunctionNode::dump(indent, class_name()); } +void FunctionDeclaration::for_each_bound_name(IteratorOrVoidFunction callback) const +{ + if (!name().is_empty()) + callback(name()); +} + void FunctionExpression::dump(int indent) const { FunctionNode::dump(indent, class_name()); @@ -1466,7 +1822,7 @@ void ForInStatement::dump(int indent) const print_indent(indent); outln("ForIn"); - lhs().dump(indent + 1); + lhs().visit([&](auto& lhs) { lhs->dump(indent + 1); }); rhs().dump(indent + 1); body().dump(indent + 1); } @@ -1477,7 +1833,7 @@ void ForOfStatement::dump(int indent) const print_indent(indent); outln("ForOf"); - lhs().dump(indent + 1); + lhs().visit([&](auto& lhs) { lhs->dump(indent + 1); }); rhs().dump(indent + 1); body().dump(indent + 1); } @@ -1486,14 +1842,11 @@ Value Identifier::execute(Interpreter& interpreter, GlobalObject& global_object) { InterpreterNodeScope node_scope { interpreter, *this }; - auto value = interpreter.vm().get_variable(string(), global_object); + auto reference = to_reference(interpreter, global_object); if (interpreter.exception()) return {}; - if (value.is_empty()) { - interpreter.vm().throw_exception(global_object, ErrorType::UnknownIdentifier, string()); - return {}; - } - return value; + + return reference.get_value(global_object); } void Identifier::dump(int indent) const @@ -1525,138 +1878,151 @@ void ThisExpression::dump(int indent) const ASTNode::dump(indent); } +// 13.15.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; -#define EXECUTE_LHS() \ - do { \ - if (auto* ptr = m_lhs.get_pointer>()) { \ - lhs_result = (*ptr)->execute(interpreter, global_object); \ - if (interpreter.exception()) \ - return {}; \ - } \ - } while (0) + if (m_op == AssignmentOp::Assignment) { + // AssignmentExpression : LeftHandSideExpression = AssignmentExpression + return m_lhs.visit( + [&](NonnullRefPtr& lhs) -> JS::Value { + auto reference = lhs->to_reference(interpreter, global_object); + if (interpreter.exception()) + return {}; -#define EXECUTE_LHS_AND_RHS() \ - do { \ - EXECUTE_LHS(); \ - rhs_result = m_rhs->execute(interpreter, global_object); \ - if (interpreter.exception()) \ - return {}; \ - } while (0) + Value rhs_result; + if (lhs->is_identifier()) { + auto& identifier_name = static_cast(*lhs).string(); + rhs_result = TRY_OR_DISCARD(interpreter.vm().named_evaluation_if_anonymous_function(global_object, m_rhs, identifier_name)); + } else { + rhs_result = m_rhs->execute(interpreter, global_object); + } + if (interpreter.exception()) + return {}; - Value lhs_result; - Value rhs_result; - switch (m_op) { - case AssignmentOp::Assignment: - break; - case AssignmentOp::AdditionAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = add(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::SubtractionAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = sub(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::MultiplicationAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = mul(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::DivisionAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = div(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::ModuloAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = mod(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::ExponentiationAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = exp(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::BitwiseAndAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = bitwise_and(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::BitwiseOrAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = bitwise_or(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::BitwiseXorAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = bitwise_xor(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::LeftShiftAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = left_shift(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::RightShiftAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = right_shift(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::UnsignedRightShiftAssignment: - EXECUTE_LHS_AND_RHS(); - rhs_result = unsigned_right_shift(global_object, lhs_result, rhs_result); - break; - case AssignmentOp::AndAssignment: - EXECUTE_LHS(); - if (!lhs_result.to_boolean()) - return lhs_result; - rhs_result = m_rhs->execute(interpreter, global_object); - break; - case AssignmentOp::OrAssignment: - EXECUTE_LHS(); - if (lhs_result.to_boolean()) - return lhs_result; - rhs_result = m_rhs->execute(interpreter, global_object); - break; - case AssignmentOp::NullishAssignment: - EXECUTE_LHS(); - if (!lhs_result.is_nullish()) - return lhs_result; - rhs_result = m_rhs->execute(interpreter, global_object); - break; + reference.put_value(global_object, rhs_result); + if (interpreter.exception()) + return {}; + + return rhs_result; + }, + [&](NonnullRefPtr& pattern) -> JS::Value { + Value rhs_result = m_rhs->execute(interpreter, global_object); + if (interpreter.exception()) + return {}; + + TRY_OR_DISCARD(interpreter.vm().destructuring_assignment_evaluation(pattern, rhs_result, global_object)); + + return rhs_result; + }); } + VERIFY(m_lhs.has>()); + + auto& lhs_expression = *m_lhs.get>(); + auto reference = lhs_expression.to_reference(interpreter, global_object); if (interpreter.exception()) return {}; - return m_lhs.visit( - [&](NonnullRefPtr& lhs) -> JS::Value { - auto reference = lhs->to_reference(interpreter, global_object); - if (interpreter.exception()) - return {}; + auto lhs_result = reference.get_value(global_object); + if (interpreter.exception()) + return {}; - if (m_op == AssignmentOp::Assignment) { - rhs_result = m_rhs->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; - } - - if (reference.is_unresolvable()) { - interpreter.vm().throw_exception(global_object, ErrorType::InvalidLeftHandAssignment); - return {}; - } - - reference.put_value(global_object, rhs_result); - if (interpreter.exception()) - return {}; - - return rhs_result; - }, - [&](NonnullRefPtr& pattern) -> JS::Value { - VERIFY(m_op == AssignmentOp::Assignment); + // AssignmentExpression : LeftHandSideExpression {&&=, ||=, ??=} AssignmentExpression + if (m_op == AssignmentOp::AndAssignment || m_op == AssignmentOp::OrAssignment || m_op == AssignmentOp::NullishAssignment) { + switch (m_op) { + case AssignmentOp::AndAssignment: + if (!lhs_result.to_boolean()) + return lhs_result; + break; + case AssignmentOp::OrAssignment: + if (lhs_result.to_boolean()) + return lhs_result; + break; + case AssignmentOp::NullishAssignment: + if (!lhs_result.is_nullish()) + return lhs_result; + break; + default: + VERIFY_NOT_REACHED(); + } + Value rhs_result; + if (lhs_expression.is_identifier()) { + auto& identifier_name = static_cast(lhs_expression).string(); + rhs_result = TRY_OR_DISCARD(interpreter.vm().named_evaluation_if_anonymous_function(global_object, m_rhs, identifier_name)); + } else { rhs_result = m_rhs->execute(interpreter, global_object); + if (interpreter.exception()) return {}; + } - interpreter.vm().assign(pattern, rhs_result, global_object); - if (interpreter.exception()) - return {}; + reference.put_value(global_object, rhs_result); + if (interpreter.exception()) + return {}; - return rhs_result; - }); + return rhs_result; + } + + // AssignmentExpression : LeftHandSideExpression AssignmentOperator AssignmentExpression + auto rhs_result = m_rhs->execute(interpreter, global_object); + if (interpreter.exception()) + return {}; + + switch (m_op) { + case AssignmentOp::AdditionAssignment: + rhs_result = add(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::SubtractionAssignment: + rhs_result = sub(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::MultiplicationAssignment: + rhs_result = mul(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::DivisionAssignment: + rhs_result = div(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::ModuloAssignment: + rhs_result = mod(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::ExponentiationAssignment: + rhs_result = exp(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::BitwiseAndAssignment: + rhs_result = bitwise_and(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::BitwiseOrAssignment: + rhs_result = bitwise_or(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::BitwiseXorAssignment: + rhs_result = bitwise_xor(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::LeftShiftAssignment: + rhs_result = left_shift(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::RightShiftAssignment: + rhs_result = right_shift(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::UnsignedRightShiftAssignment: + rhs_result = unsigned_right_shift(global_object, lhs_result, rhs_result); + break; + case AssignmentOp::Assignment: + case AssignmentOp::AndAssignment: + case AssignmentOp::OrAssignment: + case AssignmentOp::NullishAssignment: + VERIFY_NOT_REACHED(); + } + + if (interpreter.exception()) + return {}; + + reference.put_value(global_object, rhs_result); + + if (interpreter.exception()) + return {}; + + return rhs_result; } Value UpdateExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -1789,19 +2155,42 @@ Value VariableDeclaration::execute(Interpreter& interpreter, GlobalObject& globa for (auto& declarator : m_declarations) { if (auto* init = declarator.init()) { - auto initializer_result = init->execute(interpreter, global_object); - if (interpreter.exception()) - return {}; declarator.target().visit( [&](NonnullRefPtr const& id) { - auto variable_name = id->string(); - if (is(*init)) - update_function_name(initializer_result, variable_name); - interpreter.vm().set_variable(variable_name, initializer_result, global_object, true); + auto reference = id->to_reference(interpreter, global_object); + if (interpreter.exception()) + return; + auto initializer_result_or_error = interpreter.vm().named_evaluation_if_anonymous_function(global_object, *init, id->string()); + if (initializer_result_or_error.is_error()) + return; + auto initializer_result = initializer_result_or_error.release_value(); + VERIFY(!initializer_result.is_empty()); + + if (m_declaration_kind == DeclarationKind::Var) + reference.put_value(global_object, initializer_result); + else + reference.initialize_referenced_binding(global_object, initializer_result); }, [&](NonnullRefPtr const& pattern) { - interpreter.vm().assign(pattern, initializer_result, global_object, true); + auto initializer_result = init->execute(interpreter, global_object); + if (interpreter.exception()) + return; + + Environment* environment = m_declaration_kind == DeclarationKind::Var ? nullptr : interpreter.lexical_environment(); + + // FIXME: I want to use TRY_OR_DISCARD here but can't return... + auto result = interpreter.vm().binding_initialization(pattern, initializer_result, environment, global_object); + (void)result; }); + if (interpreter.exception()) + return {}; + } else if (m_declaration_kind != DeclarationKind::Var) { + VERIFY(declarator.target().has>()); + auto& identifier = declarator.target().get>(); + auto reference = identifier->to_reference(interpreter, global_object); + reference.initialize_referenced_binding(global_object, js_undefined()); + if (interpreter.exception()) + return {}; } } return {}; @@ -1815,6 +2204,21 @@ Value VariableDeclarator::execute(Interpreter& interpreter, GlobalObject&) const VERIFY_NOT_REACHED(); } +void VariableDeclaration::for_each_bound_name(IteratorOrVoidFunction callback) const +{ + for (auto& entry : declarations()) { + entry.target().template visit( + [&](const NonnullRefPtr& id) { + callback(id->string()); + }, + [&](const NonnullRefPtr& binding) { + binding->for_each_bound_name([&](const auto& name) { + callback(name); + }); + }); + } +} + void VariableDeclaration::dump(int indent) const { const char* declaration_kind_string = nullptr; @@ -2335,30 +2739,39 @@ Value TryStatement::execute(Interpreter& interpreter, GlobalObject& global_objec { InterpreterNodeScope node_scope { interpreter, *this }; - auto result = interpreter.execute_statement(global_object, m_block, ScopeType::Try); + // FIXME: Use Completions here to be closer to the spec. + auto result = m_block->execute(interpreter, global_object); + if (interpreter.vm().unwind_until() == ScopeType::Try) + interpreter.vm().stop_unwind(); if (auto* exception = interpreter.exception()) { + // 14.15.2 Runtime Semantics: CatchClauseEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation if (m_handler) { interpreter.vm().clear_exception(); - HashMap parameters; + auto* catch_scope = new_declarative_environment(*interpreter.lexical_environment()); + m_handler->parameter().visit( [&](FlyString const& parameter) { - parameters.set(parameter, Variable { exception->value(), DeclarationKind::Var }); + catch_scope->create_mutable_binding(global_object, parameter, false); }, [&](NonnullRefPtr const& pattern) { pattern->for_each_bound_name([&](auto& name) { - parameters.set(name, Variable { Value {}, DeclarationKind::Var }); + catch_scope->create_mutable_binding(global_object, name, false); }); }); - auto* catch_scope = interpreter.heap().allocate(global_object, move(parameters), interpreter.vm().running_execution_context().lexical_environment); + TemporaryChange scope_change(interpreter.vm().running_execution_context().lexical_environment, catch_scope); - if (auto* pattern = m_handler->parameter().get_pointer>()) - interpreter.vm().assign(*pattern, exception->value(), global_object, true); - if (interpreter.exception()) - result = js_undefined(); - else - result = interpreter.execute_statement(global_object, m_handler->body()); + m_handler->parameter().visit( + [&](FlyString const& parameter) { + catch_scope->initialize_binding(global_object, parameter, exception->value()); + }, + [&](NonnullRefPtr const& pattern) { + (void)interpreter.vm().binding_initialization(pattern, exception->value(), catch_scope, global_object); + }); + + if (!interpreter.exception()) + result = m_handler->body().execute(interpreter, global_object); } } @@ -2424,10 +2837,19 @@ Value SwitchStatement::execute(Interpreter& interpreter, GlobalObject& global_ob if (interpreter.exception()) return {}; + auto* old_environment = interpreter.lexical_environment(); + ScopeGuard restore_environment = [&] { + interpreter.vm().running_execution_context().lexical_environment = old_environment; + }; + auto* block_environment = new_declarative_environment(*old_environment); + block_declaration_instantiation(global_object, block_environment); + + interpreter.vm().running_execution_context().lexical_environment = block_environment; + auto last_value = js_undefined(); auto execute_switch_case = [&](auto const& switch_case) -> Optional { - for (auto& statement : switch_case.consequent()) { + for (auto& statement : switch_case.children()) { auto value = statement.execute(interpreter, global_object); if (!value.is_empty()) last_value = value; @@ -2509,7 +2931,6 @@ void SwitchStatement::dump(int indent) const void SwitchCase::dump(int indent) const { - ASTNode::dump(indent); print_indent(indent + 1); if (m_test) { outln("(Test)"); @@ -2519,8 +2940,7 @@ void SwitchCase::dump(int indent) const } print_indent(indent + 1); outln("(Consequent)"); - for (auto& statement : m_consequent) - statement.dump(indent + 2); + ScopeNode::dump(indent + 2); } Value ConditionalExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -2582,19 +3002,90 @@ Value DebuggerStatement::execute(Interpreter& interpreter, GlobalObject&) const return {}; } -void ScopeNode::add_variables(NonnullRefPtrVector variables) +void ScopeNode::for_each_lexically_scoped_declaration(IteratorOrVoidFunction&& callback) const { - m_variables.extend(move(variables)); + for (auto& declaration : m_lexical_declarations) { + if (callback(declaration) == IterationDecision::Break) + break; + } } -void ScopeNode::add_functions(NonnullRefPtrVector functions) +void ScopeNode::for_each_lexically_declared_name(IteratorOrVoidFunction&& callback) const { - m_functions.extend(move(functions)); + auto running = true; + for (auto& declaration : m_lexical_declarations) { + declaration.for_each_bound_name([&](auto const& name) { + if (callback(name) == IterationDecision::Break) { + running = false; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + if (!running) + break; + } } -void ScopeNode::add_hoisted_function(NonnullRefPtr hoisted_function) +void ScopeNode::for_each_var_declared_name(IteratorOrVoidFunction&& callback) const { - m_hoisted_functions.append(hoisted_function); + auto running = true; + for (auto& declaration : m_var_declarations) { + declaration.for_each_bound_name([&](auto const& name) { + if (callback(name) == IterationDecision::Break) { + running = false; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + if (!running) + break; + } +} + +void ScopeNode::for_each_var_function_declaration_in_reverse_order(IteratorOrVoidFunction&& callback) const +{ + for (ssize_t i = m_var_declarations.size() - 1; i >= 0; i--) { + auto& declaration = m_var_declarations[i]; + if (is(declaration)) { + if (callback(static_cast(declaration)) == IterationDecision::Break) + break; + } + } +} + +void ScopeNode::for_each_var_scoped_variable_declaration(IteratorOrVoidFunction&& callback) const +{ + for (auto& declaration : m_var_declarations) { + if (!is(declaration)) { + VERIFY(is(declaration)); + if (callback(static_cast(declaration)) == IterationDecision::Break) + break; + } + } +} + +void ScopeNode::for_each_function_hoistable_with_annexB_extension(IteratorOrVoidFunction&& callback) const +{ + for (auto& function : m_functions_hoistable_with_annexB_extension) { + // We need const_cast here since it might have to set a property on function declaration. + if (callback(const_cast(function)) == IterationDecision::Break) + break; + } +} + +void ScopeNode::add_lexical_declaration(NonnullRefPtr declaration) +{ + m_lexical_declarations.append(move(declaration)); +} + +void ScopeNode::add_var_scoped_declaration(NonnullRefPtr declaration) +{ + m_var_declarations.append(move(declaration)); +} + +void ScopeNode::add_hoisted_function(NonnullRefPtr declaration) +{ + m_functions_hoistable_with_annexB_extension.append(move(declaration)); } Value ImportStatement::execute(Interpreter& interpreter, GlobalObject&) const @@ -2664,4 +3155,181 @@ bool ImportStatement::has_bound_name(StringView name) const }); } +// 14.2.3 BlockDeclarationInstantiation ( code, env ), https://tc39.es/ecma262/#sec-blockdeclarationinstantiation +void ScopeNode::block_declaration_instantiation(GlobalObject& global_object, Environment* environment) const +{ + // See also B.3.2.6 Changes to BlockDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-blockdeclarationinstantiation + VERIFY(environment); + for_each_lexically_scoped_declaration([&](Declaration const& declaration) { + auto is_constant_declaration = declaration.is_constant_declaration(); + declaration.for_each_bound_name([&](auto const& name) { + if (is_constant_declaration) { + environment->create_immutable_binding(global_object, name, true); + } else { + if (!environment->has_binding(name)) + environment->create_mutable_binding(global_object, name, false); + } + }); + + if (is(declaration)) { + auto& function_declaration = static_cast(declaration); + auto* function = ECMAScriptFunctionObject::create(global_object, function_declaration.name(), function_declaration.body(), function_declaration.parameters(), function_declaration.function_length(), environment, function_declaration.kind(), function_declaration.is_strict_mode()); + VERIFY(is(*environment)); + static_cast(*environment).initialize_or_set_mutable_binding({}, global_object, function_declaration.name(), function); + } + }); +} + +// 16.1.7 GlobalDeclarationInstantiation ( script, env ), https://tc39.es/ecma262/#sec-globaldeclarationinstantiation +ThrowCompletionOr Program::global_declaration_instantiation(Interpreter& interpreter, GlobalObject& global_object, GlobalEnvironment& global_environment) const +{ + for_each_lexically_declared_name([&](FlyString const& name) { + if (global_environment.has_var_declaration(name) || global_environment.has_lexical_declaration(name)) { + interpreter.vm().throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Lexical variable top level already declared"); + return IterationDecision::Break; + } + + auto restricted_global = global_environment.has_restricted_global_property(name); + if (interpreter.exception()) + return IterationDecision::Break; + + if (restricted_global) + interpreter.vm().throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Restricted global property"); + + return IterationDecision::Continue; + }); + + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + for_each_var_declared_name([&](auto const& name) { + if (global_environment.has_lexical_declaration(name)) { + interpreter.vm().throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Var declared variable top level also lexically declared"); + return IterationDecision::Break; + } + + return IterationDecision::Continue; + }); + + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + HashTable declared_function_names; + Vector functions_to_initialize; + + for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) { + if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry) + return IterationDecision::Continue; + + auto function_definable = global_environment.can_declare_global_function(function.name()); + + if (interpreter.exception()) + return IterationDecision::Break; + + if (!function_definable) { + interpreter.vm().throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Global function not definable"); + return IterationDecision::Break; + } + + functions_to_initialize.append(function); + return IterationDecision::Continue; + }); + + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + HashTable declared_var_names; + + for_each_var_scoped_variable_declaration([&](Declaration const& declaration) { + declaration.for_each_bound_name([&](auto const& name) { + if (declared_function_names.contains(name)) + return IterationDecision::Continue; + + auto var_definable = global_environment.can_declare_global_var(name); + if (interpreter.exception()) + return IterationDecision::Break; + + if (!var_definable) { + interpreter.vm().throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Global variable not definable"); + return IterationDecision::Break; + } + + declared_var_names.set(name); + return IterationDecision::Continue; + }); + if (interpreter.exception()) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + if (!m_is_strict_mode) { + for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { + auto& function_name = function_declaration.name(); + if (global_environment.has_lexical_declaration(function_name)) + return IterationDecision::Continue; + + auto function_definable = global_environment.can_declare_global_function(function_name); + + if (interpreter.exception()) + return IterationDecision::Break; + + if (!function_definable) { + interpreter.vm().throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Global function not definable"); + return IterationDecision::Break; + } + + if (!declared_function_names.contains(function_name) && !declared_var_names.contains(function_name)) { + global_environment.create_global_var_binding(function_name, false); + if (interpreter.exception()) + return IterationDecision::Break; + declared_function_names.set(function_name); + } + + function_declaration.set_should_do_additional_annexB_steps(); + + return IterationDecision::Continue; + }); + + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + + // We should not use declared function names below here anymore since these functions are not in there in the spec. + declared_function_names.clear(); + } + + for_each_lexically_scoped_declaration([&](Declaration const& declaration) { + declaration.for_each_bound_name([&](auto const& name) { + if (declaration.is_constant_declaration()) + global_environment.create_immutable_binding(global_object, name, true); + else + global_environment.create_mutable_binding(global_object, name, false); + + if (interpreter.exception()) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + if (interpreter.exception()) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + + for (auto& declaration : functions_to_initialize) { + auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), &global_environment, declaration.kind(), declaration.is_strict_mode()); + global_environment.create_global_function_binding(declaration.name(), function, false); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + } + + for (auto& var_name : declared_var_names) { + global_environment.create_global_var_binding(var_name, false); + if (auto* exception = interpreter.exception()) + return throw_completion(exception->value()); + } + + return {}; +} + } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 13ff3f8fe3..052cfb5c22 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -24,10 +24,12 @@ namespace JS { -class VariableDeclaration; +class Declaration; +class ClassDeclaration; class FunctionDeclaration; class Identifier; class MemberExpression; +class VariableDeclaration; enum class FunctionKind { Generator, @@ -137,6 +139,25 @@ private: NonnullRefPtr m_expression; }; +template +class IteratorOrVoidFunction : public Function { +public: + template + IteratorOrVoidFunction(CallableType&& callable) requires(VoidFunction) + : Function([callable = forward(callable)](Args... args) { + callable(forward(args)...); + return IterationDecision::Continue; + }) + { + } + + template + IteratorOrVoidFunction(CallableType&& callable) requires(IteratorFunction) + : Function(forward(callable)) + { + } +}; + class ScopeNode : public LabelableStatement { public: template @@ -152,16 +173,26 @@ public: } NonnullRefPtrVector const& children() const { return m_children; } - virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; - void add_variables(NonnullRefPtrVector); - void add_functions(NonnullRefPtrVector); - void add_hoisted_function(NonnullRefPtr); - NonnullRefPtrVector const& variables() const { return m_variables; } - NonnullRefPtrVector const& functions() const { return m_functions; } - NonnullRefPtrVector const& hoisted_functions() const { return m_hoisted_functions; } + Value evaluate_statements(Interpreter& interpreter, GlobalObject& global_object) const; + + void add_var_scoped_declaration(NonnullRefPtr variables); + void add_lexical_declaration(NonnullRefPtr variables); + void add_hoisted_function(NonnullRefPtr declaration); + + void for_each_lexically_scoped_declaration(IteratorOrVoidFunction&& callback) const; + void for_each_lexically_declared_name(IteratorOrVoidFunction&& callback) const; + + void for_each_var_declared_name(IteratorOrVoidFunction&& callback) const; + + void for_each_var_function_declaration_in_reverse_order(IteratorOrVoidFunction&& callback) const; + void for_each_var_scoped_variable_declaration(IteratorOrVoidFunction&& callback) const; + + void block_declaration_instantiation(GlobalObject& global_object, Environment* environment) const; + + void for_each_function_hoistable_with_annexB_extension(IteratorOrVoidFunction&& callback) const; protected: explicit ScopeNode(SourceRange source_range) @@ -173,9 +204,10 @@ private: virtual bool is_scope_node() const final { return true; } NonnullRefPtrVector m_children; - NonnullRefPtrVector m_variables; - NonnullRefPtrVector m_functions; - NonnullRefPtrVector m_hoisted_functions; + NonnullRefPtrVector m_lexical_declarations; + NonnullRefPtrVector m_var_declarations; + + NonnullRefPtrVector m_functions_hoistable_with_annexB_extension; }; class ImportStatement final : public Statement { @@ -280,6 +312,8 @@ public: NonnullRefPtrVector const& imports() const { return m_imports; } NonnullRefPtrVector const& exports() const { return m_exports; } + ThrowCompletionOr global_declaration_instantiation(Interpreter& interpreter, GlobalObject& global_object, GlobalEnvironment& global_environment) const; + private: virtual bool is_program() const override { return true; } @@ -296,6 +330,24 @@ public: : ScopeNode(source_range) { } + Value execute(Interpreter& interpreter, GlobalObject& object) const override; +}; + +class FunctionBody final : public ScopeNode { +public: + explicit FunctionBody(SourceRange source_range) + : ScopeNode(source_range) + { + } + + void set_strict_mode() { m_in_strict_mode = true; } + + bool in_strict_mode() const { return m_in_strict_mode; } + + virtual Value execute(Interpreter&, GlobalObject&) const override; + +private: + bool m_in_strict_mode { false }; }; class Expression : public ASTNode { @@ -313,6 +365,15 @@ public: : Statement(source_range) { } + + virtual void for_each_bound_name(IteratorOrVoidFunction callback) const = 0; + + // 8.1.3 Static Semantics: IsConstantDeclaration, https://tc39.es/ecma262/#sec-static-semantics-isconstantdeclaration + virtual bool is_constant_declaration() const { return false; } + + virtual bool is_function_declaration() const { return false; } + + virtual bool is_lexical_declaration() const { return false; } }; class ErrorDeclaration final : public Declaration { @@ -322,6 +383,11 @@ public: { } Value execute(Interpreter&, GlobalObject&) const override { return {}; } + + void for_each_bound_name(IteratorOrVoidFunction) const override + { + VERIFY_NOT_REACHED(); + } }; struct BindingPattern : RefCounted { @@ -346,6 +412,8 @@ struct BindingPattern : RefCounted { template void for_each_bound_name(C&& callback) const; + bool contains_expression() const; + Vector entries; Kind kind { Kind::Object }; }; @@ -412,6 +480,15 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; + + void for_each_bound_name(IteratorOrVoidFunction callback) const override; + + virtual bool is_function_declaration() const override { return true; } + + void set_should_do_additional_annexB_steps() { m_is_hoisted = true; } + +private: + bool m_is_hoisted { false }; }; class FunctionExpression final @@ -429,25 +506,11 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; - void set_name_if_possible(FlyString new_name) - { - if (m_cannot_auto_rename) - return; - m_cannot_auto_rename = true; - if (name().is_empty()) { - set_name(move(new_name)); - m_is_auto_renamed = true; - } - } - bool cannot_auto_rename() const { return m_cannot_auto_rename; } - bool is_auto_renamed() const { return m_is_auto_renamed; } - void set_cannot_auto_rename() { m_cannot_auto_rename = true; } - virtual void generate_bytecode(Bytecode::Generator&) const override; -private: - bool m_cannot_auto_rename { false }; - bool m_is_auto_renamed { false }; + bool has_name() const { return !name().is_empty(); } + + Value instantiate_ordinary_function_expression(Interpreter& interpreter, GlobalObject& global_object, FlyString given_name) const; }; class ErrorExpression final : public Expression { @@ -614,7 +677,7 @@ private: class ForInStatement final : public IterationStatement { public: - ForInStatement(SourceRange source_range, NonnullRefPtr lhs, NonnullRefPtr rhs, NonnullRefPtr body) + ForInStatement(SourceRange source_range, Variant, NonnullRefPtr> lhs, NonnullRefPtr rhs, NonnullRefPtr body) : IterationStatement(source_range) , m_lhs(move(lhs)) , m_rhs(move(rhs)) @@ -622,7 +685,7 @@ public: { } - ASTNode const& lhs() const { return *m_lhs; } + auto const& lhs() const { return m_lhs; } Expression const& rhs() const { return *m_rhs; } Statement const& body() const { return *m_body; } @@ -630,14 +693,14 @@ public: virtual void dump(int indent) const override; private: - NonnullRefPtr m_lhs; + Variant, NonnullRefPtr> m_lhs; NonnullRefPtr m_rhs; NonnullRefPtr m_body; }; class ForOfStatement final : public IterationStatement { public: - ForOfStatement(SourceRange source_range, NonnullRefPtr lhs, NonnullRefPtr rhs, NonnullRefPtr body) + ForOfStatement(SourceRange source_range, Variant, NonnullRefPtr> lhs, NonnullRefPtr rhs, NonnullRefPtr body) : IterationStatement(source_range) , m_lhs(move(lhs)) , m_rhs(move(rhs)) @@ -645,7 +708,7 @@ public: { } - ASTNode const& lhs() const { return *m_lhs; } + auto const& lhs() const { return m_lhs; } Expression const& rhs() const { return *m_rhs; } Statement const& body() const { return *m_body; } @@ -653,7 +716,7 @@ public: virtual void dump(int indent) const override; private: - NonnullRefPtr m_lhs; + Variant, NonnullRefPtr> m_lhs; NonnullRefPtr m_rhs; NonnullRefPtr m_body; }; @@ -1000,6 +1063,10 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void dump(int indent) const override; + bool has_name() const { return !m_name.is_empty(); } + + ThrowCompletionOr class_definition_evaluation(Interpreter& interpreter, GlobalObject& global_object, FlyString const& binding_name = {}, FlyString const& class_name = {}) const; + private: String m_name; RefPtr m_constructor; @@ -1020,6 +1087,10 @@ public: virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; + void for_each_bound_name(IteratorOrVoidFunction callback) const override; + + virtual bool is_lexical_declaration() const override { return true; } + private: NonnullRefPtr m_class_expression; }; @@ -1239,6 +1310,12 @@ public: NonnullRefPtrVector const& declarations() const { return m_declarations; } + virtual void for_each_bound_name(IteratorOrVoidFunction callback) const override; + + virtual bool is_constant_declaration() const override { return m_declaration_kind == DeclarationKind::Const; }; + + virtual bool is_lexical_declaration() const override { return m_declaration_kind != DeclarationKind::Var; } + private: DeclarationKind m_declaration_kind; NonnullRefPtrVector m_declarations; @@ -1550,32 +1627,28 @@ private: NonnullRefPtr m_argument; }; -class SwitchCase final : public ASTNode { +class SwitchCase final : public ScopeNode { public: - SwitchCase(SourceRange source_range, RefPtr test, NonnullRefPtrVector consequent) - : ASTNode(source_range) + SwitchCase(SourceRange source_range, RefPtr test) + : ScopeNode(source_range) , m_test(move(test)) - , m_consequent(move(consequent)) { } Expression const* test() const { return m_test; } - NonnullRefPtrVector const& consequent() const { return m_consequent; } virtual void dump(int indent) const override; virtual Value execute(Interpreter&, GlobalObject&) const override; private: RefPtr m_test; - NonnullRefPtrVector m_consequent; }; -class SwitchStatement final : public LabelableStatement { +class SwitchStatement final : public ScopeNode { public: - SwitchStatement(SourceRange source_range, NonnullRefPtr discriminant, NonnullRefPtrVector cases) - : LabelableStatement(source_range) + SwitchStatement(SourceRange source_range, NonnullRefPtr discriminant) + : ScopeNode(source_range) , m_discriminant(move(discriminant)) - , m_cases(move(cases)) { } @@ -1583,6 +1656,8 @@ public: virtual Value execute(Interpreter&, GlobalObject&) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; + void add_case(NonnullRefPtr switch_case) { m_cases.append(move(switch_case)); } + private: NonnullRefPtr m_discriminant; NonnullRefPtrVector m_cases; diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 4c4d145a72..834ddc650e 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -26,46 +26,7 @@ void ASTNode::generate_bytecode(Bytecode::Generator&) const void ScopeNode::generate_bytecode(Bytecode::Generator& generator) const { - for (auto& function : functions()) { - generator.emit(function); - generator.emit(generator.intern_string(function.name())); - } - - HashMap scope_variables_with_declaration_kind; - - bool is_program_node = is(*this); - for (auto& declaration : variables()) { - for (auto& declarator : declaration.declarations()) { - if (is_program_node && declaration.declaration_kind() == DeclarationKind::Var) { - declarator.target().visit( - [&](const NonnullRefPtr& id) { - generator.emit(js_undefined()); - generator.emit(Bytecode::Register::global_object(), generator.intern_string(id->string())); - }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - generator.emit(js_undefined()); - generator.emit(Bytecode::Register::global_object(), generator.intern_string(name)); - }); - }); - } else { - declarator.target().visit( - [&](const NonnullRefPtr& id) { - scope_variables_with_declaration_kind.set((size_t)generator.intern_string(id->string()).value(), { js_undefined(), declaration.declaration_kind() }); - }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - scope_variables_with_declaration_kind.set((size_t)generator.intern_string(name).value(), { js_undefined(), declaration.declaration_kind() }); - }); - }); - } - } - } - - if (!scope_variables_with_declaration_kind.is_empty()) { - generator.emit(move(scope_variables_with_declaration_kind)); - } - + // FIXME: Register lexical and variable scope declarations for (auto& child : children()) { child.generate_bytecode(generator); if (generator.is_current_block_terminated()) @@ -1294,7 +1255,7 @@ void SwitchStatement::generate_bytecode(Bytecode::Generator& generator) const generator.switch_to_basic_block(*current_block); generator.emit(js_undefined()); - for (auto& statement : switch_case.consequent()) { + for (auto& statement : switch_case.children()) { statement.generate_bytecode(generator); } if (!generator.is_current_block_terminated()) { diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 2a509d4c5e..939395cad9 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -217,12 +217,22 @@ void ConcatString::execute_impl(Bytecode::Interpreter& interpreter) const void GetVariable::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.accumulator() = interpreter.vm().get_variable(interpreter.current_executable().get_string(m_identifier), interpreter.global_object()); + auto& vm = interpreter.vm(); + auto reference = vm.resolve_binding(interpreter.current_executable().get_string(m_identifier)); + if (vm.exception()) + return; + + interpreter.accumulator() = reference.get_value(interpreter.global_object()); } void SetVariable::execute_impl(Bytecode::Interpreter& interpreter) const { - interpreter.vm().set_variable(interpreter.current_executable().get_string(m_identifier), interpreter.accumulator(), interpreter.global_object()); + auto& vm = interpreter.vm(); + auto reference = vm.resolve_binding(interpreter.current_executable().get_string(m_identifier)); + if (vm.exception()) + return; + + reference.put_value(interpreter.global_object(), interpreter.accumulator()); } void GetById::execute_impl(Bytecode::Interpreter& interpreter) const @@ -390,10 +400,7 @@ void ContinuePendingUnwind::replace_references_impl(BasicBlock const& from, Basi void PushDeclarativeEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const { - HashMap resolved_variables; - for (auto& it : m_variables) - resolved_variables.set(interpreter.current_executable().get_string(it.key), it.value); - auto* environment = interpreter.vm().heap().allocate(interpreter.global_object(), move(resolved_variables), interpreter.vm().lexical_environment()); + auto* environment = interpreter.vm().heap().allocate(interpreter.global_object(), interpreter.vm().lexical_environment()); interpreter.vm().running_execution_context().lexical_environment = environment; interpreter.vm().running_execution_context().variable_environment = environment; } diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp index 4e61f3b627..e3864cbc97 100644 --- a/Userland/Libraries/LibJS/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Interpreter.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -93,139 +94,4 @@ const Realm& Interpreter::realm() const return static_cast(*m_realm.cell()); } -void Interpreter::enter_scope(const ScopeNode& scope_node, ScopeType scope_type, GlobalObject& global_object) -{ - ScopeGuard guard([&] { - for (auto& declaration : scope_node.hoisted_functions()) { - lexical_environment()->put_into_environment(declaration.name(), { js_undefined(), DeclarationKind::Var }); - } - for (auto& declaration : scope_node.functions()) { - auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), lexical_environment(), declaration.kind(), declaration.is_strict_mode()); - vm().set_variable(declaration.name(), function, global_object); - } - }); - - if (scope_type == ScopeType::Function) { - push_scope({ scope_type, scope_node, false }); - for (auto& declaration : scope_node.functions()) - lexical_environment()->put_into_environment(declaration.name(), { js_undefined(), DeclarationKind::Var }); - return; - } - - HashMap scope_variables_with_declaration_kind; - - if (!scope_node.variables().is_empty()) - scope_variables_with_declaration_kind.ensure_capacity(16); - - bool is_program_node = is(scope_node); - - for (auto& declaration : scope_node.variables()) { - for (auto& declarator : declaration.declarations()) { - if (is_program_node && declaration.declaration_kind() == DeclarationKind::Var) { - declarator.target().visit( - [&](const NonnullRefPtr& id) { - global_object.define_direct_property(id->string(), js_undefined(), JS::Attribute::Writable | JS::Attribute::Enumerable); - }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - global_object.define_direct_property(name, js_undefined(), JS::Attribute::Writable | JS::Attribute::Enumerable); - }); - }); - if (exception()) - return; - } else { - declarator.target().visit( - [&](const NonnullRefPtr& id) { - scope_variables_with_declaration_kind.set(id->string(), { js_undefined(), declaration.declaration_kind() }); - }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - scope_variables_with_declaration_kind.set(name, { js_undefined(), declaration.declaration_kind() }); - }); - }); - } - } - } - - bool pushed_environment = false; - - if (!scope_variables_with_declaration_kind.is_empty()) { - auto* environment = heap().allocate(global_object, move(scope_variables_with_declaration_kind), lexical_environment()); - vm().running_execution_context().lexical_environment = environment; - vm().running_execution_context().variable_environment = environment; - pushed_environment = true; - } - - push_scope({ scope_type, scope_node, pushed_environment }); -} - -void Interpreter::exit_scope(const ScopeNode& scope_node) -{ - while (!m_scope_stack.is_empty()) { - auto popped_scope = m_scope_stack.take_last(); - if (popped_scope.pushed_environment) { - vm().running_execution_context().lexical_environment = vm().running_execution_context().lexical_environment->outer_environment(); - vm().running_execution_context().variable_environment = vm().running_execution_context().variable_environment->outer_environment(); - } - if (popped_scope.scope_node.ptr() == &scope_node) - break; - } - - // If we unwind all the way, just reset m_unwind_until so that future "return" doesn't break. - if (m_scope_stack.is_empty()) - vm().stop_unwind(); -} - -void Interpreter::push_scope(ScopeFrame frame) -{ - m_scope_stack.append(move(frame)); -} - -Value Interpreter::execute_statement(GlobalObject& global_object, const Statement& statement, ScopeType scope_type) -{ - if (!is(statement)) - return statement.execute(*this, global_object); - - auto& block = static_cast(statement); - Vector const& labels = [&] { - if (is(block)) { - return static_cast(block).labels(); - } else { - return Vector(); - } - }(); - - enter_scope(block, scope_type, global_object); - - Value last_value; - for (auto& node : block.children()) { - auto value = node.execute(*this, global_object); - if (!value.is_empty()) - last_value = value; - if (vm().should_unwind()) { - if (!labels.is_empty() && vm().should_unwind_until(ScopeType::Breakable, labels)) - vm().stop_unwind(); - break; - } - } - - if (scope_type == ScopeType::Function) { - bool did_return = vm().unwind_until() == ScopeType::Function; - if (!did_return) - last_value = js_undefined(); - } - - if (vm().unwind_until() == scope_type) - vm().stop_unwind(); - - exit_scope(block); - - return last_value; -} - -FunctionEnvironment* Interpreter::current_function_environment() -{ - return verify_cast(vm().running_execution_context().lexical_environment); -} - } diff --git a/Userland/Libraries/LibJS/Interpreter.h b/Userland/Libraries/LibJS/Interpreter.h index aaea4fc382..1f46a531a8 100644 --- a/Userland/Libraries/LibJS/Interpreter.h +++ b/Userland/Libraries/LibJS/Interpreter.h @@ -67,11 +67,6 @@ public: Environment* lexical_environment() { return vm().lexical_environment(); } - FunctionEnvironment* current_function_environment(); - - void enter_scope(const ScopeNode&, ScopeType, GlobalObject&); - void exit_scope(const ScopeNode&); - void push_ast_node(ExecutingASTNodeChain& chain_node) { chain_node.previous = m_ast_node_chain; @@ -85,17 +80,10 @@ public: } const ASTNode* current_node() const { return m_ast_node_chain ? &m_ast_node_chain->node : nullptr; } - ExecutingASTNodeChain* executing_ast_node_chain() { return m_ast_node_chain; } - const ExecutingASTNodeChain* executing_ast_node_chain() const { return m_ast_node_chain; } - - Value execute_statement(GlobalObject&, const Statement&, ScopeType = ScopeType::Block); private: explicit Interpreter(VM&); - void push_scope(ScopeFrame frame); - - Vector m_scope_stack; ExecutingASTNodeChain* m_ast_node_chain { nullptr }; NonnullRefPtr m_vm; diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 89dd33ed10..db8818cff6 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -18,81 +18,184 @@ namespace JS { -static bool statement_is_use_strict_directive(NonnullRefPtr statement) -{ - if (!is(*statement)) - return false; - auto& expression_statement = static_cast(*statement); - auto& expression = expression_statement.expression(); - if (!is(expression)) - return false; - return static_cast(expression).is_use_strict_directive(); -} - class ScopePusher { -public: - enum Type { - Var = 1, - Let = 2, - }; - ScopePusher(Parser& parser, unsigned mask, Parser::Scope::Type scope_type) +private: + ScopePusher(Parser& parser, ScopeNode* node, bool is_top_level) : m_parser(parser) - , m_mask(mask) + , m_is_top_level(is_top_level) { - if (m_mask & Var) - m_parser.m_state.var_scopes.append(NonnullRefPtrVector()); - if (m_mask & Let) - m_parser.m_state.let_scopes.append(NonnullRefPtrVector()); + m_parent_scope = exchange(m_parser.m_state.current_scope_pusher, this); + VERIFY(node || (m_parent_scope && !is_top_level)); + if (!node) + m_node = m_parent_scope->m_node; + else + m_node = node; + VERIFY(m_node); - m_parser.m_state.current_scope = make_ref_counted(scope_type, m_parser.m_state.current_scope); + if (!is_top_level) + m_top_level_scope = m_parent_scope->m_top_level_scope; + else + m_top_level_scope = this; + } + +public: + static ScopePusher function_scope(Parser& parser, FunctionBody& function_body, Vector const& parameters) + { + ScopePusher scope_pusher(parser, &function_body, true); + for (auto& parameter : parameters) { + parameter.binding.visit( + [&](FlyString const& name) { + scope_pusher.m_forbidden_lexical_names.set(name); + }, + [&](NonnullRefPtr const& binding_pattern) { + binding_pattern->for_each_bound_name([&](auto const& name) { + scope_pusher.m_forbidden_lexical_names.set(name); + }); + }); + } + return scope_pusher; + } + + static ScopePusher program_scope(Parser& parser, Program& program) + { + return ScopePusher(parser, &program, true); + } + + static ScopePusher block_scope(Parser& parser, ScopeNode& node) + { + return ScopePusher(parser, &node, false); + } + + static ScopePusher for_loop_scope(Parser& parser, RefPtr const& init) + { + ScopePusher scope_pusher(parser, nullptr, false); + if (init && is(*init)) { + auto& variable_declaration = static_cast(*init); + if (variable_declaration.declaration_kind() != DeclarationKind::Var) { + variable_declaration.for_each_bound_name([&](auto const& name) { + scope_pusher.m_forbidden_var_names.set(name); + }); + } + } + + return scope_pusher; + } + + static ScopePusher catch_scope(Parser& parser, RefPtr const& pattern) + { + ScopePusher scope_pusher(parser, nullptr, false); + if (pattern) { + pattern->for_each_bound_name([&](auto const& name) { + scope_pusher.m_forbidden_var_names.set(name); + }); + } + return scope_pusher; + } + + void add_declaration(NonnullRefPtr declaration) + { + if (declaration->is_lexical_declaration()) { + declaration->for_each_bound_name([&](auto const& name) { + 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()) { + declaration->for_each_bound_name([&](auto const& name) { + 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->m_is_top_level) + break; + + VERIFY(pusher->m_parent_scope != nullptr); + pusher = pusher->m_parent_scope; + } + VERIFY(pusher->m_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_is_top_level) { + declaration->for_each_bound_name([&](auto const& name) { + m_var_names.set(name); + }); + 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::Regular || 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() { - if (m_mask & Var) - m_parser.m_state.var_scopes.take_last(); - if (m_mask & Let) - m_parser.m_state.let_scopes.take_last(); + VERIFY(m_is_top_level || m_parent_scope); - auto& popped = m_parser.m_state.current_scope; - // Manual clear required to resolve circular references - popped->hoisted_function_declarations.clear(); + 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())) + continue; + if (m_is_top_level) + m_node->add_hoisted_function(move(m_functions_to_hoist[i])); + else + m_parent_scope->m_functions_to_hoist.append(move(m_functions_to_hoist[i])); + } - m_parser.m_state.current_scope = popped->parent; + VERIFY(m_parser.m_state.current_scope_pusher == this); + m_parser.m_state.current_scope_pusher = m_parent_scope; } - void add_to_scope_node(NonnullRefPtr scope_node) +private: + void throw_identifier_declared(FlyString const& name, NonnullRefPtr const& declaration) { - if (m_mask & Var) - scope_node->add_variables(m_parser.m_state.var_scopes.last()); - if (m_mask & Let) - scope_node->add_variables(m_parser.m_state.let_scopes.last()); - - auto& scope = m_parser.m_state.current_scope; - scope_node->add_functions(scope->function_declarations); - - for (auto& hoistable_function : scope->hoisted_function_declarations) { - if (is_hoistable(hoistable_function)) { - scope_node->add_hoisted_function(hoistable_function.declaration); - } - } - } - - static bool is_hoistable(Parser::Scope::HoistableDeclaration& declaration) - { - auto& name = declaration.declaration->name(); - // See if we find any conflicting lexical declaration on the way up - for (RefPtr scope = declaration.scope; !scope.is_null(); scope = scope->parent) { - if (scope->lexical_declarations.contains(name)) { - return false; - } - } - return true; + m_parser.syntax_error(String::formatted("Identifier '{}' already declared", name), declaration->source_range().start); } Parser& m_parser; - unsigned m_mask { 0 }; + ScopeNode* m_node { nullptr }; + bool m_is_top_level { false }; + + ScopePusher* m_parent_scope { nullptr }; + ScopePusher* m_top_level_scope { nullptr }; + + HashTable m_lexical_names; + HashTable m_var_names; + HashTable m_function_names; + + HashTable m_forbidden_lexical_names; + HashTable m_forbidden_var_names; + NonnullRefPtrVector m_functions_to_hoist; }; class OperatorPrecedenceTable { @@ -217,24 +320,6 @@ Parser::ParserState::ParserState(Lexer l, Program::Type program_type) current_token = lexer.next(); } -Parser::Scope::Scope(Parser::Scope::Type type, RefPtr parent_scope) - : type(type) - , parent(move(parent_scope)) -{ -} - -RefPtr Parser::Scope::get_current_function_scope() -{ - if (this->type == Parser::Scope::Function) { - return *this; - } - auto result = this->parent; - while (result->type != Parser::Scope::Function) { - result = result->parent; - } - return result; -} - Parser::Parser(Lexer lexer, Program::Type program_type) : m_state(move(lexer), program_type) , m_program_type(program_type) @@ -282,60 +367,68 @@ Associativity Parser::operator_associativity(TokenType type) const } } +bool Parser::parse_directive(ScopeNode& body) +{ + bool found_use_strict = false; + while (!done() && match(TokenType::StringLiteral)) { + // It cannot be a labelled function since we hit a string literal. + auto statement = parse_statement(AllowLabelledFunction::No); + body.append(statement); + + VERIFY(is(*statement)); + auto& expression = static_cast(*statement).expression(); + + if (!is(expression)) + break; + + auto& string_literal = static_cast(expression); + if (string_literal.is_use_strict_directive()) { + found_use_strict = true; + + if (m_state.string_legacy_octal_escape_sequence_in_scope) + syntax_error("Octal escape sequence in string literal not allowed in strict mode"); + break; + } + } + + m_state.string_legacy_octal_escape_sequence_in_scope = false; + return found_use_strict; +} + NonnullRefPtr Parser::parse_program(bool starts_in_strict_mode) { auto rule_start = push_start(); - ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Let, Scope::Function); auto program = adopt_ref(*new Program({ m_filename, rule_start.position(), position() }, m_program_type)); - if (starts_in_strict_mode || m_program_type == Program::Type::Module) { + ScopePusher program_scope = ScopePusher::program_scope(*this, *program); + if (starts_in_strict_mode || m_program_type == Program::Type::Module) + m_state.strict_mode = true; + + bool has_use_strict = parse_directive(program); + + if (m_state.strict_mode || has_use_strict) { program->set_strict_mode(); m_state.strict_mode = true; } - bool parsing_directives = true; while (!done()) { - if (match_declaration()) { - program->append(parse_declaration()); - parsing_directives = false; - } else if (match_statement()) { - auto statement = parse_statement(AllowLabelledFunction::Yes); - program->append(statement); - if (statement_is_use_strict_directive(statement)) { - if (parsing_directives) { - program->set_strict_mode(); - m_state.strict_mode = true; - } - if (m_state.string_legacy_octal_escape_sequence_in_scope) - syntax_error("Octal escape sequence in string literal not allowed in strict mode"); - } + parse_statement_list(program, AllowLabelledFunction::Yes); - if (parsing_directives && is(*statement)) { - auto& expression_statement = static_cast(*statement); - auto& expression = expression_statement.expression(); - parsing_directives = is(expression); - } else { - parsing_directives = false; - } + if (done()) + break; - } else if (match_export_or_import()) { + if (match_export_or_import()) { VERIFY(m_state.current_token.type() == TokenType::Export || m_state.current_token.type() == TokenType::Import); if (m_state.current_token.type() == TokenType::Export) program->append_export(parse_export_statement(*program)); else program->append_import(parse_import_statement(*program)); - parsing_directives = false; } else { expected("statement or declaration"); consume(); - parsing_directives = false; } } - if (m_state.var_scopes.size() == 1) { - scope.add_to_scope_node(program); - } else { - syntax_error("Unclosed lexical_environment"); - } + program->source_range().end = position(); return program; } @@ -346,13 +439,8 @@ NonnullRefPtr Parser::parse_declaration() switch (m_state.current_token.type()) { case TokenType::Class: return parse_class_declaration(); - case TokenType::Function: { - auto declaration = parse_function_node(); - m_state.current_scope->function_declarations.append(declaration); - auto hoisting_target = m_state.current_scope->get_current_function_scope(); - hoisting_target->hoisted_function_declarations.append({ declaration, *m_state.current_scope }); - return declaration; - } + case TokenType::Function: + return parse_function_node(); case TokenType::Let: case TokenType::Const: return parse_variable_declaration(); @@ -372,8 +460,11 @@ NonnullRefPtr Parser::parse_statement(AllowLabelledFunction allow_lab return parse_block_statement(); case TokenType::Return: return parse_return_statement(); - case TokenType::Var: - return parse_variable_declaration(); + case TokenType::Var: { + auto declaration = parse_variable_declaration(); + m_state.current_scope_pusher->add_declaration(declaration); + return declaration; + } case TokenType::For: return parse_for_statement(); case TokenType::If: @@ -518,24 +609,16 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe if (function_length == -1) function_length = parameters.size(); - m_state.function_parameters.append(parameters); - auto old_labels_in_scope = move(m_state.labels_in_scope); ScopeGuard guard([&]() { m_state.labels_in_scope = move(old_labels_in_scope); }); - bool is_strict = false; - - auto function_body_result = [&]() -> RefPtr { + auto function_body_result = [&]() -> RefPtr { TemporaryChange change(m_state.in_arrow_function_context, true); if (match(TokenType::CurlyOpen)) { // Parse a function body with statements - ScopePusher scope(*this, ScopePusher::Var, Scope::Function); - - auto body = parse_block_statement(is_strict, !is_simple_parameter_list(parameters)); - scope.add_to_scope_node(body); - return body; + return parse_function_body(parameters, FunctionKind::Regular); } if (match_expression()) { // Parse a function body which returns a single expression @@ -545,16 +628,16 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe // 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() }); + auto return_block = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }); return_block->append({ m_filename, rule_start.position(), position() }, move(return_expression)); + if (m_state.strict_mode) + return_block->set_strict_mode(); return return_block; } // Invalid arrow function body return nullptr; }(); - m_state.function_parameters.take_last(); - if (function_body_result.is_null()) return nullptr; @@ -562,7 +645,7 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe discard_saved_state(); auto body = function_body_result.release_nonnull(); - if (is_strict) { + if (body->in_strict_mode()) { for (auto& parameter : parameters) { parameter.binding.visit( [&](FlyString const& name) { @@ -574,7 +657,7 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe return create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, "", move(body), - move(parameters), function_length, FunctionKind::Regular, is_strict, true); + move(parameters), function_length, FunctionKind::Regular, body->in_strict_mode(), true); } RefPtr Parser::try_parse_labelled_statement(AllowLabelledFunction allow_function) @@ -637,9 +720,8 @@ RefPtr Parser::try_parse_labelled_statement(AllowLabelledFunction all if (match(TokenType::Function)) { m_state.labels_in_scope.set(identifier, {}); auto function_declaration = parse_function_node(); - m_state.current_scope->function_declarations.append(function_declaration); - auto hoisting_target = m_state.current_scope->get_current_function_scope(); - hoisting_target->hoisted_function_declarations.append({ function_declaration, *m_state.current_scope }); + VERIFY(m_state.current_scope_pusher); + m_state.current_scope_pusher->add_declaration(function_declaration); if (function_declaration->kind() == FunctionKind::Generator) syntax_error("Generator functions cannot be defined in labelled statements"); @@ -890,7 +972,6 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ { m_state.current_token.filename(), rule_start.position(), position() }, Vector { CallExpression::Argument { create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, "args"), true } }); constructor_body->append(create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(super_call))); - constructor_body->add_variables(m_state.var_scopes.last()); constructor = create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), @@ -928,7 +1009,6 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression() consume(TokenType::ParenClose); if (is(*expression)) { auto& function = static_cast(*expression); - function.set_cannot_auto_rename(); if (function.kind() == FunctionKind::Generator && function.name() == "yield"sv) syntax_error("function is not allowed to be called 'yield' in this context", function.source_range().start); } @@ -1656,14 +1736,6 @@ NonnullRefPtr Parser::parse_assignment_expression(Assignme syntax_error("Cannot assign to function call"); } auto rhs = parse_expression(min_precedence, associativity); - if (assignment_op == AssignmentOp::Assignment && is(*rhs)) { - auto ident = lhs; - if (is(*lhs)) { - ident = static_cast(*lhs).property(); - } - if (is(*ident)) - static_cast(*rhs).set_name_if_possible(static_cast(*ident).string()); - } return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, assignment_op, move(lhs), move(rhs)); } @@ -1787,64 +1859,91 @@ NonnullRefPtr Parser::parse_return_statement() return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, nullptr); } +void Parser::parse_statement_list(ScopeNode& output_node, AllowLabelledFunction allow_labelled_functions) +{ + while (!done()) { + if (match_declaration()) { + auto declaration = parse_declaration(); + VERIFY(m_state.current_scope_pusher); + m_state.current_scope_pusher->add_declaration(declaration); + output_node.append(move(declaration)); + } else if (match_statement()) { + output_node.append(parse_statement(allow_labelled_functions)); + } else { + break; + } + } +} + +// FunctionBody, https://tc39.es/ecma262/#prod-FunctionBody +NonnullRefPtr Parser::parse_function_body(Vector const& parameters, FunctionKind function_kind) +{ + auto rule_start = push_start(); + auto function_body = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }); + ScopePusher function_scope = ScopePusher::function_scope(*this, function_body, parameters); // FIXME <- + consume(TokenType::CurlyOpen); + auto has_use_strict = parse_directive(function_body); + bool previous_strict_mode = m_state.strict_mode; + if (has_use_strict) { + m_state.strict_mode = true; + function_body->set_strict_mode(); + if (!is_simple_parameter_list(parameters)) + syntax_error("Illegal 'use strict' directive in function with non-simple parameter list"); + } else if (previous_strict_mode) { + function_body->set_strict_mode(); + } + + parse_statement_list(function_body); + + // If the function contains 'use strict' we need to check the parameters (again). + if (function_body->in_strict_mode() || function_kind != FunctionKind::Regular) { + Vector parameter_names; + for (auto& parameter : parameters) { + parameter.binding.visit( + [&](FlyString const& parameter_name) { + check_identifier_name_for_assignment_validity(parameter_name, function_body->in_strict_mode()); + if (function_kind == FunctionKind::Generator && parameter_name == "yield"sv) + syntax_error("Parameter name 'yield' not allowed in this context"); + + for (auto& previous_name : parameter_names) { + if (previous_name == parameter_name) { + syntax_error(String::formatted("Duplicate parameter '{}' not allowed in strict mode", parameter_name)); + } + } + + parameter_names.append(parameter_name); + }, + [&](NonnullRefPtr const& binding) { + binding->for_each_bound_name([&](auto& bound_name) { + if (function_kind == FunctionKind::Generator && bound_name == "yield"sv) + syntax_error("Parameter name 'yield' not allowed in this context"); + + for (auto& previous_name : parameter_names) { + if (previous_name == bound_name) { + syntax_error(String::formatted("Duplicate parameter '{}' not allowed in strict mode", bound_name)); + break; + } + } + parameter_names.append(bound_name); + }); + }); + } + } + + consume(TokenType::CurlyClose); + m_state.strict_mode = previous_strict_mode; + return function_body; +} + NonnullRefPtr Parser::parse_block_statement() { auto rule_start = push_start(); - bool dummy = false; - return parse_block_statement(dummy); -} - -NonnullRefPtr Parser::parse_block_statement(bool& is_strict, bool function_with_non_simple_parameter_list) -{ - auto rule_start = push_start(); - ScopePusher scope(*this, ScopePusher::Let, Parser::Scope::Block); auto block = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }); + ScopePusher block_scope = ScopePusher::block_scope(*this, block); consume(TokenType::CurlyOpen); - - bool initial_strict_mode_state = m_state.strict_mode; - if (initial_strict_mode_state) - is_strict = true; - - bool parsing_directives = true; - while (!done() && !match(TokenType::CurlyClose)) { - if (match_declaration()) { - block->append(parse_declaration()); - parsing_directives = false; - } else if (match_statement()) { - auto statement = parse_statement(AllowLabelledFunction::Yes); - block->append(statement); - if (statement_is_use_strict_directive(statement)) { - if (parsing_directives) { - if (!initial_strict_mode_state) { - is_strict = true; - m_state.strict_mode = true; - } - } - if (m_state.string_legacy_octal_escape_sequence_in_scope) - syntax_error("Octal escape sequence in string literal not allowed in strict mode"); - - if (function_with_non_simple_parameter_list) { - syntax_error("Illegal 'use strict' directive in function with non-simple parameter list", statement->source_range().start); - } - } - - if (parsing_directives && is(*statement)) { - auto& expression_statement = static_cast(*statement); - auto& expression = expression_statement.expression(); - parsing_directives = is(expression); - } else { - parsing_directives = false; - } - } else { - expected("statement or declaration"); - consume(); - parsing_directives = false; - } - } - m_state.strict_mode = initial_strict_mode_state; - m_state.string_legacy_octal_escape_sequence_in_scope = false; + parse_statement_list(block); consume(TokenType::CurlyClose); - scope.add_to_scope_node(block); + return block; } @@ -1860,16 +1959,14 @@ NonnullRefPtr Parser::parse_function_node(u8 parse_options) TemporaryChange continue_context_rollback(m_state.in_continue_context, false); TemporaryChange class_field_initializer_rollback(m_state.in_class_field_initializer, false); - ScopePusher scope(*this, ScopePusher::Var, Parser::Scope::Function); - constexpr auto is_function_expression = IsSame; - auto is_generator = (parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0; + auto function_kind = (parse_options & FunctionNodeParseOptions::IsGeneratorFunction) != 0 ? FunctionKind::Generator : FunctionKind::Regular; String name; if (parse_options & FunctionNodeParseOptions::CheckForFunctionAndName) { consume(TokenType::Function); - if (!is_generator) { - is_generator = match(TokenType::Asterisk); - if (is_generator) { + if (function_kind == FunctionKind::Regular) { + function_kind = match(TokenType::Asterisk) ? FunctionKind::Generator : FunctionKind::Regular; + if (function_kind == FunctionKind::Generator) { consume(TokenType::Asterisk); parse_options = parse_options | FunctionNodeParseOptions::IsGeneratorFunction; } @@ -1882,7 +1979,7 @@ NonnullRefPtr Parser::parse_function_node(u8 parse_options) check_identifier_name_for_assignment_validity(name); } - TemporaryChange generator_change(m_state.in_generator_function_context, is_generator); + TemporaryChange generator_change(m_state.in_generator_function_context, function_kind == FunctionKind::Generator); consume(TokenType::ParenOpen); i32 function_length = -1; @@ -1899,55 +1996,17 @@ NonnullRefPtr Parser::parse_function_node(u8 parse_options) m_state.labels_in_scope = move(old_labels_in_scope); }); - m_state.function_parameters.append(parameters); + auto body = parse_function_body(parameters, function_kind); - bool is_strict = false; - auto body = parse_block_statement(is_strict, !is_simple_parameter_list(parameters)); + auto has_strict_directive = body->in_strict_mode(); - // If the function contains 'use strict' we need to check the parameters (again). - if (is_strict || is_generator) { - Vector parameter_names; - for (auto& parameter : parameters) { - parameter.binding.visit( - [&](FlyString const& parameter_name) { - check_identifier_name_for_assignment_validity(parameter_name, is_strict); - if (is_generator && parameter_name == "yield"sv) - syntax_error("Parameter name 'yield' not allowed in this context"); - - for (auto& previous_name : parameter_names) { - if (previous_name == parameter_name) { - syntax_error(String::formatted("Duplicate parameter '{}' not allowed in strict mode", parameter_name)); - } - } - - parameter_names.append(parameter_name); - }, - [&](NonnullRefPtr const& binding) { - binding->for_each_bound_name([&](auto& bound_name) { - if (is_generator && bound_name == "yield"sv) - syntax_error("Parameter name 'yield' not allowed in this context"); - - for (auto& previous_name : parameter_names) { - if (previous_name == bound_name) { - syntax_error(String::formatted("Duplicate parameter '{}' not allowed in strict mode", bound_name)); - break; - } - } - parameter_names.append(bound_name); - }); - }); - } - check_identifier_name_for_assignment_validity(name, is_strict); - } - - m_state.function_parameters.take_last(); - - scope.add_to_scope_node(body); + if (has_strict_directive) + check_identifier_name_for_assignment_validity(name, true); return create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, name, move(body), move(parameters), function_length, - is_generator ? FunctionKind::Generator : FunctionKind::Regular, is_strict); + function_kind, has_strict_directive); } Vector Parser::parse_formal_parameters(int& function_length, u8 parse_options) @@ -2270,49 +2329,6 @@ NonnullRefPtr Parser::parse_variable_declaration(bool for_l check_identifier_name_for_assignment_validity(name); if ((declaration_kind == DeclarationKind::Let || declaration_kind == DeclarationKind::Const) && name == "let"sv) syntax_error("Lexical binding may not be called 'let'"); - - // Check we do not have duplicates - auto check_declarations = [&](VariableDeclarator const& declarator) { - declarator.target().visit([&](NonnullRefPtr const& identifier) { - if (identifier->string() == name) - syntax_error(String::formatted("Identifier '{}' has already been declared", name), identifier_start.position()); }, - [&](auto const&) {}); - }; - - // In any previous let scope - if (!m_state.let_scopes.is_empty()) { - for (auto& decls : m_state.let_scopes.last()) { - for (auto& decl : decls.declarations()) { - check_declarations(decl); - } - } - } - - // or this declaration - if (declaration_kind == DeclarationKind::Let || declaration_kind == DeclarationKind::Const) { - // FIXME: We should check the var_scopes here as well however this has edges cases with for loops. - // See duplicated-variable-declarations.js. - - if (!m_state.function_parameters.is_empty() && m_state.current_scope->parent->type == Scope::Function) { - for (auto& parameter : m_state.function_parameters.last()) { - parameter.binding.visit( - [&](FlyString const& parameter_name) { - if (parameter_name == name) - syntax_error(String::formatted("Identifier '{}' has already been declared", name), identifier_start.position()); - }, - [&](NonnullRefPtr const& binding) { - binding->for_each_bound_name([&](auto& bound_name) { - if (bound_name == name) - syntax_error(String::formatted("Identifier '{}' has already been declared", name), identifier_start.position()); - }); - }); - } - } - - for (auto& declaration : declarations) { - check_declarations(declaration); - } - } } else if (auto pattern = parse_binding_pattern(declaration_kind != DeclarationKind::Var ? AllowDuplicates::No : AllowDuplicates::Yes, AllowMemberExpressions::No)) { target = pattern.release_nonnull(); @@ -2343,17 +2359,18 @@ NonnullRefPtr Parser::parse_variable_declaration(bool for_l RefPtr init; if (match(TokenType::Equals)) { consume(); - init = parse_expression(2); + // In a for loop 'in' can be ambiguous so we do not allow it + // 14.7.4 The for Statement, https://tc39.es/ecma262/#prod-ForStatement and 14.7.5 The for-in, for-of, and for-await-of Statements, https://tc39.es/ecma262/#prod-ForInOfStatement + if (for_loop_variable_declaration) + init = parse_expression(2, Associativity::Right, { TokenType::In }); + else + init = parse_expression(2); } else if (!for_loop_variable_declaration && declaration_kind == DeclarationKind::Const) { syntax_error("Missing initializer in 'const' variable declaration"); } else if (!for_loop_variable_declaration && target.has>()) { syntax_error("Missing initializer in destructuring assignment"); } - if (init && is(*init) && target.has>()) { - static_cast(*init).set_name_if_possible(target.get>()->string()); - } - declarations.append(create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, move(target).downcast, NonnullRefPtr>(), @@ -2369,23 +2386,6 @@ NonnullRefPtr Parser::parse_variable_declaration(bool for_l consume_or_insert_semicolon(); auto declaration = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, declaration_kind, move(declarations)); - if (declaration_kind == DeclarationKind::Var) { - m_state.var_scopes.last().append(declaration); - } else { - m_state.let_scopes.last().append(declaration); - - for (auto& declarator : declaration->declarations()) { - declarator.target().visit( - [&](const NonnullRefPtr& id) { - m_state.current_scope->lexical_declarations.set(id->string()); - }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - m_state.current_scope->lexical_declarations.set(name); - }); - }); - } - } return declaration; } @@ -2617,6 +2617,10 @@ NonnullRefPtr Parser::parse_switch_statement() NonnullRefPtrVector cases; + auto switch_statement = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(determinant)); + + ScopePusher switch_scope = ScopePusher::block_scope(*this, switch_statement); + auto has_default = false; while (match(TokenType::Case) || match(TokenType::Default)) { if (match(TokenType::Default)) { @@ -2624,12 +2628,12 @@ NonnullRefPtr Parser::parse_switch_statement() syntax_error("Multiple 'default' clauses in switch statement"); has_default = true; } - cases.append(parse_switch_case()); + switch_statement->add_case(parse_switch_case()); } consume(TokenType::CurlyClose); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(determinant), move(cases)); + return switch_statement; } NonnullRefPtr Parser::parse_with_statement() @@ -2659,16 +2663,10 @@ NonnullRefPtr Parser::parse_switch_case() NonnullRefPtrVector consequent; TemporaryChange break_change(m_state.in_break_context, true); - for (;;) { - if (match_declaration()) - consequent.append(parse_declaration()); - else if (match_statement()) - consequent.append(parse_statement()); - else - break; - } + auto switch_case = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(test)); + parse_statement_list(switch_case); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(test), move(consequent)); + return switch_case; } NonnullRefPtr Parser::parse_catch_clause() @@ -2692,13 +2690,29 @@ NonnullRefPtr Parser::parse_catch_clause() if (should_expect_parameter && parameter.is_empty() && !pattern_parameter) expected("an identifier or a binding pattern"); - if (pattern_parameter) - pattern_parameter->for_each_bound_name([this](auto& name) { check_identifier_name_for_assignment_validity(name); }); + HashTable bound_names; - if (!parameter.is_empty()) + if (pattern_parameter) { + pattern_parameter->for_each_bound_name( + [&](auto& name) { + check_identifier_name_for_assignment_validity(name); + bound_names.set(name); + }); + } + + if (!parameter.is_empty()) { check_identifier_name_for_assignment_validity(parameter); + bound_names.set(parameter); + } + ScopePusher catch_scope = ScopePusher::catch_scope(*this, pattern_parameter); auto body = parse_block_statement(); + + body->for_each_lexically_declared_name([&](auto const& name) { + if (bound_names.contains(name)) + syntax_error(String::formatted("Identifier '{}' already declared as catch parameter", name)); + }); + if (pattern_parameter) { return create_ast_node( { m_state.current_token.filename(), rule_start.position(), position() }, @@ -2721,15 +2735,17 @@ NonnullRefPtr Parser::parse_if_statement() // FunctionDeclaration[?Yield, ?Await, ~Default] was the sole StatementListItem // of a BlockStatement occupying that position in the source code. VERIFY(match(TokenType::Function)); - ScopePusher scope(*this, ScopePusher::Let, Parser::Scope::Block); auto block = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }); + ScopePusher block_scope = ScopePusher::block_scope(*this, *block); auto declaration = parse_declaration(); + VERIFY(m_state.current_scope_pusher); + block_scope.add_declaration(declaration); + VERIFY(is(*declaration)); auto& function_declaration = static_cast(*declaration); if (function_declaration.kind() == FunctionKind::Generator) syntax_error("Generator functions can only be declared in top-level or within a block"); block->append(move(declaration)); - scope.add_to_scope_node(block); return block; }; @@ -2771,14 +2787,24 @@ NonnullRefPtr Parser::parse_for_statement() RefPtr init; if (!match(TokenType::Semicolon)) { if (match_variable_declaration()) { - if (!match(TokenType::Var)) - scope_pusher.emplace(*this, ScopePusher::Let, Scope::Block); - init = parse_variable_declaration(true); + auto declaration = parse_variable_declaration(true); + if (declaration->declaration_kind() == DeclarationKind::Var) { + m_state.current_scope_pusher->add_declaration(declaration); + } else { + // This does not follow the normal declaration structure so we need additional checks. + HashTable bound_names; + declaration->for_each_bound_name([&](auto const& name) { + if (bound_names.set(name) != AK::HashSetResult::InsertedNewEntry) + syntax_error(String::formatted("Identifier '{}' already declared in for loop initializer", name), declaration->source_range().start); + }); + } + + init = move(declaration); if (match_for_in_of()) return parse_for_in_of_statement(*init); if (static_cast(*init).declaration_kind() == DeclarationKind::Const) { - for (auto& declaration : static_cast(*init).declarations()) { - if (!declaration.init()) + for (auto& variable : static_cast(*init).declarations()) { + if (!variable.init()) syntax_error("Missing initializer in 'const' variable declaration"); } } @@ -2806,6 +2832,7 @@ NonnullRefPtr Parser::parse_for_statement() TemporaryChange break_change(m_state.in_break_context, true); TemporaryChange continue_change(m_state.in_continue_context, true); + ScopePusher for_loop_scope = ScopePusher::for_loop_scope(*this, init); auto body = parse_statement(); return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(init), move(test), move(update), move(body)); @@ -2813,37 +2840,62 @@ NonnullRefPtr Parser::parse_for_statement() NonnullRefPtr Parser::parse_for_in_of_statement(NonnullRefPtr lhs) { + Variant, NonnullRefPtr> for_declaration = lhs; auto rule_start = push_start(); + + auto has_annexB_for_in_init_extension = false; + if (is(*lhs)) { - auto declarations = static_cast(*lhs).declarations(); - if (declarations.size() > 1) - syntax_error("multiple declarations not allowed in for..in/of"); - if (declarations.size() < 1) - syntax_error("need exactly one variable declaration in for..in/of"); - else if (declarations.first().init() != nullptr) - syntax_error("variable initializer not allowed in for..in/of"); - } else if (!lhs->is_identifier() && !is(*lhs) && !is(*lhs) && !is(*lhs)) { - syntax_error(String::formatted("Invalid left-hand side in for-loop ('{}')", lhs->class_name())); + auto& declaration = static_cast(*lhs); + if (declaration.declarations().size() > 1) { + syntax_error("Multiple declarations not allowed in for..in/of"); + } else if (declaration.declarations().size() < 1) { + syntax_error("Need exactly one variable declaration in for..in/of"); + } else { + // AnnexB extension B.3.5 Initializers in ForIn Statement Heads, https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads + auto& variable = declaration.declarations().first(); + if (variable.init()) { + if (m_state.strict_mode || declaration.declaration_kind() != DeclarationKind::Var || !variable.target().has>()) + syntax_error("Variable initializer not allowed in for..in/of"); + else + has_annexB_for_in_init_extension = true; + } + } + } else if (!lhs->is_identifier() && !is(*lhs)) { + bool valid = false; + if (is(*lhs) || is(*lhs)) { + auto synthesized_binding_pattern = synthesize_binding_pattern(static_cast(*lhs)); + if (synthesized_binding_pattern) { + for_declaration = synthesized_binding_pattern.release_nonnull(); + valid = true; + } + } + if (!valid) + syntax_error(String::formatted("Invalid left-hand side in for-loop ('{}')", lhs->class_name())); } auto in_or_of = consume(); + auto is_in = in_or_of.type() == TokenType::In; - if (in_or_of.type() != TokenType::In) { + if (!is_in) { if (is(*lhs)) { auto& member = static_cast(*lhs); if (member.object().is_identifier() && static_cast(member.object()).string() == "let"sv) syntax_error("For of statement may not start with let."); } + if (has_annexB_for_in_init_extension) + syntax_error("Variable initializer not allowed in for..of", rule_start.position()); } - auto rhs = parse_expression(0); + auto rhs = parse_expression(is_in ? 0 : 2); consume(TokenType::ParenClose); TemporaryChange break_change(m_state.in_break_context, true); TemporaryChange continue_change(m_state.in_continue_context, true); + ScopePusher for_loop_scope = ScopePusher::for_loop_scope(*this, lhs); auto body = parse_statement(); - if (in_or_of.type() == TokenType::In) - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), move(rhs), move(body)); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), move(rhs), move(body)); + if (is_in) + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(for_declaration), move(rhs), move(body)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(for_declaration), move(rhs), move(body)); } NonnullRefPtr Parser::parse_debugger_statement() diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 654b036408..8783cd3e77 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -33,6 +33,8 @@ struct FunctionNodeParseOptions { }; }; +class ScopePusher; + class Parser { public: explicit Parser(Lexer lexer, Program::Type program_type = Program::Type::Script); @@ -69,7 +71,7 @@ public: NonnullRefPtr parse_statement(AllowLabelledFunction allow_labelled_function = AllowLabelledFunction::No); NonnullRefPtr parse_block_statement(); - NonnullRefPtr parse_block_statement(bool& is_strict, bool function_with_non_simple_parameter_list = false); + NonnullRefPtr parse_function_body(Vector const& parameters, FunctionKind function_kind); NonnullRefPtr parse_return_statement(); NonnullRefPtr parse_variable_declaration(bool for_loop_variable_declaration = false); NonnullRefPtr parse_for_statement(); @@ -201,6 +203,9 @@ private: bool match_invalid_escaped_keyword() const; + bool parse_directive(ScopeNode& body); + void parse_statement_list(ScopeNode& output_node, AllowLabelledFunction allow_labelled_functions = AllowLabelledFunction::No); + struct RulePosition { AK_MAKE_NONCOPYABLE(RulePosition); AK_MAKE_NONMOVABLE(RulePosition); @@ -229,37 +234,11 @@ private: [[nodiscard]] RulePosition push_start() { return { *this, position() }; } - struct Scope : public RefCounted { - enum Type { - Function, - Block, - }; - struct HoistableDeclaration { - NonnullRefPtr declaration; - NonnullRefPtr scope; // where it is actually declared - }; - - Type type; - RefPtr parent; - - NonnullRefPtrVector function_declarations; - Vector hoisted_function_declarations; - - HashTable lexical_declarations; - - explicit Scope(Type, RefPtr); - RefPtr get_current_function_scope(); - }; - struct ParserState { Lexer lexer; Token current_token; Vector errors; - Vector> var_scopes; - Vector> let_scopes; - RefPtr current_scope; - - Vector&> function_parameters; + ScopePusher* current_scope_pusher; HashMap> labels_in_scope; bool strict_mode { false }; diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 3b42b4cf57..db880add23 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -423,6 +424,8 @@ ThrowCompletionOr perform_eval(Value x, GlobalObject& caller_realm, Calle return x; auto& vm = caller_realm.vm(); + auto& eval_realm = vm.running_execution_context().realm; + auto& code_string = x.as_string(); Parser parser { Lexer { code_string.string() } }; auto program = parser.parse_program(strict_caller == CallerMode::Strict); @@ -432,20 +435,249 @@ ThrowCompletionOr perform_eval(Value x, GlobalObject& caller_realm, Calle return vm.throw_completion(caller_realm, error.to_string()); } - auto& interpreter = vm.interpreter(); + auto strict_eval = strict_caller == CallerMode::Strict; + if (program->is_strict_mode()) + strict_eval = true; + + auto& running_context = vm.running_execution_context(); + + Environment* lexical_environment; + Environment* variable_environment; if (direct == EvalMode::Direct) { - auto result = interpreter.execute_statement(caller_realm, program).value_or(js_undefined()); - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); - return result; + lexical_environment = new_declarative_environment(*running_context.lexical_environment); + variable_environment = running_context.variable_environment; + } else { + lexical_environment = new_declarative_environment(eval_realm->global_environment()); + variable_environment = &eval_realm->global_environment(); } - TemporaryChange scope_change(vm.running_execution_context().lexical_environment, static_cast(&interpreter.realm().global_environment())); - TemporaryChange scope_change_strict(vm.running_execution_context().is_strict_mode, strict_caller == CallerMode::Strict); - auto result = interpreter.execute_statement(caller_realm, program).value_or(js_undefined()); + if (strict_eval) + variable_environment = lexical_environment; + + // 18. If runningContext is not already suspended, suspend runningContext. + // FIXME: We don't have this concept yet. + + ExecutionContext eval_context(vm.heap()); + eval_context.realm = eval_realm; + eval_context.variable_environment = variable_environment; + eval_context.lexical_environment = lexical_environment; + vm.push_execution_context(eval_context, eval_realm->global_object()); + + ScopeGuard pop_guard = [&] { + vm.pop_execution_context(); + }; + + TRY(eval_declaration_instantiation(vm, eval_realm->global_object(), program, variable_environment, lexical_environment, strict_eval)); + + auto& interpreter = vm.interpreter(); + TemporaryChange scope_change_strict(vm.running_execution_context().is_strict_mode, strict_eval); + // Note: We specifically use evaluate_statements here since we don't want to use global_declaration_instantiation from Program::execute. + auto eval_result = program->evaluate_statements(interpreter, caller_realm); + if (auto* exception = vm.exception()) return throw_completion(exception->value()); - return result; + else + return eval_result.value_or(js_undefined()); +} + +// 19.2.1.3 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/ecma262/#sec-evaldeclarationinstantiation +ThrowCompletionOr eval_declaration_instantiation(VM& vm, GlobalObject& global_object, Program const& program, Environment* variable_environment, Environment* lexical_environment, bool strict) +{ + // FIXME: I'm not sure if the global object is correct here. And this is quite a crucial spot! + GlobalEnvironment* global_var_environment = variable_environment->is_global_environment() ? static_cast(variable_environment) : nullptr; + + if (!strict) { + if (global_var_environment) { + program.for_each_var_declared_name([&](auto const& name) { + if (global_var_environment->has_lexical_declaration(name)) { + vm.throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Var already declared lexically"); + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + } + + auto* this_environment = lexical_environment; + while (this_environment != variable_environment) { + if (!is(*this_environment)) { + program.for_each_var_declared_name([&](auto const& name) { + if (this_environment->has_binding(name)) { + vm.throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Var already declared lexically"); + return IterationDecision::Break; + } + // FIXME: NOTE: Annex B.3.4 defines alternate semantics for the above step. + // In particular it only throw the syntax error if it is not an environment from a catchclause. + return IterationDecision::Continue; + }); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + } + + this_environment = this_environment->outer_environment(); + VERIFY(this_environment); + } + } + + HashTable declared_function_names; + Vector functions_to_initialize; + program.for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) { + if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry) + return IterationDecision::Continue; + + if (global_var_environment) { + auto function_definable = global_var_environment->can_declare_global_function(function.name()); + if (vm.exception()) + return IterationDecision::Break; + if (!function_definable) { + vm.throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Cannot define global function"); + return IterationDecision::Break; + } + } + functions_to_initialize.append(function); + return IterationDecision::Continue; + }); + + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + + if (!strict) { + // The spec here uses 'declaredVarNames' but that has not been declared yet. + HashTable hoisted_functions; + program.for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { + auto& function_name = function_declaration.name(); + auto* this_environment = lexical_environment; + + while (this_environment != variable_environment) { + if (!is(*this_environment) && this_environment->has_binding(function_name)) + return IterationDecision::Continue; + + this_environment = this_environment->outer_environment(); + VERIFY(this_environment); + } + + if (global_var_environment) { + if (global_var_environment->has_lexical_declaration(function_name)) + return IterationDecision::Continue; + auto var_definable = global_var_environment->can_declare_global_var(function_name); + if (vm.exception()) + return IterationDecision::Break; + if (!var_definable) + return IterationDecision::Continue; + } + + if (!declared_function_names.contains(function_name) && !hoisted_functions.contains(function_name)) { + if (global_var_environment) { + global_var_environment->create_global_var_binding(function_name, true); + if (vm.exception()) + return IterationDecision::Break; + } else { + if (!variable_environment->has_binding(function_name)) { + variable_environment->create_mutable_binding(global_object, function_name, true); + variable_environment->initialize_binding(global_object, function_name, js_undefined()); + VERIFY(!vm.exception()); + } + } + + hoisted_functions.set(function_name); + } + + function_declaration.set_should_do_additional_annexB_steps(); + + return IterationDecision::Continue; + }); + + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + } + + HashTable declared_var_names; + + program.for_each_var_scoped_variable_declaration([&](VariableDeclaration const& declaration) { + declaration.for_each_bound_name([&](auto const& name) { + if (!declared_function_names.contains(name)) { + if (global_var_environment) { + auto variable_definable = global_var_environment->can_declare_global_var(name); + if (vm.exception()) + return IterationDecision::Break; + if (!variable_definable) { + vm.throw_exception(global_object, ErrorType::FixmeAddAnErrorStringWithMessage, "Cannot define global var"); + return IterationDecision::Break; + } + } + declared_var_names.set(name); + } + return IterationDecision::Continue; + }); + if (vm.exception()) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + + // 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a global Environment Record and the global object is a Proxy exotic object. + + program.for_each_lexically_scoped_declaration([&](Declaration const& declaration) { + declaration.for_each_bound_name([&](auto const& name) { + if (declaration.is_constant_declaration()) + lexical_environment->create_immutable_binding(global_object, name, true); + else + lexical_environment->create_mutable_binding(global_object, name, false); + if (vm.exception()) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + if (vm.exception()) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + + for (auto& declaration : functions_to_initialize) { + auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), lexical_environment, declaration.kind(), declaration.is_strict_mode()); + if (global_var_environment) { + global_var_environment->create_global_function_binding(declaration.name(), function, true); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + } else { + auto binding_exists = variable_environment->has_binding(declaration.name()); + + if (!binding_exists) { + variable_environment->create_mutable_binding(global_object, declaration.name(), true); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + variable_environment->initialize_binding(global_object, declaration.name(), function); + } else { + variable_environment->set_mutable_binding(global_object, declaration.name(), function, false); + } + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + } + } + + for (auto& var_name : declared_var_names) { + if (global_var_environment) { + global_var_environment->create_global_var_binding(var_name, true); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + } else { + auto binding_exists = variable_environment->has_binding(var_name); + + if (!binding_exists) { + variable_environment->create_mutable_binding(global_object, var_name, true); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + variable_environment->initialize_binding(global_object, var_name, js_undefined()); + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + } + } + } + + return {}; } // 10.4.4.6 CreateUnmappedArgumentsObject ( argumentsList ), https://tc39.es/ecma262/#sec-createunmappedargumentsobject @@ -554,15 +786,11 @@ Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObje // 3. Perform map.[[DefineOwnProperty]](! ToString(𝔽(index)), PropertyDescriptor { [[Set]]: p, [[Get]]: g, [[Enumerable]]: false, [[Configurable]]: true }). object->parameter_map().define_native_accessor( String::number(index), - [&environment, name](VM&, GlobalObject&) -> Value { - auto variable = environment.get_from_environment(name); - if (!variable.has_value()) - return {}; - return variable->value; + [&environment, name](VM&, GlobalObject& global_object_getter) -> Value { + return environment.get_binding_value(global_object_getter, name, false); }, - [&environment, name](VM& vm, GlobalObject&) { - auto value = vm.argument(0); - environment.put_into_environment(name, Variable { value, DeclarationKind::Var }); + [&environment, name](VM& vm, GlobalObject& global_object_setter) { + environment.set_mutable_binding(global_object_setter, name, vm.argument(0), false); return js_undefined(); }, Attribute::Configurable); diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h index 2b2b9b755a..73e0cc60aa 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h @@ -42,6 +42,8 @@ enum class EvalMode { }; ThrowCompletionOr perform_eval(Value, GlobalObject&, CallerMode, EvalMode); +ThrowCompletionOr eval_declaration_instantiation(VM& vm, GlobalObject& global_object, Program const& program, Environment* variable_environment, Environment* lexical_environment, bool strict); + // 10.1.13 OrdinaryCreateFromConstructor ( constructor, intrinsicDefaultProto [ , internalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor template ThrowCompletionOr ordinary_create_from_constructor(GlobalObject& global_object, FunctionObject const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)(), Args&&... args) diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp index 397cbb4b0a..5e5ea075fa 100644 --- a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp @@ -43,9 +43,9 @@ Value BoundFunction::construct(FunctionObject& new_target) return m_bound_target_function->construct(new_target); } -FunctionEnvironment* BoundFunction::create_environment(FunctionObject& function_being_invoked) +FunctionEnvironment* BoundFunction::new_function_environment(Object* new_target) { - return m_bound_target_function->create_environment(function_being_invoked); + return m_bound_target_function->new_function_environment(new_target); } void BoundFunction::visit_edges(Visitor& visitor) diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.h b/Userland/Libraries/LibJS/Runtime/BoundFunction.h index 0b83c78ad6..ccf92b6499 100644 --- a/Userland/Libraries/LibJS/Runtime/BoundFunction.h +++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.h @@ -20,7 +20,7 @@ public: virtual Value call() override; virtual Value construct(FunctionObject& new_target) override; - virtual FunctionEnvironment* create_environment(FunctionObject&) override; + virtual FunctionEnvironment* new_function_environment(Object* new_target) override; virtual const FlyString& name() const override { return m_name; } virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); } virtual bool has_constructor() const override { return true; } diff --git a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp index e192b56713..5f56b2ecb9 100644 --- a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp +++ b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.cpp @@ -23,12 +23,6 @@ DeclarativeEnvironment::DeclarativeEnvironment(Environment* parent_scope) { } -DeclarativeEnvironment::DeclarativeEnvironment(HashMap variables, Environment* parent_scope) - : Environment(parent_scope) - , m_variables(move(variables)) -{ -} - DeclarativeEnvironment::~DeclarativeEnvironment() { } @@ -36,28 +30,10 @@ DeclarativeEnvironment::~DeclarativeEnvironment() void DeclarativeEnvironment::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); - for (auto& it : m_variables) - visitor.visit(it.value.value); for (auto& it : m_bindings) visitor.visit(it.value.value); } -Optional DeclarativeEnvironment::get_from_environment(FlyString const& name) const -{ - return m_variables.get(name); -} - -bool DeclarativeEnvironment::put_into_environment(FlyString const& name, Variable variable) -{ - m_variables.set(name, variable); - return true; -} - -bool DeclarativeEnvironment::delete_from_environment(FlyString const& name) -{ - return m_variables.remove(name); -} - // 9.1.1.1.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n bool DeclarativeEnvironment::has_binding(FlyString const& name) const { @@ -156,4 +132,14 @@ bool DeclarativeEnvironment::delete_binding(GlobalObject&, FlyString const& name return true; } +void DeclarativeEnvironment::initialize_or_set_mutable_binding(Badge, GlobalObject& global_object, FlyString const& name, Value value) +{ + auto it = m_bindings.find(name); + VERIFY(it != m_bindings.end()); + if (!it->value.initialized) + initialize_binding(global_object, name, value); + else + set_mutable_binding(global_object, name, value, false); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.h b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.h index ebd1ad1252..07e7775399 100644 --- a/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.h +++ b/Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.h @@ -19,16 +19,8 @@ class DeclarativeEnvironment : public Environment { public: DeclarativeEnvironment(); explicit DeclarativeEnvironment(Environment* parent_scope); - DeclarativeEnvironment(HashMap variables, Environment* parent_scope); virtual ~DeclarativeEnvironment() override; - // ^Environment - virtual Optional get_from_environment(FlyString const&) const override; - virtual bool put_into_environment(FlyString const&, Variable) override; - virtual bool delete_from_environment(FlyString const&) override; - - HashMap const& variables() const { return m_variables; } - virtual bool has_binding(FlyString const& name) const override; virtual void create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted) override; virtual void create_immutable_binding(GlobalObject&, FlyString const& name, bool strict) override; @@ -37,14 +29,14 @@ public: virtual Value get_binding_value(GlobalObject&, FlyString const& name, bool strict) override; virtual bool delete_binding(GlobalObject&, FlyString const& name) override; + void initialize_or_set_mutable_binding(Badge, GlobalObject& global_object, FlyString const& name, Value value); + protected: virtual void visit_edges(Visitor&) override; private: virtual bool is_declarative_environment() const override { return true; } - HashMap m_variables; - struct Binding { Value value; bool strict { false }; diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index df3fd9e1d0..94bd59e967 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -107,86 +108,269 @@ void ECMAScriptFunctionObject::visit_edges(Visitor& visitor) } } -FunctionEnvironment* ECMAScriptFunctionObject::create_environment(FunctionObject& function_being_invoked) +// 9.1.2.4 NewFunctionEnvironment ( F, newTarget ), https://tc39.es/ecma262/#sec-newfunctionenvironment +FunctionEnvironment* ECMAScriptFunctionObject::new_function_environment(Object* new_target) { - HashMap variables; + auto* environment = heap().allocate(global_object(), m_environment); + environment->set_function_object(*this); + if (this_mode() == ThisMode::Lexical) { + environment->set_this_binding_status(FunctionEnvironment::ThisBindingStatus::Lexical); + } + + environment->set_new_target(new_target ? new_target : js_undefined()); + return environment; +} + +// 10.2.11 FunctionDeclarationInstantiation ( func, argumentsList ), https://tc39.es/ecma262/#sec-functiondeclarationinstantiation +ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantiation(Interpreter* interpreter) +{ + auto& vm = this->vm(); + + auto& callee_context = vm.running_execution_context(); + + // Needed to extract declarations and functions + ScopeNode const* scope_body = nullptr; + if (is(*m_ecmascript_code)) + scope_body = static_cast(m_ecmascript_code.ptr()); + + bool has_parameter_expressions = false; + + // FIXME: Maybe compute has duplicates at parse time? (We need to anyway since it's an error in some cases) + + bool has_duplicates = false; + HashTable parameter_names; for (auto& parameter : m_formal_parameters) { + if (parameter.default_value) + has_parameter_expressions = true; + parameter.binding.visit( - [&](const FlyString& name) { variables.set(name, { js_undefined(), DeclarationKind::Var }); }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - variables.set(name, { js_undefined(), DeclarationKind::Var }); + [&](FlyString const& name) { + if (parameter_names.set(name) != AK::HashSetResult::InsertedNewEntry) + has_duplicates = true; + }, + [&](NonnullRefPtr const& pattern) { + if (pattern->contains_expression()) + has_parameter_expressions = true; + + pattern->for_each_bound_name([&](auto& name) { + if (parameter_names.set(name) != AK::HashSetResult::InsertedNewEntry) + has_duplicates = true; }); }); } - if (is(ecmascript_code())) { - for (auto& declaration : static_cast(ecmascript_code()).variables()) { - for (auto& declarator : declaration.declarations()) { - declarator.target().visit( - [&](const NonnullRefPtr& id) { - variables.set(id->string(), { js_undefined(), declaration.declaration_kind() }); - }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - variables.set(name, { js_undefined(), declaration.declaration_kind() }); - }); - }); - } + auto needs_argument_object = this_mode() != ThisMode::Lexical; + if (parameter_names.contains(vm.names.arguments.as_string())) + needs_argument_object = false; + + HashTable function_names; + Vector functions_to_initialize; + + if (scope_body) { + 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 arguments_name = vm.names.arguments.as_string(); + + if (!has_parameter_expressions && function_names.contains(arguments_name)) + needs_argument_object = false; + + if (!has_parameter_expressions && needs_argument_object) { + scope_body->for_each_lexically_declared_name([&](auto const& name) { + if (name == arguments_name) + needs_argument_object = false; + return IterationDecision::Continue; + }); } } - auto* environment = heap().allocate(global_object(), m_environment, move(variables)); - environment->set_function_object(static_cast(function_being_invoked)); - if (m_is_arrow_function) { - environment->set_this_binding_status(FunctionEnvironment::ThisBindingStatus::Lexical); - if (is(m_environment)) - environment->set_new_target(static_cast(m_environment)->new_target()); + Environment* environment; + + if (is_strict_mode() || !has_parameter_expressions) { + environment = callee_context.lexical_environment; + } else { + environment = new_declarative_environment(*callee_context.lexical_environment); + VERIFY(callee_context.variable_environment == callee_context.lexical_environment); + callee_context.lexical_environment = environment; } - return environment; + + for (auto const& parameter_name : parameter_names) { + if (environment->has_binding(parameter_name)) + continue; + + environment->create_mutable_binding(global_object(), parameter_name, false); + if (has_duplicates) + environment->initialize_binding(global_object(), parameter_name, js_undefined()); + VERIFY(!vm.exception()); + } + + if (needs_argument_object) { + Object* arguments_object; + if (is_strict_mode() || !has_simple_parameter_list()) + arguments_object = create_unmapped_arguments_object(global_object(), vm.running_execution_context().arguments); + else + arguments_object = create_mapped_arguments_object(global_object(), *this, formal_parameters(), vm.running_execution_context().arguments, *environment); + + if (is_strict_mode()) + environment->create_immutable_binding(global_object(), vm.names.arguments.as_string(), false); + else + environment->create_mutable_binding(global_object(), vm.names.arguments.as_string(), false); + + environment->initialize_binding(global_object(), vm.names.arguments.as_string(), arguments_object); + parameter_names.set(vm.names.arguments.as_string()); + } + + // We now treat parameterBindings as parameterNames. + + // The spec makes an iterator here to do IteratorBindingInitialization but we just do it manually + auto& execution_context_arguments = vm.running_execution_context().arguments; + + for (size_t i = 0; i < m_formal_parameters.size(); ++i) { + auto& parameter = m_formal_parameters[i]; + parameter.binding.visit( + [&](auto const& param) { + Value argument_value; + if (parameter.is_rest) { + auto* array = Array::create(global_object(), 0); + for (size_t rest_index = i; rest_index < execution_context_arguments.size(); ++rest_index) + array->indexed_properties().append(execution_context_arguments[rest_index]); + argument_value = move(array); + } else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) { + argument_value = execution_context_arguments[i]; + } else if (parameter.default_value) { + // FIXME: Support default arguments in the bytecode world! + if (interpreter) + argument_value = parameter.default_value->execute(*interpreter, global_object()); + if (vm.exception()) + return; + } else { + argument_value = js_undefined(); + } + + Environment* used_environment = has_duplicates ? nullptr : environment; + + if constexpr (IsSame) { + Reference reference = vm.resolve_binding(param, used_environment); + if (vm.exception()) + return; + // Here the difference from hasDuplicates is important + if (has_duplicates) + reference.put_value(global_object(), argument_value); + else + reference.initialize_referenced_binding(global_object(), argument_value); + } else if (IsSame const&, decltype(param)>) { + // Here the difference from hasDuplicates is important + auto result = vm.binding_initialization(param, argument_value, used_environment, global_object()); + if (result.is_error()) + return; + } + + if (vm.exception()) + return; + }); + + if (auto* exception = vm.exception()) + return throw_completion(exception->value()); + } + + Environment* var_environment; + + HashTable instantiated_var_names; + + if (!has_parameter_expressions) { + if (scope_body) { + scope_body->for_each_var_declared_name([&](auto const& name) { + if (!parameter_names.contains(name) && instantiated_var_names.set(name) == AK::HashSetResult::InsertedNewEntry) { + environment->create_mutable_binding(global_object(), name, false); + environment->initialize_binding(global_object(), name, js_undefined()); + } + }); + } + var_environment = environment; + } else { + var_environment = new_declarative_environment(*environment); + callee_context.variable_environment = var_environment; + + if (scope_body) { + scope_body->for_each_var_declared_name([&](auto const& name) { + if (instantiated_var_names.set(name) != AK::HashSetResult::InsertedNewEntry) + return IterationDecision::Continue; + var_environment->create_mutable_binding(global_object(), name, false); + + Value initial_value; + if (!parameter_names.contains(name) || function_names.contains(name)) + initial_value = js_undefined(); + else + initial_value = environment->get_binding_value(global_object(), name, false); + + var_environment->initialize_binding(global_object(), name, initial_value); + + return IterationDecision::Continue; + }); + } + } + + // B.3.2.1 Changes to FunctionDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation + if (!m_strict && scope_body) { + 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 IterationDecision::Continue; + // 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()) { + var_environment->create_mutable_binding(global_object(), function_name, false); + VERIFY(!vm.exception()); + var_environment->initialize_binding(global_object(), function_name, js_undefined()); + instantiated_var_names.set(function_name); + } + + function_declaration.set_should_do_additional_annexB_steps(); + return IterationDecision::Continue; + }); + } + + Environment* lex_environment; + + if (!is_strict_mode()) + lex_environment = new_declarative_environment(*var_environment); + else + lex_environment = var_environment; + + callee_context.lexical_environment = lex_environment; + + if (!scope_body) + return {}; + + scope_body->for_each_lexically_scoped_declaration([&](Declaration const& declaration) { + declaration.for_each_bound_name([&](auto const& name) { + if (declaration.is_constant_declaration()) + lex_environment->create_immutable_binding(global_object(), name, true); + else + lex_environment->create_mutable_binding(global_object(), name, false); + return IterationDecision::Continue; + }); + }); + + VERIFY(!vm.exception()); + + for (auto& declaration : functions_to_initialize) { + auto* function = ECMAScriptFunctionObject::create(global_object(), declaration.name(), declaration.body(), declaration.parameters(), declaration.function_length(), lex_environment, declaration.kind(), declaration.is_strict_mode()); + var_environment->set_mutable_binding(global_object(), declaration.name(), function, false); + } + + return {}; } Value ECMAScriptFunctionObject::execute_function_body() { auto& vm = this->vm(); - - Interpreter* ast_interpreter = nullptr; auto* bytecode_interpreter = Bytecode::Interpreter::current(); - auto prepare_arguments = [&] { - auto& execution_context_arguments = vm.running_execution_context().arguments; - for (size_t i = 0; i < m_formal_parameters.size(); ++i) { - auto& parameter = m_formal_parameters[i]; - parameter.binding.visit( - [&](const auto& param) { - Value argument_value; - if (parameter.is_rest) { - auto* array = Array::create(global_object(), 0); - for (size_t rest_index = i; rest_index < execution_context_arguments.size(); ++rest_index) - array->indexed_properties().append(execution_context_arguments[rest_index]); - argument_value = move(array); - } else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) { - argument_value = execution_context_arguments[i]; - } else if (parameter.default_value) { - // FIXME: Support default arguments in the bytecode world! - if (!bytecode_interpreter) - argument_value = parameter.default_value->execute(*ast_interpreter, global_object()); - if (vm.exception()) - return; - } else { - argument_value = js_undefined(); - } - - vm.assign(param, argument_value, global_object(), true, vm.lexical_environment()); - }); - - if (vm.exception()) - return; - } - }; - if (bytecode_interpreter) { - prepare_arguments(); + // FIXME: pass something to evaluate default arguments with + TRY_OR_DISCARD(function_declaration_instantiation(nullptr)); if (!m_bytecode_executable.has_value()) { m_bytecode_executable = Bytecode::Generator::generate(m_ecmascript_code, m_kind == FunctionKind::Generator); auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(); @@ -206,7 +390,7 @@ Value ECMAScriptFunctionObject::execute_function_body() } else { VERIFY(m_kind != FunctionKind::Generator); OwnPtr local_interpreter; - ast_interpreter = vm.interpreter_if_exists(); + Interpreter* ast_interpreter = vm.interpreter_if_exists(); if (!ast_interpreter) { local_interpreter = Interpreter::create_with_existing_realm(*realm()); @@ -215,11 +399,9 @@ Value ECMAScriptFunctionObject::execute_function_body() VM::InterpreterExecutionScope scope(*ast_interpreter); - prepare_arguments(); - if (vm.exception()) - return {}; + TRY_OR_DISCARD(function_declaration_instantiation(ast_interpreter)); - return ast_interpreter->execute_statement(global_object(), m_ecmascript_code, ScopeType::Function); + return m_ecmascript_code->execute(*ast_interpreter, global_object()); } } diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index f88837dad7..9e5f5eebcf 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -78,9 +78,10 @@ protected: private: virtual bool is_ecmascript_function_object() const override { return true; } - virtual FunctionEnvironment* create_environment(FunctionObject&) override; + virtual FunctionEnvironment* new_function_environment(Object* new_target) override; virtual void visit_edges(Visitor&) override; + ThrowCompletionOr function_declaration_instantiation(Interpreter*); Value execute_function_body(); // Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects diff --git a/Userland/Libraries/LibJS/Runtime/Environment.h b/Userland/Libraries/LibJS/Runtime/Environment.h index 265d54fc6b..711aa3409a 100644 --- a/Userland/Libraries/LibJS/Runtime/Environment.h +++ b/Userland/Libraries/LibJS/Runtime/Environment.h @@ -27,10 +27,6 @@ public: virtual void initialize(GlobalObject&) override; - virtual Optional get_from_environment(FlyString const&) const = 0; - virtual bool put_into_environment(FlyString const&, Variable) = 0; - virtual bool delete_from_environment(FlyString const&) = 0; - virtual bool has_this_binding() const { return false; } virtual Value get_this_binding(GlobalObject&) const { return {}; } diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 61ffac4bbb..5b6d90dc23 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -23,6 +23,7 @@ M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \ M(Convert, "Cannot convert {} to {}") \ M(DataViewOutOfRangeByteOffset, "Data view byte offset {} is out of range for buffer with length {}") \ + M(DerivedConstructorReturningInvalidValue, "Derived constructor return invalid value") \ M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \ M(DetachedArrayBuffer, "ArrayBuffer is detached") \ M(DivisionByZero, "Division by zero") \ @@ -223,6 +224,7 @@ M(BadArgCountAtLeastOne, "{}() needs at least one argument") \ M(BadArgCountMany, "{}() needs {} arguments") \ M(FixmeAddAnErrorString, "FIXME: Add a string for this error.") \ + M(FixmeAddAnErrorStringWithMessage, "FIXME: Add a real string for this error '{}'") \ M(NotEnoughMemoryToAllocate, "Not enough memory to allocate {} bytes") namespace JS { diff --git a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp index 1bb8e7252f..3ec7edc950 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp +++ b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp @@ -11,8 +11,8 @@ namespace JS { -FunctionEnvironment::FunctionEnvironment(Environment* parent_scope, HashMap variables) - : DeclarativeEnvironment(move(variables), parent_scope) +FunctionEnvironment::FunctionEnvironment(Environment* parent_scope) + : DeclarativeEnvironment(parent_scope) { } diff --git a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.h b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.h index dbf68df575..29a486c205 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.h +++ b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.h @@ -21,7 +21,7 @@ public: Uninitialized, }; - FunctionEnvironment(Environment* parent_scope, HashMap variables); + explicit FunctionEnvironment(Environment* parent_scope); virtual ~FunctionEnvironment() override; // [[ThisValue]] diff --git a/Userland/Libraries/LibJS/Runtime/FunctionObject.h b/Userland/Libraries/LibJS/Runtime/FunctionObject.h index 4b898e3795..659f6373b6 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/FunctionObject.h @@ -21,7 +21,7 @@ public: virtual Value call() = 0; virtual Value construct(FunctionObject& new_target) = 0; virtual const FlyString& name() const = 0; - virtual FunctionEnvironment* create_environment(FunctionObject&) = 0; + virtual FunctionEnvironment* new_function_environment(Object* new_target) = 0; BoundFunction* bind(Value bound_this_value, Vector arguments); diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp index 14026b4fcc..6075663a5a 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp @@ -30,27 +30,9 @@ void GlobalEnvironment::visit_edges(Cell::Visitor& visitor) visitor.visit(m_declarative_record); } -Optional GlobalEnvironment::get_from_environment(FlyString const& name) const -{ - // FIXME: This should be a "composite" of the object record and the declarative record. - return m_object_record->get_from_environment(name); -} - -bool GlobalEnvironment::put_into_environment(FlyString const& name, Variable variable) -{ - // FIXME: This should be a "composite" of the object record and the declarative record. - return m_object_record->put_into_environment(name, variable); -} - -bool GlobalEnvironment::delete_from_environment(FlyString const& name) -{ - // FIXME: This should be a "composite" of the object record and the declarative record. - return object_record().delete_from_environment(name); -} - Value GlobalEnvironment::get_this_binding(GlobalObject&) const { - return &global_object(); + return m_global_this_value; } // 9.1.1.4.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-global-environment-records-hasbinding-n diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.h b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.h index d126747365..a3a313018d 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.h +++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.h @@ -16,9 +16,6 @@ class GlobalEnvironment final : public Environment { public: GlobalEnvironment(GlobalObject&, Object& this_value); - virtual Optional get_from_environment(FlyString const&) const override; - virtual bool put_into_environment(FlyString const&, Variable) override; - virtual bool delete_from_environment(FlyString const&) override; virtual bool has_this_binding() const final { return true; } virtual Value get_this_binding(GlobalObject&) const final; diff --git a/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp index 4f3916cc4b..60f02c945a 100644 --- a/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/NativeFunction.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -55,14 +56,22 @@ Value NativeFunction::construct(FunctionObject&) return {}; } -FunctionEnvironment* NativeFunction::create_environment(FunctionObject&) +FunctionEnvironment* NativeFunction::new_function_environment(Object* new_target) { - return nullptr; + // Simplified version of 9.1.2.4 NewFunctionEnvironment ( F, newTarget ) + Environment* parent_scope = nullptr; + if (!vm().execution_context_stack().is_empty()) + parent_scope = vm().lexical_environment(); + + auto* environment = heap().allocate(global_object(), parent_scope); + environment->set_new_target(new_target ? new_target : js_undefined()); + + return environment; } bool NativeFunction::is_strict_mode() const { - return vm().in_strict_mode(); + return true; } } diff --git a/Userland/Libraries/LibJS/Runtime/NativeFunction.h b/Userland/Libraries/LibJS/Runtime/NativeFunction.h index d42b156144..a8d6632817 100644 --- a/Userland/Libraries/LibJS/Runtime/NativeFunction.h +++ b/Userland/Libraries/LibJS/Runtime/NativeFunction.h @@ -33,7 +33,7 @@ protected: explicit NativeFunction(Object& prototype); private: - virtual FunctionEnvironment* create_environment(FunctionObject&) override final; + virtual FunctionEnvironment* new_function_environment(Object* new_target) override final; virtual bool is_native_function() const final { return true; } FlyString m_name; diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index e3718be6f4..c45ce91487 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -469,6 +469,34 @@ MarkedValueList Object::enumerable_own_property_names(PropertyKind kind) const return properties; } +// 7.3.25 CopyDataProperties ( target, source, excludedItems ), https://tc39.es/ecma262/#sec-copydataproperties +ThrowCompletionOr Object::copy_data_properties(Value source, HashTable const& seen_names, GlobalObject& global_object) +{ + if (source.is_nullish()) + return this; + + auto* from_object = source.to_object(global_object); + VERIFY(from_object); + + for (auto& next_key_value : TRY(from_object->internal_own_property_keys())) { + auto next_key = PropertyName::from_value(global_object, next_key_value); + if (seen_names.contains(next_key)) + continue; + + auto desc = TRY(from_object->internal_get_own_property(next_key)); + + if (desc.has_value() && desc->attributes().is_enumerable()) { + auto prop_value = from_object->get(next_key); + if (auto* thrown_exception = vm().exception()) + return JS::throw_completion(thrown_exception->value()); + create_data_property_or_throw(next_key, prop_value); + if (auto* thrown_exception = vm().exception()) + return JS::throw_completion(thrown_exception->value()); + } + } + return this; +} + // 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots // 10.1.1 [[GetPrototypeOf]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index 0f828293fa..c7406c2d8c 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -88,6 +88,7 @@ public: bool set_integrity_level(IntegrityLevel); bool test_integrity_level(IntegrityLevel) const; MarkedValueList enumerable_own_property_names(PropertyKind kind) const; + ThrowCompletionOr copy_data_properties(Value source, HashTable const& seen_names, GlobalObject& global_object); // 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp index 2da747851c..f597d8bcb2 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp +++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp @@ -24,24 +24,6 @@ void ObjectEnvironment::visit_edges(Cell::Visitor& visitor) visitor.visit(&m_binding_object); } -Optional ObjectEnvironment::get_from_environment(FlyString const& name) const -{ - if (!m_binding_object.storage_has(name)) - return {}; - auto value = m_binding_object.get(name); - return Variable { value, DeclarationKind::Var }; -} - -bool ObjectEnvironment::put_into_environment(FlyString const& name, Variable variable) -{ - return m_binding_object.set(name, variable.value, Object::ShouldThrowExceptions::No); -} - -bool ObjectEnvironment::delete_from_environment(FlyString const& name) -{ - return TRY_OR_DISCARD(m_binding_object.internal_delete(name)); -} - // 9.1.1.2.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n bool ObjectEnvironment::has_binding(FlyString const& name) const { diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.h b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.h index e1cebee4c0..d9f16fc7d6 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.h +++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.h @@ -20,10 +20,6 @@ public: }; ObjectEnvironment(Object& binding_object, IsWithEnvironment, Environment* outer_environment); - virtual Optional get_from_environment(FlyString const&) const override; - virtual bool put_into_environment(FlyString const&, Variable) override; - virtual bool delete_from_environment(FlyString const&) override; - virtual bool has_binding(FlyString const& name) const override; virtual void create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted) override; virtual void create_immutable_binding(GlobalObject&, FlyString const& name, bool strict) override; diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp index 790444f324..d61dabc6e3 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -891,10 +891,10 @@ const FlyString& ProxyObject::name() const return static_cast(m_target).name(); } -FunctionEnvironment* ProxyObject::create_environment(FunctionObject& function_being_invoked) +FunctionEnvironment* ProxyObject::new_function_environment(Object* new_target) { VERIFY(is_function()); - return static_cast(m_target).create_environment(function_being_invoked); + return static_cast(m_target).new_function_environment(new_target); } } diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.h b/Userland/Libraries/LibJS/Runtime/ProxyObject.h index bcd203a52d..1e1dab55fc 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyObject.h +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.h @@ -24,7 +24,7 @@ public: virtual Value call() override; virtual Value construct(FunctionObject& new_target) override; virtual const FlyString& name() const override; - virtual FunctionEnvironment* create_environment(FunctionObject&) override; + virtual FunctionEnvironment* new_function_environment(Object* new_target) override; virtual bool has_constructor() const override { return true; } const Object& target() const { return m_target; } diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp index f34e0fcce0..da6cccab20 100644 --- a/Userland/Libraries/LibJS/Runtime/Reference.cpp +++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp @@ -31,15 +31,6 @@ void Reference::put_value(GlobalObject& global_object, Value value) } if (is_property_reference()) { - // FIXME: This is an ad-hoc hack until we support proper variable bindings. - if (!m_base_value.is_object() && vm.in_strict_mode()) { - if (m_base_value.is_nullish()) - vm.throw_exception(global_object, ErrorType::ReferenceNullishSetProperty, m_name, m_base_value.to_string_without_side_effects()); - else - vm.throw_exception(global_object, ErrorType::ReferencePrimitiveSetProperty, m_name, m_base_value.typeof(), m_base_value.to_string_without_side_effects()); - return; - } - auto* base_obj = m_base_value.to_object(global_object); if (!base_obj) return; @@ -56,29 +47,8 @@ void Reference::put_value(GlobalObject& global_object, Value value) } VERIFY(m_base_type == BaseType::Environment); - // FIXME: This entire following section is 100% not spec-compliant. - auto existing_variable = m_base_environment->get_from_environment(m_name.as_string()); - Variable variable { - .value = value, - .declaration_kind = existing_variable.has_value() ? existing_variable->declaration_kind : DeclarationKind::Var - }; - - // FIXME: This is a hack until we support proper variable bindings. - if (variable.declaration_kind == DeclarationKind::Const) { - vm.throw_exception(global_object, ErrorType::InvalidAssignToConst); - return; - } - - bool succeeded = m_base_environment->put_into_environment(m_name.as_string(), variable); - if (vm.exception()) - return; - - if (!succeeded) { - if (m_base_environment->has_binding(m_name.as_string())) { - m_base_environment->set_mutable_binding(global_object, m_name.as_string(), value, is_strict()); - return; - } - } + VERIFY(m_base_environment); + m_base_environment->set_mutable_binding(global_object, m_name.as_string(), value, m_strict); } void Reference::throw_reference_error(GlobalObject& global_object) const @@ -91,7 +61,7 @@ void Reference::throw_reference_error(GlobalObject& global_object) const } // 6.2.4.5 GetValue ( V ), https://tc39.es/ecma262/#sec-getvalue -Value Reference::get_value(GlobalObject& global_object, bool throw_if_undefined) const +Value Reference::get_value(GlobalObject& global_object) const { if (!is_valid_reference() || is_unresolvable()) { throw_reference_error(global_object); @@ -106,19 +76,8 @@ Value Reference::get_value(GlobalObject& global_object, bool throw_if_undefined) } VERIFY(m_base_type == BaseType::Environment); - // FIXME: This entire section is 100% not spec-compliant. - auto value = m_base_environment->get_from_environment(m_name.as_string()); - if (!value.has_value()) { - if (m_base_environment->has_binding(m_name.as_string())) - return m_base_environment->get_binding_value(global_object, m_name.as_string(), is_strict()); - if (!throw_if_undefined) { - // FIXME: This is an ad-hoc hack for the `typeof` operator until we support proper variable bindings. - return js_undefined(); - } - throw_reference_error(global_object); - return {}; - } - return value->value; + VERIFY(m_base_environment); + return m_base_environment->get_binding_value(global_object, m_name.as_string(), m_strict); } // 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation @@ -177,11 +136,7 @@ bool Reference::delete_(GlobalObject& global_object) VERIFY(m_base_type == BaseType::Environment); // c. Return ? base.DeleteBinding(ref.[[ReferencedName]]). - if (m_base_environment->has_binding(m_name.as_string())) - return m_base_environment->delete_binding(global_object, m_name.as_string()); - - // FIXME: This is ad-hoc, we should be calling DeleteBinding. - return m_base_environment->delete_from_environment(m_name.as_string()); + return m_base_environment->delete_binding(global_object, m_name.as_string()); } String Reference::to_string() const diff --git a/Userland/Libraries/LibJS/Runtime/Reference.h b/Userland/Libraries/LibJS/Runtime/Reference.h index 7d24a8b0bf..8bd025f853 100644 --- a/Userland/Libraries/LibJS/Runtime/Reference.h +++ b/Userland/Libraries/LibJS/Runtime/Reference.h @@ -103,8 +103,15 @@ public: return m_base_type == BaseType::Environment; } + void initialize_referenced_binding(GlobalObject& global_object, Value value) const + { + VERIFY(!is_unresolvable()); + VERIFY(m_base_type == BaseType::Environment); + m_base_environment->initialize_binding(global_object, m_name.as_string(), value); + } + void put_value(GlobalObject&, Value); - Value get_value(GlobalObject&, bool throw_if_undefined = true) const; + Value get_value(GlobalObject&) const; bool delete_(GlobalObject&); String to_string() const; diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index ec114f2a47..696217f14a 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020-2021, Linus Groh + * Copyright (c) 2021, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -16,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -137,294 +137,310 @@ Symbol* VM::get_global_symbol(const String& description) return new_global_symbol; } -void VM::set_variable(const FlyString& name, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope) +ThrowCompletionOr VM::named_evaluation_if_anonymous_function(GlobalObject& global_object, ASTNode const& expression, FlyString const& name) { - Optional possible_match; - if (!specific_scope && m_execution_context_stack.size()) { - for (auto* environment = lexical_environment(); environment; environment = environment->outer_environment()) { - possible_match = environment->get_from_environment(name); - if (possible_match.has_value()) { - specific_scope = environment; - break; - } + // 8.3.3 Static Semantics: IsAnonymousFunctionDefinition ( expr ), https://tc39.es/ecma262/#sec-isanonymousfunctiondefinition + // And 8.3.5 Runtime Semantics: NamedEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-namedevaluation + if (is(expression)) { + auto& function = static_cast(expression); + if (!function.has_name()) { + return function.instantiate_ordinary_function_expression(interpreter(), global_object, name); + } + } else if (is(expression)) { + auto& class_expression = static_cast(expression); + if (!class_expression.has_name()) { + return TRY(class_expression.class_definition_evaluation(interpreter(), global_object, {}, name)); } } - if (specific_scope && possible_match.has_value()) { - if (!first_assignment && possible_match.value().declaration_kind == DeclarationKind::Const) { - throw_exception(global_object, ErrorType::InvalidAssignToConst); - return; - } - - specific_scope->put_into_environment(name, { value, possible_match.value().declaration_kind }); - return; - } - - if (specific_scope) { - specific_scope->put_into_environment(name, { value, DeclarationKind::Var }); - return; - } - - global_object.set(name, value, Object::ShouldThrowExceptions::Yes); + auto value = expression.execute(interpreter(), global_object); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + return value; } -void VM::assign(const FlyString& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope) +// 13.15.5.2 Runtime Semantics: DestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-destructuringassignmentevaluation +ThrowCompletionOr VM::destructuring_assignment_evaluation(NonnullRefPtr const& target, Value value, GlobalObject& global_object) { - set_variable(target, move(value), global_object, first_assignment, specific_scope); + // Note: DestructuringAssignmentEvaluation is just like BindingInitialization without an environment + // And it allows member expressions. We thus trust the parser to disallow member expressions + // in any non assignment binding and just call BindingInitialization with a nullptr environment + return binding_initialization(target, value, nullptr, global_object); } -void VM::assign(const Variant, NonnullRefPtr>& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope) +// 8.5.2 Runtime Semantics: BindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization +ThrowCompletionOr VM::binding_initialization(FlyString const& target, Value value, Environment* environment, GlobalObject& global_object) { - if (auto id_ptr = target.get_pointer>()) - return assign((*id_ptr)->string(), move(value), global_object, first_assignment, specific_scope); - - assign(target.get>(), move(value), global_object, first_assignment, specific_scope); -} - -void VM::assign(const NonnullRefPtr& target, Value value, GlobalObject& global_object, bool first_assignment, Environment* specific_scope) -{ - auto& binding = *target; - - switch (binding.kind) { - case BindingPattern::Kind::Array: { - auto iterator = get_iterator(global_object, value); - if (!iterator) - return; - - for (size_t i = 0; i < binding.entries.size(); i++) { - if (exception()) - return; - - auto& entry = binding.entries[i]; - - Optional assignment_target; - entry.alias.visit( - [&](Empty) {}, - [&](NonnullRefPtr const&) { - // FIXME: We need to get the reference but bindings are broken so this doesn't work yet. - }, - [&](NonnullRefPtr const&) {}, - [&](NonnullRefPtr const& member_expression) { - assignment_target = member_expression->to_reference(interpreter(), global_object); - }); - - if (exception()) - return; - - if (entry.is_rest) { - VERIFY(i == binding.entries.size() - 1); - - auto* array = Array::create(global_object, 0); - for (;;) { - auto next_object = iterator_next(*iterator); - if (!next_object) - return; - - auto done_property = next_object->get(names.done); - if (exception()) - return; - - if (done_property.to_boolean()) - break; - - auto next_value = next_object->get(names.value); - if (exception()) - return; - - array->indexed_properties().append(next_value); - } - value = array; - } else if (iterator) { - auto next_object = iterator_next(*iterator); - if (!next_object) - return; - - auto done_property = next_object->get(names.done); - if (exception()) - return; - - if (done_property.to_boolean()) { - iterator = nullptr; - value = js_undefined(); - } else { - value = next_object->get(names.value); - if (exception()) - return; - } - } else { - value = js_undefined(); - } - - if (value.is_undefined() && entry.initializer) { - value = entry.initializer->execute(interpreter(), global_object); - if (exception()) - return; - } - - entry.alias.visit( - [&](Empty) {}, - [&](NonnullRefPtr const& identifier) { - set_variable(identifier->string(), value, global_object, first_assignment, specific_scope); - }, - [&](NonnullRefPtr const& pattern) { - assign(pattern, value, global_object, first_assignment, specific_scope); - }, - [&](NonnullRefPtr const&) { - VERIFY(assignment_target.has_value()); - assignment_target->put_value(global_object, value); - }); - - if (exception()) - return; - - if (entry.is_rest) - break; - } - - break; - } - case BindingPattern::Kind::Object: { - auto object = value.to_object(global_object); - HashTable seen_names; - for (auto& property : binding.entries) { - VERIFY(!property.is_elision()); - - PropertyName assignment_name; - Optional assignment_target; - - JS::Value value_to_assign; - if (property.is_rest) { - if (auto identifier_ptr = property.name.get_pointer>()) - assignment_name = (*identifier_ptr)->string(); - property.alias.visit( - [&](Empty) {}, - [&](NonnullRefPtr const&) { - // FIXME: We need to get the reference but bindings are broken so this doesn't work yet. - }, - [&](NonnullRefPtr const&) {}, - [&](NonnullRefPtr const& member_expression) { - assignment_target = member_expression->to_reference(interpreter(), global_object); - }); - - auto* rest_object = Object::create(global_object, global_object.object_prototype()); - for (auto& object_property : object->shape().property_table()) { - if (!object_property.value.attributes.is_enumerable()) - continue; - if (seen_names.contains(object_property.key.to_display_string())) - continue; - rest_object->set(object_property.key, object->get(object_property.key), Object::ShouldThrowExceptions::Yes); - if (exception()) - return; - } - - value_to_assign = rest_object; - } else { - property.name.visit( - [&](Empty) { VERIFY_NOT_REACHED(); }, - [&](NonnullRefPtr const& identifier) { - assignment_name = identifier->string(); - }, - [&](NonnullRefPtr const& expression) { - auto result = expression->execute(interpreter(), global_object); - if (exception()) - return; - assignment_name = result.to_property_key(global_object); - }); - property.alias.visit( - [&](Empty) {}, - [&](NonnullRefPtr const&) { - // FIXME: We need to get the reference but bindings are broken so this doesn't work yet. - }, - [&](NonnullRefPtr const&) {}, - [&](NonnullRefPtr const& member_expression) { - assignment_target = member_expression->to_reference(interpreter(), global_object); - }); - - if (exception()) - break; - - value_to_assign = object->get(assignment_name); - } - - seen_names.set(assignment_name); - - if (value_to_assign.is_empty()) - value_to_assign = js_undefined(); - - if (value_to_assign.is_undefined() && property.initializer) - value_to_assign = property.initializer->execute(interpreter(), global_object); - - if (exception()) - break; - - property.alias.visit( - [&](Empty) { - set_variable(assignment_name.to_string(), value_to_assign, global_object, first_assignment, specific_scope); - }, - [&](NonnullRefPtr const& identifier) { - VERIFY(!property.is_rest); - set_variable(identifier->string(), value_to_assign, global_object, first_assignment, specific_scope); - }, - [&](NonnullRefPtr const& pattern) { - VERIFY(!property.is_rest); - assign(pattern, value_to_assign, global_object, first_assignment, specific_scope); - }, - [&](NonnullRefPtr const&) { - VERIFY(assignment_target.has_value()); - assignment_target->put_value(global_object, value_to_assign); - }); - - if (exception()) - return; - - if (property.is_rest) - break; - } - break; - } - } -} - -Value VM::get_variable(const FlyString& name, GlobalObject& global_object) -{ - if (!m_execution_context_stack.is_empty()) { - auto& context = running_execution_context(); - if (name == names.arguments.as_string() && context.function) { - // HACK: Special handling for the name "arguments": - // If the name "arguments" is defined in the current scope, for example via - // a function parameter, or by a local var declaration, we use that. - // Otherwise, we return a lazily constructed Array with all the argument values. - // FIXME: Do something much more spec-compliant. - auto possible_match = lexical_environment()->get_from_environment(name); - if (possible_match.has_value()) - return possible_match.value().value; - if (!context.arguments_object) { - if (context.function->is_strict_mode() || (is(context.function) && !static_cast(context.function)->has_simple_parameter_list())) { - context.arguments_object = create_unmapped_arguments_object(global_object, context.arguments.span()); - } else { - context.arguments_object = create_mapped_arguments_object(global_object, *context.function, verify_cast(context.function)->formal_parameters(), context.arguments.span(), *lexical_environment()); - } - } - return context.arguments_object; - } - - for (auto* environment = lexical_environment(); environment; environment = environment->outer_environment()) { - auto possible_match = environment->get_from_environment(name); - if (exception()) - return {}; - if (possible_match.has_value()) - return possible_match.value().value; - if (environment->has_binding(name)) - return environment->get_binding_value(global_object, name, false); - } - } - - if (!global_object.storage_has(name)) { - if (m_underscore_is_last_value && name == "_") - return m_last_value; + if (environment) { + environment->initialize_binding(global_object, target, value); return {}; } - return global_object.get(name); + auto reference = resolve_binding(target); + reference.put_value(global_object, value); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + return {}; +} + +// 8.5.2 Runtime Semantics: BindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-bindinginitialization +ThrowCompletionOr VM::binding_initialization(NonnullRefPtr const& target, Value value, Environment* environment, GlobalObject& global_object) +{ + if (target->kind == BindingPattern::Kind::Object) { + TRY(require_object_coercible(global_object, value)); + TRY(property_binding_initialization(*target, value, environment, global_object)); + return {}; + } else { + auto* iterator = get_iterator(global_object, value); + if (!iterator) { + VERIFY(exception()); + return JS::throw_completion(exception()->value()); + } + + auto iterator_done = false; + + auto result = iterator_binding_initialization(*target, iterator, iterator_done, environment, global_object); + + if (!iterator_done) { + // FIXME: Iterator close should take result and potentially return that. This logic should achieve the same until that is possible. + iterator_close(*iterator); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + } + return result; + } +} + +// 13.15.5.3 Runtime Semantics: PropertyDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-propertydestructuringassignmentevaluation +// 14.3.3.1 Runtime Semantics: PropertyBindingInitialization, https://tc39.es/ecma262/#sec-destructuring-binding-patterns-runtime-semantics-propertybindinginitialization +ThrowCompletionOr VM::property_binding_initialization(BindingPattern const& binding, Value value, Environment* environment, GlobalObject& global_object) +{ + auto* object = value.to_object(global_object); + if (!object) { + VERIFY(exception()); + return JS::throw_completion(exception()->value()); + } + + HashTable seen_names; + for (auto& property : binding.entries) { + + VERIFY(!property.is_elision()); + + if (property.is_rest) { + Reference assignment_target; + if (auto identifier_ptr = property.name.get_pointer>()) { + assignment_target = resolve_binding((*identifier_ptr)->string(), environment); + } else if (auto member_ptr = property.alias.get_pointer>()) { + assignment_target = (*member_ptr)->to_reference(interpreter(), global_object); + } else { + VERIFY_NOT_REACHED(); + } + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + auto* rest_object = Object::create(global_object, global_object.object_prototype()); + VERIFY(rest_object); + + TRY(rest_object->copy_data_properties(object, seen_names, global_object)); + if (!environment) + assignment_target.put_value(global_object, rest_object); + else + assignment_target.initialize_referenced_binding(global_object, rest_object); + + break; + } + + PropertyName name; + + property.name.visit( + [&](Empty) { VERIFY_NOT_REACHED(); }, + [&](NonnullRefPtr const& identifier) { + name = identifier->string(); + }, + [&](NonnullRefPtr const& expression) { + auto result = expression->execute(interpreter(), global_object); + if (exception()) + return; + name = result.to_property_key(global_object); + }); + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + seen_names.set(name); + + if (property.name.has>() && property.alias.has()) { + // FIXME: this branch and not taking this have a lot in common we might want to unify it more (like it was before). + auto& identifier = *property.name.get>(); + auto reference = resolve_binding(identifier.string(), environment); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + auto value_to_assign = object->get(name); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + if (property.initializer && value_to_assign.is_undefined()) { + value_to_assign = TRY(named_evaluation_if_anonymous_function(global_object, *property.initializer, identifier.string())); + } + + if (!environment) + reference.put_value(global_object, value_to_assign); + else + reference.initialize_referenced_binding(global_object, value_to_assign); + continue; + } + + Optional reference_to_assign_to; + + property.alias.visit( + [&](Empty) {}, + [&](NonnullRefPtr const& identifier) { + reference_to_assign_to = resolve_binding(identifier->string(), environment); + }, + [&](NonnullRefPtr const&) {}, + [&](NonnullRefPtr const& member_expression) { + reference_to_assign_to = member_expression->to_reference(interpreter(), global_object); + }); + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + auto value_to_assign = object->get(name); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + if (property.initializer && value_to_assign.is_undefined()) { + if (auto* identifier_ptr = property.alias.get_pointer>()) + value_to_assign = TRY(named_evaluation_if_anonymous_function(global_object, *property.initializer, (*identifier_ptr)->string())); + else + value_to_assign = property.initializer->execute(interpreter(), global_object); + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + } + + if (auto* binding_ptr = property.alias.get_pointer>()) { + TRY(binding_initialization(*binding_ptr, value_to_assign, environment, global_object)); + } else { + VERIFY(reference_to_assign_to.has_value()); + if (!environment) + reference_to_assign_to->put_value(global_object, value_to_assign); + else + reference_to_assign_to->initialize_referenced_binding(global_object, value_to_assign); + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + } + } + + return {}; +} + +// 13.15.5.5 Runtime Semantics: IteratorDestructuringAssignmentEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-iteratordestructuringassignmentevaluation +// 8.5.3 Runtime Semantics: IteratorBindingInitialization, https://tc39.es/ecma262/#sec-runtime-semantics-iteratorbindinginitialization +ThrowCompletionOr VM::iterator_binding_initialization(BindingPattern const& binding, Object* iterator, bool& iterator_done, Environment* environment, GlobalObject& global_object) +{ + // FIXME: this method is nearly identical to destructuring assignment! + for (size_t i = 0; i < binding.entries.size(); i++) { + auto& entry = binding.entries[i]; + Value value; + + Optional assignment_target; + entry.alias.visit( + [&](Empty) {}, + [&](NonnullRefPtr const& identifier) { + assignment_target = resolve_binding(identifier->string(), environment); + }, + [&](NonnullRefPtr const&) {}, + [&](NonnullRefPtr const& member_expression) { + assignment_target = member_expression->to_reference(interpreter(), global_object); + }); + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + if (entry.is_rest) { + VERIFY(i == binding.entries.size() - 1); + + auto* array = Array::create(global_object, 0); + while (!iterator_done) { + auto next_object = iterator_next(*iterator); + if (!next_object) { + iterator_done = true; + VERIFY(exception()); + return JS::throw_completion(exception()->value()); + } + + auto done_property = next_object->get(names.done); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + if (done_property.to_boolean()) { + iterator_done = true; + break; + } + + auto next_value = next_object->get(names.value); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + array->indexed_properties().append(next_value); + } + value = array; + + } else if (!iterator_done) { + auto next_object = iterator_next(*iterator); + if (!next_object) { + iterator_done = true; + VERIFY(exception()); + return JS::throw_completion(exception()->value()); + } + + auto done_property = next_object->get(names.done); + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + + if (done_property.to_boolean()) { + iterator_done = true; + value = js_undefined(); + } else { + value = next_object->get(names.value); + if (auto* thrown_exception = exception()) { + iterator_done = true; + return JS::throw_completion(thrown_exception->value()); + } + } + } else { + value = js_undefined(); + } + + if (value.is_undefined() && entry.initializer) { + VERIFY(!entry.is_rest); + if (auto* identifier_ptr = entry.alias.get_pointer>()) + value = TRY(named_evaluation_if_anonymous_function(global_object, *entry.initializer, (*identifier_ptr)->string())); + else + value = entry.initializer->execute(interpreter(), global_object); + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + } + + if (auto* binding_ptr = entry.alias.get_pointer>()) { + TRY(binding_initialization(*binding_ptr, value, environment, global_object)); + } else if (!entry.alias.has()) { + VERIFY(assignment_target.has_value()); + if (!environment) + assignment_target->put_value(global_object, value); + else + assignment_target->initialize_referenced_binding(global_object, value); + + if (auto* thrown_exception = exception()) + return JS::throw_completion(thrown_exception->value()); + } + } + + return {}; } // 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference @@ -435,23 +451,14 @@ Reference VM::get_identifier_reference(Environment* environment, FlyString name, // a. Return the Reference Record { [[Base]]: unresolvable, [[ReferencedName]]: name, [[Strict]]: strict, [[ThisValue]]: empty }. return Reference { Reference::BaseType::Unresolvable, move(name), strict }; } + auto exists = environment->has_binding(name); + if (exception()) + return {}; - // FIXME: The remainder of this function is non-conforming. - - for (; environment && environment->outer_environment(); environment = environment->outer_environment()) { - auto possible_match = environment->get_from_environment(name); - if (possible_match.has_value()) - return Reference { *environment, move(name), strict }; - if (environment->has_binding(name)) - return Reference { *environment, move(name), strict }; - } - - auto& global_environment = interpreter().realm().global_environment(); - if (global_environment.has_binding(name) || !in_strict_mode()) { - return Reference { global_environment, move(name), strict }; - } - - return Reference { Reference::BaseType::Unresolvable, move(name), strict }; + if (exists) + return Reference { *environment, move(name), strict }; + else + return get_identifier_reference(environment->outer_environment(), move(name), strict); } // 9.4.2 ResolveBinding ( name [ , env ] ), https://tc39.es/ecma262/#sec-resolvebinding @@ -546,7 +553,9 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option return {}; } + auto* constructor_environment = callee_context.lexical_environment; auto result = function.construct(new_target); + VERIFY(constructor_environment); pop_execution_context(); pop_guard.disarm(); @@ -557,8 +566,8 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option if ((!is(function) || static_cast(function).constructor_kind() == ECMAScriptFunctionObject::ConstructorKind::Base) && is(new_target) && static_cast(new_target).constructor_kind() == ECMAScriptFunctionObject::ConstructorKind::Derived && result.is_object()) { - if (auto* environment = callee_context.lexical_environment) - verify_cast(environment)->replace_this_binding(result); + verify_cast(constructor_environment)->replace_this_binding(result); + auto prototype = new_target.get(names.prototype); if (exception()) return {}; @@ -573,9 +582,16 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option if (result.is_object()) return result; - if (auto* environment = callee_context.lexical_environment) - return environment->get_this_binding(global_object); - return this_argument; + if (is(function) && static_cast(function).constructor_kind() == ECMAScriptFunctionObject::ConstructorKind::Base) + return this_argument; + + if (!result.is_empty() && !result.is_undefined()) { + throw_exception(global_object, ErrorType::DerivedConstructorReturningInvalidValue); + return {}; + } + + VERIFY(constructor_environment); + return constructor_environment->get_this_binding(global_object); } void VM::throw_exception(Exception& exception) @@ -609,7 +625,7 @@ Value VM::get_new_target() } // 10.2.1.1 PrepareForOrdinaryCall ( F, newTarget ), https://tc39.es/ecma262/#sec-prepareforordinarycall -void VM::prepare_for_ordinary_call(FunctionObject& function, ExecutionContext& callee_context, [[maybe_unused]] Object* new_target) +void VM::prepare_for_ordinary_call(FunctionObject& function, ExecutionContext& callee_context, Object* new_target) { // NOTE: This is a LibJS specific hack for NativeFunction to inherit the strictness of its caller. // FIXME: I feel like we should be able to get rid of this. @@ -643,8 +659,7 @@ void VM::prepare_for_ordinary_call(FunctionObject& function, ExecutionContext& c // FIXME: Our execution context struct currently does not track this item. // 7. Let localEnv be NewFunctionEnvironment(F, newTarget). - // FIXME: This should call NewFunctionEnvironment instead of the ad-hoc FunctionObject::create_environment() - auto* local_environment = function.create_environment(function); + auto* local_environment = function.new_function_environment(new_target); // 8. Set the LexicalEnvironment of calleeContext to localEnv. callee_context.lexical_environment = local_environment; @@ -724,8 +739,7 @@ ThrowCompletionOr VM::call_internal(FunctionObject& function, Value this_ callee_context.this_value = this_value; append_bound_and_passed_arguments(callee_context.arguments, {}, move(arguments)); - if (callee_context.lexical_environment) - ordinary_call_bind_this(function, callee_context, this_value); + ordinary_call_bind_this(function, callee_context, this_value); if (auto* exception = this->exception()) return JS::throw_completion(exception->value()); @@ -810,19 +824,6 @@ void VM::dump_backtrace() const } } -void VM::dump_environment_chain() const -{ - for (auto* environment = lexical_environment(); environment; environment = environment->outer_environment()) { - dbgln("+> {} ({:p})", environment->class_name(), environment); - if (is(*environment)) { - auto& declarative_environment = static_cast(*environment); - for (auto& variable : declarative_environment.variables()) { - dbgln(" {}", variable.key); - } - } - } -} - VM::CustomData::~CustomData() { } diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h index abc2ca5467..216662fa40 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.h +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2020, Andreas Kling * Copyright (c) 2020-2021, Linus Groh + * Copyright (c) 2021, David Tuin * * SPDX-License-Identifier: BSD-2-Clause */ @@ -38,12 +39,6 @@ enum class ScopeType { Continuable, }; -struct ScopeFrame { - ScopeType type; - NonnullRefPtr scope_node; - bool pushed_environment { false }; -}; - class VM : public RefCounted { public: struct CustomData { @@ -67,7 +62,6 @@ public: void clear_exception() { m_exception = nullptr; } void dump_backtrace() const; - void dump_environment_chain() const; class InterpreterExecutionScope { public: @@ -206,12 +200,6 @@ public: ScopeType unwind_until() const { return m_unwind_until; } FlyString unwind_until_label() const { return m_unwind_until_label; } - Value get_variable(const FlyString& name, GlobalObject&); - void set_variable(const FlyString& name, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr); - void assign(const Variant, NonnullRefPtr>& target, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr); - void assign(const FlyString& target, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr); - void assign(const NonnullRefPtr& target, Value, GlobalObject&, bool first_assignment = false, Environment* specific_scope = nullptr); - Reference resolve_binding(FlyString const&, Environment* = nullptr); Reference get_identifier_reference(Environment*, FlyString, bool strict); @@ -285,6 +273,12 @@ public: CustomData* custom_data() { return m_custom_data; } + ThrowCompletionOr destructuring_assignment_evaluation(NonnullRefPtr const& target, Value value, GlobalObject& global_object); + ThrowCompletionOr binding_initialization(FlyString const& target, Value value, Environment* environment, GlobalObject& global_object); + ThrowCompletionOr binding_initialization(NonnullRefPtr const& target, Value value, Environment* environment, GlobalObject& global_object); + + ThrowCompletionOr named_evaluation_if_anonymous_function(GlobalObject& global_object, ASTNode const& expression, FlyString const& name); + private: explicit VM(OwnPtr); @@ -293,6 +287,11 @@ private: [[nodiscard]] ThrowCompletionOr call_internal(FunctionObject&, Value this_value, Optional arguments); void prepare_for_ordinary_call(FunctionObject&, ExecutionContext& callee_context, Object* new_target); + ThrowCompletionOr copy_data_properties(Object& rest_object, Object const& source, HashTable const& seen_names, GlobalObject& global_object); + + ThrowCompletionOr property_binding_initialization(BindingPattern const& binding, Value value, Environment* environment, GlobalObject& global_object); + ThrowCompletionOr iterator_binding_initialization(BindingPattern const& binding, Object* iterator, bool& iterator_done, Environment* environment, GlobalObject& global_object); + Exception* m_exception { nullptr }; Heap m_heap; diff --git a/Userland/Libraries/LibJS/Tests/functions/function-name.js b/Userland/Libraries/LibJS/Tests/functions/function-name.js index 8a4d492e2c..9b73f5f976 100644 --- a/Userland/Libraries/LibJS/Tests/functions/function-name.js +++ b/Userland/Libraries/LibJS/Tests/functions/function-name.js @@ -39,8 +39,9 @@ test("functions in objects", () => { expect(o.a.name).toBe("a"); expect(o.b.name).toBe("a"); + // Member expressions do not get named. o.c = function () {}; - expect(o.c.name).toBe("c"); + expect(o.c.name).toBe(""); }); test("names of native functions", () => { @@ -49,16 +50,25 @@ test("names of native functions", () => { expect(console.debug.name).toBe("debug"); }); -test("no invalid autonaming of anonymous functions", () => { - // prettier-ignore - let f1 = (function () {}); - expect(f1.name).toBe(""); - let f2 = f1; - expect(f2.name).toBe(""); - let f3; - f3 = false || f2; - expect(f3.name).toBe(""); - let f4 = false; - f4 ||= function () {}; - expect(f4.name).toBe(""); +describe("some anonymous functions get renamed", () => { + test("direct assignment does name new function expression", () => { + // prettier-ignore + let f1 = (function () {}); + expect(f1.name).toBe("f1"); + + let f2 = false; + f2 ||= function () {}; + expect(f2.name).toBe("f2"); + }); + + test("assignment from variable does not name", () => { + const f1 = function () {}; + let f3 = f1; + expect(f3.name).toBe("f1"); + }); + + test("assignment via expression does not name", () => { + let f4 = false || function () {}; + expect(f4.name).toBe(""); + }); }); diff --git a/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js b/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js index 62b59ff708..d5e70845fc 100644 --- a/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js +++ b/Userland/Libraries/LibJS/Tests/loops/for-in-basic.js @@ -51,5 +51,24 @@ test("use already-declared variable", () => { }); test("allow binding patterns", () => { - expect(`for (let [a, b] in foo) {}`).toEval(); + const expected = [ + ["1", "3", []], + ["s", undefined, []], + ["l", "n", ["g", "N", "a", "m", "e"]], + ]; + let counter = 0; + + for (let [a, , b, ...c] in { 123: 1, sm: 2, longName: 3 }) { + expect(a).toBe(expected[counter][0]); + expect(b).toBe(expected[counter][1]); + expect(c).toEqual(expected[counter][2]); + counter++; + } + expect(counter).toBe(3); +}); + +test("allow member expression as variable", () => { + const f = {}; + for (f.a in "abc"); + expect(f.a).toBe("2"); }); diff --git a/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js b/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js index 4f6277e3f3..892894356c 100644 --- a/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js +++ b/Userland/Libraries/LibJS/Tests/loops/for-of-basic.js @@ -100,5 +100,20 @@ describe("errors", () => { }); test("allow binding patterns", () => { - expect(`for (let [a, b] of foo) {}`).toEval(); + let counter = 0; + for (let [a, b] of [ + [1, 2], + [3, 4], + [5, 6], + ]) { + expect(a + 1).toBe(b); + counter++; + } + expect(counter).toBe(3); +}); + +test("allow member expression as variable", () => { + const f = {}; + for (f.a of "abc"); + expect(f.a).toBe("c"); }); diff --git a/Userland/Libraries/LibJS/Tests/operators/delete-local-variable.js b/Userland/Libraries/LibJS/Tests/operators/delete-local-variable.js index c1585aa5c8..795c1bab83 100644 --- a/Userland/Libraries/LibJS/Tests/operators/delete-local-variable.js +++ b/Userland/Libraries/LibJS/Tests/operators/delete-local-variable.js @@ -1,8 +1,16 @@ test("basic functionality", () => { let a = 5; - expect(delete a).toBeTrue(); + var b = 6; + c = 7; + expect(delete a).toBeFalse(); + expect(a).toBe(5); + + expect(delete b).toBeFalse(); + expect(b).toBe(6); + + expect(delete c).toBeTrue(); expect(() => { - a; - }).toThrowWithMessage(ReferenceError, "'a' is not defined"); + c; + }).toThrowWithMessage(ReferenceError, "'c' is not defined"); }); diff --git a/Userland/Libraries/LibJS/Tests/strict-mode-errors.js b/Userland/Libraries/LibJS/Tests/strict-mode-errors.js index acbe586500..632895df20 100644 --- a/Userland/Libraries/LibJS/Tests/strict-mode-errors.js +++ b/Userland/Libraries/LibJS/Tests/strict-mode-errors.js @@ -4,15 +4,12 @@ test("basic functionality", () => { [true, false, "foo", 123].forEach(primitive => { expect(() => { primitive.foo = "bar"; - }).toThrowWithMessage( - TypeError, - `Cannot set property 'foo' of ${typeof primitive} '${primitive}'` - ); + }).toThrowWithMessage(TypeError, `Cannot set property 'foo' of ${primitive}`); expect(() => { primitive[Symbol.hasInstance] = 123; }).toThrowWithMessage( TypeError, - `Cannot set property 'Symbol(Symbol.hasInstance)' of ${typeof primitive} '${primitive}'` + `Cannot set property 'Symbol(Symbol.hasInstance)' of ${primitive}` ); }); [null, undefined].forEach(primitive => { diff --git a/Userland/Libraries/LibJS/Tests/string-escapes.js b/Userland/Libraries/LibJS/Tests/string-escapes.js index 8a0920361e..64a81b1fe1 100644 --- a/Userland/Libraries/LibJS/Tests/string-escapes.js +++ b/Userland/Libraries/LibJS/Tests/string-escapes.js @@ -58,6 +58,8 @@ describe("octal escapes", () => { expect("'use strict'; '\\123'").not.toEval(); expect('"use strict"; "\\123"').not.toEval(); // Special case, string literal precedes use strict directive - expect("'\\123'; somethingElse; 'use strict'").not.toEval(); + expect("'\\123'; 'use strict'").not.toEval(); + // Because of the non string statement in the middle strict mode is not enabled. + expect("'\\123'; somethingElse; 'use strict'").toEval(); }); }); diff --git a/Userland/Libraries/LibJS/Tests/syntax/destructuring-assignment.js b/Userland/Libraries/LibJS/Tests/syntax/destructuring-assignment.js index c01b420b7f..7a709a31bb 100644 --- a/Userland/Libraries/LibJS/Tests/syntax/destructuring-assignment.js +++ b/Userland/Libraries/LibJS/Tests/syntax/destructuring-assignment.js @@ -196,6 +196,11 @@ describe("evaluating", () => { expect(a).toBe(o[0]); expect(length).toBe(o[1].length); } + { + expect(() => { + let [a, b, [...{ length }]] = o; + }).toThrowWithMessage(TypeError, "ToObject on null or undefined"); + } }); test("patterns with default", () => { diff --git a/Userland/Libraries/LibJS/Tests/syntax/switch-as-statement.js b/Userland/Libraries/LibJS/Tests/syntax/switch-as-statement.js new file mode 100644 index 0000000000..c71e2dddd1 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/syntax/switch-as-statement.js @@ -0,0 +1,69 @@ +describe("switch statement is a valid statement and gets executed", () => { + test("switch statement in a block", () => { + let hit = false; + { + switch (true) { + case true: + hit = true; + } + expect(hit).toBeTrue(); + } + }); + + test("switch statement in an if statement when true", () => { + let hit = false; + var a = true; + if (a) + switch (true) { + case true: + hit = true; + } + else + switch (true) { + case true: + expect().fail(); + } + + expect(hit).toBeTrue(); + }); + + test("switch statement in an if statement when false", () => { + let hit = false; + var a = false; + if (a) + switch (a) { + default: + expect().fail(); + } + else + switch (a) { + default: + hit = true; + } + + expect(hit).toBeTrue(); + }); + + test("switch statement in an while statement", () => { + var a = 0; + var loops = 0; + while (a < 1 && loops++ < 5) + switch (a) { + case 0: + a = 1; + } + + expect(a).toBe(1); + }); + + test("switch statement in an for statement", () => { + var loops = 0; + for (let a = 0; a < 1 && loops++ < 5; ) + switch (a) { + case 0: + a = 1; + } + + expect(loops).toBe(1); + }); +}); diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index 6c0346021b..73197113ba 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -1332,14 +1332,21 @@ int main(int argc, char** argv) switch (mode) { case CompleteProperty: { - auto maybe_variable = vm->get_variable(variable_name, interpreter->global_object()); - if (maybe_variable.is_empty()) { - maybe_variable = interpreter->global_object().get(FlyString(variable_name)); - if (maybe_variable.is_empty()) + Optional maybe_value; + auto maybe_variable = vm->resolve_binding(variable_name); + if (vm->exception()) + break; + if (!maybe_variable.is_unresolvable()) { + maybe_value = maybe_variable.get_value(interpreter->global_object()); + if (vm->exception()) + break; + } else { + maybe_value = interpreter->global_object().get(FlyString(variable_name)); + if (maybe_value->is_empty()) break; } - auto variable = maybe_variable; + auto variable = *maybe_value; if (!variable.is_object()) break;