Browse Source

Separate native and JavaScript functions (#3322)

pull/3332/head
Haled Odat 1 year 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. 17
      boa_engine/src/builtins/error/type.rs
  3. 267
      boa_engine/src/builtins/function/mod.rs
  4. 52
      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. 128
      boa_engine/src/object/mod.rs
  13. 32
      boa_engine/src/object/operations.rs
  14. 536
      boa_engine/src/vm/code_block.rs
  15. 64
      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 {
return Err(JsNativeError::typ()
.with_message("expected function object")
.with_message("expected an ordinary function object")
.into());
};
let code = function.codeblock().ok_or_else(|| {
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
let code = function.codeblock();
let mut graph = Graph::new(direction);
code.to_graph(context.interner(), graph.subgraph(String::default()));
@ -118,12 +116,10 @@ fn bytecode(_: &JsValue, args: &[JsValue], context: &mut Context<'_>) -> JsResul
let object = object.borrow();
let Some(function) = object.as_function() else {
return Err(JsNativeError::typ()
.with_message("expected function object")
.with_message("expected an ordinary function object")
.into());
};
let code = function.codeblock().ok_or_else(|| {
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
let code = function.codeblock();
Ok(js_string!(code.to_interned_string(context.interner())).into())
}
@ -132,12 +128,10 @@ fn set_trace_flag_in_function_object(object: &JsObject, value: bool) -> JsResult
let object = object.borrow();
let Some(function) = object.as_function() else {
return Err(JsNativeError::typ()
.with_message("expected function object")
.with_message("expected an ordinary function object")
.into());
};
let code = function.codeblock().ok_or_else(|| {
JsNativeError::typ().with_message("native functions do not have bytecode")
})?;
let code = function.codeblock();
code.set_traceable(value);
Ok(())
}

17
boa_engine/src/builtins/error/type.rs

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

267
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<ConstructorKind>,
},
/// A bytecode function.
Ordinary {
/// The code block containing the compiled function.
code: Gc<CodeBlock>,
/// The `[[Environment]]` internal slot.
environments: EnvironmentStack,
/// The `[[ConstructorKind]]` internal slot.
constructor_kind: ConstructorKind,
/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
/// The `[[Fields]]` internal slot.
fields: ThinVec<ClassFieldDefinition>,
/// The `[[PrivateMethods]]` internal slot.
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.
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>,
},
Async,
/// A bytecode generator function.
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>,
},
Generator,
/// A bytecode async generator function.
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>,
},
AsyncGenerator,
}
impl fmt::Debug for FunctionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Native {
function,
constructor,
} => f
.debug_struct("FunctionKind::Native")
.field("function", &function)
.field("constructor", &constructor)
.finish(),
Self::Ordinary { .. } => f
.debug_struct("FunctionKind::Ordinary")
.finish_non_exhaustive(),
@ -266,77 +189,65 @@ impl fmt::Debug for FunctionKind {
unsafe impl Trace for FunctionKind {
custom_trace! {this, {
match this {
Self::Native { function, .. } => {mark(function)}
Self::Ordinary {
code,
environments,
home_object,
fields,
private_methods,
class_object,
script_or_module,
..
} => {
mark(code);
mark(environments);
mark(home_object);
for elem in fields {
mark(elem);
}
for (_, elem) in private_methods {
mark(elem);
}
mark(class_object);
mark(script_or_module);
}
Self::Async { code, environments, home_object, class_object, script_or_module }
| Self::Generator { code, environments, home_object, class_object, script_or_module}
| Self::AsyncGenerator { code, environments, home_object, class_object, script_or_module} => {
mark(code);
mark(environments);
mark(home_object);
mark(class_object);
mark(script_or_module);
}
Self::Async
| Self::Generator
| Self::AsyncGenerator => {}
}
}}
}
/// Boa representation of a Function Object.
/// Boa representation of a JavaScript Function Object.
///
/// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code
/// (AST Node).
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Debug, Trace, Finalize)]
pub struct Function {
kind: FunctionKind,
realm: Realm,
pub struct OrdinaryFunction {
/// The code block containing the compiled function.
pub(crate) code: Gc<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 {
/// Returns the codeblock of the function, or `None` if the function is a [`NativeFunction`].
impl OrdinaryFunction {
/// Returns the codeblock of the function.
#[must_use]
pub fn codeblock(&self) -> Option<&CodeBlock> {
match &self.kind {
FunctionKind::Native { .. } => None,
FunctionKind::Ordinary { code, .. }
| FunctionKind::Async { code, .. }
| FunctionKind::Generator { code, .. }
| FunctionKind::AsyncGenerator { code, .. } => Some(code),
}
}
/// Creates a new `Function`.
pub(crate) fn new(kind: FunctionKind, realm: Realm) -> Self {
Self { kind, realm }
pub fn codeblock(&self) -> &CodeBlock {
&self.code
}
/// Push a private environment to the function.
pub(crate) fn push_private_environment(&mut self, environment: Gc<PrivateEnvironment>) {
if let FunctionKind::Ordinary { environments, .. } = &mut self.kind {
environments.push_private(environment);
}
self.environments.push_private(environment);
}
/// Returns true if the function object is a derived constructor.
@ -353,33 +264,17 @@ impl Function {
/// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value.
pub(crate) fn in_class_field_initializer(&self) -> bool {
if let FunctionKind::Ordinary { code, .. } = &self.kind {
code.in_class_field_initializer()
} else {
false
}
self.code.in_class_field_initializer()
}
/// Returns a reference to the function `[[HomeObject]]` slot if present.
pub(crate) const fn get_home_object(&self) -> Option<&JsObject> {
match &self.kind {
FunctionKind::Ordinary { home_object, .. }
| FunctionKind::Async { home_object, .. }
| FunctionKind::Generator { home_object, .. }
| FunctionKind::AsyncGenerator { home_object, .. } => home_object.as_ref(),
FunctionKind::Native { .. } => None,
}
self.home_object.as_ref()
}
/// Sets the `[[HomeObject]]` slot if present.
pub(crate) fn set_home_object(&mut self, object: JsObject) {
match &mut self.kind {
FunctionKind::Ordinary { home_object, .. }
| FunctionKind::Async { home_object, .. }
| FunctionKind::Generator { home_object, .. }
| FunctionKind::AsyncGenerator { home_object, .. } => *home_object = Some(object),
FunctionKind::Native { .. } => {}
}
self.home_object = Some(object);
}
/// Returns the values of the `[[Fields]]` internal slot.
@ -429,13 +324,7 @@ impl Function {
/// Sets the class object.
pub(crate) fn set_class_object(&mut self, object: JsObject) {
match &mut self.kind {
FunctionKind::Ordinary { class_object, .. }
| FunctionKind::Async { class_object, .. }
| FunctionKind::Generator { class_object, .. }
| FunctionKind::AsyncGenerator { class_object, .. } => *class_object = Some(object),
FunctionKind::Native { .. } => {}
}
self.class_object = Some(object);
}
/// Gets the `Realm` from where this function originates.
@ -961,44 +850,64 @@ impl BuiltInFunctionObject {
#[allow(clippy::wrong_self_convention)]
fn to_string(this: &JsValue, _: &[JsValue], context: &mut Context<'_>) -> JsResult<JsValue> {
let object = this.as_object().map(JsObject::borrow);
let function = object
.as_deref()
.and_then(Object::as_function)
.ok_or_else(|| JsNativeError::typ().with_message("Not a function"))?;
let name = {
// Is there a case here where if there is no name field on a value
// name should default to None? Do all functions have names set?
let value = this
.as_object()
.expect("checked that `this` was an object above")
.get(utf16!("name"), &mut *context)?;
if value.is_null_or_undefined() {
None
} else {
Some(value.to_string(context)?)
}
// 1. Let func be the this value.
let func = this;
// 2. If func is an Object, func has a [[SourceText]] internal slot, func.[[SourceText]] is a sequence of Unicode code points,and HostHasSourceTextAvailable(func) is true, then
// a. Return CodePointsToString(func.[[SourceText]]).
// 3. If func is a built-in function object, return an implementation-defined String source code representation of func.
// The representation must have the syntax of a NativeFunction. Additionally, if func has an [[InitialName]] internal slot and
// func.[[InitialName]] is a String, the portion of the returned String that would be matched by
// NativeFunctionAccessor_opt PropertyName must be the value of func.[[InitialName]].
// 4. If func is an Object and IsCallable(func) is true, return an implementation-defined String source code representation of func.
// The representation must have the syntax of a NativeFunction.
// 5. Throw a TypeError exception.
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ().with_message("not a function").into());
};
let name = name
.filter(|n| !n.is_empty())
.unwrap_or_else(|| "anonymous".into());
if object.borrow().is_native_function() {
let name = {
// Is there a case here where if there is no name field on a value
// name should default to None? Do all functions have names set?
let value = this
.as_object()
.expect("checked that `this` was an object above")
.get(utf16!("name"), &mut *context)?;
if value.is_null_or_undefined() {
js_string!()
} else {
value.to_string(context)?
}
};
return Ok(
js_string!(utf16!("function "), &name, utf16!("() { [native code] }")).into(),
);
}
let object = object.borrow();
let function = object
.as_function()
.ok_or_else(|| JsNativeError::typ().with_message("not a function"))?;
let code = function.codeblock();
match function.kind {
FunctionKind::Native { .. } | FunctionKind::Ordinary { .. } => {
Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into())
let prefix = match function.kind {
FunctionKind::Ordinary { .. } => {
utf16!("function ")
}
FunctionKind::Async { .. } => {
Ok(js_string!(utf16!("[AsyncFunction: "), &name, utf16!("]")).into())
utf16!("async function ")
}
FunctionKind::Generator { .. } => {
Ok(js_string!(utf16!("[GeneratorFunction: "), &name, utf16!("]")).into())
}
FunctionKind::AsyncGenerator { .. } => {
Ok(js_string!(utf16!("[AsyncGeneratorFunction: "), &name, utf16!("]")).into())
utf16!("function* ")
}
}
FunctionKind::AsyncGenerator { .. } => utf16!("async function* "),
};
Ok(js_string!(prefix, code.name(), utf16!("() { [native code] }")).into())
}
/// `Function.prototype [ @@hasInstance ] ( V )`

52
boa_engine/src/builtins/mod.rs

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

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

@ -42,7 +42,7 @@ fn species() {
// symbol-species
TestAction::assert_eq("descriptor.set", JsValue::undefined()),
TestAction::assert_with_op("accessor", |v, _| {
v.as_object().map_or(false, JsObject::is_function)
v.as_object().map_or(false, JsObject::is_native_function)
}),
TestAction::assert("!descriptor.enumerable"),
TestAction::assert("descriptor.configurable"),

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

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

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
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
};

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

@ -1,11 +1,14 @@
use crate::{
context::intrinsics::StandardConstructors,
object::{
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
JsObject,
JsObject, ObjectData, ObjectKind,
},
Context, JsResult, JsValue,
Context, JsNativeError, JsResult, JsValue,
};
use super::get_prototype_from_constructor;
/// Definitions of the internal object methods for function objects.
///
/// More information:
@ -13,14 +16,13 @@ use crate::{
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects
pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call),
__construct__: None,
__call__: function_call,
..ORDINARY_INTERNAL_METHODS
};
pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__call__: Some(function_call),
__construct__: Some(function_construct),
__call__: function_call,
__construct__: function_construct,
..ORDINARY_INTERNAL_METHODS
};
@ -31,7 +33,6 @@ pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = Internal
/// Panics if the object is currently mutably borrowed.
// <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,
@ -47,7 +48,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],
@ -56,3 +56,133 @@ 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 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,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::JsValue,
Context, JsResult,
Context, JsNativeError, JsResult,
};
use boa_profiler::Profiler;
@ -225,11 +225,7 @@ impl JsObject {
context: &mut Context<'_>,
) -> JsResult<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]]`
@ -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)
}
}
@ -279,8 +271,8 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj
__set__: ordinary_set,
__delete__: ordinary_delete,
__own_property_keys__: ordinary_own_property_keys,
__call__: None,
__construct__: None,
__call__: non_existant_call,
__construct__: non_existant_construct,
};
/// The internal representation of the internal methods of a `JsObject`.
@ -307,10 +299,9 @@ pub(crate) struct InternalObjectMethods {
fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context<'_>) -> JsResult<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`.
@ -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("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 super::ORDINARY_INTERNAL_METHODS;
/// Definitions of the internal object methods for array exotic objects.
///
/// More information:
@ -28,20 +30,19 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods =
__set__: proxy_exotic_set,
__delete__: proxy_exotic_delete,
__own_property_keys__: proxy_exotic_own_property_keys,
__call__: None,
__construct__: None,
..ORDINARY_INTERNAL_METHODS
};
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(proxy_exotic_call),
__call__: proxy_exotic_call,
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
};
pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods =
InternalObjectMethods {
__call__: Some(proxy_exotic_call),
__construct__: Some(proxy_exotic_construct),
__call__: proxy_exotic_call,
__construct__: proxy_exotic_construct,
..PROXY_EXOTIC_INTERNAL_METHODS_BASIC
};

