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 if let Some(ref extra) = self.extra {
1538 if let Some(ref order) = extra.property_order {
1539 for atom in order.iter() {
1540 if atom.0 & 0x40000000 != 0 {
1542 continue;
1543 }
1544 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 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 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 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 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 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}