1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:58:12 +00:00

LibJS: Add parsing and evaluation of private fields and methods

This commit is contained in:
davidot 2021-10-12 22:45:52 +02:00 committed by Linus Groh
parent c7a6572789
commit 16cc82460f
9 changed files with 368 additions and 69 deletions

View file

@ -850,6 +850,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
NonnullRefPtrVector<ClassElement> elements;
RefPtr<Expression> super_class;
RefPtr<FunctionExpression> constructor;
HashTable<FlyString> found_private_names;
String class_name = expect_class_name || match_identifier() || match(TokenType::Yield) || match(TokenType::Await)
? consume_identifier_reference().value().to_string()
@ -881,6 +882,13 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
consume(TokenType::CurlyOpen);
HashTable<StringView> referenced_private_names;
HashTable<StringView>* outer_referenced_private_names = m_state.referenced_private_names;
m_state.referenced_private_names = &referenced_private_names;
ScopeGuard restore_private_name_table = [&] {
m_state.referenced_private_names = outer_referenced_private_names;
};
while (!done() && !match(TokenType::CurlyClose)) {
RefPtr<Expression> property_key;
bool is_static = false;
@ -899,7 +907,7 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
}
StringView name;
if (match_property_key()) {
if (match_property_key() || match(TokenType::PrivateIdentifier)) {
if (!is_generator && m_state.current_token.original_value() == "static"sv) {
if (match(TokenType::Identifier)) {
consume();
@ -923,12 +931,49 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
}
}
if (match_property_key()) {
if (match_property_key() || match(TokenType::PrivateIdentifier)) {
switch (m_state.current_token.type()) {
case TokenType::Identifier:
name = consume().value();
property_key = create_ast_node<StringLiteral>({ m_state.current_token.filename(), rule_start.position(), position() }, name);
break;
case TokenType::PrivateIdentifier:
name = consume().value();
if (name == "#constructor")
syntax_error("Private property with name '#constructor' is not allowed");
if (method_kind != ClassMethod::Kind::Method) {
// It is a Syntax Error if PrivateBoundIdentifiers of ClassElementList contains any duplicate entries,
// unless the name is used once for a getter and once for a setter and in no other entries,
// and the getter and setter are either both static or both non-static.
for (auto& element : elements) {
auto private_name = element.private_bound_identifier();
if (!private_name.has_value() || private_name.value() != name)
continue;
if (element.class_element_kind() != ClassElement::ElementKind::Method
|| element.is_static() != is_static) {
syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
break;
}
VERIFY(is<ClassMethod>(element));
auto& class_method_element = static_cast<ClassMethod const&>(element);
if (class_method_element.kind() == ClassMethod::Kind::Method || class_method_element.kind() == method_kind) {
syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
break;
}
}
found_private_names.set(name);
} else if (found_private_names.set(name) != AK::HashSetResult::InsertedNewEntry) {
syntax_error(String::formatted("Duplicate private field or method named '{}'", name));
}
property_key = create_ast_node<PrivateIdentifier>({ m_state.current_token.filename(), rule_start.position(), position() }, name);
break;
case TokenType::StringLiteral: {
auto string_literal = parse_string_literal(consume());
name = string_literal->value();
@ -1064,6 +1109,16 @@ NonnullRefPtr<ClassExpression> Parser::parse_class_expression(bool expect_class_
}
}
// We could be in a subclass defined within the main class so must move all non declared private names to outer.
for (auto& private_name : referenced_private_names) {
if (found_private_names.contains(private_name))
continue;
if (outer_referenced_private_names)
outer_referenced_private_names->set(private_name);
else // FIXME: Make these error appear in the appropriate places.
syntax_error(String::formatted("Reference to undeclared private field or method '{}'", private_name));
}
return create_ast_node<ClassExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(class_name), move(constructor), move(super_class), move(elements));
}
@ -1264,6 +1319,11 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
if (is<Identifier>(*rhs) && m_state.strict_mode) {
syntax_error("Delete of an unqualified identifier in strict mode.", rhs_start);
}
if (is<MemberExpression>(*rhs)) {
auto& member_expression = static_cast<MemberExpression const&>(*rhs);
if (member_expression.ends_in_private_name())
syntax_error("Private fields cannot be deleted");
}
return create_ast_node<UnaryExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, UnaryOp::Delete, move(rhs));
}
default:
@ -1711,8 +1771,17 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
return parse_assignment_expression(AssignmentOp::Assignment, move(lhs), min_precedence, associativity);
case TokenType::Period:
consume();
if (!match_identifier_name())
if (match(TokenType::PrivateIdentifier)) {
if (!is_private_identifier_valid())
syntax_error(String::formatted("Reference to undeclared private field or method '{}'", m_state.current_token.value()));
else if (is<SuperExpression>(*lhs))
syntax_error(String::formatted("Cannot access private field or method '{}' on super", m_state.current_token.value()));
return create_ast_node<MemberExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), create_ast_node<PrivateIdentifier>({ m_state.current_token.filename(), rule_start.position(), position() }, consume().value()));
} else if (!match_identifier_name()) {
expected("IdentifierName");
}
return create_ast_node<MemberExpression>({ m_state.current_token.filename(), rule_start.position(), position() }, move(lhs), create_ast_node<Identifier>({ m_state.current_token.filename(), rule_start.position(), position() }, consume().value()));
case TokenType::BracketOpen: {
consume(TokenType::BracketOpen);
@ -1780,6 +1849,17 @@ NonnullRefPtr<Expression> Parser::parse_secondary_expression(NonnullRefPtr<Expre
}
}
bool Parser::is_private_identifier_valid() const
{
VERIFY(match(TokenType::PrivateIdentifier));
if (!m_state.referenced_private_names)
return false;
// We might not have hit the declaration yet so class will check this in the end
m_state.referenced_private_names->set(m_state.current_token.value());
return true;
}
RefPtr<BindingPattern> Parser::synthesize_binding_pattern(Expression const& expression)
{
VERIFY(is<ArrayExpression>(expression) || is<ObjectExpression>(expression));
@ -3042,6 +3122,7 @@ bool Parser::match_expression() const
|| type == TokenType::TemplateLiteralStart
|| type == TokenType::NullLiteral
|| match_identifier()
|| (type == TokenType::PrivateIdentifier && next_token().type() == TokenType::In)
|| type == TokenType::Await
|| type == TokenType::New
|| type == TokenType::Class