From f55d7bf6f339af701d72204331fd0661a8bcd986 Mon Sep 17 00:00:00 2001 From: Anonymous Date: Tue, 15 Feb 2022 02:11:56 -0800 Subject: [PATCH] LibJS: Fix cases where we incorrectly allowed 'in' in for loops We needed to propagate the forbidden token set to all parsing functions that can call back into parse_expression. --- Userland/Libraries/LibJS/Parser.cpp | 94 +++++++++---------- Userland/Libraries/LibJS/Parser.h | 6 +- .../LibJS/Tests/syntax/for-loop-invalid-in.js | 19 ++++ 3 files changed, 69 insertions(+), 50 deletions(-) create mode 100644 Userland/Libraries/LibJS/Tests/syntax/for-loop-invalid-in.js diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 04daecf795..3d8002308f 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -1922,7 +1922,7 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati check_for_invalid_object_property(expression); Associativity new_associativity = operator_associativity(m_state.current_token.type()); - expression = parse_secondary_expression(move(expression), new_precedence, new_associativity); + expression = parse_secondary_expression(move(expression), new_precedence, new_associativity, forbidden); while (match(TokenType::TemplateLiteralStart) && !is(*expression)) { auto template_literal = parse_template_literal(true); expression = create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(expression), move(template_literal)); @@ -1965,104 +1965,104 @@ NonnullRefPtr Parser::parse_expression(int min_precedence, Associati return expression; } -NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr lhs, int min_precedence, Associativity associativity) +NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr lhs, int min_precedence, Associativity associativity, Vector const& forbidden) { auto rule_start = push_start(); switch (m_state.current_token.type()) { case TokenType::Plus: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Addition, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Addition, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::PlusEquals: - return parse_assignment_expression(AssignmentOp::AdditionAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::AdditionAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::Minus: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Subtraction, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Subtraction, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::MinusEquals: - return parse_assignment_expression(AssignmentOp::SubtractionAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::SubtractionAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::Asterisk: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Multiplication, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Multiplication, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::AsteriskEquals: - return parse_assignment_expression(AssignmentOp::MultiplicationAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::MultiplicationAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::Slash: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Division, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Division, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::SlashEquals: - return parse_assignment_expression(AssignmentOp::DivisionAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::DivisionAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::Percent: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Modulo, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Modulo, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::PercentEquals: - return parse_assignment_expression(AssignmentOp::ModuloAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::ModuloAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::DoubleAsterisk: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Exponentiation, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::Exponentiation, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::DoubleAsteriskEquals: - return parse_assignment_expression(AssignmentOp::ExponentiationAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::ExponentiationAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::GreaterThan: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::GreaterThan, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::GreaterThan, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::GreaterThanEquals: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::GreaterThanEquals, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::GreaterThanEquals, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::LessThan: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LessThan, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LessThan, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::LessThanEquals: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LessThanEquals, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LessThanEquals, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::EqualsEqualsEquals: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::StrictlyEquals, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::StrictlyEquals, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::ExclamationMarkEqualsEquals: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::StrictlyInequals, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::StrictlyInequals, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::EqualsEquals: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LooselyEquals, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LooselyEquals, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::ExclamationMarkEquals: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LooselyInequals, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LooselyInequals, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::In: consume(); return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::In, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::Instanceof: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::InstanceOf, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::InstanceOf, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::Ampersand: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::BitwiseAnd, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::BitwiseAnd, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::AmpersandEquals: - return parse_assignment_expression(AssignmentOp::BitwiseAndAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::BitwiseAndAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::Pipe: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::BitwiseOr, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::BitwiseOr, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::PipeEquals: - return parse_assignment_expression(AssignmentOp::BitwiseOrAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::BitwiseOrAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::Caret: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::BitwiseXor, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::BitwiseXor, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::CaretEquals: - return parse_assignment_expression(AssignmentOp::BitwiseXorAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::BitwiseXorAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::ShiftLeft: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LeftShift, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::LeftShift, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::ShiftLeftEquals: - return parse_assignment_expression(AssignmentOp::LeftShiftAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::LeftShiftAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::ShiftRight: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::RightShift, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::RightShift, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::ShiftRightEquals: - return parse_assignment_expression(AssignmentOp::RightShiftAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::RightShiftAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::UnsignedShiftRight: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::UnsignedRightShift, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, BinaryOp::UnsignedRightShift, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::UnsignedShiftRightEquals: - return parse_assignment_expression(AssignmentOp::UnsignedRightShiftAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::UnsignedRightShiftAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::ParenOpen: return parse_call_expression(move(lhs)); case TokenType::Equals: - return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::Period: consume(); if (match(TokenType::PrivateIdentifier)) { @@ -2112,21 +2112,21 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr({ m_state.current_token.filename(), rule_start.position(), position() }, UpdateOp::Decrement, move(lhs)); case TokenType::DoubleAmpersand: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, LogicalOp::And, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, LogicalOp::And, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::DoubleAmpersandEquals: - return parse_assignment_expression(AssignmentOp::AndAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::AndAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::DoublePipe: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, LogicalOp::Or, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, LogicalOp::Or, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::DoublePipeEquals: - return parse_assignment_expression(AssignmentOp::OrAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::OrAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::DoubleQuestionMark: consume(); - return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, LogicalOp::NullishCoalescing, move(lhs), parse_expression(min_precedence, associativity)); + return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, LogicalOp::NullishCoalescing, move(lhs), parse_expression(min_precedence, associativity, forbidden)); case TokenType::DoubleQuestionMarkEquals: - return parse_assignment_expression(AssignmentOp::NullishAssignment, move(lhs), min_precedence, associativity); + return parse_assignment_expression(AssignmentOp::NullishAssignment, move(lhs), min_precedence, associativity, forbidden); case TokenType::QuestionMark: - return parse_conditional_expression(move(lhs)); + return parse_conditional_expression(move(lhs), forbidden); case TokenType::QuestionMarkPeriod: // FIXME: This should allow `(new Foo)?.bar', but as our parser strips parenthesis, // we can't really tell if `lhs' was parenthesized at this point. @@ -2192,7 +2192,7 @@ RefPtr Parser::synthesize_binding_pattern(Expression const& expr return result; } -NonnullRefPtr Parser::parse_assignment_expression(AssignmentOp assignment_op, NonnullRefPtr lhs, int min_precedence, Associativity associativity) +NonnullRefPtr Parser::parse_assignment_expression(AssignmentOp assignment_op, NonnullRefPtr lhs, int min_precedence, Associativity associativity, Vector const& forbidden) { auto rule_start = push_start(); VERIFY(match(TokenType::Equals) @@ -2234,7 +2234,7 @@ NonnullRefPtr Parser::parse_assignment_expression(Assignme } else if (m_state.strict_mode && is(*lhs)) { syntax_error("Cannot assign to function call"); } - auto rhs = parse_expression(min_precedence, associativity); + auto rhs = parse_expression(min_precedence, associativity, forbidden); return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, assignment_op, move(lhs), move(rhs)); } @@ -3026,13 +3026,13 @@ NonnullRefPtr Parser::parse_continue_statement() return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, target_label); } -NonnullRefPtr Parser::parse_conditional_expression(NonnullRefPtr test) +NonnullRefPtr Parser::parse_conditional_expression(NonnullRefPtr test, Vector const& forbidden) { auto rule_start = push_start(); consume(TokenType::QuestionMark); auto consequent = parse_expression(2); consume(TokenType::Colon); - auto alternate = parse_expression(2); + auto alternate = parse_expression(2, Associativity::Right, forbidden); return create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, move(test), move(consequent), move(alternate)); } diff --git a/Userland/Libraries/LibJS/Parser.h b/Userland/Libraries/LibJS/Parser.h index 546ece8518..f4641c79d1 100644 --- a/Userland/Libraries/LibJS/Parser.h +++ b/Userland/Libraries/LibJS/Parser.h @@ -97,7 +97,7 @@ public: NonnullRefPtr parse_while_statement(); NonnullRefPtr parse_with_statement(); NonnullRefPtr parse_debugger_statement(); - NonnullRefPtr parse_conditional_expression(NonnullRefPtr test); + NonnullRefPtr parse_conditional_expression(NonnullRefPtr test, Vector const&); NonnullRefPtr parse_optional_chain(NonnullRefPtr base); NonnullRefPtr parse_expression(int min_precedence, Associativity associate = Associativity::Right, const Vector& forbidden = {}); PrimaryExpressionParseResult parse_primary_expression(); @@ -107,7 +107,7 @@ public: NonnullRefPtr parse_array_expression(); NonnullRefPtr parse_string_literal(const Token& token, bool in_template_literal = false); NonnullRefPtr parse_template_literal(bool is_tagged); - NonnullRefPtr parse_secondary_expression(NonnullRefPtr, int min_precedence, Associativity associate = Associativity::Right); + NonnullRefPtr parse_secondary_expression(NonnullRefPtr, int min_precedence, Associativity associate = Associativity::Right, Vector const& forbidden = {}); NonnullRefPtr parse_call_expression(NonnullRefPtr); NonnullRefPtr parse_new_expression(); NonnullRefPtr parse_class_declaration(); @@ -115,7 +115,7 @@ public: NonnullRefPtr parse_yield_expression(); NonnullRefPtr parse_await_expression(); NonnullRefPtr parse_property_key(); - NonnullRefPtr parse_assignment_expression(AssignmentOp, NonnullRefPtr lhs, int min_precedence, Associativity); + NonnullRefPtr parse_assignment_expression(AssignmentOp, NonnullRefPtr lhs, int min_precedence, Associativity, Vector const& forbidden = {}); NonnullRefPtr parse_identifier(); NonnullRefPtr parse_import_statement(Program& program); NonnullRefPtr parse_export_statement(Program& program); diff --git a/Userland/Libraries/LibJS/Tests/syntax/for-loop-invalid-in.js b/Userland/Libraries/LibJS/Tests/syntax/for-loop-invalid-in.js new file mode 100644 index 0000000000..a62b61920a --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/syntax/for-loop-invalid-in.js @@ -0,0 +1,19 @@ +test("The use of the 'in' keyword is restricted in for loop headers", () => { + expect("for (a ? a : a in a; false; ) ;").not.toEval(); + expect("for (a + a in b);").not.toEval(); + expect("for (a in a of a);").not.toEval(); + expect("for(a = a in a; false;);").not.toEval(); + expect("for(a += a in a; false;);").not.toEval(); + expect("for(a -= a in a; false;);").not.toEval(); + expect("for(a *= a in a; false;);").not.toEval(); + expect("for(a /= a in a; false;);").not.toEval(); + expect("for(a &= a in a; false;);").not.toEval(); + expect("for(a |= a in a; false;);").not.toEval(); + expect("for(a ^= a in a; false;);").not.toEval(); + expect("for(a ^^= a in a; false;);").not.toEval(); + expect("for(a == a in a; false;);").not.toEval(); + expect("for(a != a in a; false;);").not.toEval(); + expect("for(a === a in a; false;);").not.toEval(); + expect("for(a !== a in a; false;);").not.toEval(); + expect("for (a ?? b in c;;);").not.toEval(); +});