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;