浏览代码

LibJS: break or continue with nonexistent label is a syntax error

Matthew Olsson 4 年之前
父节点
当前提交
e8da5f99b1

+ 21 - 3
Libraries/LibJS/Parser.cpp

@@ -393,6 +393,11 @@ RefPtr<FunctionExpression> 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<BlockStatement> {
@@ -440,7 +445,9 @@ RefPtr<Statement> 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<FunctionNodeType> 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<BreakStatement> 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<ContinueStatement> Parser::parse_continue_statement()
         consume();
         return create_ast_node<ContinueStatement>(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<ContinueStatement>(target_label);
 }

+ 2 - 0
Libraries/LibJS/Parser.h

@@ -26,6 +26,7 @@
 
 #pragma once
 
+#include <AK/HashTable.h>
 #include <AK/NonnullRefPtr.h>
 #include <AK/StringBuilder.h>
 #include <LibJS/AST.h>
@@ -155,6 +156,7 @@ private:
         Vector<NonnullRefPtrVector<VariableDeclaration>> m_let_scopes;
         Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes;
         UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None };
+        HashTable<StringView> m_labels_in_scope;
         bool m_strict_mode { false };
         bool m_allow_super_property_lookup { false };
         bool m_allow_super_constructor_call { false };

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

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