Skip to main content

boa_engine/native_function/
mod.rs

1//! Boa's wrappers for native Rust functions to be compatible with ECMAScript calls.
2//!
3//! [`NativeFunction`] is the main type of this module, providing APIs to create native callables
4//! from native Rust functions and closures.
5
6use std::cell::RefCell;
7
8use boa_gc::{Finalize, Gc, Trace, custom_trace};
9use boa_string::JsString;
10
11use crate::job::NativeAsyncJob;
12use crate::object::internal_methods::InternalMethodCallContext;
13use crate::value::JsVariant;
14use crate::{
15    Context, JsNativeError, JsObject, JsResult, JsValue,
16    builtins::{OrdinaryObject, function::ConstructorKind, math::Math},
17    context::intrinsics::StandardConstructors,
18    object::{
19        FunctionObjectBuilder, JsData, JsFunction, JsPromise,
20        internal_methods::{
21            CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
22            get_prototype_from_constructor,
23        },
24    },
25    realm::Realm,
26};
27
28mod arguments;
29mod continuation;
30
31use arguments::NativeCall;
32
33pub(crate) use continuation::{CoroutineBranch, CoroutineState, NativeCoroutine};
34
35/// The required signature for all native built-in function pointers.
36///
37/// # Arguments
38///
39/// - The first argument represents the `this` variable of every ECMAScript function.
40///
41/// - The second argument represents the list of all arguments passed to the function.
42///
43/// - The last argument is the engine [`Context`].
44pub type NativeFunctionPointer = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
45
46trait TraceableClosure: Trace {
47    fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue>;
48}
49
50#[derive(Trace, Finalize)]
51struct Closure<F, T>
52where
53    F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue>,
54    T: Trace,
55{
56    // SAFETY: `NativeFunction`'s safe API ensures only `Copy` closures are stored; its unsafe API,
57    // on the other hand, explains the invariants to hold in order for this to be safe, shifting
58    // the responsibility to the caller.
59    #[unsafe_ignore_trace]
60    f: F,
61    captures: T,
62}
63
64impl<F, T> TraceableClosure for Closure<F, T>
65where
66    F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue>,
67    T: Trace,
68{
69    fn call(&self, this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
70        (self.f)(this, args, &self.captures, context)
71    }
72}
73
74#[derive(Clone, Debug, Finalize)]
75/// The data of an object containing a `NativeFunction`.
76pub struct NativeFunctionObject {
77    /// The rust function.
78    pub(crate) f: NativeFunction,
79
80    /// JavaScript name of the function.
81    pub(crate) name: JsString,
82
83    /// The kind of the function constructor if it is a constructor.
84    pub(crate) constructor: Option<ConstructorKind>,
85
86    /// The [`Realm`] in which the function is defined, or `None` if the realm is uninitialized.
87    pub(crate) realm: Option<Realm>,
88}
89
90// SAFETY: this traces all fields that need to be traced by the GC.
91unsafe impl Trace for NativeFunctionObject {
92    custom_trace!(this, mark, {
93        mark(&this.f);
94        mark(&this.realm);
95    });
96}
97
98impl JsData for NativeFunctionObject {
99    fn internal_methods(&self) -> &'static InternalObjectMethods {
100        static FUNCTION: InternalObjectMethods = InternalObjectMethods {
101            __call__: native_function_call,
102            ..ORDINARY_INTERNAL_METHODS
103        };
104
105        static CONSTRUCTOR: InternalObjectMethods = InternalObjectMethods {
106            __call__: native_function_call,
107            __construct__: native_function_construct,
108            ..ORDINARY_INTERNAL_METHODS
109        };
110
111        if self.constructor.is_some() {
112            &CONSTRUCTOR
113        } else {
114            &FUNCTION
115        }
116    }
117}
118
119/// A callable Rust function that can be invoked by the engine.
120///
121/// `NativeFunction` functions are divided in two:
122/// - Function pointers a.k.a common functions (see [`NativeFunctionPointer`]).
123/// - Closure functions that can capture the current environment.
124///
125/// # Caveats
126///
127/// By limitations of the Rust language, the garbage collector currently cannot inspect closures
128/// in order to trace their captured variables. This means that only [`Copy`] closures are 100% safe
129/// to use. All other closures can also be stored in a `NativeFunction`, albeit by using an `unsafe`
130/// API, but note that passing closures implicitly capturing traceable types could cause
131/// **Undefined Behaviour**.
132#[derive(Clone, Finalize)]
133pub struct NativeFunction {
134    inner: Inner,
135}
136
137#[derive(Clone)]
138enum Inner {
139    PointerFn(NativeFunctionPointer),
140    Closure(Gc<dyn TraceableClosure>),
141}
142
143// Manual implementation because deriving `Trace` triggers the `single_use_lifetimes` lint.
144// SAFETY: Only closures can contain `Trace` captures, so this implementation is safe.
145unsafe impl Trace for NativeFunction {
146    custom_trace!(this, mark, {
147        if let Inner::Closure(c) = &this.inner {
148            mark(c);
149        }
150    });
151}
152
153impl std::fmt::Debug for NativeFunction {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        f.debug_struct("NativeFunction").finish_non_exhaustive()
156    }
157}
158
159impl NativeFunction {
160    /// Returns `true` if this is the function pointer `function`.
161    pub(crate) fn is_fn_ptr(&self, function: NativeFunctionPointer) -> bool {
162        matches!(self.inner, Inner::PointerFn(f) if std::ptr::fn_addr_eq(f, function))
163    }
164
165    /// Creates a `NativeFunction` from a function pointer.
166    #[inline]
167    pub fn from_fn_ptr(function: NativeFunctionPointer) -> Self {
168        Self {
169            inner: Inner::PointerFn(function),
170        }
171    }
172
173    /// Creates a `NativeFunction` from a function returning a [`Future`]-like.
174    ///
175    /// The returned `NativeFunction` will return an ECMAScript `Promise` that will be fulfilled
176    /// or rejected when the returned `Future` completes.
177    ///
178    /// If you only need to convert a `Future`-like into a [`JsPromise`], see
179    /// [`JsPromise::from_async_fn`].
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// # use std::cell::RefCell;
185    /// # use boa_engine::{
186    /// #   JsValue,
187    /// #   Context,
188    /// #   JsResult,
189    /// #   NativeFunction,
190    /// #   JsArgs,
191    /// # };
192    /// async fn test(
193    ///     _this: &JsValue,
194    ///     args: &[JsValue],
195    ///     context: &RefCell<&mut Context>,
196    /// ) -> JsResult<JsValue> {
197    ///     let arg = args.get_or_undefined(0).clone();
198    ///     std::future::ready(()).await;
199    ///     let value = arg.to_u32(&mut context.borrow_mut())?;
200    ///     Ok(JsValue::from(value * 2))
201    /// }
202    /// NativeFunction::from_async_fn(test);
203    /// ```
204    pub fn from_async_fn<F>(f: F) -> Self
205    where
206        F: AsyncFn(&JsValue, &[JsValue], &RefCell<&mut Context>) -> JsResult<JsValue> + 'static,
207        F: Copy,
208    {
209        Self::from_copy_closure(move |this, args, context| {
210            let (promise, resolvers) = JsPromise::new_pending(context);
211            let this = this.clone();
212            let args = args.to_vec();
213
214            context.enqueue_job(
215                NativeAsyncJob::new(async move |context| {
216                    let result = f(&this, &args, context).await;
217
218                    let context = &mut context.borrow_mut();
219                    match result {
220                        Ok(v) => resolvers.resolve.call(&JsValue::undefined(), &[v], context),
221                        Err(e) => {
222                            let e = e.into_opaque(context)?;
223                            resolvers.reject.call(&JsValue::undefined(), &[e], context)
224                        }
225                    }
226                })
227                .into(),
228            );
229
230            Ok(promise.into())
231        })
232    }
233
234    /// Creates a `NativeFunction` from a `Copy` closure.
235    pub fn from_copy_closure<F>(closure: F) -> Self
236    where
237        F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + Copy + 'static,
238    {
239        // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure.
240        unsafe { Self::from_closure(closure) }
241    }
242
243    /// Creates a `NativeFunction` from a `Copy` closure and a list of traceable captures.
244    pub fn from_copy_closure_with_captures<F, T>(closure: F, captures: T) -> Self
245    where
246        F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue> + Copy + 'static,
247        T: Trace + 'static,
248    {
249        // SAFETY: The `Copy` bound ensures there are no traceable types inside the closure.
250        unsafe { Self::from_closure_with_captures(closure, captures) }
251    }
252
253    /// Creates a new `NativeFunction` from a closure.
254    ///
255    /// # Safety
256    ///
257    /// Passing a closure that contains a captured variable that needs to be traced by the garbage
258    /// collector could cause an use after free, memory corruption or other kinds of **Undefined
259    /// Behaviour**. See <https://github.com/Manishearth/rust-gc/issues/50> for a technical explanation
260    /// on why that is the case.
261    pub unsafe fn from_closure<F>(closure: F) -> Self
262    where
263        F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + 'static,
264    {
265        // SAFETY: The caller must ensure the invariants of the closure hold.
266        unsafe {
267            Self::from_closure_with_captures(
268                move |this, args, (), context| closure(this, args, context),
269                (),
270            )
271        }
272    }
273
274    /// Create a new `NativeFunction` from a closure and a list of traceable captures.
275    ///
276    /// # Safety
277    ///
278    /// Passing a closure that contains a captured variable that needs to be traced by the garbage
279    /// collector could cause an use after free, memory corruption or other kinds of **Undefined
280    /// Behaviour**. See <https://github.com/Manishearth/rust-gc/issues/50> for a technical explanation
281    /// on why that is the case.
282    pub unsafe fn from_closure_with_captures<F, T>(closure: F, captures: T) -> Self
283    where
284        F: Fn(&JsValue, &[JsValue], &T, &mut Context) -> JsResult<JsValue> + 'static,
285        T: Trace + 'static,
286    {
287        // Hopefully, this unsafe operation will be replaced by the `CoerceUnsized` API in the
288        // future: https://github.com/rust-lang/rust/issues/18598
289        let ptr = Gc::into_raw(Gc::new(Closure {
290            f: closure,
291            captures,
292        }));
293        // SAFETY: The pointer returned by `into_raw` is only used to coerce to a trait object,
294        // meaning this is safe.
295        unsafe {
296            Self {
297                inner: Inner::Closure(Gc::from_raw(ptr)),
298            }
299        }
300    }
301
302    /// Calls this `NativeFunction`, forwarding the arguments to the corresponding function.
303    #[inline]
304    pub fn call(
305        &self,
306        this: &JsValue,
307        args: &[JsValue],
308        context: &mut Context,
309    ) -> JsResult<JsValue> {
310        match self.inner {
311            Inner::PointerFn(f) => f(this, args, context),
312            Inner::Closure(ref c) => c.call(this, args, context),
313        }
314    }
315
316    /// Converts this `NativeFunction` into a `JsFunction` without setting its name or length.
317    ///
318    /// Useful to create functions that will only be used once, such as callbacks.
319    #[must_use]
320    pub fn to_js_function(self, realm: &Realm) -> JsFunction {
321        FunctionObjectBuilder::new(realm, self).build()
322    }
323}
324
325/// Call this object.
326///
327/// # Panics
328///
329/// Panics if the object is currently mutably borrowed.
330// <https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist>
331pub(crate) fn native_function_call(
332    obj: &JsObject,
333    argument_count: usize,
334    context: &mut InternalMethodCallContext<'_>,
335) -> JsResult<CallValue> {
336    let call = NativeCall::new(&mut context.vm.stack, argument_count);
337    let result = native_function_call_with_args(obj, call.this(), call.arguments(), context)?;
338    context.vm.stack.push(result);
339    Ok(CallValue::Complete)
340}
341
342/// Calls a native function using arguments already owned by the caller.
343///
344/// This shares the native calling semantics with the VM entry point without
345/// copying the calling convention through the VM stack.
346pub(crate) fn native_function_call_with_args(
347    obj: &JsObject,
348    this: &JsValue,
349    args: &[JsValue],
350    context: &mut InternalMethodCallContext<'_>,
351) -> JsResult<JsValue> {
352    // An intrinsic Math function on numbers runs no user code and cannot throw, so it needs no
353    // backtrace entry, realm switch or active function.
354    if args.iter().all(JsValue::is_number)
355        && let Some(math) = obj
356            .downcast_ref::<NativeFunctionObject>()
357            .filter(|native| Math::is_pure_on_numbers(&native.f))
358    {
359        return math.f.call(this, args, context);
360    }
361
362    // We technically don't need this since native functions don't push any new frames to the
363    // vm, but we'll eventually have to combine the native stack with the vm stack.
364    context.check_runtime_limits()?;
365    let this_function_object = obj.clone();
366
367    let NativeFunctionObject {
368        f: function,
369        name,
370        constructor,
371        realm,
372    } = obj
373        .downcast_ref::<NativeFunctionObject>()
374        .expect("the object should be a native function object")
375        .clone();
376
377    let pc = context.vm.frame().pc;
378    let native_source_info = context.native_source_info();
379    context
380        .vm
381        .shadow_stack
382        .push_native(pc, name, native_source_info);
383
384    let mut realm = realm.unwrap_or_else(|| context.realm().clone());
385
386    context.swap_realm(&mut realm);
387    context.vm.native_active_function = Some(this_function_object);
388
389    let result = if constructor.is_some() {
390        let outer = context.vm.native_constructor_receiver.replace(this.clone());
391        let result = function.call(&JsValue::undefined(), args, context);
392        context.vm.native_constructor_receiver = outer;
393        result
394    } else {
395        function.call(this, args, context)
396    }
397    .map_err(|err| err.inject_realm(context.realm().clone()));
398
399    context.vm.native_active_function = None;
400    context.swap_realm(&mut realm);
401
402    context.vm.shadow_stack.pop();
403
404    result
405}
406
407/// Construct an instance of this object with the specified arguments.
408///
409/// # Panics
410///
411/// Panics if the object is currently mutably borrowed.
412// <https://tc39.es/ecma262/#sec-built-in-function-objects-construct-argumentslist-newtarget>
413fn native_function_construct(
414    obj: &JsObject,
415    argument_count: usize,
416    context: &mut InternalMethodCallContext<'_>,
417) -> JsResult<CallValue> {
418    // We technically don't need this since native functions don't push any new frames to the
419    // vm, but we'll eventually have to combine the native stack with the vm stack.
420    context.check_runtime_limits()?;
421    let this_function_object = obj.clone();
422
423    let NativeFunctionObject {
424        f: function,
425        name,
426        constructor,
427        realm,
428    } = obj
429        .downcast_ref::<NativeFunctionObject>()
430        .expect("the object should be a native function object")
431        .clone();
432
433    let pc = context.vm.frame().pc;
434    let native_source_info = context.native_source_info();
435    context
436        .vm
437        .shadow_stack
438        .push_native(pc, name, native_source_info);
439
440    let mut realm = realm.unwrap_or_else(|| context.realm().clone());
441
442    context.swap_realm(&mut realm);
443    context.vm.native_active_function = Some(this_function_object);
444
445    let new_target = context.vm.stack.pop();
446    let call = NativeCall::new(&mut context.vm.stack, argument_count);
447
448    let result = function
449        .call(&new_target, call.arguments(), context)
450        .map_err(|err| err.inject_realm(context.realm().clone()))
451        .and_then(|v| match v.variant() {
452            JsVariant::Object(o) => Ok(o.clone()),
453            val => {
454                if constructor.expect("must be a constructor").is_base() || val.is_undefined() {
455                    let prototype = get_prototype_from_constructor(
456                        &new_target,
457                        StandardConstructors::object,
458                        context,
459                    )?;
460                    Ok(JsObject::from_proto_and_data_with_shared_shape(
461                        context.root_shape(),
462                        prototype,
463                        OrdinaryObject,
464                    )
465                    .upcast())
466                } else {
467                    Err(JsNativeError::typ()
468                        .with_message("derived constructor can only return an Object or undefined")
469                        .into())
470                }
471            }
472        });
473
474    context.vm.native_active_function = None;
475    context.swap_realm(&mut realm);
476
477    context.vm.shadow_stack.pop();
478
479    context.vm.stack.push(result?);
480
481    Ok(CallValue::Complete)
482}