mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 19:07:35 +00:00
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.
This commit is contained in:
parent
b85af075b7
commit
e49ea1b520
3 changed files with 44 additions and 5 deletions
|
@ -1394,16 +1394,23 @@ NonnullRefPtr<BreakStatement> Parser::parse_break_statement()
|
||||||
FlyString target_label;
|
FlyString target_label;
|
||||||
if (match(TokenType::Semicolon)) {
|
if (match(TokenType::Semicolon)) {
|
||||||
consume();
|
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();
|
if (target_label.is_null() && !m_parser_state.m_in_break_context)
|
||||||
consume_or_insert_semicolon();
|
syntax_error("Unlabeled 'break' not allowed outside of a loop or switch statement");
|
||||||
|
|
||||||
return create_ast_node<BreakStatement>(target_label);
|
return create_ast_node<BreakStatement>(target_label);
|
||||||
}
|
}
|
||||||
|
|
||||||
NonnullRefPtr<ContinueStatement> Parser::parse_continue_statement()
|
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);
|
consume(TokenType::Continue);
|
||||||
FlyString target_label;
|
FlyString target_label;
|
||||||
if (match(TokenType::Semicolon)) {
|
if (match(TokenType::Semicolon)) {
|
||||||
|
@ -1448,7 +1455,11 @@ NonnullRefPtr<DoWhileStatement> Parser::parse_do_while_statement()
|
||||||
{
|
{
|
||||||
consume(TokenType::Do);
|
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::While);
|
||||||
consume(TokenType::ParenOpen);
|
consume(TokenType::ParenOpen);
|
||||||
|
@ -1470,6 +1481,8 @@ NonnullRefPtr<WhileStatement> Parser::parse_while_statement()
|
||||||
|
|
||||||
consume(TokenType::ParenClose);
|
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();
|
auto body = parse_statement();
|
||||||
|
|
||||||
return create_ast_node<WhileStatement>(move(test), move(body));
|
return create_ast_node<WhileStatement>(move(test), move(body));
|
||||||
|
@ -1506,6 +1519,7 @@ NonnullRefPtr<SwitchCase> Parser::parse_switch_case()
|
||||||
consume(TokenType::Colon);
|
consume(TokenType::Colon);
|
||||||
|
|
||||||
NonnullRefPtrVector<Statement> consequent;
|
NonnullRefPtrVector<Statement> consequent;
|
||||||
|
TemporaryChange break_change(m_parser_state.m_in_break_context, true);
|
||||||
while (match_statement())
|
while (match_statement())
|
||||||
consequent.append(parse_statement());
|
consequent.append(parse_statement());
|
||||||
|
|
||||||
|
@ -1585,6 +1599,8 @@ NonnullRefPtr<Statement> Parser::parse_for_statement()
|
||||||
|
|
||||||
consume(TokenType::ParenClose);
|
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();
|
auto body = parse_statement();
|
||||||
|
|
||||||
if (in_scope) {
|
if (in_scope) {
|
||||||
|
@ -1610,6 +1626,9 @@ NonnullRefPtr<Statement> Parser::parse_for_in_of_statement(NonnullRefPtr<ASTNode
|
||||||
auto in_or_of = consume();
|
auto in_or_of = consume();
|
||||||
auto rhs = parse_expression(0);
|
auto rhs = parse_expression(0);
|
||||||
consume(TokenType::ParenClose);
|
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();
|
auto body = parse_statement();
|
||||||
if (in_or_of.type() == TokenType::In)
|
if (in_or_of.type() == TokenType::In)
|
||||||
return create_ast_node<ForInStatement>(move(lhs), move(rhs), move(body));
|
return create_ast_node<ForInStatement>(move(lhs), move(rhs), move(body));
|
||||||
|
|
|
@ -159,6 +159,8 @@ private:
|
||||||
bool m_allow_super_property_lookup { false };
|
bool m_allow_super_property_lookup { false };
|
||||||
bool m_allow_super_constructor_call { false };
|
bool m_allow_super_constructor_call { false };
|
||||||
bool m_in_function_context { false };
|
bool m_in_function_context { false };
|
||||||
|
bool m_in_break_context { false };
|
||||||
|
bool m_in_continue_context { false };
|
||||||
|
|
||||||
explicit ParserState(Lexer);
|
explicit ParserState(Lexer);
|
||||||
};
|
};
|
||||||
|
|
18
Libraries/LibJS/Tests/break-continue-syntax-errors.js
Normal file
18
Libraries/LibJS/Tests/break-continue-syntax-errors.js
Normal file
|
@ -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();
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue