Skip to main content

boa_engine/object/
jsobject.rs

1//! This module implements the `JsObject` structure.
2//!
3//! The `JsObject` is a garbage collected Object.
4
5use super::{
6    JsPrototype, NativeObject, Object, ObjectData, PrivateName, PropertyMap,
7    internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
8    shape::RootShape,
9};
10use crate::{
11    Context, JsData, JsResult, JsString, JsSymbol, JsValue,
12    builtins::{
13        array::ARRAY_EXOTIC_INTERNAL_METHODS,
14        array_buffer::{ArrayBuffer, BufferObject, SharedArrayBuffer},
15        object::OrdinaryObject,
16    },
17    context::intrinsics::Intrinsics,
18    error::JsNativeError,
19    js_error, js_string,
20    property::{PropertyDescriptor, PropertyKey},
21    value::PreferredType,
22};
23use boa_gc::{self, Finalize, Gc, GcRef, GcRefCell, GcRefMut, Trace};
24use core::ptr::fn_addr_eq;
25use std::collections::HashSet;
26use std::{
27    cell::RefCell,
28    collections::HashMap,
29    error::Error,
30    fmt::{self, Debug, Display},
31    hash::Hash,
32    result::Result as StdResult,
33};
34use thin_vec::ThinVec;
35
36#[cfg(not(feature = "jsvalue-enum"))]
37use boa_gc::GcBox;
38
39#[cfg(not(feature = "jsvalue-enum"))]
40use std::ptr::NonNull;
41
42/// A wrapper type for an immutably borrowed type T.
43pub type Ref<'a, T> = GcRef<'a, T>;
44
45/// A wrapper type for a mutably borrowed type T.
46pub type RefMut<'a, T> = GcRefMut<'a, T>;
47
48pub(crate) type ErasedVTableObject = VTableObject<ErasedObjectData>;
49
50/// An `Object` with inner data set to `ErasedObjectData`.
51pub type ErasedObject = Object<ErasedObjectData>;
52
53/// A erased object data type that must never be used directly.
54#[derive(Debug, Trace, Finalize)]
55pub struct ErasedObjectData {}
56
57impl JsData for ErasedObjectData {}
58
59/// Garbage collected `Object`.
60#[derive(Trace, Finalize)]
61#[boa_gc(unsafe_no_drop)]
62pub struct JsObject<T: NativeObject = ErasedObjectData> {
63    inner: Gc<VTableObject<T>>,
64}
65
66impl<T: NativeObject> Clone for JsObject<T> {
67    fn clone(&self) -> Self {
68        Self {
69            inner: self.inner.clone(),
70        }
71    }
72}
73
74/// An `Object` that has an additional `vtable` with its internal methods.
75// We have to skip implementing `Debug` for this because not using the
76// implementation of `Debug` for `JsObject` could easily cause stack overflows,
77// so we have to force our users to debug the `JsObject` instead.
78#[allow(missing_debug_implementations)]
79#[derive(Trace, Finalize)]
80pub(crate) struct VTableObject<T: NativeObject + ?Sized> {
81    #[unsafe_ignore_trace]
82    vtable: &'static InternalObjectMethods,
83    object: GcRefCell<Object<T>>,
84}
85
86impl JsObject {
87    /// Converts the `JsObject` into a raw pointer to its inner `GcBox<ErasedVTableObject>`.
88    #[cfg(not(feature = "jsvalue-enum"))]
89    pub(crate) fn into_raw(self) -> NonNull<GcBox<ErasedVTableObject>> {
90        Gc::into_raw(self.inner)
91    }
92
93    /// Creates a new `JsObject` from a raw pointer.
94    ///
95    /// # Safety
96    /// The caller must ensure that the pointer is valid and points to a `GcBox<ErasedVTableObject>`.
97    /// The pointer must not be null.
98    #[cfg(not(feature = "jsvalue-enum"))]
99    pub(crate) unsafe fn from_raw(raw: NonNull<GcBox<ErasedVTableObject>>) -> Self {
100        // SAFETY: The caller guaranteed the value to be a valid pointer to a `GcBox<ErasedVTableObject>`.
101        let inner = unsafe { Gc::from_raw(raw) };
102
103        JsObject { inner }
104    }
105
106    /// Creates a new ordinary object with its prototype set to the `Object` prototype.
107    ///
108    /// This is an alias for [`Self::with_object_proto`].
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// # use boa_engine::{Context, JsObject};
114    /// let context = &mut Context::default();
115    /// let obj = JsObject::default(context.intrinsics());
116    ///
117    /// assert!(obj.is_ordinary());
118    /// ```
119    #[inline]
120    #[must_use]
121    pub fn default(intrinsics: &Intrinsics) -> Self {
122        Self::with_object_proto(intrinsics)
123    }
124
125    /// Creates a new `JsObject` from its inner object and its vtable.
126    pub(crate) fn from_object_and_vtable<T: NativeObject>(
127        object: Object<T>,
128        vtable: &'static InternalObjectMethods,
129    ) -> Self {
130        let inner = Gc::new(VTableObject {
131            object: GcRefCell::new(object),
132            vtable,
133        });
134
135        JsObject { inner }.upcast()
136    }
137
138    /// Creates a new ordinary object with its prototype set to the `Object` prototype.
139    ///
140    /// This is equivalent to calling the specification's abstract operation
141    /// [`OrdinaryObjectCreate(%Object.prototype%)`][call].
142    ///
143    /// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// # use boa_engine::{Context, JsObject};
149    /// let context = &mut Context::default();
150    /// let obj = JsObject::with_object_proto(context.intrinsics());
151    ///
152    /// assert!(obj.is_ordinary());
153    /// assert!(obj.prototype().is_some());
154    /// ```
155    #[inline]
156    #[must_use]
157    pub fn with_object_proto(intrinsics: &Intrinsics) -> Self {
158        Self::from_proto_and_data(
159            intrinsics.constructors().object().prototype(),
160            OrdinaryObject,
161        )
162    }
163
164    /// Creates a new ordinary object, with its prototype set to null.
165    ///
166    /// This is equivalent to calling the specification's abstract operation
167    /// [`OrdinaryObjectCreate(null)`][call].
168    ///
169    /// [call]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
170    ///
171    /// # Examples
172    ///
173    /// ```
174    /// # use boa_engine::JsObject;
175    /// let obj = JsObject::with_null_proto();
176    /// assert!(obj.prototype().is_none());
177    /// assert!(obj.is_ordinary());
178    /// ```
179    #[inline]
180    #[must_use]
181    pub fn with_null_proto() -> Self {
182        Self::from_proto_and_data(None, OrdinaryObject)
183    }
184
185    /// Creates a new object with the provided prototype and object data.
186    ///
187    /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`],
188    /// with the difference that the `additionalInternalSlotsList` parameter is determined by
189    /// the provided `data`.
190    ///
191    /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
192    ///
193    /// # Examples
194    ///
195    /// ```
196    /// # use boa_engine::{Context, JsObject};
197    /// # use boa_engine::builtins::object::OrdinaryObject;
198    /// let context = &mut Context::default();
199    /// let obj = JsObject::from_proto_and_data(
200    ///     context.intrinsics().constructors().object().prototype(),
201    ///     OrdinaryObject,
202    /// );
203    ///
204    /// assert!(obj.is_ordinary());
205    /// assert!(obj.prototype().is_some());
206    ///
207    /// // Create an object with no prototype.
208    /// let null_obj = JsObject::from_proto_and_data(None, OrdinaryObject);
209    /// assert!(null_obj.prototype().is_none());
210    /// ```
211    pub fn from_proto_and_data<O: Into<Option<Self>>, T: NativeObject>(
212        prototype: O,
213        data: T,
214    ) -> Self {
215        let internal_methods = data.internal_methods();
216        let inner = Gc::new(VTableObject {
217            object: GcRefCell::new(Object {
218                data: ObjectData::new(data),
219                properties: PropertyMap::from_prototype_unique_shape(prototype.into()),
220                extensible: true,
221                private_elements: ThinVec::new(),
222            }),
223            vtable: internal_methods,
224        });
225
226        JsObject { inner }.upcast()
227    }
228
229    /// Creates a new object with the provided prototype and object data.
230    ///
231    /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`],
232    /// with the difference that the `additionalInternalSlotsList` parameter is determined by
233    /// the provided `data`.
234    ///
235    /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
236    pub(crate) fn from_proto_and_data_with_shared_shape<O: Into<Option<Self>>, T: NativeObject>(
237        root_shape: &RootShape,
238        prototype: O,
239        data: T,
240    ) -> JsObject<T> {
241        let internal_methods = data.internal_methods();
242        let inner = Gc::new(VTableObject {
243            object: GcRefCell::new(Object {
244                data: ObjectData::new(data),
245                properties: PropertyMap::from_prototype_with_shared_shape(
246                    root_shape,
247                    prototype.into(),
248                ),
249                extensible: true,
250                private_elements: ThinVec::new(),
251            }),
252            vtable: internal_methods,
253        });
254
255        JsObject { inner }
256    }
257
258    /// Downcasts the object's inner data if the object is of type `T`.
259    ///
260    /// # Panics
261    ///
262    /// Panics if the object is currently mutably borrowed.
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// # use boa_engine::{JsObject, JsData, Trace, Finalize};
268    /// # use boa_engine::builtins::object::OrdinaryObject;
269    /// #[derive(Debug, Trace, Finalize, JsData)]
270    /// struct CustomStruct;
271    ///
272    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
273    ///
274    /// // Downcast consumes the object on success.
275    /// let typed = obj.downcast::<OrdinaryObject>();
276    /// assert!(typed.is_ok());
277    ///
278    /// // Downcast fails for a wrong type, returning the original object.
279    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
280    /// let result = obj.downcast::<CustomStruct>();
281    /// assert!(result.is_err());
282    /// ```
283    pub fn downcast<T: NativeObject>(self) -> Result<JsObject<T>, Self> {
284        if self.is::<T>() {
285            // SAFETY: We have verified that the object is of type `T`, so we can safely cast it.
286            let object = unsafe { self.downcast_unchecked::<T>() };
287
288            Ok(object)
289        } else {
290            Err(self)
291        }
292    }
293
294    /// Downcasts the object's inner data to `T` without verifying the inner type of `T`.
295    ///
296    /// # Safety
297    ///
298    /// For this cast to be sound, `self` must contain an instance of `T` inside its inner data.
299    #[must_use]
300    pub unsafe fn downcast_unchecked<T: NativeObject>(self) -> JsObject<T> {
301        // SAFETY: The caller guarantees `T` is the original inner data type of the underlying
302        // object.
303        // The pointer is guaranteed to be valid because we just created it.
304        // `VTableObject<ErasedObjectData>` and `VTableObject<T>` have the same size and alignment.
305        let inner = unsafe { Gc::cast_unchecked::<VTableObject<T>>(self.inner) };
306
307        JsObject { inner }
308    }
309
310    /// Downcasts a reference to the object,
311    /// if the object is of type `T`.
312    ///
313    /// # Panics
314    ///
315    /// Panics if the object is currently mutably borrowed.
316    ///
317    /// # Examples
318    ///
319    /// ```
320    /// # use boa_engine::{JsObject, JsData, Trace, Finalize};
321    /// # use boa_engine::builtins::object::OrdinaryObject;
322    /// #[derive(Debug, Trace, Finalize, JsData)]
323    /// struct CustomStruct;
324    ///
325    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
326    ///
327    /// // Downcast ref succeeds for the correct type.
328    /// assert!(obj.downcast_ref::<OrdinaryObject>().is_some());
329    ///
330    /// // Returns `None` for a wrong type.
331    /// assert!(obj.downcast_ref::<CustomStruct>().is_none());
332    /// ```
333    #[must_use]
334    #[track_caller]
335    pub fn downcast_ref<T: NativeObject>(&self) -> Option<Ref<'_, T>> {
336        if self.is::<T>() {
337            let obj = self.borrow();
338
339            // SAFETY: We have verified that the object is of type `T`, so we can safely cast it.
340            let obj = unsafe { GcRef::cast::<Object<T>>(obj) };
341
342            return Some(Ref::map(obj, |r| r.data()));
343        }
344        None
345    }
346
347    /// Downcasts a mutable reference to the object,
348    /// if the object is type native object type `T`.
349    ///
350    /// # Panics
351    ///
352    /// Panics if the object is currently borrowed.
353    ///
354    /// # Examples
355    ///
356    /// ```
357    /// # use boa_engine::{JsObject, JsData, Trace, Finalize};
358    /// # use boa_engine::builtins::object::OrdinaryObject;
359    /// #[derive(Debug, Trace, Finalize, JsData)]
360    /// struct CustomStruct;
361    ///
362    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
363    ///
364    /// // Downcast mut succeeds for the correct type.
365    /// assert!(obj.downcast_mut::<OrdinaryObject>().is_some());
366    ///
367    /// // Returns `None` for a wrong type.
368    /// assert!(obj.downcast_mut::<CustomStruct>().is_none());
369    /// ```
370    #[must_use]
371    #[track_caller]
372    pub fn downcast_mut<T: NativeObject>(&self) -> Option<RefMut<'_, T>> {
373        if self.is::<T>() {
374            let obj = self.borrow_mut();
375
376            // SAFETY: We have verified that the object is of type `T`, so we can safely cast it.
377            let obj = unsafe { GcRefMut::cast::<Object<T>>(obj) };
378
379            return Some(RefMut::map(obj, |c| c.data_mut()));
380        }
381        None
382    }
383
384    /// Checks if this object is an instance of a certain `NativeObject`.
385    ///
386    /// # Panics
387    ///
388    /// Panics if the object is currently mutably borrowed.
389    ///
390    /// # Examples
391    ///
392    /// ```
393    /// # use boa_engine::{JsObject, JsData, Trace, Finalize};
394    /// # use boa_engine::builtins::object::OrdinaryObject;
395    /// #[derive(Debug, Trace, Finalize, JsData)]
396    /// struct CustomStruct;
397    ///
398    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
399    ///
400    /// assert!(obj.is::<OrdinaryObject>());
401    /// assert!(!obj.is::<CustomStruct>());
402    /// ```
403    #[inline]
404    #[must_use]
405    #[track_caller]
406    pub fn is<T: NativeObject>(&self) -> bool {
407        Gc::is::<VTableObject<T>>(&self.inner)
408    }
409
410    /// Checks if it's an ordinary object.
411    ///
412    /// # Panics
413    ///
414    /// Panics if the object is currently mutably borrowed.
415    ///
416    /// # Examples
417    ///
418    /// ```
419    /// # use boa_engine::{Context, JsObject};
420    /// let context = &mut Context::default();
421    /// let obj = JsObject::with_object_proto(context.intrinsics());
422    ///
423    /// assert!(obj.is_ordinary());
424    /// ```
425    #[inline]
426    #[must_use]
427    #[track_caller]
428    pub fn is_ordinary(&self) -> bool {
429        self.is::<OrdinaryObject>()
430    }
431
432    /// Checks if it's an `Array` object.
433    ///
434    /// # Examples
435    ///
436    /// ```
437    /// # use boa_engine::{Context, JsObject, JsValue ,JsResult};
438    /// # use boa_engine::object::builtins::JsArray;
439    /// # fn main() -> JsResult<()> {
440    /// let context = &mut Context::default();
441    ///
442    /// let array = JsArray::new(context)?;
443    /// // A JsArray's inner JsObject is an array.
444    /// assert!(JsObject::from(array).is_array());
445    ///
446    /// // An ordinary object is not an array.
447    /// let obj = JsObject::with_object_proto(context.intrinsics());
448    /// assert!(!obj.is_array());
449    /// # Ok(())
450    /// # }
451    /// ```
452    #[inline]
453    #[must_use]
454    #[track_caller]
455    pub fn is_array(&self) -> bool {
456        std::ptr::eq(self.vtable(), &raw const ARRAY_EXOTIC_INTERNAL_METHODS)
457    }
458
459    /// The inner implementation of `deep_strict_equals`, which keeps a list of values we've
460    /// seen to avoid recursive objects.
461    pub(crate) fn deep_strict_equals_inner(
462        lhs: &Self,
463        rhs: &Self,
464        encounters: &mut HashSet<usize>,
465        context: &mut Context,
466    ) -> JsResult<bool> {
467        // Loop through all the keys and if one is not equal, return false.
468        fn key_loop(
469            lhs: &JsObject,
470            rhs: &JsObject,
471            encounters: &mut HashSet<usize>,
472            context: &mut Context,
473        ) -> JsResult<bool> {
474            let l_keys = lhs.own_property_keys(context)?;
475            let r_keys = rhs.own_property_keys(context)?;
476
477            if l_keys.len() != r_keys.len() {
478                return Ok(false);
479            }
480
481            for key in &l_keys {
482                let vl = lhs.get_property(key);
483                let vr = rhs.get_property(key);
484
485                match (vl, vr) {
486                    (None, None) => {}
487                    (Some(vl), Some(vr)) => match (vl.value(), vr.value()) {
488                        (None, None) => {}
489                        (Some(lv), Some(rv)) => {
490                            if !lv.deep_strict_equals_inner(rv, encounters, context)? {
491                                return Ok(false);
492                            }
493                        }
494                        _ => {
495                            return Ok(false);
496                        }
497                    },
498                    _ => {
499                        return Ok(false);
500                    }
501                }
502            }
503            Ok(true)
504        }
505
506        let addr_l = std::ptr::from_ref::<Self>(lhs) as usize;
507        let addr_r = std::ptr::from_ref::<Self>(rhs) as usize;
508
509        if addr_r == addr_l {
510            return Ok(true);
511        }
512
513        let contains_l = encounters.contains(&addr_l);
514        let contains_r = encounters.contains(&addr_r);
515        if contains_l || contains_r {
516            return Ok(false);
517        }
518
519        encounters.insert(addr_l);
520        encounters.insert(addr_r);
521
522        // Make sure we clean up after the recursion.
523        let result = key_loop(lhs, rhs, encounters, context);
524        encounters.remove(&addr_l);
525        encounters.remove(&addr_r);
526        result
527    }
528
529    /// Checks that all own property keys and values are equal (recursively).
530    ///
531    /// # Examples
532    ///
533    /// ```
534    /// # use boa_engine::{Context, JsObject, JsResult, js_string};
535    /// # fn main() -> JsResult<()> {
536    /// let context = &mut Context::default();
537    ///
538    /// let obj1 = JsObject::with_object_proto(context.intrinsics());
539    /// obj1.set(js_string!("key"), 42, false, context)?;
540    ///
541    /// let obj2 = JsObject::with_object_proto(context.intrinsics());
542    /// obj2.set(js_string!("key"), 42, false, context)?;
543    ///
544    /// assert!(JsObject::deep_strict_equals(&obj1, &obj2, context)?);
545    /// # Ok(())
546    /// # }
547    /// ```
548    #[inline]
549    pub fn deep_strict_equals(lhs: &Self, rhs: &Self, context: &mut Context) -> JsResult<bool> {
550        Self::deep_strict_equals_inner(lhs, rhs, &mut HashSet::new(), context)
551    }
552
553    /// The abstract operation `ToPrimitive` takes an input argument and an optional argument
554    /// `PreferredType`.
555    ///
556    /// <https://tc39.es/ecma262/#sec-toprimitive>
557    pub fn to_primitive(
558        &self,
559        context: &mut Context,
560        preferred_type: PreferredType,
561    ) -> JsResult<JsValue> {
562        // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
563        let Some(exotic_to_prim) = self.get_method(JsSymbol::to_primitive(), context)? else {
564            // c. If preferredType is not present, let preferredType be number.
565            let preferred_type = match preferred_type {
566                PreferredType::Default | PreferredType::Number => PreferredType::Number,
567                PreferredType::String => PreferredType::String,
568            };
569            return self.ordinary_to_primitive(context, preferred_type);
570        };
571
572        // b. If exoticToPrim is not undefined, then
573        //    i. If preferredType is not present, let hint be "default".
574        //    ii. Else if preferredType is string, let hint be "string".
575        //    iii. Else,
576        //        1. Assert: preferredType is number.
577        //        2. Let hint be "number".
578        let hint = match preferred_type {
579            PreferredType::Default => js_string!("default"),
580            PreferredType::String => js_string!("string"),
581            PreferredType::Number => js_string!("number"),
582        }
583        .into();
584
585        //    iv. Let result be ? Call(exoticToPrim, input, « hint »).
586        let result = exotic_to_prim.call(&self.clone().into(), &[hint], context)?;
587
588        //    v. If Type(result) is not Object, return result.
589        //    vi. Throw a TypeError exception.
590        if result.is_object() {
591            Err(js_error!(
592                TypeError: "method `[Symbol.toPrimitive]` cannot return an object"
593            ))
594        } else {
595            Ok(result)
596        }
597    }
598
599    /// Converts an object to a primitive.
600    ///
601    /// Diverges from the spec to prevent a stack overflow when the object is recursive.
602    /// For example,
603    /// ```javascript
604    /// let a = [1];
605    /// a[1] = a;
606    /// console.log(a.toString()); // We print "1,"
607    /// ```
608    /// The spec doesn't mention what to do in this situation, but a naive implementation
609    /// would overflow the stack recursively calling `toString()`. We follow v8 and SpiderMonkey
610    /// instead by returning a default value for the given `hint` -- either `0.` or `""`.
611    /// Example in v8: <https://repl.it/repls/IvoryCircularCertification#index.js>
612    ///
613    /// More information:
614    ///  - [ECMAScript][spec]
615    ///
616    /// [spec]: https://tc39.es/ecma262/#sec-ordinarytoprimitive
617    pub(crate) fn ordinary_to_primitive(
618        &self,
619        context: &mut Context,
620        hint: PreferredType,
621    ) -> JsResult<JsValue> {
622        // 1. Assert: Type(O) is Object.
623        //      Already is JsObject by type.
624        // 2. Assert: Type(hint) is String and its value is either "string" or "number".
625        debug_assert!(hint == PreferredType::String || hint == PreferredType::Number);
626
627        // Diverge from the spec here to make sure we aren't going to overflow the stack by converting
628        // a recursive structure
629        // We can follow v8 & SpiderMonkey's lead and return a default value for the hint in this situation
630        // (see https://repl.it/repls/IvoryCircularCertification#index.js)
631        let recursion_limiter = RecursionLimiter::new(self.as_ref());
632        if recursion_limiter.live {
633            // we're in a recursive object, bail
634            return Ok(match hint {
635                PreferredType::Number => JsValue::new(0),
636                PreferredType::String => JsValue::new(js_string!()),
637                PreferredType::Default => unreachable!("checked type hint in step 2"),
638            });
639        }
640
641        // 3. If hint is "string", then
642        //    a. Let methodNames be « "toString", "valueOf" ».
643        // 4. Else,
644        //    a. Let methodNames be « "valueOf", "toString" ».
645        let method_names = if hint == PreferredType::String {
646            [js_string!("toString"), js_string!("valueOf")]
647        } else {
648            [js_string!("valueOf"), js_string!("toString")]
649        };
650
651        // 5. For each name in methodNames in List order, do
652        for name in method_names {
653            // a. Let method be ? Get(O, name).
654            let method = self.get(name, context)?;
655
656            // b. If IsCallable(method) is true, then
657            if let Some(method) = method.as_callable() {
658                // i. Let result be ? Call(method, O).
659                let result = method.call(&self.clone().into(), &[], context)?;
660
661                // ii. If Type(result) is not Object, return result.
662                if !result.is_object() {
663                    return Ok(result);
664                }
665            }
666        }
667
668        // 6. Throw a TypeError exception.
669        Err(JsNativeError::typ()
670            .with_message("cannot convert object to primitive value")
671            .into())
672    }
673
674    /// The abstract operation `ToPropertyDescriptor`.
675    ///
676    /// More information:
677    /// - [ECMAScript reference][spec]
678    ///
679    /// [spec]: https://tc39.es/ecma262/#sec-topropertydescriptor
680    pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
681        // 1 is implemented on the method `to_property_descriptor` of value
682
683        // 2. Let desc be a new Property Descriptor that initially has no fields.
684        let mut desc = PropertyDescriptor::builder();
685
686        // 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable").
687        // 4. If hasEnumerable is true, then ...
688        if let Some(enumerable) = self.try_get(js_string!("enumerable"), context)? {
689            // a. Let enumerable be ! ToBoolean(? Get(Obj, "enumerable")).
690            // b. Set desc.[[Enumerable]] to enumerable.
691            desc = desc.enumerable(enumerable.to_boolean());
692        }
693
694        // 5. Let hasConfigurable be ? HasProperty(Obj, "configurable").
695        // 6. If hasConfigurable is true, then ...
696        if let Some(configurable) = self.try_get(js_string!("configurable"), context)? {
697            // a. Let configurable be ! ToBoolean(? Get(Obj, "configurable")).
698            // b. Set desc.[[Configurable]] to configurable.
699            desc = desc.configurable(configurable.to_boolean());
700        }
701
702        // 7. Let hasValue be ? HasProperty(Obj, "value").
703        // 8. If hasValue is true, then ...
704        if let Some(value) = self.try_get(js_string!("value"), context)? {
705            // a. Let value be ? Get(Obj, "value").
706            // b. Set desc.[[Value]] to value.
707            desc = desc.value(value);
708        }
709
710        // 9. Let hasWritable be ? HasProperty(Obj, ).
711        // 10. If hasWritable is true, then ...
712        if let Some(writable) = self.try_get(js_string!("writable"), context)? {
713            // a. Let writable be ! ToBoolean(? Get(Obj, "writable")).
714            // b. Set desc.[[Writable]] to writable.
715            desc = desc.writable(writable.to_boolean());
716        }
717
718        // 11. Let hasGet be ? HasProperty(Obj, "get").
719        // 12. If hasGet is true, then
720        // 12.a. Let getter be ? Get(Obj, "get").
721        let get = if let Some(getter) = self.try_get(js_string!("get"), context)? {
722            // b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception.
723            // todo: extract IsCallable to be callable from Value
724            if !getter.is_undefined() && getter.as_object().is_none_or(|o| !o.is_callable()) {
725                return Err(JsNativeError::typ()
726                    .with_message("Property descriptor getter must be callable")
727                    .into());
728            }
729            // c. Set desc.[[Get]] to getter.
730            Some(getter)
731        } else {
732            None
733        };
734
735        // 13. Let hasSet be ? HasProperty(Obj, "set").
736        // 14. If hasSet is true, then
737        // 14.a. Let setter be ? Get(Obj, "set").
738        let set = if let Some(setter) = self.try_get(js_string!("set"), context)? {
739            // 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception.
740            // todo: extract IsCallable to be callable from Value
741            if !setter.is_undefined() && setter.as_object().is_none_or(|o| !o.is_callable()) {
742                return Err(JsNativeError::typ()
743                    .with_message("Property descriptor setter must be callable")
744                    .into());
745            }
746            // 14.c. Set desc.[[Set]] to setter.
747            Some(setter)
748        } else {
749            None
750        };
751
752        // 15. If desc.[[Get]] is present or desc.[[Set]] is present, then ...
753        // a. If desc.[[Value]] is present or desc.[[Writable]] is present, throw a TypeError exception.
754        if get.as_ref().or(set.as_ref()).is_some() && desc.inner().is_data_descriptor() {
755            return Err(JsNativeError::typ()
756                .with_message(
757                    "Invalid property descriptor.\
758Cannot both specify accessors and a value or writable attribute",
759                )
760                .into());
761        }
762
763        desc = desc.maybe_get(get).maybe_set(set);
764
765        // 16. Return desc.
766        Ok(desc.build())
767    }
768
769    // Allow lint, false positive.
770    #[allow(clippy::assigning_clones)]
771    pub(crate) fn get_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
772        let mut obj = Some(self.clone());
773
774        while let Some(o) = obj {
775            if let Some(v) = o.borrow().properties.get(key) {
776                return Some(v);
777            }
778            obj = o.borrow().prototype().clone();
779        }
780        None
781    }
782
783    /// Casts to a `BufferObject` if the object is an `ArrayBuffer` or a `SharedArrayBuffer`.
784    #[inline]
785    pub(crate) fn into_buffer_object(self) -> Result<BufferObject, JsObject> {
786        match self.downcast::<ArrayBuffer>() {
787            Ok(buffer) => Ok(BufferObject::Buffer(buffer)),
788            Err(object) => object
789                .downcast::<SharedArrayBuffer>()
790                .map(BufferObject::SharedBuffer),
791        }
792    }
793}
794
795impl<T: NativeObject> JsObject<T> {
796    /// Immutably borrows the `Object`.
797    ///
798    /// The borrow lasts until the returned `Ref` exits scope.
799    /// Multiple immutable borrows can be taken out at the same time.
800    ///
801    /// # Panics
802    ///
803    /// Panics if the object is currently mutably borrowed.
804    ///
805    /// # Examples
806    ///
807    /// ```
808    /// # use boa_engine::JsObject;
809    /// # use boa_engine::builtins::object::OrdinaryObject;
810    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
811    ///
812    /// // Multiple immutable borrows are allowed.
813    /// let borrowed = obj.borrow();
814    /// assert!(borrowed.prototype().is_none());
815    /// ```
816    #[inline]
817    #[must_use]
818    #[track_caller]
819    pub fn borrow(&self) -> Ref<'_, Object<T>> {
820        self.try_borrow().expect("Object already mutably borrowed")
821    }
822
823    /// Mutably borrows the Object.
824    ///
825    /// The borrow lasts until the returned `RefMut` exits scope.
826    /// The object cannot be borrowed while this borrow is active.
827    ///
828    /// # Panics
829    /// Panics if the object is currently borrowed.
830    ///
831    /// # Examples
832    ///
833    /// ```
834    /// # use boa_engine::{Context, JsObject};
835    /// # use boa_engine::builtins::object::OrdinaryObject;
836    /// let context = &mut Context::default();
837    /// let obj = JsObject::from_proto_and_data(
838    ///     context.intrinsics().constructors().object().prototype(),
839    ///     OrdinaryObject,
840    /// );
841    ///
842    /// // Set the prototype to `None` via a mutable borrow.
843    /// obj.borrow_mut().set_prototype(None);
844    /// assert!(obj.prototype().is_none());
845    /// ```
846    #[inline]
847    #[must_use]
848    #[track_caller]
849    pub fn borrow_mut(&self) -> RefMut<'_, Object<T>> {
850        self.try_borrow_mut().expect("Object already borrowed")
851    }
852
853    /// Immutably borrows the `Object`, returning an error if the value is currently mutably borrowed.
854    ///
855    /// The borrow lasts until the returned `GcCellRef` exits scope.
856    /// Multiple immutable borrows can be taken out at the same time.
857    ///
858    /// This is the non-panicking variant of [`borrow`](#method.borrow).
859    ///
860    /// # Examples
861    ///
862    /// ```
863    /// # use boa_engine::JsObject;
864    /// # use boa_engine::builtins::object::OrdinaryObject;
865    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
866    ///
867    /// // Non-panicking immutable borrow.
868    /// let result = obj.try_borrow();
869    /// assert!(result.is_ok());
870    /// ```
871    #[inline]
872    pub fn try_borrow(&self) -> StdResult<Ref<'_, Object<T>>, BorrowError> {
873        self.inner.object.try_borrow().map_err(|_| BorrowError)
874    }
875
876    /// Mutably borrows the object, returning an error if the value is currently borrowed.
877    ///
878    /// The borrow lasts until the returned `GcCellRefMut` exits scope.
879    /// The object be borrowed while this borrow is active.
880    ///
881    /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
882    ///
883    /// # Examples
884    ///
885    /// ```
886    /// # use boa_engine::JsObject;
887    /// # use boa_engine::builtins::object::OrdinaryObject;
888    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
889    ///
890    /// // Non-panicking mutable borrow.
891    /// let result = obj.try_borrow_mut();
892    /// assert!(result.is_ok());
893    /// ```
894    #[inline]
895    pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_, Object<T>>, BorrowMutError> {
896        self.inner
897            .object
898            .try_borrow_mut()
899            .map_err(|_| BorrowMutError)
900    }
901
902    /// Checks if the garbage collected memory is the same.
903    ///
904    /// # Examples
905    ///
906    /// ```
907    /// # use boa_engine::JsObject;
908    /// # use boa_engine::builtins::object::OrdinaryObject;
909    /// let obj = JsObject::from_proto_and_data(None, OrdinaryObject);
910    /// let clone = obj.clone();
911    ///
912    /// // A clone points to the same GC allocation.
913    /// assert!(JsObject::equals(&obj, &clone));
914    ///
915    /// // A separate object is different, even with identical data.
916    /// let other = JsObject::from_proto_and_data(None, OrdinaryObject);
917    /// assert!(!JsObject::equals(&obj, &other));
918    /// ```
919    #[must_use]
920    #[inline]
921    pub fn equals(lhs: &Self, rhs: &Self) -> bool {
922        Gc::ptr_eq(lhs.inner(), rhs.inner())
923    }
924
925    /// Get the prototype of the object.
926    ///
927    /// # Panics
928    ///
929    /// Panics if the object is currently mutably borrowed.
930    ///
931    /// # Examples
932    ///
933    /// ```
934    /// # use boa_engine::{Context, JsObject};
935    /// let context = &mut Context::default();
936    ///
937    /// let obj = JsObject::with_object_proto(context.intrinsics());
938    /// assert!(obj.prototype().is_some());
939    ///
940    /// let null_obj = JsObject::with_null_proto();
941    /// assert!(null_obj.prototype().is_none());
942    /// ```
943    #[inline]
944    #[must_use]
945    #[track_caller]
946    pub fn prototype(&self) -> JsPrototype {
947        self.borrow().prototype()
948    }
949
950    /// Get the extensibility of the object.
951    ///
952    /// # Panics
953    ///
954    /// Panics if the object is currently mutably borrowed.
955    pub(crate) fn extensible(&self) -> bool {
956        self.borrow().extensible
957    }
958
959    /// Set the prototype of the object.
960    ///
961    /// # Panics
962    ///
963    /// Panics if the object is currently mutably borrowed
964    ///
965    /// # Examples
966    ///
967    /// ```
968    /// # use boa_engine::{Context, JsObject};
969    /// let context = &mut Context::default();
970    /// let obj = JsObject::with_object_proto(context.intrinsics());
971    ///
972    /// assert!(obj.prototype().is_some());
973    ///
974    /// // Set the prototype to `None`.
975    /// obj.set_prototype(None);
976    /// assert!(obj.prototype().is_none());
977    /// ```
978    #[inline]
979    #[track_caller]
980    #[allow(clippy::must_use_candidate)]
981    pub fn set_prototype(&self, prototype: JsPrototype) -> bool {
982        self.borrow_mut().set_prototype(prototype)
983    }
984
985    /// Helper function for property insertion.
986    #[track_caller]
987    pub(crate) fn insert<K, P>(&self, key: K, property: P) -> bool
988    where
989        K: Into<PropertyKey>,
990        P: Into<PropertyDescriptor>,
991    {
992        self.borrow_mut().insert(key, property)
993    }
994
995    /// Inserts a field in the object `properties` without checking if it's writable.
996    ///
997    /// If a field was already in the object with the same name, than `true` is returned
998    /// with that field, otherwise `false` is returned.
999    pub fn insert_property<K, P>(&self, key: K, property: P) -> bool
1000    where
1001        K: Into<PropertyKey>,
1002        P: Into<PropertyDescriptor>,
1003    {
1004        self.insert(key.into(), property)
1005    }
1006
1007    /// It determines if Object is a callable function with a `[[Call]]` internal method.
1008    ///
1009    /// More information:
1010    /// - [ECMAScript reference][spec]
1011    ///
1012    /// [spec]: https://tc39.es/ecma262/#sec-iscallable
1013    #[inline]
1014    #[must_use]
1015    pub fn is_callable(&self) -> bool {
1016        !fn_addr_eq(
1017            self.inner.vtable.__call__,
1018            ORDINARY_INTERNAL_METHODS.__call__,
1019        )
1020    }
1021
1022    /// It determines if Object is a function object with a `[[Construct]]` internal method.
1023    ///
1024    /// More information:
1025    /// - [ECMAScript reference][spec]
1026    ///
1027    /// [spec]: https://tc39.es/ecma262/#sec-isconstructor
1028    #[inline]
1029    #[must_use]
1030    pub fn is_constructor(&self) -> bool {
1031        !fn_addr_eq(
1032            self.inner.vtable.__construct__,
1033            ORDINARY_INTERNAL_METHODS.__construct__,
1034        )
1035    }
1036
1037    pub(crate) fn vtable(&self) -> &'static InternalObjectMethods {
1038        self.inner.vtable
1039    }
1040
1041    pub(crate) fn inner(&self) -> &Gc<VTableObject<T>> {
1042        &self.inner
1043    }
1044
1045    pub(crate) fn from_inner(inner: Gc<VTableObject<T>>) -> Self {
1046        Self { inner }
1047    }
1048
1049    /// Create a new private name with this object as the unique identifier.
1050    pub(crate) fn private_name(&self, description: JsString) -> PrivateName {
1051        let ptr: *const _ = self.as_ref();
1052        PrivateName::new(description, ptr.cast::<()>() as usize)
1053    }
1054}
1055
1056impl<T: NativeObject> JsObject<T> {
1057    /// Creates a new `JsObject` from its root shape, prototype, and data.
1058    ///
1059    /// Note that the returned object will not be erased to be convertible to a
1060    /// `JsValue`. To erase the pointer, call [`JsObject::upcast`].
1061    ///
1062    /// # Examples
1063    ///
1064    /// ```
1065    /// # use boa_engine::{Context, JsObject};
1066    /// # use boa_engine::builtins::object::OrdinaryObject;
1067    /// let context = &mut Context::default();
1068    ///
1069    /// let typed_obj = JsObject::new(
1070    ///     context.root_shape(),
1071    ///     context.intrinsics().constructors().object().prototype(),
1072    ///     OrdinaryObject,
1073    /// );
1074    ///
1075    /// // Upcast to an erased JsObject to use with JsValue.
1076    /// let obj = typed_obj.upcast();
1077    /// assert!(obj.is_ordinary());
1078    /// ```
1079    pub fn new<O: Into<Option<JsObject>>>(root_shape: &RootShape, prototype: O, data: T) -> Self {
1080        let internal_methods = data.internal_methods();
1081        let inner = Gc::new(VTableObject {
1082            object: GcRefCell::new(Object {
1083                data: ObjectData::new(data),
1084                properties: PropertyMap::from_prototype_with_shared_shape(
1085                    root_shape,
1086                    prototype.into(),
1087                ),
1088                extensible: true,
1089                private_elements: ThinVec::new(),
1090            }),
1091            vtable: internal_methods,
1092        });
1093
1094        Self { inner }
1095    }
1096
1097    /// Creates a new `JsObject` from prototype, and data.
1098    ///
1099    /// Note that the returned object will not be erased to be convertible to a
1100    /// `JsValue`. To erase the pointer, call [`JsObject::upcast`].
1101    ///
1102    /// # Examples
1103    ///
1104    /// ```
1105    /// # use boa_engine::JsObject;
1106    /// # use boa_engine::builtins::object::OrdinaryObject;
1107    /// let typed_obj = JsObject::new_unique(None, OrdinaryObject);
1108    ///
1109    /// // Upcast to an erased JsObject.
1110    /// let obj = typed_obj.upcast();
1111    /// assert!(obj.is_ordinary());
1112    /// assert!(obj.prototype().is_none());
1113    /// ```
1114    pub fn new_unique<O: Into<Option<JsObject>>>(prototype: O, data: T) -> Self {
1115        let internal_methods = data.internal_methods();
1116        let inner = Gc::new(VTableObject {
1117            object: GcRefCell::new(Object {
1118                data: ObjectData::new(data),
1119                properties: PropertyMap::from_prototype_unique_shape(prototype.into()),
1120                extensible: true,
1121                private_elements: ThinVec::new(),
1122            }),
1123            vtable: internal_methods,
1124        });
1125
1126        Self { inner }
1127    }
1128
1129    /// Upcasts this object's inner data from a specific type `T` to an erased type
1130    /// `dyn NativeObject`.
1131    ///
1132    /// # Examples
1133    ///
1134    /// ```
1135    /// # use boa_engine::JsObject;
1136    /// # use boa_engine::builtins::object::OrdinaryObject;
1137    /// // Create a typed JsObject<OrdinaryObject>.
1138    /// let typed_obj = JsObject::new_unique(None, OrdinaryObject);
1139    ///
1140    /// // Upcast erases the type, producing an untyped JsObject.
1141    /// let obj: JsObject = typed_obj.upcast();
1142    /// assert!(obj.is_ordinary());
1143    /// ```
1144    #[must_use]
1145    pub fn upcast(self) -> JsObject {
1146        // SAFETY: The pointer is guaranteed to be valid.
1147        // `VTableObject<ErasedObjectData>` and `VTableObject<T>` have the same size and alignment.
1148        let inner = unsafe { Gc::cast_unchecked::<ErasedVTableObject>(self.inner) };
1149
1150        JsObject { inner }
1151    }
1152}
1153
1154impl<T: NativeObject> AsRef<GcRefCell<Object<T>>> for JsObject<T> {
1155    #[inline]
1156    fn as_ref(&self) -> &GcRefCell<Object<T>> {
1157        &self.inner.object
1158    }
1159}
1160
1161impl<T: NativeObject> From<Gc<VTableObject<T>>> for JsObject<T> {
1162    #[inline]
1163    fn from(inner: Gc<VTableObject<T>>) -> Self {
1164        Self { inner }
1165    }
1166}
1167
1168impl<T: NativeObject> PartialEq for JsObject<T> {
1169    fn eq(&self, other: &Self) -> bool {
1170        Self::equals(self, other)
1171    }
1172}
1173
1174impl<T: NativeObject> Eq for JsObject<T> {}
1175
1176impl<T: NativeObject> Hash for JsObject<T> {
1177    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1178        std::ptr::hash(self.as_ref(), state);
1179    }
1180}
1181
1182/// An error returned by [`JsObject::try_borrow`](struct.JsObject.html#method.try_borrow).
1183#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1184pub struct BorrowError;
1185
1186impl Display for BorrowError {
1187    #[inline]
1188    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1189        Display::fmt("Object already mutably borrowed", f)
1190    }
1191}
1192
1193impl Error for BorrowError {}
1194
1195/// An error returned by [`JsObject::try_borrow_mut`](struct.JsObject.html#method.try_borrow_mut).
1196#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1197pub struct BorrowMutError;
1198
1199impl Display for BorrowMutError {
1200    #[inline]
1201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1202        Display::fmt("Object already borrowed", f)
1203    }
1204}
1205
1206impl Error for BorrowMutError {}
1207
1208#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1209enum RecursionValueState {
1210    /// This value is "live": there's an active `RecursionLimiter` that hasn't been dropped.
1211    Live,
1212    /// This value has been seen before, but the recursion limiter has been dropped.
1213    /// For example:
1214    /// ```javascript
1215    /// let b = [];
1216    /// JSON.stringify([ // Create a recursion limiter for the root here
1217    ///    b,            // state for b's &JsObject here is None
1218    ///    b,            // state for b's &JsObject here is Visited
1219    /// ]);
1220    /// ```
1221    Visited,
1222}
1223
1224/// Prevents infinite recursion during `Debug::fmt`, `JSON.stringify`, and other conversions.
1225/// This uses a thread local, so is not safe to use where the object graph will be traversed by
1226/// multiple threads!
1227#[derive(Debug)]
1228pub struct RecursionLimiter {
1229    /// If this was the first `JsObject` in the tree.
1230    top_level: bool,
1231    /// The ptr being kept in the `HashSet`, so we can delete it when we drop.
1232    ptr: usize,
1233    /// If this `JsObject` has been visited before in the graph, but not in the current branch.
1234    pub visited: bool,
1235    /// If this `JsObject` has been visited in the current branch of the graph.
1236    pub live: bool,
1237}
1238
1239impl Drop for RecursionLimiter {
1240    fn drop(&mut self) {
1241        if self.top_level {
1242            // When the top level of the graph is dropped, we can free the entire map for the next traversal.
1243            SEEN.with(|hm| hm.borrow_mut().clear());
1244        } else if !self.live {
1245            // This was the first RL for this object to become live, so it's no longer live now that it's dropped.
1246            SEEN.with(|hm| {
1247                hm.borrow_mut()
1248                    .insert(self.ptr, RecursionValueState::Visited)
1249            });
1250        }
1251    }
1252}
1253
1254thread_local! {
1255    /// The map of pointers to `JsObject` that have been visited during the current `Debug::fmt` graph,
1256    /// and the current state of their RecursionLimiter (dropped or live -- see `RecursionValueState`)
1257    static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new());
1258}
1259
1260impl RecursionLimiter {
1261    /// Determines if the specified `T` has been visited, and returns a struct that will free it when dropped.
1262    ///
1263    /// This is done by maintaining a thread-local hashset containing the pointers of `T` values that have been
1264    /// visited. The first `T` visited will clear the hashset, while any others will check if they are contained
1265    /// by the hashset.
1266    pub fn new<T: ?Sized>(o: &T) -> Self {
1267        let ptr: *const _ = o;
1268        let ptr = ptr.cast::<()>() as usize;
1269        let (top_level, visited, live) = SEEN.with(|hm| {
1270            let mut hm = hm.borrow_mut();
1271            let top_level = hm.is_empty();
1272            let old_state = hm.insert(ptr, RecursionValueState::Live);
1273
1274            (
1275                top_level,
1276                old_state == Some(RecursionValueState::Visited),
1277                old_state == Some(RecursionValueState::Live),
1278            )
1279        });
1280
1281        Self {
1282            top_level,
1283            ptr,
1284            visited,
1285            live,
1286        }
1287    }
1288}
1289
1290impl<T: NativeObject> Debug for JsObject<T> {
1291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1292        let limiter = RecursionLimiter::new(self.as_ref());
1293
1294        // Typically, using `!limiter.live` would be good enough here.
1295        // However, the JS object hierarchy involves quite a bit of repetition, and the sheer amount of data makes
1296        // understanding the Debug output impossible; limiting the usefulness of it.
1297        //
1298        // Instead, we check if the object has appeared before in the entire graph. This means that objects will appear
1299        // at most once, hopefully making things a bit clearer.
1300        if !limiter.visited && !limiter.live {
1301            let ptr: *const _ = self.as_ref();
1302            let ptr = ptr.cast::<()>();
1303            let obj = self.borrow();
1304            let kind = obj.data().type_name_of_value();
1305            if self.is_callable() {
1306                let name_prop = obj
1307                    .properties()
1308                    .get(&PropertyKey::String(js_string!("name")));
1309                let name = match name_prop {
1310                    None => JsString::default(),
1311                    Some(prop) => prop
1312                        .value()
1313                        .and_then(JsValue::as_string)
1314                        .unwrap_or_default(),
1315                };
1316
1317                return f.write_fmt(format_args!("({:?}) {:?} 0x{:X}", kind, name, ptr as usize));
1318            }
1319
1320            f.write_fmt(format_args!("({:?}) 0x{:X}", kind, ptr as usize))
1321        } else {
1322            f.write_str("{ ... }")
1323        }
1324    }
1325}