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

1
boa_engine/Cargo.toml

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

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

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

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

@ -14,9 +14,10 @@ use crate::{
builtins::{typed_array::TypedArrayKind, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
error::JsNativeError,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionObjectBuilder, JsObject, ObjectData,
},
property::Attribute,
symbol::WellKnownSymbols,
@ -55,12 +56,14 @@ impl BuiltIn for ArrayBuffer {
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]")
.constructor(false)
.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")
.build();

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

@ -12,6 +12,7 @@ use crate::{
function::{ConstructorKind, Function},
BuiltIn,
},
native_function::NativeFunction,
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
@ -62,7 +63,7 @@ impl BuiltIn for AsyncFunction {
.configurable(false);
constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor,
function: NativeFunction::from_fn_ptr(Self::constructor),
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,
},
error::JsNativeError,
object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData},
native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor},
symbol::WellKnownSymbols,
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:
// 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures(
let on_fulfilled = FunctionObjectBuilder::new(
context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| {
let mut generator_borrow_mut = generator.borrow_mut();
let gen = generator_borrow_mut
@ -653,6 +655,7 @@ impl AsyncGenerator {
Ok(JsValue::undefined())
},
generator.clone(),
),
)
.name("")
.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:
// 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionBuilder::closure_with_captures(
let on_rejected = FunctionObjectBuilder::new(
context,
NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| {
let mut generator_borrow_mut = generator.borrow_mut();
let gen = generator_borrow_mut
@ -686,6 +690,7 @@ impl AsyncGenerator {
Ok(JsValue::undefined())
},
generator,
),
)
.name("")
.length(1)

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

