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