javascript/core/
ffi.rs

1use super::{Value, evaluate_script, utf16_to_utf8};
2use std::ffi::c_void;
3
4#[repr(C)]
5#[derive(Copy, Clone)]
6pub union JSValueUnion {
7    pub int32: i32,
8    pub float64: f64,
9    pub ptr: *mut c_void,
10    pub short_big_int: i64,
11}
12
13#[repr(C)]
14#[derive(Copy, Clone)]
15pub struct JSValue {
16    pub u: JSValueUnion,
17    pub tag: i64,
18}
19
20pub const JS_TAG_FIRST: i32 = -9;
21pub const JS_TAG_BIG_INT: i32 = -9;
22pub const JS_TAG_SYMBOL: i32 = -8;
23pub const JS_TAG_STRING: i32 = -7;
24pub const JS_TAG_STRING_ROPE: i32 = -6;
25pub const JS_TAG_MODULE: i32 = -3;
26pub const JS_TAG_FUNCTION_BYTECODE: i32 = -2;
27pub const JS_TAG_OBJECT: i32 = -1;
28
29pub const JS_TAG_INT: i32 = 0;
30pub const JS_TAG_BOOL: i32 = 1;
31pub const JS_TAG_NULL: i32 = 2;
32pub const JS_TAG_UNDEFINED: i32 = 3;
33pub const JS_TAG_UNINITIALIZED: i32 = 4;
34pub const JS_TAG_CATCH_OFFSET: i32 = 5;
35pub const JS_TAG_EXCEPTION: i32 = 6;
36pub const JS_TAG_SHORT_BIG_INT: i32 = 7;
37pub const JS_TAG_FLOAT64: i32 = 8;
38
39pub const JS_FLOAT64_NAN: f64 = f64::NAN;
40
41impl JSValue {
42    pub fn new_int32(val: i32) -> JSValue {
43        JSValue {
44            u: JSValueUnion { int32: val },
45            tag: JS_TAG_INT as i64,
46        }
47    }
48
49    pub fn new_bool(val: bool) -> JSValue {
50        JSValue {
51            u: JSValueUnion {
52                int32: if val { 1 } else { 0 },
53            },
54            tag: JS_TAG_BOOL as i64,
55        }
56    }
57
58    pub fn new_float64(val: f64) -> JSValue {
59        JSValue {
60            u: JSValueUnion { float64: val },
61            tag: JS_TAG_FLOAT64 as i64,
62        }
63    }
64
65    pub fn new_ptr(tag: i32, ptr: *mut c_void) -> JSValue {
66        JSValue {
67            u: JSValueUnion { ptr },
68            tag: tag as i64,
69        }
70    }
71
72    pub fn has_ref_count(&self) -> bool {
73        let t = self.tag as i32;
74        (JS_TAG_FIRST..=JS_TAG_OBJECT).contains(&t)
75    }
76
77    pub fn get_ptr(&self) -> *mut c_void {
78        unsafe { self.u.ptr }
79    }
80
81    pub fn get_tag(&self) -> i32 {
82        self.tag as i32
83    }
84}
85
86pub const JS_NULL: JSValue = JSValue {
87    u: JSValueUnion { int32: 0 },
88    tag: JS_TAG_NULL as i64,
89};
90
91pub const JS_UNDEFINED: JSValue = JSValue {
92    u: JSValueUnion { int32: 0 },
93    tag: JS_TAG_UNDEFINED as i64,
94};
95
96pub const JS_FALSE: JSValue = JSValue {
97    u: JSValueUnion { int32: 0 },
98    tag: JS_TAG_BOOL as i64,
99};
100
101pub const JS_TRUE: JSValue = JSValue {
102    u: JSValueUnion { int32: 1 },
103    tag: JS_TAG_BOOL as i64,
104};
105
106pub const JS_EXCEPTION: JSValue = JSValue {
107    u: JSValueUnion { int32: 0 },
108    tag: JS_TAG_EXCEPTION as i64,
109};
110
111pub const JS_UNINITIALIZED: JSValue = JSValue {
112    u: JSValueUnion { int32: 0 },
113    tag: JS_TAG_UNINITIALIZED as i64,
114};
115
116#[repr(C)]
117pub struct list_head {
118    pub prev: *mut list_head,
119    pub next: *mut list_head,
120}
121
122impl list_head {
123    /// # Safety
124    /// The caller must ensure that the list_head is properly initialized and not concurrently accessed.
125    pub unsafe fn init(&mut self) {
126        self.prev = self;
127        self.next = self;
128    }
129
130    /// # Safety
131    /// The caller must ensure that `new_entry` is a valid pointer to an uninitialized list_head,
132    /// and that the list is not concurrently modified.
133    pub unsafe fn add_tail(&mut self, new_entry: *mut list_head) {
134        unsafe {
135            let prev = self.prev;
136            (*new_entry).next = self;
137            (*new_entry).prev = prev;
138            (*prev).next = new_entry;
139            self.prev = new_entry;
140        }
141    }
142
143    /// # Safety
144    /// The caller must ensure that the list_head is part of a valid linked list and not concurrently accessed.
145    pub unsafe fn del(&mut self) {
146        unsafe {
147            let next = self.next;
148            let prev = self.prev;
149            (*next).prev = prev;
150            (*prev).next = next;
151            self.next = std::ptr::null_mut();
152            self.prev = std::ptr::null_mut();
153        }
154    }
155}
156
157#[repr(C)]
158pub struct JSMallocState {
159    pub malloc_count: usize,
160    pub malloc_size: usize,
161    pub malloc_limit: usize,
162    pub opaque: *mut c_void,
163}
164
165#[repr(C)]
166pub struct JSMallocFunctions {
167    pub js_malloc: Option<unsafe extern "C" fn(*mut JSMallocState, usize) -> *mut c_void>,
168    pub js_free: Option<unsafe extern "C" fn(*mut JSMallocState, *mut c_void)>,
169    pub js_realloc: Option<unsafe extern "C" fn(*mut JSMallocState, *mut c_void, usize) -> *mut c_void>,
170    pub js_malloc_usable_size: Option<unsafe extern "C" fn(*const c_void) -> usize>,
171}
172
173pub type JSAtom = u32;
174
175#[repr(C)]
176pub struct JSRefCountHeader {
177    pub ref_count: i32,
178}
179
180#[repr(C)]
181pub struct JSString {
182    pub header: JSRefCountHeader,
183    pub len: u32,  // len: 31, is_wide_char: 1 (packed manually)
184    pub hash: u32, // hash: 30, atom_type: 2 (packed manually)
185    pub hash_next: u32,
186    // Variable length data follows
187}
188
189pub type JSAtomStruct = JSString;
190
191#[repr(C)]
192pub struct JSClass {
193    pub class_id: u32,
194    pub class_name: JSAtom,
195    pub finalizer: *mut c_void, // JSClassFinalizer
196    pub gc_mark: *mut c_void,   // JSClassGCMark
197    pub call: *mut c_void,      // JSClassCall
198    pub exotic: *mut c_void,    // JSClassExoticMethods
199}
200
201#[repr(C)]
202pub struct JSRuntime {
203    pub mf: JSMallocFunctions,
204    pub malloc_state: JSMallocState,
205    pub rt_info: *const i8,
206
207    pub atom_hash_size: i32,
208    pub atom_count: i32,
209    pub atom_size: i32,
210    pub atom_count_resize: i32,
211    pub atom_hash: *mut u32,
212    pub atom_array: *mut *mut JSAtomStruct,
213    pub atom_free_index: i32,
214
215    pub class_count: i32,
216    pub class_array: *mut JSClass,
217
218    pub context_list: list_head,
219    pub gc_obj_list: list_head,
220    pub gc_zero_ref_count_list: list_head,
221    pub tmp_obj_list: list_head,
222    pub gc_phase: u8,
223    pub malloc_gc_threshold: usize,
224    pub weakref_list: list_head,
225
226    pub shape_hash_bits: i32,
227    pub shape_hash_size: i32,
228    pub shape_hash_count: i32,
229    pub shape_hash: *mut *mut JSShape,
230    pub user_opaque: *mut c_void,
231}
232
233#[repr(C)]
234pub struct JSGCObjectHeader {
235    pub ref_count: i32,
236    pub gc_obj_type: u8, // 4 bits
237    pub mark: u8,        // 1 bit
238    pub dummy0: u8,      // 3 bits
239    pub dummy1: u8,
240    pub dummy2: u16,
241    pub link: list_head,
242}
243
244#[repr(C)]
245pub struct JSShape {
246    pub header: JSGCObjectHeader,
247    pub is_hashed: u8,
248    pub has_small_array_index: u8,
249    pub hash: u32,
250    pub prop_hash_mask: u32,
251    pub prop_size: i32,
252    pub prop_count: i32,
253    pub deleted_prop_count: i32,
254    pub prop: *mut JSShapeProperty,
255    pub prop_hash: *mut u32,
256    // Linked list of objects which currently use this shape. Each object's
257    // `next_in_shape` pointer links to the next object in the list.
258    pub first_object: *mut JSObject,
259    pub proto: *mut JSObject,
260}
261
262#[repr(C)]
263pub struct JSContext {
264    pub header: JSGCObjectHeader,
265    pub rt: *mut JSRuntime,
266    pub link: list_head,
267
268    pub binary_object_count: u16,
269    pub binary_object_size: i32,
270    pub std_array_prototype: u8,
271
272    pub array_shape: *mut JSShape,
273    pub arguments_shape: *mut JSShape,
274    pub mapped_arguments_shape: *mut JSShape,
275    pub regexp_shape: *mut JSShape,
276    pub regexp_result_shape: *mut JSShape,
277
278    pub class_proto: *mut JSValue,
279    pub function_proto: JSValue,
280    pub function_ctor: JSValue,
281    pub array_ctor: JSValue,
282    pub regexp_ctor: JSValue,
283    pub promise_ctor: JSValue,
284    pub native_error_proto: [JSValue; 8], // JS_NATIVE_ERROR_COUNT = 8 (usually)
285    pub iterator_ctor: JSValue,
286    pub async_iterator_proto: JSValue,
287    pub array_proto_values: JSValue,
288    pub throw_type_error: JSValue,
289    pub eval_obj: JSValue,
290
291    pub global_obj: JSValue,
292    pub global_var_obj: JSValue,
293
294    pub random_state: u64,
295    pub interrupt_counter: i32,
296
297    pub loaded_modules: list_head,
298
299    pub compile_regexp: Option<unsafe extern "C" fn(*mut JSContext, JSValue, JSValue) -> JSValue>,
300    pub eval_internal: Option<unsafe extern "C" fn(*mut JSContext, JSValue, *const i8, usize, *const i8, i32, i32) -> JSValue>,
301    pub user_opaque: *mut c_void,
302}
303
304#[repr(C)]
305pub struct JSFunctionBytecode {
306    pub header: JSGCObjectHeader,
307    pub js_mode: u8,
308    pub flags: u16, // Packed bitfields
309    pub byte_code_buf: *mut u8,
310    pub byte_code_len: i32,
311    pub func_name: JSAtom,
312    pub vardefs: *mut c_void,     // JSBytecodeVarDef
313    pub closure_var: *mut c_void, // JSClosureVar
314    pub arg_count: u16,
315    pub var_count: u16,
316    pub defined_arg_count: u16,
317    pub stack_size: u16,
318    pub var_ref_count: u16,
319    pub realm: *mut JSContext,
320    pub cpool: *mut JSValue,
321    pub cpool_count: i32,
322    pub closure_var_count: i32,
323    // debug info
324    pub filename: JSAtom,
325    pub source_len: i32,
326    pub pc2line_len: i32,
327    pub pc2line_buf: *mut u8,
328    pub source: *mut i8,
329}
330
331#[repr(C)]
332pub struct JSStackFrame {
333    pub prev_frame: *mut JSStackFrame,
334    pub cur_func: JSValue,
335    pub arg_buf: *mut JSValue,
336    pub var_buf: *mut JSValue,
337    pub var_refs: *mut *mut c_void, // JSVarRef
338    pub cur_pc: *const u8,
339    pub arg_count: i32,
340    pub js_mode: i32,
341    pub cur_sp: *mut JSValue,
342}
343
344pub const JS_GC_OBJ_TYPE_JS_OBJECT: u8 = 1;
345pub const JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: u8 = 2;
346pub const JS_GC_OBJ_TYPE_SHAPE: u8 = 3;
347pub const JS_GC_OBJ_TYPE_VAR_REF: u8 = 4;
348pub const JS_GC_OBJ_TYPE_ASYNC_FUNCTION: u8 = 5;
349pub const JS_GC_OBJ_TYPE_JS_CONTEXT: u8 = 6;
350
351#[repr(C)]
352pub struct JSShapeProperty {
353    pub hash_next: u32,
354    pub flags: u8,
355    pub atom: JSAtom,
356}
357
358#[repr(C)]
359pub struct JSProperty {
360    pub u: JSPropertyUnion,
361}
362
363#[repr(C)]
364#[derive(Copy, Clone)]
365pub union JSPropertyUnion {
366    pub value: JSValue,
367    pub next: *mut JSProperty, // simplified for now
368}
369
370#[repr(C)]
371pub struct JSObject {
372    pub header: JSGCObjectHeader,
373    pub shape: *mut JSShape,
374    pub prop: *mut JSProperty,
375    pub first_weak_ref: *mut JSObject,
376    // Pointer to next object in the shape's object list
377    pub next_in_shape: *mut JSObject,
378}
379
380#[repr(C)]
381pub struct JSClassDef {
382    pub class_name: *const i8,
383    pub finalizer: Option<unsafe extern "C" fn(*mut JSRuntime, JSValue)>,
384    pub gc_mark: Option<unsafe extern "C" fn(*mut JSRuntime, JSValue, *mut c_void)>,
385    pub call: Option<unsafe extern "C" fn(*mut JSContext, JSValue, JSValue, i32, *mut JSValue, i32) -> JSValue>,
386    pub exotic: *mut c_void,
387}
388
389impl JSShape {
390    /// # Safety
391    /// The caller must ensure that the JSShape and its property arrays are valid and not concurrently modified.
392    pub unsafe fn find_own_property(&self, atom: JSAtom) -> Option<(i32, *mut JSShapeProperty)> {
393        unsafe {
394            if self.is_hashed != 0 {
395                let h = atom & self.prop_hash_mask;
396                let mut prop_idx = *self.prop_hash.offset(h as isize);
397                while prop_idx != 0 {
398                    let idx = (prop_idx - 1) as i32;
399                    let pr = self.prop.offset(idx as isize);
400                    if (*pr).atom == atom {
401                        return Some((idx, pr));
402                    }
403                    prop_idx = (*pr).hash_next;
404                }
405                None
406            } else {
407                for i in 0..self.prop_count {
408                    let pr = self.prop.offset(i as isize);
409                    if (*pr).atom == atom {
410                        return Some((i, pr));
411                    }
412                }
413                None
414            }
415        }
416    }
417}
418
419impl JSRuntime {
420    /// # Safety
421    /// The caller must ensure that `sh` is a valid pointer to a JSShape that is not concurrently accessed,
422    /// and that the runtime's memory allocation functions are properly set up.
423    pub unsafe fn resize_shape(&mut self, sh: *mut JSShape, new_size: i32) -> i32 {
424        unsafe {
425            let new_prop = self.js_realloc_rt(
426                (*sh).prop as *mut c_void,
427                new_size as usize * std::mem::size_of::<JSShapeProperty>(),
428            ) as *mut JSShapeProperty;
429
430            if new_prop.is_null() {
431                return -1;
432            }
433            (*sh).prop = new_prop;
434            (*sh).prop_size = new_size;
435            0
436        }
437    }
438
439    /// # Safety
440    /// The caller must ensure that `sh` is a valid pointer to a JSShape that is not concurrently accessed,
441    /// and that the runtime's memory allocation functions are properly set up.
442    pub unsafe fn add_property(&mut self, sh: *mut JSShape, atom: JSAtom, flags: u8) -> (i32, i32) {
443        unsafe {
444            // Remember previous prop_size (before any possible resize)
445            let prev_prop_size = (*sh).prop_size;
446
447            // Check if property already exists
448            if let Some((idx, _)) = (*sh).find_own_property(atom) {
449                // Already exists
450                return (idx, prev_prop_size);
451            }
452
453            if (*sh).prop_count >= (*sh).prop_size {
454                let new_size = if (*sh).prop_size == 0 { 4 } else { (*sh).prop_size * 3 / 2 };
455                // Resize the shape's property descriptor array
456                if self.resize_shape(sh, new_size) < 0 {
457                    return (-1, prev_prop_size);
458                }
459
460                // For all objects that currently use this shape, reallocate their
461                // JSProperty arrays to match the new shape size so every object
462                // has space for the newly added property slots.
463                let mut obj_ptr = (*sh).first_object;
464                while !obj_ptr.is_null() {
465                    // keep next pointer in case reallocation moves memory
466                    let next_obj = (*obj_ptr).next_in_shape;
467
468                    let old_prop = (*obj_ptr).prop;
469                    let new_prop_obj = self.js_realloc_rt(old_prop as *mut c_void, (new_size as usize) * std::mem::size_of::<JSProperty>())
470                        as *mut JSProperty;
471
472                    if new_prop_obj.is_null() {
473                        return (-1, prev_prop_size);
474                    }
475
476                    // If we just allocated the array, zero-initialize all slots.
477                    if old_prop.is_null() {
478                        // Initialize all slots to JS_UNDEFINED (correct default), avoid raw zeros
479                        for i in 0..(new_size as isize) {
480                            (*new_prop_obj.offset(i)).u.value = JS_UNDEFINED;
481                        }
482                    } else if new_size > prev_prop_size {
483                        // Zero only the newly-added slots
484                        let start_index = prev_prop_size as usize;
485                        let new_slots = (new_size - prev_prop_size) as usize;
486                        if new_slots > 0 {
487                            for i in start_index..(start_index + new_slots) {
488                                (*new_prop_obj.add(i)).u.value = JS_UNDEFINED;
489                            }
490                        }
491                    }
492
493                    (*obj_ptr).prop = new_prop_obj;
494                    obj_ptr = next_obj;
495                }
496            }
497
498            // Enable hash if needed
499            if (*sh).prop_count >= 4 && (*sh).is_hashed == 0 {
500                (*sh).is_hashed = 1;
501                (*sh).prop_hash_mask = 15; // 16 - 1
502                let hash_size = 16;
503                (*sh).prop_hash = self.js_malloc_rt(hash_size * std::mem::size_of::<u32>()) as *mut u32;
504                if (*sh).prop_hash.is_null() {
505                    return (-1, prev_prop_size);
506                }
507                for i in 0..hash_size {
508                    *(*sh).prop_hash.add(i) = 0;
509                }
510                // Fill hash table with existing properties
511                for i in 0..(*sh).prop_count {
512                    let pr = (*sh).prop.add(i as usize);
513                    let h = ((*pr).atom) & (*sh).prop_hash_mask;
514                    (*pr).hash_next = *(*sh).prop_hash.add(h as usize);
515                    *(*sh).prop_hash.add(h as usize) = (i + 1) as u32;
516                }
517            }
518
519            let idx = (*sh).prop_count;
520            let pr = (*sh).prop.add(idx as usize);
521            (*pr).atom = atom;
522            (*pr).flags = flags;
523            if (*sh).is_hashed != 0 {
524                let h = (atom) & (*sh).prop_hash_mask;
525                (*pr).hash_next = *(*sh).prop_hash.add(h as usize);
526                *(*sh).prop_hash.add(h as usize) = (idx + 1) as u32;
527            } else {
528                (*pr).hash_next = 0;
529            }
530            (*sh).prop_count += 1;
531
532            // return index and previous prop_size so callers can resize object prop arrays
533            (idx, prev_prop_size)
534        }
535    }
536
537    /// # Safety
538    /// The caller must ensure that the runtime's memory allocation functions are properly set up,
539    /// and that `ptr` is either null or a valid pointer previously returned by the allocator.
540    pub unsafe fn js_realloc_rt(&mut self, ptr: *mut c_void, size: usize) -> *mut c_void {
541        unsafe {
542            if let Some(realloc_func) = self.mf.js_realloc {
543                realloc_func(&mut self.malloc_state, ptr, size)
544            } else {
545                std::ptr::null_mut()
546            }
547        }
548    }
549
550    /// # Safety
551    /// The caller must ensure that the runtime's memory allocation functions are properly set up.
552    pub unsafe fn js_malloc_rt(&mut self, size: usize) -> *mut c_void {
553        unsafe {
554            if let Some(malloc_func) = self.mf.js_malloc {
555                malloc_func(&mut self.malloc_state, size)
556            } else {
557                std::ptr::null_mut()
558            }
559        }
560    }
561
562    /// # Safety
563    /// The caller must ensure that `ptr` is either null or a valid pointer previously returned by the allocator,
564    /// and that the runtime's memory allocation functions are properly set up.
565    pub unsafe fn js_free_rt(&mut self, ptr: *mut c_void) {
566        unsafe {
567            if let Some(free_func) = self.mf.js_free {
568                free_func(&mut self.malloc_state, ptr);
569            }
570        }
571    }
572
573    /// # Safety
574    /// The caller must ensure that the runtime's memory allocation functions are properly set up
575    /// and that the runtime is not concurrently accessed.
576    pub unsafe fn init_atoms(&mut self) {
577        unsafe {
578            self.atom_hash_size = 16;
579            self.atom_count = 0;
580            self.atom_size = 16;
581            self.atom_count_resize = 8;
582            self.atom_hash = self.js_malloc_rt((self.atom_hash_size as usize) * std::mem::size_of::<u32>()) as *mut u32;
583            if self.atom_hash.is_null() {
584                return;
585            }
586            for i in 0..self.atom_hash_size {
587                *self.atom_hash.offset(i as isize) = 0;
588            }
589            self.atom_array =
590                self.js_malloc_rt((self.atom_size as usize) * std::mem::size_of::<*mut JSAtomStruct>()) as *mut *mut JSAtomStruct;
591            if self.atom_array.is_null() {
592                self.js_free_rt(self.atom_hash as *mut c_void);
593                self.atom_hash = std::ptr::null_mut();
594                return;
595            }
596            for i in 0..self.atom_size {
597                *self.atom_array.offset(i as isize) = std::ptr::null_mut();
598            }
599            self.atom_free_index = 0;
600        }
601    }
602
603    /// # Safety
604    /// The caller must ensure that `proto` is either null or a valid pointer to a JSObject,
605    /// and that the runtime's memory allocation functions are properly set up.
606    pub unsafe fn js_new_shape(&mut self, proto: *mut JSObject) -> *mut JSShape {
607        unsafe {
608            let sh = self.js_malloc_rt(std::mem::size_of::<JSShape>()) as *mut JSShape;
609            if sh.is_null() {
610                return std::ptr::null_mut();
611            }
612            (*sh).header.ref_count = 1;
613            (*sh).header.gc_obj_type = 0; // JS_GC_OBJ_TYPE_SHAPE
614            (*sh).header.mark = 0;
615            (*sh).header.dummy0 = 0;
616            (*sh).header.dummy1 = 0;
617            (*sh).header.dummy2 = 0;
618            (*sh).header.link.init();
619            (*sh).is_hashed = 0;
620            (*sh).has_small_array_index = 0;
621            (*sh).hash = 0;
622            (*sh).prop_hash_mask = 0;
623            (*sh).prop_size = 0;
624            (*sh).prop_count = 0;
625            (*sh).deleted_prop_count = 0;
626            (*sh).prop = std::ptr::null_mut();
627            (*sh).prop_hash = std::ptr::null_mut();
628            (*sh).first_object = std::ptr::null_mut();
629            (*sh).proto = proto;
630            sh
631        }
632    }
633
634    /// # Safety
635    /// The caller must ensure that `sh` is either null or a valid pointer to a JSShape previously allocated by this runtime,
636    /// and that the runtime's memory allocation functions are properly set up.
637    pub unsafe fn js_free_shape(&mut self, sh: *mut JSShape) {
638        unsafe {
639            if !sh.is_null() {
640                if !(*sh).prop.is_null() {
641                    self.js_free_rt((*sh).prop as *mut c_void);
642                }
643                if !(*sh).prop_hash.is_null() {
644                    self.js_free_rt((*sh).prop_hash as *mut c_void);
645                }
646                self.js_free_rt(sh as *mut c_void);
647            }
648        }
649    }
650}
651
652/// # Safety
653/// The caller must ensure that `ctx` and `this_obj` are valid pointers, and that the runtime is properly initialized.
654pub unsafe fn JS_DefinePropertyValue(ctx: *mut JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue, flags: i32) -> i32 {
655    if this_obj.tag != JS_TAG_OBJECT as i64 {
656        return -1; // TypeError
657    }
658    let p = unsafe { this_obj.u.ptr } as *mut JSObject;
659    let sh = unsafe { (*p).shape };
660
661    // Add property to shape
662    // Note: In real QuickJS, we might need to clone shape if it is shared
663    // For now, assume shape is unique to object or we modify it in place (dangerous if shared)
664
665    let (idx, _prev_prop_size) = unsafe { (*(*ctx).rt).add_property(sh, prop, flags as u8) };
666    if idx < 0 {
667        return -1;
668    }
669
670    // After add_property we should have ensured the shape has enough capacity and
671    // that objects using this shape have their prop arrays resized. However it's
672    // still possible this particular object has no prop array (for example a
673    // newly created object that was not present when the shape grew). In that
674    // case allocate a prop array sized to the current shape.
675    let old_prop = unsafe { (*p).prop };
676    if old_prop.is_null() && unsafe { (*sh).prop_size } > 0 {
677        let size = unsafe { (*sh).prop_size as usize } * std::mem::size_of::<JSProperty>();
678        let new_prop = unsafe { (*(*ctx).rt).js_realloc_rt(std::ptr::null_mut(), size) as *mut JSProperty };
679        if new_prop.is_null() {
680            return -1;
681        }
682        unsafe {
683            (*p).prop = new_prop;
684            // initialize slots to JS_UNDEFINED
685            let n = (*sh).prop_size as isize;
686            for i in 0..n {
687                (*new_prop.offset(i)).u.value = JS_UNDEFINED;
688            }
689        }
690    }
691
692    // Set value
693    let pr = unsafe { (*p).prop.offset(idx as isize) };
694    // If replacing an existing value, free it
695    let old_val = unsafe { (*pr).u.value };
696    if old_val.has_ref_count() {
697        unsafe { JS_FreeValue((*ctx).rt, old_val) };
698    }
699    // Duplicate incoming value if it's ref-counted
700    if val.has_ref_count() {
701        unsafe { JS_DupValue((*ctx).rt, val) };
702    }
703    unsafe { (*pr).u.value = val };
704
705    1
706}
707
708/// # Safety
709/// This function initializes a new JavaScript runtime with default memory allocation functions.
710/// The caller must ensure that the returned runtime is properly freed with JS_FreeRuntime.
711pub unsafe fn JS_NewRuntime() -> *mut JSRuntime {
712    unsafe extern "C" fn my_malloc(_state: *mut JSMallocState, size: usize) -> *mut c_void {
713        unsafe { libc::malloc(size) }
714    }
715    unsafe extern "C" fn my_free(_state: *mut JSMallocState, ptr: *mut c_void) {
716        unsafe { libc::free(ptr) };
717    }
718    unsafe extern "C" fn my_realloc(_state: *mut JSMallocState, ptr: *mut c_void, size: usize) -> *mut c_void {
719        unsafe { libc::realloc(ptr, size) }
720    }
721
722    unsafe {
723        let rt = libc::malloc(std::mem::size_of::<JSRuntime>()) as *mut JSRuntime;
724        if rt.is_null() {
725            return std::ptr::null_mut();
726        }
727
728        // Initialize malloc functions
729        (*rt).mf.js_malloc = Some(my_malloc);
730        (*rt).mf.js_free = Some(my_free);
731        (*rt).mf.js_realloc = Some(my_realloc);
732        (*rt).mf.js_malloc_usable_size = None;
733
734        (*rt).malloc_state = JSMallocState {
735            malloc_count: 0,
736            malloc_size: 0,
737            malloc_limit: 0,
738            opaque: std::ptr::null_mut(),
739        };
740
741        (*rt).rt_info = std::ptr::null();
742
743        // Initialize atoms
744        (*rt).atom_hash_size = 0;
745        (*rt).atom_count = 0;
746        (*rt).atom_size = 0;
747        (*rt).atom_count_resize = 0;
748        (*rt).atom_hash = std::ptr::null_mut();
749        (*rt).atom_array = std::ptr::null_mut();
750        (*rt).atom_free_index = 0;
751
752        (*rt).class_count = 0;
753        (*rt).class_array = std::ptr::null_mut();
754
755        (*rt).context_list.init();
756        (*rt).gc_obj_list.init();
757        (*rt).gc_zero_ref_count_list.init();
758        (*rt).tmp_obj_list.init();
759        (*rt).gc_phase = 0;
760        (*rt).malloc_gc_threshold = 0;
761        (*rt).weakref_list.init();
762
763        (*rt).shape_hash_bits = 0;
764        (*rt).shape_hash_size = 0;
765        (*rt).shape_hash_count = 0;
766        (*rt).shape_hash = std::ptr::null_mut();
767
768        (*rt).user_opaque = std::ptr::null_mut();
769
770        (*rt).init_atoms();
771
772        rt
773    }
774}
775
776/// # Safety
777/// The caller must ensure that `rt` is either null or a valid pointer to a JSRuntime previously created by JS_NewRuntime,
778/// and that no contexts or objects from this runtime are still in use.
779pub unsafe fn JS_FreeRuntime(rt: *mut JSRuntime) {
780    if !rt.is_null() {
781        // Free allocated resources
782        // For now, just free the rt
783        unsafe { libc::free(rt as *mut c_void) };
784    }
785}
786
787/// # Safety
788/// The caller must ensure that `rt` is a valid pointer to a JSRuntime, and that the context is properly freed with JS_FreeContext.
789pub unsafe fn JS_NewContext(rt: *mut JSRuntime) -> *mut JSContext {
790    unsafe {
791        let ctx = (*rt).js_malloc_rt(std::mem::size_of::<JSContext>()) as *mut JSContext;
792        if ctx.is_null() {
793            return std::ptr::null_mut();
794        }
795        (*ctx).header.ref_count = 1;
796        (*ctx).header.gc_obj_type = 0;
797        (*ctx).header.mark = 0;
798        (*ctx).header.dummy0 = 0;
799        (*ctx).header.dummy1 = 0;
800        (*ctx).header.dummy2 = 0;
801        (*ctx).header.link.init();
802        (*ctx).rt = rt;
803        (*ctx).link.init();
804        // Initialize other fields to zero/null
805        (*ctx).binary_object_count = 0;
806        (*ctx).binary_object_size = 0;
807        (*ctx).std_array_prototype = 0;
808        (*ctx).array_shape = std::ptr::null_mut();
809        (*ctx).arguments_shape = std::ptr::null_mut();
810        (*ctx).mapped_arguments_shape = std::ptr::null_mut();
811        (*ctx).regexp_shape = std::ptr::null_mut();
812        (*ctx).regexp_result_shape = std::ptr::null_mut();
813        (*ctx).class_proto = std::ptr::null_mut();
814        (*ctx).function_proto = JS_NULL;
815        (*ctx).function_ctor = JS_NULL;
816        (*ctx).array_ctor = JS_NULL;
817        (*ctx).regexp_ctor = JS_NULL;
818        (*ctx).promise_ctor = JS_NULL;
819        for i in 0..8 {
820            (*ctx).native_error_proto[i] = JS_NULL;
821        }
822        (*ctx).iterator_ctor = JS_NULL;
823        (*ctx).async_iterator_proto = JS_NULL;
824        (*ctx).array_proto_values = JS_NULL;
825        (*ctx).throw_type_error = JS_NULL;
826        (*ctx).eval_obj = JS_NULL;
827        (*ctx).global_obj = JS_NULL;
828        (*ctx).global_var_obj = JS_NULL;
829        (*ctx).random_state = 0;
830        (*ctx).interrupt_counter = 0;
831        (*ctx).loaded_modules.init();
832        (*ctx).compile_regexp = None;
833        (*ctx).eval_internal = None;
834        (*ctx).user_opaque = std::ptr::null_mut();
835        ctx
836    }
837}
838
839/// # Safety
840/// The caller must ensure that `ctx` is either null or a valid pointer to a JSContext previously created by JS_NewContext,
841/// and that no objects from this context are still in use.
842pub unsafe fn JS_FreeContext(ctx: *mut JSContext) {
843    if !ctx.is_null() {
844        unsafe { (*(*ctx).rt).js_free_rt(ctx as *mut c_void) };
845    }
846}
847
848/// # Safety
849/// The caller must ensure that `ctx` is a valid pointer to a JSContext.
850pub unsafe fn JS_NewObject(ctx: *mut JSContext) -> JSValue {
851    unsafe {
852        let obj = (*(*ctx).rt).js_malloc_rt(std::mem::size_of::<JSObject>()) as *mut JSObject;
853        if obj.is_null() {
854            return JS_EXCEPTION;
855        }
856        (*obj).header.ref_count = 1;
857        (*obj).header.gc_obj_type = 0;
858        (*obj).header.mark = 0;
859        (*obj).header.dummy0 = 0;
860        (*obj).header.dummy1 = 0;
861        (*obj).header.dummy2 = 0;
862        (*obj).header.link.init();
863        (*obj).shape = (*(*ctx).rt).js_new_shape(std::ptr::null_mut());
864        if (*obj).shape.is_null() {
865            (*(*ctx).rt).js_free_rt(obj as *mut c_void);
866            return JS_EXCEPTION;
867        }
868        (*obj).prop = std::ptr::null_mut();
869        (*obj).first_weak_ref = std::ptr::null_mut();
870        (*obj).next_in_shape = std::ptr::null_mut();
871
872        // Link object into its shape's object list so that when the shape changes
873        // (for example when its prop_size grows) we can update all objects using
874        // the same shape.
875        if !(*obj).shape.is_null() {
876            let sh = (*obj).shape;
877            // prepend to shape's list
878            (*obj).next_in_shape = (*sh).first_object;
879            (*sh).first_object = obj;
880        }
881        JSValue::new_ptr(JS_TAG_OBJECT, obj as *mut c_void)
882    }
883}
884
885/// # Safety
886/// The caller must ensure that `ctx` is a valid pointer to a JSContext.
887pub unsafe fn JS_NewString(ctx: *mut JSContext, s: &[u16]) -> JSValue {
888    unsafe {
889        let utf8_str = utf16_to_utf8(s);
890        let len = utf8_str.len();
891        if len == 0 {
892            // Empty string
893            return JSValue::new_ptr(JS_TAG_STRING, std::ptr::null_mut());
894        }
895        let str_size = std::mem::size_of::<JSString>() + len;
896        let p = (*(*ctx).rt).js_malloc_rt(str_size) as *mut JSString;
897        if p.is_null() {
898            return JS_EXCEPTION;
899        }
900        (*p).header.ref_count = 1;
901        (*p).len = len as u32;
902        (*p).hash = 0; // TODO: compute hash
903        (*p).hash_next = 0;
904        // Copy string data
905        let str_data = (p as *mut u8).add(std::mem::size_of::<JSString>());
906        for (i, &byte) in utf8_str.as_bytes().iter().enumerate() {
907            *str_data.add(i) = byte;
908        }
909        JSValue::new_ptr(JS_TAG_STRING, p as *mut c_void)
910    }
911}
912
913/// # Safety
914/// The caller must ensure that `ctx` is a valid pointer to a JSContext, and that `input` points to valid UTF-8 data of length `input_len`.
915pub unsafe fn JS_Eval(_ctx: *mut JSContext, input: *const i8, input_len: usize, _filename: *const i8, _eval_flags: i32) -> JSValue {
916    unsafe {
917        if input_len == 0 {
918            return JS_UNDEFINED;
919        }
920        let s = std::slice::from_raw_parts(input as *const u8, input_len);
921        let script = std::str::from_utf8(s).unwrap_or("");
922
923        // Evaluate statements
924        match evaluate_script(script.trim()) {
925            Ok(Value::Number(num)) => JSValue::new_float64(num),
926            Ok(Value::String(s)) => JS_NewString(_ctx, &s),
927            Ok(Value::Boolean(b)) => {
928                if b {
929                    JS_TRUE
930                } else {
931                    JS_FALSE
932                }
933            }
934            Ok(Value::Undefined) => JS_UNDEFINED,
935            Ok(Value::Object(_)) => JS_UNDEFINED,             // For now
936            Ok(Value::Function(_)) => JS_UNDEFINED,           // For now
937            Ok(Value::Closure(_, _, _)) => JS_UNDEFINED,      // For now
938            Ok(Value::AsyncClosure(_, _, _)) => JS_UNDEFINED, // For now
939            Ok(Value::ClassDefinition(_)) => JS_UNDEFINED,    // For now
940            Ok(Value::Getter(_, _)) => JS_UNDEFINED,          // For now
941            Ok(Value::Setter(_, _, _)) => JS_UNDEFINED,       // For now
942            Ok(Value::Property { .. }) => JS_UNDEFINED,       // For now
943            Ok(Value::Promise(_)) => JS_UNDEFINED,            // For now
944            Ok(Value::Symbol(_)) => JS_UNDEFINED,             // For now
945            Ok(Value::BigInt(_)) => JS_UNDEFINED,
946            Ok(Value::Map(_)) => JS_UNDEFINED,                     // For now
947            Ok(Value::Set(_)) => JS_UNDEFINED,                     // For now
948            Ok(Value::WeakMap(_)) => JS_UNDEFINED,                 // For now
949            Ok(Value::WeakSet(_)) => JS_UNDEFINED,                 // For now
950            Ok(Value::GeneratorFunction(_, _, _)) => JS_UNDEFINED, // For now
951            Ok(Value::Generator(_)) => JS_UNDEFINED,               // For now
952            Ok(Value::Proxy(_)) => JS_UNDEFINED,                   // For now
953            Ok(Value::ArrayBuffer(_)) => JS_UNDEFINED,             // For now
954            Ok(Value::DataView(_)) => JS_UNDEFINED,                // For now
955            Ok(Value::TypedArray(_)) => JS_UNDEFINED,              // For now
956            Err(_) => JS_UNDEFINED,
957        }
958    }
959}
960
961/// # Safety
962/// The caller must ensure that `_ctx` is a valid pointer to a JSContext and `this_obj` is a valid JSValue.
963pub unsafe fn JS_GetProperty(_ctx: *mut JSContext, this_obj: JSValue, prop: JSAtom) -> JSValue {
964    unsafe {
965        if this_obj.tag != JS_TAG_OBJECT as i64 {
966            return JS_UNDEFINED;
967        }
968        let p = this_obj.u.ptr as *mut JSObject;
969        let sh = (*p).shape;
970        if let Some((idx, _)) = (*sh).find_own_property(prop) {
971            let prop_val = (*(*p).prop.offset(idx as isize)).u.value;
972            // Duplicate returned value when it's ref-counted so caller owns a reference
973            if prop_val.has_ref_count() {
974                JS_DupValue((*_ctx).rt, prop_val);
975            }
976            prop_val
977        } else {
978            JS_UNDEFINED
979        }
980    }
981}
982
983// Reference-count helpers: basic dup/free on objects/strings that store a ref_count
984// NOTE: This is a minimal implementation. Proper finalizers and nested frees
985// are not implemented here and should be added per object type.
986/// # Safety
987/// The caller must ensure that `v` is a valid JSValue and `_rt` is a valid JSRuntime pointer.
988pub unsafe fn JS_DupValue(_rt: *mut JSRuntime, v: JSValue) {
989    unsafe {
990        if v.has_ref_count() {
991            let p = v.get_ptr();
992            if !p.is_null() {
993                let header = p as *mut JSRefCountHeader;
994                (*header).ref_count += 1;
995            }
996        }
997    }
998}
999
1000/// # Safety
1001/// The caller must ensure that `rt` is a valid JSRuntime pointer and `v` is a valid JSValue.
1002pub unsafe fn JS_FreeValue(rt: *mut JSRuntime, v: JSValue) {
1003    unsafe {
1004        if v.has_ref_count() {
1005            let p = v.get_ptr();
1006            if p.is_null() {
1007                return;
1008            }
1009            let header = p as *mut JSRefCountHeader;
1010            (*header).ref_count -= 1;
1011            if (*header).ref_count > 0 {
1012                return;
1013            }
1014            // ref_count reached zero: dispatch based on tag to proper finalizer
1015            match v.get_tag() {
1016                x if x == JS_TAG_STRING => {
1017                    js_free_string(rt, v);
1018                }
1019                x if x == JS_TAG_OBJECT => {
1020                    js_free_object(rt, v);
1021                }
1022                x if x == JS_TAG_FUNCTION_BYTECODE => {
1023                    js_free_function_bytecode(rt, v);
1024                }
1025                x if x == JS_TAG_SYMBOL => {
1026                    js_free_symbol(rt, v);
1027                }
1028                x if x == JS_TAG_BIG_INT => {
1029                    js_free_bigint(rt, v);
1030                }
1031                x if x == JS_TAG_MODULE => {
1032                    js_free_module(rt, v);
1033                }
1034                // For other heap types, do a default free of the pointer
1035                _ => {
1036                    (*rt).js_free_rt(p);
1037                }
1038            }
1039        }
1040    }
1041}
1042
1043unsafe fn js_free_string(rt: *mut JSRuntime, v: JSValue) {
1044    unsafe {
1045        let p = v.get_ptr() as *mut JSString;
1046        if p.is_null() {
1047            return;
1048        }
1049        // The whole JSString allocation was allocated via js_malloc_rt
1050        (*rt).js_free_rt(p as *mut c_void);
1051    }
1052}
1053
1054unsafe fn js_free_object(rt: *mut JSRuntime, v: JSValue) {
1055    unsafe {
1056        let p = v.get_ptr() as *mut JSObject;
1057        if p.is_null() {
1058            return;
1059        }
1060        // Free property array
1061        if !(*p).prop.is_null() {
1062            (*rt).js_free_rt((*p).prop as *mut c_void);
1063            (*p).prop = std::ptr::null_mut();
1064        }
1065        // Unlink from shape's object list and free shape only if no objects remain
1066        if !(*p).shape.is_null() {
1067            let sh = (*p).shape;
1068            // unlink p from sh->first_object list
1069            if (*sh).first_object == p {
1070                (*sh).first_object = (*p).next_in_shape;
1071            } else {
1072                let mut prev = (*sh).first_object;
1073                while !prev.is_null() {
1074                    if (*prev).next_in_shape == p {
1075                        (*prev).next_in_shape = (*p).next_in_shape;
1076                        break;
1077                    }
1078                    prev = (*prev).next_in_shape;
1079                }
1080            }
1081            // clear link for p
1082            (*p).next_in_shape = std::ptr::null_mut();
1083
1084            // if no objects remain using this shape, free it
1085            if (*sh).first_object.is_null() {
1086                (*rt).js_free_shape(sh);
1087            }
1088            (*p).shape = std::ptr::null_mut();
1089        }
1090        // Free object struct
1091        (*rt).js_free_rt(p as *mut c_void);
1092    }
1093}
1094
1095unsafe fn js_free_function_bytecode(rt: *mut JSRuntime, v: JSValue) {
1096    unsafe {
1097        let p = v.get_ptr() as *mut JSFunctionBytecode;
1098        if p.is_null() {
1099            return;
1100        }
1101        // Free bytecode buffer
1102        if !(*p).byte_code_buf.is_null() {
1103            (*rt).js_free_rt((*p).byte_code_buf as *mut c_void);
1104            (*p).byte_code_buf = std::ptr::null_mut();
1105        }
1106        // Free pc2line buffer
1107        if !(*p).pc2line_buf.is_null() {
1108            (*rt).js_free_rt((*p).pc2line_buf as *mut c_void);
1109            (*p).pc2line_buf = std::ptr::null_mut();
1110        }
1111        // Free source
1112        if !(*p).source.is_null() {
1113            (*rt).js_free_rt((*p).source as *mut c_void);
1114            (*p).source = std::ptr::null_mut();
1115        }
1116        // Free cpool values
1117        if !(*p).cpool.is_null() && (*p).cpool_count > 0 {
1118            for i in 0..(*p).cpool_count as isize {
1119                let val = *(*p).cpool.offset(i);
1120                if val.has_ref_count() {
1121                    JS_FreeValue(rt, val);
1122                }
1123            }
1124            (*rt).js_free_rt((*p).cpool as *mut c_void);
1125            (*p).cpool = std::ptr::null_mut();
1126        }
1127        // Finally free the struct
1128        (*rt).js_free_rt(p as *mut c_void);
1129    }
1130}
1131
1132unsafe fn js_free_symbol(rt: *mut JSRuntime, v: JSValue) {
1133    let p = v.get_ptr();
1134    if p.is_null() {
1135        return;
1136    }
1137    // Symbols typically store their name as a JSString or internal struct
1138    // For now, free the pointer directly. Add type-aware finalizer later.
1139    unsafe { (*rt).js_free_rt(p) };
1140}
1141
1142unsafe fn js_free_bigint(rt: *mut JSRuntime, v: JSValue) {
1143    let p = v.get_ptr();
1144    if p.is_null() {
1145        return;
1146    }
1147    // BigInt representation may be inline or heap-allocated. Here we free pointer.
1148    unsafe { (*rt).js_free_rt(p) };
1149}
1150
1151unsafe fn js_free_module(rt: *mut JSRuntime, v: JSValue) {
1152    let p = v.get_ptr();
1153    if p.is_null() {
1154        return;
1155    }
1156    // Module structure not modelled here; free pointer for now.
1157    unsafe { (*rt).js_free_rt(p) };
1158}
1159
1160/// # Safety
1161/// The caller must ensure that `ctx` is a valid JSContext pointer, `this_obj` is a valid JSValue, and `prop` is a valid JSAtom.
1162pub unsafe fn JS_SetProperty(ctx: *mut JSContext, this_obj: JSValue, prop: JSAtom, val: JSValue) -> i32 {
1163    unsafe { JS_DefinePropertyValue(ctx, this_obj, prop, val, 0) }
1164}
1165
1166impl JSRuntime {
1167    /// # Safety
1168    /// The caller must ensure that `name` points to a valid buffer of at least `len` bytes.
1169    pub unsafe fn js_new_atom_len(&mut self, name: *const u8, len: usize) -> JSAtom {
1170        if len == 0 {
1171            return 0; // invalid
1172        }
1173        // Compute hash
1174        let mut h = 0u32;
1175        for i in 0..len {
1176            h = h.wrapping_mul(31).wrapping_add(unsafe { *name.add(i) } as u32);
1177        }
1178        // Find in hash table
1179        let hash_index = (h % self.atom_hash_size as u32) as i32;
1180        let mut atom = unsafe { *self.atom_hash.offset(hash_index as isize) };
1181        while atom != 0 {
1182            let p = unsafe { *self.atom_array.offset((atom - 1) as isize) };
1183            if unsafe { (*p).len == len as u32 && (*p).hash == h } {
1184                // Check string
1185                let str_data = unsafe { (p as *mut u8).add(std::mem::size_of::<JSString>()) };
1186                let mut equal = true;
1187                for i in 0..len {
1188                    if unsafe { *str_data.add(i) != *name.add(i) } {
1189                        equal = false;
1190                        break;
1191                    }
1192                }
1193                if equal {
1194                    return atom;
1195                }
1196            }
1197            atom = unsafe { (*p).hash_next };
1198        }
1199        // Not found, create new
1200        if self.atom_count >= self.atom_size {
1201            let new_size = self.atom_size * 2;
1202            let new_array = unsafe {
1203                self.js_realloc_rt(
1204                    self.atom_array as *mut c_void,
1205                    (new_size as usize) * std::mem::size_of::<*mut JSAtomStruct>(),
1206                )
1207            } as *mut *mut JSAtomStruct;
1208            if new_array.is_null() {
1209                return 0;
1210            }
1211            self.atom_array = new_array;
1212            self.atom_size = new_size;
1213            for i in self.atom_count..new_size {
1214                unsafe { *self.atom_array.offset(i as isize) = std::ptr::null_mut() };
1215            }
1216        }
1217        // Allocate JSString
1218        let str_size = std::mem::size_of::<JSString>() + len;
1219        let p = unsafe { self.js_malloc_rt(str_size) } as *mut JSString;
1220        if p.is_null() {
1221            return 0;
1222        }
1223        unsafe { (*p).header.ref_count = 1 };
1224        unsafe { (*p).len = len as u32 };
1225        unsafe { (*p).hash = h };
1226        unsafe { (*p).hash_next = *self.atom_hash.offset(hash_index as isize) };
1227        // Copy string
1228        let str_data = unsafe { (p as *mut u8).add(std::mem::size_of::<JSString>()) };
1229        for i in 0..len {
1230            unsafe { *str_data.add(i) = *name.add(i) };
1231        }
1232        let new_atom = (self.atom_count + 1) as u32;
1233        unsafe { *self.atom_array.offset(self.atom_count as isize) = p };
1234        unsafe { *self.atom_hash.offset(hash_index as isize) = new_atom };
1235        self.atom_count += 1;
1236        new_atom
1237    }
1238}