/* * Copyright (c) 2020, Stephan Unverwerth * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace JS { ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_object, FlyString name, Statement const& ecmascript_code, Vector parameters, i32 m_function_length, Environment* parent_scope, FunctionKind kind, bool is_strict, bool is_arrow_function) { Object* prototype = nullptr; switch (kind) { case FunctionKind::Regular: prototype = global_object.function_prototype(); break; case FunctionKind::Generator: prototype = global_object.generator_function_prototype(); break; } return global_object.heap().allocate(global_object, move(name), ecmascript_code, move(parameters), m_function_length, parent_scope, *prototype, kind, is_strict, is_arrow_function); } ECMAScriptFunctionObject::ECMAScriptFunctionObject(FlyString name, Statement const& ecmascript_code, Vector formal_parameters, i32 function_length, Environment* parent_scope, Object& prototype, FunctionKind kind, bool strict, bool is_arrow_function) : FunctionObject(prototype) , m_environment(parent_scope) , m_formal_parameters(move(formal_parameters)) , m_ecmascript_code(ecmascript_code) , m_realm(vm().interpreter_if_exists() ? &vm().interpreter().realm() : nullptr) , m_strict(strict) , m_name(move(name)) , m_function_length(function_length) , m_kind(kind) , m_is_arrow_function(is_arrow_function) { // NOTE: This logic is from OrdinaryFunctionCreate, https://tc39.es/ecma262/#sec-ordinaryfunctioncreate if (m_is_arrow_function) m_this_mode = ThisMode::Lexical; else if (m_strict) m_this_mode = ThisMode::Strict; else m_this_mode = ThisMode::Global; // 15.1.3 Static Semantics: IsSimpleParameterList, https://tc39.es/ecma262/#sec-static-semantics-issimpleparameterlist m_has_simple_parameter_list = all_of(m_formal_parameters, [&](auto& parameter) { if (parameter.is_rest) return false; if (parameter.default_value) return false; if (!parameter.binding.template has()) return false; return true; }); } void ECMAScriptFunctionObject::initialize(GlobalObject& global_object) { auto& vm = this->vm(); Base::initialize(global_object); if (!m_is_arrow_function) { auto* prototype = vm.heap().allocate(global_object, *global_object.new_ordinary_function_prototype_object_shape()); switch (m_kind) { case FunctionKind::Regular: prototype->define_property_or_throw(vm.names.constructor, { .value = this, .writable = true, .enumerable = false, .configurable = true }); break; case FunctionKind::Generator: // prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) (void)prototype->internal_set_prototype_of(global_object.generator_object_prototype()); break; } define_direct_property(vm.names.prototype, prototype, Attribute::Writable); } define_property_or_throw(vm.names.length, { .value = Value(m_function_length), .writable = false, .enumerable = false, .configurable = true }); define_property_or_throw(vm.names.name, { .value = js_string(vm, m_name.is_null() ? "" : m_name), .writable = false, .enumerable = false, .configurable = true }); } ECMAScriptFunctionObject::~ECMAScriptFunctionObject() { } void ECMAScriptFunctionObject::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_environment); visitor.visit(m_realm); visitor.visit(m_home_object); for (auto& field : m_fields) { field.name.visit_edges(visitor); visitor.visit(field.initializer); } } FunctionEnvironment* ECMAScriptFunctionObject::create_environment(FunctionObject& function_being_invoked) { HashMap variables; for (auto& parameter : m_formal_parameters) { parameter.binding.visit( [&](const FlyString& name) { variables.set(name, { js_undefined(), DeclarationKind::Var }); }, [&](const NonnullRefPtr& binding) { binding->for_each_bound_name([&](const auto& name) { variables.set(name, { js_undefined(), DeclarationKind::Var }); }); }); } if (is(ecmascript_code())) { for (auto& declaration : static_cast(ecmascript_code()).variables()) { for (auto& declarator : declaration.declarations()) { declarator.target().visit( [&](const NonnullRefPtr& id) { variables.set(id->string(), { js_undefined(), declaration.declaration_kind() }); }, [&](const NonnullRefPtr& binding) { binding->for_each_bound_name([&](const auto& name) { variables.set(name, { js_undefined(), declaration.declaration_kind() }); }); }); } } } auto* environment = heap().allocate(global_object(), m_environment, move(variables)); environment->set_function_object(static_cast(function_being_invoked)); if (m_is_arrow_function) { environment->set_this_binding_status(FunctionEnvironment::ThisBindingStatus::Lexical); if (is(m_environment)) environment->set_new_target(static_cast(m_environment)->new_target()); } return environment; } Value ECMAScriptFunctionObject::execute_function_body() { auto& vm = this->vm(); Interpreter* ast_interpreter = nullptr; auto* bytecode_interpreter = Bytecode::Interpreter::current(); auto prepare_arguments = [&] { auto& execution_context_arguments = vm.running_execution_context().arguments; for (size_t i = 0; i < m_formal_parameters.size(); ++i) { auto& parameter = m_formal_parameters[i]; parameter.binding.visit( [&](const auto& param) { Value argument_value; if (parameter.is_rest) { auto* array = Array::create(global_object(), 0); for (size_t rest_index = i; rest_index < execution_context_arguments.size(); ++rest_index) array->indexed_properties().append(execution_context_arguments[rest_index]); argument_value = move(array); } else if (i < execution_context_arguments.size() && !execution_context_arguments[i].is_undefined()) { argument_value = execution_context_arguments[i]; } else if (parameter.default_value) { // FIXME: Support default arguments in the bytecode world! if (!bytecode_interpreter) argument_value = parameter.default_value->execute(*ast_interpreter, global_object()); if (vm.exception()) return; } else { argument_value = js_undefined(); } vm.assign(param, argument_value, global_object(), true, vm.lexical_environment()); }); if (vm.exception()) return; } }; if (bytecode_interpreter) { prepare_arguments(); if (!m_bytecode_executable.has_value()) { m_bytecode_executable = Bytecode::Generator::generate(m_ecmascript_code, m_kind == FunctionKind::Generator); auto& passes = JS::Bytecode::Interpreter::optimization_pipeline(); passes.perform(*m_bytecode_executable); if constexpr (JS_BYTECODE_DEBUG) { dbgln("Optimisation passes took {}us", passes.elapsed()); dbgln("Compiled Bytecode::Block for function '{}':", m_name); for (auto& block : m_bytecode_executable->basic_blocks) block.dump(*m_bytecode_executable); } } auto result = bytecode_interpreter->run(*m_bytecode_executable); if (m_kind != FunctionKind::Generator) return result; return GeneratorObject::create(global_object(), result, this, vm.running_execution_context().lexical_environment, bytecode_interpreter->snapshot_frame()); } else { VERIFY(m_kind != FunctionKind::Generator); OwnPtr local_interpreter; ast_interpreter = vm.interpreter_if_exists(); if (!ast_interpreter) { local_interpreter = Interpreter::create_with_existing_realm(*realm()); ast_interpreter = local_interpreter.ptr(); } VM::InterpreterExecutionScope scope(*ast_interpreter); prepare_arguments(); if (vm.exception()) return {}; return ast_interpreter->execute_statement(global_object(), m_ecmascript_code, ScopeType::Function); } } Value ECMAScriptFunctionObject::call() { if (m_is_class_constructor) { vm().throw_exception(global_object(), ErrorType::ClassConstructorWithoutNew, m_name); return {}; } return execute_function_body(); } Value ECMAScriptFunctionObject::construct(FunctionObject&) { if (m_is_arrow_function || m_kind == FunctionKind::Generator) { vm().throw_exception(global_object(), ErrorType::NotAConstructor, m_name); return {}; } return execute_function_body(); } void ECMAScriptFunctionObject::set_name(const FlyString& name) { VERIFY(!name.is_null()); auto& vm = this->vm(); m_name = name; auto success = define_property_or_throw(vm.names.name, { .value = js_string(vm, m_name), .writable = false, .enumerable = false, .configurable = true }); VERIFY(success); } // 7.3.31 DefineField ( receiver, fieldRecord ), https://tc39.es/ecma262/#sec-definefield void ECMAScriptFunctionObject::InstanceField::define_field(VM& vm, Object& receiver) const { Value init_value = js_undefined(); if (initializer) { auto init_value_or_error = vm.call(*initializer, receiver.value_of()); if (init_value_or_error.is_error()) return; init_value = init_value_or_error.release_value(); } receiver.create_data_property_or_throw(name, init_value); } }