Skip to content

Commit

Permalink
Separate internal method for native function call
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Sep 30, 2023
1 parent 332fd65 commit 270d196
Show file tree
Hide file tree
Showing 11 changed files with 256 additions and 151 deletions.
5 changes: 5 additions & 0 deletions boa_engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ 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.
Expand Down
8 changes: 5 additions & 3 deletions boa_engine/src/object/builtins/jsfunction.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! A Rust API wrapper for Boa's `Function` Builtin ECMAScript Object
use crate::{
object::{
internal_methods::function::{CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS},
internal_methods::function::{
NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS,
},
JsObject, JsObjectType, Object,
},
value::TryFromJs,
Expand Down Expand Up @@ -32,9 +34,9 @@ impl JsFunction {
inner: JsObject::from_object_and_vtable(
Object::default(),
if constructor {
&CONSTRUCTOR_INTERNAL_METHODS
&NATIVE_CONSTRUCTOR_INTERNAL_METHODS
} else {
&FUNCTION_INTERNAL_METHODS
&NATIVE_FUNCTION_INTERNAL_METHODS
},
),
}
Expand Down
6 changes: 3 additions & 3 deletions boa_engine/src/object/internal_methods/bound_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(bound_function_exotic_call),
__call__: bound_function_exotic_call,
..ORDINARY_INTERNAL_METHODS
};

pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(bound_function_exotic_call),
__construct__: Some(bound_function_exotic_construct),
__call__: bound_function_exotic_call,
__construct__: bound_function_exotic_construct,
..ORDINARY_INTERNAL_METHODS
};

Expand Down
148 changes: 140 additions & 8 deletions boa_engine/src/object/internal_methods/function.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
use crate::{
builtins::function::FunctionKind,
context::intrinsics::StandardConstructors,
object::{
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
JsObject,
JsObject, ObjectData,
},
Context, JsResult, JsValue,
Context, JsNativeError, JsResult, JsValue,
};

use super::get_prototype_from_constructor;

/// Definitions of the internal object methods for function objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call),
__construct__: None,
__call__: function_call,
..ORDINARY_INTERNAL_METHODS
};

pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call),
__construct__: Some(function_construct),
__call__: function_call,
__construct__: function_construct,
..ORDINARY_INTERNAL_METHODS
};

Expand All @@ -31,7 +34,6 @@ pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = Internal
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
fn function_call(
obj: &JsObject,
this: &JsValue,
Expand All @@ -47,7 +49,6 @@ fn function_call(
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
fn function_construct(
obj: &JsObject,
args: &[JsValue],
Expand All @@ -56,3 +57,134 @@ fn function_construct(
) -> JsResult<JsObject> {
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 function_object = object.as_function().expect("not a function");
let mut realm = function_object.realm().clone();

let FunctionKind::Native {
function,
constructor,
} = function_object.kind()
else {
unreachable!("the object should be a native function object")
};

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-ecmascript-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 function_object = object.as_function().expect("not a function");
let mut realm = function_object.realm().clone();

let FunctionKind::Native {
function,
constructor,
..
} = function_object.kind()
else {
unreachable!("the object should be a native function object");
};

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 changes: 31 additions & 16 deletions boa_engine/src/object/internal_methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
object::JsObject,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::JsValue,
Context, JsResult,
Context, JsNativeError, JsResult,
};
use boa_profiler::Profiler;

Expand Down Expand Up @@ -225,11 +225,7 @@ impl JsObject {
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__call__", "object");
self.vtable()
.__call__
.expect("called `[[Call]]` for object without a `[[Call]]` internal method")(
self, this, args, context,
)
(self.vtable().__call__)(self, this, args, context)
}

/// Internal method `[[Construct]]`
Expand All @@ -248,11 +244,7 @@ impl JsObject {
context: &mut Context<'_>,
) -> JsResult<Self> {
let _timer = Profiler::global().start_event("Object::__construct__", "object");
self.vtable()
.__construct__
.expect("called `[[Construct]]` for object without a `[[Construct]]` internal method")(
self, args, new_target, context,
)
(self.vtable().__construct__)(self, args, new_target, context)
}
}

Expand All @@ -279,8 +271,8 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj
__set__: ordinary_set,
__delete__: ordinary_delete,
__own_property_keys__: ordinary_own_property_keys,
__call__: None,
__construct__: None,
__call__: non_existant_call,
__construct__: non_existant_construct,
};

/// The internal representation of the internal methods of a `JsObject`.
Expand All @@ -307,10 +299,9 @@ pub(crate) struct InternalObjectMethods {
fn(&JsObject, PropertyKey, JsValue, JsValue, &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) __call__:
Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>>,
pub(crate) __call__: fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>,
pub(crate) __construct__:
Option<fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult<JsObject>>,
fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult<JsObject>,
}

/// Abstract operation `OrdinaryGetPrototypeOf`.
Expand Down Expand Up @@ -919,3 +910,27 @@ where
// b. Set proto to realm's intrinsic object named intrinsicDefaultProto.
Ok(default(realm.intrinsics().constructors()).prototype())
}

pub(crate) fn non_existant_call(
_obj: &JsObject,
_this: &JsValue,
_args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
Err(JsNativeError::typ()
.with_message("only callable objects / functions can be called")
.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 changes: 6 additions & 5 deletions boa_engine/src/object/internal_methods/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use crate::{
};
use rustc_hash::FxHashSet;

use super::ORDINARY_INTERNAL_METHODS;

/// Definitions of the internal object methods for array exotic objects.
///
/// More information:
Expand All @@ -28,20 +30,19 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods =
__set__: proxy_exotic_set,
__delete__: proxy_exotic_delete,
__own_property_keys__: proxy_exotic_own_property_keys,
__call__: None,
__construct__: None,
..ORDINARY_INTERNAL_METHODS
};

pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(proxy_exotic_call),
__call__: proxy_exotic_call,
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
};

pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(proxy_exotic_call),
__construct__: Some(proxy_exotic_construct),
__call__: proxy_exotic_call,
__construct__: proxy_exotic_construct,
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
};

Expand Down
13 changes: 8 additions & 5 deletions boa_engine/src/object/jsobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
//! The `JsObject` is a garbage collected Object.
use super::{
internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS},
internal_methods::{
non_existant_call, non_existant_construct, InternalObjectMethods,
ARRAY_EXOTIC_INTERNAL_METHODS,
},
shape::RootShape,
JsPrototype, NativeObject, Object, PrivateName, PropertyMap,
};
Expand Down Expand Up @@ -956,9 +959,9 @@ Cannot both specify accessors and a value or writable attribute",
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
#[must_use]
#[track_caller]
#[allow(clippy::fn_address_comparisons)]
pub fn is_callable(&self) -> bool {
self.inner.vtable.__call__.is_some()
self.inner.vtable.__call__ != non_existant_call
}

/// It determines if Object is a function object with a `[[Construct]]` internal method.
Expand All @@ -969,9 +972,9 @@ Cannot both specify accessors and a value or writable attribute",
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
#[must_use]
#[track_caller]
#[allow(clippy::fn_address_comparisons)]
pub fn is_constructor(&self) -> bool {
self.inner.vtable.__construct__.is_some()
self.inner.vtable.__construct__ != non_existant_construct
}

/// Returns true if the `JsObject` is the global for a Realm
Expand Down
Loading

0 comments on commit 270d196

Please sign in to comment.