summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorLinus Groh <mail@linusgroh.de>2022-01-05 19:11:16 +0100
committerLinus Groh <mail@linusgroh.de>2022-01-06 12:36:23 +0100
commit9d0d3affd4feaab62c37e284a17432815c071c24 (patch)
tree205b9f342ad4c53e5865ff80906873992ccc759f /Userland
parenteed764e1dd3288f203108229c7e1ca9d09aef4d5 (diff)
downloadserenity-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.cpp666
-rw-r--r--Userland/Libraries/LibJS/AST.h22
-rw-r--r--Userland/Libraries/LibJS/Interpreter.cpp4
-rw-r--r--Userland/Libraries/LibJS/Parser.cpp25
-rw-r--r--Userland/Libraries/LibJS/Parser.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/AsyncFunctionDriverWrapper.cpp1
-rw-r--r--Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp29
-rw-r--r--Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/Promise.cpp1
-rw-r--r--Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp1
-rw-r--r--Userland/Libraries/LibJS/Runtime/PromiseJobs.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/PromiseReaction.h1
-rw-r--r--Userland/Libraries/LibJS/Runtime/VM.cpp2
-rw-r--r--Userland/Libraries/LibJS/Tests/labels.js32
-rw-r--r--Userland/Libraries/LibWeb/WebAssembly/WebAssemblyObject.cpp2
-rw-r--r--Userland/Utilities/js.cpp1
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);