1use crate::{
6 ecmascript::{
7 Agent, BIGINT_DISCRIMINANT, BOOLEAN_DISCRIMINANT, BUILTIN_STRING_MEMORY, BigInt,
8 FLOAT_DISCRIMINANT, HeapBigInt, HeapNumber, HeapString, INTEGER_DISCRIMINANT,
9 InternalMethods, InternalSlots, JsResult, NUMBER_DISCRIMINANT, Number, Object,
10 OrdinaryObject, Primitive, PropertyDescriptor, PropertyKey, ProtoIntrinsics,
11 SMALL_BIGINT_DISCRIMINANT, SMALL_STRING_DISCRIMINANT, STRING_DISCRIMINANT,
12 SYMBOL_DISCRIMINANT, SetResult, SmallBigInt, SmallF64, SmallInteger, String, Symbol,
13 TryError, TryGetResult, TryHasResult, TryResult, Value, is_compatible_property_descriptor,
14 js_result_into_try, object_handle, ordinary_define_own_property, ordinary_delete,
15 ordinary_get, ordinary_get_own_property, ordinary_has_property, ordinary_set, unwrap_try,
16 },
17 engine::{Bindable, GcScope, NoGcScope, bindable_handle},
18 heap::{
19 ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
20 HeapMarkAndSweep, HeapSweepWeakReference, IntrinsicPrimitiveObjectIndexes, WorkQueues,
21 arena_vec_access,
22 },
23};
24use small_string::SmallString;
25
26use super::{
27 ObjectShape, PropertyLookupCache, ordinary_own_property_keys, ordinary_try_get,
28 ordinary_try_has_property, ordinary_try_set,
29};
30
31#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
47#[repr(transparent)]
48pub struct PrimitiveObject<'a>(BaseIndex<'a, PrimitiveObjectRecord<'static>>);
49object_handle!(PrimitiveObject);
50arena_vec_access!(PrimitiveObject, 'a, PrimitiveObjectRecord, primitive_objects);
51
52impl IntrinsicPrimitiveObjectIndexes {
53 pub(crate) const fn get_primitive_object<'a>(
54 self,
55 base: BaseIndex<'a, PrimitiveObjectRecord<'static>>,
56 ) -> PrimitiveObject<'a> {
57 PrimitiveObject(BaseIndex::from_index_u32_const(
58 self as u32 + base.get_index_u32_const() + Self::PRIMITIVE_OBJECT_INDEX_OFFSET,
59 ))
60 }
61}
62
63impl PrimitiveObject<'_> {
64 pub fn is_bigint_object(self, agent: &Agent) -> bool {
72 matches!(
73 self.get(agent).data,
74 PrimitiveObjectData::BigInt(_) | PrimitiveObjectData::SmallBigInt(_)
75 )
76 }
77
78 pub fn is_number_object(self, agent: &Agent) -> bool {
87 matches!(
88 self.get(agent).data,
89 PrimitiveObjectData::SmallF64(_)
90 | PrimitiveObjectData::Integer(_)
91 | PrimitiveObjectData::Number(_)
92 )
93 }
94
95 pub fn is_string_object(self, agent: &Agent) -> bool {
104 matches!(
105 self.get(agent).data,
106 PrimitiveObjectData::String(_) | PrimitiveObjectData::SmallString(_)
107 )
108 }
109
110 pub fn is_symbol_object(self, agent: &Agent) -> bool {
118 matches!(self.get(agent).data, PrimitiveObjectData::Symbol(_))
119 }
120}
121
122impl<'a> InternalSlots<'a> for PrimitiveObject<'a> {
123 #[inline(always)]
124 fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
125 self.get(agent).object_index.unbind()
126 }
127
128 fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) {
129 assert!(
130 self.get_mut(agent)
131 .object_index
132 .replace(backing_object.unbind())
133 .is_none()
134 );
135 }
136
137 fn object_shape(self, agent: &mut Agent) -> ObjectShape<'static> {
138 if let Some(bo) = self.get_backing_object(agent) {
139 bo.object_shape(agent)
140 } else {
141 let primitive: Primitive = self.get(agent).data.into();
142 primitive.object_shape(agent).unwrap()
143 }
144 }
145
146 fn internal_prototype(self, agent: &Agent) -> Option<Object<'static>> {
147 match self.get_backing_object(agent) {
148 Some(obj) => obj.internal_prototype(agent),
149 None => {
150 let intrinsic_default_proto = match self.get(agent).data {
151 PrimitiveObjectData::Boolean(_) => ProtoIntrinsics::Boolean,
152 PrimitiveObjectData::String(_) | PrimitiveObjectData::SmallString(_) => {
153 ProtoIntrinsics::String
154 }
155 PrimitiveObjectData::Symbol(_) => ProtoIntrinsics::Symbol,
156 PrimitiveObjectData::Number(_)
157 | PrimitiveObjectData::Integer(_)
158 | PrimitiveObjectData::SmallF64(_) => ProtoIntrinsics::Number,
159 PrimitiveObjectData::BigInt(_) | PrimitiveObjectData::SmallBigInt(_) => {
160 ProtoIntrinsics::BigInt
161 }
162 };
163 Some(
166 agent
167 .current_realm_record()
168 .intrinsics()
169 .get_intrinsic_default_proto(intrinsic_default_proto),
170 )
171 }
172 }
173 }
174}
175
176impl<'a> InternalMethods<'a> for PrimitiveObject<'a> {
177 fn try_get_own_property<'gc>(
178 self,
179 agent: &mut Agent,
180 property_key: PropertyKey,
181 cache: Option<PropertyLookupCache>,
182 gc: NoGcScope<'gc, '_>,
183 ) -> TryResult<'gc, Option<PropertyDescriptor<'gc>>> {
184 let o = self.bind(gc);
185 if let Some(backing_object) = o.get_backing_object(agent)
191 && let Some(property_descriptor) =
192 ordinary_get_own_property(agent, o.into(), backing_object, property_key, cache, gc)
193 {
194 return TryResult::Continue(Some(property_descriptor));
195 }
196
197 if let Ok(string) = String::try_from(self.get(agent).data) {
198 TryResult::Continue(string.get_property_descriptor(agent, property_key))
200 } else {
201 TryResult::Continue(None)
202 }
203 }
204
205 fn try_define_own_property<'gc>(
206 self,
207 agent: &mut Agent,
208 property_key: PropertyKey,
209 property_descriptor: PropertyDescriptor,
210 cache: Option<PropertyLookupCache>,
211 gc: NoGcScope<'gc, '_>,
212 ) -> TryResult<'gc, bool> {
213 if let Ok(string) = String::try_from(self.get(agent).data) {
214 if let Some(string_desc) = string.get_property_descriptor(agent, property_key) {
218 return match is_compatible_property_descriptor(
221 agent,
222 self.internal_extensible(agent),
223 property_descriptor,
224 Some(string_desc),
225 gc,
226 ) {
227 Ok(b) => TryResult::Continue(b),
228 Err(_) => TryError::GcError.into(),
229 };
230 }
231 }
233
234 let backing_object = self
235 .get_backing_object(agent)
236 .unwrap_or_else(|| self.create_backing_object(agent));
237 js_result_into_try(ordinary_define_own_property(
238 agent,
239 self.into(),
240 backing_object,
241 property_key,
242 property_descriptor,
243 cache,
244 gc,
245 ))
246 }
247
248 fn try_has_property<'gc>(
249 self,
250 agent: &mut Agent,
251 property_key: PropertyKey,
252 cache: Option<PropertyLookupCache>,
253 gc: NoGcScope<'gc, '_>,
254 ) -> TryResult<'gc, TryHasResult<'gc>> {
255 if let Ok(string) = String::try_from(self.get(agent).data)
256 && string.get_property_value(agent, property_key).is_some()
257 {
258 return TryHasResult::Custom(0, self.bind(gc).into()).into();
259 }
260
261 ordinary_try_has_property(
263 agent,
264 self.into(),
265 self.get_backing_object(agent),
266 property_key,
267 cache,
268 gc,
269 )
270 }
271
272 fn internal_has_property<'gc>(
273 self,
274 agent: &mut Agent,
275 property_key: PropertyKey,
276 gc: GcScope<'gc, '_>,
277 ) -> JsResult<'gc, bool> {
278 let property_key = property_key.bind(gc.nogc());
279 if let Ok(string) = String::try_from(self.get(agent).data)
280 && string.get_property_value(agent, property_key).is_some()
281 {
282 return Ok(true);
283 }
284
285 match self.get_backing_object(agent) {
287 Some(backing_object) => ordinary_has_property(
288 agent,
289 self.into(),
290 backing_object,
291 property_key.unbind(),
292 gc,
293 ),
294 None => {
295 let parent = unwrap_try(self.try_get_prototype_of(agent, gc.nogc()));
298
299 if let Some(parent) = parent {
301 parent
303 .unbind()
304 .internal_has_property(agent, property_key.unbind(), gc)
305 } else {
306 Ok(false)
308 }
309 }
310 }
311 }
312
313 fn try_get<'gc>(
314 self,
315 agent: &mut Agent,
316 property_key: PropertyKey,
317 receiver: Value,
318 cache: Option<PropertyLookupCache>,
319 gc: NoGcScope<'gc, '_>,
320 ) -> TryResult<'gc, TryGetResult<'gc>> {
321 if let Ok(string) = String::try_from(self.get(agent).data)
322 && let Some(value) = string.get_property_value(agent, property_key)
323 {
324 return TryGetResult::Value(value.bind(gc)).into();
325 }
326 ordinary_try_get(
328 agent,
329 self.into(),
330 self.get_backing_object(agent),
331 property_key,
332 receiver,
333 cache,
334 gc,
335 )
336 }
337
338 fn internal_get<'gc>(
339 self,
340 agent: &mut Agent,
341 property_key: PropertyKey,
342 receiver: Value,
343 gc: GcScope<'gc, '_>,
344 ) -> JsResult<'gc, Value<'gc>> {
345 let property_key = property_key.bind(gc.nogc());
346 if let Ok(string) = String::try_from(self.get(agent).data)
347 && let Some(value) = string.get_property_value(agent, property_key)
348 {
349 return Ok(value.bind(gc.into_nogc()));
350 }
351
352 match self.get_backing_object(agent) {
354 Some(backing_object) => {
355 ordinary_get(agent, backing_object, property_key.unbind(), receiver, gc)
356 }
357 None => {
358 let Some(parent) = unwrap_try(self.try_get_prototype_of(agent, gc.nogc())) else {
360 return Ok(Value::Undefined);
362 };
363
364 parent
366 .unbind()
367 .internal_get(agent, property_key.unbind(), receiver, gc)
368 }
369 }
370 }
371
372 fn try_set<'gc>(
373 self,
374 agent: &mut Agent,
375 property_key: PropertyKey,
376 value: Value,
377 receiver: Value,
378 cache: Option<PropertyLookupCache>,
379 gc: NoGcScope<'gc, '_>,
380 ) -> TryResult<'gc, SetResult<'gc>> {
381 if let Ok(string) = String::try_from(self.get(agent).data)
382 && string.get_property_value(agent, property_key).is_some()
383 {
384 return SetResult::Unwritable.into();
385 }
386
387 ordinary_try_set(agent, self, property_key, value, receiver, cache, gc)
389 }
390
391 fn internal_set<'gc>(
392 self,
393 agent: &mut Agent,
394 property_key: PropertyKey,
395 value: Value,
396 receiver: Value,
397 gc: GcScope<'gc, '_>,
398 ) -> JsResult<'gc, bool> {
399 let property_key = property_key.bind(gc.nogc());
400 if let Ok(string) = String::try_from(self.get(agent).data)
401 && string.get_property_value(agent, property_key).is_some()
402 {
403 return Ok(false);
404 }
405
406 ordinary_set(
408 agent,
409 self.into(),
410 property_key.unbind(),
411 value,
412 receiver,
413 gc,
414 )
415 }
416
417 fn try_delete<'gc>(
418 self,
419 agent: &mut Agent,
420 property_key: PropertyKey,
421 gc: NoGcScope<'gc, '_>,
422 ) -> TryResult<'gc, bool> {
423 if let Ok(string) = String::try_from(self.get(agent).data) {
424 if property_key == BUILTIN_STRING_MEMORY.length.into() {
427 return TryResult::Continue(false);
428 } else if let PropertyKey::Integer(index) = property_key {
429 let index = index.into_i64();
430 if index >= 0 && (index as usize) < string.utf16_len_(agent) {
431 return TryResult::Continue(false);
432 }
433 }
434 }
435
436 match self.get_backing_object(agent) {
438 Some(backing_object) => TryResult::Continue(ordinary_delete(
439 agent,
440 self.into(),
441 backing_object,
442 property_key,
443 gc,
444 )),
445 None => TryResult::Continue(true),
446 }
447 }
448
449 fn try_own_property_keys<'gc>(
450 self,
451 agent: &mut Agent,
452 gc: NoGcScope<'gc, '_>,
453 ) -> TryResult<'gc, Vec<PropertyKey<'gc>>> {
454 if let Ok(string) = String::try_from(self.get(agent).data) {
455 let len = string.utf16_len_(agent);
456 let mut keys = Vec::with_capacity(len + 1);
457
458 keys.extend(
460 (0..len)
461 .map(|idx| PropertyKey::Integer(SmallInteger::try_from(idx as u64).unwrap())),
462 );
463
464 let backing_object_keys;
465 let (integer_keys, other_keys) = match self.get_backing_object(agent) {
466 Some(backing_object) => {
467 backing_object_keys = ordinary_own_property_keys(agent, backing_object, gc);
468 if let Some(PropertyKey::Integer(smi)) = backing_object_keys.first() {
469 debug_assert!(smi.into_i64() >= len as i64);
470 }
471 let split_idx = backing_object_keys
472 .iter()
473 .position(|pk| !pk.is_array_index());
474 if let Some(idx) = split_idx {
475 backing_object_keys.split_at(idx)
476 } else {
477 (&backing_object_keys[..], &[][..])
478 }
479 }
480 None => (&[][..], &[][..]),
481 };
482
483 keys.extend(integer_keys);
485 keys.push(BUILTIN_STRING_MEMORY.length.into());
486 keys.extend(other_keys);
487
488 return TryResult::Continue(keys);
489 }
490
491 match self.get_backing_object(agent) {
493 Some(backing_object) => {
494 TryResult::Continue(ordinary_own_property_keys(agent, backing_object, gc))
495 }
496 None => TryResult::Continue(vec![]),
497 }
498 }
499}
500
501#[derive(Debug, Clone, Copy, PartialEq)]
502#[repr(u8)]
503pub(crate) enum PrimitiveObjectData<'a> {
504 Boolean(bool) = BOOLEAN_DISCRIMINANT,
505 String(HeapString<'a>) = STRING_DISCRIMINANT,
506 SmallString(SmallString) = SMALL_STRING_DISCRIMINANT,
507 Symbol(Symbol<'a>) = SYMBOL_DISCRIMINANT,
508 Number(HeapNumber<'a>) = NUMBER_DISCRIMINANT,
509 Integer(SmallInteger) = INTEGER_DISCRIMINANT,
510 SmallF64(SmallF64) = FLOAT_DISCRIMINANT,
511 BigInt(HeapBigInt<'a>) = BIGINT_DISCRIMINANT,
512 SmallBigInt(SmallBigInt) = SMALL_BIGINT_DISCRIMINANT,
513}
514bindable_handle!(PrimitiveObjectData);
515
516impl<'a> TryFrom<PrimitiveObjectData<'a>> for BigInt<'a> {
517 type Error = ();
518
519 fn try_from(value: PrimitiveObjectData<'a>) -> Result<Self, Self::Error> {
520 match value {
521 PrimitiveObjectData::BigInt(data) => Ok(BigInt::BigInt(data)),
522 PrimitiveObjectData::SmallBigInt(data) => Ok(BigInt::SmallBigInt(data)),
523 _ => Err(()),
524 }
525 }
526}
527
528impl<'a> TryFrom<PrimitiveObjectData<'a>> for Number<'a> {
529 type Error = ();
530
531 fn try_from(value: PrimitiveObjectData<'a>) -> Result<Self, Self::Error> {
532 match value {
533 PrimitiveObjectData::Number(data) => Ok(Number::Number(data)),
534 PrimitiveObjectData::Integer(data) => Ok(Number::Integer(data)),
535 PrimitiveObjectData::SmallF64(data) => Ok(Number::SmallF64(data)),
536 _ => Err(()),
537 }
538 }
539}
540
541impl<'a> TryFrom<PrimitiveObjectData<'a>> for String<'a> {
542 type Error = ();
543
544 fn try_from(value: PrimitiveObjectData<'a>) -> Result<Self, Self::Error> {
545 match value {
546 PrimitiveObjectData::String(data) => Ok(String::String(data)),
547 PrimitiveObjectData::SmallString(data) => Ok(String::SmallString(data)),
548 _ => Err(()),
549 }
550 }
551}
552
553impl<'a> TryFrom<PrimitiveObjectData<'a>> for Symbol<'a> {
554 type Error = ();
555
556 fn try_from(value: PrimitiveObjectData<'a>) -> Result<Self, Self::Error> {
557 match value {
558 PrimitiveObjectData::Symbol(data) => Ok(data),
559 _ => Err(()),
560 }
561 }
562}
563
564impl<'a> From<PrimitiveObjectData<'a>> for Primitive<'a> {
565 fn from(value: PrimitiveObjectData<'a>) -> Self {
566 match value {
567 PrimitiveObjectData::Boolean(d) => Self::Boolean(d),
568 PrimitiveObjectData::String(d) => Self::String(d),
569 PrimitiveObjectData::SmallString(d) => Self::SmallString(d),
570 PrimitiveObjectData::Symbol(d) => Self::Symbol(d),
571 PrimitiveObjectData::Number(d) => Self::Number(d),
572 PrimitiveObjectData::Integer(d) => Self::Integer(d),
573 PrimitiveObjectData::SmallF64(d) => Self::SmallF64(d),
574 PrimitiveObjectData::BigInt(d) => Self::BigInt(d),
575 PrimitiveObjectData::SmallBigInt(d) => Self::SmallBigInt(d),
576 }
577 }
578}
579
580#[derive(Debug, Clone, Copy)]
581pub(crate) struct PrimitiveObjectRecord<'a> {
582 pub(crate) object_index: Option<OrdinaryObject<'a>>,
583 pub(crate) data: PrimitiveObjectData<'a>,
584}
585bindable_handle!(PrimitiveObjectRecord);
586
587impl<'a> PrimitiveObjectRecord<'a> {
588 pub(crate) const BLANK: Self = Self {
589 object_index: None,
590 data: PrimitiveObjectData::Boolean(false),
591 };
592
593 pub(crate) fn new_big_int_object(big_int: BigInt<'a>) -> Self {
594 let data = match big_int {
595 BigInt::BigInt(data) => PrimitiveObjectData::BigInt(data.unbind()),
596 BigInt::SmallBigInt(data) => PrimitiveObjectData::SmallBigInt(data),
597 };
598 Self {
599 object_index: None,
600 data,
601 }
602 }
603
604 pub(crate) fn new_boolean_object(boolean: bool) -> Self {
605 Self {
606 object_index: None,
607 data: PrimitiveObjectData::Boolean(boolean),
608 }
609 }
610
611 pub(crate) fn new_number_object(number: Number<'a>) -> Self {
612 let data = match number {
613 Number::Number(data) => PrimitiveObjectData::Number(data.unbind()),
614 Number::Integer(data) => PrimitiveObjectData::Integer(data),
615 Number::SmallF64(data) => PrimitiveObjectData::SmallF64(data),
616 };
617 Self {
618 object_index: None,
619 data,
620 }
621 }
622
623 pub(crate) fn new_string_object(string: String<'a>) -> Self {
624 let data = match string {
625 String::String(data) => PrimitiveObjectData::String(data),
626 String::SmallString(data) => PrimitiveObjectData::SmallString(data),
627 };
628 Self {
629 object_index: None,
630 data,
631 }
632 }
633
634 pub(crate) fn new_symbol_object(symbol: Symbol<'a>) -> Self {
635 Self {
636 object_index: None,
637 data: PrimitiveObjectData::Symbol(symbol.unbind()),
638 }
639 }
640}
641
642impl HeapMarkAndSweep for PrimitiveObjectRecord<'static> {
643 fn mark_values(&self, queues: &mut WorkQueues) {
644 let Self { object_index, data } = self;
645 object_index.mark_values(queues);
646 match data {
647 PrimitiveObjectData::String(data) => data.mark_values(queues),
648 PrimitiveObjectData::Symbol(data) => data.mark_values(queues),
649 PrimitiveObjectData::Number(data) => data.mark_values(queues),
650 PrimitiveObjectData::BigInt(data) => data.mark_values(queues),
651 _ => {}
652 }
653 }
654
655 fn sweep_values(&mut self, compactions: &CompactionLists) {
656 let Self { object_index, data } = self;
657 object_index.sweep_values(compactions);
658 match data {
659 PrimitiveObjectData::String(data) => data.sweep_values(compactions),
660 PrimitiveObjectData::Symbol(data) => data.sweep_values(compactions),
661 PrimitiveObjectData::Number(data) => data.sweep_values(compactions),
662 PrimitiveObjectData::BigInt(data) => data.sweep_values(compactions),
663 _ => {}
664 }
665 }
666}
667
668impl HeapMarkAndSweep for PrimitiveObject<'static> {
669 fn mark_values(&self, queues: &mut WorkQueues) {
670 queues.primitive_objects.push(*self);
671 }
672
673 fn sweep_values(&mut self, compactions: &CompactionLists) {
674 compactions.primitive_objects.shift_index(&mut self.0);
675 }
676}
677
678impl HeapSweepWeakReference for PrimitiveObject<'static> {
679 fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
680 compactions
681 .primitive_objects
682 .shift_weak_index(self.0)
683 .map(Self)
684 }
685}
686
687impl<'a> CreateHeapData<PrimitiveObjectRecord<'a>, PrimitiveObject<'a>> for Heap {
688 fn create(&mut self, data: PrimitiveObjectRecord<'a>) -> PrimitiveObject<'a> {
689 self.primitive_objects.push(data.unbind());
690 self.alloc_counter += core::mem::size_of::<PrimitiveObjectRecord<'static>>();
691 PrimitiveObject(BaseIndex::last(&self.primitive_objects))
692 }
693}