1use 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
35pub 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 #[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)]
75pub struct NativeFunctionObject {
77 pub(crate) f: NativeFunction,
79
80 pub(crate) name: JsString,
82
83 pub(crate) constructor: Option<ConstructorKind>,
85
86 pub(crate) realm: Option<Realm>,
88}
89
90unsafe 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#[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
143unsafe 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 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 #[inline]
167 pub fn from_fn_ptr(function: NativeFunctionPointer) -> Self {
168 Self {
169 inner: Inner::PointerFn(function),
170 }
171 }
172
173 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 pub fn from_copy_closure<F>(closure: F) -> Self
236 where
237 F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + Copy + 'static,
238 {
239 unsafe { Self::from_closure(closure) }
241 }
242
243 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 unsafe { Self::from_closure_with_captures(closure, captures) }
251 }
252
253 pub unsafe fn from_closure<F>(closure: F) -> Self
262 where
263 F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + 'static,
264 {
265 unsafe {
267 Self::from_closure_with_captures(
268 move |this, args, (), context| closure(this, args, context),
269 (),
270 )
271 }
272 }
273
274 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 let ptr = Gc::into_raw(Gc::new(Closure {
290 f: closure,
291 captures,
292 }));
293 unsafe {
296 Self {
297 inner: Inner::Closure(Gc::from_raw(ptr)),
298 }
299 }
300 }
301
302 #[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 #[must_use]
320 pub fn to_js_function(self, realm: &Realm) -> JsFunction {
321 FunctionObjectBuilder::new(realm, self).build()
322 }
323}
324
325pub(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
342pub(crate) fn native_function_call_with_args(
347 obj: &JsObject,
348 this: &JsValue,
349 args: &[JsValue],
350 context: &mut InternalMethodCallContext<'_>,
351) -> JsResult<JsValue> {
352 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 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
407fn native_function_construct(
414 obj: &JsObject,
415 argument_count: usize,
416 context: &mut InternalMethodCallContext<'_>,
417) -> JsResult<CallValue> {
418 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}