1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 09:34:59 +00:00

LibJS: Allow CallExpressions as lhs of assignments in most cases

Although not quite like the spec says the web reality is that a lhs
target of CallExpression should not give a SyntaxError but only a
ReferenceError once executed.
This commit is contained in:
davidot 2022-11-30 01:47:25 +01:00 committed by Andreas Kling
parent 8319d7ac06
commit d218a68296
2 changed files with 41 additions and 16 deletions

View file

@ -1559,6 +1559,11 @@ NonnullRefPtr<RegExpLiteral> Parser::parse_regexp_literal()
return create_ast_node<RegExpLiteral>(move(range), move(parsed_regex), move(parsed_pattern), move(parsed_flags), pattern.to_string(), move(flags));
}
static bool is_simple_assignment_target(Expression const& expression, bool allow_web_reality_call_expression = true)
{
return is<Identifier>(expression) || is<MemberExpression>(expression) || (allow_web_reality_call_expression && is<CallExpression>(expression));
}
NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
{
auto rule_start = push_start();
@ -1569,9 +1574,7 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
consume();
auto rhs_start = position();
auto rhs = parse_expression(precedence, associativity);
// FIXME: Apparently for functions this should also not be enforced on a parser level,
// other engines throw ReferenceError for ++foo()
if (!is<Identifier>(*rhs) && !is<MemberExpression>(*rhs))
if (!is_simple_assignment_target(*rhs))
syntax_error(String::formatted("Right-hand side of prefix increment operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
if (m_state.strict_mode && is<Identifier>(*rhs)) {
@ -1586,9 +1589,7 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
consume();
auto rhs_start = position();
auto rhs = parse_expression(precedence, associativity);
// FIXME: Apparently for functions this should also not be enforced on a parser level,
// other engines throw ReferenceError for --foo()
if (!is<Identifier>(*rhs) && !is<MemberExpression>(*rhs))
if (!is_simple_assignment_target(*rhs))
syntax_error(String::formatted("Right-hand side of prefix decrement operator must be identifier or member expression, got {}", rhs->class_name()), rhs_start);
if (m_state.strict_mode && is<Identifier>(*rhs)) {
@ -2145,9 +2146,7 @@ Parser::ExpressionResult Parser::parse_secondary_expression(NonnullRefPtr<Expres
return expression;
}
case TokenType::PlusPlus:
// FIXME: Apparently for functions this should also not be enforced on a parser level,
// other engines throw ReferenceError for foo()++
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs))
if (!is_simple_assignment_target(*lhs))
syntax_error(String::formatted("Left-hand side of postfix increment operator must be identifier or member expression, got {}", lhs->class_name()));
if (m_state.strict_mode && is<Identifier>(*lhs)) {
@ -2159,9 +2158,7 @@ Parser::ExpressionResult Parser::parse_secondary_expression(NonnullRefPtr<Expres
consume();
return create_ast_node<UpdateExpression>({ m_source_code, rule_start.position(), position() }, UpdateOp::Increment, move(lhs));
case TokenType::MinusMinus:
// FIXME: Apparently for functions this should also not be enforced on a parser level,
// other engines throw ReferenceError for foo()--
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs))
if (!is_simple_assignment_target(*lhs))
syntax_error(String::formatted("Left-hand side of postfix increment operator must be identifier or member expression, got {}", lhs->class_name()));
if (m_state.strict_mode && is<Identifier>(*lhs)) {
@ -2293,13 +2290,18 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
}
}
}
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*lhs) && !is<CallExpression>(*lhs)) {
// Note: The web reality is that all but &&=, ||= and ??= do allow left hand side CallExpresions.
// These are the exception as they are newer.
auto has_web_reality_assignment_target_exceptions = assignment_op != AssignmentOp::AndAssignment
&& assignment_op != AssignmentOp::OrAssignment
&& assignment_op != AssignmentOp::NullishAssignment;
if (!is_simple_assignment_target(*lhs, has_web_reality_assignment_target_exceptions)) {
syntax_error("Invalid left-hand side in assignment");
} else if (m_state.strict_mode && is<Identifier>(*lhs)) {
auto const& name = static_cast<Identifier const&>(*lhs).string();
check_identifier_name_for_assignment_validity(name);
} else if (m_state.strict_mode && is<CallExpression>(*lhs)) {
syntax_error("Cannot assign to function call");
}
auto rhs = parse_expression(min_precedence, associativity, forbidden);
return create_ast_node<AssignmentExpression>({ m_source_code, rule_start.position(), position() }, assignment_op, move(lhs), move(rhs));

View file

@ -6,7 +6,7 @@ test("assignment to function call", () => {
});
test("assignment to function call in strict mode", () => {
expect("'use strict'; foo() = 'foo'").not.toEval();
expect("'use strict'; foo() = 'foo'").toEval();
});
test("assignment to inline function call", () => {
@ -29,4 +29,27 @@ 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();
});
test("assignment to call LHS is only syntax error for new operators", () => {
expect("f() += 1").toEval();
expect("f() -= 1").toEval();
expect("f() *= 1").toEval();
expect("f() /= 1").toEval();
expect("f() %= 1").toEval();
expect("f() **= 1").toEval();
expect("f() &= 1").toEval();
expect("f() |= 1").toEval();
expect("f() ^= 1").toEval();
expect("f() <<= 1").toEval();
expect("f() >>= 1").toEval();
expect("f() >>>= 1").toEval();
expect("f() = 1").toEval();
expect("f() &&= 1").not.toEval();
expect("f() ||= 1").not.toEval();
expect("f() ??= 1").not.toEval();
});