From 744536763da43c24b7f18df2e112dc5bf1f931b3 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Tue, 3 Oct 2023 02:04:38 +0200 Subject: [PATCH] Separate native and JavaScript functions (#3322) --- boa_cli/src/debug/function.rs | 18 +- boa_engine/src/builtins/error/type.rs | 17 +- boa_engine/src/builtins/function/mod.rs | 267 +++------ boa_engine/src/builtins/mod.rs | 52 +- boa_engine/src/builtins/regexp/tests.rs | 2 +- boa_engine/src/object/builtins/jsfunction.rs | 8 +- .../object/internal_methods/bound_function.rs | 6 +- .../src/object/internal_methods/function.rs | 146 ++++- boa_engine/src/object/internal_methods/mod.rs | 47 +- .../src/object/internal_methods/proxy.rs | 11 +- boa_engine/src/object/jsobject.rs | 31 +- boa_engine/src/object/mod.rs | 128 +++-- boa_engine/src/object/operations.rs | 32 +- boa_engine/src/vm/code_block.rs | 536 +++++++----------- boa_engine/src/vm/opcode/call/mod.rs | 64 +-- 15 files changed, 662 insertions(+), 703 deletions(-) diff --git a/boa_cli/src/debug/function.rs b/boa_cli/src/debug/function.rs index 6cb367fdde..9bb2c8ee0f 100644 --- a/boa_cli/src/debug/function.rs +++ b/boa_cli/src/debug/function.rs @@ -85,13 +85,11 @@ fn flowgraph(_this: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> Js let Some(function) = object.as_function() else { return Err(JsNativeError::typ() - .with_message("expected function object") + .with_message("expected an ordinary function object") .into()); }; - let code = function.codeblock().ok_or_else(|| { - JsNativeError::typ().with_message("native functions do not have bytecode") - })?; + let code = function.codeblock(); let mut graph = Graph::new(direction); code.to_graph(context.interner(), graph.subgraph(String::default())); @@ -118,12 +116,10 @@ fn bytecode(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResul let object = object.borrow(); let Some(function) = object.as_function() else { return Err(JsNativeError::typ() - .with_message("expected function object") + .with_message("expected an ordinary function object") .into()); }; - let code = function.codeblock().ok_or_else(|| { - JsNativeError::typ().with_message("native functions do not have bytecode") - })?; + let code = function.codeblock(); Ok(js_string!(code.to_interned_string(context.interner())).into()) } @@ -132,12 +128,10 @@ fn set_trace_flag_in_function_object(object: &JsObject, value: bool) -> JsResult let object = object.borrow(); let Some(function) = object.as_function() else { return Err(JsNativeError::typ() - .with_message("expected function object") + .with_message("expected an ordinary function object") .into()); }; - let code = function.codeblock().ok_or_else(|| { - JsNativeError::typ().with_message("native functions do not have bytecode") - })?; + let code = function.codeblock(); code.set_traceable(value); Ok(()) } diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 550949501e..9ebbed89cc 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -16,10 +16,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError use crate::{ - builtins::{ - function::{Function, FunctionKind}, - BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, - }, + builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, @@ -136,13 +133,11 @@ impl IntrinsicObject for ThrowTypeError { let mut obj = obj.borrow_mut(); obj.extensible = false; - *obj.kind_mut() = ObjectKind::Function(Function::new( - FunctionKind::Native { - function: NativeFunction::from_fn_ptr(throw_type_error), - constructor: None, - }, - realm.clone(), - )); + *obj.kind_mut() = ObjectKind::NativeFunction { + function: NativeFunction::from_fn_ptr(throw_type_error), + constructor: None, + realm: realm.clone(), + } } fn get(intrinsics: &Intrinsics) -> JsObject { diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index a9c1e4027f..08d88be8e4 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -18,8 +18,7 @@ use crate::{ environments::{EnvironmentStack, PrivateEnvironment}, error::JsNativeError, js_string, - native_function::NativeFunction, - object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, object::{JsFunction, PrivateElement, PrivateName}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -146,107 +145,31 @@ unsafe impl Trace for ClassFieldDefinition { #[derive(Finalize)] pub(crate) enum FunctionKind { - /// A rust function. - Native { - /// The rust function. - function: NativeFunction, - - /// The kind of the function constructor if it is a constructor. - constructor: Option, - }, /// A bytecode function. Ordinary { - /// The code block containing the compiled function. - code: Gc, - - /// The `[[Environment]]` internal slot. - environments: EnvironmentStack, - /// The `[[ConstructorKind]]` internal slot. constructor_kind: ConstructorKind, - /// The `[[HomeObject]]` internal slot. - home_object: Option, - /// The `[[Fields]]` internal slot. fields: ThinVec, /// The `[[PrivateMethods]]` internal slot. private_methods: ThinVec<(PrivateName, PrivateElement)>, - - /// The class object that this function is associated with. - class_object: Option, - - /// The `[[ScriptOrModule]]` internal slot. - script_or_module: Option, }, /// A bytecode async function. - Async { - /// The code block containing the compiled function. - code: Gc, - - /// The `[[Environment]]` internal slot. - environments: EnvironmentStack, - - /// The `[[HomeObject]]` internal slot. - home_object: Option, - - /// The class object that this function is associated with. - class_object: Option, - - /// The `[[ScriptOrModule]]` internal slot. - script_or_module: Option, - }, + Async, /// A bytecode generator function. - Generator { - /// The code block containing the compiled function. - code: Gc, - - /// The `[[Environment]]` internal slot. - environments: EnvironmentStack, - - /// The `[[HomeObject]]` internal slot. - home_object: Option, - - /// The class object that this function is associated with. - class_object: Option, - - /// The `[[ScriptOrModule]]` internal slot. - script_or_module: Option, - }, + Generator, /// A bytecode async generator function. - AsyncGenerator { - /// The code block containing the compiled function. - code: Gc, - - /// The `[[Environment]]` internal slot. - environments: EnvironmentStack, - - /// The `[[HomeObject]]` internal slot. - home_object: Option, - - /// The class object that this function is associated with. - class_object: Option, - - /// The `[[ScriptOrModule]]` internal slot. - script_or_module: Option, - }, + AsyncGenerator, } impl fmt::Debug for FunctionKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Native { - function, - constructor, - } => f - .debug_struct("FunctionKind::Native") - .field("function", &function) - .field("constructor", &constructor) - .finish(), Self::Ordinary { .. } => f .debug_struct("FunctionKind::Ordinary") .finish_non_exhaustive(), @@ -266,77 +189,65 @@ impl fmt::Debug for FunctionKind { unsafe impl Trace for FunctionKind { custom_trace! {this, { match this { - Self::Native { function, .. } => {mark(function)} Self::Ordinary { - code, - environments, - home_object, fields, private_methods, - class_object, - script_or_module, .. } => { - mark(code); - mark(environments); - mark(home_object); for elem in fields { mark(elem); } for (_, elem) in private_methods { mark(elem); } - mark(class_object); - mark(script_or_module); - } - Self::Async { code, environments, home_object, class_object, script_or_module } - | Self::Generator { code, environments, home_object, class_object, script_or_module} - | Self::AsyncGenerator { code, environments, home_object, class_object, script_or_module} => { - mark(code); - mark(environments); - mark(home_object); - mark(class_object); - mark(script_or_module); } + Self::Async + | Self::Generator + | Self::AsyncGenerator => {} } }} } -/// Boa representation of a Function Object. +/// Boa representation of a JavaScript Function Object. /// /// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code /// (AST Node). /// /// #[derive(Debug, Trace, Finalize)] -pub struct Function { - kind: FunctionKind, - realm: Realm, +pub struct OrdinaryFunction { + /// The code block containing the compiled function. + pub(crate) code: Gc, + + /// The `[[Environment]]` internal slot. + pub(crate) environments: EnvironmentStack, + + /// The `[[HomeObject]]` internal slot. + pub(crate) home_object: Option, + + /// The class object that this function is associated with. + pub(crate) class_object: Option, + + /// The `[[ScriptOrModule]]` internal slot. + pub(crate) script_or_module: Option, + + /// The [`Realm`] the function is defined in. + pub(crate) realm: Realm, + + /// The kind of ordinary function. + pub(crate) kind: FunctionKind, } -impl Function { - /// Returns the codeblock of the function, or `None` if the function is a [`NativeFunction`]. +impl OrdinaryFunction { + /// Returns the codeblock of the function. #[must_use] - pub fn codeblock(&self) -> Option<&CodeBlock> { - match &self.kind { - FunctionKind::Native { .. } => None, - FunctionKind::Ordinary { code, .. } - | FunctionKind::Async { code, .. } - | FunctionKind::Generator { code, .. } - | FunctionKind::AsyncGenerator { code, .. } => Some(code), - } - } - - /// Creates a new `Function`. - pub(crate) fn new(kind: FunctionKind, realm: Realm) -> Self { - Self { kind, realm } + pub fn codeblock(&self) -> &CodeBlock { + &self.code } /// Push a private environment to the function. pub(crate) fn push_private_environment(&mut self, environment: Gc) { - if let FunctionKind::Ordinary { environments, .. } = &mut self.kind { - environments.push_private(environment); - } + self.environments.push_private(environment); } /// Returns true if the function object is a derived constructor. @@ -353,33 +264,17 @@ impl Function { /// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value. pub(crate) fn in_class_field_initializer(&self) -> bool { - if let FunctionKind::Ordinary { code, .. } = &self.kind { - code.in_class_field_initializer() - } else { - false - } + self.code.in_class_field_initializer() } /// Returns a reference to the function `[[HomeObject]]` slot if present. pub(crate) const fn get_home_object(&self) -> Option<&JsObject> { - match &self.kind { - FunctionKind::Ordinary { home_object, .. } - | FunctionKind::Async { home_object, .. } - | FunctionKind::Generator { home_object, .. } - | FunctionKind::AsyncGenerator { home_object, .. } => home_object.as_ref(), - FunctionKind::Native { .. } => None, - } + self.home_object.as_ref() } /// Sets the `[[HomeObject]]` slot if present. pub(crate) fn set_home_object(&mut self, object: JsObject) { - match &mut self.kind { - FunctionKind::Ordinary { home_object, .. } - | FunctionKind::Async { home_object, .. } - | FunctionKind::Generator { home_object, .. } - | FunctionKind::AsyncGenerator { home_object, .. } => *home_object = Some(object), - FunctionKind::Native { .. } => {} - } + self.home_object = Some(object); } /// Returns the values of the `[[Fields]]` internal slot. @@ -429,13 +324,7 @@ impl Function { /// Sets the class object. pub(crate) fn set_class_object(&mut self, object: JsObject) { - match &mut self.kind { - FunctionKind::Ordinary { class_object, .. } - | FunctionKind::Async { class_object, .. } - | FunctionKind::Generator { class_object, .. } - | FunctionKind::AsyncGenerator { class_object, .. } => *class_object = Some(object), - FunctionKind::Native { .. } => {} - } + self.class_object = Some(object); } /// Gets the `Realm` from where this function originates. @@ -961,44 +850,64 @@ impl BuiltInFunctionObject { #[allow(clippy::wrong_self_convention)] fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult { - let object = this.as_object().map(JsObject::borrow); - let function = object - .as_deref() - .and_then(Object::as_function) - .ok_or_else(|| JsNativeError::typ().with_message("Not a function"))?; - - let name = { - // Is there a case here where if there is no name field on a value - // name should default to None? Do all functions have names set? - let value = this - .as_object() - .expect("checked that `this` was an object above") - .get(utf16!("name"), &mut *context)?; - if value.is_null_or_undefined() { - None - } else { - Some(value.to_string(context)?) - } + // 1. Let func be the this value. + let func = this; + + // 2. If func is an Object, func has a [[SourceText]] internal slot, func.[[SourceText]] is a sequence of Unicode code points,and HostHasSourceTextAvailable(func) is true, then + // a. Return CodePointsToString(func.[[SourceText]]). + + // 3. If func is a built-in function object, return an implementation-defined String source code representation of func. + // The representation must have the syntax of a NativeFunction. Additionally, if func has an [[InitialName]] internal slot and + // func.[[InitialName]] is a String, the portion of the returned String that would be matched by + // NativeFunctionAccessor_opt PropertyName must be the value of func.[[InitialName]]. + + // 4. If func is an Object and IsCallable(func) is true, return an implementation-defined String source code representation of func. + // The representation must have the syntax of a NativeFunction. + // 5. Throw a TypeError exception. + let Some(object) = func.as_object() else { + return Err(JsNativeError::typ().with_message("not a function").into()); }; - let name = name - .filter(|n| !n.is_empty()) - .unwrap_or_else(|| "anonymous".into()); + if object.borrow().is_native_function() { + let name = { + // Is there a case here where if there is no name field on a value + // name should default to None? Do all functions have names set? + let value = this + .as_object() + .expect("checked that `this` was an object above") + .get(utf16!("name"), &mut *context)?; + if value.is_null_or_undefined() { + js_string!() + } else { + value.to_string(context)? + } + }; + return Ok( + js_string!(utf16!("function "), &name, utf16!("() { [native code] }")).into(), + ); + } + + let object = object.borrow(); + let function = object + .as_function() + .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?; + + let code = function.codeblock(); - match function.kind { - FunctionKind::Native { .. } | FunctionKind::Ordinary { .. } => { - Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into()) + let prefix = match function.kind { + FunctionKind::Ordinary { .. } => { + utf16!("function ") } FunctionKind::Async { .. } => { - Ok(js_string!(utf16!("[AsyncFunction: "), &name, utf16!("]")).into()) + utf16!("async function ") } FunctionKind::Generator { .. } => { - Ok(js_string!(utf16!("[GeneratorFunction: "), &name, utf16!("]")).into()) - } - FunctionKind::AsyncGenerator { .. } => { - Ok(js_string!(utf16!("[AsyncGeneratorFunction: "), &name, utf16!("]")).into()) + utf16!("function* ") } - } + FunctionKind::AsyncGenerator { .. } => utf16!("async function* "), + }; + + Ok(js_string!(prefix, code.name(), utf16!("() { [native code] }")).into()) } /// `Function.prototype [ @@hasInstance ] ( V )` diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 440fa20f4c..5e0d9df2ec 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -543,15 +543,10 @@ impl ApplyToObject for OrdinaryFunction { impl ApplyToObject for Callable { fn apply_to(self, object: &mut BuiltInObjectInitializer) { - let function = ObjectData::function( - function::Function::new( - function::FunctionKind::Native { - function: NativeFunction::from_fn_ptr(self.function), - constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base), - }, - self.realm, - ), + let function = ObjectData::native_function( + NativeFunction::from_fn_ptr(self.function), S::IS_CONSTRUCTOR, + self.realm, ); object.set_data(function); object.insert( @@ -815,13 +810,11 @@ impl BuiltInConstructorWithPrototype<'_> { } fn build(mut self) { - let function = function::Function::new( - function::FunctionKind::Native { - function: NativeFunction::from_fn_ptr(self.function), - constructor: (true).then_some(function::ConstructorKind::Base), - }, - self.realm.clone(), - ); + let function = ObjectKind::NativeFunction { + function: NativeFunction::from_fn_ptr(self.function), + constructor: Some(function::ConstructorKind::Base), + realm: self.realm.clone(), + }; let length = self.length; let name = self.name.clone(); @@ -852,7 +845,7 @@ impl BuiltInConstructorWithPrototype<'_> { } let mut object = self.object.borrow_mut(); - *object.kind_mut() = ObjectKind::Function(function); + *object.kind_mut() = function; object .properties_mut() .shape @@ -867,13 +860,11 @@ impl BuiltInConstructorWithPrototype<'_> { } fn build_without_prototype(mut self) { - let function = function::Function::new( - function::FunctionKind::Native { - function: NativeFunction::from_fn_ptr(self.function), - constructor: (true).then_some(function::ConstructorKind::Base), - }, - self.realm.clone(), - ); + let function = ObjectKind::NativeFunction { + function: NativeFunction::from_fn_ptr(self.function), + constructor: Some(function::ConstructorKind::Base), + realm: self.realm.clone(), + }; let length = self.length; let name = self.name.clone(); @@ -881,7 +872,7 @@ impl BuiltInConstructorWithPrototype<'_> { self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); let mut object = self.object.borrow_mut(); - *object.kind_mut() = ObjectKind::Function(function); + *object.kind_mut() = function; object .properties_mut() .shape @@ -922,15 +913,12 @@ impl BuiltInCallable<'_> { } fn build(self) -> JsFunction { - let function = function::FunctionKind::Native { - function: NativeFunction::from_fn_ptr(self.function), - constructor: None, - }; - - let function = function::Function::new(function, self.realm.clone()); - let object = self.realm.intrinsics().templates().function().create( - ObjectData::function(function, false), + ObjectData::native_function( + NativeFunction::from_fn_ptr(self.function), + false, + self.realm.clone(), + ), vec![JsValue::new(self.length), JsValue::new(self.name)], ); diff --git a/boa_engine/src/builtins/regexp/tests.rs b/boa_engine/src/builtins/regexp/tests.rs index e76778de77..7e413cefe8 100644 --- a/boa_engine/src/builtins/regexp/tests.rs +++ b/boa_engine/src/builtins/regexp/tests.rs @@ -42,7 +42,7 @@ fn species() { // symbol-species TestAction::assert_eq("descriptor.set", JsValue::undefined()), TestAction::assert_with_op("accessor", |v, _| { - v.as_object().map_or(false, JsObject::is_function) + v.as_object().map_or(false, JsObject::is_native_function) }), TestAction::assert("!descriptor.enumerable"), TestAction::assert("descriptor.configurable"), diff --git a/boa_engine/src/object/builtins/jsfunction.rs b/boa_engine/src/object/builtins/jsfunction.rs index 541873e165..d252b84bcd 100644 --- a/boa_engine/src/object/builtins/jsfunction.rs +++ b/boa_engine/src/object/builtins/jsfunction.rs @@ -1,7 +1,9 @@ //! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object use crate::{ object::{ - internal_methods::function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, + internal_methods::function::{ + NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS, + }, JsObject, JsObjectType, Object, }, value::TryFromJs, @@ -32,9 +34,9 @@ impl JsFunction { inner: JsObject::from_object_and_vtable( Object::default(), if constructor { - &CONSTRUCTOR_INTERNAL_METHODS + &NATIVE_CONSTRUCTOR_INTERNAL_METHODS } else { - &FUNCTION_INTERNAL_METHODS + &NATIVE_FUNCTION_INTERNAL_METHODS }, ), } diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs index 8fe5297356..cd39eac0cd 100644 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ b/boa_engine/src/object/internal_methods/bound_function.rs @@ -10,14 +10,14 @@ use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __call__: Some(bound_function_exotic_call), + __call__: bound_function_exotic_call, ..ORDINARY_INTERNAL_METHODS }; pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __call__: Some(bound_function_exotic_call), - __construct__: Some(bound_function_exotic_construct), + __call__: bound_function_exotic_call, + __construct__: bound_function_exotic_construct, ..ORDINARY_INTERNAL_METHODS }; diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs index 2b32875a84..4515bf6074 100644 --- a/boa_engine/src/object/internal_methods/function.rs +++ b/boa_engine/src/object/internal_methods/function.rs @@ -1,11 +1,14 @@ use crate::{ + context::intrinsics::StandardConstructors, object::{ internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, - JsObject, + JsObject, ObjectData, ObjectKind, }, - Context, JsResult, JsValue, + Context, JsNativeError, JsResult, JsValue, }; +use super::get_prototype_from_constructor; + /// Definitions of the internal object methods for function objects. /// /// More information: @@ -13,14 +16,13 @@ use crate::{ /// /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __call__: Some(function_call), - __construct__: None, + __call__: function_call, ..ORDINARY_INTERNAL_METHODS }; pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __call__: Some(function_call), - __construct__: Some(function_construct), + __call__: function_call, + __construct__: function_construct, ..ORDINARY_INTERNAL_METHODS }; @@ -31,7 +33,6 @@ pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = Internal /// Panics if the object is currently mutably borrowed. // // -#[track_caller] fn function_call( obj: &JsObject, this: &JsValue, @@ -47,7 +48,6 @@ fn function_call( /// /// Panics if the object is currently mutably borrowed. // -#[track_caller] fn function_construct( obj: &JsObject, args: &[JsValue], @@ -56,3 +56,133 @@ fn function_construct( ) -> JsResult { obj.construct_internal(args, &new_target.clone().into(), context) } + +/// Definitions of the internal object methods for native function objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects +pub(crate) static NATIVE_FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __call__: native_function_call, + ..ORDINARY_INTERNAL_METHODS +}; + +pub(crate) static NATIVE_CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = + InternalObjectMethods { + __call__: native_function_call, + __construct__: native_function_construct, + ..ORDINARY_INTERNAL_METHODS + }; + +/// Call this object. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +/// +// +#[track_caller] +pub(crate) fn native_function_call( + obj: &JsObject, + this: &JsValue, + args: &[JsValue], + context: &mut Context<'_>, +) -> JsResult { + let this_function_object = obj.clone(); + let object = obj.borrow(); + + let ObjectKind::NativeFunction { + function, + constructor, + realm, + } = object.kind() + else { + unreachable!("the object should be a native function object"); + }; + + let mut realm = realm.clone(); + let function = function.clone(); + let constructor = *constructor; + drop(object); + + context.swap_realm(&mut realm); + context.vm.native_active_function = Some(this_function_object); + + let result = if constructor.is_some() { + function.call(&JsValue::undefined(), args, context) + } else { + function.call(this, args, context) + } + .map_err(|err| err.inject_realm(context.realm().clone())); + + context.vm.native_active_function = None; + context.swap_realm(&mut realm); + + result +} + +/// Construct an instance of this object with the specified arguments. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +#[track_caller] +fn native_function_construct( + obj: &JsObject, + args: &[JsValue], + new_target: &JsObject, + context: &mut Context<'_>, +) -> JsResult { + let this_function_object = obj.clone(); + let object = obj.borrow(); + + let ObjectKind::NativeFunction { + function, + constructor, + realm, + } = object.kind() + else { + unreachable!("the object should be a native function object"); + }; + + let mut realm = realm.clone(); + let function = function.clone(); + let constructor = *constructor; + drop(object); + + context.swap_realm(&mut realm); + context.vm.native_active_function = Some(this_function_object); + + let new_target = new_target.clone().into(); + let result = function + .call(&new_target, args, context) + .map_err(|err| err.inject_realm(context.realm().clone())) + .and_then(|v| match v { + JsValue::Object(ref o) => Ok(o.clone()), + val => { + if constructor.expect("must be a constructor").is_base() || val.is_undefined() { + let prototype = get_prototype_from_constructor( + &new_target, + StandardConstructors::object, + context, + )?; + Ok(JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype, + ObjectData::ordinary(), + )) + } else { + Err(JsNativeError::typ() + .with_message("derived constructor can only return an Object or undefined") + .into()) + } + } + }); + + context.vm.native_active_function = None; + context.swap_realm(&mut realm); + + result +} diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 9e51f2369e..4fdb576ab6 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -11,7 +11,7 @@ use crate::{ object::JsObject, property::{DescriptorKind, PropertyDescriptor, PropertyKey}, value::JsValue, - Context, JsResult, + Context, JsNativeError, JsResult, }; use boa_profiler::Profiler; @@ -225,11 +225,7 @@ impl JsObject { context: &mut Context<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__call__", "object"); - self.vtable() - .__call__ - .expect("called `[[Call]]` for object without a `[[Call]]` internal method")( - self, this, args, context, - ) + (self.vtable().__call__)(self, this, args, context) } /// Internal method `[[Construct]]` @@ -248,11 +244,7 @@ impl JsObject { context: &mut Context<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__construct__", "object"); - self.vtable() - .__construct__ - .expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")( - self, args, new_target, context, - ) + (self.vtable().__construct__)(self, args, new_target, context) } } @@ -279,8 +271,8 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj __set__: ordinary_set, __delete__: ordinary_delete, __own_property_keys__: ordinary_own_property_keys, - __call__: None, - __construct__: None, + __call__: non_existant_call, + __construct__: non_existant_construct, }; /// The internal representation of the internal methods of a `JsObject`. @@ -307,10 +299,9 @@ pub(crate) struct InternalObjectMethods { fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context<'_>) -> JsResult, pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context<'_>) -> JsResult, pub(crate) __own_property_keys__: fn(&JsObject, &mut Context<'_>) -> JsResult>, - pub(crate) __call__: - Option) -> JsResult>, + pub(crate) __call__: fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult, pub(crate) __construct__: - Option) -> JsResult>, + fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult, } /// Abstract operation `OrdinaryGetPrototypeOf`. @@ -919,3 +910,27 @@ where // b. Set proto to realm's intrinsic object named intrinsicDefaultProto. Ok(default(realm.intrinsics().constructors()).prototype()) } + +pub(crate) fn non_existant_call( + _obj: &JsObject, + _this: &JsValue, + _args: &[JsValue], + context: &mut Context<'_>, +) -> JsResult { + Err(JsNativeError::typ() + .with_message("not a callable function") + .with_realm(context.realm().clone()) + .into()) +} + +pub(crate) fn non_existant_construct( + _obj: &JsObject, + _args: &[JsValue], + _new_target: &JsObject, + context: &mut Context<'_>, +) -> JsResult { + Err(JsNativeError::typ() + .with_message("object is not constructable") + .with_realm(context.realm().clone()) + .into()) +} diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 6874d8d5d9..4ebe9554dc 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -9,6 +9,8 @@ use crate::{ }; use rustc_hash::FxHashSet; +use super::ORDINARY_INTERNAL_METHODS; + /// Definitions of the internal object methods for array exotic objects. /// /// More information: @@ -28,20 +30,19 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods = __set__: proxy_exotic_set, __delete__: proxy_exotic_delete, __own_property_keys__: proxy_exotic_own_property_keys, - __call__: None, - __construct__: None, + ..ORDINARY_INTERNAL_METHODS }; pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods = InternalObjectMethods { - __call__: Some(proxy_exotic_call), + __call__: proxy_exotic_call, ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC }; pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods = InternalObjectMethods { - __call__: Some(proxy_exotic_call), - __construct__: Some(proxy_exotic_construct), + __call__: proxy_exotic_call, + __construct__: proxy_exotic_construct, ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC }; diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index fea283986e..a6a2323d2a 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -3,7 +3,10 @@ //! The `JsObject` is a garbage collected Object. use super::{ - internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS}, + internal_methods::{ + non_existant_call, non_existant_construct, InternalObjectMethods, + ARRAY_EXOTIC_INTERNAL_METHODS, + }, shape::RootShape, JsPrototype, NativeObject, Object, PrivateName, PropertyMap, }; @@ -488,8 +491,20 @@ impl JsObject { #[inline] #[must_use] #[track_caller] - pub fn is_function(&self) -> bool { - self.borrow().is_function() + pub fn is_ordinary_function(&self) -> bool { + self.borrow().is_ordinary_function() + } + + /// Checks if it's a native `Function` object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[must_use] + #[track_caller] + pub fn is_native_function(&self) -> bool { + self.borrow().is_native_function() } /// Checks if it's a `Generator` object. @@ -956,9 +971,9 @@ Cannot both specify accessors and a value or writable attribute", /// [spec]: https://tc39.es/ecma262/#sec-iscallable #[inline] #[must_use] - #[track_caller] + #[allow(clippy::fn_address_comparisons)] pub fn is_callable(&self) -> bool { - self.inner.vtable.__call__.is_some() + self.inner.vtable.__call__ != non_existant_call } /// It determines if Object is a function object with a `[[Construct]]` internal method. @@ -969,9 +984,9 @@ Cannot both specify accessors and a value or writable attribute", /// [spec]: https://tc39.es/ecma262/#sec-isconstructor #[inline] #[must_use] - #[track_caller] + #[allow(clippy::fn_address_comparisons)] pub fn is_constructor(&self) -> bool { - self.inner.vtable.__construct__.is_some() + self.inner.vtable.__construct__ != non_existant_construct } /// Returns true if the `JsObject` is the global for a Realm @@ -1146,7 +1161,7 @@ impl Debug for JsObject { let ptr: *const _ = self.as_ref(); let obj = self.borrow(); let kind = obj.kind(); - if obj.is_function() { + if obj.is_ordinary_function() { let name_prop = obj .properties() .get(&PropertyKey::String(JsString::from("name"))); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 293f705674..4264266b4d 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -14,7 +14,10 @@ use self::{ bound_function::{ BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS, }, - function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, + function::{ + CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS, + NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS, + }, immutable_prototype::IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, module_namespace::MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS, @@ -41,8 +44,8 @@ use crate::{ array_buffer::ArrayBuffer, async_generator::AsyncGenerator, error::ErrorKind, - function::{arguments::Arguments, FunctionKind}, - function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function}, + function::arguments::Arguments, + function::{arguments::ParameterMap, BoundFunction, ConstructorKind, OrdinaryFunction}, generator::Generator, iterable::AsyncFromSyncIterator, map::ordered_map::OrderedMap, @@ -297,7 +300,7 @@ pub enum ObjectKind { AsyncGenerator(AsyncGenerator), /// The `AsyncGeneratorFunction` object kind. - AsyncGeneratorFunction(Function), + AsyncGeneratorFunction(OrdinaryFunction), /// The `Array` object kind. Array, @@ -333,7 +336,7 @@ pub enum ObjectKind { ForInIterator(ForInIterator), /// The `Function` object kind. - Function(Function), + OrdinaryFunction(OrdinaryFunction), /// The `BoundFunction` object kind. BoundFunction(BoundFunction), @@ -342,7 +345,19 @@ pub enum ObjectKind { Generator(Generator), /// The `GeneratorFunction` object kind. - GeneratorFunction(Function), + GeneratorFunction(OrdinaryFunction), + + /// A native rust function. + NativeFunction { + /// The rust function. + function: NativeFunction, + + /// The kind of the function constructor if it is a constructor. + constructor: Option, + + /// The [`Realm`] in which the function is defined. + realm: Realm, + }, /// The `Set` object kind. Set(OrderedSet), @@ -445,9 +460,13 @@ unsafe impl Trace for ObjectKind { Self::RegExpStringIterator(i) => mark(i), Self::DataView(v) => mark(v), Self::ForInIterator(i) => mark(i), - Self::Function(f) | Self::GeneratorFunction(f) | Self::AsyncGeneratorFunction(f) => mark(f), + Self::OrdinaryFunction(f) | Self::GeneratorFunction(f) | Self::AsyncGeneratorFunction(f) => mark(f), Self::BoundFunction(f) => mark(f), Self::Generator(g) => mark(g), + Self::NativeFunction { function, constructor: _, realm } => { + mark(function); + mark(realm); + } Self::Set(s) => mark(s), Self::SetIterator(i) => mark(i), Self::StringIterator(i) => mark(i), @@ -518,7 +537,7 @@ impl ObjectData { /// Create the `AsyncGeneratorFunction` object data #[must_use] - pub fn async_generator_function(function: Function) -> Self { + pub fn async_generator_function(function: OrdinaryFunction) -> Self { Self { internal_methods: &FUNCTION_INTERNAL_METHODS, kind: ObjectKind::GeneratorFunction(function), @@ -633,16 +652,37 @@ impl ObjectData { } } - /// Create the `Function` object data + /// Create the ordinary function object data #[must_use] - pub fn function(function: Function, constructor: bool) -> Self { + pub fn ordinary_function(function: OrdinaryFunction, constructor: bool) -> Self { + let internal_methods = if constructor { + &CONSTRUCTOR_INTERNAL_METHODS + } else { + &FUNCTION_INTERNAL_METHODS + }; + Self { - internal_methods: if constructor { - &CONSTRUCTOR_INTERNAL_METHODS - } else { - &FUNCTION_INTERNAL_METHODS + internal_methods, + kind: ObjectKind::OrdinaryFunction(function), + } + } + + /// Create the native function object data + #[must_use] + pub fn native_function(function: NativeFunction, constructor: bool, realm: Realm) -> Self { + let internal_methods = if constructor { + &NATIVE_CONSTRUCTOR_INTERNAL_METHODS + } else { + &NATIVE_FUNCTION_INTERNAL_METHODS + }; + + Self { + internal_methods, + kind: ObjectKind::NativeFunction { + function, + constructor: constructor.then_some(ConstructorKind::Base), + realm, }, - kind: ObjectKind::Function(function), } } @@ -670,7 +710,7 @@ impl ObjectData { /// Create the `GeneratorFunction` object data #[must_use] - pub fn generator_function(function: Function) -> Self { + pub fn generator_function(function: OrdinaryFunction) -> Self { Self { internal_methods: &FUNCTION_INTERNAL_METHODS, kind: ObjectKind::GeneratorFunction(function), @@ -930,10 +970,11 @@ impl Debug for ObjectKind { Self::ArrayIterator(_) => "ArrayIterator", Self::ArrayBuffer(_) => "ArrayBuffer", Self::ForInIterator(_) => "ForInIterator", - Self::Function(_) => "Function", + Self::OrdinaryFunction(_) => "Function", Self::BoundFunction(_) => "BoundFunction", Self::Generator(_) => "Generator", Self::GeneratorFunction(_) => "GeneratorFunction", + Self::NativeFunction { .. } => "NativeFunction", Self::RegExp(_) => "RegExp", Self::RegExpStringIterator(_) => "RegExpStringIterator", Self::Map(_) => "Map", @@ -1267,27 +1308,29 @@ impl Object { /// Checks if the object is a `Function` object. #[inline] #[must_use] - pub const fn is_function(&self) -> bool { - matches!(self.kind, ObjectKind::Function(_)) + pub const fn is_ordinary_function(&self) -> bool { + matches!( + self.kind, + ObjectKind::OrdinaryFunction(_) | ObjectKind::GeneratorFunction(_) + ) } /// Gets the function data if the object is a `Function`. #[inline] #[must_use] - pub const fn as_function(&self) -> Option<&Function> { + pub const fn as_function(&self) -> Option<&OrdinaryFunction> { match self.kind { - ObjectKind::Function(ref function) | ObjectKind::GeneratorFunction(ref function) => { - Some(function) - } + ObjectKind::OrdinaryFunction(ref function) + | ObjectKind::GeneratorFunction(ref function) => Some(function), _ => None, } } /// Gets the mutable function data if the object is a `Function`. #[inline] - pub fn as_function_mut(&mut self) -> Option<&mut Function> { + pub fn as_function_mut(&mut self) -> Option<&mut OrdinaryFunction> { match self.kind { - ObjectKind::Function(ref mut function) + ObjectKind::OrdinaryFunction(ref mut function) | ObjectKind::GeneratorFunction(ref mut function) => Some(function), _ => None, } @@ -1721,6 +1764,13 @@ impl Object { matches!(self.kind, ObjectKind::NativeObject(_)) } + /// Returns `true` if it holds a native Rust function. + #[inline] + #[must_use] + pub const fn is_native_function(&self) -> bool { + matches!(self.kind, ObjectKind::NativeFunction { .. }) + } + /// Gets the native object data if the object is a `NativeObject`. #[inline] #[must_use] @@ -2107,15 +2157,12 @@ impl<'realm> FunctionObjectBuilder<'realm> { /// Build the function object. #[must_use] pub fn build(self) -> JsFunction { - let function = Function::new( - FunctionKind::Native { - function: self.function, - constructor: self.constructor, - }, - self.realm.clone(), - ); let object = self.realm.intrinsics().templates().function().create( - ObjectData::function(function, self.constructor.is_some()), + ObjectData::native_function( + self.function, + self.constructor.is_some(), + self.realm.clone(), + ), vec![self.length.into(), self.name.into()], ); @@ -2522,15 +2569,6 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { /// Build the constructor function object. #[must_use] pub fn build(mut self) -> StandardConstructor { - // Create the native function - let function = Function::new( - FunctionKind::Native { - function: self.function, - constructor: self.kind, - }, - self.context.realm().clone(), - ); - let length = PropertyDescriptor::builder() .value(self.length) .writable(false) @@ -2562,7 +2600,11 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { let mut constructor = self.constructor_object; constructor.insert(utf16!("length"), length); constructor.insert(utf16!("name"), name); - let data = ObjectData::function(function, self.kind.is_some()); + let data = ObjectData::native_function( + self.function, + self.kind.is_some(), + self.context.realm().clone(), + ); constructor.kind = data.kind; diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index 3b294039e4..9dc5627659 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -2,7 +2,7 @@ use crate::{ builtins::{function::ClassFieldDefinition, Array}, context::intrinsics::{StandardConstructor, StandardConstructors}, error::JsNativeError, - object::{JsFunction, JsObject, PrivateElement, PrivateName, CONSTRUCTOR, PROTOTYPE}, + object::{JsObject, PrivateElement, PrivateName, CONSTRUCTOR, PROTOTYPE}, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, realm::Realm, string::utf16, @@ -10,6 +10,8 @@ use crate::{ Context, JsResult, JsSymbol, JsValue, }; +use super::ObjectKind; + /// Object integrity level. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IntegrityLevel { @@ -315,14 +317,14 @@ impl JsObject { args: &[JsValue], context: &mut Context<'_>, ) -> JsResult { - // 1. If argumentsList is not present, set argumentsList to a new empty List. - // 2. If IsCallable(F) is false, throw a TypeError exception. - let function = JsFunction::from_object(self.clone()).ok_or_else(|| { - JsNativeError::typ().with_message("only callable objects / functions can be called") - })?; + // SKIP: 1. If argumentsList is not present, set argumentsList to a new empty List. + // SKIP: 2. If IsCallable(F) is false, throw a TypeError exception. + + // NOTE(HalidOdat): For object's that are not callable we implement a special __call__ internal method + // that throws on call. // 3. Return ? F.[[Call]](V, argumentsList). - function.__call__(this, args, context) + self.__call__(this, args, context) } /// `Construct ( F [ , argumentsList [ , newTarget ] ] )` @@ -678,6 +680,10 @@ impl JsObject { return Ok(fun.realm().clone()); } + if let ObjectKind::NativeFunction { realm, .. } = constructor.kind() { + return Ok(realm.clone()); + } + if let Some(bound) = constructor.as_bound_function() { let fun = bound.target_function().clone(); drop(constructor); @@ -1153,14 +1159,16 @@ impl JsValue { args: &[Self], context: &mut Context<'_>, ) -> JsResult { - self.as_callable() - .ok_or_else(|| { - JsNativeError::typ().with_message(format!( + let Some(object) = self.as_object() else { + return Err(JsNativeError::typ() + .with_message(format!( "value with type `{}` is not callable", self.type_of() )) - })? - .__call__(this, args, context) + .into()); + }; + + object.__call__(this, args, context) } /// Abstract operation `( V, P [ , argumentsList ] )` diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index deb59f34bb..e274e200e3 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -3,7 +3,9 @@ //! This module is for the `CodeBlock` which implements a function representation in the VM use crate::{ - builtins::function::{arguments::Arguments, ConstructorKind, Function, FunctionKind, ThisMode}, + builtins::function::{ + arguments::Arguments, ConstructorKind, FunctionKind, OrdinaryFunction, ThisMode, + }, context::intrinsics::StandardConstructors, environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus}, error::JsNativeError, @@ -778,33 +780,32 @@ pub(crate) fn create_function_object( let script_or_module = context.get_active_script_or_module(); let function = if r#async { - Function::new( - FunctionKind::Async { - code, - environments: context.vm.environments.clone(), - home_object: None, - class_object: None, - script_or_module, - }, - context.realm().clone(), - ) + OrdinaryFunction { + code, + environments: context.vm.environments.clone(), + home_object: None, + class_object: None, + script_or_module, + kind: FunctionKind::Async, + realm: context.realm().clone(), + } } else { - Function::new( - FunctionKind::Ordinary { - code, - environments: context.vm.environments.clone(), + OrdinaryFunction { + code, + environments: context.vm.environments.clone(), + home_object: None, + class_object: None, + script_or_module, + kind: FunctionKind::Ordinary { constructor_kind: ConstructorKind::Base, - home_object: None, fields: ThinVec::new(), private_methods: ThinVec::new(), - class_object: None, - script_or_module, }, - context.realm().clone(), - ) + realm: context.realm().clone(), + } }; - let data = ObjectData::function(function, !r#async); + let data = ObjectData::ordinary_function(function, !r#async); let templates = context.intrinsics().templates(); @@ -857,30 +858,27 @@ pub(crate) fn create_function_object_fast( let script_or_module = context.get_active_script_or_module(); - let function = if r#async { - FunctionKind::Async { - code, - environments: context.vm.environments.clone(), - home_object: None, - class_object: None, - script_or_module, - } + let kind = if r#async { + FunctionKind::Async } else { FunctionKind::Ordinary { - code, - environments: context.vm.environments.clone(), constructor_kind: ConstructorKind::Base, - home_object: None, fields: ThinVec::new(), private_methods: ThinVec::new(), - class_object: None, - script_or_module, } }; - let function = Function::new(function, context.realm().clone()); + let function = OrdinaryFunction { + code, + environments: context.vm.environments.clone(), + class_object: None, + script_or_module, + home_object: None, + kind, + realm: context.realm().clone(), + }; - let data = ObjectData::function(function, !method && !arrow && !r#async); + let data = ObjectData::ordinary_function(function, !method && !arrow && !r#async); if r#async { context @@ -963,32 +961,30 @@ pub(crate) fn create_generator_function_object( let script_or_module = context.get_active_script_or_module(); let constructor = if r#async { - let function = Function::new( - FunctionKind::AsyncGenerator { - code, - environments: context.vm.environments.clone(), - home_object: None, - class_object: None, - script_or_module, - }, - context.realm().clone(), - ); + let function = OrdinaryFunction { + code, + environments: context.vm.environments.clone(), + home_object: None, + class_object: None, + script_or_module, + kind: FunctionKind::AsyncGenerator, + realm: context.realm().clone(), + }; JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), function_prototype, ObjectData::async_generator_function(function), ) } else { - let function = Function::new( - FunctionKind::Generator { - code, - environments: context.vm.environments.clone(), - home_object: None, - class_object: None, - script_or_module, - }, - context.realm().clone(), - ); + let function = OrdinaryFunction { + code, + environments: context.vm.environments.clone(), + home_object: None, + class_object: None, + script_or_module, + kind: FunctionKind::Generator, + realm: context.realm().clone(), + }; JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), function_prototype, @@ -1031,82 +1027,23 @@ impl JsObject { let this_function_object = self.clone(); let object = self.borrow(); - let function_object = object.as_function().expect("not a function"); - let realm = function_object.realm().clone(); - + let function = object.as_function().expect("not a function"); + let realm = function.realm().clone(); + + if let FunctionKind::Ordinary { .. } = function.kind() { + if function.code.is_class_constructor() { + return Err(JsNativeError::typ() + .with_message("class constructor cannot be invoked without 'new'") + .with_realm(realm) + .into()); + } + } context.enter_realm(realm); - let (code, mut environments, class_object, script_or_module) = match function_object.kind() - { - FunctionKind::Native { - function, - constructor, - } => { - let function = function.clone(); - let constructor = *constructor; - drop(object); - - context.vm.native_active_function = Some(this_function_object); - - let result = if constructor.is_some() { - function.call(&JsValue::undefined(), args, context) - } else { - function.call(this, args, context) - } - .map_err(|err| err.inject_realm(context.realm().clone())); - - context.vm.native_active_function = None; - - return result; - } - FunctionKind::Ordinary { - code, - environments, - class_object, - script_or_module, - .. - } => { - let code = code.clone(); - if code.is_class_constructor() { - return Err(JsNativeError::typ() - .with_message("class constructor cannot be invoked without 'new'") - .with_realm(context.realm().clone()) - .into()); - } - ( - code, - environments.clone(), - class_object.clone(), - script_or_module.clone(), - ) - } - FunctionKind::Async { - code, - environments, - class_object, - script_or_module, - .. - } - | FunctionKind::Generator { - code, - environments, - class_object, - script_or_module, - .. - } - | FunctionKind::AsyncGenerator { - code, - environments, - class_object, - script_or_module, - .. - } => ( - code.clone(), - environments.clone(), - class_object.clone(), - script_or_module.clone(), - ), - }; + let code = function.code.clone(); + let mut environments = function.environments.clone(); + let script_or_module = function.script_or_module.clone(); + let class_object = function.class_object.clone(); drop(object); @@ -1240,220 +1177,165 @@ impl JsObject { let this_function_object = self.clone(); let object = self.borrow(); - let function_object = object.as_function().expect("not a function"); - let realm = function_object.realm().clone(); + let function = object.as_function().expect("not a function"); + let realm = function.realm().clone(); context.enter_realm(realm); - match function_object.kind() { - FunctionKind::Native { - function, - constructor, - .. - } => { - let function = function.clone(); - let constructor = *constructor; - drop(object); - - context.vm.native_active_function = Some(this_function_object); - - let result = function - .call(this_target, args, context) - .map_err(|err| err.inject_realm(context.realm().clone())) - .and_then(|v| match v { - JsValue::Object(ref o) => Ok(o.clone()), - val => { - if constructor.expect("must be a constructor").is_base() - || val.is_undefined() - { - let prototype = get_prototype_from_constructor( - this_target, - StandardConstructors::object, - context, - )?; - Ok(Self::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::ordinary(), - )) - } else { - Err(JsNativeError::typ() - .with_message( - "derived constructor can only return an Object or undefined", - ) - .into()) - } - } - }); - - context.vm.native_active_function = None; - - result - } - FunctionKind::Ordinary { - code, - environments, - constructor_kind, - script_or_module, - .. - } => { - let code = code.clone(); - let mut environments = environments.clone(); - let script_or_module = script_or_module.clone(); - let constructor_kind = *constructor_kind; - drop(object); - - let this = if constructor_kind.is_base() { - // If the prototype of the constructor is not an object, then use the default object - // prototype as prototype for the new object - // see - // see - let prototype = get_prototype_from_constructor( - this_target, - StandardConstructors::object, - context, - )?; - let this = Self::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::ordinary(), - ); - - this.initialize_instance_elements(self, context)?; - - Some(this) - } else { - None - }; + let FunctionKind::Ordinary { + constructor_kind, .. + } = function.kind() + else { + unreachable!("not a constructor") + }; - let environments_len = environments.len(); - std::mem::swap(&mut environments, &mut context.vm.environments); + let code = function.code.clone(); + let mut environments = function.environments.clone(); + let script_or_module = function.script_or_module.clone(); + let constructor_kind = *constructor_kind; + drop(object); - let new_target = this_target.as_object().expect("must be object"); + let this = if constructor_kind.is_base() { + // If the prototype of the constructor is not an object, then use the default object + // prototype as prototype for the new object + // see + // see + let prototype = + get_prototype_from_constructor(this_target, StandardConstructors::object, context)?; + let this = Self::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype, + ObjectData::ordinary(), + ); + + this.initialize_instance_elements(self, context)?; + + Some(this) + } else { + None + }; - let mut last_env = code.compile_environments.len() - 1; + let environments_len = environments.len(); + std::mem::swap(&mut environments, &mut context.vm.environments); - if code.has_binding_identifier() { - let index = context - .vm - .environments - .push_lexical(code.compile_environments[last_env].clone()); - context - .vm - .environments - .put_lexical_value(index, 0, self.clone().into()); - last_env -= 1; - } + let new_target = this_target.as_object().expect("must be object"); - context.vm.environments.push_function( - code.compile_environments[last_env].clone(), - FunctionSlots::new( - this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { - ThisBindingStatus::Initialized(o.into()) - }), - self.clone(), - Some(new_target.clone()), - ), - ); - - let environment = context.vm.environments.current(); - - if code.has_parameters_env_bindings() { - last_env -= 1; - context - .vm - .environments - .push_lexical(code.compile_environments[last_env].clone()); - } + let mut last_env = code.compile_environments.len() - 1; - // Taken from: `FunctionDeclarationInstantiation` abstract function. - // - // Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation - // - // 22. If argumentsObjectNeeded is true, then - if code.needs_arguments_object() { - // a. If strict is true or simpleParameterList is false, then - // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). - // b. Else, - // i. NOTE: A mapped argument object is only provided for non-strict functions - // that don't have a rest parameter, any parameter - // default value initializers, or any destructured parameters. - // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). - let arguments_obj = if code.strict() || !code.params.is_simple() { - Arguments::create_unmapped_arguments_object(args, context) - } else { - let env = context.vm.environments.current(); - Arguments::create_mapped_arguments_object( - &this_function_object, - &code.params, - args, - env.declarative_expect(), - context, - ) - }; - - let env_index = context.vm.environments.len() as u32 - 1; - context - .vm - .environments - .put_lexical_value(env_index, 0, arguments_obj.into()); - } + if code.has_binding_identifier() { + let index = context + .vm + .environments + .push_lexical(code.compile_environments[last_env].clone()); + context + .vm + .environments + .put_lexical_value(index, 0, self.clone().into()); + last_env -= 1; + } - let argument_count = args.len(); - let parameters_count = code.params.as_ref().len(); + context.vm.environments.push_function( + code.compile_environments[last_env].clone(), + FunctionSlots::new( + this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { + ThisBindingStatus::Initialized(o.into()) + }), + self.clone(), + Some(new_target.clone()), + ), + ); - context.vm.push_frame( - CallFrame::new(code, script_or_module, Some(self.clone())) - .with_argument_count(argument_count as u32) - .with_env_fp(environments_len as u32), - ); + let environment = context.vm.environments.current(); - // Push function arguments to the stack. - for _ in argument_count..parameters_count { - context.vm.push(JsValue::undefined()); - } - context.vm.stack.extend(args.iter().rev().cloned()); + if code.has_parameters_env_bindings() { + last_env -= 1; + context + .vm + .environments + .push_lexical(code.compile_environments[last_env].clone()); + } - let record = context.run(); + // Taken from: `FunctionDeclarationInstantiation` abstract function. + // + // Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + // + // 22. If argumentsObjectNeeded is true, then + if code.needs_arguments_object() { + // a. If strict is true or simpleParameterList is false, then + // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). + // b. Else, + // i. NOTE: A mapped argument object is only provided for non-strict functions + // that don't have a rest parameter, any parameter + // default value initializers, or any destructured parameters. + // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). + let arguments_obj = if code.strict() || !code.params.is_simple() { + Arguments::create_unmapped_arguments_object(args, context) + } else { + let env = context.vm.environments.current(); + Arguments::create_mapped_arguments_object( + &this_function_object, + &code.params, + args, + env.declarative_expect(), + context, + ) + }; - context.vm.pop_frame(); + let env_index = context.vm.environments.len() as u32 - 1; + context + .vm + .environments + .put_lexical_value(env_index, 0, arguments_obj.into()); + } - std::mem::swap(&mut environments, &mut context.vm.environments); + let argument_count = args.len(); + let parameters_count = code.params.as_ref().len(); - let result = record - .consume() - .map_err(|err| err.inject_realm(context.realm().clone()))?; + context.vm.push_frame( + CallFrame::new(code, script_or_module, Some(self.clone())) + .with_argument_count(argument_count as u32) + .with_env_fp(environments_len as u32), + ); - if let Some(result) = result.as_object() { - Ok(result.clone()) - } else if let Some(this) = this { - Ok(this) - } else if !result.is_undefined() { - Err(JsNativeError::typ() - .with_message("derived constructor can only return an Object or undefined") - .into()) - } else { - let function_env = environment - .declarative_expect() - .kind() - .as_function() - .expect("must be function environment"); - function_env - .get_this_binding() - .map(|v| { - v.expect("constructors cannot be arrow functions") - .as_object() - .expect("this binding must be object") - .clone() - }) - .map_err(JsError::from) - } - } - FunctionKind::Generator { .. } - | FunctionKind::Async { .. } - | FunctionKind::AsyncGenerator { .. } => { - unreachable!("not a constructor") - } + // Push function arguments to the stack. + for _ in argument_count..parameters_count { + context.vm.push(JsValue::undefined()); + } + context.vm.stack.extend(args.iter().rev().cloned()); + + let record = context.run(); + + context.vm.pop_frame(); + + std::mem::swap(&mut environments, &mut context.vm.environments); + + let result = record + .consume() + .map_err(|err| err.inject_realm(context.realm().clone()))?; + + if let Some(result) = result.as_object() { + Ok(result.clone()) + } else if let Some(this) = this { + Ok(this) + } else if !result.is_undefined() { + Err(JsNativeError::typ() + .with_message("derived constructor can only return an Object or undefined") + .into()) + } else { + let function_env = environment + .declarative_expect() + .kind() + .as_function() + .expect("must be function environment"); + function_env + .get_this_binding() + .map(|v| { + v.expect("constructors cannot be arrow functions") + .as_object() + .expect("this binding must be object") + .clone() + }) + .map_err(JsError::from) } } } diff --git a/boa_engine/src/vm/opcode/call/mod.rs b/boa_engine/src/vm/opcode/call/mod.rs index 453965713a..2de9e45121 100644 --- a/boa_engine/src/vm/opcode/call/mod.rs +++ b/boa_engine/src/vm/opcode/call/mod.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::{function::FunctionKind, promise::PromiseCapability, Promise}, + builtins::{promise::PromiseCapability, Promise}, error::JsNativeError, module::{ModuleKind, Referrer}, object::FunctionObjectBuilder, @@ -38,26 +38,18 @@ impl CallEval { let func = context.vm.pop(); let this = context.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()); - } + let Some(object) = func.as_object() else { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()); }; // A native function with the name "eval" implies, that is this the built-in eval function. - let eval = object - .borrow() - .as_function() - .map(|f| matches!(f.kind(), FunctionKind::Native { .. })) - .unwrap_or_default(); - - let strict = context.vm.frame().code_block.strict(); + let eval = object.borrow().is_native_function(); if eval { if let Some(x) = arguments.get(0) { + let strict = context.vm.frame().code_block.strict(); let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; context.vm.push(result); } else { @@ -132,26 +124,18 @@ impl Operation for CallEvalSpread { let func = context.vm.pop(); let this = context.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()); - } + let Some(object) = func.as_object() else { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()); }; // A native function with the name "eval" implies, that is this the built-in eval function. - let eval = object - .borrow() - .as_function() - .map(|f| matches!(f.kind(), FunctionKind::Native { .. })) - .unwrap_or_default(); - - let strict = context.vm.frame().code_block.strict(); + let eval = object.borrow().is_native_function(); if eval { if let Some(x) = arguments.get(0) { + let strict = context.vm.frame().code_block.strict(); let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; context.vm.push(result); } else { @@ -196,13 +180,10 @@ impl Call { let func = context.vm.pop(); let this = context.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()); - } + let Some(object) = func.as_object() else { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()); }; let result = object.__call__(&this, &arguments, context)?; @@ -269,13 +250,10 @@ impl Operation for CallSpread { let func = context.vm.pop(); let this = context.vm.pop(); - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()) - } + let Some(object) = func.as_object() else { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()); }; let result = object.__call__(&this, &arguments, context)?;