Skip to main content

pipa/builtins/
function.rs

1use crate::host::HostFunction;
2use crate::object::function::JSFunction;
3use crate::object::object::JSObject;
4use crate::runtime::context::JSContext;
5use crate::util::FxHashMap;
6use crate::value::JSValue;
7
8fn throw_type_error(ctx: &mut JSContext, message: &str) {
9    if let Some(ptr) = ctx.get_register_vm_ptr() {
10        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
11        let mut err = JSObject::new_typed(crate::object::object::ObjectType::Error);
12        err.set(
13            ctx.common_atoms.message,
14            JSValue::new_string(ctx.intern(message)),
15        );
16        err.set(
17            ctx.common_atoms.name,
18            JSValue::new_string(ctx.intern("TypeError")),
19        );
20        if let Some(proto) = ctx.get_type_error_prototype() {
21            err.prototype = Some(proto);
22        }
23        let err_ptr = Box::into_raw(Box::new(err)) as usize;
24        ctx.runtime_mut().gc_heap_mut().track(err_ptr);
25        vm.pending_throw = Some(JSValue::new_object(err_ptr));
26    }
27}
28
29fn create_builtin_method(
30    ctx: &mut JSContext,
31    name: &str,
32    arity: u32,
33    display_name: &str,
34) -> JSValue {
35    let mut func = JSFunction::new_builtin(ctx.intern(name), 1);
36    func.set_builtin_marker(ctx, name);
37    if let Some(fn_proto_ptr) = ctx.get_function_prototype() {
38        func.base.set_prototype_raw(fn_proto_ptr);
39    }
40    {
41        let mut desc =
42            crate::object::object::PropertyDescriptor::new_data(JSValue::new_int(arity as i64));
43        desc.writable = false;
44        desc.enumerable = false;
45        desc.configurable = true;
46        func.base
47            .define_property_ext(ctx.common_atoms.length, desc, true, true, true);
48    }
49    {
50        let mut desc = crate::object::object::PropertyDescriptor::new_data(JSValue::new_string(
51            ctx.intern(display_name),
52        ));
53        desc.writable = false;
54        desc.enumerable = false;
55        desc.configurable = true;
56        func.base
57            .define_property_ext(ctx.common_atoms.name, desc, true, true, true);
58    }
59    let ptr = Box::into_raw(Box::new(func)) as usize;
60    ctx.runtime_mut().gc_heap_mut().track_function(ptr);
61    JSValue::new_function(ptr)
62}
63
64fn patch_function_values_with_function_prototype(obj: &mut JSObject, fn_proto_ptr: *mut JSObject) {
65    patch_function_values_recursive(obj, fn_proto_ptr, &mut Vec::new());
66}
67
68fn patch_function_values_recursive(
69    obj: &mut JSObject,
70    fn_proto_ptr: *mut JSObject,
71    visited: &mut Vec<usize>,
72) {
73    let obj_addr = obj as *mut JSObject as usize;
74    if visited.contains(&obj_addr) {
75        return;
76    }
77    visited.push(obj_addr);
78
79    let mut values = Vec::new();
80    obj.for_each_property(|_atom, value, _attrs| {
81        values.push(value);
82    });
83
84    for value in values {
85        if value.is_function() {
86            let func = unsafe { JSValue::function_from_ptr_mut(value.get_ptr()) };
87            if func.base.prototype.is_none() {
88                func.base.set_prototype_raw(fn_proto_ptr);
89            }
90            patch_function_values_recursive(&mut func.base, fn_proto_ptr, visited);
91        } else if value.is_object() {
92            let nested = value.as_object_mut();
93            patch_function_values_recursive(nested, fn_proto_ptr, visited);
94        }
95    }
96}
97
98pub fn init_function(ctx: &mut JSContext) {
99    let mut proto_obj = JSObject::new_function();
100    if let Some(object_proto_ptr) = ctx.get_object_prototype() {
101        proto_obj.prototype = Some(object_proto_ptr);
102    }
103    proto_obj.set(
104        ctx.common_atoms.bind,
105        create_builtin_method(ctx, "function_bind", 1, "bind"),
106    );
107    proto_obj.set(
108        ctx.common_atoms.call,
109        create_builtin_method(ctx, "function_call", 1, "call"),
110    );
111    proto_obj.set(
112        ctx.common_atoms.apply,
113        create_builtin_method(ctx, "function_apply", 2, "apply"),
114    );
115    proto_obj.set(
116        ctx.common_atoms.to_string,
117        create_builtin_method(ctx, "function_toString", 0, "toString"),
118    );
119    {
120        let mut desc = crate::object::object::PropertyDescriptor::new_data(JSValue::new_int(0));
121        desc.writable = false;
122        desc.enumerable = false;
123        desc.configurable = true;
124        proto_obj.define_property_ext(ctx.common_atoms.length, desc, true, true, true);
125    }
126    {
127        let mut desc = crate::object::object::PropertyDescriptor::new_data(JSValue::new_string(
128            ctx.intern(""),
129        ));
130        desc.writable = false;
131        desc.enumerable = false;
132        desc.configurable = true;
133        proto_obj.define_property_ext(ctx.common_atoms.name, desc, true, true, true);
134    }
135    // Throw TypeError accessors for .caller and .arguments.
136    // Per spec, bound and strict mode functions throw when accessing these.
137    let throw_type_error_fn = {
138        let mut f = JSFunction::new_builtin(ctx.intern("ThrowTypeError"), 0);
139        f.set_builtin_marker(ctx, "throw_type_error_caller");
140        let ptr = Box::into_raw(Box::new(f)) as usize;
141        ctx.runtime_mut().gc_heap_mut().track_function(ptr);
142        JSValue::new_function(ptr)
143    };
144    {
145        let entry = crate::object::object::AccessorEntry {
146            get: Some(throw_type_error_fn.clone()),
147            set: Some(throw_type_error_fn.clone()),
148            enumerable: false,
149            configurable: true,
150        };
151        proto_obj
152            .ensure_extra()
153            .accessors
154            .get_or_insert_with(|| Box::new(FxHashMap::default()))
155            .insert(ctx.intern("caller"), entry);
156    }
157    {
158        let entry = crate::object::object::AccessorEntry {
159            get: Some(throw_type_error_fn.clone()),
160            set: Some(throw_type_error_fn.clone()),
161            enumerable: false,
162            configurable: true,
163        };
164        proto_obj
165            .ensure_extra()
166            .accessors
167            .get_or_insert_with(|| Box::new(FxHashMap::default()))
168            .insert(ctx.intern("arguments"), entry);
169    }
170
171    if let Some(obj_proto_ptr) = ctx.get_object_prototype() {
172        proto_obj.prototype = Some(obj_proto_ptr);
173    }
174
175    let proto_ptr = Box::into_raw(Box::new(proto_obj)) as usize;
176    ctx.runtime_mut().gc_heap_mut().track(proto_ptr);
177    let proto_value = JSValue::new_object(proto_ptr);
178
179    ctx.set_function_prototype(proto_ptr);
180
181    let proto_obj_mut = unsafe { &mut *(proto_ptr as *mut JSObject) };
182    for atom in [
183        ctx.common_atoms.bind,
184        ctx.common_atoms.call,
185        ctx.common_atoms.apply,
186        ctx.common_atoms.to_string,
187    ] {
188        if let Some(v) = proto_obj_mut.get(atom) {
189            if v.is_function() {
190                let f = unsafe { JSValue::function_from_ptr_mut(v.get_ptr()) };
191                f.base.set_prototype_raw(proto_ptr as *mut JSObject);
192            }
193        }
194    }
195
196    let global = ctx.global();
197    if global.is_object() {
198        let global_obj = global.as_object_mut();
199        patch_function_values_with_function_prototype(global_obj, proto_ptr as *mut JSObject);
200    }
201
202    let mut function_ctor = JSFunction::new_builtin(ctx.common_atoms.function, 1);
203    function_ctor.set_builtin_marker(ctx, "function_constructor");
204    function_ctor.base.prototype = Some(proto_ptr as *mut JSObject);
205    function_ctor
206        .base
207        .set(ctx.common_atoms.prototype, proto_value.clone());
208
209    let function_ptr = Box::into_raw(Box::new(function_ctor)) as usize;
210    ctx.runtime_mut().gc_heap_mut().track_function(function_ptr);
211    let function_value = JSValue::new_function(function_ptr);
212
213    unsafe {
214        let proto_obj_ptr = proto_ptr as *mut crate::object::object::JSObject;
215        (*proto_obj_ptr).set(ctx.common_atoms.constructor, function_value);
216    }
217
218    let global = ctx.global();
219    if global.is_object() {
220        let global_obj = global.as_object_mut();
221        crate::builtins::global::set_non_enumerable(
222            global_obj,
223            ctx.common_atoms.function,
224            function_value,
225        );
226        crate::builtins::global::set_non_enumerable(
227            global_obj,
228            ctx.intern("FunctionPrototype"),
229            proto_value,
230        );
231    }
232}
233
234pub fn register_builtins(ctx: &mut JSContext) {
235    ctx.register_builtin(
236        "function_bind",
237        HostFunction::method("bind", 1, function_bind),
238    );
239    ctx.register_builtin(
240        "function_call",
241        HostFunction::method("call", 1, function_call),
242    );
243    ctx.register_builtin(
244        "function_apply",
245        HostFunction::method("apply", 2, function_apply),
246    );
247    ctx.register_builtin(
248        "function_toString",
249        HostFunction::method("toString", 0, function_to_string),
250    );
251    ctx.register_builtin(
252        "function_length",
253        HostFunction::method("length", 0, function_length),
254    );
255    ctx.register_builtin(
256        "function_name",
257        HostFunction::method("name", 0, function_name),
258    );
259    ctx.register_builtin(
260        "function_constructor",
261        HostFunction::ctor("Function", 1, function_constructor),
262    );
263    ctx.register_builtin(
264        "function_has_instance",
265        HostFunction::method(SYMBOL_HAS_INSTANCE_DISPLAY, 1, function_has_instance),
266    );
267    ctx.register_builtin(
268        "throw_type_error_callee",
269        HostFunction::new("callee", 0, throw_type_error_callee),
270    );
271    ctx.register_builtin(
272        "throw_type_error_caller",
273        HostFunction::new("caller", 0, throw_type_error_caller_args),
274    );
275}
276
277fn throw_type_error_callee(ctx: &mut JSContext, _args: &[JSValue]) -> JSValue {
278    if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
279        let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
280        vm.set_pending_type_error(ctx, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them");
281    }
282    JSValue::undefined()
283}
284
285fn throw_type_error_caller_args(ctx: &mut JSContext, _args: &[JSValue]) -> JSValue {
286    if let Some(vm_ptr) = ctx.get_register_vm_ptr() {
287        let vm = unsafe { &mut *(vm_ptr as *mut crate::runtime::vm::VM) };
288        vm.set_pending_type_error(ctx, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them");
289    }
290    JSValue::undefined()
291}
292
293const SYMBOL_HAS_INSTANCE_DISPLAY: &str = "[Symbol.hasInstance]";
294
295fn function_has_instance(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
296    if args.len() < 2 {
297        return JSValue::bool(false);
298    }
299    let f = &args[0];
300    let v = &args[1];
301    if !f.is_function() && !f.is_object() {
302        return JSValue::bool(false);
303    }
304    if !v.is_object() && !v.is_function() {
305        return JSValue::bool(false);
306    }
307    let proto_atom = ctx.common_atoms.prototype;
308    let proto_ptr = if f.is_function() {
309        let js_func = f.as_function();
310        if !js_func.cached_prototype_ptr.is_null() {
311            js_func.cached_prototype_ptr as *const crate::object::object::JSObject
312        } else {
313            let pv = js_func.base.get(proto_atom).unwrap_or(JSValue::undefined());
314            if pv.is_object() {
315                pv.get_ptr() as *const crate::object::object::JSObject
316            } else {
317                return JSValue::bool(false);
318            }
319        }
320    } else {
321        let pv = f
322            .as_object()
323            .get(proto_atom)
324            .unwrap_or(JSValue::undefined());
325        if pv.is_object() {
326            pv.get_ptr() as *const crate::object::object::JSObject
327        } else {
328            return JSValue::bool(false);
329        }
330    };
331    let obj_ptr = v.get_ptr() as *const crate::object::object::JSObject;
332    let mut current = unsafe { (*obj_ptr).prototype };
333    while let Some(p) = current {
334        if std::ptr::eq(p, proto_ptr) {
335            return JSValue::bool(true);
336        }
337        current = unsafe { (*p).prototype };
338    }
339    JSValue::bool(false)
340}
341
342fn function_constructor(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
343    if args.is_empty() {
344        return match crate::eval(ctx, "(function anonymous() {})") {
345            Ok(val) => {
346                if val.is_function() {
347                    val.as_function_mut().name = ctx.intern("anonymous");
348                    let name_atom = ctx.intern("name");
349                    let obj = val.as_object_mut();
350                    let mut desc = crate::object::object::PropertyDescriptor::new_data(
351                        JSValue::new_string(ctx.intern("anonymous")),
352                    );
353                    desc.writable = false;
354                    desc.enumerable = false;
355                    desc.configurable = true;
356                    obj.define_property_ext(name_atom, desc, true, true, true);
357                }
358                val
359            }
360            Err(_) => JSValue::undefined(),
361        };
362    }
363    let body_idx = args.len() - 1;
364    let body_str = if args[body_idx].is_string() {
365        ctx.get_atom_str(args[body_idx].get_atom()).to_string()
366    } else {
367        String::new()
368    };
369    let mut params = Vec::new();
370    for i in 0..body_idx {
371        let p = if args[i].is_string() {
372            ctx.get_atom_str(args[i].get_atom()).to_string()
373        } else {
374            String::new()
375        };
376        for part in p.trim().split(',') {
377            let part = part.trim();
378            if !part.is_empty() {
379                params.push(part.to_string());
380            }
381        }
382    }
383    let params_str = params.join(",");
384    let source = format!("(function anonymous({}){{{}}})", params_str, body_str);
385    match crate::eval(ctx, &source) {
386        Ok(val) => {
387            if val.is_function() {
388                val.as_function_mut().name = ctx.intern("anonymous");
389                let name_atom = ctx.intern("name");
390                let obj = val.as_object_mut();
391                let mut desc = crate::object::object::PropertyDescriptor::new_data(
392                    JSValue::new_string(ctx.intern("anonymous")),
393                );
394                desc.writable = false;
395                desc.enumerable = false;
396                desc.configurable = true;
397                obj.define_property_ext(name_atom, desc, true, true, true);
398            }
399            val
400        }
401        Err(e) => {
402            let mut err = crate::object::object::JSObject::new();
403            err.set(
404                ctx.intern("name"),
405                JSValue::new_string(ctx.intern("SyntaxError")),
406            );
407            err.set(ctx.intern("message"), JSValue::new_string(ctx.intern(&e)));
408            if let Some(proto) = ctx.get_syntax_error_prototype() {
409                err.prototype = Some(proto);
410            }
411            let ptr = Box::into_raw(Box::new(err)) as usize;
412            ctx.runtime_mut().gc_heap_mut().track(ptr);
413            ctx.pending_exception = Some(JSValue::new_object(ptr));
414            JSValue::undefined()
415        }
416    }
417}
418
419fn function_bind(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
420    if args.is_empty() {
421        return JSValue::undefined();
422    }
423
424    let this_val = &args[0];
425    if !this_val.is_function() && !this_val.is_object() {
426        let mut err =
427            crate::object::object::JSObject::new_typed(crate::object::object::ObjectType::Error);
428        if let Some(proto) = ctx.get_type_error_prototype() {
429            err.prototype = Some(proto);
430        }
431        err.set(
432            ctx.common_atoms.message,
433            JSValue::new_string(ctx.intern("this is not a function")),
434        );
435        let ptr = Box::into_raw(Box::new(err)) as usize;
436        ctx.runtime_mut().gc_heap_mut().track(ptr);
437        ctx.pending_exception = Some(JSValue::new_object(ptr));
438        return JSValue::undefined();
439    }
440
441    let is_bound = this_val.is_object() && !this_val.is_function();
442    if is_bound {
443        let obj = this_val.as_object();
444        if obj.get(ctx.common_atoms.__boundFn).is_none() {
445            let mut err = crate::object::object::JSObject::new_typed(
446                crate::object::object::ObjectType::Error,
447            );
448            if let Some(proto) = ctx.get_type_error_prototype() {
449                err.prototype = Some(proto);
450            }
451            err.set(
452                ctx.common_atoms.message,
453                JSValue::new_string(ctx.intern("this is not a function")),
454            );
455            let eptr = Box::into_raw(Box::new(err)) as usize;
456            ctx.runtime_mut().gc_heap_mut().track(eptr);
457            ctx.pending_exception = Some(JSValue::new_object(eptr));
458            return JSValue::undefined();
459        }
460    }
461
462    let this_arg = if args.len() > 1 {
463        args[1].clone()
464    } else {
465        JSValue::undefined()
466    };
467
468    let mut wrapper = JSObject::new();
469    if let Some(proto_ptr) = ctx.get_function_prototype() {
470        wrapper.prototype = Some(proto_ptr);
471    }
472    wrapper.set(ctx.common_atoms.__boundFn, *this_val);
473    wrapper.set(ctx.common_atoms.__boundThis, this_arg);
474
475    let mut args_arr = JSObject::new_array();
476    let length_key = ctx.common_atoms.length;
477    let bound_count = if args.len() > 2 {
478        (args.len() - 2) as i64
479    } else {
480        0
481    };
482    args_arr.set(length_key, JSValue::new_int(bound_count));
483    let mut idx = 0;
484    for arg in args.iter().skip(2) {
485        let key = ctx.intern(&idx.to_string());
486        args_arr.set(key, arg.clone());
487        idx += 1;
488    }
489    let boxed_args_arr = Box::new(args_arr);
490    let args_ptr = Box::into_raw(boxed_args_arr) as usize;
491    ctx.runtime_mut().gc_heap_mut().track(args_ptr);
492    wrapper.set(ctx.common_atoms.__boundArgs, JSValue::new_object(args_ptr));
493
494    let target_length = if this_val.is_function() {
495        this_val.as_function().arity as i64
496    } else {
497        let obj = this_val.as_object();
498        if let Some(len) = obj.get(ctx.common_atoms.length) {
499            if len.is_int() { len.get_int() } else { 0 }
500        } else {
501            0
502        }
503    };
504    let bound_length = 0i64.max(target_length - bound_count);
505    wrapper.define_property(
506        ctx.common_atoms.length,
507        crate::object::object::PropertyDescriptor {
508            value: Some(JSValue::new_int(bound_length)),
509            writable: false,
510            enumerable: false,
511            configurable: true,
512            get: None,
513            set: None,
514        },
515    );
516
517    let target_name = if this_val.is_function() {
518        ctx.get_atom_str(this_val.as_function().name).to_string()
519    } else {
520        let obj = this_val.as_object();
521        if let Some(n) = obj.get(ctx.common_atoms.name) {
522            if n.is_string() {
523                ctx.get_atom_str(n.get_atom()).to_string()
524            } else {
525                String::new()
526            }
527        } else {
528            String::new()
529        }
530    };
531    let bound_name = format!("bound {}", target_name);
532    wrapper.define_property(
533        ctx.common_atoms.name,
534        crate::object::object::PropertyDescriptor {
535            value: Some(JSValue::new_string(ctx.intern(&bound_name))),
536            writable: false,
537            enumerable: false,
538            configurable: true,
539            get: None,
540            set: None,
541        },
542    );
543
544    let boxed_wrapper = Box::new(wrapper);
545    let ptr = Box::into_raw(boxed_wrapper) as usize;
546    ctx.runtime_mut().gc_heap_mut().track(ptr);
547    JSValue::new_object(ptr)
548}
549
550fn function_call(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
551    if args.is_empty() {
552        return JSValue::undefined();
553    }
554    let this_val = &args[0];
555    if !this_val.is_function() {
556        throw_type_error(ctx, "this is not a function");
557        return JSValue::undefined();
558    }
559    let this_arg = if args.len() > 1 {
560        args[1].clone()
561    } else {
562        JSValue::undefined()
563    };
564    let mut call_args = Vec::new();
565    for arg in args.iter().skip(2) {
566        call_args.push(arg.clone());
567    }
568
569    if let Some(ptr) = ctx.get_register_vm_ptr() {
570        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
571        let result = vm.call_function_with_this(ctx, *this_val, this_arg, &call_args);
572        match result {
573            Ok(val) => val,
574            Err(msg) => {
575                let exc = vm.last_caught_exception.take()
576                    .or_else(|| ctx.pending_exception.take())
577                    .unwrap_or_else(|| {
578                        let mut err = crate::object::object::JSObject::new_typed(
579                            crate::object::object::ObjectType::Error,
580                        );
581                        let msg_only = if let Some(idx) = msg.find(": ") {
582                            msg[idx + 2..].to_string()
583                        } else {
584                            msg.clone()
585                        };
586                        err.set(
587                            ctx.common_atoms.message,
588                            JSValue::new_string(ctx.intern(&msg_only)),
589                        );
590                        if msg.contains("TypeError") {
591                            err.set(
592                                ctx.common_atoms.name,
593                                JSValue::new_string(ctx.intern("TypeError")),
594                            );
595                            if let Some(proto) = ctx.get_type_error_prototype() {
596                                err.prototype = Some(proto);
597                            }
598                        } else if msg.contains("RangeError") {
599                            err.set(
600                                ctx.common_atoms.name,
601                                JSValue::new_string(ctx.intern("RangeError")),
602                            );
603                            if let Some(proto) = ctx.get_range_error_prototype() {
604                                err.prototype = Some(proto);
605                            }
606                        } else {
607                            err.set(
608                                ctx.common_atoms.name,
609                                JSValue::new_string(ctx.intern("Error")),
610                            );
611                            if let Some(proto) = ctx.get_error_prototype() {
612                                err.prototype = Some(proto);
613                            }
614                        }
615                        let err_ptr = Box::into_raw(Box::new(err)) as usize;
616                        ctx.runtime_mut().gc_heap_mut().track(err_ptr);
617                        JSValue::new_object(err_ptr)
618                    });
619                vm.pending_throw = Some(exc);
620                JSValue::undefined()
621            }
622        }
623    } else {
624        JSValue::undefined()
625    }
626}
627
628fn function_apply(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
629    if args.is_empty() {
630        return JSValue::undefined();
631    }
632    let this_val = &args[0];
633    if !this_val.is_function() {
634        throw_type_error(ctx, "this is not a function");
635        return JSValue::undefined();
636    }
637
638    let this_arg = if args.len() > 1 {
639        args[1].clone()
640    } else {
641        JSValue::undefined()
642    };
643
644    let mut call_args_buf = [JSValue::undefined(); 16];
645    let mut call_args_vec = Vec::new();
646    let call_args: &[JSValue];
647
648    if args.len() > 2 && args[2].is_object_like() {
649        let arr_obj = args[2].as_object();
650        let length_atom = ctx.common_atoms.length;
651
652        let len_val = arr_obj.get(length_atom);
653        if let Some(lv) = len_val {
654            if !lv.is_int() {
655                call_args = &[];
656                if let Some(ptr) = ctx.get_register_vm_ptr() {
657                    let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
658                    let result = vm.call_function_with_this(ctx, *this_val, this_arg, call_args);
659                    return match result {
660                        Ok(val) => val,
661                        Err(_) => {
662                            if let Some(exc) = vm.last_caught_exception.take() {
663                                vm.pending_throw = Some(exc);
664                            }
665                            JSValue::undefined()
666                        }
667                    };
668                }
669                return JSValue::undefined();
670            }
671            let len = lv.get_int() as usize;
672
673            if arr_obj.is_mapped_arguments() {
674                let fi = arr_obj.mapped_args_frame_index();
675                let param_count = arr_obj.mapped_args_param_count() as usize;
676                let vm_ptr = ctx.get_register_vm_ptr();
677                if let Some(ptr) = vm_ptr {
678                    let vm = unsafe { &*(ptr as *const crate::runtime::vm::VM) };
679                    if fi < vm.frames.len() {
680                        let frame = &vm.frames[fi];
681                        let base = frame.registers_base;
682                        let target = if len <= 16 {
683                            &mut call_args_buf[..len]
684                        } else {
685                            call_args_vec.resize(len, JSValue::undefined());
686                            &mut call_args_vec[..len]
687                        };
688                        let saved = &frame.saved_args;
689                        for i in 0..len {
690                            target[i] = if i < param_count {
691                                let reg_idx = base + 1 + i;
692                                if reg_idx < vm.registers.len() {
693                                    vm.registers[reg_idx]
694                                } else {
695                                    JSValue::undefined()
696                                }
697                            } else if i < saved.len() {
698                                saved[i]
699                            } else {
700                                arr_obj.get_indexed(i).unwrap_or(JSValue::undefined())
701                            };
702                        }
703                        call_args = if len <= 16 {
704                            &call_args_buf[..len]
705                        } else {
706                            &call_args_vec[..len]
707                        };
708                    } else {
709                        call_args = &[];
710                    }
711                } else {
712                    call_args = &[];
713                }
714            } else if len <= 16 {
715                if arr_obj.is_array() {
716                    let ptr = arr_obj as *const _ as usize;
717                    if arr_obj.is_dense_array() {
718                        let arr_ptr =
719                            unsafe { &*(ptr as *const crate::object::array_obj::JSArrayObject) };
720                        for i in 0..len {
721                            call_args_buf[i] = arr_ptr.get(i).unwrap_or(JSValue::undefined());
722                        }
723                    } else {
724                        for i in 0..len {
725                            let key = ctx.int_atom_mut(i);
726                            call_args_buf[i] = arr_obj.get(key).unwrap_or(JSValue::undefined());
727                        }
728                    }
729                } else if let Some(slice) = arr_obj.get_dense_slice(len) {
730                    call_args_buf[..len].copy_from_slice(slice);
731                } else {
732                    for i in 0..len {
733                        if let Some(val) = arr_obj.get_indexed(i) {
734                            call_args_buf[i] = val;
735                        } else {
736                            let key = ctx.int_atom_mut(i);
737                            call_args_buf[i] = arr_obj.get(key).unwrap_or(JSValue::undefined());
738                        }
739                    }
740                }
741                call_args = &call_args_buf[..len];
742            } else {
743                if arr_obj.is_array() {
744                    let ptr = arr_obj as *const _ as usize;
745                    if arr_obj.is_dense_array() {
746                        let arr_ptr =
747                            unsafe { &*(ptr as *const crate::object::array_obj::JSArrayObject) };
748                        for i in 0..len {
749                            call_args_vec.push(arr_ptr.get(i).unwrap_or(JSValue::undefined()));
750                        }
751                    } else {
752                        for i in 0..len {
753                            let key = ctx.int_atom_mut(i);
754                            call_args_vec.push(arr_obj.get(key).unwrap_or(JSValue::undefined()));
755                        }
756                    }
757                } else if let Some(slice) = arr_obj.get_dense_slice(len) {
758                    call_args_vec.extend_from_slice(slice);
759                } else {
760                    for i in 0..len {
761                        if let Some(val) = arr_obj.get_indexed(i) {
762                            call_args_vec.push(val);
763                        } else {
764                            let key = ctx.int_atom_mut(i);
765                            if let Some(val) = arr_obj.get(key) {
766                                call_args_vec.push(val);
767                            } else {
768                                call_args_vec.push(JSValue::undefined());
769                            }
770                        }
771                    }
772                }
773                call_args = &call_args_vec;
774            }
775        } else {
776            call_args = &[];
777        }
778    } else if args.len() > 2
779        && !args[2].is_null()
780        && !args[2].is_undefined()
781        && !args[2].is_object_like()
782    {
783        throw_type_error(ctx, "CreateListFromArrayLike called on non-object");
784        return JSValue::undefined();
785    } else {
786        call_args = &[];
787    }
788
789    if let Some(ptr) = ctx.get_register_vm_ptr() {
790        let vm = unsafe { &mut *(ptr as *mut crate::runtime::vm::VM) };
791        let result = vm.call_function_with_this(ctx, *this_val, this_arg, call_args);
792        match result {
793            Ok(val) => val,
794            Err(_) => {
795                if let Some(exc) = vm.last_caught_exception.take() {
796                    vm.pending_throw = Some(exc);
797                }
798                JSValue::undefined()
799            }
800        }
801    } else {
802        JSValue::undefined()
803    }
804}
805
806fn function_to_string(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
807    if args.is_empty() {
808        return JSValue::new_string(ctx.intern("function () { [native code] }"));
809    }
810    let this_val = &args[0];
811    if this_val.is_function() {
812        let js_func = this_val.as_function();
813
814        let func_name = ctx.get_atom_str(js_func.name).to_string();
815        let arity = js_func.arity as usize;
816
817        let params: Vec<String> = (0..arity).map(|i| format!("a{}", i)).collect();
818        let _param_str = params.join(", ");
819
820        if js_func.is_builtin() {
821            if func_name.is_empty() {
822                JSValue::new_string(ctx.intern(&format!("function() {{ [native code] }}")))
823            } else {
824                JSValue::new_string(
825                    ctx.intern(&format!("function {}() {{ [native code] }}", func_name)),
826                )
827            }
828        } else {
829            let prefix = if js_func.is_async() { "async " } else { "" };
830            let suffix = if js_func.is_generator() { "*" } else { "" };
831            if func_name.is_empty() {
832                JSValue::new_string(ctx.intern(&format!(
833                    "{}function{}() {{ [native code] }}",
834                    prefix, suffix
835                )))
836            } else {
837                JSValue::new_string(ctx.intern(&format!(
838                    "{}function{} {}() {{ [native code] }}",
839                    prefix, suffix, func_name
840                )))
841            }
842        }
843    } else {
844        JSValue::new_string(ctx.intern("[object Function]"))
845    }
846}
847
848fn function_length(_ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
849    if args.is_empty() {
850        return JSValue::undefined();
851    }
852    let this_val = &args[0];
853    if !this_val.is_function() {
854        return JSValue::undefined();
855    }
856    let js_func = this_val.as_function();
857    JSValue::new_int(js_func.arity as i64)
858}
859
860fn function_name(ctx: &mut JSContext, args: &[JSValue]) -> JSValue {
861    if args.is_empty() {
862        return JSValue::undefined();
863    }
864    let this_val = &args[0];
865    if !this_val.is_function() {
866        return JSValue::undefined();
867    }
868    let js_func = this_val.as_function();
869    let name_str = ctx.get_atom_str(js_func.name).to_string();
870    JSValue::new_string(ctx.intern(&name_str))
871}