From 283ee678f7ea33463412bd943896f238bb8db18b Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sun, 4 Oct 2020 23:58:57 +0100 Subject: [PATCH] LibJS: Validate all assignment expressions, not just "=" The check for invalid lhs and assignment to eval/arguments in strict mode should happen for all kinds of assignment expressions, not just AssignmentOp::Assignment. --- Libraries/LibJS/Parser.cpp | 78 +++++++++---------- Libraries/LibJS/Parser.h | 1 + .../LibJS/Tests/invalid-lhs-in-assignment.js | 16 ++++ 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 834a42b8bf..273fef5ea2 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -974,38 +974,32 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr(BinaryOp::Addition, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::PlusEquals: - consume(); - return create_ast_node(AssignmentOp::AdditionAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::AdditionAssignment, move(lhs), min_precedence, associativity); case TokenType::Minus: consume(); return create_ast_node(BinaryOp::Subtraction, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::MinusEquals: - consume(); - return create_ast_node(AssignmentOp::SubtractionAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::SubtractionAssignment, move(lhs), min_precedence, associativity); case TokenType::Asterisk: consume(); return create_ast_node(BinaryOp::Multiplication, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::AsteriskEquals: - consume(); - return create_ast_node(AssignmentOp::MultiplicationAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::MultiplicationAssignment, move(lhs), min_precedence, associativity); case TokenType::Slash: consume(); return create_ast_node(BinaryOp::Division, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::SlashEquals: - consume(); - return create_ast_node(AssignmentOp::DivisionAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::DivisionAssignment, move(lhs), min_precedence, associativity); case TokenType::Percent: consume(); return create_ast_node(BinaryOp::Modulo, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::PercentEquals: - consume(); - return create_ast_node(AssignmentOp::ModuloAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::ModuloAssignment, move(lhs), min_precedence, associativity); case TokenType::DoubleAsterisk: consume(); return create_ast_node(BinaryOp::Exponentiation, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::DoubleAsteriskEquals: - consume(); - return create_ast_node(AssignmentOp::ExponentiationAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::ExponentiationAssignment, move(lhs), min_precedence, associativity); case TokenType::GreaterThan: consume(); return create_ast_node(BinaryOp::GreaterThan, move(lhs), parse_expression(min_precedence, associativity)); @@ -1040,56 +1034,36 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr(BinaryOp::BitwiseAnd, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::AmpersandEquals: - consume(); - return create_ast_node(AssignmentOp::BitwiseAndAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::BitwiseAndAssignment, move(lhs), min_precedence, associativity); case TokenType::Pipe: consume(); return create_ast_node(BinaryOp::BitwiseOr, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::PipeEquals: - consume(); - return create_ast_node(AssignmentOp::BitwiseOrAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::BitwiseOrAssignment, move(lhs), min_precedence, associativity); case TokenType::Caret: consume(); return create_ast_node(BinaryOp::BitwiseXor, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::CaretEquals: - consume(); - return create_ast_node(AssignmentOp::BitwiseXorAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::BitwiseXorAssignment, move(lhs), min_precedence, associativity); case TokenType::ShiftLeft: consume(); return create_ast_node(BinaryOp::LeftShift, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::ShiftLeftEquals: - consume(); - return create_ast_node(AssignmentOp::LeftShiftAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::LeftShiftAssignment, move(lhs), min_precedence, associativity); case TokenType::ShiftRight: consume(); return create_ast_node(BinaryOp::RightShift, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::ShiftRightEquals: - consume(); - return create_ast_node(AssignmentOp::RightShiftAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::RightShiftAssignment, move(lhs), min_precedence, associativity); case TokenType::UnsignedShiftRight: consume(); return create_ast_node(BinaryOp::UnsignedRightShift, move(lhs), parse_expression(min_precedence, associativity)); case TokenType::UnsignedShiftRightEquals: - consume(); - return create_ast_node(AssignmentOp::UnsignedRightShiftAssignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::UnsignedRightShiftAssignment, move(lhs), min_precedence, associativity); case TokenType::ParenOpen: return parse_call_expression(move(lhs)); case TokenType::Equals: - consume(); - if (!lhs->is_identifier() && !lhs->is_member_expression() && !lhs->is_call_expression()) { - syntax_error("Invalid left-hand side in assignment"); - return create_ast_node(); - } - if (m_parser_state.m_strict_mode && lhs->is_identifier()) { - auto name = static_cast(*lhs).string(); - if (name == "eval" || name == "arguments") { - syntax_error( - String::formatted("'{}' cannot be assigned to in strict mode code", name), - m_parser_state.m_current_token.line_number(), - m_parser_state.m_current_token.line_column()); - } - } - return create_ast_node(AssignmentOp::Assignment, move(lhs), parse_expression(min_precedence, associativity)); + return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity); case TokenType::Period: consume(); if (!match_identifier_name()) @@ -1133,6 +1107,32 @@ NonnullRefPtr Parser::parse_secondary_expression(NonnullRefPtr Parser::parse_assignment_expression(AssignmentOp assignment_op, NonnullRefPtr lhs, int min_precedence, Associativity associativity) +{ + ASSERT(match(TokenType::Equals) + || match(TokenType::PlusEquals) + || match(TokenType::MinusEquals) + || match(TokenType::AsteriskEquals) + || match(TokenType::SlashEquals) + || match(TokenType::PercentEquals) + || match(TokenType::DoubleAsteriskEquals) + || match(TokenType::AmpersandEquals) + || match(TokenType::PipeEquals) + || match(TokenType::CaretEquals) + || match(TokenType::ShiftLeftEquals) + || match(TokenType::ShiftRightEquals) + || match(TokenType::UnsignedShiftRightEquals)); + consume(); + if (!lhs->is_identifier() && !lhs->is_member_expression() && !lhs->is_call_expression()) { + syntax_error("Invalid left-hand side in assignment"); + } else if (m_parser_state.m_strict_mode && lhs->is_identifier()) { + auto name = static_cast(*lhs).string(); + if (name == "eval" || name == "arguments") + syntax_error(String::formatted("'{}' cannot be assigned to in strict mode code", name)); + } + return create_ast_node(assignment_op, move(lhs), parse_expression(min_precedence, associativity)); +} + NonnullRefPtr Parser::parse_call_expression(NonnullRefPtr lhs) { if (!m_parser_state.m_allow_super_constructor_call && lhs->is_super_expression()) diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index d5e113e677..c89af8aba8 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -84,6 +84,7 @@ public: NonnullRefPtr parse_class_declaration(); NonnullRefPtr parse_class_expression(bool expect_class_name); NonnullRefPtr parse_property_key(); + NonnullRefPtr parse_assignment_expression(AssignmentOp, NonnullRefPtr lhs, int min_precedence, Associativity); struct Error { String message; diff --git a/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js b/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js index 7abad0f08d..7018a94b78 100644 --- a/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js +++ b/Libraries/LibJS/Tests/invalid-lhs-in-assignment.js @@ -10,3 +10,19 @@ test("assignment to inline function call", () => { (function () {})() = "foo"; }).toThrowWithMessage(ReferenceError, "Invalid left-hand side in assignment"); }); + +test("assignment to invalid LHS is syntax error", () => { + expect("1 += 1").not.toEval(); + expect("1 -= 1").not.toEval(); + expect("1 *= 1").not.toEval(); + expect("1 /= 1").not.toEval(); + expect("1 %= 1").not.toEval(); + expect("1 **= 1").not.toEval(); + expect("1 &= 1").not.toEval(); + expect("1 |= 1").not.toEval(); + expect("1 ^= 1").not.toEval(); + expect("1 <<= 1").not.toEval(); + expect("1 >>= 1").not.toEval(); + expect("1 >>>= 1").not.toEval(); + expect("1 = 1").not.toEval(); +});