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}