summaryrefslogtreecommitdiff
path: root/Libraries/LibJS
diff options
context:
space:
mode:
authorMatthew Olsson <matthewcolsson@gmail.com>2020-10-07 12:21:15 -0700
committerAndreas Kling <kling@serenityos.org>2020-10-08 10:20:49 +0200
commite49ea1b5209f3b76c9caae317554a6a4a6188d5f (patch)
treed21e3b1cee9189eabfc46530ba87a1bda2c8579d /Libraries/LibJS
parentb85af075b7d654b309b98905e3693483264186c6 (diff)
downloadserenity-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.cpp29
-rw-r--r--Libraries/LibJS/Parser.h2
-rw-r--r--Libraries/LibJS/Tests/break-continue-syntax-errors.js18
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();
+});