From 4c8090a45d42c21a5d68f0d7fe25a7bf562c0674 Mon Sep 17 00:00:00 2001 From: davidot Date: Wed, 13 Oct 2021 19:59:38 +0200 Subject: LibJS: Use ClassFieldInitializerStatement for class fields This is necessary as we might have to perform named evaluation with the field name. Ideally we would also skip some setup parts of the function like function_declaration_instantiation however this would require bigger changes to ECMAScriptFunctionObject. --- Userland/Libraries/LibJS/AST.cpp | 34 ++++++++++++++++++++-- Userland/Libraries/LibJS/AST.h | 4 ++- Userland/Libraries/LibJS/Parser.cpp | 11 ++++++- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 4 ++- 4 files changed, 48 insertions(+), 5 deletions(-) (limited to 'Userland/Libraries/LibJS') diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 471eb4cee0..009065051c 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -1278,6 +1278,35 @@ ThrowCompletionOr ClassMethod::class_element_evaluatio } } +// We use this class to mimic Initializer : = AssignmentExpression of +// 10.2.1.3 Runtime Semantics: EvaluateBody, https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody +class ClassFieldInitializerStatement : public Statement { +public: + ClassFieldInitializerStatement(SourceRange source_range, NonnullRefPtr expression, FlyString field_name) + : Statement(source_range) + , m_expression(move(expression)) + , m_class_field_identifier_name(move(field_name)) + { + } + + Value execute(Interpreter& interpreter, GlobalObject& global_object) const override + { + VERIFY(interpreter.vm().argument_count() == 0); + VERIFY(!m_class_field_identifier_name.is_empty()); + return TRY_OR_DISCARD(interpreter.vm().named_evaluation_if_anonymous_function(global_object, m_expression, m_class_field_identifier_name)); + } + + void dump(int) const override + { + // This should not be dumped as it is never part of an actual AST. + VERIFY_NOT_REACHED(); + } + +private: + NonnullRefPtr m_expression; + FlyString m_class_field_identifier_name; // [[ClassFieldIdentifierName]] +}; + // 15.7.10 Runtime Semantics: ClassFieldDefinitionEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-classfielddefinitionevaluation ThrowCompletionOr ClassField::class_element_evaluation(Interpreter& interpreter, GlobalObject& global_object, Object& target) const { @@ -1285,7 +1314,6 @@ ThrowCompletionOr ClassField::class_element_evaluation ECMAScriptFunctionObject* initializer = nullptr; if (m_initializer) { auto copy_initializer = m_initializer; - auto body = create_ast_node(m_initializer->source_range(), copy_initializer.release_nonnull()); auto name = property_key.visit( [&](PropertyName const& property_name) -> String { return property_name.is_number() ? property_name.to_string() : property_name.to_string_or_symbol().to_display_string(); @@ -1293,8 +1321,10 @@ ThrowCompletionOr ClassField::class_element_evaluation [&](PrivateName const& private_name) -> String { return private_name.description; }); + // FIXME: A potential optimization is not creating the functions here since these are never directly accessible. - initializer = ECMAScriptFunctionObject::create(interpreter.global_object(), name, *body, {}, 0, interpreter.lexical_environment(), interpreter.vm().running_execution_context().private_environment, FunctionKind::Regular, false, false); + auto function_code = create_ast_node(m_initializer->source_range(), copy_initializer.release_nonnull(), name); + initializer = ECMAScriptFunctionObject::create(interpreter.global_object(), String::empty(), *function_code, {}, 0, interpreter.lexical_environment(), interpreter.vm().running_execution_context().private_environment, FunctionKind::Regular, true, false, m_contains_direct_call_to_eval, false); initializer->set_home_object(&target); } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 64f4773f32..88ef640da4 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -1085,10 +1085,11 @@ private: class ClassField final : public ClassElement { public: - ClassField(SourceRange source_range, NonnullRefPtr key, RefPtr init, bool is_static) + ClassField(SourceRange source_range, NonnullRefPtr key, RefPtr init, bool contains_direct_call_to_eval, bool is_static) : ClassElement(source_range, is_static) , m_key(move(key)) , m_initializer(move(init)) + , m_contains_direct_call_to_eval(contains_direct_call_to_eval) { } @@ -1105,6 +1106,7 @@ public: private: NonnullRefPtr m_key; RefPtr m_initializer; + bool m_contains_direct_call_to_eval { false }; }; class StaticInitializer final : public ClassElement { diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 0bd4ca0698..787fc3ff90 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -102,6 +102,11 @@ public: return ScopePusher(parser, &node, true); } + static ScopePusher class_field_scope(Parser& parser) + { + return ScopePusher(parser, nullptr, false); + } + void add_declaration(NonnullRefPtr declaration) { if (declaration->is_lexical_declaration()) { @@ -1071,16 +1076,20 @@ NonnullRefPtr Parser::parse_class_expression(bool expect_class_ syntax_error("Class cannot have field named 'constructor'"); RefPtr initializer; + bool contains_direct_call_to_eval = false; if (match(TokenType::Equals)) { consume(); TemporaryChange super_property_access_rollback(m_state.allow_super_property_lookup, true); TemporaryChange field_initializer_rollback(m_state.in_class_field_initializer, true); + + auto class_field_scope = ScopePusher::class_field_scope(*this); initializer = parse_expression(2); + contains_direct_call_to_eval = class_field_scope.contains_direct_call_to_eval(); } - elements.append(create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, property_key.release_nonnull(), move(initializer), is_static)); + elements.append(create_ast_node({ m_state.current_token.filename(), rule_start.position(), position() }, property_key.release_nonnull(), move(initializer), contains_direct_call_to_eval, is_static)); consume_or_insert_semicolon(); } } diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index 18dccadda1..ac313c205e 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -78,13 +78,15 @@ public: protected: virtual bool is_strict_mode() const final { return m_strict; } + virtual Completion ordinary_call_evaluate_body(); + private: virtual bool is_ecmascript_function_object() const override { return true; } virtual void visit_edges(Visitor&) override; void prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target); void ordinary_call_bind_this(ExecutionContext&, Value this_argument); - Completion ordinary_call_evaluate_body(); + ThrowCompletionOr function_declaration_instantiation(Interpreter*); // Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects -- cgit v1.2.3