diff options
author | Linus Groh <mail@linusgroh.de> | 2022-01-05 19:11:16 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-01-06 12:36:23 +0100 |
commit | 9d0d3affd4feaab62c37e284a17432815c071c24 (patch) | |
tree | 205b9f342ad4c53e5865ff80906873992ccc759f /Userland | |
parent | eed764e1dd3288f203108229c7e1ca9d09aef4d5 (diff) | |
download | serenity-9d0d3affd4feaab62c37e284a17432815c071c24.zip |
LibJS: Replace the custom unwind mechanism with completions :^)
This includes:
- Parsing proper LabelledStatements with try_parse_labelled_statement()
- Removing LabelableStatement
- Implementing the LoopEvaluation semantics via loop_evaluation() in
each IterationStatement subclass; and IterationStatement evaluation
via {For,ForIn,ForOf,ForAwaitOf,While,DoWhile}Statement::execute()
- Updating ReturnStatement, BreakStatement and ContinueStatement to
return the appropriate completion types
- Basically reimplementing TryStatement and SwitchStatement according to
the spec, using completions
- Honoring result completion types in AsyncBlockStart and
OrdinaryCallEvaluateBody
- Removing any uses of the VM unwind mechanism - most importantly,
VM::throw_exception() now exclusively sets an exception and no longer
triggers any unwinding mechanism.
However, we already did a good job updating all of LibWeb and userland
applications to not use it, and the few remaining uses elsewhere don't
rely on unwinding AFAICT.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibJS/AST.cpp | 666 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/AST.h | 22 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Interpreter.cpp | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Parser.cpp | 25 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Parser.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp | 29 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/Promise.cpp | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/PromiseReaction.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Runtime/VM.cpp | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibJS/Tests/labels.js | 32 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp | 2 | ||||
-rw-r--r-- | Userland/Utilities/js.cpp | 1 |
16 files changed, 513 insertions, 280 deletions
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 819ca64ebd..48d9db812a 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -93,17 +93,10 @@ static ThrowCompletionOr<String> get_function_name(GlobalObject& global_object, // StatementList : StatementList StatementListItem Completion ScopeNode::evaluate_statements(Interpreter& interpreter, GlobalObject& global_object) const { - auto& vm = interpreter.vm(); auto completion = normal_completion({}); for (auto const& node : children()) { completion = node.execute(interpreter, global_object).update_empty(completion.value()); - // FIXME: Use ReturnIfAbrupt once we get rid of the old unwinding mechanism. - // NOTE: There is *something* (iterators, I think) *somewhere* (no idea) incorrectly - // clearing the unwind state, causing us to get a throw completion here that thinks it - // doesn't need to unwind. Given that we're about to remove the unwinding mechanism - // altogether and it literally only affects 0.0001% of test262 tests (6 / 45156), this - // double check will do for now. - if (completion.is_abrupt() || vm.should_unwind()) + if (completion.is_abrupt()) break; } return completion; @@ -219,14 +212,8 @@ Completion FunctionBody::execute(Interpreter& interpreter, GlobalObject& global_ InterpreterNodeScope node_scope { interpreter, *this }; // Note: Scoping should have already been set up by whoever is calling this FunctionBody. - auto function_result = TRY(evaluate_statements(interpreter, global_object)); - - if (interpreter.vm().unwind_until() != ScopeType::Function) - function_result = js_undefined(); - else - interpreter.vm().stop_unwind(); - - return normal_completion(move(function_result)); + // 1. Return ? EvaluateFunctionBody of FunctionBody with arguments functionObject and argumentsList. + return evaluate_statements(interpreter, global_object); } // 14.2.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation @@ -251,10 +238,7 @@ Completion BlockStatement::execute(Interpreter& interpreter, GlobalObject& globa restore_environment.disarm(); } - auto block_value = evaluate_statements(interpreter, global_object); - if (!labels().is_empty() && vm.should_unwind_until(ScopeType::Breakable, labels())) - vm.stop_unwind(); - return block_value; + return evaluate_statements(interpreter, global_object); } Completion Program::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -523,10 +507,22 @@ Completion ReturnStatement::execute(Interpreter& interpreter, GlobalObject& glob { InterpreterNodeScope node_scope { interpreter, *this }; - // TODO: Return 'return' completion - auto value = argument() ? TRY(argument()->execute(interpreter, global_object)).release_value() : js_undefined(); - interpreter.vm().unwind(ScopeType::Function); - return value; + // ReturnStatement : return ; + if (!m_argument) { + // 1. Return Completion { [[Type]]: return, [[Value]]: undefined, [[Target]]: empty }. + return { Completion::Type::Return, js_undefined(), {} }; + } + + // ReturnStatement : return Expression ; + // 1. Let exprRef be the result of evaluating Expression. + // 2. Let exprValue be ? GetValue(exprRef). + auto value = TRY(m_argument->execute(interpreter, global_object)); + + // NOTE: Generators are not supported in the AST interpreter + // 3. If ! GetGeneratorKind() is async, set exprValue to ? Await(exprValue). + + // 4. Return Completion { [[Type]]: return, [[Value]]: exprValue, [[Target]]: empty }. + return { Completion::Type::Return, value, {} }; } // 14.6.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-if-statement-runtime-semantics-evaluation @@ -613,9 +609,18 @@ static bool loop_continues(Completion const& completion, Vector<FlyString> const return false; } -// 14.7.3.2 Runtime Semantics: WhileLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-whileloopevaluation +// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : IterationStatement Completion WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { + // 1. Let newLabelSet be a new empty List. + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return labelled_evaluation(interpreter, global_object, *this, {}); +} + +// 14.7.3.2 Runtime Semantics: WhileLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-whileloopevaluation +Completion WhileStatement::loop_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<FlyString> const& label_set) const +{ InterpreterNodeScope node_scope { interpreter, *this }; // 1. Let V be undefined. @@ -635,16 +640,8 @@ Completion WhileStatement::execute(Interpreter& interpreter, GlobalObject& globa auto body_result = m_body->execute(interpreter, global_object); // e. If LoopContinues(stmtResult, labelSet) is false, return Completion(UpdateEmpty(stmtResult, V)). - 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(); - return body_result.update_empty(last_value); - } else { - return body_result.update_empty(last_value); - } - } + if (!loop_continues(body_result, label_set)) + return body_result.update_empty(last_value); // f. If stmtResult.[[Value]] is not empty, set V to stmtResult.[[Value]]. if (body_result.value().has_value()) @@ -654,9 +651,18 @@ Completion WhileStatement::execute(Interpreter& interpreter, GlobalObject& globa VERIFY_NOT_REACHED(); } -// 14.7.2.2 Runtime Semantics: DoWhileLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-dowhileloopevaluation +// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : IterationStatement Completion DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { + // 1. Let newLabelSet be a new empty List. + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return labelled_evaluation(interpreter, global_object, *this, {}); +} + +// 14.7.2.2 Runtime Semantics: DoWhileLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-dowhileloopevaluation +Completion DoWhileStatement::loop_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<FlyString> const& label_set) const +{ InterpreterNodeScope node_scope { interpreter, *this }; // 1. Let V be undefined. @@ -668,16 +674,8 @@ Completion DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& glo auto body_result = m_body->execute(interpreter, global_object); // b. If LoopContinues(stmtResult, labelSet) is false, return Completion(UpdateEmpty(stmtResult, V)). - 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(); - return body_result.update_empty(last_value); - } else { - return body_result.update_empty(last_value); - } - } + if (!loop_continues(body_result, label_set)) + return body_result.update_empty(last_value); // c. If stmtResult.[[Value]] is not empty, set V to stmtResult.[[Value]]. if (body_result.value().has_value()) @@ -695,9 +693,18 @@ Completion DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& glo VERIFY_NOT_REACHED(); } -// 14.7.4.2 Runtime Semantics: ForLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forloopevaluation +// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : IterationStatement Completion ForStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { + // 1. Let newLabelSet be a new empty List. + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return labelled_evaluation(interpreter, global_object, *this, {}); +} + +// 14.7.4.2 Runtime Semantics: ForLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forloopevaluation +Completion ForStatement::loop_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<FlyString> const& label_set) const +{ InterpreterNodeScope node_scope { interpreter, *this }; // Note we don't always set a new environment but to use RAII we must do this here. @@ -790,16 +797,8 @@ Completion ForStatement::execute(Interpreter& interpreter, GlobalObject& global_ auto result = m_body->execute(interpreter, global_object); // c. If LoopContinues(result, labelSet) is false, return Completion(UpdateEmpty(result, V)). - 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(); - return result.update_empty(last_value); - } else { - return result.update_empty(last_value); - } - } + if (!loop_continues(result, label_set)) + return result.update_empty(last_value); // d. If result.[[Value]] is not empty, set V to result.[[Value]]. if (result.value().has_value()) @@ -985,9 +984,18 @@ static ThrowCompletionOr<ForInOfHeadState> for_in_of_head_execute(Interpreter& i return state; } -// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : IterationStatement Completion ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { + // 1. Let newLabelSet be a new empty List. + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return labelled_evaluation(interpreter, global_object, *this, {}); +} + +// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +Completion ForInStatement::loop_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<FlyString> const& label_set) const +{ InterpreterNodeScope node_scope { interpreter, *this }; auto for_in_head_state = TRY(for_in_of_head_execute(interpreter, global_object, m_lhs, *m_rhs)); @@ -999,8 +1007,7 @@ Completion ForInStatement::execute(Interpreter& interpreter, GlobalObject& globa // a. If exprValue is undefined or null, then if (rhs_result.is_nullish()) { // i. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }. - // TODO: Return 'break' completion - return js_undefined(); + return { Completion::Type::Break, {}, {} }; } // b. Let obj be ! ToObject(exprValue). @@ -1029,17 +1036,9 @@ Completion ForInStatement::execute(Interpreter& interpreter, GlobalObject& globa interpreter.vm().running_execution_context().lexical_environment = old_environment; // n. If LoopContinues(result, labelSet) is false, then - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); - 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(); - return result.update_empty(last_value); - } else { - return result.update_empty(last_value); - } + if (!loop_continues(result, label_set)) { + // 1. Return Completion(UpdateEmpty(result, V)). + return result.update_empty(last_value); } // o. If result.[[Value]] is not empty, set V to result.[[Value]]. @@ -1051,9 +1050,18 @@ Completion ForInStatement::execute(Interpreter& interpreter, GlobalObject& globa return last_value; } -// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : IterationStatement Completion ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { + // 1. Let newLabelSet be a new empty List. + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return labelled_evaluation(interpreter, global_object, *this, {}); +} + +// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +Completion ForOfStatement::loop_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<FlyString> const& label_set) const +{ InterpreterNodeScope node_scope { interpreter, *this }; auto for_of_head_state = TRY(for_in_of_head_execute(interpreter, global_object, m_lhs, m_rhs)); @@ -1072,6 +1080,8 @@ Completion ForOfStatement::execute(Interpreter& interpreter, GlobalObject& globa // 3. Let V be undefined. auto last_value = js_undefined(); + Optional<Completion> status; + (void)TRY(get_iterator_values(global_object, rhs_result, [&](Value value) -> Optional<Completion> { TRY(for_of_head_state.execute_head(interpreter, global_object, value)); @@ -1081,35 +1091,40 @@ Completion ForOfStatement::execute(Interpreter& interpreter, GlobalObject& globa // m. Set the running execution context's LexicalEnvironment to oldEnv. interpreter.vm().running_execution_context().lexical_environment = old_environment; + // n. If LoopContinues(result, labelSet) is false, then + if (!loop_continues(result, label_set)) { + // 2. Set status to UpdateEmpty(result, V). + status = result.update_empty(last_value); + + // 4. Return ? IteratorClose(iteratorRecord, status). + // NOTE: This is done by returning a completion from the callback. + return status; + } + // o. If result.[[Value]] is not empty, set V to result.[[Value]]. - // NOTE: We need to do this out of order as we can't directly return from here as we're - // supposed to, since this is the get_iterator_values() callback. Instead, we have to rely - // on updating the outer last_value. if (result.value().has_value()) last_value = *result.value(); - // n. If LoopContinues(result, labelSet) is false, then - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); - 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(); - return normal_completion(last_value); - } else { - return normal_completion(last_value); - } - } return {}; })); - return last_value; + // Return `status` set during step n.2. in the callback, or... + // e. If done is true, return NormalCompletion(V). + return status.value_or(normal_completion(last_value)); } -// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : IterationStatement Completion ForAwaitOfStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { + // 1. Let newLabelSet be a new empty List. + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return labelled_evaluation(interpreter, global_object, *this, {}); +} + +// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +Completion ForAwaitOfStatement::loop_evaluation(Interpreter& interpreter, GlobalObject& global_object, Vector<FlyString> const& label_set) const +{ InterpreterNodeScope node_scope { interpreter, *this }; // 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation @@ -1172,32 +1187,15 @@ Completion ForAwaitOfStatement::execute(Interpreter& interpreter, GlobalObject& // m. Set the running execution context's LexicalEnvironment to oldEnv. interpreter.vm().running_execution_context().lexical_environment = old_environment; - // NOTE: Until we use the full range of completion types, we have to have a number of checks here. // n. If LoopContinues(result, labelSet) is false, then - if (auto* exception = vm.exception()) { + if (!loop_continues(result, label_set)) { + // 2. Set status to UpdateEmpty(result, V). + auto status = result.update_empty(last_value); + // 3. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). - return async_iterator_close(*iterator, throw_completion(exception->value())); + return async_iterator_close(*iterator, move(status)); } - if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { - // NOTE: In this case LoopContinues is not actually false so we don't perform step 6.n.ii.3. - interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { - interpreter.vm().stop_unwind(); - // 2. Set status to UpdateEmpty(result, V). - auto status = result.update_empty(last_value); - - // 3. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). - return async_iterator_close(*iterator, move(status)); - } else { - // 2. Set status to UpdateEmpty(result, V). - auto status = result.update_empty(last_value); - - // 3. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). - return async_iterator_close(*iterator, move(status)); - } - } // o. If result.[[Value]] is not empty, set V to result.[[Value]]. if (result.value().has_value()) last_value = *result.value(); @@ -1594,9 +1592,21 @@ public: Completion execute(Interpreter& interpreter, GlobalObject& global_object) const override { + // 1. Assert: argumentsList is empty. VERIFY(interpreter.vm().argument_count() == 0); + + // 2. Assert: functionObject.[[ClassFieldInitializerName]] is not empty. VERIFY(!m_class_field_identifier_name.is_empty()); - return TRY(interpreter.vm().named_evaluation_if_anonymous_function(global_object, m_expression, m_class_field_identifier_name)); + + // 3. If IsAnonymousFunctionDefinition(AssignmentExpression) is true, then + // a. Let value be NamedEvaluation of Initializer with argument functionObject.[[ClassFieldInitializerName]]. + // 4. Else, + // a. Let rhs be the result of evaluating AssignmentExpression. + // b. Let value be ? GetValue(rhs). + auto value = TRY(interpreter.vm().named_evaluation_if_anonymous_function(global_object, m_expression, m_class_field_identifier_name)); + + // 5. Return Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + return { Completion::Type::Return, value, {} }; } void dump(int) const override @@ -3426,80 +3436,114 @@ void ThrowStatement::dump(int indent) const argument().dump(indent + 1); } -void TryStatement::add_label(FlyString string) -{ - m_block->add_label(move(string)); -} - // 14.15.3 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-try-statement-runtime-semantics-evaluation Completion TryStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { InterpreterNodeScope node_scope { interpreter, *this }; - // 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(); + auto& vm = interpreter.vm(); - auto* catch_scope = new_declarative_environment(*interpreter.lexical_environment()); + // 14.15.2 Runtime Semantics: CatchClauseEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation + auto catch_clause_evaluation = [&](Value thrown_value) { + // 1. Let oldEnv be the running execution context's LexicalEnvironment. + auto* old_environment = vm.running_execution_context().lexical_environment; - m_handler->parameter().visit( - [&](FlyString const& parameter) { - MUST(catch_scope->create_mutable_binding(global_object, parameter, false)); - }, - [&](NonnullRefPtr<BindingPattern> const& pattern) { - pattern->for_each_bound_name([&](auto& name) { - MUST(catch_scope->create_mutable_binding(global_object, name, false)); - }); + // 2. Let catchEnv be NewDeclarativeEnvironment(oldEnv). + auto* catch_environment = new_declarative_environment(*old_environment); + + m_handler->parameter().visit( + [&](FlyString const& parameter) { + // 3. For each element argName of the BoundNames of CatchParameter, do + // a. Perform ! catchEnv.CreateMutableBinding(argName, false). + MUST(catch_environment->create_mutable_binding(global_object, parameter, false)); + }, + [&](NonnullRefPtr<BindingPattern> const& pattern) { + // 3. For each element argName of the BoundNames of CatchParameter, do + pattern->for_each_bound_name([&](auto& name) { + // a. Perform ! catchEnv.CreateMutableBinding(argName, false). + MUST(catch_environment->create_mutable_binding(global_object, name, false)); }); + }); - TemporaryChange<Environment*> scope_change(interpreter.vm().running_execution_context().lexical_environment, catch_scope); + // 4. Set the running execution context's LexicalEnvironment to catchEnv. + vm.running_execution_context().lexical_environment = catch_environment; - m_handler->parameter().visit( - [&](FlyString const& parameter) { - (void)catch_scope->initialize_binding(global_object, parameter, exception->value()); - }, - [&](NonnullRefPtr<BindingPattern> const& pattern) { - (void)interpreter.vm().binding_initialization(pattern, exception->value(), catch_scope, global_object); - }); + // 5. Let status be BindingInitialization of CatchParameter with arguments thrownValue and catchEnv. + auto status = m_handler->parameter().visit( + [&](FlyString const& parameter) { + return catch_environment->initialize_binding(global_object, parameter, thrown_value); + }, + [&](NonnullRefPtr<BindingPattern> const& pattern) { + return vm.binding_initialization(pattern, thrown_value, catch_environment, global_object); + }); + + // 6. If status is an abrupt completion, then + if (status.is_error()) { + // a. Set the running execution context's LexicalEnvironment to oldEnv. + vm.running_execution_context().lexical_environment = old_environment; - if (!interpreter.exception()) - result = m_handler->body().execute(interpreter, global_object); + // b. Return Completion(status). + return status.release_error(); } + + // 7. Let B be the result of evaluating Block. + auto handler_result = m_handler->body().execute(interpreter, global_object); + + // 8. Set the running execution context's LexicalEnvironment to oldEnv. + vm.running_execution_context().lexical_environment = old_environment; + + // 9. Return Completion(B). + return handler_result; + }; + + Completion result; + + // 1. Let B be the result of evaluating Block. + auto block_result = m_block->execute(interpreter, global_object); + + // TryStatement : try Block Catch + // TryStatement : try Block Catch Finally + if (m_handler) { + vm.clear_exception(); + // 2. If B.[[Type]] is throw, let C be CatchClauseEvaluation of Catch with argument B.[[Value]]. + if (block_result.type() == Completion::Type::Throw) + result = catch_clause_evaluation(*block_result.value()); + // 3. Else, let C be B. + else + result = move(block_result); + } else { + // TryStatement : try Block Finally + // This variant doesn't have C & uses B in the finalizer step. + result = move(block_result); } + // TryStatement : try Block Finally + // TryStatement : try Block Catch Finally if (m_finalizer) { + // NOTE: Temporary until VM::exception() is removed // Keep, if any, and then clear the current exception so we can // execute() the finalizer without an exception in our way. - auto* previous_exception = interpreter.exception(); - interpreter.vm().clear_exception(); - - // Remember what scope type we were unwinding to, and temporarily - // clear it as well (e.g. return from handler). - auto unwind_until = interpreter.vm().unwind_until(); - interpreter.vm().stop_unwind(); + auto* previous_exception = vm.exception(); + vm.clear_exception(); + // 4. Let F be the result of evaluating Finally. auto finalizer_result = m_finalizer->execute(interpreter, global_object); - if (interpreter.vm().should_unwind()) { - // This was NOT a 'normal' completion (e.g. return from finalizer). - result = finalizer_result; - } else { - // Continue unwinding to whatever we found ourselves unwinding - // to when the finalizer was entered (e.g. return from handler, - // which is unaffected by normal completion from finalizer). - interpreter.vm().unwind(unwind_until); - - // If we previously had an exception and the finalizer didn't - // throw a new one, restore the old one. - if (previous_exception && !interpreter.exception()) - interpreter.vm().set_exception(*previous_exception); - } + + // 5. If F.[[Type]] is normal, set F to C. + if (finalizer_result.type() == Completion::Type::Normal) + finalizer_result = move(result); + + // NOTE: Temporary until VM::exception() is removed + // If we previously had an exception and we're carrying over + // the catch block completion, restore it. + if (finalizer_result.type() == Completion::Type::Normal && previous_exception) + vm.set_exception(*previous_exception); + + // 6. Return Completion(UpdateEmpty(F, undefined)). + return finalizer_result.update_empty(js_undefined()); } + // 4. Return Completion(UpdateEmpty(C, undefined)). return result.update_empty(js_undefined()); } @@ -3527,76 +3571,234 @@ Completion ThrowStatement::execute(Interpreter& interpreter, GlobalObject& globa return throw_completion(value); } -// 14.12.2 Runtime Semantics: CaseBlockEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-caseblockevaluation +// 14.1.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-statement-semantics-runtime-semantics-evaluation +// BreakableStatement : SwitchStatement Completion SwitchStatement::execute(Interpreter& interpreter, GlobalObject& global_object) const { - // FIXME: This needs a massive refactoring, ideally once we start using continue, break, and return completions. - // Instead of having an optional test expression, SwitchCase should be split into CaseClause and DefaultClause. - // https://tc39.es/ecma262/#sec-switch-statement + // 1. Let newLabelSet be a new empty List. + // 2. Return the result of performing LabelledEvaluation of this BreakableStatement with argument newLabelSet. + return labelled_evaluation(interpreter, global_object, *this, {}); +} +// NOTE: Since we don't have the 'BreakableStatement' from the spec as a separate ASTNode that wraps IterationStatement / SwitchStatement, +// execute() needs to take care of LabelledEvaluation, which in turn calls execute_impl(). +// 14.12.4 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-switch-statement-runtime-semantics-evaluation +Completion SwitchStatement::execute_impl(Interpreter& interpreter, GlobalObject& global_object) const +{ InterpreterNodeScope node_scope { interpreter, *this }; - auto discriminant_result = TRY(m_discriminant->execute(interpreter, global_object)).release_value(); + auto& vm = interpreter.vm(); - // Optimization: Avoid creating a lexical environment if there are no lexical declarations. - Optional<TemporaryChange<Environment*>> lexical_environment_changer; - if (has_lexical_declarations()) { - auto* old_environment = interpreter.lexical_environment(); - auto* block_environment = new_declarative_environment(*old_environment); - block_declaration_instantiation(global_object, block_environment); - lexical_environment_changer.emplace(interpreter.vm().running_execution_context().lexical_environment, block_environment); - } + // 14.12.3 CaseClauseIsSelected ( C, input ), https://tc39.es/ecma262/#sec-runtime-semantics-caseclauseisselected + auto case_clause_is_selected = [&](auto const& case_clause, auto input) -> ThrowCompletionOr<bool> { + // 1. Assert: C is an instance of the production CaseClause : case Expression : StatementList[opt] . + VERIFY(case_clause.test()); - Optional<size_t> first_passing_case; - for (size_t i = 0; i < m_cases.size(); ++i) { - auto& switch_case = m_cases[i]; - if (switch_case.test()) { - auto test_result = TRY(switch_case.test()->execute(interpreter, global_object)).release_value(); - if (is_strictly_equal(discriminant_result, test_result)) { - first_passing_case = i; - break; - } + // 2. Let exprRef be the result of evaluating the Expression of C. + // 3. Let clauseSelector be ? GetValue(exprRef). + auto clause_selector = TRY(case_clause.test()->execute(interpreter, global_object)).release_value(); + + // 4. Return IsStrictlyEqual(input, clauseSelector). + return is_strictly_equal(input, clause_selector); + }; + + // 14.12.2 Runtime Semantics: CaseBlockEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-caseblockevaluation + auto case_block_evaluation = [&](auto input) { + // CaseBlock : { } + if (m_cases.is_empty()) { + // 1. Return NormalCompletion(undefined). + return normal_completion(js_undefined()); } - } - // FIXME: we could optimize and store the location of the default case in a member variable. - if (!first_passing_case.has_value()) { - for (size_t i = 0; i < m_cases.size(); ++i) { - auto& switch_case = m_cases[i]; - if (!switch_case.test()) { - first_passing_case = i; - break; + NonnullRefPtrVector<SwitchCase> case_clauses_1; + NonnullRefPtrVector<SwitchCase> case_clauses_2; + RefPtr<SwitchCase> default_clause; + for (auto const& switch_case : m_cases) { + if (!switch_case.test()) + default_clause = switch_case; + else if (!default_clause) + case_clauses_1.append(switch_case); + else + case_clauses_2.append(switch_case); + } + + // CaseBlock : { CaseClauses } + if (!default_clause) { + VERIFY(!case_clauses_1.is_empty()); + VERIFY(case_clauses_2.is_empty()); + + // 1. Let V be undefined. + auto last_value = js_undefined(); + + // 2. Let A be the List of CaseClause items in CaseClauses, in source text order. + // NOTE: A is case_clauses_1. + + // 3. Let found be false. + auto found = false; + + // 4. For each CaseClause C of A, do + for (auto const& case_clause : case_clauses_1) { + // a. If found is false, then + if (!found) { + // i. Set found to ? CaseClauseIsSelected(C, input). + found = TRY(case_clause_is_selected(case_clause, input)); + } + + // b. If found is true, then + if (found) { + // i. Let R be the result of evaluating C. + auto result = case_clause.evaluate_statements(interpreter, global_object); + + // ii. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (result.value().has_value()) + last_value = *result.value(); + + // iii. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (result.is_abrupt()) + return result.update_empty(last_value); + } } + + // 5. Return NormalCompletion(V). + return normal_completion(last_value); } - } + // CaseBlock : { CaseClauses[opt] DefaultClause CaseClauses[opt] } + else { + // 1. Let V be undefined. + auto last_value = js_undefined(); + + // 2. If the first CaseClauses is present, then + // a. Let A be the List of CaseClause items in the first CaseClauses, in source text order. + // 3. Else, + // a. Let A be « ». + // NOTE: A is case_clauses_1. + + // 4. Let found be false. + auto found = false; + + // 5. For each CaseClause C of A, do + for (auto const& case_clause : case_clauses_1) { + // a. If found is false, then + if (!found) { + // i. Set found to ? CaseClauseIsSelected(C, input). + found = TRY(case_clause_is_selected(case_clause, input)); + } - auto last_value = js_undefined(); + // b. If found is true, then + if (found) { + // i. Let R be the result of evaluating C. + auto result = case_clause.evaluate_statements(interpreter, global_object); - if (!first_passing_case.has_value()) { - return last_value; - } - VERIFY(first_passing_case.value() < m_cases.size()); - - for (size_t i = first_passing_case.value(); i < m_cases.size(); ++i) { - auto& switch_case = m_cases[i]; - for (auto& statement : switch_case.children()) { - auto value = TRY(statement.execute(interpreter, global_object)); - if (value.has_value()) - last_value = *value; - if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { - // No stop_unwind(), the outer loop will handle that - we just need to break out of the switch/case. - return last_value; - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { - interpreter.vm().stop_unwind(); - return last_value; - } else { - return last_value; + // ii. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (result.value().has_value()) + last_value = *result.value(); + + // iii. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (result.is_abrupt()) + return result.update_empty(last_value); + } + } + + // 6. Let foundInB be false. + auto found_in_b = false; + + // 7. If the second CaseClauses is present, then + // a. Let B be the List of CaseClause items in the second CaseClauses, in source text order. + // 8. Else, + // a. Let B be « ». + // NOTE: B is case_clauses_2. + + // 9. If found is false, then + if (!found) { + // a. For each CaseClause C of B, do + for (auto const& case_clause : case_clauses_2) { + // i. If foundInB is false, then + if (!found_in_b) { + // 1. Set foundInB to ? CaseClauseIsSelected(C, input). + found_in_b = TRY(case_clause_is_selected(case_clause, input)); + } + + // ii. If foundInB is true, then + if (found_in_b) { + // 1. Let R be the result of evaluating CaseClause C. + auto result = case_clause.evaluate_statements(interpreter, global_object); + + // 2. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (result.value().has_value()) + last_value = *result.value(); + + // 3. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (result.is_abrupt()) + return result.update_empty(last_value); + } } } + + // 10. If foundInB is true, return NormalCompletion(V). + if (found_in_b) + return normal_completion(last_value); + + // 11. Let R be the result of evaluating DefaultClause. + auto result = default_clause->evaluate_statements(interpreter, global_object); + + // 12. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (result.value().has_value()) + last_value = *result.value(); + + // 13. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (result.is_abrupt()) + return result.update_empty(last_value); + + // 14. NOTE: The following is another complete iteration of the second CaseClauses. + // 15. For each CaseClause C of B, do + for (auto const& case_clause : case_clauses_2) { + // a. Let R be the result of evaluating CaseClause C. + result = case_clause.evaluate_statements(interpreter, global_object); + + // b. If R.[[Value]] is not empty, set V to R.[[Value]]. + if (result.value().has_value()) + last_value = *result.value(); + + // c. If R is an abrupt completion, return Completion(UpdateEmpty(R, V)). + if (result.is_abrupt()) + return result.update_empty(last_value); + } + + // 16. Return NormalCompletion(V). + return normal_completion(last_value); } + + VERIFY_NOT_REACHED(); + }; + + // SwitchStatement : switch ( Expression ) CaseBlock + // 1. Let exprRef be the result of evaluating Expression. + // 2. Let switchValue be ? GetValue(exprRef). + auto switch_value = TRY(m_discriminant->execute(interpreter, global_object)).release_value(); + + // 3. Let oldEnv be the running execution context's LexicalEnvironment. + auto* old_environment = interpreter.lexical_environment(); + + // Optimization: Avoid creating a lexical environment if there are no lexical declarations. + if (has_lexical_declarations()) { + // 4. Let blockEnv be NewDeclarativeEnvironment(oldEnv). + auto* block_environment = new_declarative_environment(*old_environment); + + // 5. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv). + block_declaration_instantiation(global_object, block_environment); + + // 6. Set the running execution context's LexicalEnvironment to blockEnv. + vm.running_execution_context().lexical_environment = block_environment; } - return last_value; + + // 7. Let R be CaseBlockEvaluation of CaseBlock with argument switchValue. + auto result = case_block_evaluation(switch_value); + + // 8. Set the running execution context's LexicalEnvironment to oldEnv. + vm.running_execution_context().lexical_environment = old_environment; + + // 9. Return R. + return result; } Completion SwitchCase::execute(Interpreter& interpreter, GlobalObject&) const @@ -3613,9 +3815,16 @@ Completion BreakStatement::execute(Interpreter& interpreter, GlobalObject&) cons { InterpreterNodeScope node_scope { interpreter, *this }; - // TODO: Return break completion - interpreter.vm().unwind(ScopeType::Breakable, m_target_label); - return normal_completion({}); + // BreakStatement : break ; + if (m_target_label.is_null()) { + // 1. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }. + return { Completion::Type::Break, {}, {} }; + } + + // BreakStatement : break LabelIdentifier ; + // 1. Let label be the StringValue of LabelIdentifier. + // 2. Return Completion { [[Type]]: break, [[Value]]: empty, [[Target]]: label }. + return { Completion::Type::Break, {}, m_target_label }; } // 14.8.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-continue-statement-runtime-semantics-evaluation @@ -3623,9 +3832,16 @@ Completion ContinueStatement::execute(Interpreter& interpreter, GlobalObject&) c { InterpreterNodeScope node_scope { interpreter, *this }; - // TODO: Return continue completion - interpreter.vm().unwind(ScopeType::Continuable, m_target_label); - return normal_completion({}); + // ContinueStatement : continue ; + if (m_target_label.is_null()) { + // 1. Return Completion { [[Type]]: continue, [[Value]]: empty, [[Target]]: empty }. + return { Completion::Type::Continue, {}, {} }; + } + + // ContinueStatement : continue LabelIdentifier ; + // 1. Let label be the StringValue of LabelIdentifier. + // 2. Return Completion { [[Type]]: continue, [[Value]]: empty, [[Target]]: label }. + return { Completion::Type::Continue, {}, m_target_label }; } void SwitchStatement::dump(int indent) const diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index d918ded5a9..cfbb18e97e 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -120,9 +120,11 @@ protected: Vector<FlyString> m_labels; }; -class IterationStatement : public LabelableStatement { +class IterationStatement : public Statement { public: - using LabelableStatement::LabelableStatement; + using Statement::Statement; + + virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const = 0; }; class EmptyStatement final : public Statement { @@ -183,7 +185,7 @@ public: } }; -class ScopeNode : public LabelableStatement { +class ScopeNode : public Statement { public: template<typename T, typename... Args> T& append(SourceRange range, Args&&... args) @@ -227,7 +229,7 @@ public: protected: explicit ScopeNode(SourceRange source_range) - : LabelableStatement(source_range) + : Statement(source_range) { } @@ -681,6 +683,7 @@ public: Statement const& body() const { return *m_body; } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override; virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; @@ -702,6 +705,7 @@ public: Statement const& body() const { return *m_body; } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override; virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; @@ -747,6 +751,7 @@ public: Statement const& body() const { return *m_body; } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override; virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; @@ -772,6 +777,7 @@ public: Statement const& body() const { return *m_body; } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override; virtual void dump(int indent) const override; private: @@ -795,6 +801,7 @@ public: Statement const& body() const { return *m_body; } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override; virtual void dump(int indent) const override; private: @@ -814,6 +821,7 @@ public: } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector<FlyString> const&) const override; virtual void dump(int indent) const override; private: @@ -1786,10 +1794,10 @@ private: NonnullRefPtr<BlockStatement> m_body; }; -class TryStatement final : public LabelableStatement { +class TryStatement final : public Statement { public: TryStatement(SourceRange source_range, NonnullRefPtr<BlockStatement> block, RefPtr<CatchClause> handler, RefPtr<BlockStatement> finalizer) - : LabelableStatement(source_range) + : Statement(source_range) , m_block(move(block)) , m_handler(move(handler)) , m_finalizer(move(finalizer)) @@ -1803,7 +1811,6 @@ public: virtual void dump(int indent) const override; virtual Completion execute(Interpreter&, GlobalObject&) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; - void add_label(FlyString string) override; private: NonnullRefPtr<BlockStatement> m_block; @@ -1858,6 +1865,7 @@ public: virtual Completion execute(Interpreter&, GlobalObject&) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; + Completion execute_impl(Interpreter&, GlobalObject&) const; void add_case(NonnullRefPtr<SwitchCase> switch_case) { m_cases.append(move(switch_case)); } private: diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp index fc215875a8..eb1cc9df18 100644 --- a/Userland/Libraries/LibJS/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Interpreter.cpp @@ -62,10 +62,6 @@ void Interpreter::run(GlobalObject& global_object, const Program& program) auto completion = program.execute(*this, global_object); vm.set_last_value(Badge<Interpreter> {}, completion.value().value_or(js_undefined())); - // FIXME: We unconditionally stop the unwind here this should be done using completions leaving - // the VM in a cleaner state after executing. For example it does still store the exception. - vm.stop_unwind(); - // At this point we may have already run any queued promise jobs via on_call_stack_emptied, // in which case this is a no-op. vm.run_queued_promise_jobs(); diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 7132713aae..404802a738 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -822,7 +822,7 @@ RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expe /* might_need_arguments_object */ false, contains_direct_call_to_eval, /* is_arrow_function */ true); } -RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction allow_function) +RefPtr<LabelledStatement> Parser::try_parse_labelled_statement(AllowLabelledFunction allow_function) { { // NOTE: This is a fast path where we try to fail early to avoid the expensive save_state+load_state. @@ -873,7 +873,7 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all if (m_state.labels_in_scope.contains(identifier)) syntax_error(String::formatted("Label '{}' has already been declared", identifier)); - RefPtr<Statement> labelled_statement; + RefPtr<Statement> labelled_item; auto is_iteration_statement = false; @@ -887,16 +887,16 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all if (function_declaration->kind() == FunctionKind::Async) syntax_error("Async functions cannot be defined in labelled statements"); - labelled_statement = move(function_declaration); + labelled_item = move(function_declaration); } else { m_state.labels_in_scope.set(identifier, {}); - labelled_statement = parse_statement(allow_function); - if (is<IterationStatement>(*labelled_statement)) { + labelled_item = parse_statement(allow_function); + // Extract the innermost statement from a potentially nested chain of LabelledStatements. + auto statement = labelled_item; + while (is<LabelledStatement>(*statement)) + statement = static_cast<LabelledStatement&>(*statement).labelled_item(); + if (is<IterationStatement>(*statement)) is_iteration_statement = true; - static_cast<IterationStatement&>(*labelled_statement).add_label(identifier); - } else if (is<LabelableStatement>(*labelled_statement)) { - static_cast<LabelableStatement&>(*labelled_statement).add_label(identifier); - } } if (!is_iteration_statement) { @@ -906,7 +906,7 @@ RefPtr<Statement> Parser::try_parse_labelled_statement(AllowLabelledFunction all m_state.labels_in_scope.remove(identifier); - return labelled_statement.release_nonnull(); + return create_ast_node<LabelledStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, identifier, labelled_item.release_nonnull()); } RefPtr<MetaProperty> Parser::try_parse_new_target_expression() @@ -1286,7 +1286,10 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_ auto super_call = create_ast_node<SuperCall>( { m_state.current_token.filename(), rule_start.position(), position() }, Vector { CallExpression::Argument { create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, "args"), true } }); - constructor_body->append(create_ast_node<ExpressionStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, move(super_call))); + // NOTE: While the JS approximation above doesn't do `return super(...args)`, the + // abstract closure is expected to capture and return the result, so we do need a + // return statement here to create the correct completion. + constructor_body->append(create_ast_node<ReturnStatement>({ m_state.current_token.filename(), rule_start.position(), position() }, move(super_call))); constructor = create_ast_node<FunctionExpression>( { m_state.current_token.filename(), rule_start.position(), position() }, class_name, move(constructor_body), diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 0d00e652de..738da60dd9 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -120,7 +120,7 @@ public: NonnullRefPtr<ExportStatement> parse_export_statement(Program& program); RefPtr<FunctionExpression> try_parse_arrow_function_expression(bool expect_parens, bool is_async = false); - RefPtr<Statement> try_parse_labelled_statement(AllowLabelledFunction allow_function); + RefPtr<LabelledStatement> try_parse_labelled_statement(AllowLabelledFunction allow_function); RefPtr<MetaProperty> try_parse_new_target_expression(); RefPtr<MetaProperty> try_parse_import_meta_expression(); NonnullRefPtr<ImportCall> parse_import_call(); diff --git a/Userland/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp b/Userland/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp index 08fd1c4d0c..b91a173e47 100644 --- a/Userland/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp +++ b/Userland/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp @@ -39,7 +39,6 @@ ThrowCompletionOr<Value> AsyncFunctionDriverWrapper::react_to_async_task_complet if (generator_result.is_throw_completion()) { VERIFY(generator_result.throw_completion().type() == Completion::Type::Throw); vm.clear_exception(); - vm.stop_unwind(); auto promise = Promise::create(global_object); promise->reject(*generator_result.throw_completion().value()); return promise; diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 4846757f5b..d4f6246bdb 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -171,8 +171,6 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argu // 9. ReturnIfAbrupt(result). if (result.is_abrupt()) { - // NOTE: I'm not sure if EvaluateBody can return a completion other than Normal, Return, or Throw. - // We're far from using completions in the AST anyway; in the meantime assume Throw. VERIFY(result.is_error()); return result; } @@ -266,9 +264,7 @@ ThrowCompletionOr<Object*> ECMAScriptFunctionObject::internal_construct(MarkedVa return vm.throw_completion<TypeError>(global_object, ErrorType::DerivedConstructorReturningInvalidValue); } // 11. Else, ReturnIfAbrupt(result). - else { - // NOTE: I'm not sure if EvaluateBody can return a completion other than Normal, Return, or Throw. - // We're far from using completions in the AST anyway; in the meantime assume Throw. + else if (result.is_abrupt()) { VERIFY(result.is_error()); return result; } @@ -713,25 +709,23 @@ void ECMAScriptFunctionObject::async_block_start(PromiseCapability const& promis // c. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. vm.pop_execution_context(); - // NOTE: Eventually we'll distinguish between normal and return completion. - // For now, we assume "return" and include the undefined fallback from the call site. // d. If result.[[Type]] is normal, then - if (false) { + if (result.type() == Completion::Type::Normal) { // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »). MUST(call(global_object, promise_capability.resolve, js_undefined(), js_undefined())); } // e. Else if result.[[Type]] is return, then - else if (result.type() == Completion::Type::Return || result.type() == Completion::Type::Normal) { + else if (result.type() == Completion::Type::Return) { // i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »). - MUST(call(global_object, promise_capability.resolve, js_undefined(), result.value().value_or(js_undefined()))); + MUST(call(global_object, promise_capability.resolve, js_undefined(), *result.value())); } // f. Else, else { // i. Assert: result.[[Type]] is throw. + VERIFY(result.type() == Completion::Type::Throw); // ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »). vm.clear_exception(); - vm.stop_unwind(); MUST(call(global_object, promise_capability.reject, js_undefined(), *result.value())); } // g. Return. @@ -756,6 +750,7 @@ void ECMAScriptFunctionObject::async_block_start(PromiseCapability const& promis } // 10.2.1.4 OrdinaryCallEvaluateBody ( F, argumentsList ), https://tc39.es/ecma262/#sec-ordinarycallevaluatebody +// 15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody, https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() { auto& vm = this->vm(); @@ -817,14 +812,16 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() VM::InterpreterExecutionScope scope(*ast_interpreter); + // FunctionBody : FunctionStatementList if (m_kind == FunctionKind::Regular) { + // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). TRY(function_declaration_instantiation(ast_interpreter)); - auto result = TRY(m_ecmascript_code->execute(*ast_interpreter, global_object())); - // NOTE: Once 'return' completions are being used, we can just return the completion from execute() directly. - // For now, we assume "return" and include the undefined fallback from the call site. - return { Completion::Type::Return, result.value_or(js_undefined()), {} }; - } else if (m_kind == FunctionKind::Async) { + // 2. Return the result of evaluating FunctionStatementList. + return m_ecmascript_code->execute(*ast_interpreter, global_object()); + } + // AsyncFunctionBody : FunctionBody + else if (m_kind == FunctionKind::Async) { // 1. Let promiseCapability be ! NewPromiseCapability(%Promise%). auto promise_capability = MUST(new_promise_capability(global_object(), global_object().promise_constructor())); diff --git a/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp index 1e158e7e60..691e13046e 100644 --- a/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp @@ -120,8 +120,6 @@ static Completion iterator_close_(Object& iterator, Completion completion, Itera if (!return_method) return completion; - vm.stop_unwind(); - // c. Set innerResult to Call(return, iterator). auto result_or_error = vm.call(*return_method, &iterator); if (result_or_error.is_error()) { diff --git a/Userland/Libraries/LibJS/Runtime/Promise.cpp b/Userland/Libraries/LibJS/Runtime/Promise.cpp index 9219d3ff53..4d4cf06fcb 100644 --- a/Userland/Libraries/LibJS/Runtime/Promise.cpp +++ b/Userland/Libraries/LibJS/Runtime/Promise.cpp @@ -115,7 +115,6 @@ Promise::ResolvingFunctions Promise::create_resolving_functions() // a. Return RejectPromise(promise, then.[[Value]]). dbgln_if(PROMISE_DEBUG, "[Promise @ {} / PromiseResolvingFunction]: Exception while getting 'then' property, rejecting with error", &promise); vm.clear_exception(); - vm.stop_unwind(); return promise.reject(*then.throw_completion().value()); } diff --git a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp index d022f6cbce..15f6000b75 100644 --- a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp @@ -329,7 +329,6 @@ ThrowCompletionOr<Object*> PromiseConstructor::construct(FunctionObject& new_tar // 10. If completion is an abrupt completion, then if (auto* exception = vm.exception()) { vm.clear_exception(); - vm.stop_unwind(); // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). TRY(vm.call(reject_function, js_undefined(), exception->value())); diff --git a/Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp b/Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp index 354aed7d68..ccd8171e37 100644 --- a/Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp +++ b/Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp @@ -84,7 +84,6 @@ ThrowCompletionOr<Value> PromiseReactionJob::call() // h. If handlerResult is an abrupt completion, then if (handler_result.is_abrupt()) { vm.clear_exception(); - vm.stop_unwind(); // i. Let status be Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). auto* reject_function = promise_capability.value().reject; @@ -139,7 +138,6 @@ ThrowCompletionOr<Value> PromiseResolveThenableJob::call() // c. If thenCallResult is an abrupt completion, then if (then_call_result.is_error()) { vm.clear_exception(); - vm.stop_unwind(); // i. Let status be Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). dbgln_if(PROMISE_DEBUG, "[PromiseResolveThenableJob @ {}]: then_call_result is an abrupt completion, calling reject function with value {}", this, *then_call_result.throw_completion().value()); diff --git a/Userland/Libraries/LibJS/Runtime/PromiseReaction.h b/Userland/Libraries/LibJS/Runtime/PromiseReaction.h index a223135972..1cbba98e33 100644 --- a/Userland/Libraries/LibJS/Runtime/PromiseReaction.h +++ b/Userland/Libraries/LibJS/Runtime/PromiseReaction.h @@ -26,7 +26,6 @@ struct PromiseCapability { /* 1. If value is an abrupt completion, then */ \ if (_temporary_try_or_reject_result.is_error()) { \ vm.clear_exception(); \ - vm.stop_unwind(); \ \ /* a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). */ \ TRY(vm.call(*capability.reject, js_undefined(), *_temporary_try_or_reject_result.release_error().value())); \ diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index 1416853766..6aec616e39 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -468,7 +468,6 @@ ThrowCompletionOr<void> VM::initialize_instance_elements(Object& object, ECMAScr void VM::throw_exception(Exception& exception) { set_exception(exception); - unwind(ScopeType::Try); } // 9.4.4 ResolveThisBinding ( ), https://tc39.es/ecma262/#sec-resolvethisbinding @@ -547,7 +546,6 @@ void VM::run_queued_promise_jobs() // exceptions when running Promise jobs. See the commit where these two lines were initially // added for a much more detailed explanation. clear_exception(); - stop_unwind(); if (pushed_execution_context) pop_execution_context(); diff --git a/Userland/Libraries/LibJS/Tests/labels.js b/Userland/Libraries/LibJS/Tests/labels.js index 4224a290a0..c908f00151 100644 --- a/Userland/Libraries/LibJS/Tests/labels.js +++ b/Userland/Libraries/LibJS/Tests/labels.js @@ -1,4 +1,4 @@ -test("labeled plain scope", () => { +test("labelled plain scope", () => { notused: test: alsonotused: { let o = 1; expect(o).toBe(1); @@ -16,7 +16,7 @@ test("break on plain scope from inner scope", () => { } }); -test("labeled for loop with break", () => { +test("labelled for loop with break", () => { let counter = 0; notused: outer: alsonotused: for (a of [1, 2, 3]) { for (b of [4, 5, 6]) { @@ -27,7 +27,7 @@ test("labeled for loop with break", () => { expect(counter).toBe(4); }); -test("labeled for loop with continue", () => { +test("labelled for loop with continue", () => { let counter = 0; notused: outer: alsonotused: for (a of [1, 2, 3]) { for (b of [4, 5, 6]) { @@ -38,6 +38,32 @@ test("labeled for loop with continue", () => { expect(counter).toBe(6); }); +test("continue label statement is not an iteration statement", () => { + expect(() => + eval(` +outer: outer2: { + for (;;) { + continue outer; + } +}`) + ).toThrowWithMessage( + SyntaxError, + "labelled continue statement cannot use non iterating statement (line: 4, column: 18)" + ); + + expect(() => + eval(` +for (;;) { + outer: outer2: { + continue outer; + } +}`) + ).toThrowWithMessage( + SyntaxError, + "labelled continue statement cannot use non iterating statement (line: 4, column: 18)" + ); +}); + test("break on try catch statement", () => { let entered = false; label1: label2: label3: try { diff --git a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp index 8c32db5c3d..201613f6f0 100644 --- a/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp +++ b/Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp @@ -162,7 +162,6 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::compile) if (buffer_or_error.is_error()) { rejection_value = *buffer_or_error.throw_completion().value(); vm.clear_exception(); - vm.stop_unwind(); } auto promise = JS::Promise::create(global_object); if (!rejection_value.is_empty()) { @@ -327,7 +326,6 @@ JS_DEFINE_NATIVE_FUNCTION(WebAssemblyObject::instantiate) if (buffer_or_error.is_error()) { auto rejection_value = *buffer_or_error.throw_completion().value(); vm.clear_exception(); - vm.stop_unwind(); promise->reject(rejection_value); return promise; } diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index 7c91bb09d0..30e20390e9 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -795,7 +795,6 @@ static void print_value(JS::Value value, HashTable<JS::Object*>& seen_objects) if (prototype_or_error.has_value() && prototype_or_error.value() == object.global_object().error_prototype()) return print_error(object, seen_objects); vm->clear_exception(); - vm->stop_unwind(); if (is<JS::RegExpObject>(object)) return print_regexp_object(object, seen_objects); |