Przeglądaj źródła

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.
Linus Groh 3 lat temu
rodzic
commit
9d0d3affd4

+ 441 - 225
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,8 +609,17 @@ 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 };
 
@@ -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,8 +651,17 @@ 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 };
 
@@ -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,8 +693,17 @@ 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 };
 
@@ -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,8 +984,17 @@ 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 };
 
@@ -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,8 +1050,17 @@ 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 };
 
@@ -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,34 +1091,39 @@ 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 };
 
@@ -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

+ 15 - 7
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:

+ 0 - 4
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();

+ 14 - 11
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),

+ 1 - 1
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();

+ 0 - 1
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;

+ 13 - 16
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()));
 

+ 0 - 2
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()) {

+ 0 - 1
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());
         }
 

+ 0 - 1
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()));

+ 0 - 2
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());

+ 0 - 1
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())); \

+ 0 - 2
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();

+ 29 - 3
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 {

+ 0 - 2
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;
     }

+ 0 - 1
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);