From 4f96143b4f6f9b375415ee264e66a54aea4dc1e1 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Fri, 29 Sep 2023 07:47:02 +0200 Subject: [PATCH] Separate native and JavaScript functions --- boa_cli/src/debug/function.rs | 18 +- boa_engine/src/builtins/error/type.rs | 17 +- boa_engine/src/builtins/function/mod.rs | 268 ++++------ boa_engine/src/builtins/mod.rs | 52 +- .../src/object/internal_methods/function.rs | 22 +- boa_engine/src/object/mod.rs | 101 ++-- boa_engine/src/object/operations.rs | 6 + boa_engine/src/vm/code_block.rs | 487 ++++++++---------- boa_engine/src/vm/opcode/call/mod.rs | 20 +- 9 files changed, 416 insertions(+), 575 deletions(-) diff --git a/boa_cli/src/debug/function.rs b/boa_cli/src/debug/function.rs index 40a6dcc3aea..aec211ceb2c 100644 --- a/boa_cli/src/debug/function.rs +++ b/boa_cli/src/debug/function.rs @@ -82,13 +82,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())); @@ -115,12 +113,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(code.to_interned_string(context.interner()).into()) } @@ -129,12 +125,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 5466c5ca534..c5acd59353d 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, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectKind}, @@ -135,13 +132,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 1abfcc9ba52..88ebf9b7f96 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,43 +189,26 @@ 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). @@ -310,33 +216,38 @@ unsafe impl Trace for FunctionKind { /// #[derive(Debug, Trace, Finalize)] pub struct Function { - kind: FunctionKind, - realm: Realm, + /// 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`]. + /// 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. @@ -453,11 +342,6 @@ impl Function { pub(crate) fn kind_mut(&mut self) -> &mut FunctionKind { &mut self.kind } - - /// Check if `self` is [`FunctionKind::Native`]. - pub(crate) const fn is_native(&self) -> bool { - matches!(self.kind(), FunctionKind::Native { .. }) - } } /// The internal representation of a `Function` object. @@ -966,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(), + ); + } - match function.kind { - FunctionKind::Native { .. } | FunctionKind::Ordinary { .. } => { - Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into()) + let object = object.borrow(); + let function = object + .as_function() + .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?; + + let code = function.codeblock(); + + 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 fe776aef1ce..25cdd8132a6 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("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/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs index 011e68c4e06..c40cc740916 100644 --- a/boa_engine/src/object/internal_methods/function.rs +++ b/boa_engine/src/object/internal_methods/function.rs @@ -1,9 +1,8 @@ use crate::{ - builtins::function::FunctionKind, context::intrinsics::StandardConstructors, object::{ internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, - JsObject, ObjectData, + JsObject, ObjectData, ObjectKind, }, Context, JsNativeError, JsResult, JsValue, }; @@ -92,17 +91,17 @@ pub(crate) fn native_function_call( ) -> JsResult { let this_function_object = obj.clone(); let object = obj.borrow(); - let function_object = object.as_function().expect("not a function"); - let mut realm = function_object.realm().clone(); - let FunctionKind::Native { + let ObjectKind::NativeFunction { function, constructor, - } = function_object.kind() + realm, + } = object.kind() else { - unreachable!("the object should be a native function object") + unreachable!("the object should be a native function object"); }; + let mut realm = realm.clone(); let function = function.clone(); let constructor = *constructor; drop(object); @@ -138,18 +137,17 @@ fn native_function_construct( ) -> JsResult { let this_function_object = obj.clone(); let object = obj.borrow(); - let function_object = object.as_function().expect("not a function"); - let mut realm = function_object.realm().clone(); - let FunctionKind::Native { + let ObjectKind::NativeFunction { function, constructor, - .. - } = function_object.kind() + 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); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 954864372cf..f9737ab8d51 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -44,7 +44,7 @@ use crate::{ array_buffer::ArrayBuffer, async_generator::AsyncGenerator, error::ErrorKind, - function::{arguments::Arguments, FunctionKind}, + function::arguments::Arguments, function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function}, generator::Generator, iterable::AsyncFromSyncIterator, @@ -346,6 +346,18 @@ pub enum ObjectKind { /// The `GeneratorFunction` object kind. GeneratorFunction(Function), + /// 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), @@ -450,6 +462,10 @@ unsafe impl Trace for ObjectKind { Self::Function(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), @@ -521,13 +537,8 @@ impl ObjectData { /// Create the `AsyncGeneratorFunction` object data #[must_use] pub fn async_generator_function(function: Function) -> Self { - let internal_methods = if function.is_native() { - &NATIVE_FUNCTION_INTERNAL_METHODS - } else { - &FUNCTION_INTERNAL_METHODS - }; Self { - internal_methods, + internal_methods: &FUNCTION_INTERNAL_METHODS, kind: ObjectKind::GeneratorFunction(function), } } @@ -643,11 +654,10 @@ impl ObjectData { /// Create the `Function` object data #[must_use] pub fn function(function: Function, constructor: bool) -> Self { - let internal_methods = match (constructor, function.is_native()) { - (false, false) => &FUNCTION_INTERNAL_METHODS, - (false, true) => &NATIVE_FUNCTION_INTERNAL_METHODS, - (true, false) => &CONSTRUCTOR_INTERNAL_METHODS, - (true, true) => &NATIVE_CONSTRUCTOR_INTERNAL_METHODS, + let internal_methods = if constructor { + &CONSTRUCTOR_INTERNAL_METHODS + } else { + &FUNCTION_INTERNAL_METHODS }; Self { @@ -656,6 +666,25 @@ impl ObjectData { } } + /// Create the `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, + }, + } + } + /// Create the `BoundFunction` object data #[must_use] pub fn bound_function(bound_function: BoundFunction, constructor: bool) -> Self { @@ -681,13 +710,8 @@ impl ObjectData { /// Create the `GeneratorFunction` object data #[must_use] pub fn generator_function(function: Function) -> Self { - let internal_methods = if function.is_native() { - &NATIVE_FUNCTION_INTERNAL_METHODS - } else { - &FUNCTION_INTERNAL_METHODS - }; Self { - internal_methods, + internal_methods: &FUNCTION_INTERNAL_METHODS, kind: ObjectKind::GeneratorFunction(function), } } @@ -949,6 +973,7 @@ impl Debug for ObjectKind { Self::BoundFunction(_) => "BoundFunction", Self::Generator(_) => "Generator", Self::GeneratorFunction(_) => "GeneratorFunction", + Self::NativeFunction { .. } => "NativeFunction", Self::RegExp(_) => "RegExp", Self::RegExpStringIterator(_) => "RegExpStringIterator", Self::Map(_) => "Map", @@ -1283,7 +1308,10 @@ impl Object { #[inline] #[must_use] pub const fn is_function(&self) -> bool { - matches!(self.kind, ObjectKind::Function(_)) + matches!( + self.kind, + ObjectKind::Function(_) | ObjectKind::GeneratorFunction(_) + ) } /// Gets the function data if the object is a `Function`. @@ -1736,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] @@ -2136,15 +2171,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()], ); @@ -2550,15 +2582,6 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> { /// Build the constructor function object. #[must_use] pub fn build(mut self) -> JsFunction { - // 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) @@ -2590,7 +2613,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 57ddbd16d55..a2a96c3cd1a 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -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 { @@ -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); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index d93693f4556..3015dc8f2b2 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -760,30 +760,29 @@ 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(), - ) + Function { + 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(), + Function { + 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); @@ -839,28 +838,25 @@ 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 = Function { + 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); @@ -945,32 +941,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 = Function { + 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 = Function { + 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, @@ -1013,64 +1007,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 { .. } => { - unreachable!("should be handled in native function call internal method") - } - 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); @@ -1204,189 +1157,175 @@ 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 { .. } => { - unreachable!("should be handled in native function construct internal method") - } - 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()), - ), - ); - - 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()), + ), + ); - let has_binding_identifier = code.has_binding_identifier(); + if code.has_parameters_env_bindings() { + last_env -= 1; + context + .vm + .environments + .push_lexical(code.compile_environments[last_env].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), - ); + // 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, + ) + }; - // 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 env_index = context.vm.environments.len() as u32 - 1; + context + .vm + .environments + .put_lexical_value(env_index, 0, arguments_obj.into()); + } - let record = context.run(); + let argument_count = args.len(); + let parameters_count = code.params.as_ref().len(); - context.vm.pop_frame(); + let has_binding_identifier = code.has_binding_identifier(); - std::mem::swap(&mut environments, &mut context.vm.environments); + 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 = if has_binding_identifier { - environments.truncate(environments_len + 2); - let environment = environments.pop(); - environments.pop(); - environment - } else { - environments.truncate(environments_len + 1); - environments.pop() - }; + // 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 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) - } - } - FunctionKind::Generator { .. } - | FunctionKind::Async { .. } - | FunctionKind::AsyncGenerator { .. } => { - unreachable!("not a constructor") - } + let record = context.run(); + + context.vm.pop_frame(); + + std::mem::swap(&mut environments, &mut context.vm.environments); + + let environment = if has_binding_identifier { + environments.truncate(environments_len + 2); + let environment = environments.pop(); + environments.pop(); + environment + } else { + environments.truncate(environments_len + 1); + environments.pop() + }; + + 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 1c279533bbc..98332e6e37e 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, @@ -49,16 +49,11 @@ impl Operation for CallEval { }; // 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 { @@ -120,16 +115,11 @@ impl Operation for CallEvalSpread { }; // 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 {