diff --git a/Cargo.lock b/Cargo.lock index 4494d572a68..33ed4619a19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,7 +131,6 @@ dependencies = [ "boa_profiler", "chrono", "criterion", - "dyn-clone", "fast-float", "float-cmp", "icu_datetime", @@ -625,12 +624,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dyn-clone" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" - [[package]] name = "either" version = "1.8.0" diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 6852bd5eaba..045c38f10b8 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -53,7 +53,6 @@ ryu-js = "0.2.2" chrono = "0.4.23" fast-float = "0.2.0" unicode-normalization = "0.1.22" -dyn-clone = "1.0.10" once_cell = "1.16.0" tap = "1.0.1" sptr = "0.3.2" diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index d98c3aff263..77a72a5a11e 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -24,6 +24,7 @@ use crate::{ builtins::Number, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -49,10 +50,11 @@ impl BuiltIn for Array { let symbol_iterator = WellKnownSymbols::iterator(); let symbol_unscopables = WellKnownSymbols::unscopables(); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); let values_function = context.intrinsics().objects().array_prototype_values(); let unscopables_object = Self::unscopables_intrinsic(context); @@ -2765,7 +2767,7 @@ impl Array { /// Creates an `Array.prototype.values( )` function object. pub(crate) fn create_array_prototype_values(context: &mut Context) -> JsFunction { - FunctionBuilder::native(context, Self::values) + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::values)) .name("values") .length(0) .constructor(false) diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 8246964f240..6d4f66bd174 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -14,6 +14,7 @@ use crate::{ builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -55,14 +56,16 @@ impl BuiltIn for ArrayBuffer { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); - let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length) - .name("get byteLength") - .build(); + let get_byte_length = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_byte_length)) + .name("get byteLength") + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/async_function/mod.rs b/boa_engine/src/builtins/async_function/mod.rs index dfbe6a31f09..b9e24f4b95d 100644 --- a/boa_engine/src/builtins/async_function/mod.rs +++ b/boa_engine/src/builtins/async_function/mod.rs @@ -12,6 +12,7 @@ use crate::{ function::{ConstructorKind, Function}, BuiltIn, }, + function::NativeCallable, object::ObjectData, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -62,7 +63,7 @@ impl BuiltIn for AsyncFunction { .configurable(false); constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().data = ObjectData::function(Function::Native { - function: Self::constructor, + function: NativeCallable::from_fn_ptr(Self::constructor), constructor: Some(ConstructorKind::Base), }); diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index fdde02d6753..0e2b227d5de 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -11,6 +11,7 @@ use crate::{ promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise, }, error::JsNativeError, + function::NativeCallable, object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, property::{Attribute, PropertyDescriptor}, symbol::WellKnownSymbols, @@ -627,32 +628,34 @@ impl AsyncGenerator { // 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called: // 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionBuilder::new( context, - |_this, args, generator, context| { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() - .expect("already checked before"); + NativeCallable::from_copy_closure_with_captures( + |_this, args, generator, context| { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); - // a. Set generator.[[AsyncGeneratorState]] to completed. - gen.state = AsyncGeneratorState::Completed; + // a. Set generator.[[AsyncGeneratorState]] to completed. + gen.state = AsyncGeneratorState::Completed; - // b. Let result be NormalCompletion(value). - let result = Ok(args.get_or_undefined(0).clone()); + // b. Let result be NormalCompletion(value). + let result = Ok(args.get_or_undefined(0).clone()); - // c. Perform AsyncGeneratorCompleteStep(generator, result, true). - let next = gen.queue.pop_front().expect("must have one entry"); - drop(generator_borrow_mut); - Self::complete_step(&next, result, true, context); + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + let next = gen.queue.pop_front().expect("must have one entry"); + drop(generator_borrow_mut); + Self::complete_step(&next, result, true, context); - // d. Perform AsyncGeneratorDrainQueue(generator). - Self::drain_queue(generator, context); + // d. Perform AsyncGeneratorDrainQueue(generator). + Self::drain_queue(generator, context); - // e. Return undefined. - Ok(JsValue::undefined()) - }, - generator.clone(), + // e. Return undefined. + Ok(JsValue::undefined()) + }, + generator.clone(), + ), ) .name("") .length(1) @@ -660,32 +663,34 @@ impl AsyncGenerator { // 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called: // 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionBuilder::new( context, - |_this, args, generator, context| { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() - .expect("already checked before"); + NativeCallable::from_copy_closure_with_captures( + |_this, args, generator, context| { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); - // a. Set generator.[[AsyncGeneratorState]] to completed. - gen.state = AsyncGeneratorState::Completed; + // a. Set generator.[[AsyncGeneratorState]] to completed. + gen.state = AsyncGeneratorState::Completed; - // b. Let result be ThrowCompletion(reason). - let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); + // b. Let result be ThrowCompletion(reason). + let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); - // c. Perform AsyncGeneratorCompleteStep(generator, result, true). - let next = gen.queue.pop_front().expect("must have one entry"); - drop(generator_borrow_mut); - Self::complete_step(&next, result, true, context); + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + let next = gen.queue.pop_front().expect("must have one entry"); + drop(generator_borrow_mut); + Self::complete_step(&next, result, true, context); - // d. Perform AsyncGeneratorDrainQueue(generator). - Self::drain_queue(generator, context); + // d. Perform AsyncGeneratorDrainQueue(generator). + Self::drain_queue(generator, context); - // e. Return undefined. - Ok(JsValue::undefined()) - }, - generator, + // e. Return undefined. + Ok(JsValue::undefined()) + }, + generator, + ), ) .name("") .length(1) diff --git a/boa_engine/src/builtins/async_generator_function/mod.rs b/boa_engine/src/builtins/async_generator_function/mod.rs index fe6e810221c..dc4aa989baa 100644 --- a/boa_engine/src/builtins/async_generator_function/mod.rs +++ b/boa_engine/src/builtins/async_generator_function/mod.rs @@ -10,6 +10,7 @@ use crate::{ function::{BuiltInFunctionObject, ConstructorKind, Function}, BuiltIn, }, + function::NativeCallable, object::ObjectData, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -67,7 +68,7 @@ impl BuiltIn for AsyncGeneratorFunction { .configurable(false); constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().data = ObjectData::function(Function::Native { - function: Self::constructor, + function: NativeCallable::from_fn_ptr(Self::constructor), constructor: Some(ConstructorKind::Base), }); diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 43170d699eb..7b4d380c27e 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -11,6 +11,7 @@ use crate::{ builtins::{array_buffer::SharedMemoryOrder, typed_array::TypedArrayKind, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -37,17 +38,20 @@ impl BuiltIn for DataView { fn init(context: &mut Context) -> Option { let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_buffer = FunctionBuilder::native(context, Self::get_buffer) - .name("get buffer") - .build(); + let get_buffer = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_buffer)) + .name("get buffer") + .build(); - let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length) - .name("get byteLength") - .build(); + let get_byte_length = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_byte_length)) + .name("get byteLength") + .build(); - let get_byte_offset = FunctionBuilder::native(context, Self::get_byte_offset) - .name("get byteOffset") - .build(); + let get_byte_offset = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_byte_offset)) + .name("get byteOffset") + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 55c262ea5b4..5715e777e95 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -19,6 +19,7 @@ use crate::{ builtins::{function::Function, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, }, @@ -103,7 +104,7 @@ pub(crate) fn create_throw_type_error(context: &mut Context) -> JsObject { let function = JsObject::from_proto_and_data( context.intrinsics().constructors().function().prototype(), ObjectData::function(Function::Native { - function: throw_type_error, + function: NativeCallable::from_fn_ptr(throw_type_error), constructor: None, }), ); diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index b051a8fd6df..0c2f5912c94 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -13,6 +13,7 @@ use crate::{ builtins::{BuiltIn, JsArgs}, environments::DeclarativeEnvironment, error::JsNativeError, + function::NativeCallable, object::FunctionBuilder, property::Attribute, Context, JsResult, JsString, JsValue, @@ -37,7 +38,7 @@ impl BuiltIn for Eval { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let object = FunctionBuilder::native(context, Self::eval) + let object = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::eval)) .name("eval") .length(1) .constructor(false) diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 0c16c1e2113..c1bed6b0a0e 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -17,12 +17,10 @@ use crate::{ context::intrinsics::StandardConstructors, environments::DeclarativeEnvironmentStack, error::JsNativeError, + function::{NativeCallable, NativeFunctionSignature}, js_string, - object::{ - internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object, - ObjectData, - }, - object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut}, + object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData}, + object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement}, property::{Attribute, PropertyDescriptor, PropertyKey}, string::utf16, symbol::WellKnownSymbols, @@ -34,69 +32,20 @@ use boa_ast::{ operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, StatementList, }; -use boa_gc::{self, custom_trace, Finalize, Gc, GcCell, Trace}; +use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; use boa_interner::Sym; use boa_parser::Parser; use boa_profiler::Profiler; -use dyn_clone::DynClone; -use std::{ - any::Any, - fmt, - ops::{Deref, DerefMut}, -}; use tap::{Conv, Pipe}; +use std::fmt; + use super::promise::PromiseCapability; pub(crate) mod arguments; #[cfg(test)] mod tests; -/// Type representing a native built-in function a.k.a. function pointer. -/// -/// Native functions need to have this signature in order to -/// be callable from Javascript. -/// -/// # Arguments -/// -/// - The first argument represents the `this` variable of every Javascript function. -/// -/// - The second argument represents a list of all arguments passed to the function. -/// -/// - The last argument is the [`Context`] of the engine. -pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult; - -// Allows restricting closures to only `Copy` ones. -// Used the sealed pattern to disallow external implementations -// of `DynCopy`. -mod sealed { - pub trait Sealed {} - impl Sealed for T {} -} - -/// This trait is implemented by any type that implements [`core::marker::Copy`]. -pub trait DynCopy: sealed::Sealed {} - -impl DynCopy for T {} - -/// Trait representing a native built-in closure. -/// -/// Closures need to have this signature in order to -/// be callable from Javascript, but most of the time the compiler -/// is smart enough to correctly infer the types. -pub trait ClosureFunctionSignature: - Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + DynCopy + DynClone + 'static -{ -} - -impl ClosureFunctionSignature for T where - T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + Copy + 'static -{ -} - -// Allows cloning Box -dyn_clone::clone_trait_object!(ClosureFunctionSignature); - /// Represents the `[[ThisMode]]` internal slot of function objects. /// /// More information: @@ -193,47 +142,6 @@ unsafe impl Trace for ClassFieldDefinition { }} } -/// Wrapper for `Gc>` that allows passing additional -/// captures through a `Copy` closure. -/// -/// Any type implementing `Trace + Any + Debug` -/// can be used as a capture context, so you can pass e.g. a String, -/// a tuple or even a full struct. -/// -/// You can cast to `Any` with `as_any`, `as_mut_any` and downcast -/// with `Any::downcast_ref` and `Any::downcast_mut` to recover the original -/// type. -#[derive(Clone, Debug, Trace, Finalize)] -pub struct Captures(Gc>>); - -impl Captures { - /// Creates a new capture context. - pub(crate) fn new(captures: T) -> Self - where - T: NativeObject, - { - Self(Gc::new(GcCell::new(Box::new(captures)))) - } - - /// Casts `Captures` to `Any` - /// - /// # Panics - /// - /// Panics if it's already borrowed as `&mut Any` - pub fn as_any(&self) -> boa_gc::GcCellRef<'_, dyn Any> { - Ref::map(self.0.borrow(), |data| data.deref().as_any()) - } - - /// Mutably casts `Captures` to `Any` - /// - /// # Panics - /// - /// Panics if it's already borrowed as `&mut Any` - pub fn as_mut_any(&self) -> boa_gc::GcCellRefMut<'_, Box, dyn Any> { - RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any()) - } -} - /// Boa representation of a Function Object. /// /// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code @@ -245,24 +153,11 @@ pub enum Function { /// A rust function. Native { /// The rust function. - function: NativeFunctionSignature, + function: NativeCallable, /// The kind of the function constructor if it is a constructor. constructor: Option, }, - - /// A rust function that may contain captured values. - Closure { - /// The rust function. - function: Box, - - /// The kind of the function constructor if it is a constructor. - constructor: Option, - - /// The captured values. - captures: Captures, - }, - /// A bytecode function. Ordinary { /// The code block containing the compiled function. @@ -327,8 +222,7 @@ pub enum Function { unsafe impl Trace for Function { custom_trace! {this, { match this { - Self::Native { .. } => {} - Self::Closure { captures, .. } => mark(captures), + Self::Native { function, .. } => {mark(function)} Self::Ordinary { code, environments, home_object, fields, private_methods, .. } => { mark(code); mark(environments); @@ -364,9 +258,7 @@ impl Function { /// Returns true if the function object is a constructor. pub fn is_constructor(&self) -> bool { match self { - Self::Native { constructor, .. } | Self::Closure { constructor, .. } => { - constructor.is_some() - } + Self::Native { constructor, .. } => constructor.is_some(), Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false, Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), } @@ -391,7 +283,7 @@ impl Function { | Self::Async { home_object, .. } | Self::Generator { home_object, .. } | Self::AsyncGenerator { home_object, .. } => home_object.as_ref(), - _ => None, + Self::Native { .. } => None, } } @@ -402,7 +294,7 @@ impl Function { | Self::Async { home_object, .. } | Self::Generator { home_object, .. } | Self::AsyncGenerator { home_object, .. } => *home_object = Some(object), - _ => {} + Self::Native { .. } => {} } } @@ -502,7 +394,7 @@ pub(crate) fn make_builtin_fn( .function() .prototype(), ObjectData::function(Function::Native { - function, + function: NativeCallable::from_fn_ptr(function), constructor: None, }), ); @@ -902,7 +794,7 @@ impl BuiltInFunctionObject { .unwrap_or_else(|| "anonymous".into()); match function { - Function::Native { .. } | Function::Closure { .. } | Function::Ordinary { .. } => { + Function::Native { .. } | Function::Ordinary { .. } => { Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into()) } Function::Async { .. } => { @@ -942,7 +834,7 @@ impl BuiltIn for BuiltInFunctionObject { let _timer = Profiler::global().start_event("function", "init"); let function_prototype = context.intrinsics().constructors().function().prototype(); - FunctionBuilder::native(context, Self::prototype) + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::prototype)) .name("") .length(0) .constructor(false) @@ -950,11 +842,12 @@ impl BuiltIn for BuiltInFunctionObject { let symbol_has_instance = WellKnownSymbols::has_instance(); - let has_instance = FunctionBuilder::native(context, Self::has_instance) - .name("[Symbol.iterator]") - .length(1) - .constructor(false) - .build(); + let has_instance = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::has_instance)) + .name("[Symbol.iterator]") + .length(1) + .constructor(false) + .build(); let throw_type_error = context.intrinsics().objects().throw_type_error(); diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index 8b290bd0129..e1a2c914eac 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -1,6 +1,8 @@ use crate::{ error::JsNativeError, - forward, forward_val, js_string, + forward, forward_val, + function::NativeCallable, + js_string, object::FunctionBuilder, property::{Attribute, PropertyDescriptor}, string::utf16, @@ -243,22 +245,26 @@ fn closure_capture_clone() { ) .unwrap(); - let func = FunctionBuilder::closure_with_captures( + let func = FunctionBuilder::new( &mut context, - |_, _, captures, context| { - let (string, object) = &captures; + NativeCallable::from_copy_closure_with_captures( + |_, _, captures, context| { + let (string, object) = &captures; - let hw = js_string!( - string, - &object - .__get_own_property__(&"key".into(), context)? - .and_then(|prop| prop.value().cloned()) - .and_then(|val| val.as_string().cloned()) - .ok_or_else(|| JsNativeError::typ().with_message("invalid `key` property"))? - ); - Ok(hw.into()) - }, - (string, object), + let hw = js_string!( + string, + &object + .__get_own_property__(&"key".into(), context)? + .and_then(|prop| prop.value().cloned()) + .and_then(|val| val.as_string().cloned()) + .ok_or_else( + || JsNativeError::typ().with_message("invalid `key` property") + )? + ); + Ok(hw.into()) + }, + (string, object), + ), ) .name("closure") .build(); diff --git a/boa_engine/src/builtins/generator_function/mod.rs b/boa_engine/src/builtins/generator_function/mod.rs index 87216474704..99e8e3a973f 100644 --- a/boa_engine/src/builtins/generator_function/mod.rs +++ b/boa_engine/src/builtins/generator_function/mod.rs @@ -15,6 +15,7 @@ use crate::{ function::{BuiltInFunctionObject, ConstructorKind, Function}, BuiltIn, }, + function::NativeCallable, object::ObjectData, property::PropertyDescriptor, symbol::WellKnownSymbols, @@ -72,7 +73,7 @@ impl BuiltIn for GeneratorFunction { .configurable(false); constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().data = ObjectData::function(Function::Native { - function: Self::constructor, + function: NativeCallable::from_fn_ptr(Self::constructor), constructor: Some(ConstructorKind::Base), }); diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index 005ec571ae4..51664b247cd 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -4,6 +4,7 @@ use crate::{ promise::{if_abrupt_reject_promise, PromiseCapability}, JsArgs, Promise, }, + function::NativeCallable, object::{FunctionBuilder, JsObject, ObjectData}, property::PropertyDescriptor, Context, JsNativeError, JsResult, JsValue, @@ -29,18 +30,27 @@ pub(crate) fn create_async_from_sync_iterator_prototype(context: &mut Context) - ObjectData::ordinary(), ); - let next_function = FunctionBuilder::native(context, AsyncFromSyncIterator::next) - .name("next") - .length(1) - .build(); - let return_function = FunctionBuilder::native(context, AsyncFromSyncIterator::r#return) - .name("return") - .length(1) - .build(); - let throw_function = FunctionBuilder::native(context, AsyncFromSyncIterator::throw) - .name("throw") - .length(1) - .build(); + let next_function = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(AsyncFromSyncIterator::next), + ) + .name("next") + .length(1) + .build(); + let return_function = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(AsyncFromSyncIterator::r#return), + ) + .name("return") + .length(1) + .build(); + let throw_function = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(AsyncFromSyncIterator::throw), + ) + .name("throw") + .length(1) + .build(); { let mut prototype_mut = prototype.borrow_mut(); @@ -400,17 +410,16 @@ impl AsyncFromSyncIterator { // 8. Let unwrap be a new Abstract Closure with parameters (value) // that captures done and performs the following steps when called: // 9. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »). - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionBuilder::new( context, - |_this, args, done, context| { + NativeCallable::from_copy_closure(move |_this, args, context| { // a. Return CreateIterResultObject(value, done). Ok(create_iter_result_object( args.get_or_undefined(0).clone(), - *done, + done, context, )) - }, - done, + }), ) .name("") .length(1) diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 7626058f9fc..bbc9fe8085d 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -16,6 +16,7 @@ use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -42,22 +43,24 @@ impl BuiltIn for Map { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); - let get_size = FunctionBuilder::native(context, Self::get_size) + let get_size = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_size)) .name("get size") .length(0) .constructor(false) .build(); - let entries_function = FunctionBuilder::native(context, Self::entries) - .name("entries") - .length(0) - .constructor(false) - .build(); + let entries_function = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::entries)) + .name("entries") + .length(0) + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 5c1c215cdb6..e3959a629d2 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -140,7 +140,7 @@ fn init_builtin(context: &mut Context) { #[inline] pub fn init(context: &mut Context) { macro_rules! globals { - ($( $builtin:ty ),*) => { + ($( $builtin:ty ),*$(,)?) => { $(init_builtin::<$builtin>(context) );* } @@ -197,7 +197,7 @@ pub fn init(context: &mut Context) { AsyncGenerator, AsyncGeneratorFunction, Uri, - WeakRef + WeakRef, }; #[cfg(feature = "intl")] diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index 290c6b7bbfa..d6d2eb6c796 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -17,6 +17,7 @@ use crate::{ builtins::{string::is_trimmable_whitespace, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -50,17 +51,18 @@ impl BuiltIn for Number { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let parse_int = FunctionBuilder::native(context, Self::parse_int) + let parse_int = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::parse_int)) .name("parseInt") .length(2) .constructor(false) .build(); - let parse_float = FunctionBuilder::native(context, Self::parse_float) - .name("parseFloat") - .length(1) - .constructor(false) - .build(); + let parse_float = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::parse_float)) + .name("parseFloat") + .length(1) + .constructor(false) + .build(); context.register_global_property( "parseInt", @@ -73,8 +75,16 @@ impl BuiltIn for Number { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - context.register_global_builtin_function("isFinite", 1, Self::global_is_finite); - context.register_global_builtin_function("isNaN", 1, Self::global_is_nan); + context.register_global_builtin_callable( + "isFinite", + 1, + NativeCallable::from_fn_ptr(Self::global_is_finite), + ); + context.register_global_builtin_callable( + "isNaN", + 1, + NativeCallable::from_fn_ptr(Self::global_is_nan), + ); let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index e3205b56457..1f3de5b38ce 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -20,6 +20,7 @@ use crate::{ builtins::{map, BuiltIn, JsArgs}, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -48,13 +49,19 @@ impl BuiltIn for Object { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let legacy_proto_getter = FunctionBuilder::native(context, Self::legacy_proto_getter) - .name("get __proto__") - .build(); + let legacy_proto_getter = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(Self::legacy_proto_getter), + ) + .name("get __proto__") + .build(); - let legacy_setter_proto = FunctionBuilder::native(context, Self::legacy_proto_setter) - .name("set __proto__") - .build(); + let legacy_setter_proto = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(Self::legacy_proto_setter), + ) + .name("set __proto__") + .build(); ConstructorBuilder::with_standard_constructor( context, @@ -1246,22 +1253,24 @@ impl Object { // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures // obj and performs the following steps when called: - let closure = FunctionBuilder::closure_with_captures( + let closure = FunctionBuilder::new( context, - |_, args, obj, context| { - let key = args.get_or_undefined(0); - let value = args.get_or_undefined(1); + NativeCallable::from_copy_closure_with_captures( + |_, args, obj, context| { + let key = args.get_or_undefined(0); + let value = args.get_or_undefined(1); - // a. Let propertyKey be ? ToPropertyKey(key). - let property_key = key.to_property_key(context)?; + // a. Let propertyKey be ? ToPropertyKey(key). + let property_key = key.to_property_key(context)?; - // b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value). - obj.create_data_property_or_throw(property_key, value, context)?; + // b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value). + obj.create_data_property_or_throw(property_key, value, context)?; - // c. Return undefined. - Ok(JsValue::undefined()) - }, - obj.clone(), + // c. Return undefined. + Ok(JsValue::undefined()) + }, + obj.clone(), + ), ); // 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »). diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 222ab558337..5c55f94fe62 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -11,6 +11,7 @@ use crate::{ builtins::{error::ErrorKind, Array, BuiltIn}, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, job::JobCallback, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -154,37 +155,39 @@ impl PromiseCapability { // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called: // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). - let executor = FunctionBuilder::closure_with_captures( + let executor = FunctionBuilder::new( context, - |_this, args: &[JsValue], captures, _| { - let mut promise_capability = captures.borrow_mut(); - // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. - if !promise_capability.resolve.is_undefined() { - return Err(JsNativeError::typ() - .with_message("promiseCapability.[[Resolve]] is not undefined") - .into()); - } - - // b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception. - if !promise_capability.reject.is_undefined() { - return Err(JsNativeError::typ() - .with_message("promiseCapability.[[Reject]] is not undefined") - .into()); - } - - let resolve = args.get_or_undefined(0); - let reject = args.get_or_undefined(1); - - // c. Set promiseCapability.[[Resolve]] to resolve. - promise_capability.resolve = resolve.clone(); - - // d. Set promiseCapability.[[Reject]] to reject. - promise_capability.reject = reject.clone(); - - // e. Return undefined. - Ok(JsValue::Undefined) - }, - promise_capability.clone(), + NativeCallable::from_copy_closure_with_captures( + |_this, args: &[JsValue], captures, _| { + let mut promise_capability = captures.borrow_mut(); + // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. + if !promise_capability.resolve.is_undefined() { + return Err(JsNativeError::typ() + .with_message("promiseCapability.[[Resolve]] is not undefined") + .into()); + } + + // b. If promiseCapability.[[Reject]] is not undefined, throw a TypeError exception. + if !promise_capability.reject.is_undefined() { + return Err(JsNativeError::typ() + .with_message("promiseCapability.[[Reject]] is not undefined") + .into()); + } + + let resolve = args.get_or_undefined(0); + let reject = args.get_or_undefined(1); + + // c. Set promiseCapability.[[Resolve]] to resolve. + promise_capability.resolve = resolve.clone(); + + // d. Set promiseCapability.[[Reject]] to reject. + promise_capability.reject = reject.clone(); + + // e. Return undefined. + Ok(JsValue::Undefined) + }, + promise_capability.clone(), + ), ) .name("") .length(2) @@ -256,10 +259,11 @@ impl BuiltIn for Promise { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, @@ -545,59 +549,62 @@ impl Promise { // o. Set onFulfilled.[[Values]] to values. // p. Set onFulfilled.[[Capability]] to resultCapability. // q. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions - - // 1. Let F be the active function object. - // 2. If F.[[AlreadyCalled]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } + NativeCallable::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions + + // 1. Let F be the active function object. + // 2. If F.[[AlreadyCalled]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // 3. Set F.[[AlreadyCalled]] to true. - captures.already_called.set(true); + // 3. Set F.[[AlreadyCalled]] to true. + captures.already_called.set(true); - // 4. Let index be F.[[Index]]. - // 5. Let values be F.[[Values]]. - // 6. Let promiseCapability be F.[[Capability]]. - // 7. Let remainingElementsCount be F.[[RemainingElements]]. + // 4. Let index be F.[[Index]]. + // 5. Let values be F.[[Values]]. + // 6. Let promiseCapability be F.[[Capability]]. + // 7. Let remainingElementsCount be F.[[RemainingElements]]. - // 8. Set values[index] to x. - captures.values.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); + // 8. Set values[index] to x. + captures.values.borrow_mut()[captures.index] = + args.get_or_undefined(0).clone(); - // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements_count - .set(captures.remaining_elements_count.get() - 1); + // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements_count + .set(captures.remaining_elements_count.get() - 1); - // 10. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements_count.get() == 0 { - // a. Let valuesArray be CreateArrayFromList(values). - let values_array = crate::builtins::Array::create_array_from_list( - captures.values.borrow().as_slice().iter().cloned(), - context, - ); + // 10. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements_count.get() == 0 { + // a. Let valuesArray be CreateArrayFromList(values). + let values_array = crate::builtins::Array::create_array_from_list( + captures.values.borrow().as_slice().iter().cloned(), + context, + ); - // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). - return captures.capability_resolve.call( - &JsValue::undefined(), - &[values_array.into()], - context, - ); - } + // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). + return captures.capability_resolve.call( + &JsValue::undefined(), + &[values_array.into()], + context, + ); + } - // 11. Return undefined. - Ok(JsValue::undefined()) - }, - ResolveElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - values: values.clone(), - capability_resolve: result_capability.resolve.clone(), - remaining_elements_count: remaining_elements_count.clone(), - }, + // 11. Return undefined. + Ok(JsValue::undefined()) + }, + ResolveElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + values: values.clone(), + capability_resolve: result_capability.resolve.clone(), + remaining_elements_count: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -787,72 +794,78 @@ impl Promise { // p. Set onFulfilled.[[Values]] to values. // q. Set onFulfilled.[[Capability]] to resultCapability. // r. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions + NativeCallable::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions - // 1. Let F be the active function object. - // 2. Let alreadyCalled be F.[[AlreadyCalled]]. + // 1. Let F be the active function object. + // 2. Let alreadyCalled be F.[[AlreadyCalled]]. - // 3. If alreadyCalled.[[Value]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } + // 3. If alreadyCalled.[[Value]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // 4. Set alreadyCalled.[[Value]] to true. - captures.already_called.set(true); + // 4. Set alreadyCalled.[[Value]] to true. + captures.already_called.set(true); - // 5. Let index be F.[[Index]]. - // 6. Let values be F.[[Values]]. - // 7. Let promiseCapability be F.[[Capability]]. - // 8. Let remainingElementsCount be F.[[RemainingElements]]. + // 5. Let index be F.[[Index]]. + // 6. Let values be F.[[Values]]. + // 7. Let promiseCapability be F.[[Capability]]. + // 8. Let remainingElementsCount be F.[[RemainingElements]]. - // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = context.construct_object(); + // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). + let obj = context.construct_object(); - // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). - obj.create_data_property_or_throw("status", "fulfilled", context) - .expect("cannot fail per spec"); + // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "fulfilled"). + obj.create_data_property_or_throw("status", "fulfilled", context) + .expect("cannot fail per spec"); - // 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x). - obj.create_data_property_or_throw("value", args.get_or_undefined(0), context) + // 11. Perform ! CreateDataPropertyOrThrow(obj, "value", x). + obj.create_data_property_or_throw( + "value", + args.get_or_undefined(0), + context, + ) .expect("cannot fail per spec"); - // 12. Set values[index] to obj. - captures.values.borrow_mut()[captures.index] = obj.into(); + // 12. Set values[index] to obj. + captures.values.borrow_mut()[captures.index] = obj.into(); - // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements - .set(captures.remaining_elements.get() - 1); + // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements + .set(captures.remaining_elements.get() - 1); - // 14. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements.get() == 0 { - // a. Let valuesArray be CreateArrayFromList(values). - let values_array = Array::create_array_from_list( - captures.values.borrow().as_slice().iter().cloned(), - context, - ); + // 14. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements.get() == 0 { + // a. Let valuesArray be CreateArrayFromList(values). + let values_array = Array::create_array_from_list( + captures.values.borrow().as_slice().iter().cloned(), + context, + ); - // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). - return captures.capability.call( - &JsValue::undefined(), - &[values_array.into()], - context, - ); - } + // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). + return captures.capability.call( + &JsValue::undefined(), + &[values_array.into()], + context, + ); + } - // 15. Return undefined. - Ok(JsValue::undefined()) - }, - ResolveRejectElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - values: values.clone(), - capability: result_capability.resolve.clone(), - remaining_elements: remaining_elements_count.clone(), - }, + // 15. Return undefined. + Ok(JsValue::undefined()) + }, + ResolveRejectElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + values: values.clone(), + capability: result_capability.resolve.clone(), + remaining_elements: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -867,72 +880,78 @@ impl Promise { // x. Set onRejected.[[Values]] to values. // y. Set onRejected.[[Capability]] to resultCapability. // z. Set onRejected.[[RemainingElements]] to remainingElementsCount. - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions + NativeCallable::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions - // 1. Let F be the active function object. - // 2. Let alreadyCalled be F.[[AlreadyCalled]]. + // 1. Let F be the active function object. + // 2. Let alreadyCalled be F.[[AlreadyCalled]]. - // 3. If alreadyCalled.[[Value]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } + // 3. If alreadyCalled.[[Value]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // 4. Set alreadyCalled.[[Value]] to true. - captures.already_called.set(true); + // 4. Set alreadyCalled.[[Value]] to true. + captures.already_called.set(true); - // 5. Let index be F.[[Index]]. - // 6. Let values be F.[[Values]]. - // 7. Let promiseCapability be F.[[Capability]]. - // 8. Let remainingElementsCount be F.[[RemainingElements]]. + // 5. Let index be F.[[Index]]. + // 6. Let values be F.[[Values]]. + // 7. Let promiseCapability be F.[[Capability]]. + // 8. Let remainingElementsCount be F.[[RemainingElements]]. - // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). - let obj = context.construct_object(); + // 9. Let obj be OrdinaryObjectCreate(%Object.prototype%). + let obj = context.construct_object(); - // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). - obj.create_data_property_or_throw("status", "rejected", context) - .expect("cannot fail per spec"); + // 10. Perform ! CreateDataPropertyOrThrow(obj, "status", "rejected"). + obj.create_data_property_or_throw("status", "rejected", context) + .expect("cannot fail per spec"); - // 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x). - obj.create_data_property_or_throw("reason", args.get_or_undefined(0), context) + // 11. Perform ! CreateDataPropertyOrThrow(obj, "reason", x). + obj.create_data_property_or_throw( + "reason", + args.get_or_undefined(0), + context, + ) .expect("cannot fail per spec"); - // 12. Set values[index] to obj. - captures.values.borrow_mut()[captures.index] = obj.into(); + // 12. Set values[index] to obj. + captures.values.borrow_mut()[captures.index] = obj.into(); - // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements - .set(captures.remaining_elements.get() - 1); + // 13. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements + .set(captures.remaining_elements.get() - 1); - // 14. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements.get() == 0 { - // a. Let valuesArray be CreateArrayFromList(values). - let values_array = Array::create_array_from_list( - captures.values.borrow().as_slice().iter().cloned(), - context, - ); + // 14. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements.get() == 0 { + // a. Let valuesArray be CreateArrayFromList(values). + let values_array = Array::create_array_from_list( + captures.values.borrow().as_slice().iter().cloned(), + context, + ); - // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). - return captures.capability.call( - &JsValue::undefined(), - &[values_array.into()], - context, - ); - } + // b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »). + return captures.capability.call( + &JsValue::undefined(), + &[values_array.into()], + context, + ); + } - // 15. Return undefined. - Ok(JsValue::undefined()) - }, - ResolveRejectElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - values: values.clone(), - capability: result_capability.resolve.clone(), - remaining_elements: remaining_elements_count.clone(), - }, + // 15. Return undefined. + Ok(JsValue::undefined()) + }, + ResolveRejectElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + values: values.clone(), + capability: result_capability.resolve.clone(), + remaining_elements: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -1133,79 +1152,82 @@ impl Promise { // o. Set onRejected.[[Errors]] to errors. // p. Set onRejected.[[Capability]] to resultCapability. // q. Set onRejected.[[RemainingElements]] to remainingElementsCount. - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionBuilder::new( context, - |_, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions - - // 1. Let F be the active function object. - - // 2. If F.[[AlreadyCalled]] is true, return undefined. - if captures.already_called.get() { - return Ok(JsValue::undefined()); - } - - // 3. Set F.[[AlreadyCalled]] to true. - captures.already_called.set(true); - - // 4. Let index be F.[[Index]]. - // 5. Let errors be F.[[Errors]]. - // 6. Let promiseCapability be F.[[Capability]]. - // 7. Let remainingElementsCount be F.[[RemainingElements]]. + NativeCallable::from_copy_closure_with_captures( + |_, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions - // 8. Set errors[index] to x. - captures.errors.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); + // 1. Let F be the active function object. - // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. - captures - .remaining_elements_count - .set(captures.remaining_elements_count.get() - 1); - - // 10. If remainingElementsCount.[[Value]] is 0, then - if captures.remaining_elements_count.get() == 0 { - // a. Let error be a newly created AggregateError object. - let error = JsObject::from_proto_and_data( - context - .intrinsics() - .constructors() - .aggregate_error() - .prototype(), - ObjectData::error(ErrorKind::Aggregate), - ); + // 2. If F.[[AlreadyCalled]] is true, return undefined. + if captures.already_called.get() { + return Ok(JsValue::undefined()); + } - // b. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). - error - .define_property_or_throw( - "errors", - PropertyDescriptorBuilder::new() - .configurable(true) - .enumerable(false) - .writable(true) - .value(Array::create_array_from_list( - captures.errors.borrow().as_slice().iter().cloned(), - context, - )), + // 3. Set F.[[AlreadyCalled]] to true. + captures.already_called.set(true); + + // 4. Let index be F.[[Index]]. + // 5. Let errors be F.[[Errors]]. + // 6. Let promiseCapability be F.[[Capability]]. + // 7. Let remainingElementsCount be F.[[RemainingElements]]. + + // 8. Set errors[index] to x. + captures.errors.borrow_mut()[captures.index] = + args.get_or_undefined(0).clone(); + + // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. + captures + .remaining_elements_count + .set(captures.remaining_elements_count.get() - 1); + + // 10. If remainingElementsCount.[[Value]] is 0, then + if captures.remaining_elements_count.get() == 0 { + // a. Let error be a newly created AggregateError object. + let error = JsObject::from_proto_and_data( + context + .intrinsics() + .constructors() + .aggregate_error() + .prototype(), + ObjectData::error(ErrorKind::Aggregate), + ); + + // b. Perform ! DefinePropertyOrThrow(error, "errors", PropertyDescriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: CreateArrayFromList(errors) }). + error + .define_property_or_throw( + "errors", + PropertyDescriptorBuilder::new() + .configurable(true) + .enumerable(false) + .writable(true) + .value(Array::create_array_from_list( + captures.errors.borrow().as_slice().iter().cloned(), + context, + )), + context, + ) + .expect("cannot fail per spec"); + // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). + return captures.capability_reject.call( + &JsValue::undefined(), + &[error.into()], context, - ) - .expect("cannot fail per spec"); - // c. Return ? Call(promiseCapability.[[Reject]], undefined, « error »). - return captures.capability_reject.call( - &JsValue::undefined(), - &[error.into()], - context, - ); - } + ); + } - // 11. Return undefined. - Ok(JsValue::undefined()) - }, - RejectElementCaptures { - already_called: Rc::new(Cell::new(false)), - index, - errors: errors.clone(), - capability_reject: result_capability.reject.clone(), - remaining_elements_count: remaining_elements_count.clone(), - }, + // 11. Return undefined. + Ok(JsValue::undefined()) + }, + RejectElementCaptures { + already_called: Rc::new(Cell::new(false)), + index, + errors: errors.clone(), + capability_reject: result_capability.reject.clone(), + remaining_elements_count: remaining_elements_count.clone(), + }, + ), ) .name("") .length(1) @@ -1257,49 +1279,50 @@ impl Promise { // 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. // 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions. // 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »). - let resolve = FunctionBuilder::closure_with_captures( + let resolve = FunctionBuilder::new( context, - |_this, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise-resolve-functions - - // 1. Let F be the active function object. - // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. - // 3. Let promise be F.[[Promise]]. - // 4. Let alreadyResolved be F.[[AlreadyResolved]]. - let RejectResolveCaptures { - promise, - already_resolved, - } = captures; + NativeCallable::from_copy_closure_with_captures( + |_this, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise-resolve-functions - // 5. If alreadyResolved.[[Value]] is true, return undefined. - if already_resolved.get() { - return Ok(JsValue::Undefined); - } + // 1. Let F be the active function object. + // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. + // 3. Let promise be F.[[Promise]]. + // 4. Let alreadyResolved be F.[[AlreadyResolved]]. + let RejectResolveCaptures { + promise, + already_resolved, + } = captures; + + // 5. If alreadyResolved.[[Value]] is true, return undefined. + if already_resolved.get() { + return Ok(JsValue::Undefined); + } - // 6. Set alreadyResolved.[[Value]] to true. - already_resolved.set(true); + // 6. Set alreadyResolved.[[Value]] to true. + already_resolved.set(true); - let resolution = args.get_or_undefined(0); + let resolution = args.get_or_undefined(0); - // 7. If SameValue(resolution, promise) is true, then - if JsValue::same_value(resolution, &promise.clone().into()) { - // a. Let selfResolutionError be a newly created TypeError object. - let self_resolution_error = JsNativeError::typ() - .with_message("SameValue(resolution, promise) is true") - .to_opaque(context); + // 7. If SameValue(resolution, promise) is true, then + if JsValue::same_value(resolution, &promise.clone().into()) { + // a. Let selfResolutionError be a newly created TypeError object. + let self_resolution_error = JsNativeError::typ() + .with_message("SameValue(resolution, promise) is true") + .to_opaque(context); - // b. Perform RejectPromise(promise, selfResolutionError). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .reject_promise(&self_resolution_error.into(), context); + // b. Perform RejectPromise(promise, selfResolutionError). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .reject_promise(&self_resolution_error.into(), context); - // c. Return undefined. - return Ok(JsValue::Undefined); - } + // c. Return undefined. + return Ok(JsValue::Undefined); + } - let Some(then) = resolution.as_object() else { + let Some(then) = resolution.as_object() else { // 8. If Type(resolution) is not Object, then // a. Perform FulfillPromise(promise, resolution). promise @@ -1312,58 +1335,59 @@ impl Promise { return Ok(JsValue::Undefined); }; - // 9. Let then be Completion(Get(resolution, "then")). - let then_action = match then.get("then", context) { - // 10. If then is an abrupt completion, then - Err(e) => { - // a. Perform RejectPromise(promise, then.[[Value]]). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .reject_promise(&e.to_opaque(context), context); - - // b. Return undefined. - return Ok(JsValue::Undefined); - } - // 11. Let thenAction be then.[[Value]]. - Ok(then) => then, - }; - - // 12. If IsCallable(thenAction) is false, then - let then_action = match then_action.as_object() { - Some(then_action) if then_action.is_callable() => then_action, - _ => { - // a. Perform FulfillPromise(promise, resolution). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .fulfill_promise(resolution, context); - - // b. Return undefined. - return Ok(JsValue::Undefined); - } - }; + // 9. Let then be Completion(Get(resolution, "then")). + let then_action = match then.get("then", context) { + // 10. If then is an abrupt completion, then + Err(e) => { + // a. Perform RejectPromise(promise, then.[[Value]]). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .reject_promise(&e.to_opaque(context), context); + + // b. Return undefined. + return Ok(JsValue::Undefined); + } + // 11. Let thenAction be then.[[Value]]. + Ok(then) => then, + }; + + // 12. If IsCallable(thenAction) is false, then + let then_action = match then_action.as_object() { + Some(then_action) if then_action.is_callable() => then_action, + _ => { + // a. Perform FulfillPromise(promise, resolution). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .fulfill_promise(resolution, context); + + // b. Return undefined. + return Ok(JsValue::Undefined); + } + }; - // 13. Let thenJobCallback be HostMakeJobCallback(thenAction). - let then_job_callback = JobCallback::make_job_callback(then_action.clone()); + // 13. Let thenJobCallback be HostMakeJobCallback(thenAction). + let then_job_callback = JobCallback::make_job_callback(then_action.clone()); - // 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback). - let job: JobCallback = PromiseJob::new_promise_resolve_thenable_job( - promise.clone(), - resolution.clone(), - then_job_callback, - context, - ); + // 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback). + let job: JobCallback = PromiseJob::new_promise_resolve_thenable_job( + promise.clone(), + resolution.clone(), + then_job_callback, + context, + ); - // 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). - context.host_enqueue_promise_job(job); + // 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). + context.host_enqueue_promise_job(job); - // 16. Return undefined. - Ok(JsValue::Undefined) - }, - resolve_captures, + // 16. Return undefined. + Ok(JsValue::Undefined) + }, + resolve_captures, + ), ) .name("") .length(1) @@ -1380,40 +1404,42 @@ impl Promise { // 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions. // 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions. // 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »). - let reject = FunctionBuilder::closure_with_captures( + let reject = FunctionBuilder::new( context, - |_this, args, captures, context| { - // https://tc39.es/ecma262/#sec-promise-reject-functions - - // 1. Let F be the active function object. - // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. - // 3. Let promise be F.[[Promise]]. - // 4. Let alreadyResolved be F.[[AlreadyResolved]]. - let RejectResolveCaptures { - promise, - already_resolved, - } = captures; + NativeCallable::from_copy_closure_with_captures( + |_this, args, captures, context| { + // https://tc39.es/ecma262/#sec-promise-reject-functions - // 5. If alreadyResolved.[[Value]] is true, return undefined. - if already_resolved.get() { - return Ok(JsValue::Undefined); - } + // 1. Let F be the active function object. + // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. + // 3. Let promise be F.[[Promise]]. + // 4. Let alreadyResolved be F.[[AlreadyResolved]]. + let RejectResolveCaptures { + promise, + already_resolved, + } = captures; + + // 5. If alreadyResolved.[[Value]] is true, return undefined. + if already_resolved.get() { + return Ok(JsValue::Undefined); + } - // 6. Set alreadyResolved.[[Value]] to true. - already_resolved.set(true); + // 6. Set alreadyResolved.[[Value]] to true. + already_resolved.set(true); - // let reason = args.get_or_undefined(0); - // 7. Perform RejectPromise(promise, reason). - promise - .borrow_mut() - .as_promise_mut() - .expect("Expected promise to be a Promise") - .reject_promise(args.get_or_undefined(0), context); + // let reason = args.get_or_undefined(0); + // 7. Perform RejectPromise(promise, reason). + promise + .borrow_mut() + .as_promise_mut() + .expect("Expected promise to be a Promise") + .reject_promise(args.get_or_undefined(0), context); - // 8. Return undefined. - Ok(JsValue::Undefined) - }, - reject_captures, + // 8. Return undefined. + Ok(JsValue::Undefined) + }, + reject_captures, + ), ) .name("") .length(1) @@ -1795,90 +1821,100 @@ impl Promise { } // a. Let thenFinallyClosure be a new Abstract Closure with parameters (value) that captures onFinally and C and performs the following steps when called: - let then_finally_closure = FunctionBuilder::closure_with_captures( + let then_finally_closure = FunctionBuilder::new( context, - |_this, args, captures, context| { - /// Capture object for the abstract `returnValue` closure. - #[derive(Debug, Trace, Finalize)] - struct ReturnValueCaptures { - value: JsValue, - } + NativeCallable::from_copy_closure_with_captures( + |_this, args, captures, context| { + /// Capture object for the abstract `returnValue` closure. + #[derive(Debug, Trace, Finalize)] + struct ReturnValueCaptures { + value: JsValue, + } - let value = args.get_or_undefined(0); + let value = args.get_or_undefined(0); - // i. Let result be ? Call(onFinally, undefined). - let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?; + // i. Let result be ? Call(onFinally, undefined). + let result = + context.call(&captures.on_finally, &JsValue::undefined(), &[])?; - // ii. Let promise be ? PromiseResolve(C, result). - let promise = Self::promise_resolve(captures.c.clone(), result, context)?; + // ii. Let promise be ? PromiseResolve(C, result). + let promise = Self::promise_resolve(captures.c.clone(), result, context)?; - // iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called: - let return_value = FunctionBuilder::closure_with_captures( - context, - |_this, _args, captures, _context| { - // 1. Return value. - Ok(captures.value.clone()) - }, - ReturnValueCaptures { - value: value.clone(), - }, - ); + // iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called: + let return_value = FunctionBuilder::new( + context, + NativeCallable::from_copy_closure_with_captures( + |_this, _args, captures, _context| { + // 1. Return value. + Ok(captures.value.clone()) + }, + ReturnValueCaptures { + value: value.clone(), + }, + ), + ); - // iv. Let valueThunk be CreateBuiltinFunction(returnValue, 0, "", « »). - let value_thunk = return_value.length(0).name("").build(); + // iv. Let valueThunk be CreateBuiltinFunction(returnValue, 0, "", « »). + let value_thunk = return_value.length(0).name("").build(); - // v. Return ? Invoke(promise, "then", « valueThunk »). - promise.invoke("then", &[value_thunk.into()], context) - }, - FinallyCaptures { - on_finally: on_finally.clone(), - c: c.clone(), - }, + // v. Return ? Invoke(promise, "then", « valueThunk »). + promise.invoke("then", &[value_thunk.into()], context) + }, + FinallyCaptures { + on_finally: on_finally.clone(), + c: c.clone(), + }, + ), ); // b. Let thenFinally be CreateBuiltinFunction(thenFinallyClosure, 1, "", « »). let then_finally = then_finally_closure.length(1).name("").build(); // c. Let catchFinallyClosure be a new Abstract Closure with parameters (reason) that captures onFinally and C and performs the following steps when called: - let catch_finally_closure = FunctionBuilder::closure_with_captures( + let catch_finally_closure = FunctionBuilder::new( context, - |_this, args, captures, context| { - /// Capture object for the abstract `throwReason` closure. - #[derive(Debug, Trace, Finalize)] - struct ThrowReasonCaptures { - reason: JsValue, - } + NativeCallable::from_copy_closure_with_captures( + |_this, args, captures, context| { + /// Capture object for the abstract `throwReason` closure. + #[derive(Debug, Trace, Finalize)] + struct ThrowReasonCaptures { + reason: JsValue, + } - let reason = args.get_or_undefined(0); + let reason = args.get_or_undefined(0); - // i. Let result be ? Call(onFinally, undefined). - let result = context.call(&captures.on_finally, &JsValue::undefined(), &[])?; + // i. Let result be ? Call(onFinally, undefined). + let result = + context.call(&captures.on_finally, &JsValue::undefined(), &[])?; - // ii. Let promise be ? PromiseResolve(C, result). - let promise = Self::promise_resolve(captures.c.clone(), result, context)?; + // ii. Let promise be ? PromiseResolve(C, result). + let promise = Self::promise_resolve(captures.c.clone(), result, context)?; - // iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called: - let throw_reason = FunctionBuilder::closure_with_captures( - context, - |_this, _args, captures, _context| { - // 1. Return ThrowCompletion(reason). - Err(JsError::from_opaque(captures.reason.clone())) - }, - ThrowReasonCaptures { - reason: reason.clone(), - }, - ); + // iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called: + let throw_reason = FunctionBuilder::new( + context, + NativeCallable::from_copy_closure_with_captures( + |_this, _args, captures, _context| { + // 1. Return ThrowCompletion(reason). + Err(JsError::from_opaque(captures.reason.clone())) + }, + ThrowReasonCaptures { + reason: reason.clone(), + }, + ), + ); - // iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »). - let thrower = throw_reason.length(0).name("").build(); + // iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »). + let thrower = throw_reason.length(0).name("").build(); - // v. Return ? Invoke(promise, "then", « thrower »). - promise.invoke("then", &[thrower.into()], context) - }, - FinallyCaptures { - on_finally: on_finally.clone(), - c, - }, + // v. Return ? Invoke(promise, "then", « thrower »). + promise.invoke("then", &[thrower.into()], context) + }, + FinallyCaptures { + on_finally: on_finally.clone(), + c, + }, + ), ); // d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »). diff --git a/boa_engine/src/builtins/promise/promise_job.rs b/boa_engine/src/builtins/promise/promise_job.rs index af97c6887b6..356337f279a 100644 --- a/boa_engine/src/builtins/promise/promise_job.rs +++ b/boa_engine/src/builtins/promise/promise_job.rs @@ -1,6 +1,7 @@ use super::{Promise, PromiseCapability}; use crate::{ builtins::promise::{ReactionRecord, ReactionType}, + function::NativeCallable, job::JobCallback, object::{FunctionBuilder, JsObject}, Context, JsValue, @@ -27,75 +28,85 @@ impl PromiseJob { } // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called: - let job = FunctionBuilder::closure_with_captures( + let job = FunctionBuilder::new( context, - |_this, _args, captures, context| { - let ReactionJobCaptures { reaction, argument } = captures; - - let ReactionRecord { - // a. Let promiseCapability be reaction.[[Capability]]. - promise_capability, - // b. Let type be reaction.[[Type]]. - reaction_type, - // c. Let handler be reaction.[[Handler]]. - handler, - } = reaction; - - let handler_result = match handler { - // d. If handler is empty, then - None => match reaction_type { - // i. If type is Fulfill, let handlerResult be NormalCompletion(argument). - ReactionType::Fulfill => Ok(argument.clone()), - // ii. Else, - // 1. Assert: type is Reject. - ReactionType::Reject => { - // 2. Let handlerResult be ThrowCompletion(argument). - Err(argument.clone()) - } - }, - // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). - Some(handler) => handler - .call_job_callback(&JsValue::Undefined, &[argument.clone()], context) - .map_err(|e| e.to_opaque(context)), - }; - - match promise_capability { - None => { - // f. If promiseCapability is undefined, then - // i. Assert: handlerResult is not an abrupt completion. - assert!( - handler_result.is_ok(), - "Assertion: failed" - ); - - // ii. Return empty. - Ok(JsValue::Undefined) - } - Some(promise_capability_record) => { - // g. Assert: promiseCapability is a PromiseCapability Record. - let PromiseCapability { - promise: _, - resolve, - reject, - } = promise_capability_record; - - match handler_result { - // h. If handlerResult is an abrupt completion, then - Err(value) => { - // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). - context.call(&reject.clone().into(), &JsValue::Undefined, &[value]) + NativeCallable::from_copy_closure_with_captures( + |_this, _args, captures, context| { + let ReactionJobCaptures { reaction, argument } = captures; + + let ReactionRecord { + // a. Let promiseCapability be reaction.[[Capability]]. + promise_capability, + // b. Let type be reaction.[[Type]]. + reaction_type, + // c. Let handler be reaction.[[Handler]]. + handler, + } = reaction; + + let handler_result = match handler { + // d. If handler is empty, then + None => match reaction_type { + // i. If type is Fulfill, let handlerResult be NormalCompletion(argument). + ReactionType::Fulfill => Ok(argument.clone()), + // ii. Else, + // 1. Assert: type is Reject. + ReactionType::Reject => { + // 2. Let handlerResult be ThrowCompletion(argument). + Err(argument.clone()) } - - // i. Else, - Ok(value) => { - // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). - context.call(&resolve.clone().into(), &JsValue::Undefined, &[value]) + }, + // e. Else, let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). + Some(handler) => handler + .call_job_callback(&JsValue::Undefined, &[argument.clone()], context) + .map_err(|e| e.to_opaque(context)), + }; + + match promise_capability { + None => { + // f. If promiseCapability is undefined, then + // i. Assert: handlerResult is not an abrupt completion. + assert!( + handler_result.is_ok(), + "Assertion: failed" + ); + + // ii. Return empty. + Ok(JsValue::Undefined) + } + Some(promise_capability_record) => { + // g. Assert: promiseCapability is a PromiseCapability Record. + let PromiseCapability { + promise: _, + resolve, + reject, + } = promise_capability_record; + + match handler_result { + // h. If handlerResult is an abrupt completion, then + Err(value) => { + // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). + context.call( + &reject.clone().into(), + &JsValue::Undefined, + &[value], + ) + } + + // i. Else, + Ok(value) => { + // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). + context.call( + &resolve.clone().into(), + &JsValue::Undefined, + &[value], + ) + } } } } - } - }, - ReactionJobCaptures { reaction, argument }, + }, + ReactionJobCaptures { reaction, argument }, + ), ) .build() .into(); @@ -121,44 +132,46 @@ impl PromiseJob { context: &mut Context, ) -> JobCallback { // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, thenable, and then and performs the following steps when called: - let job = FunctionBuilder::closure_with_captures( + let job = FunctionBuilder::new( context, - |_this: &JsValue, _args: &[JsValue], captures, context: &mut Context| { - let JobCapture { - promise_to_resolve, - thenable, - then, - } = captures; - - // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). - let resolving_functions = - Promise::create_resolving_functions(promise_to_resolve, context); - - // b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)). - let then_call_result = then.call_job_callback( - thenable, - &[ - resolving_functions.resolve, - resolving_functions.reject.clone(), - ], - context, - ); - - // c. If thenCallResult is an abrupt completion, then - if let Err(value) = then_call_result { - let value = value.to_opaque(context); - // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). - return context.call( - &resolving_functions.reject, - &JsValue::Undefined, - &[value], + NativeCallable::from_copy_closure_with_captures( + |_this: &JsValue, _args: &[JsValue], captures, context: &mut Context| { + let JobCapture { + promise_to_resolve, + thenable, + then, + } = captures; + + // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). + let resolving_functions = + Promise::create_resolving_functions(promise_to_resolve, context); + + // b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)). + let then_call_result = then.call_job_callback( + thenable, + &[ + resolving_functions.resolve, + resolving_functions.reject.clone(), + ], + context, ); - } - // d. Return ? thenCallResult. - then_call_result - }, - JobCapture::new(promise_to_resolve, thenable, then), + // c. If thenCallResult is an abrupt completion, then + if let Err(value) = then_call_result { + let value = value.to_opaque(context); + // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). + return context.call( + &resolving_functions.reject, + &JsValue::Undefined, + &[value], + ); + } + + // d. Return ? thenCallResult. + then_call_result + }, + JobCapture::new(promise_to_resolve, thenable, then), + ), ) .build(); diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 9a771a4e18f..cafdf88018a 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -10,9 +10,12 @@ //! [spec]: https://tc39.es/ecma262/#sec-proxy-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy +use std::cell::Cell; + use crate::{ builtins::{BuiltIn, JsArgs}, error::JsNativeError, + function::NativeCallable, object::{ConstructorBuilder, FunctionBuilder, JsFunction, JsObject, ObjectData}, Context, JsResult, JsValue, }; @@ -134,27 +137,29 @@ impl Proxy { pub(crate) fn revoker(proxy: JsObject, context: &mut Context) -> JsFunction { // 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »). // 4. Set revoker.[[RevocableProxy]] to p. - FunctionBuilder::closure_with_captures( + FunctionBuilder::new( context, - |_, _, revocable_proxy, _| { - // a. Let F be the active function object. - // b. Let p be F.[[RevocableProxy]]. - // d. Set F.[[RevocableProxy]] to null. - if let Some(p) = revocable_proxy.take() { - // e. Assert: p is a Proxy object. - // f. Set p.[[ProxyTarget]] to null. - // g. Set p.[[ProxyHandler]] to null. - p.borrow_mut() - .as_proxy_mut() - .expect("[[RevocableProxy]] must be a proxy object") - .data = None; - } - - // c. If p is null, return undefined. - // h. Return undefined. - Ok(JsValue::undefined()) - }, - Some(proxy), + NativeCallable::from_copy_closure_with_captures( + |_, _, revocable_proxy, _| { + // a. Let F be the active function object. + // b. Let p be F.[[RevocableProxy]]. + // d. Set F.[[RevocableProxy]] to null. + if let Some(p) = revocable_proxy.take() { + // e. Assert: p is a Proxy object. + // f. Set p.[[ProxyTarget]] to null. + // g. Set p.[[ProxyHandler]] to null. + p.borrow_mut() + .as_proxy_mut() + .expect("[[RevocableProxy]] must be a proxy object") + .data = None; + } + + // c. If p is null, return undefined. + // h. Return undefined. + Ok(JsValue::undefined()) + }, + Cell::new(Some(proxy)), + ), ) .build() } diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index d1e34cca8c9..66cb5a598ad 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -17,6 +17,7 @@ use crate::{ builtins::{array::Array, string, BuiltIn}, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -53,49 +54,58 @@ impl BuiltIn for RegExp { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; - let get_has_indices = FunctionBuilder::native(context, Self::get_has_indices) - .name("get hasIndices") - .constructor(false) - .build(); - let get_global = FunctionBuilder::native(context, Self::get_global) - .name("get global") - .constructor(false) - .build(); - let get_ignore_case = FunctionBuilder::native(context, Self::get_ignore_case) - .name("get ignoreCase") - .constructor(false) - .build(); - let get_multiline = FunctionBuilder::native(context, Self::get_multiline) - .name("get multiline") - .constructor(false) - .build(); - let get_dot_all = FunctionBuilder::native(context, Self::get_dot_all) - .name("get dotAll") - .constructor(false) - .build(); - let get_unicode = FunctionBuilder::native(context, Self::get_unicode) - .name("get unicode") - .constructor(false) - .build(); - let get_sticky = FunctionBuilder::native(context, Self::get_sticky) - .name("get sticky") - .constructor(false) - .build(); - let get_flags = FunctionBuilder::native(context, Self::get_flags) + let get_has_indices = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_has_indices)) + .name("get hasIndices") + .constructor(false) + .build(); + let get_global = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_global)) + .name("get global") + .constructor(false) + .build(); + let get_ignore_case = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_ignore_case)) + .name("get ignoreCase") + .constructor(false) + .build(); + let get_multiline = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_multiline)) + .name("get multiline") + .constructor(false) + .build(); + let get_dot_all = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_dot_all)) + .name("get dotAll") + .constructor(false) + .build(); + let get_unicode = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_unicode)) + .name("get unicode") + .constructor(false) + .build(); + let get_sticky = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_sticky)) + .name("get sticky") + .constructor(false) + .build(); + let get_flags = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_flags)) .name("get flags") .constructor(false) .build(); - let get_source = FunctionBuilder::native(context, Self::get_source) - .name("get source") - .constructor(false) - .build(); + let get_source = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_source)) + .name("get source") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, Self::constructor, diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 928ba7b7d72..50a3ae7ec5e 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -16,6 +16,7 @@ use crate::{ builtins::BuiltIn, context::intrinsics::StandardConstructors, error::JsNativeError, + function::NativeCallable, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, @@ -41,25 +42,28 @@ impl BuiltIn for Set { fn init(context: &mut Context) -> Option { let _timer = Profiler::global().start_event(Self::NAME, "init"); - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); - let size_getter = FunctionBuilder::native(context, Self::size_getter) - .constructor(false) - .name("get size") - .build(); + let size_getter = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::size_getter)) + .constructor(false) + .name("get size") + .build(); let iterator_symbol = WellKnownSymbols::iterator(); let to_string_tag = WellKnownSymbols::to_string_tag(); - let values_function = FunctionBuilder::native(context, Self::values) - .name("values") - .length(0) - .constructor(false) - .build(); + let values_function = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::values)) + .name("values") + .length(0) + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index 6d81a81dc71..ae6277caea4 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -22,6 +22,7 @@ use super::JsArgs; use crate::{ builtins::BuiltIn, error::JsNativeError, + function::NativeCallable, object::{ConstructorBuilder, FunctionBuilder}, property::Attribute, symbol::{JsSymbol, WellKnownSymbols}, @@ -96,16 +97,18 @@ impl BuiltIn for Symbol { let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; - let to_primitive = FunctionBuilder::native(context, Self::to_primitive) - .name("[Symbol.toPrimitive]") - .length(1) - .constructor(false) - .build(); + let to_primitive = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::to_primitive)) + .name("[Symbol.toPrimitive]") + .length(1) + .constructor(false) + .build(); - let get_description = FunctionBuilder::native(context, Self::get_description) - .name("get description") - .constructor(false) - .build(); + let get_description = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_description)) + .name("get description") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index ba4c12f8579..13970563b95 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -21,6 +21,7 @@ use crate::{ }, context::intrinsics::{StandardConstructor, StandardConstructors}, error::JsNativeError, + function::NativeCallable, js_string, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, @@ -66,10 +67,13 @@ macro_rules! typed_array { .typed_array() .prototype(); - let get_species = FunctionBuilder::native(context, TypedArray::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(TypedArray::get_species), + ) + .name("get [Symbol.species]") + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, @@ -250,41 +254,46 @@ pub(crate) struct TypedArray; impl BuiltIn for TypedArray { const NAME: &'static str = "TypedArray"; fn init(context: &mut Context) -> Option { - let get_species = FunctionBuilder::native(context, Self::get_species) - .name("get [Symbol.species]") - .constructor(false) - .build(); + let get_species = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::get_species)) + .name("get [Symbol.species]") + .constructor(false) + .build(); - let get_buffer = FunctionBuilder::native(context, Self::buffer) + let get_buffer = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::buffer)) .name("get buffer") .constructor(false) .build(); - let get_byte_length = FunctionBuilder::native(context, Self::byte_length) - .name("get byteLength") - .constructor(false) - .build(); + let get_byte_length = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::byte_length)) + .name("get byteLength") + .constructor(false) + .build(); - let get_byte_offset = FunctionBuilder::native(context, Self::byte_offset) - .name("get byteOffset") - .constructor(false) - .build(); + let get_byte_offset = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::byte_offset)) + .name("get byteOffset") + .constructor(false) + .build(); - let get_length = FunctionBuilder::native(context, Self::length) + let get_length = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::length)) .name("get length") .constructor(false) .build(); - let get_to_string_tag = FunctionBuilder::native(context, Self::to_string_tag) - .name("get [Symbol.toStringTag]") - .constructor(false) - .build(); - - let values_function = FunctionBuilder::native(context, Self::values) - .name("values") - .length(0) - .constructor(false) - .build(); + let get_to_string_tag = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::to_string_tag)) + .name("get [Symbol.toStringTag]") + .constructor(false) + .build(); + + let values_function = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::values)) + .name("values") + .length(0) + .constructor(false) + .build(); ConstructorBuilder::with_standard_constructor( context, diff --git a/boa_engine/src/builtins/uri/mod.rs b/boa_engine/src/builtins/uri/mod.rs index 9292e65936b..d7803e5b072 100644 --- a/boa_engine/src/builtins/uri/mod.rs +++ b/boa_engine/src/builtins/uri/mod.rs @@ -19,8 +19,8 @@ use self::consts::{ use super::BuiltIn; use crate::{ - builtins::JsArgs, js_string, object::FunctionBuilder, property::Attribute, string::CodePoint, - Context, JsNativeError, JsResult, JsString, JsValue, + builtins::JsArgs, function::NativeCallable, js_string, object::FunctionBuilder, + property::Attribute, string::CodePoint, Context, JsNativeError, JsResult, JsString, JsValue, }; /// URI Handling Functions @@ -31,11 +31,12 @@ impl BuiltIn for Uri { const NAME: &'static str = "Uri"; fn init(context: &mut Context) -> Option { - let decode_uri = FunctionBuilder::native(context, Self::decode_uri) - .name("decodeURI") - .length(1) - .constructor(false) - .build(); + let decode_uri = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::decode_uri)) + .name("decodeURI") + .length(1) + .constructor(false) + .build(); context.register_global_property( "decodeURI", @@ -43,11 +44,14 @@ impl BuiltIn for Uri { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - let decode_uri_component = FunctionBuilder::native(context, Self::decode_uri_component) - .name("decodeURIComponent") - .length(1) - .constructor(false) - .build(); + let decode_uri_component = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(Self::decode_uri_component), + ) + .name("decodeURIComponent") + .length(1) + .constructor(false) + .build(); context.register_global_property( "decodeURIComponent", @@ -55,11 +59,12 @@ impl BuiltIn for Uri { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - let encode_uri = FunctionBuilder::native(context, Self::encode_uri) - .name("encodeURI") - .length(1) - .constructor(false) - .build(); + let encode_uri = + FunctionBuilder::new(context, NativeCallable::from_fn_ptr(Self::encode_uri)) + .name("encodeURI") + .length(1) + .constructor(false) + .build(); context.register_global_property( "encodeURI", @@ -67,11 +72,14 @@ impl BuiltIn for Uri { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, ); - let encode_uri_component = FunctionBuilder::native(context, Self::encode_uri_component) - .name("encodeURIComponent") - .length(1) - .constructor(false) - .build(); + let encode_uri_component = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(Self::encode_uri_component), + ) + .name("encodeURIComponent") + .length(1) + .constructor(false) + .build(); context.register_global_property( "encodeURIComponent", diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index 69a5184a50c..251ae1a9cd6 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -62,8 +62,8 @@ //! [class-trait]: ./trait.Class.html use crate::{ - builtins::function::NativeFunctionSignature, error::JsNativeError, + function::NativeFunctionSignature, object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE}, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsResult, JsValue, diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 41de6029ef6..a5762b4ebed 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -12,10 +12,11 @@ use intrinsics::{IntrinsicObjects, Intrinsics}; #[cfg(feature = "console")] use crate::builtins::console::Console; use crate::{ - builtins::{self, function::NativeFunctionSignature}, + builtins, bytecompiler::ByteCompiler, class::{Class, ClassBuilder}, error::JsNativeError, + function::NativeCallable, job::JobCallback, object::{FunctionBuilder, GlobalPropertyMap, JsObject, ObjectData}, property::{Attribute, PropertyDescriptor, PropertyKey}, @@ -238,10 +239,7 @@ impl Context { .expect("Into used as message") } - /// Register a global native function. - /// - /// This is more efficient that creating a closure function, since this does not allocate, - /// it is just a function pointer. + /// Register a global native callable. /// /// The function will be both `constructable` (call with `new ()`) and `callable` (call /// with `()`). @@ -251,19 +249,13 @@ impl Context { /// /// # Note /// - /// If you want to make a function only `constructable`, or wish to bind it differently - /// to the global object, you can create the function object with - /// [`FunctionBuilder`](crate::object::FunctionBuilder::native). And bind it to the global - /// object with [`Context::register_global_property`](Context::register_global_property) + /// If you wish to bind the function differently to the global object, you can create the + /// function object with [`FunctionBuilder`](crate::object::FunctionBuilder::native) and bind it + /// to the global object with [`Context::register_global_property`](Context::register_global_property) /// method. #[inline] - pub fn register_global_function( - &mut self, - name: &str, - length: usize, - body: NativeFunctionSignature, - ) { - let function = FunctionBuilder::native(self, body) + pub fn register_global_callable(&mut self, name: &str, length: usize, body: NativeCallable) { + let function = FunctionBuilder::new(self, body) .name(name) .length(length) .constructor(true) @@ -282,25 +274,21 @@ impl Context { /// Register a global native function that is not a constructor. /// - /// This is more efficient that creating a closure function, since this does not allocate, - /// it is just a function pointer. - /// /// The function will be bound to the global object with `writable`, `non-enumerable` /// and `configurable` attributes. The same as when you create a function in JavaScript. /// /// # Note /// - /// The difference to [`Context::register_global_function`](Context::register_global_function) is, - /// that the function will not be `constructable`. - /// Usage of the function as a constructor will produce a `TypeError`. + /// The difference to [`Context::register_global_callable`] is, that the function will not be + /// `constructable`. Usage of the function as a constructor will produce a `TypeError`. #[inline] - pub fn register_global_builtin_function( + pub fn register_global_builtin_callable( &mut self, name: &str, length: usize, - body: NativeFunctionSignature, + body: NativeCallable, ) { - let function = FunctionBuilder::native(self, body) + let function = FunctionBuilder::new(self, body) .name(name) .length(length) .constructor(false) @@ -317,52 +305,6 @@ impl Context { ); } - /// Register a global closure function. - /// - /// The function will be both `constructable` (call with `new`). - /// - /// The function will be bound to the global object with `writable`, `non-enumerable` - /// and `configurable` attributes. The same as when you create a function in JavaScript. - /// - /// # Note #1 - /// - /// If you want to make a function only `constructable`, or wish to bind it differently - /// to the global object, you can create the function object with - /// [`FunctionBuilder`](crate::object::FunctionBuilder::closure). And bind it to the global - /// object with [`Context::register_global_property`](Context::register_global_property) - /// method. - /// - /// # Note #2 - /// - /// This function will only accept `Copy` closures, meaning you cannot - /// move `Clone` types, just `Copy` types. If you need to move `Clone` types - /// as captures, see [`FunctionBuilder::closure_with_captures`]. - /// - /// See for an explanation on - /// why we need to restrict the set of accepted closures. - #[inline] - pub fn register_global_closure(&mut self, name: &str, length: usize, body: F) -> JsResult<()> - where - F: Fn(&JsValue, &[JsValue], &mut Self) -> JsResult + Copy + 'static, - { - let function = FunctionBuilder::closure(self, body) - .name(name) - .length(length) - .constructor(true) - .build(); - - self.global_bindings_mut().insert( - name.into(), - PropertyDescriptor::builder() - .value(function) - .writable(true) - .enumerable(false) - .configurable(true) - .build(), - ); - Ok(()) - } - /// #[inline] pub(crate) fn has_property(&mut self, obj: &JsValue, key: &PropertyKey) -> JsResult { diff --git a/boa_engine/src/function.rs b/boa_engine/src/function.rs new file mode 100644 index 00000000000..0b53127e998 --- /dev/null +++ b/boa_engine/src/function.rs @@ -0,0 +1,186 @@ +//! Boa's wrappers for native Rust functions to be compatible with ECMAScript calls. +//! +//! [`NativeCallable`] is the main type of this module, providing APIs to create native callables +//! from native Rust functions and closures. + +use boa_gc::{custom_trace, Finalize, Gc, Trace}; + +use crate::{Context, JsResult, JsValue}; + +/// A native built-in function a.k.a. function pointer. +/// +/// Native functions need to have this signature in order to be callable from ECMAScript. +/// +/// # Arguments +/// +/// - The first argument represents the `this` variable of every ECMAScript function. +/// +/// - The second argument represents the list of all arguments passed to the function. +/// +/// - The last argument is the engine [`Context`]. +pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult; + +trait TraceableClosure: Trace { + fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult; +} + +#[derive(Trace, Finalize)] +struct Closure +where + F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult, + T: Trace, +{ + // SAFETY: `NativeCallable`'s safe API ensures only `Copy` closures are stored; its unsafe API, + // on the other hand, explains the invariants to hold in order for this to be safe, shifting + // the responsibility to the caller. + #[unsafe_ignore_trace] + f: F, + captures: T, +} + +impl TraceableClosure for Closure +where + F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult, + T: Trace, +{ + fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + (self.f)(this, args, &self.captures, context) + } +} + +/// A callable Rust function that can be invoked by the engine. +/// +/// `NativeCallable` functions are divided in two: +/// - Function pointers a.k.a common functions (see [`NativeFunctionSignature`]). +/// - Closure functions that can capture the current environment. +/// +/// # Caveats +/// +/// By limitations of the Rust language, the garbage collector currently cannot inspect closures +/// in order to trace their captured variables. This means that only [`Copy`] closures are 100% safe +/// to use. All other closures can also be stored in a `NativeCallable`, albeit by using an `unsafe` +/// API, but note that passing closures with traceable types could cause **Undefined Behaviour**. +#[derive(Clone)] +pub struct NativeCallable { + inner: Inner, +} + +impl Finalize for NativeCallable { + fn finalize(&self) { + if let Inner::Closure(c) = &self.inner { + c.finalize(); + } + } +} + +// Manual implementation because deriving triggers the `single_use_lifetimes` lint. +// SAFETY: Only closures can contain `Trace` captures, so this implementation is safe. +unsafe impl Trace for NativeCallable { + custom_trace!(this, { + if let Inner::Closure(c) = &this.inner { + mark(c); + } + }); +} + +#[derive(Clone)] +enum Inner { + PointerFn(NativeFunctionSignature), + Closure(Gc), +} + +impl std::fmt::Debug for NativeCallable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NativeCallable").finish_non_exhaustive() + } +} + +impl NativeCallable { + /// Creates a `NativeCallable` from a function pointer. + #[inline] + pub fn from_fn_ptr(function: NativeFunctionSignature) -> Self { + Self { + inner: Inner::PointerFn(function), + } + } + + /// Creates a `NativeCallable` from a `Copy` closure. + pub fn from_copy_closure(closure: F) -> Self + where + F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult + Copy + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traced types inside the closure. + unsafe { Self::from_closure(closure) } + } + + /// Creates a `NativeCallable` from a `Copy` closure and a list of traceable captures. + pub fn from_copy_closure_with_captures(closure: F, captures: T) -> Self + where + F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult + Copy + 'static, + T: Trace + 'static, + { + // SAFETY: The `Copy` bound ensures there are no traced types inside the closure. + unsafe { Self::from_closure_with_captures(closure, captures) } + } + + /// Creates a new `NativeCallable` from a closure. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub unsafe fn from_closure(closure: F) -> Self + where + F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult + 'static, + { + // SAFETY: The caller must ensure the invariants of the closure hold. + unsafe { + Self::from_closure_with_captures( + move |this, args, _, context| closure(this, args, context), + (), + ) + } + } + + /// Create a new `NativeCallable` from a closure and a list of traceable captures. + /// + /// # Safety + /// + /// Passing a closure that contains a captured variable that needs to be traced by the garbage + /// collector could cause an use after free, memory corruption or other kinds of **Undefined + /// Behaviour**. See for a technical explanation + /// on why that is the case. + pub unsafe fn from_closure_with_captures(closure: F, captures: T) -> Self + where + F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult + 'static, + T: Trace + 'static, + { + let ptr = Gc::into_raw(Gc::new(Closure { + f: closure, + captures, + })); + // SAFETY: The pointer returned by `into_raw` is only used to cast to a trait object, + // meaning this is safe. + unsafe { + Self { + inner: Inner::Closure(Gc::from_raw(ptr)), + } + } + } + + /// Calls this `NativeCallable`, forwarding the arguments to the corresponding function. + #[inline] + pub fn call( + &self, + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + match self.inner { + Inner::PointerFn(f) => f(this, args, context), + Inner::Closure(ref c) => c.call(this, args, context), + } + } +} diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index d4a9181f75c..dc9c60abf8b 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -113,6 +113,7 @@ pub mod class; pub mod context; pub mod environments; pub mod error; +pub mod function; pub mod job; pub mod object; pub mod property; diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index 28fd04603a9..413e2b3144d 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/boa_engine/src/object/builtins/jsproxy.rs @@ -2,7 +2,8 @@ use boa_gc::{Finalize, Trace}; use crate::{ - builtins::{function::NativeFunctionSignature, Proxy}, + builtins::Proxy, + function::{NativeCallable, NativeFunctionSignature}, object::{FunctionBuilder, JsObject, JsObjectType, ObjectData}, Context, JsResult, JsValue, }; @@ -359,13 +360,15 @@ impl JsProxyBuilder { let handler = context.construct_object(); if let Some(apply) = self.apply { - let f = FunctionBuilder::native(context, apply).length(3).build(); + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(apply)) + .length(3) + .build(); handler .create_data_property_or_throw("apply", f, context) .expect("new object should be writable"); } if let Some(construct) = self.construct { - let f = FunctionBuilder::native(context, construct) + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(construct)) .length(3) .build(); handler @@ -373,7 +376,7 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(define_property) = self.define_property { - let f = FunctionBuilder::native(context, define_property) + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(define_property)) .length(3) .build(); handler @@ -381,7 +384,7 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(delete_property) = self.delete_property { - let f = FunctionBuilder::native(context, delete_property) + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(delete_property)) .length(2) .build(); handler @@ -389,21 +392,26 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(get) = self.get { - let f = FunctionBuilder::native(context, get).length(3).build(); + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(get)) + .length(3) + .build(); handler .create_data_property_or_throw("get", f, context) .expect("new object should be writable"); } if let Some(get_own_property_descriptor) = self.get_own_property_descriptor { - let f = FunctionBuilder::native(context, get_own_property_descriptor) - .length(2) - .build(); + let f = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(get_own_property_descriptor), + ) + .length(2) + .build(); handler .create_data_property_or_throw("getOwnPropertyDescriptor", f, context) .expect("new object should be writable"); } if let Some(get_prototype_of) = self.get_prototype_of { - let f = FunctionBuilder::native(context, get_prototype_of) + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(get_prototype_of)) .length(1) .build(); handler @@ -411,13 +419,15 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(has) = self.has { - let f = FunctionBuilder::native(context, has).length(2).build(); + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(has)) + .length(2) + .build(); handler .create_data_property_or_throw("has", f, context) .expect("new object should be writable"); } if let Some(is_extensible) = self.is_extensible { - let f = FunctionBuilder::native(context, is_extensible) + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(is_extensible)) .length(1) .build(); handler @@ -425,13 +435,15 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(own_keys) = self.own_keys { - let f = FunctionBuilder::native(context, own_keys).length(1).build(); + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(own_keys)) + .length(1) + .build(); handler .create_data_property_or_throw("ownKeys", f, context) .expect("new object should be writable"); } if let Some(prevent_extensions) = self.prevent_extensions { - let f = FunctionBuilder::native(context, prevent_extensions) + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(prevent_extensions)) .length(1) .build(); handler @@ -439,13 +451,15 @@ impl JsProxyBuilder { .expect("new object should be writable"); } if let Some(set) = self.set { - let f = FunctionBuilder::native(context, set).length(4).build(); + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(set)) + .length(4) + .build(); handler .create_data_property_or_throw("set", f, context) .expect("new object should be writable"); } if let Some(set_prototype_of) = self.set_prototype_of { - let f = FunctionBuilder::native(context, set_prototype_of) + let f = FunctionBuilder::new(context, NativeCallable::from_fn_ptr(set_prototype_of)) .length(2) .build(); handler diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 6971516d475..425474e68f2 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -31,10 +31,7 @@ use crate::{ async_generator::AsyncGenerator, error::ErrorKind, function::arguments::Arguments, - function::{ - arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function, - NativeFunctionSignature, - }, + function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function}, generator::Generator, iterable::AsyncFromSyncIterator, map::map_iterator::MapIterator, @@ -49,10 +46,10 @@ use crate::{ DataView, Date, Promise, RegExp, }, context::intrinsics::StandardConstructor, - error::JsNativeError, + function::{NativeCallable, NativeFunctionSignature}, js_string, property::{Attribute, PropertyDescriptor, PropertyKey}, - Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, + Context, JsBigInt, JsString, JsSymbol, JsValue, }; use boa_gc::{custom_trace, Finalize, GcCell, Trace, WeakGc}; @@ -1798,9 +1795,9 @@ pub struct FunctionBuilder<'context> { } impl<'context> FunctionBuilder<'context> { - /// Create a new `FunctionBuilder` for creating a native function. + /// Creates a new `FunctionBuilder` from a static `NativeCallable` function. #[inline] - pub fn native(context: &'context mut Context, function: NativeFunctionSignature) -> Self { + pub fn new(context: &'context mut Context, function: NativeCallable) -> Self { Self { context, function: Function::Native { @@ -1812,59 +1809,6 @@ impl<'context> FunctionBuilder<'context> { } } - /// Create a new `FunctionBuilder` for creating a closure function. - #[inline] - pub fn closure(context: &'context mut Context, function: F) -> Self - where - F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult + Copy + 'static, - { - Self { - context, - function: Function::Closure { - function: Box::new(move |this, args, _, context| function(this, args, context)), - constructor: None, - captures: Captures::new(()), - }, - name: js_string!(), - length: 0, - } - } - - /// Create a new closure function with additional captures. - /// - /// # Note - /// - /// You can only move variables that implement `Debug + Any + Trace + Clone`. - /// In other words, only `NativeObject + Clone` objects are movable. - #[inline] - pub fn closure_with_captures( - context: &'context mut Context, - function: F, - captures: C, - ) -> Self - where - F: Fn(&JsValue, &[JsValue], &mut C, &mut Context) -> JsResult + Copy + 'static, - C: NativeObject, - { - Self { - context, - function: Function::Closure { - function: Box::new(move |this, args, captures: Captures, context| { - let mut captures = captures.as_mut_any(); - let captures = captures.downcast_mut::().ok_or_else(|| { - JsNativeError::typ() - .with_message("cannot downcast `Captures` to given type") - })?; - function(this, args, captures, context) - }), - constructor: None, - captures: Captures::new(captures), - }, - name: js_string!(), - length: 0, - } - } - /// Specify the name property of object function object. /// /// The default is `""` (empty string). @@ -1900,10 +1844,6 @@ impl<'context> FunctionBuilder<'context> { Function::Native { ref mut constructor, .. - } - | Function::Closure { - ref mut constructor, - .. } => { *constructor = yes.then_some(ConstructorKind::Base); } @@ -2007,7 +1947,7 @@ impl<'context> ObjectInitializer<'context> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::native(self.context, function) + let function = FunctionBuilder::new(self.context, NativeCallable::from_fn_ptr(function)) .name(binding.name) .length(length) .constructor(false) @@ -2129,7 +2069,7 @@ impl<'context> ConstructorBuilder<'context> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::native(self.context, function) + let function = FunctionBuilder::new(self.context, NativeCallable::from_fn_ptr(function)) .name(binding.name) .length(length) .constructor(false) @@ -2158,7 +2098,7 @@ impl<'context> ConstructorBuilder<'context> { B: Into, { let binding = binding.into(); - let function = FunctionBuilder::native(self.context, function) + let function = FunctionBuilder::new(self.context, NativeCallable::from_fn_ptr(function)) .name(binding.name) .length(length) .constructor(false) @@ -2350,7 +2290,7 @@ impl<'context> ConstructorBuilder<'context> { pub fn build(&mut self) -> JsFunction { // Create the native function let function = Function::Native { - function: self.function, + function: NativeCallable::from_fn_ptr(self.function), constructor: self.constructor, }; diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index ec31f629c17..c0b9361216e 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -706,25 +706,16 @@ impl JsObject { function, constructor, } => { - let function = *function; + let function = function.clone(); let constructor = *constructor; drop(object); if constructor.is_some() { - function(&JsValue::undefined(), args, context) + function.call(&JsValue::undefined(), args, context) } else { - function(this, args, context) + function.call(this, args, context) } } - Function::Closure { - function, captures, .. - } => { - let function = function.clone(); - let captures = captures.clone(); - drop(object); - - (function)(this, args, captures, context) - } Function::Ordinary { code, environments, .. } => { @@ -1289,41 +1280,15 @@ impl JsObject { function, constructor, .. - } => { - let function = *function; - let constructor = *constructor; - drop(object); - - match function(this_target, args, context)? { - JsValue::Object(ref o) => Ok(o.clone()), - val => { - if constructor.expect("hmm").is_base() || val.is_undefined() { - create_this(context) - } else { - Err(JsNativeError::typ() - .with_message( - "Derived constructor can only return an Object or undefined", - ) - .into()) - } - } - } - } - Function::Closure { - function, - captures, - constructor, - .. } => { let function = function.clone(); - let captures = captures.clone(); let constructor = *constructor; drop(object); - match (function)(this_target, args, captures, context)? { + match function.call(this_target, args, context)? { JsValue::Object(ref o) => Ok(o.clone()), val => { - if constructor.expect("hmma").is_base() || val.is_undefined() { + if constructor.expect("hmm").is_base() || val.is_undefined() { create_this(context) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/vm/opcode/await_stm/mod.rs b/boa_engine/src/vm/opcode/await_stm/mod.rs index c9e6abb1bd0..77060029be5 100644 --- a/boa_engine/src/vm/opcode/await_stm/mod.rs +++ b/boa_engine/src/vm/opcode/await_stm/mod.rs @@ -1,5 +1,8 @@ +use boa_gc::{Gc, GcCell}; + use crate::{ builtins::{JsArgs, Promise}, + function::NativeCallable, object::FunctionBuilder, vm::{call_frame::GeneratorResumeKind, opcode::Operation, ShouldExit}, Context, JsResult, JsValue, @@ -28,37 +31,41 @@ impl Operation for Await { // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). - let on_fulfilled = FunctionBuilder::closure_with_captures( + let on_fulfilled = FunctionBuilder::new( context, - |_this, args, (environment, stack, frame), context| { - // a. Let prevContext be the running execution context. - // b. Suspend prevContext. - // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. - // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. - // f. Return undefined. - - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; - context.vm.push(args.get_or_undefined(0)); - context.run()?; - - *frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - - Ok(JsValue::undefined()) - }, - ( - context.realm.environments.clone(), - context.vm.stack.clone(), - context.vm.frame().clone(), + NativeCallable::from_copy_closure_with_captures( + |_this, args, captures, context| { + let mut captures = captures.borrow_mut(); + let (environment, stack, frame) = &mut *captures; + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + context.vm.push_frame(frame.clone()); + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; + context.vm.push(args.get_or_undefined(0)); + context.run()?; + + *frame = context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + + Ok(JsValue::undefined()) + }, + Gc::new(GcCell::new(( + context.realm.environments.clone(), + context.vm.stack.clone(), + context.vm.frame().clone(), + ))), ), ) .name("") @@ -67,37 +74,41 @@ impl Operation for Await { // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). - let on_rejected = FunctionBuilder::closure_with_captures( + let on_rejected = FunctionBuilder::new( context, - |_this, args, (environment, stack, frame), context| { - // a. Let prevContext be the running execution context. - // b. Suspend prevContext. - // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. - // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. - // f. Return undefined. - - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; - context.vm.push(args.get_or_undefined(0)); - context.run()?; - - *frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - - Ok(JsValue::undefined()) - }, - ( - context.realm.environments.clone(), - context.vm.stack.clone(), - context.vm.frame().clone(), + NativeCallable::from_copy_closure_with_captures( + |_this, args, captures, context| { + let mut captures = captures.borrow_mut(); + let (environment, stack, frame) = &mut *captures; + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + context.vm.push_frame(frame.clone()); + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; + context.vm.push(args.get_or_undefined(0)); + context.run()?; + + *frame = context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + + Ok(JsValue::undefined()) + }, + Gc::new(GcCell::new(( + context.realm.environments.clone(), + context.vm.stack.clone(), + context.vm.frame().clone(), + ))), ), ) .name("") diff --git a/boa_examples/src/bin/closures.rs b/boa_examples/src/bin/closures.rs index c6f059f03a9..7994a06cada 100644 --- a/boa_examples/src/bin/closures.rs +++ b/boa_examples/src/bin/closures.rs @@ -1,33 +1,40 @@ -// This example goes into the details on how to pass closures as functions -// inside Rust and call them from Javascript. +// This example goes into the details on how to pass closures as functions inside Rust and call them +// from Javascript. + +use std::cell::{Cell, RefCell}; use boa_engine::{ + function::NativeCallable, js_string, - object::{FunctionBuilder, JsObject}, + object::{builtins::JsArray, FunctionBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, string::utf16, - Context, JsError, JsString, JsValue, + Context, JsError, JsNativeError, JsString, JsValue, }; -use boa_gc::{Finalize, Trace}; +use boa_gc::{Finalize, GcCell, Trace}; fn main() -> Result<(), JsError> { // We create a new `Context` to create a new Javascript executor. let mut context = Context::default(); - // We make some operations in Rust that return a `Copy` value that we want - // to pass to a Javascript function. + // We make some operations in Rust that return a `Copy` value that we want to pass to a Javascript + // function. let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1; // We register a global closure function that has the name 'closure' with length 0. - context.register_global_closure("closure", 0, move |_, _, _| { - println!("Called `closure`"); - // `variable` is captured from the main function. - println!("variable = {variable}"); - println!(); + context.register_global_callable( + "closure", + 0, + NativeCallable::from_copy_closure(move |_, _, _| { + println!("Called `closure`"); + // `variable` is captured from the main function. + println!("variable = {variable}"); + println!(); - // We return the moved variable as a `JsValue`. - Ok(JsValue::new(variable)) - })?; + // We return the moved variable as a `JsValue`. + Ok(JsValue::new(variable)) + }), + ); assert_eq!(context.eval("closure()")?, 255.into()); @@ -59,34 +66,38 @@ fn main() -> Result<(), JsError> { object, }; - // We can use `FunctionBuilder` to define a closure with additional - // captures. - let js_function = FunctionBuilder::closure_with_captures( + // We can use `FunctionBuilder` to define a closure with additional captures and custom property + // attributes. + let js_function = FunctionBuilder::new( &mut context, - |_, _, captures, context| { - println!("Called `createMessage`"); - // We obtain the `name` property of `captures.object` - let name = captures.object.get("name", context)?; - - // We create a new message from our captured variable. - let message = js_string!( - utf16!("message from `"), - &name.to_string(context)?, - utf16!("`: "), - &captures.greeting - ); - - // We can also mutate the moved data inside the closure. - captures.greeting = js_string!(&captures.greeting, utf16!(" Hello!")); - - println!("{}", message.to_std_string_escaped()); - println!(); - - // We convert `message` into `JsValue` to be able to return it. - Ok(message.into()) - }, - // Here is where we move `clone_variable` into the closure. - clone_variable, + NativeCallable::from_copy_closure_with_captures( + |_, _, captures, context| { + let mut captures = captures.borrow_mut(); + let BigStruct { greeting, object } = &mut *captures; + println!("Called `createMessage`"); + // We obtain the `name` property of `captures.object` + let name = object.get("name", context)?; + + // We create a new message from our captured variable. + let message = js_string!( + utf16!("message from `"), + &name.to_string(context)?, + utf16!("`: "), + greeting + ); + + // We can also mutate the moved data inside the closure. + captures.greeting = js_string!(greeting, utf16!(" Hello!")); + + println!("{}", message.to_std_string_escaped()); + println!(); + + // We convert `message` into `JsValue` to be able to return it. + Ok(message.into()) + }, + // Here is where we move `clone_variable` into the closure. + GcCell::new(clone_variable), + ), ) // And here we assign `createMessage` to the `name` property of the closure. .name("createMessage") @@ -102,7 +113,7 @@ fn main() -> Result<(), JsError> { // We pass `js_function` as a property value. js_function, // We assign to the "createMessage" property the desired attributes. - Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ); assert_eq!( @@ -119,5 +130,65 @@ fn main() -> Result<(), JsError> { // We have moved `Clone` variables into a closure and executed that closure // inside Javascript! + // ADVANCED + + // If we can ensure the captured variables are not traceable by the garbage collector, + // we can pass any static closure easily. + + let index = Cell::new(0i32); + let numbers = RefCell::new(Vec::new()); + + // We register a global closure that is not `Copy`. + context.register_global_callable( + "enumerate", + 0, + // Note that it is required to use `unsafe` code, since the compiler cannot verify that the + // types captured by the closure are not traceable. + unsafe { + NativeCallable::from_closure(move |_, _, context| { + println!("Called `enumerate`"); + // `index` is captured from the main function. + println!("index = {}", index.get()); + println!(); + + numbers.borrow_mut().push(index.get()); + index.set(index.get() + 1); + + // We return the moved variable as a `JsValue`. + Ok( + JsArray::from_iter( + numbers.borrow().iter().cloned().map(JsValue::from), + context, + ) + .into(), + ) + }) + }, + ); + + // First call should return the array `[0]`. + let result = context.eval("enumerate()")?; + let object = result + .as_object() + .cloned() + .ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?; + let array = JsArray::from_object(object)?; + + assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32)); + assert_eq!(array.get(1, &mut context)?, JsValue::undefined()); + + // First call should return the array `[0, 1]`. + let result = context.eval("enumerate()")?; + let object = result + .as_object() + .cloned() + .ok_or_else(|| JsNativeError::typ().with_message("not an array!"))?; + let array = JsArray::from_object(object)?; + + assert_eq!(array.get(0, &mut context)?, JsValue::from(0i32)); + assert_eq!(array.get(1, &mut context)?, JsValue::from(1i32)); + assert_eq!(array.get(2, &mut context)?, JsValue::undefined()); + + // We have moved non-traceable variables into a closure and executed that closure inside Javascript! Ok(()) } diff --git a/boa_examples/src/bin/jsarray.rs b/boa_examples/src/bin/jsarray.rs index ae79ceddd6b..c362946f706 100644 --- a/boa_examples/src/bin/jsarray.rs +++ b/boa_examples/src/bin/jsarray.rs @@ -1,6 +1,7 @@ // This example shows how to manipulate a Javascript array using Rust code. use boa_engine::{ + function::NativeCallable, object::{builtins::JsArray, FunctionBuilder}, string::utf16, Context, JsResult, JsValue, @@ -60,17 +61,23 @@ fn main() -> JsResult<()> { let joined_array = array.join(Some("::".into()), context)?; assert_eq!(&joined_array, utf16!("14::false::false::false::10")); - let filter_callback = FunctionBuilder::native(context, |_this, args, _context| { - Ok(args.get(0).cloned().unwrap_or_default().is_number().into()) - }) + let filter_callback = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(|_this, args, _context| { + Ok(args.get(0).cloned().unwrap_or_default().is_number().into()) + }), + ) .build(); - let map_callback = FunctionBuilder::native(context, |_this, args, context| { - args.get(0) - .cloned() - .unwrap_or_default() - .pow(&JsValue::new(2), context) - }) + let map_callback = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(|_this, args, context| { + args.get(0) + .cloned() + .unwrap_or_default() + .pow(&JsValue::new(2), context) + }), + ) .build(); let mut data = Vec::new(); @@ -88,12 +95,15 @@ fn main() -> JsResult<()> { assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3")); - let reduce_callback = FunctionBuilder::native(context, |_this, args, context| { - let accumulator = args.get(0).cloned().unwrap_or_default(); - let value = args.get(1).cloned().unwrap_or_default(); + let reduce_callback = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(|_this, args, context| { + let accumulator = args.get(0).cloned().unwrap_or_default(); + let value = args.get(1).cloned().unwrap_or_default(); - accumulator.add(&value, context) - }) + accumulator.add(&value, context) + }), + ) .build(); assert_eq!( diff --git a/boa_examples/src/bin/jstypedarray.rs b/boa_examples/src/bin/jstypedarray.rs index cf9f1cdfb09..36fe9858b68 100644 --- a/boa_examples/src/bin/jstypedarray.rs +++ b/boa_examples/src/bin/jstypedarray.rs @@ -1,6 +1,7 @@ // This example shows how to manipulate a Javascript array using Rust code. use boa_engine::{ + function::NativeCallable, object::{builtins::JsUint8Array, FunctionBuilder}, property::Attribute, Context, JsResult, JsValue, @@ -23,12 +24,15 @@ fn main() -> JsResult<()> { sum += i; } - let callback = FunctionBuilder::native(context, |_this, args, context| { - let accumulator = args.get(0).cloned().unwrap_or_default(); - let value = args.get(1).cloned().unwrap_or_default(); + let callback = FunctionBuilder::new( + context, + NativeCallable::from_fn_ptr(|_this, args, context| { + let accumulator = args.get(0).cloned().unwrap_or_default(); + let value = args.get(1).cloned().unwrap_or_default(); - accumulator.add(&value, context) - }) + accumulator.add(&value, context) + }), + ) .build(); assert_eq!( diff --git a/boa_examples/src/bin/modulehandler.rs b/boa_examples/src/bin/modulehandler.rs index 1bd4b76514b..133322d8aef 100644 --- a/boa_examples/src/bin/modulehandler.rs +++ b/boa_examples/src/bin/modulehandler.rs @@ -1,7 +1,9 @@ // This example implements a custom module handler which mimics // the require/module.exports pattern -use boa_engine::{prelude::JsObject, property::Attribute, Context, JsResult, JsValue}; +use boa_engine::{ + function::NativeCallable, prelude::JsObject, property::Attribute, Context, JsResult, JsValue, +}; use std::fs::read_to_string; fn main() { @@ -17,7 +19,7 @@ fn main() { let mut ctx = Context::default(); // Adding custom implementation that mimics 'require' - ctx.register_global_function("require", 0, require); + ctx.register_global_callable("require", 0, NativeCallable::from_fn_ptr(require)); // Adding custom object that mimics 'module.exports' let moduleobj = JsObject::default(); diff --git a/boa_gc/src/internals/gc_box.rs b/boa_gc/src/internals/gc_box.rs index 8ce48182ecf..15f02bee8e6 100644 --- a/boa_gc/src/internals/gc_box.rs +++ b/boa_gc/src/internals/gc_box.rs @@ -111,7 +111,7 @@ impl fmt::Debug for GcBoxHeader { /// A garbage collected allocation. #[derive(Debug)] -pub(crate) struct GcBox { +pub struct GcBox { pub(crate) header: GcBoxHeader, pub(crate) value: T, } diff --git a/boa_gc/src/internals/mod.rs b/boa_gc/src/internals/mod.rs index 7539365723b..0074c8f1a4c 100644 --- a/boa_gc/src/internals/mod.rs +++ b/boa_gc/src/internals/mod.rs @@ -1,4 +1,5 @@ mod ephemeron_box; mod gc_box; -pub(crate) use self::{ephemeron_box::EphemeronBox, gc_box::GcBox}; +pub(crate) use self::ephemeron_box::EphemeronBox; +pub use self::gc_box::GcBox; diff --git a/boa_gc/src/lib.rs b/boa_gc/src/lib.rs index 814b89fd888..cf67839aa42 100644 --- a/boa_gc/src/lib.rs +++ b/boa_gc/src/lib.rs @@ -94,7 +94,6 @@ mod trace; pub(crate) mod internals; use boa_profiler::Profiler; -use internals::GcBox; use std::{ cell::{Cell, RefCell}, mem, @@ -104,6 +103,7 @@ use std::{ pub use crate::trace::{Finalize, Trace}; pub use boa_macros::{Finalize, Trace}; pub use cell::{GcCell, GcCellRef, GcCellRefMut}; +pub use internals::GcBox; pub use pointers::{Ephemeron, Gc, WeakGc}; type GcPointer = NonNull>; diff --git a/boa_gc/src/pointers/gc.rs b/boa_gc/src/pointers/gc.rs index 2dc9f275fae..cd2ea87f968 100644 --- a/boa_gc/src/pointers/gc.rs +++ b/boa_gc/src/pointers/gc.rs @@ -47,6 +47,16 @@ impl Gc { gc.set_root(); gc } + + /// Consumes the `Gc`, returning a wrapped raw pointer. + /// + /// To avoid a memory leak, the pointer must be converted back to a `Gc` using [`Gc::from_raw`]. + #[allow(clippy::use_self)] + pub fn into_raw(this: Gc) -> NonNull> { + let ptr = this.inner_ptr.get(); + std::mem::forget(this); + ptr + } } impl Gc { @@ -55,9 +65,26 @@ impl Gc { GcBox::ptr_eq(this.inner(), other.inner()) } - /// Will return a new rooted `Gc` from a `GcBox` pointer - pub(crate) fn from_ptr(ptr: NonNull>) -> Self { - // SAFETY: the value provided as a pointer MUST be a valid GcBox. + /// Constructs a `Gc` from a raw pointer. + /// + /// The raw pointer must have been returned by a previous call to [`Gc::into_raw`][Gc::into_raw] + /// where `U` must have the same size and alignment as `T`. + /// + /// # Safety + /// + /// This function is unsafe because improper use may lead to memory corruption, double-free, + /// or misbehaviour of the garbage collector. + #[must_use] + pub const unsafe fn from_raw(ptr: NonNull>) -> Self { + Self { + inner_ptr: Cell::new(ptr), + marker: PhantomData, + } + } + + /// Return a rooted `Gc` from a `GcBox` pointer + pub(crate) unsafe fn from_ptr(ptr: NonNull>) -> Self { + // SAFETY: the caller must ensure that the pointer is valid. unsafe { ptr.as_ref().root_inner(); let gc = Self { @@ -167,7 +194,8 @@ unsafe impl Trace for Gc { impl Clone for Gc { #[inline] fn clone(&self) -> Self { - Self::from_ptr(self.inner_ptr()) + // SAFETY: `&self` is a valid Gc pointer. + unsafe { Self::from_ptr(self.inner_ptr()) } } } diff --git a/boa_gc/src/trace.rs b/boa_gc/src/trace.rs index 06512fcaa7c..c1cd44c8eed 100644 --- a/boa_gc/src/trace.rs +++ b/boa_gc/src/trace.rs @@ -1,5 +1,6 @@ use std::{ borrow::{Cow, ToOwned}, + cell::Cell, collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, hash::{BuildHasher, Hash}, marker::PhantomData, @@ -450,3 +451,21 @@ where } }); } + +impl Finalize for Cell> { + fn finalize(&self) { + if let Some(t) = self.take() { + t.finalize(); + self.set(Some(t)); + } + } +} + +unsafe impl Trace for Cell> { + custom_trace!(this, { + if let Some(t) = this.take() { + mark(&t); + this.set(Some(t)); + } + }); +} diff --git a/boa_tester/src/exec/mod.rs b/boa_tester/src/exec/mod.rs index 89eba98563c..7ddc60acf6b 100644 --- a/boa_tester/src/exec/mod.rs +++ b/boa_tester/src/exec/mod.rs @@ -7,14 +7,13 @@ use super::{ }; use crate::read::ErrorType; use boa_engine::{ - builtins::JsArgs, object::FunctionBuilder, property::Attribute, Context, JsNativeErrorKind, - JsResult, JsValue, + builtins::JsArgs, function::NativeCallable, object::FunctionBuilder, property::Attribute, + Context, JsNativeErrorKind, JsValue, }; -use boa_gc::{Finalize, Gc, GcCell, Trace}; use boa_parser::Parser; use colored::Colorize; use rayon::prelude::*; -use std::borrow::Cow; +use std::{borrow::Cow, cell::RefCell, rc::Rc}; impl TestSuite { /// Runs the test suite. @@ -360,11 +359,25 @@ impl Test { /// Registers the print function in the context. fn register_print_fn(context: &mut Context, async_result: AsyncResult) { // We use `FunctionBuilder` to define a closure with additional captures. - let js_function = - FunctionBuilder::closure_with_captures(context, test262_print, async_result) - .name("print") - .length(1) - .build(); + let js_function = FunctionBuilder::new( + context, + // SAFETY: `AsyncResult` has only non-traceable captures, making this safe. + unsafe { + NativeCallable::from_closure(move |_, args, context| { + let message = args + .get_or_undefined(0) + .to_string(context)? + .to_std_string_escaped(); + if message != "Test262:AsyncTestComplete" { + *async_result.inner.borrow_mut() = Err(message); + } + Ok(JsValue::undefined()) + }) + }, + ) + .name("print") + .length(1) + .build(); context.register_global_property( "print", @@ -375,33 +388,15 @@ impl Test { } /// Object which includes the result of the async operation. -#[derive(Debug, Clone, Trace, Finalize)] +#[derive(Debug, Clone)] struct AsyncResult { - inner: Gc>>, + inner: Rc>>, } impl Default for AsyncResult { fn default() -> Self { Self { - inner: Gc::new(GcCell::new(Ok(()))), + inner: Rc::new(RefCell::new(Ok(()))), } } } - -/// `print()` function required by the test262 suite. -#[allow(clippy::unnecessary_wraps)] -fn test262_print( - _this: &JsValue, - args: &[JsValue], - async_result: &mut AsyncResult, - context: &mut Context, -) -> JsResult { - let message = args - .get_or_undefined(0) - .to_string(context)? - .to_std_string_escaped(); - if message != "Test262:AsyncTestComplete" { - *async_result.inner.borrow_mut() = Err(message); - } - Ok(JsValue::undefined()) -}