Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

449 lines
15 KiB

//! 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 boa_gc::{custom_trace, Finalize, Gc, Trace};
use crate::{
builtins::{function::ConstructorKind, OrdinaryObject},
context::intrinsics::StandardConstructors,
object::{
internal_methods::{
get_prototype_from_constructor, CallValue, InternalObjectMethods,
ORDINARY_INTERNAL_METHODS,
},
FunctionObjectBuilder, JsData, JsFunction, JsPromise,
},
realm::Realm,
Context, JsNativeError, JsObject, 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)
}
}
#[derive(Clone, Debug, Finalize)]
/// The data of an object containing a `NativeFunction`.
pub struct NativeFunctionObject {
/// The rust function.
pub(crate) f: NativeFunction,
/// The kind of the function constructor if it is a constructor.
pub(crate) constructor: Option<ConstructorKind>,
/// The [`Realm`] in which the function is defined, or `None` if the realm is uninitialized.
pub(crate) realm: Option<Realm>,
}
// SAFETY: this traces all fields that need to be traced by the GC.
unsafe impl Trace for NativeFunctionObject {
custom_trace!(this, mark, {
mark(&this.f);
mark(&this.realm);
});
}
impl JsData for NativeFunctionObject {
fn internal_methods(&self) -> &'static InternalObjectMethods {
static FUNCTION: InternalObjectMethods = InternalObjectMethods {
__call__: native_function_call,
..ORDINARY_INTERNAL_METHODS
};
static CONSTRUCTOR: InternalObjectMethods = InternalObjectMethods {
__call__: native_function_call,
__construct__: native_function_construct,
..ORDINARY_INTERNAL_METHODS
};
if self.constructor.is_some() {
&CONSTRUCTOR
} else {
&FUNCTION
}
}
}
/// 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, Finalize)]
pub struct NativeFunction {
inner: Inner,
}
#[derive(Clone)]
enum Inner {
PointerFn(NativeFunctionPointer),
Closure(Gc<dyn TraceableClosure>),
}
// 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, mark, {
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 function returning a [`Future`]-like.
///
/// The returned `NativeFunction` will return an ECMAScript `Promise` that will be fulfilled
/// or rejected when the returned [`Future`] completes.
///
/// If you only need to convert a [`Future`]-like into a [`JsPromise`], see
/// [`JsPromise::from_future`].
///
/// # Caveats
///
/// Certain async functions need to be desugared for them to be `'static'`. For example, the
/// following won't compile:
///
/// ```compile_fail
/// # use boa_engine::{
/// # JsValue,
/// # Context,
/// # JsResult,
/// # NativeFunction
/// # };
/// async fn test(
/// _this: &JsValue,
/// args: &[JsValue],
/// _context: &mut Context,
/// ) -> JsResult<JsValue> {
/// let arg = args.get(0).cloned();
/// std::future::ready(()).await;
/// drop(arg);
/// Ok(JsValue::null())
/// }
/// NativeFunction::from_async_fn(test);
/// ```
///
/// Even though `args` is only used before the first await point, Rust's async functions are
/// fully lazy, which makes `test` equivalent to something like:
///
/// ```
/// # use std::future::Future;
/// # use boa_engine::{JsValue, Context, JsResult};
/// fn test<'a>(
/// _this: &JsValue,
/// args: &'a [JsValue],
/// _context: &mut Context,
/// ) -> impl Future<Output = JsResult<JsValue>> + 'a {
/// async move {
/// let arg = args.get(0).cloned();
/// std::future::ready(()).await;
/// drop(arg);
/// Ok(JsValue::null())
/// }
/// }
/// ```
///
/// Note that `args` is used inside the `async move`, making the whole future not `'static`.
///
/// In those cases, you can manually restrict the lifetime of the arguments:
///
/// ```
/// # use std::future::Future;
/// # use boa_engine::{
/// # JsValue,
/// # Context,
/// # JsResult,
/// # NativeFunction
/// # };
/// fn test(
/// _this: &JsValue,
/// args: &[JsValue],
/// _context: &mut Context,
/// ) -> impl Future<Output = JsResult<JsValue>> {
/// let arg = args.get(0).cloned();
/// async move {
/// std::future::ready(()).await;
/// drop(arg);
/// Ok(JsValue::null())
/// }
/// }
/// NativeFunction::from_async_fn(test);
/// ```
///
/// And this should always return a `'static` future.
///
/// [`Future`]: std::future::Future
pub fn from_async_fn<Fut>(f: fn(&JsValue, &[JsValue], &mut Context) -> Fut) -> Self
where
Fut: std::future::IntoFuture<Output = JsResult<JsValue>> + 'static,
{
Self::from_copy_closure(move |this, args, context| {
let future = f(this, args, context);
Ok(JsPromise::from_future(future, context).into())
})
}
/// 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),
}
}
/// Converts this `NativeFunction` into a `JsFunction` without setting its name or length.
///
/// Useful to create functions that will only be used once, such as callbacks.
#[must_use]
pub fn to_js_function(self, realm: &Realm) -> JsFunction {
FunctionObjectBuilder::new(realm, self).build()
}
}
/// Call this object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist>
pub(crate) fn native_function_call(
obj: &JsObject,
argument_count: usize,
context: &mut Context,
) -> JsResult<CallValue> {
let args = context.vm.pop_n_values(argument_count);
let _func = context.vm.pop();
let this = context.vm.pop();
// We technically don't need this since native functions don't push any new frames to the
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
let this_function_object = obj.clone();
let NativeFunctionObject {
f: function,
constructor,
realm,
} = obj
.downcast_ref::<NativeFunctionObject>()
.expect("the object should be a native function object")
.clone();
let mut realm = realm.unwrap_or_else(|| context.realm().clone());
context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object);
let result = if constructor.is_some() {
function.call(&JsValue::undefined(), &args, context)
} else {
function.call(&this, &args, context)
}
.map_err(|err| err.inject_realm(context.realm().clone()));
context.vm.native_active_function = None;
context.swap_realm(&mut realm);
context.vm.push(result?);
Ok(CallValue::Complete)
}
/// Construct an instance of this object with the specified arguments.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-built-in-function-objects-construct-argumentslist-newtarget>
fn native_function_construct(
obj: &JsObject,
argument_count: usize,
context: &mut Context,
) -> JsResult<CallValue> {
// We technically don't need this since native functions don't push any new frames to the
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
let this_function_object = obj.clone();
let NativeFunctionObject {
f: function,
constructor,
realm,
} = obj
.downcast_ref::<NativeFunctionObject>()
.expect("the object should be a native function object")
.clone();
let mut realm = realm.unwrap_or_else(|| context.realm().clone());
context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object);
let new_target = context.vm.pop();
let args = context.vm.pop_n_values(argument_count);
let _func = context.vm.pop();
let result = function
.call(&new_target, &args, context)
.map_err(|err| err.inject_realm(context.realm().clone()))
.and_then(|v| match v {
JsValue::Object(ref o) => Ok(o.clone()),
val => {
if constructor.expect("must be a constructor").is_base() || val.is_undefined() {
let prototype = get_prototype_from_constructor(
&new_target,
StandardConstructors::object,
context,
)?;
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
OrdinaryObject,
))
} else {
Err(JsNativeError::typ()
.with_message("derived constructor can only return an Object or undefined")
.into())
}
}
});
context.vm.native_active_function = None;
context.swap_realm(&mut realm);
context.vm.push(result?);
Ok(CallValue::Complete)
}