Skip to main content

pipa/object/
object.rs

1use super::shape::{Shape, ShapeCache};
2use crate::runtime::atom::Atom;
3use crate::util::FxHashMap;
4use crate::value::JSValue;
5use std::mem::MaybeUninit;
6
7pub struct GeneratorState {
8    pub bytecode: Box<crate::compiler::opcode::Bytecode>,
9
10    pub snapshot: Vec<JSValue>,
11
12    pub pc: usize,
13
14    pub done: bool,
15}
16use std::ptr::NonNull;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum TypedArrayKind {
20    Int8,
21    Uint8,
22    Uint8Clamped,
23    Int16,
24    Uint16,
25    Int32,
26    Uint32,
27    Float32,
28    Float64,
29    BigInt64,
30    BigUint64,
31}
32
33impl TypedArrayKind {
34    pub fn bytes_per_element(&self) -> usize {
35        match self {
36            TypedArrayKind::Int8 | TypedArrayKind::Uint8 | TypedArrayKind::Uint8Clamped => 1,
37            TypedArrayKind::Int16 | TypedArrayKind::Uint16 => 2,
38            TypedArrayKind::Int32 | TypedArrayKind::Uint32 | TypedArrayKind::Float32 => 4,
39            TypedArrayKind::Float64 | TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => 8,
40        }
41    }
42
43    pub fn name(&self) -> &'static str {
44        match self {
45            TypedArrayKind::Int8 => "Int8Array",
46            TypedArrayKind::Uint8 => "Uint8Array",
47            TypedArrayKind::Uint8Clamped => "Uint8ClampedArray",
48            TypedArrayKind::Int16 => "Int16Array",
49            TypedArrayKind::Uint16 => "Uint16Array",
50            TypedArrayKind::Int32 => "Int32Array",
51            TypedArrayKind::Uint32 => "Uint32Array",
52            TypedArrayKind::Float32 => "Float32Array",
53            TypedArrayKind::Float64 => "Float64Array",
54            TypedArrayKind::BigInt64 => "BigInt64Array",
55            TypedArrayKind::BigUint64 => "BigUint64Array",
56        }
57    }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum ObjectType {
62    Ordinary,
63    Array,
64    Function,
65    NativeFunction,
66    Error,
67    Date,
68    RegExp,
69    Promise,
70    Proxy,
71    BigInt,
72    Boolean,
73    ArrayBuffer,
74    TypedArray,
75    DataView,
76    MappedArguments,
77}
78
79#[derive(Clone, Debug)]
80pub struct PropertyDescriptor {
81    pub value: Option<JSValue>,
82    pub writable: bool,
83    pub enumerable: bool,
84    pub configurable: bool,
85    pub get: Option<JSValue>,
86    pub set: Option<JSValue>,
87}
88
89impl PropertyDescriptor {
90    pub fn new_data(value: JSValue) -> Self {
91        PropertyDescriptor {
92            value: Some(value),
93            writable: true,
94            enumerable: true,
95            configurable: true,
96            get: None,
97            set: None,
98        }
99    }
100
101    pub fn new_accessor(get: Option<JSValue>, set: Option<JSValue>) -> Self {
102        PropertyDescriptor {
103            value: None,
104            writable: false,
105            enumerable: true,
106            configurable: true,
107            get,
108            set,
109        }
110    }
111
112    pub fn is_accessor(&self) -> bool {
113        self.get.is_some() || self.set.is_some()
114    }
115
116    pub fn is_data_descriptor(&self) -> bool {
117        self.value.is_some() || (!self.is_accessor() && (self.writable || !self.configurable))
118    }
119
120    pub fn default() -> Self {
121        PropertyDescriptor {
122            value: None,
123            writable: false,
124            enumerable: false,
125            configurable: false,
126            get: None,
127            set: None,
128        }
129    }
130}
131
132const FLAG_EXTENSIBLE: u8 = 1 << 0;
133const FLAG_SEALED: u8 = 1 << 1;
134const FLAG_FROZEN: u8 = 1 << 2;
135const FLAG_IS_GENERATOR: u8 = 1 << 3;
136
137const FLAG_DENSE_ARRAY: u8 = 1 << 4;
138
139const FLAG_HAS_DELETED_PROPS: u8 = 1 << 5;
140const FLAG_IS_RAW_JSON: u8 = 1 << 6;
141
142pub const ATTR_WRITABLE: u8 = 1 << 0;
143pub const ATTR_ENUMERABLE: u8 = 1 << 1;
144pub const ATTR_CONFIGURABLE: u8 = 1 << 2;
145const ATTR_DELETED: u8 = 1 << 7;
146const ATTR_DEFAULT: u8 = ATTR_WRITABLE | ATTR_ENUMERABLE | ATTR_CONFIGURABLE;
147
148#[derive(Clone, Debug)]
149pub struct AccessorEntry {
150    pub get: Option<JSValue>,
151    pub set: Option<JSValue>,
152    pub enumerable: bool,
153    pub configurable: bool,
154}
155
156#[derive(Clone)]
157pub struct PropSlot {
158    pub value: JSValue,
159    pub atom: Atom,
160    pub attrs: u8,
161    _pad: [u8; 3],
162}
163
164impl PropSlot {
165    fn new(atom: Atom, value: JSValue, attrs: u8) -> Self {
166        PropSlot {
167            atom,
168            value,
169            attrs,
170            _pad: [0; 3],
171        }
172    }
173}
174
175pub const INLINE_PROPS: usize = 6;
176
177pub struct SmallPropVec {
178    inline: [MaybeUninit<PropSlot>; INLINE_PROPS],
179    len: u8,
180    heap: Option<Vec<PropSlot>>,
181}
182
183impl SmallPropVec {
184    #[inline(always)]
185    pub fn new() -> Self {
186        SmallPropVec {
187            inline: unsafe { MaybeUninit::uninit().assume_init() },
188            len: 0,
189            heap: None,
190        }
191    }
192
193    #[inline(always)]
194    pub fn len(&self) -> usize {
195        self.len as usize + self.heap.as_ref().map_or(0, |v| v.len())
196    }
197
198    #[inline(always)]
199    pub fn is_empty(&self) -> bool {
200        self.len() == 0
201    }
202
203    #[inline(always)]
204    pub fn push(&mut self, slot: PropSlot) {
205        if (self.len as usize) < INLINE_PROPS {
206            self.inline[self.len as usize].write(slot);
207            self.len += 1;
208        } else if let Some(ref mut v) = self.heap {
209            v.push(slot);
210        } else {
211            let mut v: Vec<PropSlot> = Vec::with_capacity(4);
212            v.push(slot);
213            self.heap = Some(v);
214        }
215    }
216
217    #[inline(always)]
218    pub fn get(&self, idx: usize) -> Option<&PropSlot> {
219        if idx < self.len as usize {
220            Some(unsafe { self.inline[idx].assume_init_ref() })
221        } else if let Some(ref v) = self.heap {
222            v.get(idx - INLINE_PROPS)
223        } else {
224            None
225        }
226    }
227
228    #[inline(always)]
229    pub fn get_mut(&mut self, idx: usize) -> Option<&mut PropSlot> {
230        if idx < self.len as usize {
231            Some(unsafe { self.inline[idx].assume_init_mut() })
232        } else if let Some(ref mut v) = self.heap {
233            v.get_mut(idx - INLINE_PROPS)
234        } else {
235            None
236        }
237    }
238
239    #[inline(always)]
240    pub fn iter(&self) -> SmallPropIter<'_> {
241        SmallPropIter { vec: self, idx: 0 }
242    }
243
244    #[inline(always)]
245    pub fn iter_mut(&mut self) -> SmallPropIterMut<'_> {
246        let len = self.len();
247        SmallPropIterMut {
248            vec: self,
249            idx: 0,
250            len,
251        }
252    }
253
254    pub fn retain<F: FnMut(&mut PropSlot) -> bool>(&mut self, mut f: F) {
255        let mut write = 0usize;
256        let read_len = self.len as usize;
257        for read in 0..read_len {
258            let keep = unsafe { f(self.inline[read].assume_init_mut()) };
259            if keep {
260                if write != read {
261                    let val = unsafe { self.inline[read].assume_init_read() };
262                    self.inline[write].write(val);
263                }
264                write += 1;
265            }
266        }
267        self.len = write as u8;
268
269        if let Some(ref mut v) = self.heap {
270            v.retain_mut(|s| f(s));
271        }
272    }
273
274    pub fn truncate(&mut self, new_len: usize) {
275        if new_len <= self.len as usize {
276            self.len = new_len as u8;
277            self.heap = None;
278        } else if let Some(ref mut v) = self.heap {
279            let heap_new = new_len - INLINE_PROPS;
280            v.truncate(heap_new);
281            if v.is_empty() {
282                self.heap = None;
283            }
284        }
285    }
286
287    pub fn clear(&mut self) {
288        self.len = 0;
289        self.heap = None;
290    }
291
292    #[inline(always)]
293    pub fn capacity(&self) -> usize {
294        INLINE_PROPS + self.heap.as_ref().map_or(0, |v| v.capacity())
295    }
296
297    pub fn drain_to_vec(&mut self) -> Vec<PropSlot> {
298        let total = self.len();
299        let mut out = Vec::with_capacity(total);
300        for i in 0..self.len as usize {
301            out.push(unsafe { self.inline[i].assume_init_read() });
302        }
303        self.len = 0;
304        if let Some(mut v) = self.heap.take() {
305            out.append(&mut v);
306        }
307        out
308    }
309
310    pub fn from_vec(v: Vec<PropSlot>) -> Self {
311        if v.len() <= INLINE_PROPS {
312            let mut s = SmallPropVec::new();
313            for slot in v {
314                s.push(slot);
315            }
316            s
317        } else {
318            let mut s = SmallPropVec::new();
319            let mut it = v.into_iter();
320            for i in 0..INLINE_PROPS {
321                s.inline[i].write(it.next().unwrap());
322            }
323            s.len = INLINE_PROPS as u8;
324            s.heap = Some(it.collect());
325            s
326        }
327    }
328}
329
330impl Default for SmallPropVec {
331    fn default() -> Self {
332        Self::new()
333    }
334}
335
336impl Clone for SmallPropVec {
337    fn clone(&self) -> Self {
338        let mut new = SmallPropVec::new();
339        new.len = self.len;
340        for i in 0..self.len as usize {
341            unsafe {
342                new.inline[i].write(self.inline[i].assume_init_ref().clone());
343            }
344        }
345        new.heap = self.heap.clone();
346        new
347    }
348}
349
350pub struct SmallPropIter<'a> {
351    vec: &'a SmallPropVec,
352    idx: usize,
353}
354
355impl<'a> Iterator for SmallPropIter<'a> {
356    type Item = &'a PropSlot;
357    fn next(&mut self) -> Option<Self::Item> {
358        let s = self.vec.get(self.idx)?;
359        self.idx += 1;
360        Some(s)
361    }
362    fn size_hint(&self) -> (usize, Option<usize>) {
363        let rem = self.vec.len().saturating_sub(self.idx);
364        (rem, Some(rem))
365    }
366}
367
368pub struct SmallPropIterMut<'a> {
369    vec: &'a mut SmallPropVec,
370    idx: usize,
371    len: usize,
372}
373
374impl<'a> Iterator for SmallPropIterMut<'a> {
375    type Item = &'a mut PropSlot;
376    fn next(&mut self) -> Option<Self::Item> {
377        if self.idx >= self.len {
378            return None;
379        }
380        let idx = self.idx;
381        self.idx += 1;
382
383        unsafe {
384            let ptr = self.vec.get_mut(idx)? as *mut PropSlot;
385            Some(&mut *ptr)
386        }
387    }
388}
389
390impl std::ops::Index<usize> for SmallPropVec {
391    type Output = PropSlot;
392    #[inline(always)]
393    fn index(&self, idx: usize) -> &PropSlot {
394        self.get(idx).expect("SmallPropVec index out of bounds")
395    }
396}
397
398impl std::ops::IndexMut<usize> for SmallPropVec {
399    #[inline(always)]
400    fn index_mut(&mut self, idx: usize) -> &mut PropSlot {
401        self.get_mut(idx).expect("SmallPropVec index out of bounds")
402    }
403}
404impl<'a> IntoIterator for &'a SmallPropVec {
405    type Item = &'a PropSlot;
406    type IntoIter = SmallPropIter<'a>;
407    fn into_iter(self) -> SmallPropIter<'a> {
408        self.iter()
409    }
410}
411
412impl<'a> IntoIterator for &'a mut SmallPropVec {
413    type Item = &'a mut PropSlot;
414    type IntoIter = SmallPropIterMut<'a>;
415    fn into_iter(self) -> SmallPropIterMut<'a> {
416        self.iter_mut()
417    }
418}
419
420pub struct ObjectExtra {
421    pub bigint_value: i128,
422    pub private_fields: Option<Box<FxHashMap<Atom, JSValue>>>,
423    pub private_accessors: Option<Box<FxHashMap<Atom, AccessorEntry>>>,
424    pub array_elements: Option<Box<Vec<JSValue>>>,
425
426    pub property_map: Option<Box<FxHashMap<Atom, u32>>>,
427    pub accessors: Option<Box<FxHashMap<Atom, AccessorEntry>>>,
428    pub property_order: Option<Box<Vec<Atom>>>,
429
430    pub compiled_regex: Option<Box<crate::regexp::Regex>>,
431
432    pub array_buffer_data: Option<usize>,
433
434    pub typed_array_kind: Option<TypedArrayKind>,
435
436    pub generator_state: Option<Box<GeneratorState>>,
437
438    pub mapped_args_frame_index: usize,
439    pub mapped_args_param_count: u32,
440}
441
442pub struct JSObject {
443    obj_type: ObjectType,
444    pub prototype: Option<*mut JSObject>,
445
446    pub gc_slot: u32,
447    flags: u8,
448    shape: Option<NonNull<Shape>>,
449
450    pub shape_id_cache: usize,
451
452    props: SmallPropVec,
453
454    extra: Option<Box<ObjectExtra>>,
455}
456
457const INLINE_THRESHOLD: usize = 16;
458
459fn attrs_from_bools(writable: bool, enumerable: bool, configurable: bool) -> u8 {
460    let mut a = 0u8;
461    if writable {
462        a |= ATTR_WRITABLE;
463    }
464    if enumerable {
465        a |= ATTR_ENUMERABLE;
466    }
467    if configurable {
468        a |= ATTR_CONFIGURABLE;
469    }
470    a
471}
472
473impl JSObject {
474    pub fn ensure_extra(&mut self) -> &mut ObjectExtra {
475        self.extra.get_or_insert_with(|| {
476            Box::new(ObjectExtra {
477                bigint_value: 0,
478                private_fields: None,
479                array_elements: None,
480                private_accessors: None,
481                property_map: None,
482                accessors: None,
483                property_order: None,
484                compiled_regex: None,
485                array_buffer_data: None,
486                typed_array_kind: None,
487                generator_state: None,
488                mapped_args_frame_index: 0,
489                mapped_args_param_count: 0,
490            })
491        })
492    }
493
494    pub fn new_typed(obj_type: ObjectType) -> Self {
495        JSObject {
496            obj_type,
497            prototype: None,
498            gc_slot: u32::MAX,
499            flags: FLAG_EXTENSIBLE,
500            shape: None,
501            shape_id_cache: usize::MAX,
502            props: SmallPropVec::new(),
503            extra: None,
504        }
505    }
506
507    #[inline]
508    pub fn new_typed_from_pool(obj_type: ObjectType, props: SmallPropVec) -> Self {
509        debug_assert!(props.is_empty());
510        JSObject {
511            obj_type,
512            prototype: None,
513            gc_slot: u32::MAX,
514            flags: FLAG_EXTENSIBLE,
515            shape: None,
516            shape_id_cache: usize::MAX,
517            props,
518            extra: None,
519        }
520    }
521
522    pub fn new() -> Self {
523        Self::new_typed(ObjectType::Ordinary)
524    }
525    pub fn new_global() -> Self {
526        let mut obj = Self::new();
527        obj.prototype = None;
528        obj
529    }
530    pub fn new_array() -> Self {
531        Self::new_typed(ObjectType::Array)
532    }
533    pub fn new_function() -> Self {
534        Self::new_typed(ObjectType::Function)
535    }
536    pub fn new_bigint() -> Self {
537        Self::new_typed(ObjectType::BigInt)
538    }
539    pub fn new_regexp() -> Self {
540        Self::new_typed(ObjectType::RegExp)
541    }
542    pub fn new_promise() -> Self {
543        Self::new_typed(ObjectType::Promise)
544    }
545    pub fn new_error() -> Self {
546        Self::new_typed(ObjectType::Error)
547    }
548
549    #[inline(always)]
550    pub fn obj_type(&self) -> ObjectType {
551        self.obj_type
552    }
553
554    #[inline(always)]
555    pub fn set_obj_type(&mut self, t: ObjectType) {
556        self.obj_type = t;
557    }
558
559    #[inline(always)]
560    pub fn is_array(&self) -> bool {
561        self.obj_type == ObjectType::Array
562    }
563
564    #[inline(always)]
565    pub fn is_promise(&self) -> bool {
566        self.obj_type == ObjectType::Promise
567    }
568
569    #[inline(always)]
570    pub fn is_dense_array(&self) -> bool {
571        self.flags & FLAG_DENSE_ARRAY != 0
572    }
573
574    #[inline(always)]
575    pub fn set_dense_array_flag(&mut self) {
576        self.flags |= FLAG_DENSE_ARRAY;
577    }
578
579    #[inline(always)]
580    pub fn clear_dense_array_flag(&mut self) {
581        self.flags &= !FLAG_DENSE_ARRAY;
582    }
583
584    #[inline(always)]
585    pub fn set_prototype_raw(&mut self, ptr: *mut JSObject) {
586        self.prototype = if ptr.is_null() { None } else { Some(ptr) }
587    }
588
589    #[inline(always)]
590    pub fn prototype_ptr(&self) -> Option<*mut JSObject> {
591        self.prototype
592    }
593
594    #[inline(always)]
595    pub fn extensible(&self) -> bool {
596        self.flags & FLAG_EXTENSIBLE != 0
597    }
598    #[inline(always)]
599    pub fn is_raw_json(&self) -> bool {
600        self.flags & FLAG_IS_RAW_JSON != 0
601    }
602    #[inline(always)]
603    pub fn set_raw_json(&mut self) {
604        self.flags |= FLAG_IS_RAW_JSON;
605    }
606    #[inline(always)]
607    pub fn set_extensible(&mut self, val: bool) {
608        if val {
609            self.flags |= FLAG_EXTENSIBLE;
610        } else {
611            self.flags &= !FLAG_EXTENSIBLE;
612        }
613    }
614    #[inline(always)]
615    pub fn sealed(&self) -> bool {
616        self.flags & FLAG_SEALED != 0
617    }
618    #[inline(always)]
619    pub fn set_sealed(&mut self, val: bool) {
620        if val {
621            self.flags |= FLAG_SEALED;
622        } else {
623            self.flags &= !FLAG_SEALED;
624        }
625    }
626    #[inline(always)]
627    pub fn frozen(&self) -> bool {
628        self.flags & FLAG_FROZEN != 0
629    }
630    #[inline(always)]
631    pub fn set_frozen(&mut self, val: bool) {
632        if val {
633            self.flags |= FLAG_FROZEN;
634        } else {
635            self.flags &= !FLAG_FROZEN;
636        }
637    }
638    #[inline(always)]
639    pub fn is_mapped_arguments(&self) -> bool {
640        self.obj_type == ObjectType::MappedArguments
641    }
642
643    pub fn mapped_args_frame_index(&self) -> usize {
644        self.extra.as_ref().map_or(0, |e| e.mapped_args_frame_index)
645    }
646
647    pub fn mapped_args_param_count(&self) -> u32 {
648        self.extra.as_ref().map_or(0, |e| e.mapped_args_param_count)
649    }
650
651    pub fn is_generator(&self) -> bool {
652        self.flags & FLAG_IS_GENERATOR != 0
653    }
654    #[inline(always)]
655    pub fn set_is_generator(&mut self, val: bool) {
656        if val {
657            self.flags |= FLAG_IS_GENERATOR;
658        } else {
659            self.flags &= !FLAG_IS_GENERATOR;
660        }
661    }
662
663    #[inline(always)]
664    pub fn get_bigint_value(&self) -> i128 {
665        self.extra.as_ref().map_or(0, |e| e.bigint_value)
666    }
667    #[inline(always)]
668    pub fn set_bigint_value(&mut self, val: i128) {
669        self.ensure_extra().bigint_value = val;
670    }
671
672    #[inline(always)]
673    pub fn ensure_elements(&mut self) -> &mut Vec<JSValue> {
674        self.ensure_extra()
675            .array_elements
676            .get_or_insert_with(|| Box::new(Vec::new()))
677    }
678
679    pub fn set_array_elements(&mut self, elements: Vec<JSValue>) {
680        self.ensure_extra().array_elements = Some(Box::new(elements));
681    }
682
683    #[inline]
684    pub fn get_compiled_regex(&self) -> Option<&crate::regexp::Regex> {
685        self.extra
686            .as_ref()
687            .and_then(|e| e.compiled_regex.as_deref())
688    }
689
690    pub fn set_compiled_regex(&mut self, re: crate::regexp::Regex) {
691        self.ensure_extra().compiled_regex = Some(Box::new(re));
692    }
693
694    #[inline]
695    pub fn get_generator_state(&self) -> Option<&GeneratorState> {
696        self.extra
697            .as_ref()
698            .and_then(|e| e.generator_state.as_deref())
699    }
700
701    #[inline]
702    pub fn get_generator_state_mut(&mut self) -> Option<&mut GeneratorState> {
703        self.extra
704            .as_mut()
705            .and_then(|e| e.generator_state.as_deref_mut())
706    }
707
708    #[inline]
709    pub fn set_generator_state(&mut self, state: GeneratorState) {
710        self.ensure_extra().generator_state = Some(Box::new(state));
711    }
712
713    #[inline]
714    pub fn take_generator_state(&mut self) -> Option<Box<GeneratorState>> {
715        self.extra.as_mut().and_then(|e| e.generator_state.take())
716    }
717
718    #[inline]
719    pub fn get_array_buffer_data(&self) -> Option<usize> {
720        self.extra.as_ref().and_then(|e| e.array_buffer_data)
721    }
722
723    pub fn set_array_buffer_data(&mut self, ptr: usize) {
724        self.ensure_extra().array_buffer_data = Some(ptr);
725    }
726
727    #[inline]
728    pub fn get_typed_array_kind(&self) -> Option<TypedArrayKind> {
729        self.extra.as_ref().and_then(|e| e.typed_array_kind)
730    }
731
732    pub fn set_typed_array_kind(&mut self, kind: TypedArrayKind) {
733        self.ensure_extra().typed_array_kind = Some(kind);
734    }
735
736    #[inline]
737    pub fn array_elements_len(&self) -> usize {
738        self.extra
739            .as_ref()
740            .and_then(|e| e.array_elements.as_ref())
741            .map_or(0, |v| v.len())
742    }
743
744    #[inline]
745    pub fn get_array_elements(&self) -> Option<&Vec<JSValue>> {
746        self.extra
747            .as_ref()
748            .and_then(|e| e.array_elements.as_deref())
749    }
750
751    pub fn for_each_array_element(&self, mut f: impl FnMut(&JSValue)) {
752        if let Some(ref extra) = self.extra {
753            if let Some(ref elements) = extra.array_elements {
754                for value in elements.iter() {
755                    f(value);
756                }
757            }
758        }
759    }
760
761    #[inline]
762    pub fn get_private_field(&self, atom: Atom) -> Option<JSValue> {
763        self.extra
764            .as_ref()
765            .and_then(|e| e.private_fields.as_ref())
766            .and_then(|fields| fields.get(&atom).cloned())
767    }
768
769    #[inline]
770    pub fn has_private_field(&self, atom: Atom) -> bool {
771        self.extra
772            .as_ref()
773            .and_then(|e| e.private_fields.as_ref())
774            .map_or(false, |fields| fields.contains_key(&atom))
775    }
776
777    pub fn set_private_field(&mut self, atom: Atom, value: JSValue) {
778        self.ensure_extra()
779            .private_fields
780            .get_or_insert_with(|| Box::new(FxHashMap::default()))
781            .insert(atom, value);
782    }
783
784    pub fn for_each_private_field(&self, mut f: impl FnMut(Atom, JSValue)) {
785        if let Some(ref extra) = self.extra {
786            if let Some(ref fields) = extra.private_fields {
787                for (&atom, value) in fields.iter() {
788                    f(atom, value.clone());
789                }
790            }
791        }
792    }
793
794    #[inline(always)]
795    fn shape_offset(&self, prop: Atom) -> Option<u32> {
796        self.shape
797            .and_then(|ptr| unsafe { (*ptr.as_ptr()).get_offset(prop) })
798    }
799
800    pub fn ensure_shape(&mut self, cache: &mut ShapeCache) -> NonNull<Shape> {
801        self.shape.unwrap_or_else(|| {
802            let r = cache.root_shape();
803            self.shape = Some(r);
804            self.shape_id_cache = unsafe { (*r.as_ptr()).id.0 };
805            r
806        })
807    }
808
809    #[inline]
810    fn find_offset_linear(&self, prop: Atom) -> Option<usize> {
811        let len = self.props.len();
812
813        if len > 0 && self.props[len - 1].atom == prop {
814            if self.props[len - 1].attrs != ATTR_DELETED {
815                return Some(len - 1);
816            }
817        }
818        for i in 0..len.saturating_sub(1) {
819            if self.props[i].atom == prop {
820                if self.props[i].attrs == ATTR_DELETED {
821                    continue;
822                }
823                return Some(i);
824            }
825        }
826        None
827    }
828
829    #[inline(always)]
830    pub fn find_offset(&self, prop: Atom) -> Option<usize> {
831        if let Some(offset) = self.shape_offset(prop) {
832            let off = offset as usize;
833            if off < self.props.len() && self.props[off].attrs == ATTR_DELETED {
834            } else {
835                return Some(off);
836            }
837        }
838
839        if let Some(ref extra) = self.extra {
840            if let Some(ref map) = extra.property_map {
841                if let Some(&offset) = map.get(&prop) {
842                    let off = offset as usize;
843                    if off < self.props.len() && self.props[off].attrs == ATTR_DELETED {
844                    } else {
845                        return Some(off);
846                    }
847                }
848            }
849        }
850        self.find_offset_linear(prop)
851    }
852
853    pub fn is_property_writable(&self, prop: Atom) -> bool {
854        if self.frozen() {
855            return false;
856        }
857        if let Some(offset) = self.find_offset(prop) {
858            if offset < self.props.len() {
859                return self.props[offset].attrs & ATTR_WRITABLE != 0;
860            }
861        }
862        true
863    }
864
865    #[inline(always)]
866    pub fn is_prop_writable_at(&self, offset: Option<usize>) -> bool {
867        if let Some(offset) = offset {
868            if offset < self.props.len() {
869                return self.props[offset].attrs & ATTR_WRITABLE != 0;
870            }
871        }
872        true
873    }
874
875    fn maybe_build_property_map(&mut self) {
876        let needs_map = self
877            .extra
878            .as_ref()
879            .map_or(true, |e| e.property_map.is_none());
880        if needs_map && self.props.len() > INLINE_THRESHOLD {
881            let mut map =
882                FxHashMap::with_capacity_and_hasher(self.props.len() * 2, Default::default());
883            for (i, slot) in self.props.iter().enumerate() {
884                map.insert(slot.atom, i as u32);
885            }
886            self.ensure_extra().property_map = Some(Box::new(map));
887        }
888    }
889
890    #[inline(always)]
891    pub fn get_by_offset(&self, offset: usize) -> Option<JSValue> {
892        if offset < self.props.len as usize {
893            let slot = unsafe { self.props.inline[offset].assume_init_ref() };
894            if slot.attrs != ATTR_DELETED {
895                return Some(slot.value);
896            }
897            return None;
898        }
899
900        if let Some(ref v) = self.props.heap {
901            let h_idx = offset - INLINE_PROPS;
902            if let Some(slot) = v.get(h_idx) {
903                if slot.attrs != ATTR_DELETED {
904                    return Some(slot.value);
905                }
906            }
907        }
908        None
909    }
910
911    #[inline(always)]
912    pub fn set_by_offset_fast(&mut self, offset: usize, value: JSValue) {
913        unsafe { self.props.inline[offset].assume_init_mut() }.value = value;
914    }
915
916    #[inline(always)]
917    pub fn get_by_offset_fast(&self, offset: usize) -> JSValue {
918        debug_assert!(
919            offset < INLINE_PROPS,
920            "offset out of inline range in get_by_offset_fast"
921        );
922        debug_assert!(
923            unsafe { self.props.inline[offset].assume_init_ref() }.attrs != ATTR_DELETED,
924            "deleted prop in get_by_offset_fast"
925        );
926        unsafe { self.props.inline[offset].assume_init_ref() }.value
927    }
928
929    #[inline(always)]
930    pub fn has_no_deleted_props(&self) -> bool {
931        self.flags & FLAG_HAS_DELETED_PROPS == 0
932    }
933
934    #[inline(always)]
935    pub fn shape_ptr(&self) -> Option<std::ptr::NonNull<super::shape::Shape>> {
936        self.shape
937    }
938
939    #[inline(always)]
940    pub fn set_by_offset(&mut self, offset: usize, value: JSValue) -> bool {
941        if offset < self.props.len as usize {
942            let slot = unsafe { self.props.inline[offset].assume_init_mut() };
943            if slot.attrs == ATTR_DELETED {
944                return false;
945            }
946            if slot.attrs & ATTR_WRITABLE == 0 {
947                return false;
948            }
949            slot.value = value;
950            return true;
951        }
952
953        if let Some(ref mut v) = self.props.heap {
954            let h_idx = offset - INLINE_PROPS;
955            if let Some(slot) = v.get_mut(h_idx) {
956                if slot.attrs == ATTR_DELETED {
957                    return false;
958                }
959                if slot.attrs & ATTR_WRITABLE == 0 {
960                    return false;
961                }
962                slot.value = value;
963                return true;
964            }
965        }
966        false
967    }
968
969    #[inline(always)]
970    pub fn push_prop_with_shape(
971        &mut self,
972        offset: usize,
973        atom: Atom,
974        value: JSValue,
975        new_shape: std::ptr::NonNull<super::shape::Shape>,
976    ) {
977        debug_assert_eq!(offset, self.props.len());
978        self.props.push(PropSlot::new(atom, value, ATTR_DEFAULT));
979        self.shape = Some(new_shape);
980        self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
981        self.update_property_map(atom);
982        self.track_property_order(atom);
983    }
984
985    #[inline(always)]
986
987    pub fn fast_init_from_simple_constructor<I>(
988        &mut self,
989        props: I,
990        final_shape: std::ptr::NonNull<super::shape::Shape>,
991    ) where
992        I: Iterator<Item = (Atom, crate::value::JSValue)>,
993    {
994        for (atom, value) in props {
995            self.props.push(PropSlot::new(atom, value, ATTR_DEFAULT));
996        }
997        self.shape = Some(final_shape);
998        self.shape_id_cache = unsafe { (*final_shape.as_ptr()).id.0 };
999    }
1000
1001    pub fn batch_push_props(
1002        &mut self,
1003        atoms: &[Atom],
1004        values: &[JSValue],
1005        new_shape: std::ptr::NonNull<super::shape::Shape>,
1006    ) {
1007        for (&atom, &value) in atoms.iter().zip(values.iter()) {
1008            self.props.push(PropSlot::new(atom, value, ATTR_DEFAULT));
1009        }
1010        self.shape = Some(new_shape);
1011        self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1012        if !atoms.is_empty() {
1013            self.maybe_build_property_map();
1014            if let Some(ref mut extra) = self.extra {
1015                if let Some(ref mut map) = extra.property_map {
1016                    for (i, &atom) in atoms.iter().enumerate() {
1017                        map.insert(atom, (self.props.len() - atoms.len() + i) as u32);
1018                    }
1019                }
1020            }
1021        }
1022    }
1023
1024    #[inline(always)]
1025    pub fn get_own_accessor_value(&self, prop: Atom) -> Option<JSValue> {
1026        if let Some(ref extra) = self.extra {
1027            if let Some(ref accs) = extra.accessors {
1028                if let Some(entry) = accs.get(&prop) {
1029                    return entry.get;
1030                }
1031            }
1032        }
1033        None
1034    }
1035
1036    pub fn get_own_accessor_entry(&self, prop: Atom) -> Option<&AccessorEntry> {
1037        self.extra.as_ref()?.accessors.as_ref()?.get(&prop)
1038    }
1039
1040    pub fn define_accessor(
1041        &mut self,
1042        prop: Atom,
1043        getter: Option<JSValue>,
1044        setter: Option<JSValue>,
1045    ) {
1046        let extra = self.ensure_extra();
1047        extra
1048            .accessors
1049            .get_or_insert_with(|| Box::new(FxHashMap::default()))
1050            .insert(
1051                prop,
1052                AccessorEntry {
1053                    get: getter,
1054                    set: setter,
1055                    enumerable: true,
1056                    configurable: true,
1057                },
1058            );
1059        let order = extra
1060            .property_order
1061            .get_or_insert_with(|| Box::new(Vec::new()));
1062        if let Some(pos) = order.iter().position(|a| *a == prop) {
1063            order.remove(pos);
1064        }
1065        order.push(prop);
1066    }
1067
1068    pub fn define_non_enumerable_accessor(
1069        &mut self,
1070        prop: Atom,
1071        getter: Option<JSValue>,
1072        setter: Option<JSValue>,
1073    ) {
1074        self.ensure_extra()
1075            .accessors
1076            .get_or_insert_with(|| Box::new(FxHashMap::default()))
1077            .insert(
1078                prop,
1079                AccessorEntry {
1080                    get: getter,
1081                    set: setter,
1082                    enumerable: false,
1083                    configurable: false,
1084                },
1085            );
1086    }
1087
1088    pub fn get_own_private_accessor_entry(&self, prop: Atom) -> Option<&AccessorEntry> {
1089        self.extra.as_ref()?.private_accessors.as_ref()?.get(&prop)
1090    }
1091
1092    pub fn define_private_accessor(
1093        &mut self,
1094        prop: Atom,
1095        getter: Option<JSValue>,
1096        setter: Option<JSValue>,
1097    ) {
1098        let existing = self
1099            .extra
1100            .as_ref()
1101            .and_then(|e| e.private_accessors.as_ref())
1102            .and_then(|a| a.get(&prop))
1103            .cloned();
1104        let (final_getter, final_setter) = match existing {
1105            Some(e) => (getter.or(e.get), setter.or(e.set)),
1106            None => (getter, setter),
1107        };
1108        self.ensure_extra()
1109            .private_accessors
1110            .get_or_insert_with(|| Box::new(FxHashMap::default()))
1111            .insert(
1112                prop,
1113                AccessorEntry {
1114                    get: final_getter,
1115                    set: final_setter,
1116                    enumerable: false,
1117                    configurable: false,
1118                },
1119            );
1120    }
1121
1122    #[inline(always)]
1123    pub fn get_own_value(&self, prop: Atom) -> Option<JSValue> {
1124        if let Some(offset) = self.find_offset(prop) {
1125            if offset < self.props.len() {
1126                return Some(self.props[offset].value);
1127            }
1128        }
1129        self.get_own_accessor_value(prop)
1130    }
1131
1132    #[inline(always)]
1133    pub fn get(&self, prop: Atom) -> Option<JSValue> {
1134        if let Some(offset) = self.find_offset(prop) {
1135            if offset < self.props.len() {
1136                return Some(self.props[offset].value);
1137            }
1138        }
1139        if let Some(ref extra) = self.extra {
1140            if let Some(ref accs) = extra.accessors {
1141                if let Some(entry) = accs.get(&prop) {
1142                    return entry.get;
1143                }
1144            }
1145        }
1146
1147        let mut current = self.prototype;
1148        let mut depth = 0u32;
1149        while let Some(proto_ptr) = current {
1150            if proto_ptr.is_null() || depth > 1000 {
1151                break;
1152            }
1153            depth += 1;
1154            unsafe {
1155                let proto = &*proto_ptr;
1156                if let Some(offset) = proto.find_offset(prop) {
1157                    if offset < proto.props.len() {
1158                        return Some(proto.props[offset].value);
1159                    }
1160                }
1161                if let Some(ref extra) = proto.extra {
1162                    if let Some(ref accs) = extra.accessors {
1163                        if let Some(entry) = accs.get(&prop) {
1164                            return entry.get;
1165                        }
1166                    }
1167                }
1168                current = proto.prototype;
1169            }
1170        }
1171        None
1172    }
1173
1174    #[inline(always)]
1175    pub fn set(&mut self, prop: Atom, value: JSValue) {
1176        if let Some(offset) = self.find_offset(prop) {
1177            if offset < self.props.len() {
1178                if self.props[offset].attrs & ATTR_WRITABLE == 0 {
1179                    return;
1180                }
1181                self.props[offset].value = value;
1182                return;
1183            }
1184        }
1185
1186        if let Some(ref extra) = self.extra {
1187            if let Some(ref accs) = extra.accessors {
1188                if accs.contains_key(&prop) {
1189                    return;
1190                }
1191            }
1192        }
1193        if !self.extensible() {
1194            return;
1195        }
1196
1197        self.props.push(PropSlot::new(prop, value, ATTR_DEFAULT));
1198        self.track_property_order(prop);
1199        self.update_property_map(prop);
1200    }
1201
1202    pub fn set_length(&mut self, prop: Atom, value: JSValue) {
1203        if self.has_own(prop) {
1204            self.set(prop, value);
1205        } else {
1206            self.props.push(PropSlot::new(prop, value, ATTR_WRITABLE));
1207            self.update_property_map(prop);
1208        }
1209    }
1210
1211    pub fn set_length_ic(
1212        &mut self,
1213        prop: Atom,
1214        value: JSValue,
1215        cache: &mut crate::object::shape::ShapeCache,
1216    ) {
1217        if let Some(shape) = self.shape {
1218            if let Some(offset) = unsafe { (*shape.as_ptr()).get_offset(prop) } {
1219                let off = offset as usize;
1220                if off < self.props.len() {
1221                    self.props[off].value = value;
1222                    return;
1223                }
1224            }
1225
1226            let new_shape = cache.transition(shape, prop);
1227            self.shape = Some(new_shape);
1228            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1229            self.props.push(PropSlot::new(prop, value, ATTR_WRITABLE));
1230            self.update_property_map(prop);
1231        } else {
1232            self.set_length(prop, value);
1233        }
1234    }
1235
1236    pub fn set_cached_non_configurable(
1237        &mut self,
1238        prop: Atom,
1239        value: JSValue,
1240        cache: &mut ShapeCache,
1241    ) {
1242        if self.has_own(prop) {
1243            if let Some(offset) = self.find_offset(prop) {
1244                if offset < self.props.len() {
1245                    if self.props[offset].attrs & ATTR_WRITABLE == 0 {
1246                        return;
1247                    }
1248                    self.props[offset].value = value;
1249                    return;
1250                }
1251            }
1252        }
1253        let _ = cache;
1254        self.props.push(PropSlot::new(prop, value, ATTR_WRITABLE));
1255        self.update_property_map(prop);
1256    }
1257
1258    #[inline(always)]
1259    pub fn set_with_cache(&mut self, prop: Atom, value: JSValue, cache: &mut ShapeCache) {
1260        if let Some(offset) = self.find_offset(prop) {
1261            if offset < self.props.len() {
1262                if self.props[offset].attrs & ATTR_WRITABLE == 0 {
1263                    return;
1264                }
1265                self.props[offset].value = value;
1266                return;
1267            }
1268        }
1269
1270        if let Some(ref extra) = self.extra {
1271            if let Some(ref accs) = extra.accessors {
1272                if accs.contains_key(&prop) {
1273                    return;
1274                }
1275            }
1276        }
1277        if !self.extensible() {
1278            return;
1279        }
1280
1281        if self.shape.is_some() {
1282            let current = self.shape.unwrap();
1283            let new_shape = cache.transition(current, prop);
1284            self.shape = Some(new_shape);
1285            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1286        } else if self.props.is_empty() {
1287            let current = self.ensure_shape(cache);
1288            let new_shape = cache.transition(current, prop);
1289            self.shape = Some(new_shape);
1290            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1291        }
1292        self.props.push(PropSlot::new(prop, value, ATTR_DEFAULT));
1293        self.update_property_map(prop);
1294    }
1295
1296    #[inline(always)]
1297    pub fn set_cached(&mut self, prop: Atom, value: JSValue, cache: &mut ShapeCache) {
1298        if let Some(offset) = self.shape_offset(prop) {
1299            let off = offset as usize;
1300            if off < self.props.len() {
1301                if self.props[off].attrs & ATTR_WRITABLE == 0 {
1302                    return;
1303                }
1304                self.props[off].value = value;
1305                return;
1306            }
1307        } else {
1308            let found = if let Some(ref extra) = self.extra {
1309                if let Some(ref map) = extra.property_map {
1310                    map.get(&prop)
1311                        .copied()
1312                        .map(|o| o as usize)
1313                        .filter(|&o| o < self.props.len() && self.props[o].attrs != ATTR_DELETED)
1314                } else {
1315                    self.find_offset_linear(prop)
1316                }
1317            } else {
1318                self.find_offset_linear(prop)
1319            };
1320            if let Some(offset) = found {
1321                if self.props[offset].attrs & ATTR_WRITABLE == 0 {
1322                    return;
1323                }
1324                self.props[offset].value = value;
1325                return;
1326            }
1327        }
1328
1329        self.set_cached_new(prop, value, cache);
1330    }
1331
1332    pub fn set_cached_with_offset(
1333        &mut self,
1334        prop: Atom,
1335        value: JSValue,
1336        cache: &mut ShapeCache,
1337        pre_offset: Option<usize>,
1338    ) {
1339        if let Some(offset) = pre_offset {
1340            if offset < self.props.len() {
1341                if self.props[offset].attrs & ATTR_WRITABLE == 0 {
1342                    return;
1343                }
1344                self.props[offset].value = value;
1345                return;
1346            }
1347        }
1348        self.set_cached_new(prop, value, cache);
1349    }
1350
1351    fn set_cached_new(&mut self, prop: Atom, value: JSValue, cache: &mut ShapeCache) {
1352        if let Some(ref extra) = self.extra {
1353            if let Some(ref accs) = extra.accessors {
1354                if accs.contains_key(&prop) {
1355                    return;
1356                }
1357            }
1358        }
1359        if !self.extensible() {
1360            return;
1361        }
1362
1363        if self.shape.is_none() && !self.props.is_empty() {
1364        } else if self.shape.is_some() {
1365            let current = self.shape.unwrap();
1366            let shape_count = unsafe { (*current.as_ptr()).property_count } as usize;
1367            if shape_count == self.props.len() {
1368                let new_shape = cache.transition(current, prop);
1369                self.shape = Some(new_shape);
1370                self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1371            }
1372        } else {
1373            let current = self.ensure_shape(cache);
1374            let new_shape = cache.transition(current, prop);
1375            self.shape = Some(new_shape);
1376            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1377        }
1378        self.props.push(PropSlot::new(prop, value, ATTR_DEFAULT));
1379        self.track_property_order(prop);
1380        self.update_property_map(prop);
1381    }
1382
1383    pub fn set_cached_non_enumerable(
1384        &mut self,
1385        prop: Atom,
1386        value: JSValue,
1387        cache: &mut ShapeCache,
1388    ) {
1389        if let Some(offset) = self.shape_offset(prop) {
1390            let off = offset as usize;
1391            if off < self.props.len() {
1392                if self.props[off].attrs & ATTR_WRITABLE == 0 {
1393                    return;
1394                }
1395                self.props[off].value = value;
1396                return;
1397            }
1398        } else {
1399            let found = self.find_offset(prop);
1400            if let Some(offset) = found {
1401                if self.props[offset].attrs & ATTR_WRITABLE == 0 {
1402                    return;
1403                }
1404                self.props[offset].value = value;
1405                return;
1406            }
1407        }
1408
1409        if let Some(ref extra) = self.extra {
1410            if let Some(ref accs) = extra.accessors {
1411                if accs.contains_key(&prop) {
1412                    return;
1413                }
1414            }
1415        }
1416        if !self.extensible() {
1417            return;
1418        }
1419
1420        if self.shape.is_none() && !self.props.is_empty() {
1421        } else if self.shape.is_some() {
1422            let current = self.shape.unwrap();
1423            let new_shape = cache.transition(current, prop);
1424            self.shape = Some(new_shape);
1425            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1426        } else {
1427            let current = self.ensure_shape(cache);
1428            let new_shape = cache.transition(current, prop);
1429            self.shape = Some(new_shape);
1430            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1431        }
1432        self.props.push(PropSlot::new(
1433            prop,
1434            value,
1435            ATTR_WRITABLE | ATTR_CONFIGURABLE,
1436        ));
1437
1438        self.update_property_map(prop);
1439    }
1440
1441    pub fn define_cached(&mut self, prop: Atom, value: JSValue, cache: &mut ShapeCache) {
1442        if let Some(offset) = self.shape_offset(prop) {
1443            let off = offset as usize;
1444            if off < self.props.len() {
1445                self.props[off].value = value;
1446                return;
1447            }
1448        } else {
1449            let found = if let Some(ref extra) = self.extra {
1450                if let Some(ref map) = extra.property_map {
1451                    map.get(&prop)
1452                        .copied()
1453                        .map(|o| o as usize)
1454                        .filter(|&o| o < self.props.len() && self.props[o].attrs != ATTR_DELETED)
1455                } else {
1456                    self.find_offset_linear(prop)
1457                }
1458            } else {
1459                self.find_offset_linear(prop)
1460            };
1461            if let Some(offset) = found {
1462                self.props[offset].value = value;
1463                return;
1464            }
1465        }
1466
1467        if let Some(ref extra) = self.extra {
1468            if let Some(ref accs) = extra.accessors {
1469                if accs.contains_key(&prop) {
1470                    return;
1471                }
1472            }
1473        }
1474
1475        if !self.extensible() {
1476            return;
1477        }
1478
1479        if self.shape.is_none() && !self.props.is_empty() {
1480        } else if self.shape.is_some() {
1481            let current = self.shape.unwrap();
1482            let new_shape = cache.transition(current, prop);
1483            self.shape = Some(new_shape);
1484            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1485        } else {
1486            let current = self.ensure_shape(cache);
1487            let new_shape = cache.transition(current, prop);
1488            self.shape = Some(new_shape);
1489            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1490        }
1491        self.props.push(PropSlot::new(prop, value, ATTR_DEFAULT));
1492        self.update_property_map(prop);
1493    }
1494
1495    #[inline]
1496    fn update_property_map(&mut self, prop: Atom) {
1497        if let Some(ref mut extra) = self.extra {
1498            if let Some(ref mut map) = extra.property_map {
1499                map.insert(prop, (self.props.len() - 1) as u32);
1500                return;
1501            }
1502        }
1503        self.maybe_build_property_map();
1504    }
1505
1506    fn track_property_order(&mut self, prop: Atom) {
1507        let extra = self.ensure_extra();
1508        let order = extra
1509            .property_order
1510            .get_or_insert_with(|| Box::new(Vec::new()));
1511        if let Some(pos) = order.iter().position(|a| *a == prop) {
1512            order.remove(pos);
1513        }
1514        order.push(prop);
1515    }
1516
1517    pub fn get_own(&self, prop: Atom) -> Option<JSValue> {
1518        if let Some(offset) = self.find_offset(prop) {
1519            if offset < self.props.len() {
1520                return Some(self.props[offset].value);
1521            }
1522        }
1523        if let Some(ref extra) = self.extra {
1524            if let Some(ref accs) = extra.accessors {
1525                if let Some(entry) = accs.get(&prop) {
1526                    return entry.get;
1527                }
1528            }
1529        }
1530        None
1531    }
1532
1533    pub fn own_properties(&self) -> Vec<(Atom, JSValue)> {
1534        let mut result = Vec::new();
1535        let mut seen = std::collections::HashSet::new();
1536        // Use the property order vector if available, otherwise fall back to props order
1537        if let Some(ref extra) = self.extra {
1538            if let Some(ref order) = extra.property_order {
1539                for atom in order.iter() {
1540                    // Skip Symbol-keyed properties
1541                    if atom.0 & 0x40000000 != 0 {
1542                        continue;
1543                    }
1544                    // Check if it's a data property
1545                    if let Some(offset) = self.find_offset(*atom) {
1546                        if offset < self.props.len() {
1547                            let slot = &self.props[offset];
1548                            if slot.attrs == ATTR_DELETED {
1549                                continue;
1550                            }
1551                            if slot.attrs & ATTR_ENUMERABLE != 0 && seen.insert(slot.atom) {
1552                                result.push((slot.atom, slot.value));
1553                            }
1554                            continue;
1555                        }
1556                    }
1557                    // Check if it's an accessor property
1558                    if let Some(ref accs) = extra.accessors {
1559                        if let Some(entry) = accs.get(atom) {
1560                            if entry.enumerable {
1561                                if let Some(getter) = entry.get {
1562                                    if seen.insert(*atom) {
1563                                        result.push((*atom, getter));
1564                                    }
1565                                }
1566                            }
1567                        }
1568                    }
1569                }
1570            }
1571        }
1572        // Append remaining enumerable data props not yet seen
1573        for slot in &self.props {
1574            if slot.attrs == ATTR_DELETED {
1575                continue;
1576            }
1577            if slot.atom.0 & 0x40000000 != 0 {
1578                continue;
1579            }
1580            if slot.attrs & ATTR_ENUMERABLE != 0 && !seen.contains(&slot.atom) {
1581                seen.insert(slot.atom);
1582                result.push((slot.atom, slot.value));
1583            }
1584        }
1585        // Append remaining enumerable accessor props not yet seen
1586        if let Some(ref extra) = self.extra {
1587            if let Some(ref accs) = extra.accessors {
1588                for (atom, entry) in accs.iter() {
1589                    if atom.0 & 0x40000000 != 0 {
1590                        continue;
1591                    }
1592                    if entry.enumerable && !seen.contains(atom) {
1593                        seen.insert(*atom);
1594                        if let Some(getter) = entry.get {
1595                            result.push((*atom, getter));
1596                        }
1597                    }
1598                }
1599            }
1600        }
1601        result
1602    }
1603
1604    pub fn non_enumerable_property_atoms(&self) -> Vec<Atom> {
1605        let mut result = Vec::new();
1606        for slot in &self.props {
1607            if slot.attrs == ATTR_DELETED {
1608                continue;
1609            }
1610            if slot.attrs & ATTR_ENUMERABLE == 0 {
1611                result.push(slot.atom);
1612            }
1613        }
1614        if let Some(ref extra) = self.extra {
1615            if let Some(ref accs) = extra.accessors {
1616                for (atom, entry) in accs.iter() {
1617                    if !entry.enumerable {
1618                        result.push(*atom);
1619                    }
1620                }
1621            }
1622        }
1623        result
1624    }
1625
1626    pub fn get_own_descriptor(&self, prop: Atom) -> Option<PropertyDescriptor> {
1627        if let Some(offset) = self.find_offset(prop) {
1628            if offset < self.props.len() {
1629                let a = self.props[offset].attrs;
1630                let (w, e, c) = (
1631                    a & ATTR_WRITABLE != 0,
1632                    a & ATTR_ENUMERABLE != 0,
1633                    a & ATTR_CONFIGURABLE != 0,
1634                );
1635                return Some(PropertyDescriptor {
1636                    value: Some(self.props[offset].value),
1637                    writable: w,
1638                    enumerable: e,
1639                    configurable: c,
1640                    get: None,
1641                    set: None,
1642                });
1643            }
1644        }
1645        if let Some(ref extra) = self.extra {
1646            if let Some(ref accs) = extra.accessors {
1647                if let Some(entry) = accs.get(&prop) {
1648                    return Some(PropertyDescriptor {
1649                        value: None,
1650                        writable: false,
1651                        enumerable: entry.enumerable,
1652                        configurable: entry.configurable,
1653                        get: entry.get,
1654                        set: entry.set,
1655                    });
1656                }
1657            }
1658        }
1659        None
1660    }
1661
1662    pub fn has_property_order(&self) -> bool {
1663        self.extra.as_ref().and_then(|e| e.property_order.as_ref()).is_some()
1664    }
1665
1666    pub fn property_order_keys(&self) -> Vec<Atom> {
1667        if let Some(ref extra) = self.extra {
1668            if let Some(ref order) = extra.property_order {
1669                return order.iter().copied().collect();
1670            }
1671        }
1672        Vec::new()
1673    }
1674
1675    pub fn accessor_keys(&self) -> Vec<(Atom, usize)> {
1676        if let Some(ref extra) = self.extra {
1677            if let Some(ref accs) = extra.accessors {
1678                return accs.keys().map(|k| (*k, 0)).collect();
1679            }
1680        }
1681        Vec::new()
1682    }
1683
1684    pub fn define_property(&mut self, prop: Atom, desc: PropertyDescriptor) -> bool {
1685        self.define_property_ext(prop, desc, true, true, true)
1686    }
1687
1688    pub fn define_property_ext(
1689        &mut self,
1690        prop: Atom,
1691        mut desc: PropertyDescriptor,
1692        has_writable: bool,
1693        has_enumerable: bool,
1694        has_configurable: bool,
1695    ) -> bool {
1696        if let Some(existing) = self.get_own_descriptor(prop) {
1697            if !existing.configurable {
1698                if has_configurable && desc.configurable {
1699                    return false;
1700                }
1701                if has_enumerable && desc.enumerable != existing.enumerable {
1702                    return false;
1703                }
1704                let existing_is_accessor = existing.get.is_some() || existing.set.is_some();
1705                let desc_is_accessor = desc.get.is_some() || desc.set.is_some();
1706                if existing_is_accessor {
1707                    if desc_is_accessor {
1708                        let undef = JSValue::undefined();
1709                        if let Some(dg) = desc.get {
1710                            let eg = existing.get.unwrap_or(undef);
1711                            if dg.raw_bits() != eg.raw_bits() {
1712                                return false;
1713                            }
1714                        }
1715                        if let Some(ds) = desc.set {
1716                            let es = existing.set.unwrap_or(undef);
1717                            if ds.raw_bits() != es.raw_bits() {
1718                                return false;
1719                            }
1720                        }
1721                        if let Some(extra) = self.extra.as_mut() {
1722                            if let Some(accs) = extra.accessors.as_mut() {
1723                                if let Some(entry) = accs.get_mut(&prop) {
1724                                    entry.enumerable = desc.enumerable;
1725                                }
1726                            }
1727                        }
1728                        return true;
1729                    }
1730                    if desc.value.is_some() || has_writable {
1731                        return false;
1732                    }
1733                    return true;
1734                }
1735                if desc_is_accessor {
1736                    return false;
1737                }
1738                if has_writable && desc.writable && !existing.writable {
1739                    return false;
1740                }
1741                if let Some(value) = desc.value {
1742                    if !existing.writable {
1743                        let cur = self
1744                            .get_by_offset(self.find_offset(prop).unwrap_or(usize::MAX))
1745                            .unwrap_or(JSValue::undefined());
1746                        if value.raw_bits() != cur.raw_bits() {
1747                            return false;
1748                        }
1749                    } else if let Some(offset) = self.find_offset(prop) {
1750                        if offset < self.props.len() && self.props[offset].attrs != ATTR_DELETED {
1751                            self.props[offset].value = value;
1752                        }
1753                    }
1754                }
1755                if has_writable {
1756                    if let Some(offset) = self.find_offset(prop) {
1757                        if offset < self.props.len() && self.props[offset].attrs != ATTR_DELETED {
1758                            self.props[offset].attrs = attrs_from_bools(
1759                                desc.writable,
1760                                desc.enumerable,
1761                                desc.configurable,
1762                            );
1763                        }
1764                    }
1765                }
1766                return true;
1767            }
1768            // Preserve existing attributes for any fields not specified in the descriptor
1769            if !has_enumerable {
1770                desc.enumerable = existing.enumerable;
1771            }
1772            if !has_writable {
1773                desc.writable = existing.writable;
1774            }
1775            if !has_configurable {
1776                desc.configurable = existing.configurable;
1777            }
1778            // Update the existing property in-place to preserve creation order
1779            if desc.is_accessor() {
1780                if let Some(offset) = self.find_offset(prop) {
1781                    if offset < self.props.len() && self.props[offset].attrs != ATTR_DELETED {
1782                        self.props[offset].value = JSValue::undefined();
1783                        self.props[offset].attrs = ATTR_DELETED;
1784                        self.flags |= FLAG_HAS_DELETED_PROPS;
1785                    }
1786                }
1787                let extra = self.ensure_extra();
1788                let accs = extra
1789                    .accessors
1790                    .get_or_insert_with(|| Box::new(FxHashMap::default()));
1791                let is_new = !accs.contains_key(&prop);
1792                accs.insert(
1793                    prop,
1794                    AccessorEntry {
1795                        get: desc.get,
1796                        set: desc.set,
1797                        enumerable: desc.enumerable,
1798                        configurable: desc.configurable,
1799                    },
1800                );
1801                if is_new {
1802                    let order = extra
1803                        .property_order
1804                        .get_or_insert_with(|| Box::new(Vec::new()));
1805                    order.push(prop);
1806                }
1807            } else {
1808                let new_attrs =
1809                    attrs_from_bools(desc.writable, desc.enumerable, desc.configurable);
1810                if let Some(value) = desc.value {
1811                    if let Some(offset) = self.find_offset(prop) {
1812                        if offset < self.props.len() && self.props[offset].attrs != ATTR_DELETED {
1813                            self.props[offset].value = value;
1814                            self.props[offset].attrs = new_attrs;
1815                        }
1816                    } else {
1817                        self.props.push(PropSlot::new(prop, value, new_attrs));
1818                    }
1819                } else if let Some(offset) = self.find_offset(prop) {
1820                    if offset < self.props.len() && self.props[offset].attrs != ATTR_DELETED {
1821                        self.props[offset].attrs = new_attrs;
1822                    }
1823                }
1824            }
1825            return true;
1826        }
1827        if !self.extensible() {
1828            return false;
1829        }
1830        if desc.is_accessor() {
1831            let extra = self.ensure_extra();
1832            let accs = extra
1833                .accessors
1834                .get_or_insert_with(|| Box::new(FxHashMap::default()));
1835            accs.insert(
1836                prop,
1837                AccessorEntry {
1838                    get: desc.get,
1839                    set: desc.set,
1840                    enumerable: desc.enumerable,
1841                    configurable: desc.configurable,
1842                },
1843            );
1844            let order = extra
1845                .property_order
1846                .get_or_insert_with(|| Box::new(Vec::new()));
1847            order.push(prop);
1848        } else {
1849            let value = desc.value.unwrap_or(JSValue::undefined());
1850            self.props.push(PropSlot::new(
1851                prop,
1852                value,
1853                attrs_from_bools(desc.writable, desc.enumerable, desc.configurable),
1854            ));
1855            self.track_property_order(prop);
1856            self.update_property_map(prop);
1857        }
1858        true
1859    }
1860
1861    pub fn delete(&mut self, prop: Atom) -> bool {
1862        if let Some(offset) = self.find_offset(prop) {
1863            if offset < self.props.len() {
1864                if self.props[offset].attrs & ATTR_CONFIGURABLE == 0 {
1865                    return false;
1866                }
1867                self.props[offset].value = JSValue::undefined();
1868                self.props[offset].attrs = ATTR_DELETED;
1869                self.flags |= FLAG_HAS_DELETED_PROPS;
1870                return true;
1871            }
1872        }
1873        if let Some(ref mut extra) = self.extra {
1874            if let Some(ref mut accs) = extra.accessors {
1875                if let Some(entry) = accs.get(&prop) {
1876                    if !entry.configurable {
1877                        return false;
1878                    }
1879                }
1880                if accs.remove(&prop).is_some() {
1881                    return true;
1882                }
1883            }
1884        }
1885        true
1886    }
1887
1888    #[inline(always)]
1889    pub fn add_property_ic(
1890        &mut self,
1891        atom: Atom,
1892        value: JSValue,
1893        offset: usize,
1894        new_shape: NonNull<crate::object::shape::Shape>,
1895    ) {
1896        debug_assert!(offset == self.props.len());
1897        self.props.push(PropSlot::new(atom, value, ATTR_DEFAULT));
1898        self.shape = Some(new_shape);
1899        self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1900
1901        self.update_property_map(atom);
1902    }
1903
1904    pub fn compact_props(&mut self, cache: &mut ShapeCache) {
1905        if self.flags & FLAG_HAS_DELETED_PROPS == 0 {
1906            return;
1907        }
1908        let has_deleted = self.props.iter().any(|p| p.attrs == ATTR_DELETED);
1909        if !has_deleted {
1910            self.flags &= !FLAG_HAS_DELETED_PROPS;
1911            return;
1912        }
1913
1914        let mut new_props: SmallPropVec = SmallPropVec::new();
1915        let mut property_atoms = Vec::new();
1916
1917        let old_len = self.props.len();
1918        for i in 0..old_len {
1919            let slot = self.props.get(i).expect("valid index");
1920            if slot.attrs != ATTR_DELETED {
1921                property_atoms.push(slot.atom);
1922                new_props.push(slot.clone());
1923            }
1924        }
1925
1926        self.props = new_props;
1927
1928        if !property_atoms.is_empty() {
1929            let new_shape = cache.create_shape_for_properties(&property_atoms);
1930            self.shape = Some(new_shape);
1931            self.shape_id_cache = unsafe { (*new_shape.as_ptr()).id.0 };
1932        } else {
1933            self.shape = None;
1934            self.shape_id_cache = usize::MAX;
1935        }
1936
1937        if let Some(ref mut extra) = self.extra {
1938            if let Some(ref mut map) = extra.property_map {
1939                let mut new_map = FxHashMap::default();
1940                for (i, atom) in property_atoms.iter().enumerate() {
1941                    new_map.insert(*atom, i as u32);
1942                }
1943                *map = Box::new(new_map);
1944            }
1945        }
1946
1947        self.flags &= !FLAG_HAS_DELETED_PROPS;
1948    }
1949
1950    pub fn has_property(&self, prop: Atom) -> bool {
1951        if self.find_offset(prop).is_some() {
1952            return true;
1953        }
1954        if let Some(ref extra) = self.extra {
1955            if let Some(ref accs) = extra.accessors {
1956                if accs.contains_key(&prop) {
1957                    return true;
1958                }
1959            }
1960        }
1961        let mut current = self.prototype;
1962        let mut depth = 0u32;
1963        while let Some(proto_ptr) = current {
1964            if proto_ptr.is_null() || depth > 1000 {
1965                break;
1966            }
1967            depth += 1;
1968            unsafe {
1969                let proto = &*proto_ptr;
1970                if proto.find_offset(prop).is_some() {
1971                    return true;
1972                }
1973                if let Some(ref extra) = proto.extra {
1974                    if let Some(ref accs) = extra.accessors {
1975                        if accs.contains_key(&prop) {
1976                            return true;
1977                        }
1978                    }
1979                }
1980                current = proto.prototype;
1981            }
1982        }
1983        false
1984    }
1985
1986    pub fn has_own(&self, prop: Atom) -> bool {
1987        if self.find_offset(prop).is_some() {
1988            return true;
1989        }
1990        self.extra
1991            .as_ref()
1992            .and_then(|e| e.accessors.as_ref())
1993            .map_or(false, |a| a.contains_key(&prop))
1994    }
1995
1996    pub fn keys(&self) -> Vec<Atom> {
1997        let mut result = Vec::new();
1998        for slot in &self.props {
1999            if slot.attrs != ATTR_DELETED && slot.atom.0 & 0x40000000 == 0 {
2000                result.push(slot.atom);
2001            }
2002        }
2003        if let Some(ref extra) = self.extra {
2004            if let Some(ref accs) = extra.accessors {
2005                for atom in accs.keys() {
2006                    if atom.0 & 0x40000000 == 0 {
2007                        result.push(*atom);
2008                    }
2009                }
2010            }
2011        }
2012        result
2013    }
2014
2015    pub fn all_keys(&self) -> Vec<Atom> {
2016        let mut result: Vec<Atom> = Vec::new();
2017        for slot in &self.props {
2018            if slot.attrs != ATTR_DELETED {
2019                result.push(slot.atom);
2020            }
2021        }
2022        if let Some(ref extra) = self.extra {
2023            if let Some(ref accs) = extra.accessors {
2024                for atom in accs.keys() {
2025                    result.push(*atom);
2026                }
2027            }
2028        }
2029        result
2030    }
2031
2032    pub fn symbol_keys(&self) -> Vec<JSValue> {
2033        let mut result = Vec::new();
2034        for i in 0..self.props.len() {
2035            let slot = self.props.get(i).expect("valid index");
2036            if slot.attrs != ATTR_DELETED && slot.atom.0 & 0x40000000 != 0 {
2037                let symbol_id = slot.atom.0 & 0x3FFFFFFF;
2038                result.push(JSValue::new_symbol_with_id(
2039                    crate::runtime::atom::Atom(0),
2040                    symbol_id,
2041                ));
2042            }
2043        }
2044        result
2045    }
2046
2047    pub fn enumerable_keys(&self) -> Vec<Atom> {
2048        let mut result = Vec::new();
2049        for slot in &self.props {
2050            if slot.attrs != ATTR_DELETED
2051                && slot.attrs & ATTR_ENUMERABLE != 0
2052                && slot.atom.0 & 0x40000000 == 0
2053            {
2054                result.push(slot.atom);
2055            }
2056        }
2057        if let Some(ref extra) = self.extra {
2058            if let Some(ref accs) = extra.accessors {
2059                for (atom, entry) in accs.iter() {
2060                    if entry.enumerable && atom.0 & 0x40000000 == 0 {
2061                        result.push(*atom);
2062                    }
2063                }
2064            }
2065        }
2066        result
2067    }
2068
2069    pub fn for_each_property<F: FnMut(Atom, JSValue, u8)>(&self, mut f: F) {
2070        for slot in &self.props {
2071            if slot.attrs == ATTR_DELETED {
2072                continue;
2073            }
2074            f(slot.atom, slot.value, slot.attrs);
2075        }
2076    }
2077
2078    pub fn for_each_accessor<F: FnMut(Atom, Option<JSValue>, Option<JSValue>)>(&self, mut f: F) {
2079        if let Some(ref extra) = self.extra {
2080            if let Some(ref accs) = extra.accessors {
2081                for (atom, entry) in accs.iter() {
2082                    f(*atom, entry.get, entry.set);
2083                }
2084            }
2085        }
2086    }
2087
2088    pub fn get_private_accessors_for_gc(&self) -> Option<Vec<JSValue>> {
2089        if let Some(ref extra) = self.extra {
2090            if let Some(ref priv_accs) = extra.private_accessors {
2091                let mut values = Vec::new();
2092                for (_atom, entry) in priv_accs.iter() {
2093                    if let Some(g) = entry.get {
2094                        values.push(g);
2095                    }
2096                    if let Some(s) = entry.set {
2097                        values.push(s);
2098                    }
2099                }
2100                if values.is_empty() {
2101                    None
2102                } else {
2103                    Some(values)
2104                }
2105            } else {
2106                None
2107            }
2108        } else {
2109            None
2110        }
2111    }
2112
2113    pub fn for_each_property_attrs_mut<F: FnMut(Atom, JSValue, &mut u8)>(&mut self, mut f: F) {
2114        for slot in &mut self.props {
2115            f(slot.atom, slot.value, &mut slot.attrs);
2116        }
2117    }
2118
2119    #[inline]
2120    pub fn get_indexed(&self, index: usize) -> Option<JSValue> {
2121        self.extra
2122            .as_ref()
2123            .and_then(|e| e.array_elements.as_ref())
2124            .and_then(|elements| elements.get(index).copied())
2125    }
2126
2127    #[inline]
2128    pub fn get_dense_slice(&self, len: usize) -> Option<&[JSValue]> {
2129        self.extra
2130            .as_ref()
2131            .and_then(|e| e.array_elements.as_ref())
2132            .filter(|elems| elems.len() >= len)
2133            .map(|elems| &elems[..len])
2134    }
2135
2136    #[inline]
2137    pub fn set_first_prop_with_shape(
2138        &mut self,
2139        atom: Atom,
2140        value: JSValue,
2141        attrs: u8,
2142        shape: std::ptr::NonNull<crate::object::shape::Shape>,
2143    ) {
2144        debug_assert!(
2145            self.props.is_empty(),
2146            "set_first_prop_with_shape: object must be empty"
2147        );
2148        self.props.push(PropSlot::new(atom, value, attrs));
2149        self.shape = Some(shape);
2150        self.shape_id_cache = unsafe { (*shape.as_ptr()).id.0 };
2151    }
2152
2153    #[inline]
2154    pub fn set_indexed(&mut self, index: usize, value: JSValue) {
2155        let elements = self.ensure_elements();
2156        if index < elements.len() {
2157            elements[index] = value;
2158        } else if index < 100000 {
2159            elements.resize(index + 1, JSValue::undefined());
2160            elements[index] = value;
2161        }
2162    }
2163
2164    #[inline]
2165    pub fn maybe_set_indexed(&mut self, index: usize, value: JSValue) -> bool {
2166        if let Some(extra) = &mut self.extra {
2167            if let Some(elements) = &mut extra.array_elements {
2168                if index < elements.len() {
2169                    elements[index] = value;
2170                    return true;
2171                }
2172            }
2173        }
2174        false
2175    }
2176
2177    #[inline]
2178    pub fn array_len(&self) -> usize {
2179        self.extra
2180            .as_ref()
2181            .and_then(|e| e.array_elements.as_ref())
2182            .map_or(0, |v| v.len())
2183    }
2184
2185    #[inline]
2186    pub fn has_dense_storage(&self) -> bool {
2187        self.extra
2188            .as_ref()
2189            .and_then(|e| e.array_elements.as_ref())
2190            .map_or(false, |v| !v.is_empty())
2191    }
2192
2193    #[inline(always)]
2194    pub fn get_shape_id(&self) -> Option<super::shape::ShapeId> {
2195        if self.shape_id_cache == usize::MAX {
2196            None
2197        } else {
2198            Some(super::shape::ShapeId(self.shape_id_cache))
2199        }
2200    }
2201
2202    #[inline(always)]
2203    pub fn get_shape_ptr(&self) -> Option<std::ptr::NonNull<super::shape::Shape>> {
2204        self.shape
2205    }
2206
2207    #[inline(always)]
2208    pub fn props_len(&self) -> usize {
2209        self.props.len()
2210    }
2211
2212    pub fn iter_props(&self) -> SmallPropIter<'_> {
2213        self.props.iter()
2214    }
2215
2216    #[inline]
2217    pub fn take_props(&mut self) -> SmallPropVec {
2218        std::mem::take(&mut self.props)
2219    }
2220
2221    pub fn clean_stale_properties(&mut self, heap: &crate::runtime::gc::GcHeap) {
2222        for slot in &mut self.props {
2223            if slot.attrs == ATTR_DELETED {
2224                continue;
2225            }
2226            if slot.value.is_object() || slot.value.is_function() {
2227                let ptr = slot.value.get_ptr();
2228                if !heap.is_ptr_alive(ptr) {
2229                    slot.value = JSValue::undefined();
2230                }
2231            }
2232        }
2233    }
2234
2235    #[inline(always)]
2236    pub fn get_inline_value_at(&self, offset: usize) -> Option<JSValue> {
2237        self.props.get(offset).map(|s| s.value)
2238    }
2239
2240    #[inline(always)]
2241    pub fn get_shape(&self) -> Option<NonNull<Shape>> {
2242        self.shape
2243    }
2244
2245    pub fn assign_shape_from_existing_props(&mut self, cache: &mut ShapeCache) {
2246        if self.shape.is_some() {
2247            return;
2248        }
2249        let mut current = cache.root_shape();
2250
2251        let inline_len = self.props.len().min(INLINE_PROPS);
2252        for i in 0..inline_len {
2253            let slot = unsafe { self.props.inline[i].assume_init_ref() };
2254            if slot.attrs != ATTR_DELETED {
2255                current = cache.transition(current, slot.atom);
2256            }
2257        }
2258
2259        if let Some(ref heap) = self.props.heap {
2260            for slot in heap.iter() {
2261                if slot.attrs != ATTR_DELETED {
2262                    current = cache.transition(current, slot.atom);
2263                }
2264            }
2265        }
2266        self.shape = Some(current);
2267        self.shape_id_cache = unsafe { (*current.as_ptr()).id.0 };
2268    }
2269
2270    #[inline(always)]
2271    pub fn inline_values_len(&self) -> usize {
2272        self.props.len()
2273    }
2274}
2275
2276impl Default for JSObject {
2277    fn default() -> Self {
2278        Self::new()
2279    }
2280}
2281
2282#[cfg(test)]
2283mod tests {
2284    use super::*;
2285
2286    #[test]
2287    fn test_prop_slot_size() {
2288        assert_eq!(std::mem::size_of::<PropSlot>(), 16);
2289    }
2290
2291    #[test]
2292    fn test_prop_slot_new() {
2293        let slot = PropSlot::new(Atom(42), JSValue::new_int(100), ATTR_DEFAULT);
2294        assert_eq!(slot.atom, Atom(42));
2295        assert_eq!(slot.value.get_int(), 100);
2296        assert_eq!(
2297            slot.attrs,
2298            ATTR_WRITABLE | ATTR_ENUMERABLE | ATTR_CONFIGURABLE
2299        );
2300    }
2301
2302    #[test]
2303    fn test_object_new_has_no_properties() {
2304        let obj = JSObject::new();
2305        assert_eq!(obj.inline_values_len(), 0);
2306        assert_eq!(obj.keys().len(), 0);
2307    }
2308
2309    #[test]
2310    fn test_object_set_and_get() {
2311        let mut obj = JSObject::new();
2312        let atom_a = Atom(1);
2313        obj.set(atom_a, JSValue::new_int(42));
2314        assert_eq!(obj.inline_values_len(), 1);
2315
2316        let val = obj.get(atom_a);
2317        assert!(val.is_some());
2318        assert_eq!(val.unwrap().get_int(), 42);
2319    }
2320
2321    #[test]
2322    fn test_object_set_multiple_properties() {
2323        let mut obj = JSObject::new();
2324        obj.set(Atom(1), JSValue::new_int(10));
2325        obj.set(Atom(2), JSValue::new_int(20));
2326        obj.set(Atom(3), JSValue::new_int(30));
2327
2328        assert_eq!(obj.inline_values_len(), 3);
2329        assert_eq!(obj.get(Atom(1)).unwrap().get_int(), 10);
2330        assert_eq!(obj.get(Atom(2)).unwrap().get_int(), 20);
2331        assert_eq!(obj.get(Atom(3)).unwrap().get_int(), 30);
2332    }
2333
2334    #[test]
2335    fn test_object_set_overwrites_existing() {
2336        let mut obj = JSObject::new();
2337        obj.set(Atom(1), JSValue::new_int(10));
2338        obj.set(Atom(1), JSValue::new_int(20));
2339
2340        assert_eq!(obj.inline_values_len(), 1);
2341        assert_eq!(obj.get(Atom(1)).unwrap().get_int(), 20);
2342    }
2343
2344    #[test]
2345    fn test_object_get_nonexistent_returns_none() {
2346        let obj = JSObject::new();
2347        assert!(obj.get(Atom(999)).is_none());
2348    }
2349
2350    #[test]
2351    fn test_object_delete_property() {
2352        let mut obj = JSObject::new();
2353        obj.set(Atom(1), JSValue::new_int(42));
2354        assert!(obj.get(Atom(1)).is_some());
2355
2356        let deleted = obj.delete(Atom(1));
2357        assert!(deleted);
2358        assert!(obj.get(Atom(1)).is_none());
2359    }
2360
2361    #[test]
2362    fn test_object_delete_nonexistent_returns_true() {
2363        let mut obj = JSObject::new();
2364        assert!(obj.delete(Atom(999)));
2365    }
2366
2367    #[test]
2368    fn test_object_delete_then_readd() {
2369        let mut obj = JSObject::new();
2370        obj.set(Atom(1), JSValue::new_int(10));
2371        obj.delete(Atom(1));
2372        obj.set(Atom(1), JSValue::new_int(20));
2373
2374        assert_eq!(obj.get(Atom(1)).unwrap().get_int(), 20);
2375    }
2376
2377    #[test]
2378    fn test_object_flags_default() {
2379        let obj = JSObject::new();
2380        assert!(obj.extensible());
2381        assert!(!obj.sealed());
2382        assert!(!obj.frozen());
2383        assert!(!obj.is_generator());
2384    }
2385
2386    #[test]
2387    fn test_object_seal() {
2388        let mut obj = JSObject::new();
2389        obj.set_sealed(true);
2390        assert!(obj.sealed());
2391        obj.set_sealed(false);
2392        assert!(!obj.sealed());
2393    }
2394
2395    #[test]
2396    fn test_object_freeze() {
2397        let mut obj = JSObject::new();
2398        obj.set_frozen(true);
2399        assert!(obj.frozen());
2400    }
2401
2402    #[test]
2403    fn test_object_not_extensible_rejects_new_props() {
2404        let mut obj = JSObject::new();
2405        obj.set(Atom(1), JSValue::new_int(10));
2406        obj.set_extensible(false);
2407
2408        obj.set(Atom(2), JSValue::new_int(20));
2409        assert!(obj.get(Atom(2)).is_none());
2410
2411        obj.set(Atom(1), JSValue::new_int(30));
2412        assert_eq!(obj.get(Atom(1)).unwrap().get_int(), 30);
2413    }
2414
2415    #[test]
2416    fn test_object_types() {
2417        let ordinary = JSObject::new();
2418        assert_eq!(ordinary.obj_type(), ObjectType::Ordinary);
2419
2420        let array = JSObject::new_array();
2421        assert_eq!(array.obj_type(), ObjectType::Array);
2422
2423        let func = JSObject::new_function();
2424        assert_eq!(func.obj_type(), ObjectType::Function);
2425    }
2426
2427    #[test]
2428    fn test_object_keys() {
2429        let mut obj = JSObject::new();
2430        obj.set(Atom(1), JSValue::new_int(10));
2431        obj.set(Atom(2), JSValue::new_int(20));
2432
2433        let keys = obj.keys();
2434        assert_eq!(keys.len(), 2);
2435        assert!(keys.contains(&Atom(1)));
2436        assert!(keys.contains(&Atom(2)));
2437    }
2438
2439    #[test]
2440    fn test_object_keys_excludes_deleted() {
2441        let mut obj = JSObject::new();
2442        obj.set(Atom(1), JSValue::new_int(10));
2443        obj.set(Atom(2), JSValue::new_int(20));
2444        obj.delete(Atom(1));
2445
2446        let keys = obj.keys();
2447        assert_eq!(keys.len(), 1);
2448        assert!(keys.contains(&Atom(2)));
2449    }
2450
2451    #[test]
2452    fn test_object_own_properties() {
2453        let mut obj = JSObject::new();
2454        obj.set(Atom(1), JSValue::new_int(10));
2455        obj.set(Atom(2), JSValue::new_int(20));
2456
2457        let props = obj.own_properties();
2458        assert_eq!(props.len(), 2);
2459    }
2460
2461    #[test]
2462    fn test_object_has_own() {
2463        let mut obj = JSObject::new();
2464        obj.set(Atom(1), JSValue::new_int(10));
2465        assert!(obj.has_own(Atom(1)));
2466        assert!(!obj.has_own(Atom(2)));
2467    }
2468
2469    #[test]
2470    fn test_object_get_own() {
2471        let mut obj = JSObject::new();
2472        obj.set(Atom(1), JSValue::new_int(42));
2473        assert_eq!(obj.get_own(Atom(1)).unwrap().get_int(), 42);
2474        assert!(obj.get_own(Atom(2)).is_none());
2475    }
2476
2477    #[test]
2478    fn test_object_get_own_descriptor() {
2479        let mut obj = JSObject::new();
2480        obj.set(Atom(1), JSValue::new_int(42));
2481
2482        let desc = obj.get_own_descriptor(Atom(1)).unwrap();
2483        assert_eq!(desc.value.unwrap().get_int(), 42);
2484        assert!(desc.writable);
2485        assert!(desc.enumerable);
2486        assert!(desc.configurable);
2487    }
2488
2489    #[test]
2490    fn test_object_define_property_data() {
2491        let mut obj = JSObject::new();
2492        let desc = PropertyDescriptor::new_data(JSValue::new_int(99));
2493        let ok = obj.define_property(Atom(1), desc);
2494        assert!(ok);
2495        assert_eq!(obj.get(Atom(1)).unwrap().get_int(), 99);
2496    }
2497
2498    #[test]
2499    fn test_object_define_property_accessor() {
2500        let mut obj = JSObject::new();
2501        let getter = JSValue::new_int(0);
2502        let desc = PropertyDescriptor::new_accessor(Some(getter), None);
2503        let ok = obj.define_property(Atom(1), desc);
2504        assert!(ok);
2505
2506        assert!(obj.get(Atom(1)).is_some());
2507    }
2508
2509    #[test]
2510    fn test_object_for_each_property() {
2511        let mut obj = JSObject::new();
2512        obj.set(Atom(1), JSValue::new_int(10));
2513        obj.set(Atom(2), JSValue::new_int(20));
2514
2515        let mut collected = Vec::new();
2516        obj.for_each_property(|atom, value, attrs| {
2517            collected.push((atom, value.get_int(), attrs));
2518        });
2519
2520        assert_eq!(collected.len(), 2);
2521        assert_eq!(collected[0].0, Atom(1));
2522        assert_eq!(collected[0].1, 10);
2523        assert_eq!(collected[1].0, Atom(2));
2524        assert_eq!(collected[1].1, 20);
2525    }
2526
2527    #[test]
2528    fn test_object_for_each_property_skips_deleted() {
2529        let mut obj = JSObject::new();
2530        obj.set(Atom(1), JSValue::new_int(10));
2531        obj.set(Atom(2), JSValue::new_int(20));
2532        obj.delete(Atom(1));
2533
2534        let mut count = 0;
2535        obj.for_each_property(|_, _, _| {
2536            count += 1;
2537        });
2538        assert_eq!(count, 1);
2539    }
2540
2541    #[test]
2542    fn test_object_for_each_property_attrs_mut() {
2543        let mut obj = JSObject::new();
2544        obj.set(Atom(1), JSValue::new_int(10));
2545
2546        obj.for_each_property_attrs_mut(|atom, _value, attrs| {
2547            assert_eq!(atom, Atom(1));
2548            *attrs = ATTR_DELETED;
2549        });
2550
2551        assert!(obj.get(Atom(1)).is_none());
2552    }
2553
2554    #[test]
2555    fn test_object_get_inline_value_at() {
2556        let mut obj = JSObject::new();
2557        obj.set(Atom(1), JSValue::new_int(42));
2558
2559        assert_eq!(obj.get_inline_value_at(0).unwrap().get_int(), 42);
2560        assert!(obj.get_inline_value_at(1).is_none());
2561    }
2562
2563    #[test]
2564    fn test_object_array_elements() {
2565        let mut obj = JSObject::new_array();
2566        obj.set_array_elements(vec![
2567            JSValue::new_int(1),
2568            JSValue::new_int(2),
2569            JSValue::new_int(3),
2570        ]);
2571
2572        assert_eq!(obj.array_elements_len(), 3);
2573        assert_eq!(obj.get_indexed(0).unwrap().get_int(), 1);
2574        assert_eq!(obj.get_indexed(2).unwrap().get_int(), 3);
2575        assert!(obj.get_indexed(3).is_none());
2576    }
2577
2578    #[test]
2579    fn test_object_set_indexed() {
2580        let mut obj = JSObject::new_array();
2581        obj.set_array_elements(vec![JSValue::new_int(0); 5]);
2582        obj.set_indexed(2, JSValue::new_int(99));
2583
2584        assert_eq!(obj.get_indexed(2).unwrap().get_int(), 99);
2585    }
2586
2587    #[test]
2588    fn test_object_ensure_elements() {
2589        let mut obj = JSObject::new_array();
2590        let elements = obj.ensure_elements();
2591        elements.push(JSValue::new_int(1));
2592        assert_eq!(obj.array_elements_len(), 1);
2593    }
2594
2595    #[test]
2596    fn test_object_has_dense_storage() {
2597        let mut obj = JSObject::new_array();
2598        assert!(!obj.has_dense_storage());
2599        obj.set_array_elements(vec![JSValue::new_int(1)]);
2600        assert!(obj.has_dense_storage());
2601    }
2602
2603    #[test]
2604    fn test_object_for_each_array_element() {
2605        let mut obj = JSObject::new_array();
2606        obj.set_array_elements(vec![JSValue::new_int(10), JSValue::new_int(20)]);
2607
2608        let mut sum = 0i64;
2609        obj.for_each_array_element(|v| {
2610            sum += v.get_int();
2611        });
2612        assert_eq!(sum, 30);
2613    }
2614
2615    #[test]
2616    fn test_object_bigint_value() {
2617        let mut obj = JSObject::new();
2618        assert_eq!(obj.get_bigint_value(), 0);
2619        obj.set_bigint_value(123456789);
2620        assert_eq!(obj.get_bigint_value(), 123456789);
2621    }
2622
2623    #[test]
2624    fn test_object_private_fields() {
2625        let mut obj = JSObject::new();
2626        assert!(!obj.has_private_field(Atom(1)));
2627        assert!(obj.get_private_field(Atom(1)).is_none());
2628
2629        obj.set_private_field(Atom(1), JSValue::new_int(42));
2630        assert!(obj.has_private_field(Atom(1)));
2631        assert_eq!(obj.get_private_field(Atom(1)).unwrap().get_int(), 42);
2632    }
2633
2634    #[test]
2635    fn test_object_prototype_chain_get() {
2636        let mut child_obj = JSObject::new();
2637        let mut parent_obj = JSObject::new();
2638        parent_obj.set(Atom(1), JSValue::new_int(42));
2639
2640        let parent_ptr = Box::into_raw(Box::new(parent_obj)) as *mut JSObject;
2641        child_obj.prototype = Some(parent_ptr);
2642
2643        assert_eq!(child_obj.get(Atom(1)).unwrap().get_int(), 42);
2644
2645        child_obj.set(Atom(1), JSValue::new_int(99));
2646        assert_eq!(child_obj.get(Atom(1)).unwrap().get_int(), 99);
2647
2648        unsafe {
2649            let _ = Box::from_raw(parent_ptr);
2650        }
2651    }
2652
2653    #[test]
2654    fn test_object_has_property_inherits() {
2655        let mut child_obj = JSObject::new();
2656        let mut parent_obj = JSObject::new();
2657        parent_obj.set(Atom(1), JSValue::new_int(42));
2658
2659        let parent_ptr = Box::into_raw(Box::new(parent_obj)) as *mut JSObject;
2660        child_obj.prototype = Some(parent_ptr);
2661
2662        assert!(child_obj.has_property(Atom(1)));
2663        assert!(!child_obj.has_own(Atom(1)));
2664
2665        unsafe {
2666            let _ = Box::from_raw(parent_ptr);
2667        }
2668    }
2669
2670    #[test]
2671    fn test_object_property_map_built_after_threshold() {
2672        let mut obj = JSObject::new();
2673
2674        for i in 0..=INLINE_THRESHOLD {
2675            obj.set(Atom(i as u32), JSValue::new_int(i as i64));
2676        }
2677
2678        for i in 0..=INLINE_THRESHOLD {
2679            assert_eq!(obj.get(Atom(i as u32)).unwrap().get_int(), i as i64);
2680        }
2681    }
2682
2683    #[test]
2684    fn test_object_set_cached_creates_shape() {
2685        let mut cache = ShapeCache::new();
2686        let mut obj = JSObject::new();
2687        obj.set_cached(Atom(1), JSValue::new_int(10), &mut cache);
2688        obj.set_cached(Atom(2), JSValue::new_int(20), &mut cache);
2689
2690        assert_eq!(obj.get(Atom(1)).unwrap().get_int(), 10);
2691        assert_eq!(obj.get(Atom(2)).unwrap().get_int(), 20);
2692        assert!(obj.shape.is_some());
2693    }
2694
2695    #[test]
2696    fn test_jsobject_size() {
2697        let size = std::mem::size_of::<JSObject>();
2698
2699        assert!(
2700            size <= 192,
2701            "JSObject size should be <= 192 bytes, got {}",
2702            size
2703        );
2704    }
2705
2706    #[test]
2707    fn test_compact_props_removes_deleted_slots() {
2708        let mut cache = ShapeCache::new();
2709        let mut obj = JSObject::new();
2710        obj.set(Atom(1), JSValue::new_int(10));
2711        obj.set(Atom(2), JSValue::new_int(20));
2712        obj.set(Atom(3), JSValue::new_int(30));
2713        obj.delete(Atom(2));
2714
2715        assert_eq!(obj.inline_values_len(), 3);
2716        obj.compact_props(&mut cache);
2717        assert_eq!(obj.inline_values_len(), 2);
2718
2719        assert_eq!(obj.get(Atom(1)).unwrap().get_int(), 10);
2720        assert_eq!(obj.get(Atom(3)).unwrap().get_int(), 30);
2721        assert!(obj.get(Atom(2)).is_none());
2722    }
2723
2724    #[test]
2725    fn test_compact_props_rebuilds_shape_offsets() {
2726        let mut cache = ShapeCache::new();
2727        let mut obj = JSObject::new();
2728        obj.set_cached(Atom(1), JSValue::new_int(10), &mut cache);
2729        obj.set_cached(Atom(2), JSValue::new_int(20), &mut cache);
2730        obj.set_cached(Atom(3), JSValue::new_int(30), &mut cache);
2731        obj.delete(Atom(2));
2732
2733        let old_shape = obj.get_shape_id().unwrap();
2734        obj.compact_props(&mut cache);
2735        let new_shape = obj.get_shape_id().unwrap();
2736
2737        assert_ne!(old_shape, new_shape);
2738
2739        let shape = obj.get_shape().unwrap();
2740        unsafe {
2741            assert_eq!((*shape.as_ptr()).get_offset(Atom(1)), Some(0));
2742            assert_eq!((*shape.as_ptr()).get_offset(Atom(3)), Some(1));
2743            assert_eq!((*shape.as_ptr()).get_offset(Atom(2)), None);
2744        }
2745    }
2746
2747    #[test]
2748    fn test_compact_props_all_deleted() {
2749        let mut cache = ShapeCache::new();
2750        let mut obj = JSObject::new();
2751        obj.set(Atom(1), JSValue::new_int(10));
2752        obj.set(Atom(2), JSValue::new_int(20));
2753        obj.delete(Atom(1));
2754        obj.delete(Atom(2));
2755
2756        obj.compact_props(&mut cache);
2757        assert_eq!(obj.inline_values_len(), 0);
2758        assert!(obj.get_shape().is_none());
2759        assert!(obj.get(Atom(1)).is_none());
2760        assert!(obj.get(Atom(2)).is_none());
2761    }
2762
2763    #[test]
2764    fn test_compact_props_no_deleted_early_return() {
2765        let mut cache = ShapeCache::new();
2766        let mut obj = JSObject::new();
2767        obj.set(Atom(1), JSValue::new_int(10));
2768        obj.set(Atom(2), JSValue::new_int(20));
2769
2770        let shape_before = obj.get_shape_id();
2771        obj.compact_props(&mut cache);
2772        let shape_after = obj.get_shape_id();
2773
2774        assert_eq!(shape_before, shape_after);
2775        assert_eq!(obj.inline_values_len(), 2);
2776    }
2777}