Browse Source

Separate native and JavaScript functions (#3322)

pull/3332/head
Haled Odat 12 months ago committed by GitHub
parent
commit
744536763d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      boa_cli/src/debug/function.rs
  2. 13
      boa_engine/src/builtins/error/type.rs
  3. 245
      boa_engine/src/builtins/function/mod.rs
  4. 48
      boa_engine/src/builtins/mod.rs
  5. 2
      boa_engine/src/builtins/regexp/tests.rs
  6. 8
      boa_engine/src/object/builtins/jsfunction.rs
  7. 6
      boa_engine/src/object/internal_methods/bound_function.rs
  8. 146
      boa_engine/src/object/internal_methods/function.rs
  9. 47
      boa_engine/src/object/internal_methods/mod.rs
  10. 11
      boa_engine/src/object/internal_methods/proxy.rs
  11. 31
      boa_engine/src/object/jsobject.rs
  12. 124
      boa_engine/src/object/mod.rs
  13. 32
      boa_engine/src/object/operations.rs
  14. 236
      boa_engine/src/vm/code_block.rs
  15. 42
      boa_engine/src/vm/opcode/call/mod.rs

18
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 { let Some(function) = object.as_function() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("expected function object") .with_message("expected an ordinary function object")
.into()); .into());
}; };
let code = function.codeblock().ok_or_else(|| { let code = function.codeblock();
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
let mut graph = Graph::new(direction); let mut graph = Graph::new(direction);
code.to_graph(context.interner(), graph.subgraph(String::default())); 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 object = object.borrow();
let Some(function) = object.as_function() else { let Some(function) = object.as_function() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("expected function object") .with_message("expected an ordinary function object")
.into()); .into());
}; };
let code = function.codeblock().ok_or_else(|| { let code = function.codeblock();
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
Ok(js_string!(code.to_interned_string(context.interner())).into()) 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 object = object.borrow();
let Some(function) = object.as_function() else { let Some(function) = object.as_function() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("expected function object") .with_message("expected an ordinary function object")
.into()); .into());
}; };
let code = function.codeblock().ok_or_else(|| { let code = function.codeblock();
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
code.set_traceable(value); code.set_traceable(value);
Ok(()) Ok(())
} }

