diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 069d1c6d35..7a890df614 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -393,6 +393,11 @@ RefPtr Parser::try_parse_arrow_function_expression(bool expe if (function_length == -1) function_length = parameters.size(); + auto old_labels_in_scope = move(m_parser_state.m_labels_in_scope); + ScopeGuard guard([&]() { + m_parser_state.m_labels_in_scope = move(old_labels_in_scope); + }); + bool is_strict = false; auto function_body_result = [&]() -> RefPtr { @@ -440,7 +445,9 @@ RefPtr Parser::try_parse_labelled_statement() if (!match_statement()) return {}; + m_parser_state.m_labels_in_scope.set(identifier); auto statement = parse_statement(); + m_parser_state.m_labels_in_scope.remove(identifier); statement->set_label(identifier); state_rollback_guard.disarm(); @@ -1318,8 +1325,13 @@ NonnullRefPtr Parser::parse_function_node(bool check_for_funct if (function_length == -1) function_length = parameters.size(); - bool is_strict = false; TemporaryChange change(m_parser_state.m_in_function_context, true); + auto old_labels_in_scope = move(m_parser_state.m_labels_in_scope); + ScopeGuard guard([&]() { + m_parser_state.m_labels_in_scope = move(old_labels_in_scope); + }); + + bool is_strict = false; auto body = parse_block_statement(is_strict); body->add_variables(m_parser_state.m_var_scopes.last()); body->add_functions(m_parser_state.m_function_scopes.last()); @@ -1395,8 +1407,11 @@ NonnullRefPtr Parser::parse_break_statement() if (match(TokenType::Semicolon)) { consume(); } else { - if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n')) + if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n')) { target_label = consume().value(); + if (!m_parser_state.m_labels_in_scope.contains(target_label)) + syntax_error(String::formatted("Label '{}' not found", target_label)); + } consume_or_insert_semicolon(); } @@ -1417,8 +1432,11 @@ NonnullRefPtr Parser::parse_continue_statement() consume(); return create_ast_node(target_label); } - if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n')) + if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n')) { target_label = consume().value(); + if (!m_parser_state.m_labels_in_scope.contains(target_label)) + syntax_error(String::formatted("Label '{}' not found", target_label)); + } consume_or_insert_semicolon(); return create_ast_node(target_label); } diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 21a63e1acc..4e3ecd7f58 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -26,6 +26,7 @@ #pragma once +#include #include #include #include @@ -155,6 +156,7 @@ private: Vector> m_let_scopes; Vector> m_function_scopes; UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None }; + HashTable m_labels_in_scope; bool m_strict_mode { false }; bool m_allow_super_property_lookup { false }; bool m_allow_super_constructor_call { false }; diff --git a/Libraries/LibJS/Tests/break-continue-syntax-errors.js b/Libraries/LibJS/Tests/break-continue-syntax-errors.js index 36f970b848..106118823f 100644 --- a/Libraries/LibJS/Tests/break-continue-syntax-errors.js +++ b/Libraries/LibJS/Tests/break-continue-syntax-errors.js @@ -2,8 +2,7 @@ test("'break' syntax errors", () => { expect("break").not.toEval(); expect("break label").not.toEval(); expect("{ break }").not.toEval(); - // FIXME: Parser does not throw error on nonexistent label - // expect("{ break label }.not.toEval(); + expect("{ break label }").not.toEval(); expect("label: { break label }").toEval(); }); diff --git a/Libraries/LibJS/Tests/labels.js b/Libraries/LibJS/Tests/labels.js index 6e9231eb96..1747e8d0bb 100644 --- a/Libraries/LibJS/Tests/labels.js +++ b/Libraries/LibJS/Tests/labels.js @@ -37,3 +37,11 @@ test("labeled for loop with continue", () => { } expect(counter).toBe(6); }); + +test("invalid label across scope", () => { + expect(` + label: { + (() => { break label; }); + } + `).not.toEval(); +});