31
boa_engine/src/object/jsobject.rs

@ -3,7 +3,10 @@
//! The `JsObject` is a garbage collected Object.
use super::{
internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS},
internal_methods::{
non_existant_call, non_existant_construct, InternalObjectMethods,
ARRAY_EXOTIC_INTERNAL_METHODS,
},
shape::RootShape,
JsPrototype, NativeObject, Object, PrivateName, PropertyMap,
};
@ -488,8 +491,20 @@ impl JsObject {
#[inline]
#[must_use]
#[track_caller]
pub fn is_function(&self) -> bool {
self.borrow().is_function()
pub fn is_ordinary_function(&self) -> bool {
self.borrow().is_ordinary_function()
}
/// Checks if it's a native `Function` object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
#[inline]
#[must_use]
#[track_caller]
pub fn is_native_function(&self) -> bool {
self.borrow().is_native_function()
}
/// Checks if it's a `Generator` object.
@ -956,9 +971,9 @@ Cannot both specify accessors and a value or writable attribute",
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
#[must_use]
#[track_caller]
#[allow(clippy::fn_address_comparisons)]
pub fn is_callable(&self) -> bool {
self.inner.vtable.__call__.is_some()
self.inner.vtable.__call__ != non_existant_call
}
/// It determines if Object is a function object with a `[[Construct]]` internal method.
@ -969,9 +984,9 @@ Cannot both specify accessors and a value or writable attribute",
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
#[must_use]
#[track_caller]
#[allow(clippy::fn_address_comparisons)]
pub fn is_constructor(&self) -> bool {
self.inner.vtable.__construct__.is_some()
self.inner.vtable.__construct__ != non_existant_construct
}
/// Returns true if the `JsObject` is the global for a Realm
@ -1146,7 +1161,7 @@ impl Debug for JsObject {
let ptr: *const _ = self.as_ref();
let obj = self.borrow();
let kind = obj.kind();
if obj.is_function() {
if obj.is_ordinary_function() {
let name_prop = obj
.properties()
.get(&PropertyKey::String(JsString::from("name")));

128
boa_engine/src/object/mod.rs

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

32
boa_engine/src/object/operations.rs

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

536
boa_engine/src/vm/code_block.rs

@ -3,7 +3,9 @@
//! This module is for the `CodeBlock` which implements a function representation in the VM
use crate::{
builtins::function::{arguments::Arguments, ConstructorKind, Function, FunctionKind, ThisMode},
builtins::function::{
arguments::Arguments, ConstructorKind, FunctionKind, OrdinaryFunction, ThisMode,
},
context::intrinsics::StandardConstructors,
environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus},
error::JsNativeError,
@ -778,33 +780,32 @@ pub(crate) fn create_function_object(
let script_or_module = context.get_active_script_or_module();
let function = if r#async {
Function::new(
FunctionKind::Async {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
},
context.realm().clone(),
)
OrdinaryFunction {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
kind: FunctionKind::Async,
realm: context.realm().clone(),
}
} else {
Function::new(
FunctionKind::Ordinary {
code,
environments: context.vm.environments.clone(),
OrdinaryFunction {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
kind: FunctionKind::Ordinary {
constructor_kind: ConstructorKind::Base,
home_object: None,
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None,
script_or_module,
},
context.realm().clone(),
)
realm: context.realm().clone(),
}
};
let data = ObjectData::function(function, !r#async);
let data = ObjectData::ordinary_function(function, !r#async);
let templates = context.intrinsics().templates();
@ -857,30 +858,27 @@ pub(crate) fn create_function_object_fast(
let script_or_module = context.get_active_script_or_module();
let function = if r#async {
FunctionKind::Async {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
}
let kind = if r#async {
FunctionKind::Async
} else {
FunctionKind::Ordinary {
code,
environments: context.vm.environments.clone(),
constructor_kind: ConstructorKind::Base,
home_object: None,
fields: ThinVec::new(),
private_methods: ThinVec::new(),
class_object: None,
script_or_module,
}
};
let function = Function::new(function, context.realm().clone());
let function = OrdinaryFunction {
code,
environments: context.vm.environments.clone(),
class_object: None,
script_or_module,
home_object: None,
kind,
realm: context.realm().clone(),
};
let data = ObjectData::function(function, !method && !arrow && !r#async);
let data = ObjectData::ordinary_function(function, !method && !arrow && !r#async);
if r#async {
context
@ -963,32 +961,30 @@ pub(crate) fn create_generator_function_object(
let script_or_module = context.get_active_script_or_module();
let constructor = if r#async {
let function = Function::new(
FunctionKind::AsyncGenerator {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
},
context.realm().clone(),
);
let function = OrdinaryFunction {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
kind: FunctionKind::AsyncGenerator,
realm: context.realm().clone(),
};
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
function_prototype,
ObjectData::async_generator_function(function),
)
} else {
let function = Function::new(
FunctionKind::Generator {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
},
context.realm().clone(),
);
let function = OrdinaryFunction {
code,
environments: context.vm.environments.clone(),
home_object: None,
class_object: None,
script_or_module,
kind: FunctionKind::Generator,
realm: context.realm().clone(),
};
JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
function_prototype,
@ -1031,82 +1027,23 @@ impl JsObject {
let this_function_object = self.clone();
let object = self.borrow();
let function_object = object.as_function().expect("not a function");
let realm = function_object.realm().clone();
let function = object.as_function().expect("not a function");
let realm = function.realm().clone();
if let FunctionKind::Ordinary { .. } = function.kind() {
if function.code.is_class_constructor() {
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(realm)
.into());
}
}
context.enter_realm(realm);
let (code, mut environments, class_object, script_or_module) = match function_object.kind()
{
FunctionKind::Native {
function,
constructor,
} => {
let function = function.clone();
let constructor = *constructor;
drop(object);
context.vm.native_active_function = Some(this_function_object);
let result = if constructor.is_some() {
function.call(&JsValue::undefined(), args, context)
} else {
function.call(this, args, context)
}
.map_err(|err| err.inject_realm(context.realm().clone()));
context.vm.native_active_function = None;
return result;
}
FunctionKind::Ordinary {
code,
environments,
class_object,
script_or_module,
..
} => {
let code = code.clone();
if code.is_class_constructor() {
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(context.realm().clone())
.into());
}
(
code,
environments.clone(),
class_object.clone(),
script_or_module.clone(),
)
}
FunctionKind::Async {
code,
environments,
class_object,
script_or_module,
..
}
| FunctionKind::Generator {
code,
environments,
class_object,
script_or_module,
..
}
| FunctionKind::AsyncGenerator {
code,
environments,
class_object,
script_or_module,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
script_or_module.clone(),
),
};
let code = function.code.clone();
let mut environments = function.environments.clone();
let script_or_module = function.script_or_module.clone();
let class_object = function.class_object.clone();
drop(object);
@ -1240,220 +1177,165 @@ impl JsObject {
let this_function_object = self.clone();
let object = self.borrow();
let function_object = object.as_function().expect("not a function");
let realm = function_object.realm().clone();
let function = object.as_function().expect("not a function");
let realm = function.realm().clone();
context.enter_realm(realm);
match function_object.kind() {
FunctionKind::Native {
function,
constructor,
..
} => {
let function = function.clone();
let constructor = *constructor;
drop(object);
context.vm.native_active_function = Some(this_function_object);
let result = function
.call(this_target, args, context)
.map_err(|err| err.inject_realm(context.realm().clone()))
.and_then(|v| match v {
JsValue::Object(ref o) => Ok(o.clone()),
val => {
if constructor.expect("must be a constructor").is_base()
|| val.is_undefined()
{
let prototype = get_prototype_from_constructor(
this_target,
StandardConstructors::object,
context,
)?;
Ok(Self::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::ordinary(),
))
} else {
Err(JsNativeError::typ()
.with_message(
"derived constructor can only return an Object or undefined",
)
.into())
}
}
});
context.vm.native_active_function = None;
result
}
FunctionKind::Ordinary {
code,
environments,
constructor_kind,
script_or_module,
..
} => {
let code = code.clone();
let mut environments = environments.clone();
let script_or_module = script_or_module.clone();
let constructor_kind = *constructor_kind;
drop(object);
let this = if constructor_kind.is_base() {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
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 <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let prototype =
get_prototype_from_constructor(this_target, StandardConstructors::object, context)?;
let this = Self::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::ordinary(),
);
this.initialize_instance_elements(self, context)?;
Some(this)
} else {
None
};
let mut last_env = code.compile_environments.len() - 1;
let environments_len = environments.len();
std::mem::swap(&mut environments, &mut context.vm.environments);
if code.has_binding_identifier() {
let index = context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
context
.vm
.environments
.put_lexical_value(index, 0, self.clone().into());
last_env -= 1;
}
let new_target = this_target.as_object().expect("must be object");
context.vm.environments.push_function(
code.compile_environments[last_env].clone(),
FunctionSlots::new(
this.clone().map_or(ThisBindingStatus::Uninitialized, |o| {
ThisBindingStatus::Initialized(o.into())
}),
self.clone(),
Some(new_target.clone()),
),
);
let environment = context.vm.environments.current();
if code.has_parameters_env_bindings() {
last_env -= 1;
context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
}
let mut last_env = code.compile_environments.len() - 1;
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
&this_function_object,
&code.params,
args,
env.declarative_expect(),
context,
)
};
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
if code.has_binding_identifier() {
let index = context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
context
.vm
.environments
.put_lexical_value(index, 0, self.clone().into());
last_env -= 1;
}
let argument_count = args.len();
let parameters_count = code.params.as_ref().len();
context.vm.environments.push_function(
code.compile_environments[last_env].clone(),
FunctionSlots::new(
this.clone().map_or(ThisBindingStatus::Uninitialized, |o| {
ThisBindingStatus::Initialized(o.into())
}),
self.clone(),
Some(new_target.clone()),
),
);
context.vm.push_frame(
CallFrame::new(code, script_or_module, Some(self.clone()))
.with_argument_count(argument_count as u32)
.with_env_fp(environments_len as u32),
);
let environment = context.vm.environments.current();
// Push function arguments to the stack.
for _ in argument_count..parameters_count {
context.vm.push(JsValue::undefined());
}
context.vm.stack.extend(args.iter().rev().cloned());
if code.has_parameters_env_bindings() {
last_env -= 1;
context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
}
let record = context.run();
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
&this_function_object,
&code.params,
args,
env.declarative_expect(),
context,
)
};
context.vm.pop_frame();
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
std::mem::swap(&mut environments, &mut context.vm.environments);
let argument_count = args.len();
let parameters_count = code.params.as_ref().len();
let result = record
.consume()
.map_err(|err| err.inject_realm(context.realm().clone()))?;
context.vm.push_frame(
CallFrame::new(code, script_or_module, Some(self.clone()))
.with_argument_count(argument_count as u32)
.with_env_fp(environments_len as u32),
);
if let Some(result) = result.as_object() {
Ok(result.clone())
} else if let Some(this) = this {
Ok(this)
} else if !result.is_undefined() {
Err(JsNativeError::typ()
.with_message("derived constructor can only return an Object or undefined")
.into())
} else {
let function_env = environment
.declarative_expect()
.kind()
.as_function()
.expect("must be function environment");
function_env
.get_this_binding()
.map(|v| {
v.expect("constructors cannot be arrow functions")
.as_object()
.expect("this binding must be object")
.clone()
})
.map_err(JsError::from)
}
}
FunctionKind::Generator { .. }
| FunctionKind::Async { .. }
| FunctionKind::AsyncGenerator { .. } => {
unreachable!("not a constructor")
}
// Push function arguments to the stack.
for _ in argument_count..parameters_count {
context.vm.push(JsValue::undefined());
}
context.vm.stack.extend(args.iter().rev().cloned());
let record = context.run();
context.vm.pop_frame();
std::mem::swap(&mut environments, &mut context.vm.environments);
let result = record
.consume()
.map_err(|err| err.inject_realm(context.realm().clone()))?;
if let Some(result) = result.as_object() {
Ok(result.clone())
} else if let Some(this) = this {
Ok(this)
} else if !result.is_undefined() {
Err(JsNativeError::typ()
.with_message("derived constructor can only return an Object or undefined")
.into())
} else {
let function_env = environment
.declarative_expect()
.kind()
.as_function()
.expect("must be function environment");
function_env
.get_this_binding()
.map(|v| {
v.expect("constructors cannot be arrow functions")
.as_object()
.expect("this binding must be object")
.clone()
})
.map_err(JsError::from)
}
}
}

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

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

Loading…
Cancel
Save