13
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 //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
use crate::{ use crate::{
builtins::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject},
function::{Function, FunctionKind},
BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject,
},
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError, error::JsNativeError,
js_string, js_string,
@ -136,13 +133,11 @@ impl IntrinsicObject for ThrowTypeError {
let mut obj = obj.borrow_mut(); let mut obj = obj.borrow_mut();
obj.extensible = false; obj.extensible = false;
*obj.kind_mut() = ObjectKind::Function(Function::new( *obj.kind_mut() = ObjectKind::NativeFunction {
FunctionKind::Native {
function: NativeFunction::from_fn_ptr(throw_type_error), function: NativeFunction::from_fn_ptr(throw_type_error),
constructor: None, constructor: None,
}, realm: realm.clone(),
realm.clone(), }
));
} }
fn get(intrinsics: &Intrinsics) -> JsObject { fn get(intrinsics: &Intrinsics) -> JsObject {

245
boa_engine/src/builtins/function/mod.rs

@ -18,8 +18,7 @@ use crate::{
environments::{EnvironmentStack, PrivateEnvironment}, environments::{EnvironmentStack, PrivateEnvironment},
error::JsNativeError, error::JsNativeError,
js_string, js_string,
native_function::NativeFunction, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData},
object::{JsFunction, PrivateElement, PrivateName}, object::{JsFunction, PrivateElement, PrivateName},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
@ -146,107 +145,31 @@ unsafe impl Trace for ClassFieldDefinition {
#[derive(Finalize)] #[derive(Finalize)]
pub(crate) enum FunctionKind { 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<ConstructorKind>,
},
/// A bytecode function. /// A bytecode function.
Ordinary { Ordinary {
/// The code block containing the compiled function.
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: EnvironmentStack,
/// The `[[ConstructorKind]]` internal slot. /// The `[[ConstructorKind]]` internal slot.
constructor_kind: ConstructorKind, constructor_kind: ConstructorKind,
/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
/// The `[[Fields]]` internal slot. /// The `[[Fields]]` internal slot.
fields: ThinVec<ClassFieldDefinition>, fields: ThinVec<ClassFieldDefinition>,
/// The `[[PrivateMethods]]` internal slot. /// The `[[PrivateMethods]]` internal slot.
private_methods: ThinVec<(PrivateName, PrivateElement)>, private_methods: ThinVec<(PrivateName, PrivateElement)>,
/// The class object that this function is associated with.
class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
}, },
/// A bytecode async function. /// A bytecode async function.
Async { Async,
/// The code block containing the compiled function.
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: EnvironmentStack,
/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
/// The class object that this function is associated with.
class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
},
/// A bytecode generator function. /// A bytecode generator function.
Generator { Generator,
/// The code block containing the compiled function.
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: EnvironmentStack,
/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
/// The class object that this function is associated with.
class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
},
/// A bytecode async generator function. /// A bytecode async generator function.
AsyncGenerator { AsyncGenerator,
/// The code block containing the compiled function.
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: EnvironmentStack,
/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
/// The class object that this function is associated with.
class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
script_or_module: Option<ActiveRunnable>,
},
} }
impl fmt::Debug for FunctionKind { impl fmt::Debug for FunctionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Native {
function,
constructor,
} => f
.debug_struct("FunctionKind::Native")
.field("function", &function)
.field("constructor", &constructor)
.finish(),
Self::Ordinary { .. } => f Self::Ordinary { .. } => f
.debug_struct("FunctionKind::Ordinary") .debug_struct("FunctionKind::Ordinary")
.finish_non_exhaustive(), .finish_non_exhaustive(),
@ -266,77 +189,65 @@ impl fmt::Debug for FunctionKind {
unsafe impl Trace for FunctionKind { unsafe impl Trace for FunctionKind {
custom_trace! {this, { custom_trace! {this, {
match this { match this {
Self::Native { function, .. } => {mark(function)}
Self::Ordinary { Self::Ordinary {
code,
environments,
home_object,
fields, fields,
private_methods, private_methods,
class_object,
script_or_module,
.. ..
} => { } => {
mark(code);
mark(environments);
mark(home_object);
for elem in fields { for elem in fields {
mark(elem); mark(elem);
} }
for (_, elem) in private_methods { for (_, elem) in private_methods {
mark(elem); 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 /// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code
/// (AST Node). /// (AST Node).
/// ///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects> /// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub struct Function { pub struct OrdinaryFunction {
kind: FunctionKind, /// The code block containing the compiled function.
realm: Realm, pub(crate) code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
pub(crate) environments: EnvironmentStack,
/// The `[[HomeObject]]` internal slot.
pub(crate) home_object: Option<JsObject>,
/// The class object that this function is associated with.
pub(crate) class_object: Option<JsObject>,
/// The `[[ScriptOrModule]]` internal slot.
pub(crate) script_or_module: Option<ActiveRunnable>,
/// The [`Realm`] the function is defined in.
pub(crate) realm: Realm,
/// The kind of ordinary function.
pub(crate) kind: FunctionKind,
} }
impl Function { impl OrdinaryFunction {
/// Returns the codeblock of the function, or `None` if the function is a [`NativeFunction`]. /// Returns the codeblock of the function.
#[must_use] #[must_use]
pub fn codeblock(&self) -> Option<&CodeBlock> { pub fn codeblock(&self) -> &CodeBlock {
match &self.kind { &self.code
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 }
} }
/// Push a private environment to the function. /// Push a private environment to the function.
pub(crate) fn push_private_environment(&mut self, environment: Gc<PrivateEnvironment>) { pub(crate) fn push_private_environment(&mut self, environment: Gc<PrivateEnvironment>) {
if let FunctionKind::Ordinary { environments, .. } = &mut self.kind { self.environments.push_private(environment);
environments.push_private(environment);
}
} }
/// Returns true if the function object is a derived constructor. /// 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. /// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value.
pub(crate) fn in_class_field_initializer(&self) -> bool { pub(crate) fn in_class_field_initializer(&self) -> bool {
if let FunctionKind::Ordinary { code, .. } = &self.kind { self.code.in_class_field_initializer()
code.in_class_field_initializer()
} else {
false
}
} }
/// Returns a reference to the function `[[HomeObject]]` slot if present. /// Returns a reference to the function `[[HomeObject]]` slot if present.
pub(crate) const fn get_home_object(&self) -> Option<&JsObject> { pub(crate) const fn get_home_object(&self) -> Option<&JsObject> {
match &self.kind { self.home_object.as_ref()
FunctionKind::Ordinary { home_object, .. }
| FunctionKind::Async { home_object, .. }
| FunctionKind::Generator { home_object, .. }
| FunctionKind::AsyncGenerator { home_object, .. } => home_object.as_ref(),
FunctionKind::Native { .. } => None,
}
} }
/// Sets the `[[HomeObject]]` slot if present. /// Sets the `[[HomeObject]]` slot if present.
pub(crate) fn set_home_object(&mut self, object: JsObject) { pub(crate) fn set_home_object(&mut self, object: JsObject) {
match &mut self.kind { self.home_object = Some(object);
FunctionKind::Ordinary { home_object, .. }
| FunctionKind::Async { home_object, .. }
| FunctionKind::Generator { home_object, .. }
| FunctionKind::AsyncGenerator { home_object, .. } => *home_object = Some(object),
FunctionKind::Native { .. } => {}
}
} }
/// Returns the values of the `[[Fields]]` internal slot. /// Returns the values of the `[[Fields]]` internal slot.
@ -429,13 +324,7 @@ impl Function {
/// Sets the class object. /// Sets the class object.
pub(crate) fn set_class_object(&mut self, object: JsObject) { pub(crate) fn set_class_object(&mut self, object: JsObject) {
match &mut self.kind { self.class_object = Some(object);
FunctionKind::Ordinary { class_object, .. }
| FunctionKind::Async { class_object, .. }
| FunctionKind::Generator { class_object, .. }
| FunctionKind::AsyncGenerator { class_object, .. } => *class_object = Some(object),
FunctionKind::Native { .. } => {}
}
} }
/// Gets the `Realm` from where this function originates. /// Gets the `Realm` from where this function originates.
@ -961,12 +850,25 @@ impl BuiltInFunctionObject {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> { fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
let object = this.as_object().map(JsObject::borrow); // 1. Let func be the this value.
let function = object let func = this;
.as_deref()
.and_then(Object::as_function) // 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
.ok_or_else(|| JsNativeError::typ().with_message("Not a function"))?; // 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());
};
if object.borrow().is_native_function() {
let name = { let name = {
// Is there a case here where if there is no name field on a value // 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? // name should default to None? Do all functions have names set?
@ -975,30 +877,37 @@ impl BuiltInFunctionObject {
.expect("checked that `this` was an object above") .expect("checked that `this` was an object above")
.get(utf16!("name"), &mut *context)?; .get(utf16!("name"), &mut *context)?;
if value.is_null_or_undefined() { if value.is_null_or_undefined() {
None js_string!()
} else { } else {
Some(value.to_string(context)?) 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 name = name let code = function.codeblock();
.filter(|n| !n.is_empty())
.unwrap_or_else(|| "anonymous".into());
match function.kind { let prefix = match function.kind {
FunctionKind::Native { .. } | FunctionKind::Ordinary { .. } => { FunctionKind::Ordinary { .. } => {
Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into()) utf16!("function ")
} }
FunctionKind::Async { .. } => { FunctionKind::Async { .. } => {
Ok(js_string!(utf16!("[AsyncFunction: "), &name, utf16!("]")).into()) utf16!("async function ")
} }
FunctionKind::Generator { .. } => { FunctionKind::Generator { .. } => {
Ok(js_string!(utf16!("[GeneratorFunction: "), &name, utf16!("]")).into()) utf16!("function* ")
}
FunctionKind::AsyncGenerator { .. } => {
Ok(js_string!(utf16!("[AsyncGeneratorFunction: "), &name, utf16!("]")).into())
}
} }
FunctionKind::AsyncGenerator { .. } => utf16!("async function* "),
};
Ok(js_string!(prefix, code.name(), utf16!("() { [native code] }")).into())
} }
/// `Function.prototype [ @@hasInstance ] ( V )` /// `Function.prototype [ @@hasInstance ] ( V )`

48
boa_engine/src/builtins/mod.rs

@ -543,15 +543,10 @@ impl ApplyToObject for OrdinaryFunction {
impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> { impl<S: ApplyToObject + IsConstructor> ApplyToObject for Callable<S> {
fn apply_to(self, object: &mut BuiltInObjectInitializer) { fn apply_to(self, object: &mut BuiltInObjectInitializer) {
let function = ObjectData::function( let function = ObjectData::native_function(
function::Function::new( NativeFunction::from_fn_ptr(self.function),
function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function),
constructor: S::IS_CONSTRUCTOR.then_some(function::ConstructorKind::Base),
},
self.realm,
),
S::IS_CONSTRUCTOR, S::IS_CONSTRUCTOR,
self.realm,
); );
object.set_data(function); object.set_data(function);
object.insert( object.insert(
@ -815,13 +810,11 @@ impl BuiltInConstructorWithPrototype<'_> {
} }
fn build(mut self) { fn build(mut self) {
let function = function::Function::new( let function = ObjectKind::NativeFunction {
function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function), function: NativeFunction::from_fn_ptr(self.function),
constructor: (true).then_some(function::ConstructorKind::Base), constructor: Some(function::ConstructorKind::Base),
}, realm: self.realm.clone(),
self.realm.clone(), };
);
let length = self.length; let length = self.length;
let name = self.name.clone(); let name = self.name.clone();
@ -852,7 +845,7 @@ impl BuiltInConstructorWithPrototype<'_> {
} }
let mut object = self.object.borrow_mut(); let mut object = self.object.borrow_mut();
*object.kind_mut() = ObjectKind::Function(function); *object.kind_mut() = function;
object object
.properties_mut() .properties_mut()
.shape .shape
@ -867,13 +860,11 @@ impl BuiltInConstructorWithPrototype<'_> {
} }
fn build_without_prototype(mut self) { fn build_without_prototype(mut self) {
let function = function::Function::new( let function = ObjectKind::NativeFunction {
function::FunctionKind::Native {
function: NativeFunction::from_fn_ptr(self.function), function: NativeFunction::from_fn_ptr(self.function),
constructor: (true).then_some(function::ConstructorKind::Base), constructor: Some(function::ConstructorKind::Base),
}, realm: self.realm.clone(),
self.realm.clone(), };
);
let length = self.length; let length = self.length;
let name = self.name.clone(); let name = self.name.clone();
@ -881,7 +872,7 @@ impl BuiltInConstructorWithPrototype<'_> {
self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE);
let mut object = self.object.borrow_mut(); let mut object = self.object.borrow_mut();
*object.kind_mut() = ObjectKind::Function(function); *object.kind_mut() = function;
object object
.properties_mut() .properties_mut()
.shape .shape
@ -922,15 +913,12 @@ impl BuiltInCallable<'_> {
} }
fn build(self) -> JsFunction { 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( 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)], vec![JsValue::new(self.length), JsValue::new(self.name)],
); );

2
boa_engine/src/builtins/regexp/tests.rs

@ -42,7 +42,7 @@ fn species() {
// symbol-species // symbol-species
TestAction::assert_eq("descriptor.set", JsValue::undefined()), TestAction::assert_eq("descriptor.set", JsValue::undefined()),
TestAction::assert_with_op("accessor", |v, _| { 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.enumerable"),
TestAction::assert("descriptor.configurable"), TestAction::assert("descriptor.configurable"),

8
boa_engine/src/object/builtins/jsfunction.rs

@ -1,7 +1,9 @@
//! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object //! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object
use crate::{ use crate::{
object::{ object::{
internal_methods::function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS}, internal_methods::function::{
NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS,
},
JsObject, JsObjectType, Object, JsObject, JsObjectType, Object,
}, },
value::TryFromJs, value::TryFromJs,
@ -32,9 +34,9 @@ impl JsFunction {
inner: JsObject::from_object_and_vtable( inner: JsObject::from_object_and_vtable(
Object::default(), Object::default(),
if constructor { if constructor {
&CONSTRUCTOR_INTERNAL_METHODS &NATIVE_CONSTRUCTOR_INTERNAL_METHODS
} else { } else {
&FUNCTION_INTERNAL_METHODS &NATIVE_FUNCTION_INTERNAL_METHODS
}, },
), ),
} }

6
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 /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods { InternalObjectMethods {
__call__: Some(bound_function_exotic_call), __call__: bound_function_exotic_call,
..ORDINARY_INTERNAL_METHODS ..ORDINARY_INTERNAL_METHODS
}; };
pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods { InternalObjectMethods {
__call__: Some(bound_function_exotic_call), __call__: bound_function_exotic_call,
__construct__: Some(bound_function_exotic_construct), __construct__: bound_function_exotic_construct,
..ORDINARY_INTERNAL_METHODS ..ORDINARY_INTERNAL_METHODS
}; };

146
boa_engine/src/object/internal_methods/function.rs

@ -1,11 +1,14 @@
use crate::{ use crate::{
context::intrinsics::StandardConstructors,
object::{ object::{
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, 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. /// Definitions of the internal object methods for function objects.
/// ///
/// More information: /// More information:
@ -13,14 +16,13 @@ use crate::{
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call), __call__: function_call,
__construct__: None,
..ORDINARY_INTERNAL_METHODS ..ORDINARY_INTERNAL_METHODS
}; };
pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call), __call__: function_call,
__construct__: Some(function_construct), __construct__: function_construct,
..ORDINARY_INTERNAL_METHODS ..ORDINARY_INTERNAL_METHODS
}; };
@ -31,7 +33,6 @@ pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = Internal
/// Panics if the object is currently mutably borrowed. /// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist> // <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
fn function_call( fn function_call(
obj: &JsObject, obj: &JsObject,
this: &JsValue, this: &JsValue,
@ -47,7 +48,6 @@ fn function_call(
/// ///
/// Panics if the object is currently mutably borrowed. /// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget> // <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
fn function_construct( fn function_construct(
obj: &JsObject, obj: &JsObject,
args: &[JsValue], args: &[JsValue],
@ -56,3 +56,133 @@ fn function_construct(
) -> JsResult<JsObject> { ) -> JsResult<JsObject> {
obj.construct_internal(args, &new_target.clone().into(), context) 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.
///
// <https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist>
#[track_caller]
pub(crate) fn native_function_call(
obj: &JsObject,
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
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.
// <https://tc39.es/ecma262/#sec-built-in-function-objects-construct-argumentslist-newtarget>
#[track_caller]
fn native_function_construct(
obj: &JsObject,
args: &[JsValue],
new_target: &JsObject,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
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
}

47
boa_engine/src/object/internal_methods/mod.rs

@ -11,7 +11,7 @@ use crate::{
object::JsObject, object::JsObject,
property::{DescriptorKind, PropertyDescriptor, PropertyKey}, property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::JsValue, value::JsValue,
Context, JsResult, Context, JsNativeError, JsResult,
}; };
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -225,11 +225,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__call__", "object"); let _timer = Profiler::global().start_event("Object::__call__", "object");
self.vtable() (self.vtable().__call__)(self, this, args, context)
.__call__
.expect("called `[[Call]]` for object without a `[[Call]]` internal method")(
self, this, args, context,
)
} }
/// Internal method `[[Construct]]` /// Internal method `[[Construct]]`
@ -248,11 +244,7 @@ impl JsObject {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<Self> { ) -> JsResult<Self> {
let _timer = Profiler::global().start_event("Object::__construct__", "object"); let _timer = Profiler::global().start_event("Object::__construct__", "object");
self.vtable() (self.vtable().__construct__)(self, args, new_target, context)
.__construct__
.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")(
self, args, new_target, context,
)
} }
} }
@ -279,8 +271,8 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj
__set__: ordinary_set, __set__: ordinary_set,
__delete__: ordinary_delete, __delete__: ordinary_delete,
__own_property_keys__: ordinary_own_property_keys, __own_property_keys__: ordinary_own_property_keys,
__call__: None, __call__: non_existant_call,
__construct__: None, __construct__: non_existant_construct,
}; };
/// The internal representation of the internal methods of a `JsObject`. /// 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<bool>, fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context<'_>) -> JsResult<bool>,
pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context<'_>) -> JsResult<bool>, pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context<'_>) -> JsResult<bool>,
pub(crate) __own_property_keys__: fn(&JsObject, &mut Context<'_>) -> JsResult<Vec<PropertyKey>>, pub(crate) __own_property_keys__: fn(&JsObject, &mut Context<'_>) -> JsResult<Vec<PropertyKey>>,
pub(crate) __call__: pub(crate) __call__: fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>,
Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>>,
pub(crate) __construct__: pub(crate) __construct__:
Option<fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult<JsObject>>, fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult<JsObject>,
} }
/// Abstract operation `OrdinaryGetPrototypeOf`. /// Abstract operation `OrdinaryGetPrototypeOf`.
@ -919,3 +910,27 @@ where
// b. Set proto to realm's intrinsic object named intrinsicDefaultProto. // b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
Ok(default(realm.intrinsics().constructors()).prototype()) Ok(default(realm.intrinsics().constructors()).prototype())
} }
pub(crate) fn non_existant_call(
_obj: &JsObject,
_this: &JsValue,
_args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
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<JsObject> {
Err(JsNativeError::typ()
.with_message("object is not constructable")
.with_realm(context.realm().clone())
.into())
}

11
boa_engine/src/object/internal_methods/proxy.rs

@ -9,6 +9,8 @@ use crate::{
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use super::ORDINARY_INTERNAL_METHODS;
/// Definitions of the internal object methods for array exotic objects. /// Definitions of the internal object methods for array exotic objects.
/// ///
/// More information: /// More information:
@ -28,20 +30,19 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods =
__set__: proxy_exotic_set, __set__: proxy_exotic_set,
__delete__: proxy_exotic_delete, __delete__: proxy_exotic_delete,
__own_property_keys__: proxy_exotic_own_property_keys, __own_property_keys__: proxy_exotic_own_property_keys,
__call__: None, ..ORDINARY_INTERNAL_METHODS
__construct__: None,
}; };
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods = pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods =
InternalObjectMethods { InternalObjectMethods {
__call__: Some(proxy_exotic_call), __call__: proxy_exotic_call,
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
}; };
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods = pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods =
InternalObjectMethods { InternalObjectMethods {
__call__: Some(proxy_exotic_call), __call__: proxy_exotic_call,
__construct__: Some(proxy_exotic_construct), __construct__: proxy_exotic_construct,
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
}; };

31
boa_engine/src/object/jsobject.rs

@ -3,7 +3,10 @@
//! The `JsObject` is a garbage collected Object. //! The `JsObject` is a garbage collected Object.
use super::{ use super::{
internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS}, internal_methods::{
non_existant_call, non_existant_construct, InternalObjectMethods,
ARRAY_EXOTIC_INTERNAL_METHODS,
},
shape::RootShape, shape::RootShape,
JsPrototype, NativeObject, Object, PrivateName, PropertyMap, JsPrototype, NativeObject, Object, PrivateName, PropertyMap,
}; };
@ -488,8 +491,20 @@ impl JsObject {
#[inline] #[inline]
#[must_use] #[must_use]
#[track_caller] #[track_caller]
pub fn is_function(&self) -> bool { pub fn is_ordinary_function(&self) -> bool {
self.borrow().is_function() 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. /// 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 /// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline] #[inline]
#[must_use] #[must_use]
#[track_caller] #[allow(clippy::fn_address_comparisons)]
pub fn is_callable(&self) -> bool { 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. /// 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 /// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline] #[inline]
#[must_use] #[must_use]
#[track_caller] #[allow(clippy::fn_address_comparisons)]
pub fn is_constructor(&self) -> bool { 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 /// 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 ptr: *const _ = self.as_ref();
let obj = self.borrow(); let obj = self.borrow();
let kind = obj.kind(); let kind = obj.kind();
if obj.is_function() { if obj.is_ordinary_function() {
let name_prop = obj let name_prop = obj
.properties() .properties()
.get(&PropertyKey::String(JsString::from("name"))); .get(&PropertyKey::String(JsString::from("name")));

124
boa_engine/src/object/mod.rs

@ -14,7 +14,10 @@ use self::{
bound_function::{ bound_function::{
BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS, 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, immutable_prototype::IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS,
integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS,
module_namespace::MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS, module_namespace::MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS,
@ -41,8 +44,8 @@ use crate::{
array_buffer::ArrayBuffer, array_buffer::ArrayBuffer,
async_generator::AsyncGenerator, async_generator::AsyncGenerator,
error::ErrorKind, error::ErrorKind,
function::{arguments::Arguments, FunctionKind}, function::arguments::Arguments,
function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function}, function::{arguments::ParameterMap, BoundFunction, ConstructorKind, OrdinaryFunction},
generator::Generator, generator::Generator,
iterable::AsyncFromSyncIterator, iterable::AsyncFromSyncIterator,
map::ordered_map::OrderedMap, map::ordered_map::OrderedMap,
@ -297,7 +300,7 @@ pub enum ObjectKind {
AsyncGenerator(AsyncGenerator), AsyncGenerator(AsyncGenerator),
/// The `AsyncGeneratorFunction` object kind. /// The `AsyncGeneratorFunction` object kind.
AsyncGeneratorFunction(Function), AsyncGeneratorFunction(OrdinaryFunction),
/// The `Array` object kind. /// The `Array` object kind.
Array, Array,
@ -333,7 +336,7 @@ pub enum ObjectKind {
ForInIterator(ForInIterator), ForInIterator(ForInIterator),
/// The `Function` object kind. /// The `Function` object kind.
Function(Function), OrdinaryFunction(OrdinaryFunction),
/// The `BoundFunction` object kind. /// The `BoundFunction` object kind.
BoundFunction(BoundFunction), BoundFunction(BoundFunction),
@ -342,7 +345,19 @@ pub enum ObjectKind {
Generator(Generator), Generator(Generator),
/// The `GeneratorFunction` object kind. /// 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<ConstructorKind>,
/// The [`Realm`] in which the function is defined.
realm: Realm,
},
/// The `Set` object kind. /// The `Set` object kind.
Set(OrderedSet), Set(OrderedSet),
@ -445,9 +460,13 @@ unsafe impl Trace for ObjectKind {
Self::RegExpStringIterator(i) => mark(i), Self::RegExpStringIterator(i) => mark(i),
Self::DataView(v) => mark(v), Self::DataView(v) => mark(v),
Self::ForInIterator(i) => mark(i), 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::BoundFunction(f) => mark(f),
Self::Generator(g) => mark(g), Self::Generator(g) => mark(g),
Self::NativeFunction { function, constructor: _, realm } => {
mark(function);
mark(realm);
}
Self::Set(s) => mark(s), Self::Set(s) => mark(s),
Self::SetIterator(i) => mark(i), Self::SetIterator(i) => mark(i),
Self::StringIterator(i) => mark(i), Self::StringIterator(i) => mark(i),
@ -518,7 +537,7 @@ impl ObjectData {
/// Create the `AsyncGeneratorFunction` object data /// Create the `AsyncGeneratorFunction` object data
#[must_use] #[must_use]
pub fn async_generator_function(function: Function) -> Self { pub fn async_generator_function(function: OrdinaryFunction) -> Self {
Self { Self {
internal_methods: &FUNCTION_INTERNAL_METHODS, internal_methods: &FUNCTION_INTERNAL_METHODS,
kind: ObjectKind::GeneratorFunction(function), kind: ObjectKind::GeneratorFunction(function),
@ -633,16 +652,37 @@ impl ObjectData {
} }
} }
/// Create the `Function` object data /// Create the ordinary function object data
#[must_use] #[must_use]
pub fn function(function: Function, constructor: bool) -> Self { pub fn ordinary_function(function: OrdinaryFunction, constructor: bool) -> Self {
Self { let internal_methods = if constructor {
internal_methods: if constructor {
&CONSTRUCTOR_INTERNAL_METHODS &CONSTRUCTOR_INTERNAL_METHODS
} else { } else {
&FUNCTION_INTERNAL_METHODS &FUNCTION_INTERNAL_METHODS
};
Self {
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 /// Create the `GeneratorFunction` object data
#[must_use] #[must_use]
pub fn generator_function(function: Function) -> Self { pub fn generator_function(function: OrdinaryFunction) -> Self {
Self { Self {
internal_methods: &FUNCTION_INTERNAL_METHODS, internal_methods: &FUNCTION_INTERNAL_METHODS,
kind: ObjectKind::GeneratorFunction(function), kind: ObjectKind::GeneratorFunction(function),
@ -930,10 +970,11 @@ impl Debug for ObjectKind {
Self::ArrayIterator(_) => "ArrayIterator", Self::ArrayIterator(_) => "ArrayIterator",
Self::ArrayBuffer(_) => "ArrayBuffer", Self::ArrayBuffer(_) => "ArrayBuffer",
Self::ForInIterator(_) => "ForInIterator", Self::ForInIterator(_) => "ForInIterator",
Self::Function(_) => "Function", Self::OrdinaryFunction(_) => "Function",
Self::BoundFunction(_) => "BoundFunction", Self::BoundFunction(_) => "BoundFunction",
Self::Generator(_) => "Generator", Self::Generator(_) => "Generator",
Self::GeneratorFunction(_) => "GeneratorFunction", Self::GeneratorFunction(_) => "GeneratorFunction",
Self::NativeFunction { .. } => "NativeFunction",
Self::RegExp(_) => "RegExp", Self::RegExp(_) => "RegExp",
Self::RegExpStringIterator(_) => "RegExpStringIterator", Self::RegExpStringIterator(_) => "RegExpStringIterator",
Self::Map(_) => "Map", Self::Map(_) => "Map",
@ -1267,27 +1308,29 @@ impl Object {
/// Checks if the object is a `Function` object. /// Checks if the object is a `Function` object.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn is_function(&self) -> bool { pub const fn is_ordinary_function(&self) -> bool {
matches!(self.kind, ObjectKind::Function(_)) matches!(
self.kind,
ObjectKind::OrdinaryFunction(_) | ObjectKind::GeneratorFunction(_)
)
} }
/// Gets the function data if the object is a `Function`. /// Gets the function data if the object is a `Function`.
#[inline] #[inline]
#[must_use] #[must_use]
pub const fn as_function(&self) -> Option<&Function> { pub const fn as_function(&self) -> Option<&OrdinaryFunction> {
match self.kind { match self.kind {
ObjectKind::Function(ref function) | ObjectKind::GeneratorFunction(ref function) => { ObjectKind::OrdinaryFunction(ref function)
Some(function) | ObjectKind::GeneratorFunction(ref function) => Some(function),
}
_ => None, _ => None,
} }
} }
/// Gets the mutable function data if the object is a `Function`. /// Gets the mutable function data if the object is a `Function`.
#[inline] #[inline]
pub fn as_function_mut(&mut self) -> Option<&mut Function> { pub fn as_function_mut(&mut self) -> Option<&mut OrdinaryFunction> {
match self.kind { match self.kind {
ObjectKind::Function(ref mut function) ObjectKind::OrdinaryFunction(ref mut function)
| ObjectKind::GeneratorFunction(ref mut function) => Some(function), | ObjectKind::GeneratorFunction(ref mut function) => Some(function),
_ => None, _ => None,
} }
@ -1721,6 +1764,13 @@ impl Object {
matches!(self.kind, ObjectKind::NativeObject(_)) 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`. /// Gets the native object data if the object is a `NativeObject`.
#[inline] #[inline]
#[must_use] #[must_use]
@ -2107,15 +2157,12 @@ impl<'realm> FunctionObjectBuilder<'realm> {
/// Build the function object. /// Build the function object.
#[must_use] #[must_use]
pub fn build(self) -> JsFunction { 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( 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()], vec![self.length.into(), self.name.into()],
); );
@ -2522,15 +2569,6 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
/// Build the constructor function object. /// Build the constructor function object.
#[must_use] #[must_use]
pub fn build(mut self) -> StandardConstructor { 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() let length = PropertyDescriptor::builder()
.value(self.length) .value(self.length)
.writable(false) .writable(false)
@ -2562,7 +2600,11 @@ impl<'ctx, 'host> ConstructorBuilder<'ctx, 'host> {
let mut constructor = self.constructor_object; let mut constructor = self.constructor_object;
constructor.insert(utf16!("length"), length); constructor.insert(utf16!("length"), length);
constructor.insert(utf16!("name"), name); 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; constructor.kind = data.kind;

32
boa_engine/src/object/operations.rs

@ -2,7 +2,7 @@ use crate::{
builtins::{function::ClassFieldDefinition, Array}, builtins::{function::ClassFieldDefinition, Array},
context::intrinsics::{StandardConstructor, StandardConstructors}, context::intrinsics::{StandardConstructor, StandardConstructors},
error::JsNativeError, error::JsNativeError,
object::{JsFunction, JsObject, PrivateElement, PrivateName, CONSTRUCTOR, PROTOTYPE}, object::{JsObject, PrivateElement, PrivateName, CONSTRUCTOR, PROTOTYPE},
property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind},
realm::Realm, realm::Realm,
string::utf16, string::utf16,
@ -10,6 +10,8 @@ use crate::{
Context, JsResult, JsSymbol, JsValue, Context, JsResult, JsSymbol, JsValue,
}; };
use super::ObjectKind;
/// Object integrity level. /// Object integrity level.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntegrityLevel { pub enum IntegrityLevel {
@ -315,14 +317,14 @@ impl JsObject {
args: &[JsValue], args: &[JsValue],
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1. If argumentsList is not present, set argumentsList to a new empty List. // SKIP: 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. If IsCallable(F) is false, throw a TypeError exception. // SKIP: 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") // 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). // 3. Return ? F.[[Call]](V, argumentsList).
function.__call__(this, args, context) self.__call__(this, args, context)
} }
/// `Construct ( F [ , argumentsList [ , newTarget ] ] )` /// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
@ -678,6 +680,10 @@ impl JsObject {
return Ok(fun.realm().clone()); return Ok(fun.realm().clone());
} }
if let ObjectKind::NativeFunction { realm, .. } = constructor.kind() {
return Ok(realm.clone());
}
if let Some(bound) = constructor.as_bound_function() { if let Some(bound) = constructor.as_bound_function() {
let fun = bound.target_function().clone(); let fun = bound.target_function().clone();
drop(constructor); drop(constructor);
@ -1153,14 +1159,16 @@ impl JsValue {
args: &[Self], args: &[Self],
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<Self> { ) -> JsResult<Self> {
self.as_callable() let Some(object) = self.as_object() else {
.ok_or_else(|| { return Err(JsNativeError::typ()
JsNativeError::typ().with_message(format!( .with_message(format!(
"value with type `{}` is not callable", "value with type `{}` is not callable",
self.type_of() self.type_of()
)) ))
})? .into());
.__call__(this, args, context) };
object.__call__(this, args, context)
} }
/// Abstract operation `( V, P [ , argumentsList ] )` /// Abstract operation `( V, P [ , argumentsList ] )`

236
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 //! This module is for the `CodeBlock` which implements a function representation in the VM
use crate::{ use crate::{
builtins::function::{arguments::Arguments, ConstructorKind, Function, FunctionKind, ThisMode}, builtins::function::{
arguments::Arguments, ConstructorKind, FunctionKind, OrdinaryFunction, ThisMode,
},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus}, environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus},
error::JsNativeError, error::JsNativeError,
@ -778,33 +780,32 @@ pub(crate) fn create_function_object(
let script_or_module = context.get_active_script_or_module(); let script_or_module = context.get_active_script_or_module();
let function = if r#async { let function = if r#async {
Function::new( OrdinaryFunction {
FunctionKind::Async {
code, code,
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
home_object: None, home_object: None,
class_object: None, class_object: None,
script_or_module, script_or_module,
}, kind: FunctionKind::Async,
context.realm().clone(), realm: context.realm().clone(),
) }
} else { } else {
Function::new( OrdinaryFunction {
FunctionKind::Ordinary {
code, code,
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
constructor_kind: ConstructorKind::Base,
home_object: None, home_object: None,
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None, class_object: None,
script_or_module, script_or_module,
kind: FunctionKind::Ordinary {
constructor_kind: ConstructorKind::Base,
fields: ThinVec::new(),
private_methods: ThinVec::new(),
}, },
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(); 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 script_or_module = context.get_active_script_or_module();
let function = if r#async { let kind = if r#async {
FunctionKind::Async { FunctionKind::Async
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
}
} else { } else {
FunctionKind::Ordinary { FunctionKind::Ordinary {
code,
environments: context.vm.environments.clone(),
constructor_kind: ConstructorKind::Base, constructor_kind: ConstructorKind::Base,
home_object: None,
fields: ThinVec::new(), fields: ThinVec::new(),
private_methods: 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 { if r#async {
context context
@ -963,32 +961,30 @@ pub(crate) fn create_generator_function_object(
let script_or_module = context.get_active_script_or_module(); let script_or_module = context.get_active_script_or_module();
let constructor = if r#async { let constructor = if r#async {
let function = Function::new( let function = OrdinaryFunction {
FunctionKind::AsyncGenerator {
code, code,
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
home_object: None, home_object: None,
class_object: None, class_object: None,
script_or_module, script_or_module,
}, kind: FunctionKind::AsyncGenerator,
context.realm().clone(), realm: context.realm().clone(),
); };
JsObject::from_proto_and_data_with_shared_shape( JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(), context.root_shape(),
function_prototype, function_prototype,
ObjectData::async_generator_function(function), ObjectData::async_generator_function(function),
) )
} else { } else {
let function = Function::new( let function = OrdinaryFunction {
FunctionKind::Generator {
code, code,
environments: context.vm.environments.clone(), environments: context.vm.environments.clone(),
home_object: None, home_object: None,
class_object: None, class_object: None,
script_or_module, script_or_module,
}, kind: FunctionKind::Generator,
context.realm().clone(), realm: context.realm().clone(),
); };
JsObject::from_proto_and_data_with_shared_shape( JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(), context.root_shape(),
function_prototype, function_prototype,
@ -1031,82 +1027,23 @@ impl JsObject {
let this_function_object = self.clone(); let this_function_object = self.clone();
let object = self.borrow(); let object = self.borrow();
let function_object = object.as_function().expect("not a function"); let function = object.as_function().expect("not a function");
let realm = function_object.realm().clone(); let realm = function.realm().clone();
context.enter_realm(realm); if let FunctionKind::Ordinary { .. } = function.kind() {
if function.code.is_class_constructor() {
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() return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'") .with_message("class constructor cannot be invoked without 'new'")
.with_realm(context.realm().clone()) .with_realm(realm)
.into()); .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 { context.enter_realm(realm);
code,
environments, let code = function.code.clone();
class_object, let mut environments = function.environments.clone();
script_or_module, let script_or_module = function.script_or_module.clone();
.. let class_object = function.class_object.clone();
} => (
code.clone(),
environments.clone(),
class_object.clone(),
script_or_module.clone(),
),
};
drop(object); drop(object);
@ -1240,66 +1177,21 @@ impl JsObject {
let this_function_object = self.clone(); let this_function_object = self.clone();
let object = self.borrow(); let object = self.borrow();
let function_object = object.as_function().expect("not a function"); let function = object.as_function().expect("not a function");
let realm = function_object.realm().clone(); let realm = function.realm().clone();
context.enter_realm(realm); context.enter_realm(realm);
match function_object.kind() { let FunctionKind::Ordinary {
FunctionKind::Native { constructor_kind, ..
function, } = function.kind()
constructor, else {
.. unreachable!("not a 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 let code = function.code.clone();
} let mut environments = function.environments.clone();
FunctionKind::Ordinary { let script_or_module = function.script_or_module.clone();
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; let constructor_kind = *constructor_kind;
drop(object); drop(object);
@ -1308,11 +1200,8 @@ impl JsObject {
// prototype as prototype for the new object // prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor> // see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor> // see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let prototype = get_prototype_from_constructor( let prototype =
this_target, get_prototype_from_constructor(this_target, StandardConstructors::object, context)?;
StandardConstructors::object,
context,
)?;
let this = Self::from_proto_and_data_with_shared_shape( let this = Self::from_proto_and_data_with_shared_shape(
context.root_shape(), context.root_shape(),
prototype, prototype,
@ -1449,11 +1338,4 @@ impl JsObject {
.map_err(JsError::from) .map_err(JsError::from)
} }
} }
FunctionKind::Generator { .. }
| FunctionKind::Async { .. }
| FunctionKind::AsyncGenerator { .. } => {
unreachable!("not a constructor")
}
}
}
} }

42
boa_engine/src/vm/opcode/call/mod.rs

@ -1,5 +1,5 @@
use crate::{ use crate::{
builtins::{function::FunctionKind, promise::PromiseCapability, Promise}, builtins::{promise::PromiseCapability, Promise},
error::JsNativeError, error::JsNativeError,
module::{ModuleKind, Referrer}, module::{ModuleKind, Referrer},
object::FunctionObjectBuilder, object::FunctionObjectBuilder,
@ -38,26 +38,18 @@ impl CallEval {
let func = context.vm.pop(); let func = context.vm.pop();
let this = context.vm.pop(); let this = context.vm.pop();
let object = match func { let Some(object) = func.as_object() else {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("not a callable function") .with_message("not a callable function")
.into()); .into());
}
}; };
// A native function with the name "eval" implies, that is this the built-in eval function. // A native function with the name "eval" implies, that is this the built-in eval function.
let eval = object let eval = object.borrow().is_native_function();
.borrow()
.as_function()
.map(|f| matches!(f.kind(), FunctionKind::Native { .. }))
.unwrap_or_default();
let strict = context.vm.frame().code_block.strict();
if eval { if eval {
if let Some(x) = arguments.get(0) { 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)?; let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?;
context.vm.push(result); context.vm.push(result);
} else { } else {
@ -132,26 +124,18 @@ impl Operation for CallEvalSpread {
let func = context.vm.pop(); let func = context.vm.pop();
let this = context.vm.pop(); let this = context.vm.pop();
let object = match func { let Some(object) = func.as_object() else {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("not a callable function") .with_message("not a callable function")
.into()); .into());
}
}; };
// A native function with the name "eval" implies, that is this the built-in eval function. // A native function with the name "eval" implies, that is this the built-in eval function.
let eval = object let eval = object.borrow().is_native_function();
.borrow()
.as_function()
.map(|f| matches!(f.kind(), FunctionKind::Native { .. }))
.unwrap_or_default();
let strict = context.vm.frame().code_block.strict();
if eval { if eval {
if let Some(x) = arguments.get(0) { 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)?; let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?;
context.vm.push(result); context.vm.push(result);
} else { } else {
@ -196,13 +180,10 @@ impl Call {
let func = context.vm.pop(); let func = context.vm.pop();
let this = context.vm.pop(); let this = context.vm.pop();
let object = match func { let Some(object) = func.as_object() else {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("not a callable function") .with_message("not a callable function")
.into()); .into());
}
}; };
let result = object.__call__(&this, &arguments, context)?; let result = object.__call__(&this, &arguments, context)?;
@ -269,13 +250,10 @@ impl Operation for CallSpread {
let func = context.vm.pop(); let func = context.vm.pop();
let this = context.vm.pop(); let this = context.vm.pop();
let object = match func { let Some(object) = func.as_object() else {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("not a callable function") .with_message("not a callable function")
.into()) .into());
}
}; };
let result = object.__call__(&this, &arguments, context)?; let result = object.__call__(&this, &arguments, context)?;

Loading…
Cancel
Save