From 3252d984aee0fc208586cf26a05967f8b8d2a9d5 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 26 Sep 2021 18:16:06 +0200 Subject: [PATCH] LibJS: Allow statements to have multiple labels This is a curious thing that occurs more often than you'd think in minified JavaScript: a: b: c: for (...) { ... break b; ... } --- Userland/Libraries/LibJS/AST.cpp | 28 +++++++++---------- Userland/Libraries/LibJS/AST.h | 7 +++-- Userland/Libraries/LibJS/Interpreter.cpp | 2 +- Userland/Libraries/LibJS/Parser.cpp | 2 +- Userland/Libraries/LibJS/Runtime/VM.h | 4 +-- .../LibJS/Tests/statement-with-many-labels.js | 9 ++++++ 6 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 Userland/Libraries/LibJS/Tests/statement-with-many-labels.js diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 90ca7d5c65..b69a286370 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -395,9 +395,9 @@ Value WhileStatement::execute(Interpreter& interpreter, GlobalObject& global_obj if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) { + if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) { + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { interpreter.vm().stop_unwind(); break; } else { @@ -421,9 +421,9 @@ Value DoWhileStatement::execute(Interpreter& interpreter, GlobalObject& global_o if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) { + if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) { + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { interpreter.vm().stop_unwind(); break; } else { @@ -477,9 +477,9 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) { + if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) { + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { interpreter.vm().stop_unwind(); break; } else { @@ -498,9 +498,9 @@ Value ForStatement::execute(Interpreter& interpreter, GlobalObject& global_objec if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) { + if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) { + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { interpreter.vm().stop_unwind(); break; } else { @@ -570,9 +570,9 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj if (interpreter.exception()) return {}; if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) { + if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) { + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { interpreter.vm().stop_unwind(); break; } else { @@ -613,9 +613,9 @@ Value ForOfStatement::execute(Interpreter& interpreter, GlobalObject& global_obj if (interpreter.exception()) return IterationDecision::Break; if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) { + if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_labels)) { interpreter.vm().stop_unwind(); - } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_label)) { + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { interpreter.vm().stop_unwind(); return IterationDecision::Break; } else { @@ -2429,10 +2429,10 @@ Value SwitchStatement::execute(Interpreter& interpreter, GlobalObject& global_ob if (interpreter.exception()) return Value {}; if (interpreter.vm().should_unwind()) { - if (interpreter.vm().should_unwind_until(ScopeType::Continuable, m_label)) { + 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_label)) { + } else if (interpreter.vm().should_unwind_until(ScopeType::Breakable, m_labels)) { interpreter.vm().stop_unwind(); return last_value; } else { diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 37f21efb62..c9aeb8bfaf 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -80,11 +80,12 @@ public: { } - FlyString const& label() const { return m_label; } - void set_label(FlyString string) { m_label = move(string); } + HashTable const& labels() const { return m_labels; } + void add_label(FlyString label) { m_labels.set(move(label)); } + bool has_label(FlyString const& label) const { return m_labels.contains(label); } protected: - FlyString m_label; + HashTable m_labels; }; class EmptyStatement final : public Statement { diff --git a/Userland/Libraries/LibJS/Interpreter.cpp b/Userland/Libraries/LibJS/Interpreter.cpp index 0d7ff1a346..04d5d64f01 100644 --- a/Userland/Libraries/LibJS/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Interpreter.cpp @@ -193,7 +193,7 @@ Value Interpreter::execute_statement(GlobalObject& global_object, const Statemen if (!value.is_empty()) last_value = value; if (vm().should_unwind()) { - if (!block.label().is_null() && vm().should_unwind_until(ScopeType::Breakable, block.label())) + if (!block.labels().is_empty() && vm().should_unwind_until(ScopeType::Breakable, block.labels())) vm().stop_unwind(); break; } diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 6ef37cf761..cb31c63a9b 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -621,7 +621,7 @@ RefPtr Parser::try_parse_labelled_statement(AllowLabelledFunction all m_state.labels_in_scope.remove(identifier); - labelled_statement->set_label(identifier); + labelled_statement->add_label(identifier); state_rollback_guard.disarm(); discard_saved_state(); return labelled_statement.release_nonnull(); diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h index 6f1f3ba2f1..0bd7d9a714 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.h +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -193,11 +193,11 @@ public: m_unwind_until = ScopeType::None; m_unwind_until_label = {}; } - bool should_unwind_until(ScopeType type, FlyString const& label) const + bool should_unwind_until(ScopeType type, HashTable const& labels) const { if (m_unwind_until_label.is_null()) return m_unwind_until == type; - return m_unwind_until == type && m_unwind_until_label == label; + return m_unwind_until == type && labels.contains(m_unwind_until_label); } bool should_unwind() const { return m_unwind_until != ScopeType::None; } diff --git a/Userland/Libraries/LibJS/Tests/statement-with-many-labels.js b/Userland/Libraries/LibJS/Tests/statement-with-many-labels.js new file mode 100644 index 0000000000..534811252e --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/statement-with-many-labels.js @@ -0,0 +1,9 @@ +test("basic support for statement with many labels", () => { + function foo() { + a: b: c: for (;;) { + break b; + } + return 1; + } + expect(foo()).toBe(1); +});