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