@ -10,6 +10,7 @@ use crate::{
function::{BuiltInFunctionObject, ConstructorKind, Function},
BuiltIn,
},
native_function::NativeFunction,
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
@ -67,7 +68,7 @@ impl BuiltIn for AsyncGeneratorFunction {
.configurable(false);
constructor.borrow_mut().insert("prototype", property);
constructor.borrow_mut().data = ObjectData::function(Function::Native {
function: Self::constructor,
function: NativeFunction::from_fn_ptr(Self::constructor),
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},
context::intrinsics::StandardConstructors,
error::JsNativeError,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionObjectBuilder, JsObject, ObjectData,
},
property::Attribute,
symbol::WellKnownSymbols,
@ -37,15 +38,18 @@ impl BuiltIn for DataView {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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")
.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")
.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")
.build();

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

@ -19,6 +19,7 @@ use crate::{
builtins::{function::Function, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
error::JsNativeError,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
@ -103,7 +104,7 @@ pub(crate) fn create_throw_type_error(context: &mut Context<'_>) -> JsObject {
let function = JsObject::from_proto_and_data(
context.intrinsics().constructors().function().prototype(),
ObjectData::function(Function::Native {
function: throw_type_error,
function: NativeFunction::from_fn_ptr(throw_type_error),
constructor: None,
}),
);

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

@ -13,7 +13,8 @@ use crate::{
builtins::{BuiltIn, JsArgs},
environments::DeclarativeEnvironment,
error::JsNativeError,
object::FunctionBuilder,
native_function::NativeFunction,
object::FunctionObjectBuilder,
property::Attribute,
Context, JsResult, JsString, JsValue,
};
@ -37,7 +38,7 @@ impl BuiltIn for Eval {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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")
.length(1)
.constructor(false)

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

@ -18,11 +18,9 @@ use crate::{
environments::DeclarativeEnvironmentStack,
error::JsNativeError,
js_string,
object::{
internal_methods::get_prototype_from_constructor, JsObject, NativeObject, Object,
ObjectData,
},
object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut},
native_function::{NativeFunction, NativeFunctionPointer},
object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData},
object::{ConstructorBuilder, FunctionObjectBuilder, JsFunction, PrivateElement},
property::{Attribute, PropertyDescriptor, PropertyKey},
string::utf16,
symbol::WellKnownSymbols,
@ -34,72 +32,20 @@ use boa_ast::{
operations::{bound_names, contains, lexically_declared_names, ContainsSymbol},
StatementList,
};
use boa_gc::{self, custom_trace, Finalize, Gc, GcCell, Trace};
use boa_gc::{self, custom_trace, Finalize, Gc, Trace};
use boa_interner::Sym;
use boa_parser::Parser;
use boa_profiler::Profiler;
use dyn_clone::DynClone;
use std::{
any::Any,
fmt,
ops::{Deref, DerefMut},
};
use tap::{Conv, Pipe};
use std::fmt;
use super::promise::PromiseCapability;
pub(crate) mod arguments;
#[cfg(test)]
mod tests;
/// Type representing a native built-in function a.k.a. function pointer.
///
/// Native functions need to have this signature in order to
/// be callable from Javascript.
///
/// # Arguments
///
/// - The first argument represents the `this` variable of every Javascript function.
///
/// - The second argument represents a list of all arguments passed to the function.
///
/// - The last argument is the [`Context`] of the engine.
pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context<'_>) -> JsResult<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.
///
/// 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.
///
/// `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.
Native {
/// The rust function.
function: NativeFunctionSignature,
function: NativeFunction,
/// The kind of the function constructor if it is a constructor.
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.
Ordinary {
/// The code block containing the compiled function.
@ -330,8 +222,7 @@ pub enum Function {
unsafe impl Trace for Function {
custom_trace! {this, {
match this {
Self::Native { .. } => {}
Self::Closure { captures, .. } => mark(captures),
Self::Native { function, .. } => {mark(function)}
Self::Ordinary { code, environments, home_object, fields, private_methods, .. } => {
mark(code);
mark(environments);
@ -367,9 +258,7 @@ impl Function {
/// Returns true if the function object is a constructor.
pub fn is_constructor(&self) -> bool {
match self {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => {
constructor.is_some()
}
Self::Native { constructor, .. } => constructor.is_some(),
Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false,
Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
}
@ -394,7 +283,7 @@ impl Function {
| Self::Async { home_object, .. }
| Self::Generator { home_object, .. }
| Self::AsyncGenerator { home_object, .. } => home_object.as_ref(),
_ => None,
Self::Native { .. } => None,
}
}
@ -405,7 +294,7 @@ impl Function {
| Self::Async { home_object, .. }
| Self::Generator { home_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.
// TODO: deprecate/remove this.
pub(crate) fn make_builtin_fn<N>(
function: NativeFunctionSignature,
function: NativeFunctionPointer,
name: N,
parent: &JsObject,
length: usize,
@ -505,7 +394,7 @@ pub(crate) fn make_builtin_fn<N>(
.function()
.prototype(),
ObjectData::function(Function::Native {
function,
function: NativeFunction::from_fn_ptr(function),
constructor: None,
}),
);
@ -905,7 +794,7 @@ impl BuiltInFunctionObject {
.unwrap_or_else(|| "anonymous".into());
match function {
Function::Native { .. } | Function::Closure { .. } | Function::Ordinary { .. } => {
Function::Native { .. } | Function::Ordinary { .. } => {
Ok(js_string!(utf16!("[Function: "), &name, utf16!("]")).into())
}
Function::Async { .. } => {
@ -949,7 +838,7 @@ impl BuiltIn for BuiltInFunctionObject {
let _timer = Profiler::global().start_event("function", "init");
let function_prototype = context.intrinsics().constructors().function().prototype();
FunctionBuilder::native(context, Self::prototype)
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::prototype))
.name("")
.length(0)
.constructor(false)
@ -957,7 +846,8 @@ impl BuiltIn for BuiltInFunctionObject {
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]")
.length(1)
.constructor(false)

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

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

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

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

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

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

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

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

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

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

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

@ -16,9 +16,10 @@ use crate::{
builtins::BuiltIn,
context::intrinsics::StandardConstructors,
error::JsNativeError,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionObjectBuilder, JsObject, ObjectData,
},
property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols,
@ -42,18 +43,21 @@ impl BuiltIn for Map {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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]")
.constructor(false)
.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")
.length(0)
.constructor(false)
.build();
let entries_function = FunctionBuilder::native(context, Self::entries)
let entries_function =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::entries))
.name("entries")
.length(0)
.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
pub fn init(context: &mut Context<'_>) {
macro_rules! globals {
($( $builtin:ty ),*) => {
($( $builtin:ty ),*$(,)?) => {
$(init_builtin::<$builtin>(context)
);*
}
@ -195,7 +195,7 @@ pub fn init(context: &mut Context<'_>) {
AsyncGenerator,
AsyncGeneratorFunction,
Uri,
WeakRef
WeakRef,
};
#[cfg(feature = "intl")]

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

@ -17,9 +17,10 @@ use crate::{
builtins::{string::is_trimmable_whitespace, BuiltIn, JsArgs},
context::intrinsics::StandardConstructors,
error::JsNativeError,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionObjectBuilder, JsObject, ObjectData,
},
property::Attribute,
string::utf16,
@ -50,13 +51,15 @@ impl BuiltIn for Number {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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")
.length(2)
.constructor(false)
.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")
.length(1)
.constructor(false)
@ -73,8 +76,16 @@ impl BuiltIn for Number {
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
context.register_global_builtin_function("isFinite", 1, Self::global_is_finite);
context.register_global_builtin_function("isNaN", 1, Self::global_is_nan);
context.register_global_builtin_callable(
"isFinite",
1,
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;

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

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

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

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

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

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

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

@ -18,9 +18,10 @@ use crate::{
context::intrinsics::StandardConstructors,
error::JsNativeError,
js_string,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionObjectBuilder, JsObject, ObjectData,
},
property::{Attribute, PropertyDescriptorBuilder},
string::{utf16, CodePoint},
@ -53,46 +54,56 @@ impl BuiltIn for RegExp {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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]")
.constructor(false)
.build();
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")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.build();

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

@ -16,9 +16,10 @@ use crate::{
builtins::BuiltIn,
context::intrinsics::StandardConstructors,
error::JsNativeError,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionObjectBuilder, JsObject, ObjectData,
},
property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols,
@ -41,12 +42,14 @@ impl BuiltIn for Set {
fn init(context: &mut Context<'_>) -> Option<JsValue> {
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]")
.constructor(false)
.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)
.name("get size")
.build();
@ -55,7 +58,8 @@ impl BuiltIn for Set {
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")
.length(0)
.constructor(false)

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

@ -22,7 +22,8 @@ use super::JsArgs;
use crate::{
builtins::BuiltIn,
error::JsNativeError,
object::{ConstructorBuilder, FunctionBuilder},
native_function::NativeFunction,
object::{ConstructorBuilder, FunctionObjectBuilder},
property::Attribute,
symbol::{JsSymbol, WellKnownSymbols},
value::JsValue,
@ -96,13 +97,15 @@ impl BuiltIn for Symbol {
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]")
.length(1)
.constructor(false)
.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")
.constructor(false)
.build();

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

@ -22,9 +22,10 @@ use crate::{
context::intrinsics::{StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, ObjectData,
internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionObjectBuilder, JsObject, ObjectData,
},
property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols,
@ -66,7 +67,10 @@ macro_rules! typed_array {
.typed_array()
.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]")
.constructor(false)
.build();
@ -250,37 +254,44 @@ pub(crate) struct TypedArray;
impl BuiltIn for TypedArray {
const NAME: &'static str = "TypedArray";
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]")
.constructor(false)
.build();
let get_buffer = FunctionBuilder::native(context, Self::buffer)
let get_buffer =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::buffer))
.name("get buffer")
.constructor(false)
.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")
.constructor(false)
.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")
.constructor(false)
.build();
let get_length = FunctionBuilder::native(context, Self::length)
let get_length =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::length))
.name("get length")
.constructor(false)
.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]")
.constructor(false)
.build();
let values_function = FunctionBuilder::native(context, Self::values)
let values_function =
FunctionObjectBuilder::new(context, NativeFunction::from_fn_ptr(Self::values))
.name("values")
.length(0)
.constructor(false)

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

@ -19,8 +19,8 @@ use self::consts::{
use super::BuiltIn;
use crate::{
builtins::JsArgs, js_string, object::FunctionBuilder, property::Attribute, string::CodePoint,
Context, JsNativeError, JsResult, JsString, JsValue,
builtins::JsArgs, js_string, native_function::NativeFunction, object::FunctionObjectBuilder,
property::Attribute, string::CodePoint, Context, JsNativeError, JsResult, JsString, JsValue,
};
/// URI Handling Functions
@ -31,7 +31,8 @@ impl BuiltIn for Uri {
const NAME: &'static str = "Uri";
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")
.length(1)
.constructor(false)
@ -43,7 +44,10 @@ impl BuiltIn for Uri {
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")
.length(1)
.constructor(false)
@ -55,7 +59,8 @@ impl BuiltIn for Uri {
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")
.length(1)
.constructor(false)
@ -67,7 +72,10 @@ impl BuiltIn for Uri {
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")
.length(1)
.constructor(false)

6
boa_engine/src/class.rs

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

85
boa_engine/src/context/mod.rs

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

1
boa_engine/src/lib.rs

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

93
boa_engine/src/object/mod.rs

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

45
boa_engine/src/vm/code_block.rs

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

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

@ -1,6 +1,9 @@
use boa_gc::{Gc, GcCell};
use crate::{
builtins::{JsArgs, Promise},
object::FunctionBuilder,
native_function::NativeFunction,
object::FunctionObjectBuilder,
vm::{call_frame::GeneratorResumeKind, opcode::Operation, ShouldExit},
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:
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures(
let on_fulfilled = FunctionObjectBuilder::new(
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.
// b. Suspend prevContext.
// 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())
},
(
Gc::new(GcCell::new((
context.realm.environments.clone(),
context.vm.stack.clone(),
context.vm.frame().clone(),
))),
),
)
.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:
// 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionBuilder::closure_with_captures(
let on_rejected = FunctionObjectBuilder::new(
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.
// b. Suspend prevContext.
// 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())
},
(
Gc::new(GcCell::new((
context.realm.environments.clone(),
context.vm.stack.clone(),
context.vm.frame().clone(),
))),
),
)
.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
// inside Rust and call them from Javascript.
// This example goes into the details on how to pass closures as functions inside Rust and call them
// from Javascript.
use std::cell::{Cell, RefCell};
use boa_engine::{
js_string,
object::{FunctionBuilder, JsObject},
native_function::NativeFunction,
object::{builtins::JsArray, FunctionObjectBuilder, JsObject},
property::{Attribute, PropertyDescriptor},
string::utf16,
Context, JsError, JsString, JsValue,
Context, JsError, JsNativeError, JsString, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_gc::{Finalize, GcCell, Trace};
fn main() -> Result<(), JsError> {
// We create a new `Context` to create a new Javascript executor.
let mut context = Context::default();
// We make some operations in Rust that return a `Copy` value that we want
// to pass to a Javascript function.
// We make some operations in Rust that return a `Copy` value that we want to pass to a Javascript
// function.
let variable = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1;
// We register a global closure function that has the name 'closure' with length 0.
context.register_global_closure("closure", 0, move |_, _, _| {
context.register_global_callable(
"closure",
0,
NativeFunction::from_copy_closure(move |_, _, _| {
println!("Called `closure`");
// `variable` is captured from the main function.
println!("variable = {variable}");
@ -27,7 +33,8 @@ fn main() -> Result<(), JsError> {
// We return the moved variable as a `JsValue`.
Ok(JsValue::new(variable))
})?;
}),
);
assert_eq!(context.eval("closure()")?, 255.into());
@ -59,25 +66,28 @@ fn main() -> Result<(), JsError> {
object,
};
// We can use `FunctionBuilder` to define a closure with additional
// captures.
let js_function = FunctionBuilder::closure_with_captures(
// We can use `FunctionBuilder` to define a closure with additional captures and custom property
// attributes.
let js_function = FunctionObjectBuilder::new(
&mut context,
NativeFunction::from_copy_closure_with_captures(
|_, _, captures, context| {
let mut captures = captures.borrow_mut();
let BigStruct { greeting, object } = &mut *captures;
println!("Called `createMessage`");
// We obtain the `name` property of `captures.object`
let name = captures.object.get("name", context)?;
let name = object.get("name", context)?;
// We create a new message from our captured variable.
let message = js_string!(
utf16!("message from `"),
&name.to_string(context)?,
utf16!("`: "),
&captures.greeting
greeting
);
// 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!();
@ -86,7 +96,8 @@ fn main() -> Result<(), JsError> {
Ok(message.into())
},
// 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.
.name("createMessage")
@ -102,7 +113,7 @@ fn main() -> Result<(), JsError> {
// We pass `js_function` as a property value.
js_function,
// We assign to the "createMessage" property the desired attributes.
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
assert_eq!(
@ -119,5 +130,65 @@ fn main() -> Result<(), JsError> {
// We have moved `Clone` variables into a closure and executed that closure
// inside Javascript!
// ADVANCED
// If we can ensure the captured variables are not traceable by the garbage collector,
// we can pass any static closure easily.
let index = Cell::new(0i32);
let numbers = RefCell::new(Vec::new());
// We register a global closure that is not `Copy`.
context.register_global_callable(
"enumerate",
0,
// Note that it is required to use `unsafe` code, since the compiler cannot verify that the
// types captured by the closure are not traceable.
unsafe {
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(())
}

24
boa_examples/src/bin/jsarray.rs

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

7
boa_examples/src/bin/modulehandler.rs

@ -1,7 +1,10 @@
// This example implements a custom module handler which mimics
// the require/module.exports pattern
use boa_engine::{prelude::JsObject, property::Attribute, Context, JsResult, JsValue};
use boa_engine::{
native_function::NativeFunction, prelude::JsObject, property::Attribute, Context, JsResult,
JsValue,
};
use std::fs::read_to_string;
fn main() {
@ -17,7 +20,7 @@ fn main() {
let mut ctx = Context::default();
// Adding custom implementation that mimics 'require'
ctx.register_global_function("require", 0, require);
ctx.register_global_callable("require", 0, NativeFunction::from_fn_ptr(require));
// Adding custom object that mimics 'module.exports'
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.
#[derive(Debug)]
pub(crate) struct GcBox<T: Trace + ?Sized + 'static> {
pub struct GcBox<T: Trace + ?Sized + 'static> {
pub(crate) header: GcBoxHeader,
pub(crate) value: T,
}

3
boa_gc/src/internals/mod.rs

@ -1,4 +1,5 @@
mod ephemeron_box;
mod gc_box;
pub(crate) use self::{ephemeron_box::EphemeronBox, gc_box::GcBox};
pub(crate) use self::ephemeron_box::EphemeronBox;
pub use self::gc_box::GcBox;

2
boa_gc/src/lib.rs

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

36
boa_gc/src/pointers/gc.rs

@ -47,6 +47,16 @@ impl<T: Trace> Gc<T> {
gc.set_root();
gc
}
/// Consumes the `Gc`, returning a wrapped raw pointer.
///
/// To avoid a memory leak, the pointer must be converted back to a `Gc` using [`Gc::from_raw`].
#[allow(clippy::use_self)]
pub fn into_raw(this: Gc<T>) -> NonNull<GcBox<T>> {
let ptr = this.inner_ptr.get();
std::mem::forget(this);
ptr
}
}
impl<T: Trace + ?Sized> Gc<T> {
@ -55,9 +65,26 @@ impl<T: Trace + ?Sized> Gc<T> {
GcBox::ptr_eq(this.inner(), other.inner())
}
/// Will return a new rooted `Gc` from a `GcBox` pointer
pub(crate) fn from_ptr(ptr: NonNull<GcBox<T>>) -> Self {
// SAFETY: the value provided as a pointer MUST be a valid GcBox.
/// Constructs a `Gc<T>` from a raw pointer.
///
/// 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 {
ptr.as_ref().root_inner();
let gc = Self {
@ -159,7 +186,8 @@ unsafe impl<T: Trace + ?Sized> Trace for Gc<T> {
impl<T: Trace + ?Sized> Clone for Gc<T> {
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::{
borrow::{Cow, ToOwned},
cell::Cell,
collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque},
hash::{BuildHasher, Hash},
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 boa_engine::{
builtins::JsArgs, object::FunctionBuilder, property::Attribute, Context, JsNativeErrorKind,
JsResult, JsValue,
builtins::JsArgs, native_function::NativeFunction, object::FunctionObjectBuilder,
property::Attribute, Context, JsNativeErrorKind, JsValue,
};
use boa_gc::{Finalize, Gc, GcCell, Trace};
use boa_parser::Parser;
use colored::Colorize;
use rayon::prelude::*;
use std::borrow::Cow;
use std::{borrow::Cow, cell::RefCell, rc::Rc};
impl TestSuite {
/// Runs the test suite.
@ -360,8 +359,22 @@ impl Test {
/// Registers the print function in the context.
fn register_print_fn(context: &mut Context<'_>, async_result: AsyncResult) {
// We use `FunctionBuilder` to define a closure with additional captures.
let js_function =
FunctionBuilder::closure_with_captures(context, test262_print, async_result)
let js_function = FunctionObjectBuilder::new(
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")
.length(1)
.build();
@ -375,33 +388,15 @@ impl Test {
}
/// Object which includes the result of the async operation.
#[derive(Debug, Clone, Trace, Finalize)]
#[derive(Debug, Clone)]
struct AsyncResult {
inner: Gc<GcCell<Result<(), String>>>,
inner: Rc<RefCell<Result<(), String>>>,
}
impl Default for AsyncResult {
fn default() -> Self {
Self {
inner: Gc::new(GcCell::new(Ok(()))),
inner: Rc::new(RefCell::new(Ok(()))),
}
}
}
/// `print()` function required by the test262 suite.
#[allow(clippy::unnecessary_wraps)]
fn test262_print(
_this: &JsValue,
args: &[JsValue],
async_result: &mut AsyncResult,
context: &mut Context<'_>,
) -> JsResult<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