1use 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
33pub 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 #[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)]
73pub struct NativeFunctionObject {
75 pub(crate) f: NativeFunction,
77
78 pub(crate) name: JsString,
80
81 pub(crate) constructor: Option<ConstructorKind>,
83
84 pub(crate) realm: Option<Realm>,
86}
87
88unsafe 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#[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
141unsafe 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 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 #[inline]
165 pub fn from_fn_ptr(function: NativeFunctionPointer) -> Self {
166 Self {
167 inner: Inner::PointerFn(function),
168 }
169 }
170
171 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 pub fn from_copy_closure<F>(closure: F) -> Self
234 where
235 F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + Copy + 'static,
236 {
237 unsafe { Self::from_closure(closure) }
239 }
240
241 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 unsafe { Self::from_closure_with_captures(closure, captures) }
249 }
250
251 pub unsafe fn from_closure<F>(closure: F) -> Self
260 where
261 F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + 'static,
262 {
263 unsafe {
265 Self::from_closure_with_captures(
266 move |this, args, (), context| closure(this, args, context),
267 (),
268 )
269 }
270 }
271
272 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 let ptr = Gc::into_raw(Gc::new(Closure {
288 f: closure,
289 captures,
290 }));
291 unsafe {
294 Self {
295 inner: Inner::Closure(Gc::from_raw(ptr)),
296 }
297 }
298 }
299
300 #[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 #[must_use]
318 pub fn to_js_function(self, realm: &Realm) -> JsFunction {
319 FunctionObjectBuilder::new(realm, self).build()
320 }
321}
322
323pub(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 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 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
412fn native_function_construct(
419 obj: &JsObject,
420 argument_count: usize,
421 context: &mut InternalMethodCallContext<'_>,
422) -> JsResult<CallValue> {
423 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}