Browse Source

Redesign native functions and closures API (#2499)

This PR is a complete redesign of our current native functions and closures API.

I was a bit dissatisfied with our previous design (even though I created it 😆), because it had a lot of superfluous traits, a forced usage of `Gc<GcCell<T>>` and an overly restrictive `NativeObject` bound. This redesign, on the other hand, simplifies a lot our public API, with a simple `NativeCallable` struct that has several constructors for each type of required native function.

This new design doesn't require wrapping every capture type with `Gc<GcCell<T>>`, relaxes the trait requirement to `Trace + 'static` for captures, can be reused in both `JsObject` functions and (soonish) host defined functions, and is (in my opinion) a bit cleaner than the previous iteration. It also offers an `unsafe` API as an escape hatch for users that want to pass non-Copy closures which don't capture traceable types.

Would ask for bikeshedding about the names though, because I don't know if `NativeCallable` is the most precise name for this. Same about the constructor names; I added the `from` prefix to all of them because it's the "standard" practice, but seeing the API doesn't have any other method aside from `call`, it may be better to just remove the prefix altogether.

Let me know what you think :)
pull/2524/head
José Julián Espina 2 years ago
parent
commit
edd404ba7f
  1. 7
      Cargo.lock
  2. 1
      boa_engine/Cargo.toml
  3. 10
      boa_engine/src/builtins/array/mod.rs
  4. 11
      boa_engine/src/builtins/array_buffer/mod.rs
  5. 3
      boa_engine/src/builtins/async_function/mod.rs
  6. 11
      boa_engine/src/builtins/async_generator/mod.rs
  7. 3
      boa_engine/src/builtins/async_generator_function/mod.rs
  8. 14
      boa_engine/src/builtins/dataview/mod.rs
  9. 3
      boa_engine/src/builtins/error/type.rs
  10. 5
      boa_engine/src/builtins/eval/mod.rs
  11. 144
      boa_engine/src/builtins/function/mod.rs
  12. 11
      boa_engine/src/builtins/function/tests.rs
  13. 3
      boa_engine/src/builtins/generator_function/mod.rs
  14. 12
      boa_engine/src/builtins/intl/collator/mod.rs
  15. 35
      boa_engine/src/builtins/intl/locale/mod.rs
  16. 27
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  17. 14
      boa_engine/src/builtins/map/mod.rs
  18. 4
      boa_engine/src/builtins/mod.rs
  19. 23
      boa_engine/src/builtins/number/mod.rs
  20. 19
      boa_engine/src/builtins/object/mod.rs
  21. 64
      boa_engine/src/builtins/promise/mod.rs
  22. 19
      boa_engine/src/builtins/promise/promise_job.rs
  23. 11
      boa_engine/src/builtins/proxy/mod.rs
  24. 35
      boa_engine/src/builtins/regexp/mod.rs
  25. 14
      boa_engine/src/builtins/set/mod.rs
  26. 9
      boa_engine/src/builtins/symbol/mod.rs
  27. 31
      boa_engine/src/builtins/typed_array/mod.rs
  28. 20
      boa_engine/src/builtins/uri/mod.rs
  29. 6
      boa_engine/src/class.rs
  30. 85
      boa_engine/src/context/mod.rs
  31. 1
      boa_engine/src/lib.rs
  32. 365
      boa_engine/src/native_function.rs
  33. 103
      boa_engine/src/object/builtins/jsproxy.rs
  34. 93
      boa_engine/src/object/mod.rs
  35. 45
      boa_engine/src/vm/code_block.rs
  36. 25
      boa_engine/src/vm/opcode/await_stm/mod.rs
  37. 105
      boa_examples/src/bin/closures.rs
  38. 24
      boa_examples/src/bin/jsarray.rs
  39. 10
      boa_examples/src/bin/jstypedarray.rs
  40. 7
      boa_examples/src/bin/modulehandler.rs
  41. 2
      boa_gc/src/internals/gc_box.rs
  42. 3
      boa_gc/src/internals/mod.rs
  43. 2
      boa_gc/src/lib.rs
  44. 36
      boa_gc/src/pointers/gc.rs
  45. 19
      boa_gc/src/trace.rs
  46. 49
      boa_tester/src/exec/mod.rs

7
Cargo.lock generated

@ -200,7 +200,6 @@ dependencies = [
"boa_profiler", "boa_profiler",
"chrono", "chrono",
"criterion", "criterion",
"dyn-clone",
"fast-float", "fast-float",
"float-cmp", "float-cmp",
"icu_calendar", "icu_calendar",
@ -993,12 +992,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "dyn-clone"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
[[package]] [[package]]
name = "dynasm" name = "dynasm"
version = "1.2.3" version = "1.2.3"

1
boa_engine/Cargo.toml

@ -57,7 +57,6 @@ ryu-js = "0.2.2"
chrono = "0.4.23" chrono = "0.4.23"
fast-float = "0.2.0" fast-float = "0.2.0"
unicode-normalization = "0.1.22" unicode-normalization = "0.1.22"
dyn-clone = "1.0.10"
once_cell = "1.17.0" once_cell = "1.17.0"
tap = "1.0.1" tap = "1.0.1"
sptr = "0.3.2" sptr = "0.3.2"

10
boa_engine/src/builtins/array/mod.rs

@ -26,9 +26,10 @@ use crate::{
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
js_string, js_string,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsFunction, JsObject, ObjectData, FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
}, },
property::{Attribute, PropertyDescriptor, PropertyNameKind}, property::{Attribute, PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -50,7 +51,8 @@ impl BuiltIn for Array {
let symbol_iterator = WellKnownSymbols::iterator(); let symbol_iterator = WellKnownSymbols::iterator();
let symbol_unscopables = WellKnownSymbols::unscopables(); let symbol_unscopables = WellKnownSymbols::unscopables();
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
@ -2847,7 +2849,7 @@ impl Array {
/// Creates an `Array.prototype.values( )` function object. /// Creates an `Array.prototype.values( )` function object.
pub(crate) fn create_array_prototype_values(context: &mut Context<'_>) -> JsFunction { pub(crate) fn create_array_prototype_values(context: &mut Context<'_>) -> JsFunction {
FunctionBuilder::native(context, Self::values) FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values))
.name("values") .name("values")
.length(0) .length(0)
.constructor(false) .constructor(false)

11
boa_engine/src/builtins/array_buffer/mod.rs

@ -14,9 +14,10 @@ use crate::{
builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs}, builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -55,12 +56,14 @@ impl BuiltIn for ArrayBuffer {
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length) let get_byte_length =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_byte_length))
.name("get byteLength") .name("get byteLength")
.build(); .build();

3
boa_engine/src/builtins/async_function/mod.rs

@ -12,6 +12,7 @@ use crate::{
function::{ConstructorKind, Function}, function::{ConstructorKind, Function},
BuiltIn, BuiltIn,
}, },
native_function::NativeFunction,
object::ObjectData, object::ObjectData,
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -62,7 +63,7 @@ impl BuiltIn for AsyncFunction {
.configurable(false); .configurable(false);
constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native { constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor, function: NativeFunction::from_fn_ptr(Self::constructor),
constructor: Some(ConstructorKind::Base), constructor: Some(ConstructorKind::Base),
}); });

11
boa_engine/src/builtins/async_generator/mod.rs

@ -11,7 +11,8 @@ use crate::{
promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise, promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise,
}, },
error::JsNativeError, error::JsNativeError,
object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::JsValue, value::JsValue,
@ -627,8 +628,9 @@ impl AsyncGenerator {
// 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called: // 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, "", « »). // 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures( let on_fulfilled = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| { |_this, args, generator, context| {
let mut generator_borrow_mut = generator.borrow_mut(); let mut generator_borrow_mut = generator.borrow_mut();
let gen = generator_borrow_mut let gen = generator_borrow_mut
@ -653,6 +655,7 @@ impl AsyncGenerator {
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
generator.clone(), generator.clone(),
),
) )
.name("") .name("")
.length(1) .length(1)
@ -660,8 +663,9 @@ impl AsyncGenerator {
// 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called: // 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, "", « »). // 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionBuilder::closure_with_captures( let on_rejected = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| { |_this, args, generator, context| {
let mut generator_borrow_mut = generator.borrow_mut(); let mut generator_borrow_mut = generator.borrow_mut();
let gen = generator_borrow_mut let gen = generator_borrow_mut
@ -686,6 +690,7 @@ impl AsyncGenerator {
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
generator, generator,
),
) )
.name("") .name("")
.length(1) .length(1)

3
boa_engine/src/builtins/async_generator_function/mod.rs

@ -10,6 +10,7 @@ use crate::{
function::{BuiltInFunctionObject, ConstructorKind, Function}, function::{BuiltInFunctionObject, ConstructorKind, Function},
BuiltIn, BuiltIn,
}, },
native_function::NativeFunction,
object::ObjectData, object::ObjectData,
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -67,7 +68,7 @@ impl BuiltIn for AsyncGeneratorFunction {
.configurable(false); .configurable(false);
constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native { constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor, function: NativeFunction::from_fn_ptr(Self::constructor),
constructor: Some(ConstructorKind::Base), constructor: Some(ConstructorKind::Base),
}); });

14
boa_engine/src/builtins/dataview/mod.rs

@ -11,9 +11,10 @@ use crate::{
builtins::{array_buffer::SharedMemoryOrder, typed_array::TypedArrayKind, BuiltIn, JsArgs}, builtins::{array_buffer::SharedMemoryOrder, typed_array::TypedArrayKind, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -37,15 +38,18 @@ impl BuiltIn for DataView {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_buffer = FunctionBuilder::native(context, Self::get_buffer) let get_buffer =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_buffer))
.name("get buffer") .name("get buffer")
.build(); .build();
let get_byte_length = FunctionBuilder::native(context, Self::get_byte_length) let get_byte_length =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_byte_length))
.name("get byteLength") .name("get byteLength")
.build(); .build();
let get_byte_offset = FunctionBuilder::native(context, Self::get_byte_offset) let get_byte_offset =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_byte_offset))
.name("get byteOffset") .name("get byteOffset")
.build(); .build();

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

@ -19,6 +19,7 @@ use crate::{
builtins::{function::Function, BuiltIn, JsArgs}, builtins::{function::Function, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, 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( let function = JsObject::from_proto_and_data(
context.intrinsics().constructors().function().prototype(), context.intrinsics().constructors().function().prototype(),
ObjectData::function(Function::Native { ObjectData::function(Function::Native {
function: throw_type_error, function: NativeFunction::from_fn_ptr(throw_type_error),
constructor: None, constructor: None,
}), }),
); );

5
boa_engine/src/builtins/eval/mod.rs

@ -13,7 +13,8 @@ use crate::{
builtins::{BuiltIn, JsArgs}, builtins::{BuiltIn, JsArgs},
environments::DeclarativeEnvironment, environments::DeclarativeEnvironment,
error::JsNativeError, error::JsNativeError,
object::FunctionBuilder, native_function::NativeFunction,
object::FunctionObjectBuilder,
property::Attribute, property::Attribute,
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
@ -37,7 +38,7 @@ impl BuiltIn for Eval {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let object = FunctionBuilder::native(context, Self::eval) let object = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::eval))
.name("eval") .name("eval")
.length(1) .length(1)
.constructor(false) .constructor(false)

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

@ -18,11 +18,9 @@ use crate::{
environments::DeclarativeEnvironmentStack, environments::DeclarativeEnvironmentStack,
error::JsNativeError, error::JsNativeError,
js_string, js_string,
object::{ native_function::{NativeFunction, NativeFunctionPointer},
internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object, object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData},
ObjectData, object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, PrivateElement},
},
object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
string::utf16, string::utf16,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -34,72 +32,20 @@ use boa_ast::{
operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
StatementList, 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_interner::Sym;
use boa_parser::Parser; use boa_parser::Parser;
use boa_profiler::Profiler; use boa_profiler::Profiler;
use dyn_clone::DynClone;
use std::{
any::Any,
fmt,
ops::{Deref, DerefMut},
};
use tap::{Conv, Pipe}; use tap::{Conv, Pipe};
use std::fmt;
use super::promise::PromiseCapability; use super::promise::PromiseCapability;
pub(crate) mod arguments; pub(crate) mod arguments;
#[cfg(test)] #[cfg(test)]
mod tests; 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<JsValue>;
// Allows restricting closures to only `Copy` ones.
// Used the sealed pattern to disallow external implementations
// of `DynCopy`.
mod sealed {
pub trait Sealed {}
impl<T: Copy> Sealed for T {}
}
/// This trait is implemented by any type that implements [`core::marker::Copy`].
pub trait DynCopy: sealed::Sealed {}
impl<T: Copy> 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<JsValue>
+ DynCopy
+ DynClone
+ 'static
{
}
impl<T> ClosureFunctionSignature for T where
T: Fn(&JsValue, &[JsValue], Captures, &mut Context<'_>) -> JsResult<JsValue> + Copy + 'static
{
}
// Allows cloning Box<dyn ClosureFunctionSignature>
dyn_clone::clone_trait_object!(ClosureFunctionSignature);
/// Represents the `[[ThisMode]]` internal slot of function objects. /// Represents the `[[ThisMode]]` internal slot of function objects.
/// ///
/// More information: /// More information:
@ -196,47 +142,6 @@ unsafe impl Trace for ClassFieldDefinition {
}} }}
} }
/// Wrapper for `Gc<GcCell<dyn NativeObject>>` 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<GcCell<Box<dyn NativeObject>>>);
impl Captures {
/// Creates a new capture context.
pub(crate) fn new<T>(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 NativeObject>, dyn Any> {
RefMut::map(self.0.borrow_mut(), |data| data.deref_mut().as_mut_any())
}
}
/// Boa representation of a Function Object. /// Boa representation of a Function Object.
/// ///
/// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code /// `FunctionBody` is specific to this interpreter, it will either be Rust code or JavaScript code
@ -248,24 +153,11 @@ pub enum Function {
/// A rust function. /// A rust function.
Native { Native {
/// The rust function. /// The rust function.
function: NativeFunctionSignature, function: NativeFunction,
/// The kind of the function constructor if it is a constructor. /// The kind of the function constructor if it is a constructor.
constructor: Option<ConstructorKind>, constructor: Option<ConstructorKind>,
}, },
/// A rust function that may contain captured values.
Closure {
/// The rust function.
function: Box<dyn ClosureFunctionSignature>,
/// The kind of the function constructor if it is a constructor.
constructor: Option<ConstructorKind>,
/// The captured values.
captures: Captures,
},
/// A bytecode function. /// A bytecode function.
Ordinary { Ordinary {
/// The code block containing the compiled function. /// The code block containing the compiled function.
@ -330,8 +222,7 @@ pub enum Function {
unsafe impl Trace for Function { unsafe impl Trace for Function {
custom_trace! {this, { custom_trace! {this, {
match this { match this {
Self::Native { .. } => {} Self::Native { function, .. } => {mark(function)}
Self::Closure { captures, .. } => mark(captures),
Self::Ordinary { code, environments, home_object, fields, private_methods, .. } => { Self::Ordinary { code, environments, home_object, fields, private_methods, .. } => {
mark(code); mark(code);
mark(environments); mark(environments);
@ -367,9 +258,7 @@ impl Function {
/// Returns true if the function object is a constructor. /// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool { pub fn is_constructor(&self) -> bool {
match self { match self {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => { Self::Native { constructor, .. } => constructor.is_some(),
constructor.is_some()
}
Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false, Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false,
Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
} }
@ -394,7 +283,7 @@ impl Function {
| Self::Async { home_object, .. } | Self::Async { home_object, .. }
| Self::Generator { home_object, .. } | Self::Generator { home_object, .. }
| Self::AsyncGenerator { home_object, .. } => home_object.as_ref(), | Self::AsyncGenerator { home_object, .. } => home_object.as_ref(),
_ => None, Self::Native { .. } => None,
} }
} }
@ -405,7 +294,7 @@ impl Function {
| Self::Async { home_object, .. } | Self::Async { home_object, .. }
| Self::Generator { home_object, .. } | Self::Generator { home_object, .. }
| Self::AsyncGenerator { home_object, .. } => *home_object = Some(object), | Self::AsyncGenerator { home_object, .. } => *home_object = Some(object),
_ => {} Self::Native { .. } => {}
} }
} }
@ -487,7 +376,7 @@ impl Function {
/// If no length is provided, the length will be set to 0. /// If no length is provided, the length will be set to 0.
// TODO: deprecate/remove this. // TODO: deprecate/remove this.
pub(crate) fn make_builtin_fn<N>( pub(crate) fn make_builtin_fn<N>(
function: NativeFunctionSignature, function: NativeFunctionPointer,
name: N, name: N,
parent: &JsObject, parent: &JsObject,
length: usize, length: usize,
@ -505,7 +394,7 @@ pub(crate) fn make_builtin_fn<N>(
.function() .function()
.prototype(), .prototype(),
ObjectData::function(Function::Native { ObjectData::function(Function::Native {
function, function: NativeFunction::from_fn_ptr(function),
constructor: None, constructor: None,
}), }),
); );
@ -905,7 +794,7 @@ impl BuiltInFunctionObject {
.unwrap_or_else(|| "anonymous".into()); .unwrap_or_else(|| "anonymous".into());
match function { match function {
Function::Native { .. } | Function::Closure { .. } | Function::Ordinary { .. } => { Function::Native { .. } | Function::Ordinary { .. } => {
Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into()) Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into())
} }
Function::Async { .. } => { Function::Async { .. } => {
@ -949,7 +838,7 @@ impl BuiltIn for BuiltInFunctionObject {
let _timer = Profiler::global().start_event("function", "init"); let _timer = Profiler::global().start_event("function", "init");
let function_prototype = context.intrinsics().constructors().function().prototype(); let function_prototype = context.intrinsics().constructors().function().prototype();
FunctionBuilder::native(context, Self::prototype) FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::prototype))
.name("") .name("")
.length(0) .length(0)
.constructor(false) .constructor(false)
@ -957,7 +846,8 @@ impl BuiltIn for BuiltInFunctionObject {
let symbol_has_instance = WellKnownSymbols::has_instance(); let symbol_has_instance = WellKnownSymbols::has_instance();
let has_instance = FunctionBuilder::native(context, Self::has_instance) let has_instance =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::has_instance))
.name("[Symbol.iterator]") .name("[Symbol.iterator]")
.length(1) .length(1)
.constructor(false) .constructor(false)

11
boa_engine/src/builtins/function/tests.rs

@ -1,7 +1,8 @@
use crate::{ use crate::{
error::JsNativeError, error::JsNativeError,
forward, forward_val, js_string, forward, forward_val, js_string,
object::{FunctionBuilder, JsObject}, native_function::NativeFunction,
object::{FunctionObjectBuilder, JsObject},
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
string::utf16, string::utf16,
Context, JsNativeErrorKind, Context, JsNativeErrorKind,
@ -244,8 +245,9 @@ fn closure_capture_clone() {
) )
.unwrap(); .unwrap();
let func = FunctionBuilder::closure_with_captures( let func = FunctionObjectBuilder::new(
&mut context, &mut context,
NativeFunction::from_copy_closure_with_captures(
|_, _, captures, context| { |_, _, captures, context| {
let (string, object) = &captures; let (string, object) = &captures;
@ -255,11 +257,14 @@ fn closure_capture_clone() {
.__get_own_property__(&"key".into(), context)? .__get_own_property__(&"key".into(), context)?
.and_then(|prop| prop.value().cloned()) .and_then(|prop| prop.value().cloned())
.and_then(|val| val.as_string().cloned()) .and_then(|val| val.as_string().cloned())
.ok_or_else(|| JsNativeError::typ().with_message("invalid `key` property"))? .ok_or_else(
|| JsNativeError::typ().with_message("invalid `key` property")
)?
); );
Ok(hw.into()) Ok(hw.into())
}, },
(string, object), (string, object),
),
) )
.name("closure") .name("closure")
.build(); .build();

3
boa_engine/src/builtins/generator_function/mod.rs

@ -15,6 +15,7 @@ use crate::{
function::{BuiltInFunctionObject, ConstructorKind, Function}, function::{BuiltInFunctionObject, ConstructorKind, Function},
BuiltIn, BuiltIn,
}, },
native_function::NativeFunction,
object::ObjectData, object::ObjectData,
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -72,7 +73,7 @@ impl BuiltIn for GeneratorFunction {
.configurable(false); .configurable(false);
constructor.borrow_mut().insert("prototype", property); constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native { constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor, function: NativeFunction::from_fn_ptr(Self::constructor),
constructor: Some(ConstructorKind::Base), constructor: Some(ConstructorKind::Base),
}); });

12
boa_engine/src/builtins/intl/collator/mod.rs

@ -14,9 +14,10 @@ use tap::{Conv, Pipe};
use crate::{ use crate::{
builtins::{BuiltIn, JsArgs}, builtins::{BuiltIn, JsArgs},
context::{intrinsics::StandardConstructors, BoaProvider}, context::{intrinsics::StandardConstructors, BoaProvider},
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsFunction, JsObject, ObjectData, FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -160,7 +161,8 @@ impl BuiltIn for Collator {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let compare = FunctionBuilder::native(context, Self::compare) let compare =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::compare))
.name("get compare") .name("get compare")
.constructor(false) .constructor(false)
.build(); .build();
@ -415,10 +417,11 @@ impl Collator {
let bound_compare = if let Some(f) = collator.bound_compare.clone() { let bound_compare = if let Some(f) = collator.bound_compare.clone() {
f f
} else { } else {
let bound_compare = FunctionBuilder::closure_with_captures( let bound_compare = FunctionObjectBuilder::new(
context, context,
// 10.3.3.1. Collator Compare Functions // 10.3.3.1. Collator Compare Functions
// https://tc39.es/ecma402/#sec-collator-compare-functions // https://tc39.es/ecma402/#sec-collator-compare-functions
NativeFunction::from_copy_closure_with_captures(
|_, args, collator, context| { |_, args, collator, context| {
// 1. Let collator be F.[[Collator]]. // 1. Let collator be F.[[Collator]].
// 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot. // 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot.
@ -441,6 +444,7 @@ impl Collator {
Ok(result.into()) Ok(result.into())
}, },
collator_obj, collator_obj,
),
) )
.length(2) .length(2)
.build(); .build();

35
boa_engine/src/builtins/intl/locale/mod.rs

@ -20,9 +20,10 @@ use crate::{
builtins::{BuiltIn, JsArgs}, builtins::{BuiltIn, JsArgs},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
js_string, js_string,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -40,52 +41,62 @@ impl BuiltIn for Locale {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let base_name = FunctionBuilder::native(context, Self::base_name) let base_name =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::base_name))
.name("get baseName") .name("get baseName")
.constructor(false) .constructor(false)
.build(); .build();
let calendar = FunctionBuilder::native(context, Self::calendar) let calendar =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::calendar))
.name("get calendar") .name("get calendar")
.constructor(false) .constructor(false)
.build(); .build();
let case_first = FunctionBuilder::native(context, Self::case_first) let case_first =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::case_first))
.name("get caseFirst") .name("get caseFirst")
.constructor(false) .constructor(false)
.build(); .build();
let collation = FunctionBuilder::native(context, Self::collation) let collation =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::collation))
.name("get collation") .name("get collation")
.constructor(false) .constructor(false)
.build(); .build();
let hour_cycle = FunctionBuilder::native(context, Self::hour_cycle) let hour_cycle =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::hour_cycle))
.name("get hourCycle") .name("get hourCycle")
.constructor(false) .constructor(false)
.build(); .build();
let numeric = FunctionBuilder::native(context, Self::numeric) let numeric =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::numeric))
.name("get numeric") .name("get numeric")
.constructor(false) .constructor(false)
.build(); .build();
let numbering_system = FunctionBuilder::native(context, Self::numbering_system) let numbering_system = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(Self::numbering_system),
)
.name("get numberingSystem") .name("get numberingSystem")
.constructor(false) .constructor(false)
.build(); .build();
let language = FunctionBuilder::native(context, Self::language) let language =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::language))
.name("get language") .name("get language")
.constructor(false) .constructor(false)
.build(); .build();
let script = FunctionBuilder::native(context, Self::script) let script = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::script))
.name("get script") .name("get script")
.constructor(false) .constructor(false)
.build(); .build();
let region = FunctionBuilder::native(context, Self::region) let region = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::region))
.name("get region") .name("get region")
.constructor(false) .constructor(false)
.build(); .build();

27
boa_engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -4,7 +4,8 @@ use crate::{
promise::{if_abrupt_reject_promise, PromiseCapability}, promise::{if_abrupt_reject_promise, PromiseCapability},
JsArgs, Promise, JsArgs, Promise,
}, },
object::{FunctionBuilder, JsObject, ObjectData}, native_function::NativeFunction,
object::{FunctionObjectBuilder, JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
Context, JsNativeError, JsResult, JsValue, Context, JsNativeError, JsResult, JsValue,
}; };
@ -29,15 +30,24 @@ pub(crate) fn create_async_from_sync_iterator_prototype(context: &mut Context<'_
ObjectData::ordinary(), ObjectData::ordinary(),
); );
let next_function = FunctionBuilder::native(context, AsyncFromSyncIterator::next) let next_function = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(AsyncFromSyncIterator::next),
)
.name("next") .name("next")
.length(1) .length(1)
.build(); .build();
let return_function = FunctionBuilder::native(context, AsyncFromSyncIterator::r#return) let return_function = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(AsyncFromSyncIterator::r#return),
)
.name("return") .name("return")
.length(1) .length(1)
.build(); .build();
let throw_function = FunctionBuilder::native(context, AsyncFromSyncIterator::throw) let throw_function = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(AsyncFromSyncIterator::throw),
)
.name("throw") .name("throw")
.length(1) .length(1)
.build(); .build();
@ -400,17 +410,16 @@ impl AsyncFromSyncIterator {
// 8. Let unwrap be a new Abstract Closure with parameters (value) // 8. Let unwrap be a new Abstract Closure with parameters (value)
// that captures done and performs the following steps when called: // that captures done and performs the following steps when called:
// 9. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »). // 9. Let onFulfilled be CreateBuiltinFunction(unwrap, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures( let on_fulfilled = FunctionObjectBuilder::new(
context, context,
|_this, args, done, context| { NativeFunction::from_copy_closure(move |_this, args, context| {
// a. Return CreateIterResultObject(value, done). // a. Return CreateIterResultObject(value, done).
Ok(create_iter_result_object( Ok(create_iter_result_object(
args.get_or_undefined(0).clone(), args.get_or_undefined(0).clone(),
*done, done,
context, context,
)) ))
}, }),
done,
) )
.name("") .name("")
.length(1) .length(1)

14
boa_engine/src/builtins/map/mod.rs

@ -16,9 +16,10 @@ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -42,18 +43,21 @@ impl BuiltIn for Map {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
let get_size = FunctionBuilder::native(context, Self::get_size) let get_size =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_size))
.name("get size") .name("get size")
.length(0) .length(0)
.constructor(false) .constructor(false)
.build(); .build();
let entries_function = FunctionBuilder::native(context, Self::entries) let entries_function =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::entries))
.name("entries") .name("entries")
.length(0) .length(0)
.constructor(false) .constructor(false)

4
boa_engine/src/builtins/mod.rs

@ -138,7 +138,7 @@ fn init_builtin<B: BuiltIn>(context: &mut Context<'_>) {
/// Initializes built-in objects and functions /// Initializes built-in objects and functions
pub fn init(context: &mut Context<'_>) { pub fn init(context: &mut Context<'_>) {
macro_rules! globals { macro_rules! globals {
($( $builtin:ty ),*) => { ($( $builtin:ty ),*$(,)?) => {
$(init_builtin::<$builtin>(context) $(init_builtin::<$builtin>(context)
);* );*
} }
@ -195,7 +195,7 @@ pub fn init(context: &mut Context<'_>) {
AsyncGenerator, AsyncGenerator,
AsyncGeneratorFunction, AsyncGeneratorFunction,
Uri, Uri,
WeakRef WeakRef,
}; };
#[cfg(feature = "intl")] #[cfg(feature = "intl")]

23
boa_engine/src/builtins/number/mod.rs

@ -17,9 +17,10 @@ use crate::{
builtins::{string::is_trimmable_whitespace, BuiltIn, JsArgs}, builtins::{string::is_trimmable_whitespace, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
string::utf16, string::utf16,
@ -50,13 +51,15 @@ impl BuiltIn for Number {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let parse_int = FunctionBuilder::native(context, Self::parse_int) let parse_int =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::parse_int))
.name("parseInt") .name("parseInt")
.length(2) .length(2)
.constructor(false) .constructor(false)
.build(); .build();
let parse_float = FunctionBuilder::native(context, Self::parse_float) let parse_float =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::parse_float))
.name("parseFloat") .name("parseFloat")
.length(1) .length(1)
.constructor(false) .constructor(false)
@ -73,8 +76,16 @@ impl BuiltIn for Number {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
); );
context.register_global_builtin_function("isFinite", 1, Self::global_is_finite); context.register_global_builtin_callable(
context.register_global_builtin_function("isNaN", 1, Self::global_is_nan); "isFinite",
1,
NativeFunction::from_fn_ptr(Self::global_is_finite),
);
context.register_global_builtin_callable(
"isNaN",
1,
NativeFunction::from_fn_ptr(Self::global_is_nan),
);
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;

19
boa_engine/src/builtins/object/mod.rs

@ -21,9 +21,10 @@ use crate::{
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
js_string, js_string,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
IntegrityLevel, JsObject, ObjectData, ObjectKind, FunctionObjectBuilder, IntegrityLevel, JsObject, ObjectData, ObjectKind,
}, },
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
string::utf16, string::utf16,
@ -48,11 +49,17 @@ impl BuiltIn for Object {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let legacy_proto_getter = FunctionBuilder::native(context, Self::legacy_proto_getter) let legacy_proto_getter = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(Self::legacy_proto_getter),
)
.name("get __proto__") .name("get __proto__")
.build(); .build();
let legacy_setter_proto = FunctionBuilder::native(context, Self::legacy_proto_setter) let legacy_setter_proto = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(Self::legacy_proto_setter),
)
.name("set __proto__") .name("set __proto__")
.build(); .build();
@ -1268,8 +1275,9 @@ impl Object {
// 4. Let closure be a new Abstract Closure with parameters (key, value) that captures // 4. Let closure be a new Abstract Closure with parameters (key, value) that captures
// obj and performs the following steps when called: // obj and performs the following steps when called:
let closure = FunctionBuilder::closure_with_captures( let closure = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_, args, obj, context| { |_, args, obj, context| {
let key = args.get_or_undefined(0); let key = args.get_or_undefined(0);
let value = args.get_or_undefined(1); let value = args.get_or_undefined(1);
@ -1284,6 +1292,7 @@ impl Object {
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
obj.clone(), obj.clone(),
),
); );
// 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »). // 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »).

64
boa_engine/src/builtins/promise/mod.rs

@ -12,9 +12,10 @@ use crate::{
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
job::JobCallback, job::JobCallback,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsFunction, JsObject, ObjectData, FunctionObjectBuilder, JsFunction, JsObject, ObjectData,
}, },
property::{Attribute, PropertyDescriptorBuilder}, property::{Attribute, PropertyDescriptorBuilder},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -152,8 +153,9 @@ impl PromiseCapability {
// 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures promiseCapability and performs the following steps when called: // 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, "", « »). // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »).
let executor = FunctionBuilder::closure_with_captures( let executor = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, args: &[JsValue], captures, _| { |_this, args: &[JsValue], captures, _| {
let mut promise_capability = captures.borrow_mut(); let mut promise_capability = captures.borrow_mut();
// a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception. // a. If promiseCapability.[[Resolve]] is not undefined, throw a TypeError exception.
@ -183,6 +185,7 @@ impl PromiseCapability {
Ok(JsValue::Undefined) Ok(JsValue::Undefined)
}, },
promise_capability.clone(), promise_capability.clone(),
),
) )
.name("") .name("")
.length(2) .length(2)
@ -254,7 +257,8 @@ impl BuiltIn for Promise {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
@ -542,8 +546,9 @@ impl Promise {
// o. Set onFulfilled.[[Values]] to values. // o. Set onFulfilled.[[Values]] to values.
// p. Set onFulfilled.[[Capability]] to resultCapability. // p. Set onFulfilled.[[Capability]] to resultCapability.
// q. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. // q. Set onFulfilled.[[RemainingElements]] to remainingElementsCount.
let on_fulfilled = FunctionBuilder::closure_with_captures( let on_fulfilled = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| { |_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions // https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions
@ -562,7 +567,8 @@ impl Promise {
// 7. Let remainingElementsCount be F.[[RemainingElements]]. // 7. Let remainingElementsCount be F.[[RemainingElements]].
// 8. Set values[index] to x. // 8. Set values[index] to x.
captures.values.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); captures.values.borrow_mut()[captures.index] =
args.get_or_undefined(0).clone();
// 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
captures captures
@ -595,6 +601,7 @@ impl Promise {
capability_resolve: result_capability.resolve.clone(), capability_resolve: result_capability.resolve.clone(),
remaining_elements_count: remaining_elements_count.clone(), remaining_elements_count: remaining_elements_count.clone(),
}, },
),
) )
.name("") .name("")
.length(1) .length(1)
@ -784,8 +791,9 @@ impl Promise {
// p. Set onFulfilled.[[Values]] to values. // p. Set onFulfilled.[[Values]] to values.
// q. Set onFulfilled.[[Capability]] to resultCapability. // q. Set onFulfilled.[[Capability]] to resultCapability.
// r. Set onFulfilled.[[RemainingElements]] to remainingElementsCount. // r. Set onFulfilled.[[RemainingElements]] to remainingElementsCount.
let on_fulfilled = FunctionBuilder::closure_with_captures( let on_fulfilled = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| { |_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions // https://tc39.es/ecma262/#sec-promise.allsettled-resolve-element-functions
@ -854,6 +862,7 @@ impl Promise {
capability: result_capability.resolve.clone(), capability: result_capability.resolve.clone(),
remaining_elements: remaining_elements_count.clone(), remaining_elements: remaining_elements_count.clone(),
}, },
),
) )
.name("") .name("")
.length(1) .length(1)
@ -868,8 +877,9 @@ impl Promise {
// x. Set onRejected.[[Values]] to values. // x. Set onRejected.[[Values]] to values.
// y. Set onRejected.[[Capability]] to resultCapability. // y. Set onRejected.[[Capability]] to resultCapability.
// z. Set onRejected.[[RemainingElements]] to remainingElementsCount. // z. Set onRejected.[[RemainingElements]] to remainingElementsCount.
let on_rejected = FunctionBuilder::closure_with_captures( let on_rejected = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| { |_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions // https://tc39.es/ecma262/#sec-promise.allsettled-reject-element-functions
@ -938,6 +948,7 @@ impl Promise {
capability: result_capability.resolve.clone(), capability: result_capability.resolve.clone(),
remaining_elements: remaining_elements_count.clone(), remaining_elements: remaining_elements_count.clone(),
}, },
),
) )
.name("") .name("")
.length(1) .length(1)
@ -1138,8 +1149,9 @@ impl Promise {
// o. Set onRejected.[[Errors]] to errors. // o. Set onRejected.[[Errors]] to errors.
// p. Set onRejected.[[Capability]] to resultCapability. // p. Set onRejected.[[Capability]] to resultCapability.
// q. Set onRejected.[[RemainingElements]] to remainingElementsCount. // q. Set onRejected.[[RemainingElements]] to remainingElementsCount.
let on_rejected = FunctionBuilder::closure_with_captures( let on_rejected = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_, args, captures, context| { |_, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise.any-reject-element-functions // https://tc39.es/ecma262/#sec-promise.any-reject-element-functions
@ -1159,7 +1171,8 @@ impl Promise {
// 7. Let remainingElementsCount be F.[[RemainingElements]]. // 7. Let remainingElementsCount be F.[[RemainingElements]].
// 8. Set errors[index] to x. // 8. Set errors[index] to x.
captures.errors.borrow_mut()[captures.index] = args.get_or_undefined(0).clone(); captures.errors.borrow_mut()[captures.index] =
args.get_or_undefined(0).clone();
// 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. // 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
captures captures
@ -1211,6 +1224,7 @@ impl Promise {
capability_reject: result_capability.reject.clone(), capability_reject: result_capability.reject.clone(),
remaining_elements_count: remaining_elements_count.clone(), remaining_elements_count: remaining_elements_count.clone(),
}, },
),
) )
.name("") .name("")
.length(1) .length(1)
@ -1262,8 +1276,9 @@ impl Promise {
// 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. // 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. // 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]] »). // 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »).
let resolve = FunctionBuilder::closure_with_captures( let resolve = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| { |_this, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise-resolve-functions // https://tc39.es/ecma262/#sec-promise-resolve-functions
@ -1369,6 +1384,7 @@ impl Promise {
Ok(JsValue::Undefined) Ok(JsValue::Undefined)
}, },
resolve_captures, resolve_captures,
),
) )
.name("") .name("")
.length(1) .length(1)
@ -1385,8 +1401,9 @@ impl Promise {
// 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions. // 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. // 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]] »). // 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »).
let reject = FunctionBuilder::closure_with_captures( let reject = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| { |_this, args, captures, context| {
// https://tc39.es/ecma262/#sec-promise-reject-functions // https://tc39.es/ecma262/#sec-promise-reject-functions
@ -1419,6 +1436,7 @@ impl Promise {
Ok(JsValue::Undefined) Ok(JsValue::Undefined)
}, },
reject_captures, reject_captures,
),
) )
.name("") .name("")
.length(1) .length(1)
@ -1817,8 +1835,9 @@ 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: // 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 = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| { |_this, args, captures, context| {
/// Capture object for the abstract `returnValue` closure. /// Capture object for the abstract `returnValue` closure.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
@ -1829,7 +1848,8 @@ impl Promise {
let value = args.get_or_undefined(0); let value = args.get_or_undefined(0);
// i. Let result be ? Call(onFinally, undefined). // i. Let result be ? Call(onFinally, undefined).
let result = captures let result =
captures
.on_finally .on_finally
.call(&JsValue::undefined(), &[], context)?; .call(&JsValue::undefined(), &[], context)?;
@ -1837,8 +1857,9 @@ impl Promise {
let promise = Self::promise_resolve(captures.c.clone(), result, context)?; 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: // 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( let return_value = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, _args, captures, _context| { |_this, _args, captures, _context| {
// 1. Return value. // 1. Return value.
Ok(captures.value.clone()) Ok(captures.value.clone())
@ -1846,6 +1867,7 @@ impl Promise {
ReturnValueCaptures { ReturnValueCaptures {
value: value.clone(), value: value.clone(),
}, },
),
); );
// iv. Let valueThunk be CreateBuiltinFunction(returnValue, 0, "", « »). // iv. Let valueThunk be CreateBuiltinFunction(returnValue, 0, "", « »).
@ -1858,14 +1880,16 @@ impl Promise {
on_finally: on_finally.clone(), on_finally: on_finally.clone(),
c: c.clone(), c: c.clone(),
}, },
),
); );
// b. Let thenFinally be CreateBuiltinFunction(thenFinallyClosure, 1, "", « »). // b. Let thenFinally be CreateBuiltinFunction(thenFinallyClosure, 1, "", « »).
let then_finally = then_finally_closure.length(1).name("").build(); 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: // 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 = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, captures, context| { |_this, args, captures, context| {
/// Capture object for the abstract `throwReason` closure. /// Capture object for the abstract `throwReason` closure.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
@ -1876,7 +1900,8 @@ impl Promise {
let reason = args.get_or_undefined(0); let reason = args.get_or_undefined(0);
// i. Let result be ? Call(onFinally, undefined). // i. Let result be ? Call(onFinally, undefined).
let result = captures let result =
captures
.on_finally .on_finally
.call(&JsValue::undefined(), &[], context)?; .call(&JsValue::undefined(), &[], context)?;
@ -1884,8 +1909,9 @@ impl Promise {
let promise = Self::promise_resolve(captures.c.clone(), result, context)?; 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: // 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( let throw_reason = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, _args, captures, _context| { |_this, _args, captures, _context| {
// 1. Return ThrowCompletion(reason). // 1. Return ThrowCompletion(reason).
Err(JsError::from_opaque(captures.reason.clone())) Err(JsError::from_opaque(captures.reason.clone()))
@ -1893,6 +1919,7 @@ impl Promise {
ThrowReasonCaptures { ThrowReasonCaptures {
reason: reason.clone(), reason: reason.clone(),
}, },
),
); );
// iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »). // iv. Let thrower be CreateBuiltinFunction(throwReason, 0, "", « »).
@ -1905,6 +1932,7 @@ impl Promise {
on_finally: on_finally.clone(), on_finally: on_finally.clone(),
c, c,
}, },
),
); );
// d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »). // d. Let catchFinally be CreateBuiltinFunction(catchFinallyClosure, 1, "", « »).

19
boa_engine/src/builtins/promise/promise_job.rs

@ -2,7 +2,8 @@ use super::{Promise, PromiseCapability};
use crate::{ use crate::{
builtins::promise::{ReactionRecord, ReactionType}, builtins::promise::{ReactionRecord, ReactionType},
job::JobCallback, job::JobCallback,
object::{FunctionBuilder, JsObject}, native_function::NativeFunction,
object::{FunctionObjectBuilder, JsObject},
Context, JsValue, Context, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -27,8 +28,9 @@ 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: // 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 = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this, _args, captures, context| { |_this, _args, captures, context| {
let ReactionJobCaptures { reaction, argument } = captures; let ReactionJobCaptures { reaction, argument } = captures;
@ -96,6 +98,7 @@ impl PromiseJob {
} }
}, },
ReactionJobCaptures { reaction, argument }, ReactionJobCaptures { reaction, argument },
),
) )
.build() .build()
.into(); .into();
@ -121,8 +124,9 @@ impl PromiseJob {
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JobCallback { ) -> 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: // 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 = FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_this: &JsValue, _args: &[JsValue], captures, context: &mut Context<'_>| { |_this: &JsValue, _args: &[JsValue], captures, context: &mut Context<'_>| {
let JobCapture { let JobCapture {
promise_to_resolve, promise_to_resolve,
@ -148,15 +152,18 @@ impl PromiseJob {
if let Err(value) = then_call_result { if let Err(value) = then_call_result {
let value = value.to_opaque(context); let value = value.to_opaque(context);
// i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »).
return resolving_functions return resolving_functions.reject.call(
.reject &JsValue::Undefined,
.call(&JsValue::Undefined, &[value], context); &[value],
context,
);
} }
// d. Return ? thenCallResult. // d. Return ? thenCallResult.
then_call_result then_call_result
}, },
JobCapture::new(promise_to_resolve, thenable, then), JobCapture::new(promise_to_resolve, thenable, then),
),
) )
.build(); .build();

11
boa_engine/src/builtins/proxy/mod.rs

@ -10,10 +10,13 @@
//! [spec]: https://tc39.es/ecma262/#sec-proxy-objects //! [spec]: https://tc39.es/ecma262/#sec-proxy-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
use std::cell::Cell;
use crate::{ use crate::{
builtins::{BuiltIn, JsArgs}, builtins::{BuiltIn, JsArgs},
error::JsNativeError, error::JsNativeError,
object::{ConstructorBuilder, FunctionBuilder, JsFunction, JsObject, ObjectData}, native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, JsObject, ObjectData},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -134,8 +137,9 @@ impl Proxy {
pub(crate) fn revoker(proxy: JsObject, context: &mut Context<'_>) -> JsFunction { pub(crate) fn revoker(proxy: JsObject, context: &mut Context<'_>) -> JsFunction {
// 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »). // 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »).
// 4. Set revoker.[[RevocableProxy]] to p. // 4. Set revoker.[[RevocableProxy]] to p.
FunctionBuilder::closure_with_captures( FunctionObjectBuilder::new(
context, context,
NativeFunction::from_copy_closure_with_captures(
|_, _, revocable_proxy, _| { |_, _, revocable_proxy, _| {
// a. Let F be the active function object. // a. Let F be the active function object.
// b. Let p be F.[[RevocableProxy]]. // b. Let p be F.[[RevocableProxy]].
@ -154,7 +158,8 @@ impl Proxy {
// h. Return undefined. // h. Return undefined.
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
Some(proxy), Cell::new(Some(proxy)),
),
) )
.build() .build()
} }

35
boa_engine/src/builtins/regexp/mod.rs

@ -18,9 +18,10 @@ use crate::{
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
js_string, js_string,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::{Attribute, PropertyDescriptorBuilder}, property::{Attribute, PropertyDescriptorBuilder},
string::{utf16, CodePoint}, string::{utf16, CodePoint},
@ -53,46 +54,56 @@ impl BuiltIn for RegExp {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE;
let get_has_indices = FunctionBuilder::native(context, Self::get_has_indices) let get_has_indices =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_has_indices))
.name("get hasIndices") .name("get hasIndices")
.constructor(false) .constructor(false)
.build(); .build();
let get_global = FunctionBuilder::native(context, Self::get_global) let get_global =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_global))
.name("get global") .name("get global")
.constructor(false) .constructor(false)
.build(); .build();
let get_ignore_case = FunctionBuilder::native(context, Self::get_ignore_case) let get_ignore_case =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_ignore_case))
.name("get ignoreCase") .name("get ignoreCase")
.constructor(false) .constructor(false)
.build(); .build();
let get_multiline = FunctionBuilder::native(context, Self::get_multiline) let get_multiline =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_multiline))
.name("get multiline") .name("get multiline")
.constructor(false) .constructor(false)
.build(); .build();
let get_dot_all = FunctionBuilder::native(context, Self::get_dot_all) let get_dot_all =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_dot_all))
.name("get dotAll") .name("get dotAll")
.constructor(false) .constructor(false)
.build(); .build();
let get_unicode = FunctionBuilder::native(context, Self::get_unicode) let get_unicode =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_unicode))
.name("get unicode") .name("get unicode")
.constructor(false) .constructor(false)
.build(); .build();
let get_sticky = FunctionBuilder::native(context, Self::get_sticky) let get_sticky =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_sticky))
.name("get sticky") .name("get sticky")
.constructor(false) .constructor(false)
.build(); .build();
let get_flags = FunctionBuilder::native(context, Self::get_flags) let get_flags =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_flags))
.name("get flags") .name("get flags")
.constructor(false) .constructor(false)
.build(); .build();
let get_source = FunctionBuilder::native(context, Self::get_source) let get_source =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_source))
.name("get source") .name("get source")
.constructor(false) .constructor(false)
.build(); .build();

14
boa_engine/src/builtins/set/mod.rs

@ -16,9 +16,10 @@ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
error::JsNativeError, error::JsNativeError,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -41,12 +42,14 @@ impl BuiltIn for Set {
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init"); let _timer = Profiler::global().start_event(Self::NAME, "init");
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
let size_getter = FunctionBuilder::native(context, Self::size_getter) let size_getter =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::size_getter))
.constructor(false) .constructor(false)
.name("get size") .name("get size")
.build(); .build();
@ -55,7 +58,8 @@ impl BuiltIn for Set {
let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag = WellKnownSymbols::to_string_tag();
let values_function = FunctionBuilder::native(context, Self::values) let values_function =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values))
.name("values") .name("values")
.length(0) .length(0)
.constructor(false) .constructor(false)

9
boa_engine/src/builtins/symbol/mod.rs

@ -22,7 +22,8 @@ use super::JsArgs;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
error::JsNativeError, error::JsNativeError,
object::{ConstructorBuilder, FunctionBuilder}, native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder},
property::Attribute, property::Attribute,
symbol::{JsSymbol, WellKnownSymbols}, symbol::{JsSymbol, WellKnownSymbols},
value::JsValue, value::JsValue,
@ -96,13 +97,15 @@ impl BuiltIn for Symbol {
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
let to_primitive = FunctionBuilder::native(context, Self::to_primitive) let to_primitive =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::to_primitive))
.name("[Symbol.toPrimitive]") .name("[Symbol.toPrimitive]")
.length(1) .length(1)
.constructor(false) .constructor(false)
.build(); .build();
let get_description = FunctionBuilder::native(context, Self::get_description) let get_description =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_description))
.name("get description") .name("get description")
.constructor(false) .constructor(false)
.build(); .build();

31
boa_engine/src/builtins/typed_array/mod.rs

@ -22,9 +22,10 @@ use crate::{
context::intrinsics::{StandardConstructor, StandardConstructors}, context::intrinsics::{StandardConstructor, StandardConstructors},
error::JsNativeError, error::JsNativeError,
js_string, js_string,
native_function::NativeFunction,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
JsObject, ObjectData, FunctionObjectBuilder, JsObject, ObjectData,
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -66,7 +67,10 @@ macro_rules! typed_array {
.typed_array() .typed_array()
.prototype(); .prototype();
let get_species = FunctionBuilder::native(context, TypedArray::get_species) let get_species = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(TypedArray::get_species),
)
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
@ -250,37 +254,44 @@ pub(crate) struct TypedArray;
impl BuiltIn for TypedArray { impl BuiltIn for TypedArray {
const NAME: &'static str = "TypedArray"; const NAME: &'static str = "TypedArray";
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let get_species = FunctionBuilder::native(context, Self::get_species) let get_species =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::get_species))
.name("get [Symbol.species]") .name("get [Symbol.species]")
.constructor(false) .constructor(false)
.build(); .build();
let get_buffer = FunctionBuilder::native(context, Self::buffer) let get_buffer =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::buffer))
.name("get buffer") .name("get buffer")
.constructor(false) .constructor(false)
.build(); .build();
let get_byte_length = FunctionBuilder::native(context, Self::byte_length) let get_byte_length =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::byte_length))
.name("get byteLength") .name("get byteLength")
.constructor(false) .constructor(false)
.build(); .build();
let get_byte_offset = FunctionBuilder::native(context, Self::byte_offset) let get_byte_offset =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::byte_offset))
.name("get byteOffset") .name("get byteOffset")
.constructor(false) .constructor(false)
.build(); .build();
let get_length = FunctionBuilder::native(context, Self::length) let get_length =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::length))
.name("get length") .name("get length")
.constructor(false) .constructor(false)
.build(); .build();
let get_to_string_tag = FunctionBuilder::native(context, Self::to_string_tag) let get_to_string_tag =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::to_string_tag))
.name("get [Symbol.toStringTag]") .name("get [Symbol.toStringTag]")
.constructor(false) .constructor(false)
.build(); .build();
let values_function = FunctionBuilder::native(context, Self::values) let values_function =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values))
.name("values") .name("values")
.length(0) .length(0)
.constructor(false) .constructor(false)

20
boa_engine/src/builtins/uri/mod.rs

@ -19,8 +19,8 @@ use self::consts::{
use super::BuiltIn; use super::BuiltIn;
use crate::{ use crate::{
builtins::JsArgs, js_string, object::FunctionBuilder, property::Attribute, string::CodePoint, builtins::JsArgs, js_string, native_function::NativeFunction, object::FunctionObjectBuilder,
Context, JsNativeError, JsResult, JsString, JsValue, property::Attribute, string::CodePoint, Context, JsNativeError, JsResult, JsString, JsValue,
}; };
/// URI Handling Functions /// URI Handling Functions
@ -31,7 +31,8 @@ impl BuiltIn for Uri {
const NAME: &'static str = "Uri"; const NAME: &'static str = "Uri";
fn init(context: &mut Context<'_>) -> Option<JsValue> { fn init(context: &mut Context<'_>) -> Option<JsValue> {
let decode_uri = FunctionBuilder::native(context, Self::decode_uri) let decode_uri =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::decode_uri))
.name("decodeURI") .name("decodeURI")
.length(1) .length(1)
.constructor(false) .constructor(false)
@ -43,7 +44,10 @@ impl BuiltIn for Uri {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
); );
let decode_uri_component = FunctionBuilder::native(context, Self::decode_uri_component) let decode_uri_component = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(Self::decode_uri_component),
)
.name("decodeURIComponent") .name("decodeURIComponent")
.length(1) .length(1)
.constructor(false) .constructor(false)
@ -55,7 +59,8 @@ impl BuiltIn for Uri {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
); );
let encode_uri = FunctionBuilder::native(context, Self::encode_uri) let encode_uri =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::encode_uri))
.name("encodeURI") .name("encodeURI")
.length(1) .length(1)
.constructor(false) .constructor(false)
@ -67,7 +72,10 @@ impl BuiltIn for Uri {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
); );
let encode_uri_component = FunctionBuilder::native(context, Self::encode_uri_component) let encode_uri_component = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(Self::encode_uri_component),
)
.name("encodeURIComponent") .name("encodeURIComponent")
.length(1) .length(1)
.constructor(false) .constructor(false)

6
boa_engine/src/class.rs

@ -62,8 +62,8 @@
//! [class-trait]: ./trait.Class.html //! [class-trait]: ./trait.Class.html
use crate::{ use crate::{
builtins::function::NativeFunctionSignature,
error::JsNativeError, error::JsNativeError,
native_function::NativeFunctionPointer,
object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE}, object::{ConstructorBuilder, JsFunction, JsObject, NativeObject, ObjectData, PROTOTYPE},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue, Context, JsResult, JsValue,
@ -183,7 +183,7 @@ impl<'ctx, 'icu> ClassBuilder<'ctx, 'icu> {
&mut self, &mut self,
name: N, name: N,
length: usize, length: usize,
function: NativeFunctionSignature, function: NativeFunctionPointer,
) -> &mut Self ) -> &mut Self
where where
N: AsRef<str>, N: AsRef<str>,
@ -199,7 +199,7 @@ impl<'ctx, 'icu> ClassBuilder<'ctx, 'icu> {
&mut self, &mut self,
name: N, name: N,
length: usize, length: usize,
function: NativeFunctionSignature, function: NativeFunctionPointer,
) -> &mut Self ) -> &mut Self
where where
N: AsRef<str>, N: AsRef<str>,

85
boa_engine/src/context/mod.rs

@ -17,11 +17,12 @@ pub use std::marker::PhantomData;
#[cfg(feature = "console")] #[cfg(feature = "console")]
use crate::builtins::console::Console; use crate::builtins::console::Console;
use crate::{ use crate::{
builtins::{self, function::NativeFunctionSignature}, builtins,
bytecompiler::ByteCompiler, bytecompiler::ByteCompiler,
class::{Class, ClassBuilder}, class::{Class, ClassBuilder},
job::JobCallback, job::JobCallback,
object::{FunctionBuilder, GlobalPropertyMap, JsObject}, native_function::NativeFunction,
object::{FunctionObjectBuilder, GlobalPropertyMap, JsObject},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm}, vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm},
@ -268,10 +269,7 @@ impl Context<'_> {
); );
} }
/// Register a global native function. /// Register a global native callable.
///
/// This is more efficient that creating a closure function, since this does not allocate,
/// it is just a function pointer.
/// ///
/// The function will be both `constructable` (call with `new <name>()`) and `callable` (call /// The function will be both `constructable` (call with `new <name>()`) and `callable` (call
/// with `<name>()`). /// with `<name>()`).
@ -281,18 +279,10 @@ impl Context<'_> {
/// ///
/// # Note /// # Note
/// ///
/// If you want to make a function only `constructable`, or wish to bind it differently /// If you wish to only create the function object without binding it to the global object, you
/// to the global object, you can create the function object with /// can use the [`FunctionObjectBuilder`] API.
/// [`FunctionBuilder`](crate::object::FunctionBuilder::native). And bind it to the global pub fn register_global_callable(&mut self, name: &str, length: usize, body: NativeFunction) {
/// object with [`Context::register_global_property`](Context::register_global_property) let function = FunctionObjectBuilder::new(self, body)
/// method.
pub fn register_global_function(
&mut self,
name: &str,
length: usize,
body: NativeFunctionSignature,
) {
let function = FunctionBuilder::native(self, body)
.name(name) .name(name)
.length(length) .length(length)
.constructor(true) .constructor(true)
@ -311,24 +301,20 @@ impl Context<'_> {
/// Register a global native function that is not a constructor. /// 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` /// 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. /// and `configurable` attributes. The same as when you create a function in JavaScript.
/// ///
/// # Note /// # Note
/// ///
/// The difference to [`Context::register_global_function`](Context::register_global_function) is, /// The difference to [`Context::register_global_callable`] is, that the function will not be
/// that the function will not be `constructable`. /// `constructable`. Usage of the function as a constructor will produce a `TypeError`.
/// Usage of the function as a constructor will produce a `TypeError`. pub fn register_global_builtin_callable(
pub fn register_global_builtin_function(
&mut self, &mut self,
name: &str, name: &str,
length: usize, length: usize,
body: NativeFunctionSignature, body: NativeFunction,
) { ) {
let function = FunctionBuilder::native(self, body) let function = FunctionObjectBuilder::new(self, body)
.name(name) .name(name)
.length(length) .length(length)
.constructor(false) .constructor(false)
@ -345,51 +331,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 <https://github.com/boa-dev/boa/issues/1515> for an explanation on
/// why we need to restrict the set of accepted closures.
pub fn register_global_closure<F>(&mut self, name: &str, length: usize, body: F) -> JsResult<()>
where
F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue> + 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(())
}
/// Register a global class of type `T`, where `T` implements `Class`. /// Register a global class of type `T`, where `T` implements `Class`.
/// ///
/// # Example /// # Example

1
boa_engine/src/lib.rs

@ -115,6 +115,7 @@ pub mod context;
pub mod environments; pub mod environments;
pub mod error; pub mod error;
pub mod job; pub mod job;
pub mod native_function;
pub mod object; pub mod object;
pub mod property; pub mod property;
pub mod realm; pub mod realm;

365
boa_engine/src/native_function.rs

@ -0,0 +1,365 @@
//! Boa's wrappers for native Rust functions to be compatible with ECMAScript calls.
//!
//! [`NativeFunction`] is the main type of this module, providing APIs to create native callables
//! from native Rust functions and closures.
use std::marker::PhantomData;
use boa_gc::{custom_trace, Finalize, Gc, Trace};
use crate::{Context, JsResult, JsValue};
/// The required signature for all native built-in function pointers.
///
/// # 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 NativeFunctionPointer = fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>;
trait TraceableClosure: Trace {
fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue>;
}
#[derive(Trace, Finalize)]
struct Closure<F, T>
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult<JsValue>,
T: Trace,
{
// SAFETY: `NativeFunction`'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<F, T> TraceableClosure for Closure<F, T>
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult<JsValue>,
T: Trace,
{
fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
(self.f)(this, args, &self.captures, context)
}
}
/// A callable Rust function that can be invoked by the engine.
///
/// `NativeFunction` functions are divided in two:
/// - Function pointers a.k.a common functions (see [`NativeFunctionPointer`]).
/// - 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 `NativeFunction`, albeit by using an `unsafe`
/// API, but note that passing closures implicitly capturing traceable types could cause
/// **Undefined Behaviour**.
#[derive(Clone)]
pub struct NativeFunction {
inner: Inner,
}
#[derive(Clone)]
enum Inner {
PointerFn(NativeFunctionPointer),
Closure(Gc<dyn TraceableClosure>),
}
impl Finalize for NativeFunction {
fn finalize(&self) {
if let Inner::Closure(c) = &self.inner {
c.finalize();
}
}
}
// Manual implementation because deriving `Trace` triggers the `single_use_lifetimes` lint.
// SAFETY: Only closures can contain `Trace` captures, so this implementation is safe.
unsafe impl Trace for NativeFunction {
custom_trace!(this, {
if let Inner::Closure(c) = &this.inner {
mark(c);
}
});
}
impl std::fmt::Debug for NativeFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeFunction").finish_non_exhaustive()
}
}
impl NativeFunction {
/// Creates a `NativeFunction` from a function pointer.
#[inline]
pub fn from_fn_ptr(function: NativeFunctionPointer) -> Self {
Self {
inner: Inner::PointerFn(function),
}
}
/// Creates a `NativeFunction` from a `Copy` closure.
pub fn from_copy_closure<F>(closure: F) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue> + Copy + 'static,
{
// SAFETY: The `Copy` bound ensures there are no traceable types inside the closure.
unsafe { Self::from_closure(closure) }
}
/// Creates a `NativeFunction` from a `Copy` closure and a list of traceable captures.
pub fn from_copy_closure_with_captures<F, T>(closure: F, captures: T) -> Self
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult<JsValue> + Copy + 'static,
T: Trace + 'static,
{
// SAFETY: The `Copy` bound ensures there are no traceable types inside the closure.
unsafe { Self::from_closure_with_captures(closure, captures) }
}
/// Creates a new `NativeFunction` 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 <https://github.com/Manishearth/rust-gc/issues/50> for a technical explanation
/// on why that is the case.
pub unsafe fn from_closure<F>(closure: F) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue> + '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 `NativeFunction` 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 <https://github.com/Manishearth/rust-gc/issues/50> for a technical explanation
/// on why that is the case.
pub unsafe fn from_closure_with_captures<F, T>(closure: F, captures: T) -> Self
where
F: Fn(&JsValue, &[JsValue], &T, &mut Context<'_>) -> JsResult<JsValue> + 'static,
T: Trace + 'static,
{
// Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the
// future: https://github.com/rust-lang/rust/issues/18598
let ptr = Gc::into_raw(Gc::new(Closure {
f: closure,
captures,
}));
// SAFETY: The pointer returned by `into_raw` is only used to coerce to a trait object,
// meaning this is safe.
unsafe {
Self {
inner: Inner::Closure(Gc::from_raw(ptr)),
}
}
}
/// Calls this `NativeFunction`, forwarding the arguments to the corresponding function.
#[inline]
pub fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
match self.inner {
Inner::PointerFn(f) => f(this, args, context),
Inner::Closure(ref c) => c.call(this, args, context),
}
}
}
trait TraceableGenericClosure<Ret, Args>: Trace {
fn call(&mut self, args: Args, context: &mut Context<'_>) -> Ret;
}
#[derive(Trace, Finalize)]
struct GenericClosure<Ret, Args, F, T>
where
F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret,
T: Trace,
{
// SAFETY: `GenericNativeFunction`'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,
#[allow(clippy::type_complexity)]
phantom: PhantomData<Box<dyn FnMut(Args, &mut T, &mut Context<'_>) -> Ret>>,
}
impl<Ret, Args, F, T> TraceableGenericClosure<Ret, Args> for GenericClosure<Ret, Args, F, T>
where
F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret,
T: Trace,
{
fn call(&mut self, args: Args, context: &mut Context<'_>) -> Ret {
(self.f)(args, &mut self.captures, context)
}
}
/// A callable generic Rust function that can be invoked by the engine.
///
/// This is a more general struct of the [`NativeFunction`] API, useful for callbacks defined in the
/// host that are useful to the engine, such as [`HostCallJobCallback`] or [`HostEnqueuePromiseJob`].
///
/// `GenericNativeFunction` functions are divided in two:
/// - Function pointers a.k.a common functions.
/// - Closure functions that can capture the current environment.
///
/// # Caveats
///
/// - Since the Rust language doesn't support [**variadic generics**], all functions requiring
/// more than 1 argument (excluding the required [`Context`]), will define its generic parameter
/// `Args` as a tuple instead, which slightly worsens the API. We hope this can improve when
/// variadic generics arrive.
///
/// - 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 `GenericNativeFunction`, albeit by using an
/// `unsafe` API, but note that passing closures implicitly capturing traceable types could cause
/// **Undefined Behaviour**.
///
/// [`HostCallJobCallback`]: https://tc39.es/ecma262/#sec-hostcalljobcallback
/// [`HostEnqueuePromiseJob`]: https://tc39.es/ecma262/#sec-hostenqueuepromisejob
/// [**variadic generics**]: https://github.com/rust-lang/rfcs/issues/376
pub struct GenericNativeFunction<Ret: 'static, Args: 'static> {
inner: GenericInner<Ret, Args>,
}
enum GenericInner<Ret: 'static, Args: 'static> {
PointerFn(fn(Args, &mut Context<'_>) -> Ret),
Closure(Box<dyn TraceableGenericClosure<Ret, Args>>),
}
impl<Ret, Args> Finalize for GenericNativeFunction<Ret, Args> {
fn finalize(&self) {
if let GenericInner::Closure(c) = &self.inner {
c.finalize();
}
}
}
// Manual implementation because deriving `Trace` triggers the `single_use_lifetimes` lint.
// SAFETY: Only closures can contain `Trace` captures, so this implementation is safe.
unsafe impl<Ret, Args> Trace for GenericNativeFunction<Ret, Args> {
custom_trace!(this, {
if let GenericInner::Closure(c) = &this.inner {
mark(c);
}
});
}
impl<Ret, Args> std::fmt::Debug for GenericNativeFunction<Ret, Args> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NativeFunction").finish_non_exhaustive()
}
}
impl<Ret, Args> GenericNativeFunction<Ret, Args> {
/// Creates a `GenericNativeFunction` from a function pointer.
#[inline]
pub fn from_fn_ptr(function: fn(Args, &mut Context<'_>) -> Ret) -> Self {
Self {
inner: GenericInner::PointerFn(function),
}
}
/// Creates a `GenericNativeFunction` from a `Copy` closure.
pub fn from_copy_closure<F>(closure: F) -> Self
where
F: FnMut(Args, &mut Context<'_>) -> Ret + Copy + 'static,
{
// SAFETY: The `Copy` bound ensures there are no traceable types inside the closure.
unsafe { Self::from_closure(closure) }
}
/// Creates a `GenericNativeFunction` from a `Copy` closure and a list of traceable captures.
pub fn from_copy_closure_with_captures<F, T>(closure: F, captures: T) -> Self
where
F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret + Copy + 'static,
T: Trace + 'static,
{
// SAFETY: The `Copy` bound ensures there are no traceable types inside the closure.
unsafe { Self::from_closure_with_captures(closure, captures) }
}
/// Creates a new `GenericNativeFunction` 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 <https://github.com/Manishearth/rust-gc/issues/50> for a technical explanation
/// on why that is the case.
pub unsafe fn from_closure<F>(mut closure: F) -> Self
where
F: FnMut(Args, &mut Context<'_>) -> Ret + 'static,
{
// SAFETY: The caller must ensure the invariants of the closure hold.
unsafe {
Self::from_closure_with_captures(move |args, _, context| closure(args, context), ())
}
}
/// Create a new `GenericNativeFunction` 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 <https://github.com/Manishearth/rust-gc/issues/50> for a technical explanation
/// on why that is the case.
pub unsafe fn from_closure_with_captures<F, T>(closure: F, captures: T) -> Self
where
F: FnMut(Args, &mut T, &mut Context<'_>) -> Ret + 'static,
T: Trace + 'static,
{
Self {
inner: GenericInner::Closure(Box::new(GenericClosure {
f: closure,
captures,
phantom: PhantomData,
})),
}
}
/// Calls this `GenericNativeFunction`, forwarding the arguments to the corresponding function.
#[inline]
pub fn call(&mut self, args: Args, context: &mut Context<'_>) -> Ret {
match self.inner {
GenericInner::PointerFn(f) => f(args, context),
GenericInner::Closure(ref mut c) => c.call(args, context),
}
}
}

103
boa_engine/src/object/builtins/jsproxy.rs

@ -2,8 +2,9 @@
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
use crate::{ use crate::{
builtins::{function::NativeFunctionSignature, Proxy}, builtins::Proxy,
object::{FunctionBuilder, JsObject, JsObjectType, ObjectData}, native_function::{NativeFunction, NativeFunctionPointer},
object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -104,19 +105,19 @@ impl std::ops::Deref for JsRevocableProxy {
#[derive(Clone)] #[derive(Clone)]
pub struct JsProxyBuilder { pub struct JsProxyBuilder {
target: JsObject, target: JsObject,
apply: Option<NativeFunctionSignature>, apply: Option<NativeFunctionPointer>,
construct: Option<NativeFunctionSignature>, construct: Option<NativeFunctionPointer>,
define_property: Option<NativeFunctionSignature>, define_property: Option<NativeFunctionPointer>,
delete_property: Option<NativeFunctionSignature>, delete_property: Option<NativeFunctionPointer>,
get: Option<NativeFunctionSignature>, get: Option<NativeFunctionPointer>,
get_own_property_descriptor: Option<NativeFunctionSignature>, get_own_property_descriptor: Option<NativeFunctionPointer>,
get_prototype_of: Option<NativeFunctionSignature>, get_prototype_of: Option<NativeFunctionPointer>,
has: Option<NativeFunctionSignature>, has: Option<NativeFunctionPointer>,
is_extensible: Option<NativeFunctionSignature>, is_extensible: Option<NativeFunctionPointer>,
own_keys: Option<NativeFunctionSignature>, own_keys: Option<NativeFunctionPointer>,
prevent_extensions: Option<NativeFunctionSignature>, prevent_extensions: Option<NativeFunctionPointer>,
set: Option<NativeFunctionSignature>, set: Option<NativeFunctionPointer>,
set_prototype_of: Option<NativeFunctionSignature>, set_prototype_of: Option<NativeFunctionPointer>,
} }
impl std::fmt::Debug for JsProxyBuilder { impl std::fmt::Debug for JsProxyBuilder {
@ -195,7 +196,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply
#[inline] #[inline]
pub fn apply(mut self, apply: NativeFunctionSignature) -> Self { pub fn apply(mut self, apply: NativeFunctionPointer) -> Self {
self.apply = Some(apply); self.apply = Some(apply);
self self
} }
@ -213,7 +214,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/construct
#[inline] #[inline]
pub fn construct(mut self, construct: NativeFunctionSignature) -> Self { pub fn construct(mut self, construct: NativeFunctionPointer) -> Self {
self.construct = Some(construct); self.construct = Some(construct);
self self
} }
@ -226,7 +227,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/defineProperty
#[inline] #[inline]
pub fn define_property(mut self, define_property: NativeFunctionSignature) -> Self { pub fn define_property(mut self, define_property: NativeFunctionPointer) -> Self {
self.define_property = Some(define_property); self.define_property = Some(define_property);
self self
} }
@ -239,7 +240,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/deleteProperty
#[inline] #[inline]
pub fn delete_property(mut self, delete_property: NativeFunctionSignature) -> Self { pub fn delete_property(mut self, delete_property: NativeFunctionPointer) -> Self {
self.delete_property = Some(delete_property); self.delete_property = Some(delete_property);
self self
} }
@ -252,7 +253,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get
#[inline] #[inline]
pub fn get(mut self, get: NativeFunctionSignature) -> Self { pub fn get(mut self, get: NativeFunctionPointer) -> Self {
self.get = Some(get); self.get = Some(get);
self self
} }
@ -267,7 +268,7 @@ impl JsProxyBuilder {
#[inline] #[inline]
pub fn get_own_property_descriptor( pub fn get_own_property_descriptor(
mut self, mut self,
get_own_property_descriptor: NativeFunctionSignature, get_own_property_descriptor: NativeFunctionPointer,
) -> Self { ) -> Self {
self.get_own_property_descriptor = Some(get_own_property_descriptor); self.get_own_property_descriptor = Some(get_own_property_descriptor);
self self
@ -281,7 +282,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getPrototypeOf
#[inline] #[inline]
pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionSignature) -> Self { pub fn get_prototype_of(mut self, get_prototype_of: NativeFunctionPointer) -> Self {
self.get_prototype_of = Some(get_prototype_of); self.get_prototype_of = Some(get_prototype_of);
self self
} }
@ -294,7 +295,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has
#[inline] #[inline]
pub fn has(mut self, has: NativeFunctionSignature) -> Self { pub fn has(mut self, has: NativeFunctionPointer) -> Self {
self.has = Some(has); self.has = Some(has);
self self
} }
@ -307,7 +308,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/isExtensible
#[inline] #[inline]
pub fn is_extensible(mut self, is_extensible: NativeFunctionSignature) -> Self { pub fn is_extensible(mut self, is_extensible: NativeFunctionPointer) -> Self {
self.is_extensible = Some(is_extensible); self.is_extensible = Some(is_extensible);
self self
} }
@ -320,7 +321,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/ownKeys
#[inline] #[inline]
pub fn own_keys(mut self, own_keys: NativeFunctionSignature) -> Self { pub fn own_keys(mut self, own_keys: NativeFunctionPointer) -> Self {
self.own_keys = Some(own_keys); self.own_keys = Some(own_keys);
self self
} }
@ -333,7 +334,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/preventExtensions
#[inline] #[inline]
pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionSignature) -> Self { pub fn prevent_extensions(mut self, prevent_extensions: NativeFunctionPointer) -> Self {
self.prevent_extensions = Some(prevent_extensions); self.prevent_extensions = Some(prevent_extensions);
self self
} }
@ -346,7 +347,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/set
#[inline] #[inline]
pub fn set(mut self, set: NativeFunctionSignature) -> Self { pub fn set(mut self, set: NativeFunctionPointer) -> Self {
self.set = Some(set); self.set = Some(set);
self self
} }
@ -359,7 +360,7 @@ impl JsProxyBuilder {
/// ///
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/setPrototypeOf
#[inline] #[inline]
pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionSignature) -> Self { pub fn set_prototype_of(mut self, set_prototype_of: NativeFunctionPointer) -> Self {
self.set_prototype_of = Some(set_prototype_of); self.set_prototype_of = Some(set_prototype_of);
self self
} }
@ -374,13 +375,15 @@ impl JsProxyBuilder {
let handler = JsObject::with_object_proto(context); let handler = JsObject::with_object_proto(context);
if let Some(apply) = self.apply { if let Some(apply) = self.apply {
let f = FunctionBuilder::native(context, apply).length(3).build(); let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(apply))
.length(3)
.build();
handler handler
.create_data_property_or_throw("apply", f, context) .create_data_property_or_throw("apply", f, context)
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(construct) = self.construct { if let Some(construct) = self.construct {
let f = FunctionBuilder::native(context, construct) let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(construct))
.length(3) .length(3)
.build(); .build();
handler handler
@ -388,7 +391,8 @@ impl JsProxyBuilder {
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(define_property) = self.define_property { if let Some(define_property) = self.define_property {
let f = FunctionBuilder::native(context, define_property) let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(define_property))
.length(3) .length(3)
.build(); .build();
handler handler
@ -396,7 +400,8 @@ impl JsProxyBuilder {
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(delete_property) = self.delete_property { if let Some(delete_property) = self.delete_property {
let f = FunctionBuilder::native(context, delete_property) let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(delete_property))
.length(2) .length(2)
.build(); .build();
handler handler
@ -404,13 +409,18 @@ impl JsProxyBuilder {
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(get) = self.get { if let Some(get) = self.get {
let f = FunctionBuilder::native(context, get).length(3).build(); let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get))
.length(3)
.build();
handler handler
.create_data_property_or_throw("get", f, context) .create_data_property_or_throw("get", f, context)
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(get_own_property_descriptor) = self.get_own_property_descriptor { if let Some(get_own_property_descriptor) = self.get_own_property_descriptor {
let f = FunctionBuilder::native(context, get_own_property_descriptor) let f = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(get_own_property_descriptor),
)
.length(2) .length(2)
.build(); .build();
handler handler
@ -418,7 +428,8 @@ impl JsProxyBuilder {
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(get_prototype_of) = self.get_prototype_of { if let Some(get_prototype_of) = self.get_prototype_of {
let f = FunctionBuilder::native(context, get_prototype_of) let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(get_prototype_of))
.length(1) .length(1)
.build(); .build();
handler handler
@ -426,13 +437,15 @@ impl JsProxyBuilder {
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(has) = self.has { if let Some(has) = self.has {
let f = FunctionBuilder::native(context, has).length(2).build(); let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(has))
.length(2)
.build();
handler handler
.create_data_property_or_throw("has", f, context) .create_data_property_or_throw("has", f, context)
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(is_extensible) = self.is_extensible { if let Some(is_extensible) = self.is_extensible {
let f = FunctionBuilder::native(context, is_extensible) let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(is_extensible))
.length(1) .length(1)
.build(); .build();
handler handler
@ -440,13 +453,18 @@ impl JsProxyBuilder {
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(own_keys) = self.own_keys { if let Some(own_keys) = self.own_keys {
let f = FunctionBuilder::native(context, own_keys).length(1).build(); let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(own_keys))
.length(1)
.build();
handler handler
.create_data_property_or_throw("ownKeys", f, context) .create_data_property_or_throw("ownKeys", f, context)
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(prevent_extensions) = self.prevent_extensions { if let Some(prevent_extensions) = self.prevent_extensions {
let f = FunctionBuilder::native(context, prevent_extensions) let f = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(prevent_extensions),
)
.length(1) .length(1)
.build(); .build();
handler handler
@ -454,13 +472,16 @@ impl JsProxyBuilder {
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(set) = self.set { if let Some(set) = self.set {
let f = FunctionBuilder::native(context, set).length(4).build(); let f = FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set))
.length(4)
.build();
handler handler
.create_data_property_or_throw("set", f, context) .create_data_property_or_throw("set", f, context)
.expect("new object should be writable"); .expect("new object should be writable");
} }
if let Some(set_prototype_of) = self.set_prototype_of { if let Some(set_prototype_of) = self.set_prototype_of {
let f = FunctionBuilder::native(context, set_prototype_of) let f =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(set_prototype_of))
.length(2) .length(2)
.build(); .build();
handler handler

93
boa_engine/src/object/mod.rs

@ -33,10 +33,7 @@ use crate::{
async_generator::AsyncGenerator, async_generator::AsyncGenerator,
error::ErrorKind, error::ErrorKind,
function::arguments::Arguments, function::arguments::Arguments,
function::{ function::{arguments::ParameterMap, BoundFunction, ConstructorKind, Function},
arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function,
NativeFunctionSignature,
},
generator::Generator, generator::Generator,
iterable::AsyncFromSyncIterator, iterable::AsyncFromSyncIterator,
map::map_iterator::MapIterator, map::map_iterator::MapIterator,
@ -51,10 +48,10 @@ use crate::{
DataView, Date, Promise, RegExp, DataView, Date, Promise, RegExp,
}, },
context::intrinsics::StandardConstructor, context::intrinsics::StandardConstructor,
error::JsNativeError,
js_string, js_string,
native_function::{NativeFunction, NativeFunctionPointer},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, Context, JsBigInt, JsString, JsSymbol, JsValue,
}; };
use boa_gc::{custom_trace, Finalize, GcCell, Trace, WeakGc}; use boa_gc::{custom_trace, Finalize, GcCell, Trace, WeakGc};
@ -1899,17 +1896,17 @@ where
/// Builder for creating native function objects /// Builder for creating native function objects
#[derive(Debug)] #[derive(Debug)]
pub struct FunctionBuilder<'ctx, 'icu> { pub struct FunctionObjectBuilder<'ctx, 'icu> {
context: &'ctx mut Context<'icu>, context: &'ctx mut Context<'icu>,
function: Function, function: Function,
name: JsString, name: JsString,
length: usize, length: usize,
} }
impl<'ctx, 'icu> FunctionBuilder<'ctx, 'icu> { impl<'ctx, 'icu> FunctionObjectBuilder<'ctx, 'icu> {
/// Create a new `FunctionBuilder` for creating a native function. /// Create a new `FunctionBuilder` for creating a native function.
#[inline] #[inline]
pub fn native(context: &'ctx mut Context<'icu>, function: NativeFunctionSignature) -> Self { pub fn new(context: &'ctx mut Context<'icu>, function: NativeFunction) -> Self {
Self { Self {
context, context,
function: Function::Native { function: Function::Native {
@ -1921,57 +1918,6 @@ impl<'ctx, 'icu> FunctionBuilder<'ctx, 'icu> {
} }
} }
/// Create a new `FunctionBuilder` for creating a closure function.
pub fn closure<F>(context: &'ctx mut Context<'icu>, function: F) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue> + 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.
pub fn closure_with_captures<F, C>(
context: &'ctx mut Context<'icu>,
function: F,
captures: C,
) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut C, &mut Context<'_>) -> JsResult<JsValue> + 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::<C>().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. /// Specify the name property of object function object.
/// ///
/// The default is `""` (empty string). /// The default is `""` (empty string).
@ -2005,10 +1951,6 @@ impl<'ctx, 'icu> FunctionBuilder<'ctx, 'icu> {
Function::Native { Function::Native {
ref mut constructor, ref mut constructor,
.. ..
}
| Function::Closure {
ref mut constructor,
..
} => { } => {
*constructor = yes.then_some(ConstructorKind::Base); *constructor = yes.then_some(ConstructorKind::Base);
} }
@ -2102,7 +2044,7 @@ impl<'ctx, 'icu> ObjectInitializer<'ctx, 'icu> {
/// Add a function to the object. /// Add a function to the object.
pub fn function<B>( pub fn function<B>(
&mut self, &mut self,
function: NativeFunctionSignature, function: NativeFunctionPointer,
binding: B, binding: B,
length: usize, length: usize,
) -> &mut Self ) -> &mut Self
@ -2110,7 +2052,8 @@ impl<'ctx, 'icu> ObjectInitializer<'ctx, 'icu> {
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
let binding = binding.into(); let binding = binding.into();
let function = FunctionBuilder::native(self.context, function) let function =
FunctionObjectBuilder::new(self.context, NativeFunction::from_fn_ptr(function))
.name(binding.name) .name(binding.name)
.length(length) .length(length)
.constructor(false) .constructor(false)
@ -2152,7 +2095,7 @@ impl<'ctx, 'icu> ObjectInitializer<'ctx, 'icu> {
/// Builder for creating constructors objects, like `Array`. /// Builder for creating constructors objects, like `Array`.
pub struct ConstructorBuilder<'ctx, 'icu> { pub struct ConstructorBuilder<'ctx, 'icu> {
context: &'ctx mut Context<'icu>, context: &'ctx mut Context<'icu>,
function: NativeFunctionSignature, function: NativeFunctionPointer,
object: JsObject, object: JsObject,
has_prototype_property: bool, has_prototype_property: bool,
prototype: JsObject, prototype: JsObject,
@ -2184,7 +2127,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> {
#[inline] #[inline]
pub fn new( pub fn new(
context: &'ctx mut Context<'icu>, context: &'ctx mut Context<'icu>,
function: NativeFunctionSignature, function: NativeFunctionPointer,
) -> ConstructorBuilder<'ctx, 'icu> { ) -> ConstructorBuilder<'ctx, 'icu> {
Self { Self {
context, context,
@ -2203,7 +2146,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> {
pub(crate) fn with_standard_constructor( pub(crate) fn with_standard_constructor(
context: &'ctx mut Context<'icu>, context: &'ctx mut Context<'icu>,
function: NativeFunctionSignature, function: NativeFunctionPointer,
standard_constructor: StandardConstructor, standard_constructor: StandardConstructor,
) -> ConstructorBuilder<'ctx, 'icu> { ) -> ConstructorBuilder<'ctx, 'icu> {
Self { Self {
@ -2224,7 +2167,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> {
/// Add new method to the constructors prototype. /// Add new method to the constructors prototype.
pub fn method<B>( pub fn method<B>(
&mut self, &mut self,
function: NativeFunctionSignature, function: NativeFunctionPointer,
binding: B, binding: B,
length: usize, length: usize,
) -> &mut Self ) -> &mut Self
@ -2232,7 +2175,8 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> {
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
let binding = binding.into(); let binding = binding.into();
let function = FunctionBuilder::native(self.context, function) let function =
FunctionObjectBuilder::new(self.context, NativeFunction::from_fn_ptr(function))
.name(binding.name) .name(binding.name)
.length(length) .length(length)
.constructor(false) .constructor(false)
@ -2252,7 +2196,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> {
/// Add new static method to the constructors object itself. /// Add new static method to the constructors object itself.
pub fn static_method<B>( pub fn static_method<B>(
&mut self, &mut self,
function: NativeFunctionSignature, function: NativeFunctionPointer,
binding: B, binding: B,
length: usize, length: usize,
) -> &mut Self ) -> &mut Self
@ -2260,7 +2204,8 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> {
B: Into<FunctionBinding>, B: Into<FunctionBinding>,
{ {
let binding = binding.into(); let binding = binding.into();
let function = FunctionBuilder::native(self.context, function) let function =
FunctionObjectBuilder::new(self.context, NativeFunction::from_fn_ptr(function))
.name(binding.name) .name(binding.name)
.length(length) .length(length)
.constructor(false) .constructor(false)
@ -2443,7 +2388,7 @@ impl<'ctx, 'icu> ConstructorBuilder<'ctx, 'icu> {
pub fn build(&mut self) -> JsFunction { pub fn build(&mut self) -> JsFunction {
// Create the native function // Create the native function
let function = Function::Native { let function = Function::Native {
function: self.function, function: NativeFunction::from_fn_ptr(self.function),
constructor: self.constructor, constructor: self.constructor,
}; };

45
boa_engine/src/vm/code_block.rs

@ -705,25 +705,16 @@ impl JsObject {
function, function,
constructor, constructor,
} => { } => {
let function = *function; let function = function.clone();
let constructor = *constructor; let constructor = *constructor;
drop(object); drop(object);
if constructor.is_some() { if constructor.is_some() {
function(&JsValue::undefined(), args, context) function.call(&JsValue::undefined(), args, context)
} else { } 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 { Function::Ordinary {
code, environments, .. code, environments, ..
} => { } => {
@ -1288,41 +1279,15 @@ impl JsObject {
function, function,
constructor, 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 function = function.clone();
let captures = captures.clone();
let constructor = *constructor; let constructor = *constructor;
drop(object); drop(object);
match (function)(this_target, args, captures, context)? { match function.call(this_target, args, context)? {
JsValue::Object(ref o) => Ok(o.clone()), JsValue::Object(ref o) => Ok(o.clone()),
val => { val => {
if constructor.expect("hmma").is_base() || val.is_undefined() { if constructor.expect("hmm").is_base() || val.is_undefined() {
create_this(context) create_this(context)
} else { } else {
Err(JsNativeError::typ() Err(JsNativeError::typ()

25
boa_engine/src/vm/opcode/await_stm/mod.rs

@ -1,6 +1,9 @@
use boa_gc::{Gc, GcCell};
use crate::{ use crate::{
builtins::{JsArgs, Promise}, builtins::{JsArgs, Promise},
object::FunctionBuilder, native_function::NativeFunction,
object::FunctionObjectBuilder,
vm::{call_frame::GeneratorResumeKind, opcode::Operation, ShouldExit}, vm::{call_frame::GeneratorResumeKind, opcode::Operation, ShouldExit},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -28,9 +31,12 @@ 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: // 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, "", « »). // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures( let on_fulfilled = FunctionObjectBuilder::new(
context, context,
|_this, args, (environment, stack, frame), context| { NativeFunction::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. // a. Let prevContext be the running execution context.
// b. Suspend prevContext. // b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
@ -55,10 +61,11 @@ impl Operation for Await {
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
( Gc::new(GcCell::new((
context.realm.environments.clone(), context.realm.environments.clone(),
context.vm.stack.clone(), context.vm.stack.clone(),
context.vm.frame().clone(), context.vm.frame().clone(),
))),
), ),
) )
.name("") .name("")
@ -67,9 +74,12 @@ 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: // 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, "", « »). // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionBuilder::closure_with_captures( let on_rejected = FunctionObjectBuilder::new(
context, context,
|_this, args, (environment, stack, frame), context| { NativeFunction::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. // a. Let prevContext be the running execution context.
// b. Suspend prevContext. // b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
@ -94,10 +104,11 @@ impl Operation for Await {
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
( Gc::new(GcCell::new((
context.realm.environments.clone(), context.realm.environments.clone(),
context.vm.stack.clone(), context.vm.stack.clone(),
context.vm.frame().clone(), context.vm.frame().clone(),
))),
), ),
) )
.name("") .name("")

105
boa_examples/src/bin/closures.rs

@ -1,25 +1,31 @@
// This example goes into the details on how to pass closures as functions // This example goes into the details on how to pass closures as functions inside Rust and call them
// inside Rust and call them from Javascript. // from Javascript.
use std::cell::{Cell, RefCell};
use boa_engine::{ use boa_engine::{
js_string, js_string,
object::{FunctionBuilder, JsObject}, native_function::NativeFunction,
object::{builtins::JsArray, FunctionObjectBuilder, JsObject},
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
string::utf16, 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> { fn main() -> Result<(), JsError> {
// We create a new `Context` to create a new Javascript executor. // We create a new `Context` to create a new Javascript executor.
let mut context = Context::default(); let mut context = Context::default();
// We make some operations in Rust that return a `Copy` value that we want // We make some operations in Rust that return a `Copy` value that we want to pass to a Javascript
// to pass to a Javascript function. // function.
let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1; let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1;
// We register a global closure function that has the name 'closure' with length 0. // We register a global closure function that has the name 'closure' with length 0.
context.register_global_closure("closure", 0, move |_, _, _| { context.register_global_callable(
"closure",
0,
NativeFunction::from_copy_closure(move |_, _, _| {
println!("Called `closure`"); println!("Called `closure`");
// `variable` is captured from the main function. // `variable` is captured from the main function.
println!("variable = {variable}"); println!("variable = {variable}");
@ -27,7 +33,8 @@ fn main() -> Result<(), JsError> {
// We return the moved variable as a `JsValue`. // We return the moved variable as a `JsValue`.
Ok(JsValue::new(variable)) Ok(JsValue::new(variable))
})?; }),
);
assert_eq!(context.eval("closure()")?, 255.into()); assert_eq!(context.eval("closure()")?, 255.into());
@ -59,25 +66,28 @@ fn main() -> Result<(), JsError> {
object, object,
}; };
// We can use `FunctionBuilder` to define a closure with additional // We can use `FunctionBuilder` to define a closure with additional captures and custom property
// captures. // attributes.
let js_function = FunctionBuilder::closure_with_captures( let js_function = FunctionObjectBuilder::new(
&mut context, &mut context,
NativeFunction::from_copy_closure_with_captures(
|_, _, captures, context| { |_, _, captures, context| {
let mut captures = captures.borrow_mut();
let BigStruct { greeting, object } = &mut *captures;
println!("Called `createMessage`"); println!("Called `createMessage`");
// We obtain the `name` property of `captures.object` // We obtain the `name` property of `captures.object`
let name = captures.object.get("name", context)?; let name = object.get("name", context)?;
// We create a new message from our captured variable. // We create a new message from our captured variable.
let message = js_string!( let message = js_string!(
utf16!("message from `"), utf16!("message from `"),
&name.to_string(context)?, &name.to_string(context)?,
utf16!("`: "), utf16!("`: "),
&captures.greeting greeting
); );
// We can also mutate the moved data inside the closure. // We can also mutate the moved data inside the closure.
captures.greeting = js_string!(&captures.greeting, utf16!(" Hello!")); captures.greeting = js_string!(greeting, utf16!(" Hello!"));
println!("{}", message.to_std_string_escaped()); println!("{}", message.to_std_string_escaped());
println!(); println!();
@ -86,7 +96,8 @@ fn main() -> Result<(), JsError> {
Ok(message.into()) Ok(message.into())
}, },
// Here is where we move `clone_variable` into the closure. // Here is where we move `clone_variable` into the closure.
clone_variable, GcCell::new(clone_variable),
),
) )
// And here we assign `createMessage` to the `name` property of the closure. // And here we assign `createMessage` to the `name` property of the closure.
.name("createMessage") .name("createMessage")
@ -102,7 +113,7 @@ fn main() -> Result<(), JsError> {
// We pass `js_function` as a property value. // We pass `js_function` as a property value.
js_function, js_function,
// We assign to the "createMessage" property the desired attributes. // 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!( assert_eq!(
@ -119,5 +130,65 @@ fn main() -> Result<(), JsError> {
// We have moved `Clone` variables into a closure and executed that closure // We have moved `Clone` variables into a closure and executed that closure
// inside Javascript! // 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 {
NativeFunction::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(()) Ok(())
} }

24
boa_examples/src/bin/jsarray.rs

@ -1,7 +1,8 @@
// This example shows how to manipulate a Javascript array using Rust code. // This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{ use boa_engine::{
object::{builtins::JsArray, FunctionBuilder}, native_function::NativeFunction,
object::{builtins::JsArray, FunctionObjectBuilder},
string::utf16, string::utf16,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -60,17 +61,23 @@ fn main() -> JsResult<()> {
let joined_array = array.join(Some("::".into()), context)?; let joined_array = array.join(Some("::".into()), context)?;
assert_eq!(&joined_array, utf16!("14::false::false::false::10")); assert_eq!(&joined_array, utf16!("14::false::false::false::10"));
let filter_callback = FunctionBuilder::native(context, |_this, args, _context| { let filter_callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, _context| {
Ok(args.get(0).cloned().unwrap_or_default().is_number().into()) Ok(args.get(0).cloned().unwrap_or_default().is_number().into())
}) }),
)
.build(); .build();
let map_callback = FunctionBuilder::native(context, |_this, args, context| { let map_callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, context| {
args.get(0) args.get(0)
.cloned() .cloned()
.unwrap_or_default() .unwrap_or_default()
.pow(&JsValue::new(2), context) .pow(&JsValue::new(2), context)
}) }),
)
.build(); .build();
let mut data = Vec::new(); let mut data = Vec::new();
@ -88,12 +95,15 @@ fn main() -> JsResult<()> {
assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3")); assert_eq!(&chained_array.join(None, context)?, utf16!("196,1,2,3"));
let reduce_callback = FunctionBuilder::native(context, |_this, args, context| { let reduce_callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, context| {
let accumulator = args.get(0).cloned().unwrap_or_default(); let accumulator = args.get(0).cloned().unwrap_or_default();
let value = args.get(1).cloned().unwrap_or_default(); let value = args.get(1).cloned().unwrap_or_default();
accumulator.add(&value, context) accumulator.add(&value, context)
}) }),
)
.build(); .build();
assert_eq!( assert_eq!(

10
boa_examples/src/bin/jstypedarray.rs

@ -1,7 +1,8 @@
// This example shows how to manipulate a Javascript array using Rust code. // This example shows how to manipulate a Javascript array using Rust code.
use boa_engine::{ use boa_engine::{
object::{builtins::JsUint8Array, FunctionBuilder}, native_function::NativeFunction,
object::{builtins::JsUint8Array, FunctionObjectBuilder},
property::Attribute, property::Attribute,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -23,12 +24,15 @@ fn main() -> JsResult<()> {
sum += i; sum += i;
} }
let callback = FunctionBuilder::native(context, |_this, args, context| { let callback = FunctionObjectBuilder::new(
context,
NativeFunction::from_fn_ptr(|_this, args, context| {
let accumulator = args.get(0).cloned().unwrap_or_default(); let accumulator = args.get(0).cloned().unwrap_or_default();
let value = args.get(1).cloned().unwrap_or_default(); let value = args.get(1).cloned().unwrap_or_default();
accumulator.add(&value, context) accumulator.add(&value, context)
}) }),
)
.build(); .build();
assert_eq!( assert_eq!(

7
boa_examples/src/bin/modulehandler.rs

@ -1,7 +1,10 @@
// This example implements a custom module handler which mimics // This example implements a custom module handler which mimics
// the require/module.exports pattern // the require/module.exports pattern
use boa_engine::{prelude::JsObject, property::Attribute, Context, JsResult, JsValue}; use boa_engine::{
native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsResult,
JsValue,
};
use std::fs::read_to_string; use std::fs::read_to_string;
fn main() { fn main() {
@ -17,7 +20,7 @@ fn main() {
let mut ctx = Context::default(); let mut ctx = Context::default();
// Adding custom implementation that mimics 'require' // Adding custom implementation that mimics 'require'
ctx.register_global_function("require", 0, require); ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require));
// Adding custom object that mimics 'module.exports' // Adding custom object that mimics 'module.exports'
let moduleobj = JsObject::default(); let moduleobj = JsObject::default();

2
boa_gc/src/internals/gc_box.rs

@ -102,7 +102,7 @@ impl fmt::Debug for GcBoxHeader {
/// A garbage collected allocation. /// A garbage collected allocation.
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct GcBox<T: Trace + ?Sized + 'static> { pub struct GcBox<T: Trace + ?Sized + 'static> {
pub(crate) header: GcBoxHeader, pub(crate) header: GcBoxHeader,
pub(crate) value: T, pub(crate) value: T,
} }

3
boa_gc/src/internals/mod.rs

@ -1,4 +1,5 @@
mod ephemeron_box; mod ephemeron_box;
mod gc_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;

2
boa_gc/src/lib.rs

@ -95,7 +95,6 @@ mod trace;
pub(crate) mod internals; pub(crate) mod internals;
use boa_profiler::Profiler; use boa_profiler::Profiler;
use internals::GcBox;
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
mem, mem,
@ -105,6 +104,7 @@ use std::{
pub use crate::trace::{Finalize, Trace}; pub use crate::trace::{Finalize, Trace};
pub use boa_macros::{Finalize, Trace}; pub use boa_macros::{Finalize, Trace};
pub use cell::{GcCell, GcCellRef, GcCellRefMut}; pub use cell::{GcCell, GcCellRef, GcCellRefMut};
pub use internals::GcBox;
pub use pointers::{Ephemeron, Gc, WeakGc}; pub use pointers::{Ephemeron, Gc, WeakGc};
type GcPointer = NonNull<GcBox<dyn Trace>>; type GcPointer = NonNull<GcBox<dyn Trace>>;

36
boa_gc/src/pointers/gc.rs

@ -47,6 +47,16 @@ impl<T: Trace> Gc<T> {
gc.set_root(); gc.set_root();
gc 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<T>) -> NonNull<GcBox<T>> {
let ptr = this.inner_ptr.get();
std::mem::forget(this);
ptr
}
} }
impl<T: Trace + ?Sized> Gc<T> { impl<T: Trace + ?Sized> Gc<T> {
@ -55,9 +65,26 @@ impl<T: Trace + ?Sized> Gc<T> {
GcBox::ptr_eq(this.inner(), other.inner()) GcBox::ptr_eq(this.inner(), other.inner())
} }
/// Will return a new rooted `Gc` from a `GcBox` pointer /// Constructs a `Gc<T>` from a raw pointer.
pub(crate) fn from_ptr(ptr: NonNull<GcBox<T>>) -> Self { ///
// SAFETY: the value provided as a pointer MUST be a valid GcBox. /// The raw pointer must have been returned by a previous call to [`Gc<U>::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<GcBox<T>>) -> 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<GcBox<T>>) -> Self {
// SAFETY: the caller must ensure that the pointer is valid.
unsafe { unsafe {
ptr.as_ref().root_inner(); ptr.as_ref().root_inner();
let gc = Self { let gc = Self {
@ -159,7 +186,8 @@ unsafe impl<T: Trace + ?Sized> Trace for Gc<T> {
impl<T: Trace + ?Sized> Clone for Gc<T> { impl<T: Trace + ?Sized> Clone for Gc<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self::from_ptr(self.inner_ptr()) // SAFETY: `&self` is a valid Gc pointer.
unsafe { Self::from_ptr(self.inner_ptr()) }
} }
} }

19
boa_gc/src/trace.rs

@ -1,5 +1,6 @@
use std::{ use std::{
borrow::{Cow, ToOwned}, borrow::{Cow, ToOwned},
cell::Cell,
collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque},
hash::{BuildHasher, Hash}, hash::{BuildHasher, Hash},
marker::PhantomData, marker::PhantomData,
@ -445,3 +446,21 @@ where
} }
}); });
} }
impl<T: Trace> Finalize for Cell<Option<T>> {
fn finalize(&self) {
if let Some(t) = self.take() {
t.finalize();
self.set(Some(t));
}
}
}
unsafe impl<T: Trace> Trace for Cell<Option<T>> {
custom_trace!(this, {
if let Some(t) = this.take() {
mark(&t);
this.set(Some(t));
}
});
}

49
boa_tester/src/exec/mod.rs

@ -7,14 +7,13 @@ use super::{
}; };
use crate::read::ErrorType; use crate::read::ErrorType;
use boa_engine::{ use boa_engine::{
builtins::JsArgs, object::FunctionBuilder, property::Attribute, Context, JsNativeErrorKind, builtins::JsArgs, native_function::NativeFunction, object::FunctionObjectBuilder,
JsResult, JsValue, property::Attribute, Context, JsNativeErrorKind, JsValue,
}; };
use boa_gc::{Finalize, Gc, GcCell, Trace};
use boa_parser::Parser; use boa_parser::Parser;
use colored::Colorize; use colored::Colorize;
use rayon::prelude::*; use rayon::prelude::*;
use std::borrow::Cow; use std::{borrow::Cow, cell::RefCell, rc::Rc};
impl TestSuite { impl TestSuite {
/// Runs the test suite. /// Runs the test suite.
@ -360,8 +359,22 @@ impl Test {
/// Registers the print function in the context. /// Registers the print function in the context.
fn register_print_fn(context: &mut Context<'_>, async_result: AsyncResult) { fn register_print_fn(context: &mut Context<'_>, async_result: AsyncResult) {
// We use `FunctionBuilder` to define a closure with additional captures. // We use `FunctionBuilder` to define a closure with additional captures.
let js_function = let js_function = FunctionObjectBuilder::new(
FunctionBuilder::closure_with_captures(context, test262_print, async_result) context,
// SAFETY: `AsyncResult` has only non-traceable captures, making this safe.
unsafe {
NativeFunction::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") .name("print")
.length(1) .length(1)
.build(); .build();
@ -375,33 +388,15 @@ impl Test {
} }
/// Object which includes the result of the async operation. /// Object which includes the result of the async operation.
#[derive(Debug, Clone, Trace, Finalize)] #[derive(Debug, Clone)]
struct AsyncResult { struct AsyncResult {
inner: Gc<GcCell<Result<(), String>>>, inner: Rc<RefCell<Result<(), String>>>,
} }
impl Default for AsyncResult { impl Default for AsyncResult {
fn default() -> Self { fn default() -> Self {
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<JsValue> {
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())
} }

Loading…
Cancel
Save