/* * 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 #include namespace JS { static ScriptFunction* typed_this(VM& vm, GlobalObject& global_object) { auto* this_object = vm.this_value(global_object).to_object(global_object); if (!this_object) return nullptr; if (!this_object->is_function()) { vm.throw_exception(global_object, ErrorType::NotAFunctionNoParam); return nullptr; } return static_cast(this_object); } ScriptFunction* ScriptFunction::create(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector parameters, i32 m_function_length, EnvironmentRecord* 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, global_object, name, body, move(parameters), m_function_length, parent_scope, *prototype, kind, is_strict, is_arrow_function); } ScriptFunction::ScriptFunction(GlobalObject& global_object, const FlyString& name, const Statement& body, Vector parameters, i32 function_length, EnvironmentRecord* parent_scope, Object& prototype, FunctionKind kind, bool is_strict, bool is_arrow_function) : Function(is_arrow_function ? vm().this_value(global_object) : Value(), {}, prototype) , m_name(name) , m_body(body) , m_parameters(move(parameters)) , m_environment(parent_scope) , m_function_length(function_length) , m_kind(kind) , m_is_strict(is_strict) , m_is_arrow_function(is_arrow_function) { // NOTE: This logic is from OrdinaryFunctionCreate, https://tc39.es/ecma262/#sec-ordinaryfunctioncreate if (m_is_arrow_function) set_this_mode(ThisMode::Lexical); else if (m_is_strict) set_this_mode(ThisMode::Strict); else set_this_mode(ThisMode::Global); } void ScriptFunction::initialize(GlobalObject& global_object) { auto& vm = this->vm(); Function::initialize(global_object); if (!m_is_arrow_function) { auto* prototype = vm.heap().allocate(global_object, *global_object.new_script_function_prototype_object_shape()); switch (m_kind) { case FunctionKind::Regular: prototype->define_property(vm.names.constructor, this, Attribute::Writable | Attribute::Configurable); break; case FunctionKind::Generator: // prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) prototype->set_prototype(global_object.generator_object_prototype()); break; } define_property(vm.names.prototype, prototype, Attribute::Writable); } define_native_property(vm.names.length, length_getter, {}, Attribute::Configurable); define_native_property(vm.names.name, name_getter, {}, Attribute::Configurable); } ScriptFunction::~ScriptFunction() { } void ScriptFunction::visit_edges(Visitor& visitor) { Function::visit_edges(visitor); visitor.visit(m_environment); } FunctionEnvironmentRecord* ScriptFunction::create_environment_record(Function& function_being_invoked) { HashMap variables; for (auto& parameter : m_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(body())) { for (auto& declaration : static_cast(body()).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, variables); environment->set_function_object(function_being_invoked); if (m_is_arrow_function) { if (is(m_environment)) environment->set_new_target(static_cast(m_environment)->new_target()); } return environment; } Value ScriptFunction::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_parameters.size(); ++i) { auto& parameter = m_parameters[i]; parameter.binding.visit( [&](const auto& param) { Value argument_value; if (parameter.is_rest) { auto* array = Array::create(global_object()); 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(); } if (i >= execution_context_arguments.size()) execution_context_arguments.resize(i + 1); execution_context_arguments[i] = argument_value; 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_body, 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_global_object(global_object()); ast_interpreter = local_interpreter.ptr(); } VM::InterpreterExecutionScope scope(*ast_interpreter); prepare_arguments(); if (vm.exception()) return {}; return ast_interpreter->execute_statement(global_object(), m_body, ScopeType::Function); } } Value ScriptFunction::call() { if (m_is_class_constructor) { vm().throw_exception(global_object(), ErrorType::ClassConstructorWithoutNew, m_name); return {}; } return execute_function_body(); } Value ScriptFunction::construct(Function&) { if (m_is_arrow_function || m_kind == FunctionKind::Generator) { vm().throw_exception(global_object(), ErrorType::NotAConstructor, m_name); return {}; } return execute_function_body(); } JS_DEFINE_NATIVE_GETTER(ScriptFunction::length_getter) { auto* function = typed_this(vm, global_object); if (!function) return {}; return Value(static_cast(function->m_function_length)); } JS_DEFINE_NATIVE_GETTER(ScriptFunction::name_getter) { auto* function = typed_this(vm, global_object); if (!function) return {}; return js_string(vm, function->name().is_null() ? "" : function->name()); } }