1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 16:47:36 +00:00

LibJS: Be more strict about reserved and special identifiers

This commit is contained in:
davidot 2021-07-12 01:27:35 +02:00 committed by Andreas Kling
parent 2a61b90fef
commit 4485df1405
2 changed files with 57 additions and 11 deletions

View file

@ -405,6 +405,15 @@ NonnullRefPtr<Statement> Parser::parse_statement()
}
}
static constexpr AK::Array<StringView, 9> strict_reserved_words = { "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield" };
static bool is_strict_reserved_word(StringView str)
{
return any_of(strict_reserved_words.begin(), strict_reserved_words.end(), [&str](StringView const& word) {
return word == str;
});
}
RefPtr<FunctionExpression> Parser::try_parse_arrow_function_expression(bool expect_parens)
{
save_state();
@ -566,6 +575,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
? consume_identifier_reference().value().to_string()
: "";
check_identifier_name_for_assignment_validity(class_name, true);
if (match(TokenType::Extends)) {
consume();
auto [expression, should_continue_parsing] = parse_primary_expression();
@ -763,6 +773,9 @@ Parser::PrimaryExpressionParseResult Parser::parse_primary_expression()
set_try_parse_arrow_function_expression_failed_at_position(position(), true);
}
auto string = consume().value();
// This could be 'eval' or 'arguments' and thus needs a custom check (`eval[1] = true`)
if (m_state.strict_mode && (string == "let" || is_strict_reserved_word(string)))
syntax_error(String::formatted("Identifier must not be a reserved word in strict mode ('{}')", string));
return { create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, string) };
}
case TokenType::NumericLiteral:
@ -846,6 +859,13 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
// other engines throw ReferenceError for ++foo()
if (!is<Identifier>(*rhs) && !is<MemberExpression>(*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)) {
auto& identifier = static_cast<Identifier&>(*rhs);
auto& name = identifier.string();
check_identifier_name_for_assignment_validity(name);
}
return create_ast_node<UpdateExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UpdateOp::Increment, move(rhs), true);
}
case TokenType::MinusMinus: {
@ -856,6 +876,13 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
// other engines throw ReferenceError for --foo()
if (!is<Identifier>(*rhs) && !is<MemberExpression>(*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)) {
auto& identifier = static_cast<Identifier&>(*rhs);
auto& name = identifier.string();
check_identifier_name_for_assignment_validity(name);
}
return create_ast_node<UpdateExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UpdateOp::Decrement, move(rhs), true);
}
case TokenType::ExclamationMark:
@ -1285,6 +1312,13 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
// other engines throw ReferenceError for foo()++
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*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)) {
auto& identifier = static_cast<Identifier&>(*lhs);
auto& name = identifier.string();
check_identifier_name_for_assignment_validity(name);
}
consume();
return create_ast_node<UpdateExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UpdateOp::Increment, move(lhs));
case TokenType::MinusMinus:
@ -1292,6 +1326,12 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
// other engines throw ReferenceError for foo()--
if (!is<Identifier>(*lhs) && !is<MemberExpression>(*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)) {
auto& identifier = static_cast<Identifier&>(*lhs);
auto& name = identifier.string();
check_identifier_name_for_assignment_validity(name);
}
consume();
return create_ast_node<UpdateExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UpdateOp::Decrement, move(lhs));
case TokenType::DoubleAmpersand:
@ -1381,8 +1421,7 @@ NonnullRefPtr<AssignmentExpression> Parser::parse_assignment_expression(Assignme
syntax_error("Invalid left-hand side in assignment");
} else if (m_state.strict_mode && is<Identifier>(*lhs)) {
auto name = static_cast<const Identifier&>(*lhs).string();
if (name == "eval" || name == "arguments")
syntax_error(String::formatted("'{}' cannot be assigned to in strict mode code", name));
check_identifier_name_for_assignment_validity(name);
} else if (m_state.strict_mode && is<CallExpression>(*lhs)) {
syntax_error("Cannot assign to function call");
}
@ -1596,6 +1635,8 @@ NonnullRefPtr<FunctionNodeType> Parser::parse_function_node(u8 parse_options)
name = consume_identifier().value();
else if (is_function_expression && (match(TokenType::Yield) || match(TokenType::Await)))
name = consume().value();
check_identifier_name_for_assignment_validity(name);
}
consume(TokenType::ParenOpen);
i32 function_length = -1;
@ -1642,6 +1683,8 @@ Vector<FunctionNode::Parameter> Parser::parse_formal_parameters(int& function_le
auto token = consume_identifier();
auto parameter_name = token.value();
check_identifier_name_for_assignment_validity(parameter_name);
for (auto& parameter : parameters) {
if (auto* ptr = parameter.binding.get_pointer<FlyString>(); !ptr || parameter_name != *ptr)
continue;
@ -1683,7 +1726,7 @@ Vector<FunctionNode::Parameter> Parser::parse_formal_parameters(int& function_le
default_value = parse_expression(2);
bool is_generator = parse_options & FunctionNodeParseOptions::IsGeneratorFunction;
if (is_generator && default_value && default_value->fast_is<Identifier>() && static_cast<Identifier&>(*default_value).string() == "yield"sv)
if ((is_generator || m_state.strict_mode) && default_value && default_value->fast_is<Identifier>() && static_cast<Identifier&>(*default_value).string() == "yield"sv)
syntax_error("Generator function parameter initializer cannot contain a reference to an identifier named \"yield\"");
}
parameters.append({ move(parameter), default_value, is_rest });
@ -1854,6 +1897,7 @@ NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration(bool for_l
target = create_ast_node<Identifier>(
{ m_state.current_token.filename(), rule_start.position(), position() },
name);
check_identifier_name_for_assignment_validity(name);
if ((declaration_kind == DeclarationKind::Let || declaration_kind == DeclarationKind::Const) && name == "let"sv)
syntax_error("Lexical binding may not be called 'let'");
} else if (auto pattern = parse_binding_pattern()) {
@ -1866,6 +1910,9 @@ NonnullRefPtr<VariableDeclaration> Parser::parse_variable_declaration(bool for_l
});
}
} else if (!m_state.in_generator_function_context && match(TokenType::Yield)) {
if (m_state.strict_mode)
syntax_error("Identifier must not be a reserved word in strict mode ('yield')");
target = create_ast_node<Identifier>(
{ m_state.current_token.filename(), rule_start.position(), position() },
consume().value());
@ -2491,8 +2538,6 @@ void Parser::consume_or_insert_semicolon()
expected("Semicolon");
}
static constexpr AK::Array<StringView, 9> strict_reserved_words = { "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield" };
Token Parser::consume_identifier()
{
if (match(TokenType::Identifier))
@ -2546,8 +2591,8 @@ Token Parser::consume(TokenType expected_type)
}
auto token = consume();
if (expected_type == TokenType::Identifier) {
if (m_state.strict_mode && any_of(strict_reserved_words.begin(), strict_reserved_words.end(), [&](auto const& word) { return word == token.value(); }))
syntax_error("Identifier must not be a class-related reserved word in strict mode");
if (m_state.strict_mode && is_strict_reserved_word(token.value()))
syntax_error(String::formatted("Identifier must not be a reserved word in strict mode ('{}')", token.value()));
}
return token;
}
@ -2620,14 +2665,15 @@ void Parser::discard_saved_state()
m_saved_state.take_last();
}
void Parser::check_identifier_name_for_assignment_validity(StringView name)
void Parser::check_identifier_name_for_assignment_validity(StringView name, bool force_strict)
{
// FIXME: this is now called from multiple places maybe the error message should be dynamic?
if (any_of(s_reserved_words.begin(), s_reserved_words.end(), [&](auto& value) { return name == value; })) {
syntax_error("Binding pattern target may not be a reserved word");
} else if (m_state.strict_mode) {
} else if (m_state.strict_mode || force_strict) {
if (name.is_one_of("arguments"sv, "eval"sv))
syntax_error("Binding pattern target may not be called 'arguments' or 'eval' in strict mode");
else if (name == "yield"sv)
else if (is_strict_reserved_word(name))
syntax_error("Binding pattern target may not be called 'yield' in strict mode");
}
}