diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2020-10-07 12:21:15 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-10-08 10:20:49 +0200 |
commit | e49ea1b5209f3b76c9caae317554a6a4a6188d5f (patch) | |
tree | d21e3b1cee9189eabfc46530ba87a1bda2c8579d /Libraries/LibJS | |
parent | b85af075b7d654b309b98905e3693483264186c6 (diff) | |
download | serenity-e49ea1b5209f3b76c9caae317554a6a4a6188d5f.zip |
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.
Diffstat (limited to 'Libraries/LibJS')
-rw-r--r-- | Libraries/LibJS/Parser.cpp | 29 | ||||
-rw-r--r-- | Libraries/LibJS/Parser.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/break-continue-syntax-errors.js | 18 |
3 files changed, 44 insertions, 5 deletions
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 8a10aeadae..069d1c6d35 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/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)); diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 882747f439..21a63e1acc 100644 --- a/Libraries/LibJS/Parser.h +++ b/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); }; diff --git a/Libraries/LibJS/Tests/break-continue-syntax-errors.js b/Libraries/LibJS/Tests/break-continue-syntax-errors.js new file mode 100644 index 0000000000..36f970b848 --- /dev/null +++ b/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(); +}); |