Kaynağa Gözat

LibJS: Disallow 'continue' & 'break' outside of their respective scopes

'continue' is no longer allowed outside of a loop, and an unlabeled
'break' is not longer allowed outside of a loop or switch statement.
Labeled 'break' statements are still allowed everywhere, even if the
label does not exist.
Matthew Olsson 4 yıl önce
ebeveyn
işleme
e49ea1b520

+ 24 - 5
Libraries/LibJS/Parser.cpp

@@ -1394,16 +1394,23 @@ NonnullRefPtr<BreakStatement> Parser::parse_break_statement()
     FlyString target_label;
     if (match(TokenType::Semicolon)) {
         consume();
-        return create_ast_node<BreakStatement>(target_label);
+    } else {
+        if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n'))
+            target_label = consume().value();
+        consume_or_insert_semicolon();
     }
-    if (match(TokenType::Identifier) && !m_parser_state.m_current_token.trivia().contains('\n'))
-        target_label = consume().value();
-    consume_or_insert_semicolon();
+
+    if (target_label.is_null() && !m_parser_state.m_in_break_context)
+        syntax_error("Unlabeled 'break' not allowed outside of a loop or switch statement");
+
     return create_ast_node<BreakStatement>(target_label);
 }
 
 NonnullRefPtr<ContinueStatement> Parser::parse_continue_statement()
 {
+    if (!m_parser_state.m_in_continue_context)
+        syntax_error("'continue' not allow outside of a loop");
+
     consume(TokenType::Continue);
     FlyString target_label;
     if (match(TokenType::Semicolon)) {
@@ -1448,7 +1455,11 @@ NonnullRefPtr<DoWhileStatement> Parser::parse_do_while_statement()
 {
     consume(TokenType::Do);
 
-    auto body = parse_statement();
+    auto body = [&]() -> NonnullRefPtr<Statement> {
+        TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+        TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
+        return parse_statement();
+    }();
 
     consume(TokenType::While);
     consume(TokenType::ParenOpen);
@@ -1470,6 +1481,8 @@ NonnullRefPtr<WhileStatement> Parser::parse_while_statement()
 
     consume(TokenType::ParenClose);
 
+    TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+    TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
     auto body = parse_statement();
 
     return create_ast_node<WhileStatement>(move(test), move(body));
@@ -1506,6 +1519,7 @@ NonnullRefPtr<SwitchCase> Parser::parse_switch_case()
     consume(TokenType::Colon);
 
     NonnullRefPtrVector<Statement> consequent;
+    TemporaryChange break_change(m_parser_state.m_in_break_context, true);
     while (match_statement())
         consequent.append(parse_statement());
 
@@ -1585,6 +1599,8 @@ NonnullRefPtr<Statement> Parser::parse_for_statement()
 
     consume(TokenType::ParenClose);
 
+    TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+    TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
     auto body = parse_statement();
 
     if (in_scope) {
@@ -1610,6 +1626,9 @@ NonnullRefPtr<Statement> Parser::parse_for_in_of_statement(NonnullRefPtr<ASTNode
     auto in_or_of = consume();
     auto rhs = parse_expression(0);
     consume(TokenType::ParenClose);
+
+    TemporaryChange break_change(m_parser_state.m_in_break_context, true);
+    TemporaryChange continue_change(m_parser_state.m_in_continue_context, true);
     auto body = parse_statement();
     if (in_or_of.type() == TokenType::In)
         return create_ast_node<ForInStatement>(move(lhs), move(rhs), move(body));

+ 2 - 0
Libraries/LibJS/Parser.h

@@ -159,6 +159,8 @@ private:
         bool m_allow_super_property_lookup { false };
         bool m_allow_super_constructor_call { false };
         bool m_in_function_context { false };
+        bool m_in_break_context { false };
+        bool m_in_continue_context { false };
 
         explicit ParserState(Lexer);
     };

+ 18 - 0
Libraries/LibJS/Tests/break-continue-syntax-errors.js

@@ -0,0 +1,18 @@
+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("label: { break label }").toEval();
+});
+
+test("'continue' syntax errors", () => {
+    expect("continue").not.toEval();
+    expect("continue label").not.toEval();
+    expect("{ continue }").not.toEval();
+    expect("{ continue label }").not.toEval();
+    expect("label: { continue label }").not.toEval();
+
+    expect("switch (true) { case true: continue; }").not.toEval();
+});