boa/object/
gcobject.rs

1//! This module implements the `JsObject` structure.
2//!
3//! The `JsObject` is a garbage collected Object.
4
5use super::{NativeObject, Object, PROTOTYPE};
6use crate::{
7    builtins::function::{
8        create_unmapped_arguments_object, Captures, ClosureFunction, Function, NativeFunction,
9    },
10    environment::{
11        environment_record_trait::EnvironmentRecordTrait,
12        function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
13        lexical_environment::Environment,
14    },
15    exec::InterpreterState,
16    object::{ObjectData, ObjectKind},
17    property::{PropertyDescriptor, PropertyKey},
18    syntax::ast::node::RcStatementList,
19    value::PreferredType,
20    Context, Executable, JsResult, JsValue,
21};
22use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace};
23use std::{
24    cell::RefCell,
25    collections::HashMap,
26    error::Error,
27    fmt::{self, Debug, Display},
28    result::Result as StdResult,
29};
30
31/// A wrapper type for an immutably borrowed type T.
32pub type Ref<'a, T> = GcCellRef<'a, T>;
33
34/// A wrapper type for a mutably borrowed type T.
35pub type RefMut<'a, T, U> = GcCellRefMut<'a, T, U>;
36
37/// Garbage collected `Object`.
38#[derive(Trace, Finalize, Clone, Default)]
39pub struct JsObject(Gc<GcCell<Object>>);
40
41/// The body of a JavaScript function.
42///
43/// This is needed for the call method since we cannot mutate the function itself since we
44/// already borrow it so we get the function body clone it then drop the borrow and run the body
45enum FunctionBody {
46    BuiltInFunction(NativeFunction),
47    BuiltInConstructor(NativeFunction),
48    Closure {
49        function: Box<dyn ClosureFunction>,
50        captures: Captures,
51    },
52    Ordinary(RcStatementList),
53}
54
55impl JsObject {
56    /// Create a new `GcObject` from a `Object`.
57    #[inline]
58    pub fn new(object: Object) -> Self {
59        Self(Gc::new(GcCell::new(object)))
60    }
61
62    /// Immutably borrows the `Object`.
63    ///
64    /// The borrow lasts until the returned `Ref` exits scope.
65    /// Multiple immutable borrows can be taken out at the same time.
66    ///
67    /// # Panics
68    ///
69    /// Panics if the object is currently mutably borrowed.
70    #[inline]
71    #[track_caller]
72    pub fn borrow(&self) -> Ref<'_, Object> {
73        self.try_borrow().expect("Object already mutably borrowed")
74    }
75
76    /// Mutably borrows the Object.
77    ///
78    /// The borrow lasts until the returned `RefMut` exits scope.
79    /// The object cannot be borrowed while this borrow is active.
80    ///
81    ///# Panics
82    /// Panics if the object is currently borrowed.
83    #[inline]
84    #[track_caller]
85    pub fn borrow_mut(&self) -> RefMut<'_, Object, Object> {
86        self.try_borrow_mut().expect("Object already borrowed")
87    }
88
89    /// Immutably borrows the `Object`, returning an error if the value is currently mutably borrowed.
90    ///
91    /// The borrow lasts until the returned `GcCellRef` exits scope.
92    /// Multiple immutable borrows can be taken out at the same time.
93    ///
94    /// This is the non-panicking variant of [`borrow`](#method.borrow).
95    #[inline]
96    pub fn try_borrow(&self) -> StdResult<Ref<'_, Object>, BorrowError> {
97        self.0.try_borrow().map_err(|_| BorrowError)
98    }
99
100    /// Mutably borrows the object, returning an error if the value is currently borrowed.
101    ///
102    /// The borrow lasts until the returned `GcCellRefMut` exits scope.
103    /// The object be borrowed while this borrow is active.
104    ///
105    /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut).
106    #[inline]
107    pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_, Object, Object>, BorrowMutError> {
108        self.0.try_borrow_mut().map_err(|_| BorrowMutError)
109    }
110
111    /// Checks if the garbage collected memory is the same.
112    #[inline]
113    pub fn equals(lhs: &Self, rhs: &Self) -> bool {
114        std::ptr::eq(lhs.as_ref(), rhs.as_ref())
115    }
116
117    /// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct).
118    ///
119    /// # Panics
120    ///
121    /// Panics if the object is currently mutably borrowed.
122    ///
123    /// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
124    /// <https://tc39.es/ecma262/#sec-ordinarycallbindthis>
125    /// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
126    /// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
127    #[track_caller]
128    pub(super) fn call_construct(
129        &self,
130        this_target: &JsValue,
131        args: &[JsValue],
132        context: &mut Context,
133        construct: bool,
134    ) -> JsResult<JsValue> {
135        let this_function_object = self.clone();
136        let mut has_parameter_expressions = false;
137
138        let body = if let Some(function) = self.borrow().as_function() {
139            if construct && !function.is_constructable() {
140                let name = self
141                    .__get__(&"name".into(), self.clone().into(), context)?
142                    .display()
143                    .to_string();
144                return context.throw_type_error(format!("{} is not a constructor", name));
145            } else {
146                match function {
147                    Function::Native {
148                        function,
149                        constructable,
150                    } => {
151                        if *constructable || construct {
152                            FunctionBody::BuiltInConstructor(function.0)
153                        } else {
154                            FunctionBody::BuiltInFunction(function.0)
155                        }
156                    }
157                    Function::Closure {
158                        function, captures, ..
159                    } => FunctionBody::Closure {
160                        function: function.clone(),
161                        captures: captures.clone(),
162                    },
163                    Function::Ordinary {
164                        body,
165                        params,
166                        environment,
167                        flags,
168                    } => {
169                        let this = if construct {
170                            // If the prototype of the constructor is not an object, then use the default object
171                            // prototype as prototype for the new object
172                            // see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
173                            // see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
174                            let proto = this_target.as_object().unwrap().__get__(
175                                &PROTOTYPE.into(),
176                                this_target.clone(),
177                                context,
178                            )?;
179                            let proto = if proto.is_object() {
180                                proto
181                            } else {
182                                context
183                                    .standard_objects()
184                                    .object_object()
185                                    .prototype()
186                                    .into()
187                            };
188                            JsValue::new(Object::create(proto))
189                        } else {
190                            this_target.clone()
191                        };
192
193                        // Create a new Function environment whose parent is set to the scope of the function declaration (self.environment)
194                        // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
195                        let local_env = FunctionEnvironmentRecord::new(
196                            this_function_object.clone(),
197                            if construct || !flags.is_lexical_this_mode() {
198                                Some(this.clone())
199                            } else {
200                                None
201                            },
202                            Some(environment.clone()),
203                            // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
204                            if flags.is_lexical_this_mode() {
205                                BindingStatus::Lexical
206                            } else {
207                                BindingStatus::Uninitialized
208                            },
209                            JsValue::undefined(),
210                            context,
211                        )?;
212
213                        let mut arguments_in_parameter_names = false;
214
215                        for param in params.iter() {
216                            has_parameter_expressions =
217                                has_parameter_expressions || param.init().is_some();
218                            arguments_in_parameter_names =
219                                arguments_in_parameter_names || param.name() == "arguments";
220                        }
221
222                        // An arguments object is added when all of the following conditions are met
223                        // - If not in an arrow function (10.2.11.16)
224                        // - If the parameter list does not contain `arguments` (10.2.11.17)
225                        // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
226                        //
227                        // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
228                        if !flags.is_lexical_this_mode()
229                            && !arguments_in_parameter_names
230                            && (has_parameter_expressions
231                                || (!body.lexically_declared_names().contains("arguments")
232                                    && !body.function_declared_names().contains("arguments")))
233                        {
234                            // Add arguments object
235                            let arguments_obj = create_unmapped_arguments_object(args, context)?;
236                            local_env.create_mutable_binding("arguments", false, true, context)?;
237                            local_env.initialize_binding("arguments", arguments_obj, context)?;
238                        }
239
240                        // Turn local_env into Environment so it can be cloned
241                        let local_env: Environment = local_env.into();
242
243                        // Push the environment first so that it will be used by default parameters
244                        context.push_environment(local_env.clone());
245
246                        // Add argument bindings to the function environment
247                        for (i, param) in params.iter().enumerate() {
248                            // Rest Parameters
249                            if param.is_rest_param() {
250                                function.add_rest_param(param, i, args, context, &local_env);
251                                break;
252                            }
253
254                            let value = match args.get(i).cloned() {
255                                None | Some(JsValue::Undefined) => param
256                                    .init()
257                                    .map(|init| init.run(context).ok())
258                                    .flatten()
259                                    .unwrap_or_default(),
260                                Some(value) => value,
261                            };
262
263                            function
264                                .add_arguments_to_environment(param, value, &local_env, context);
265                        }
266
267                        if has_parameter_expressions {
268                            // Create a second environment when default parameter expressions are used
269                            // This prevents variables declared in the function body from being
270                            // used in default parameter initializers.
271                            // https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
272                            let second_env = FunctionEnvironmentRecord::new(
273                                this_function_object,
274                                if construct || !flags.is_lexical_this_mode() {
275                                    Some(this)
276                                } else {
277                                    None
278                                },
279                                Some(local_env),
280                                // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
281                                if flags.is_lexical_this_mode() {
282                                    BindingStatus::Lexical
283                                } else {
284                                    BindingStatus::Uninitialized
285                                },
286                                JsValue::undefined(),
287                                context,
288                            )?;
289                            context.push_environment(second_env);
290                        }
291
292                        FunctionBody::Ordinary(body.clone())
293                    }
294                }
295            }
296        } else {
297            return context.throw_type_error("not a function");
298        };
299
300        match body {
301            FunctionBody::BuiltInConstructor(function) if construct => {
302                function(this_target, args, context)
303            }
304            FunctionBody::BuiltInConstructor(function) => {
305                function(&JsValue::undefined(), args, context)
306            }
307            FunctionBody::BuiltInFunction(function) => function(this_target, args, context),
308            FunctionBody::Closure { function, captures } => {
309                (function)(this_target, args, context, captures)
310            }
311            FunctionBody::Ordinary(body) => {
312                let result = body.run(context);
313                let this = context.get_this_binding();
314
315                if has_parameter_expressions {
316                    context.pop_environment();
317                }
318                context.pop_environment();
319
320                if construct {
321                    // https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
322                    // 12. If result.[[Type]] is return, then
323                    if context.executor().get_current_state() == &InterpreterState::Return {
324                        // a. If Type(result.[[Value]]) is Object, return NormalCompletion(result.[[Value]]).
325                        if let Ok(v) = &result {
326                            if v.is_object() {
327                                return result;
328                            }
329                        }
330                    }
331
332                    // 13. Else, ReturnIfAbrupt(result).
333                    result?;
334
335                    // 14. Return ? constructorEnv.GetThisBinding().
336                    this
337                } else if context.executor().get_current_state() == &InterpreterState::Return {
338                    result
339                } else {
340                    result?;
341                    Ok(JsValue::undefined())
342                }
343            }
344        }
345    }
346
347    /// Converts an object to a primitive.
348    ///
349    /// Diverges from the spec to prevent a stack overflow when the object is recursive.
350    /// For example,
351    /// ```javascript
352    /// let a = [1];
353    /// a[1] = a;
354    /// console.log(a.toString()); // We print "1,"
355    /// ```
356    /// The spec doesn't mention what to do in this situation, but a naive implementation
357    /// would overflow the stack recursively calling `toString()`. We follow v8 and SpiderMonkey
358    /// instead by returning a default value for the given `hint` -- either `0.` or `""`.
359    /// Example in v8: <https://repl.it/repls/IvoryCircularCertification#index.js>
360    ///
361    /// More information:
362    ///  - [ECMAScript][spec]
363    ///
364    /// [spec]: https://tc39.es/ecma262/#sec-ordinarytoprimitive
365    pub(crate) fn ordinary_to_primitive(
366        &self,
367        context: &mut Context,
368        hint: PreferredType,
369    ) -> JsResult<JsValue> {
370        // 1. Assert: Type(O) is Object.
371        //      Already is JsObject by type.
372        // 2. Assert: Type(hint) is String and its value is either "string" or "number".
373        debug_assert!(hint == PreferredType::String || hint == PreferredType::Number);
374
375        // Diverge from the spec here to make sure we aren't going to overflow the stack by converting
376        // a recursive structure
377        // We can follow v8 & SpiderMonkey's lead and return a default value for the hint in this situation
378        // (see https://repl.it/repls/IvoryCircularCertification#index.js)
379        let recursion_limiter = RecursionLimiter::new(self);
380        if recursion_limiter.live {
381            // we're in a recursive object, bail
382            return Ok(match hint {
383                PreferredType::Number => JsValue::new(0),
384                PreferredType::String => JsValue::new(""),
385                PreferredType::Default => unreachable!("checked type hint in step 2"),
386            });
387        }
388
389        // 3. If hint is "string", then
390        //    a. Let methodNames be « "toString", "valueOf" ».
391        // 4. Else,
392        //    a. Let methodNames be « "valueOf", "toString" ».
393        let method_names = if hint == PreferredType::String {
394            ["toString", "valueOf"]
395        } else {
396            ["valueOf", "toString"]
397        };
398
399        // 5. For each name in methodNames in List order, do
400        let this = JsValue::new(self.clone());
401        for name in &method_names {
402            // a. Let method be ? Get(O, name).
403            let method: JsValue = this.get_field(*name, context)?;
404            // b. If IsCallable(method) is true, then
405            if method.is_function() {
406                // i. Let result be ? Call(method, O).
407                let result = context.call(&method, &this, &[])?;
408                // ii. If Type(result) is not Object, return result.
409                if !result.is_object() {
410                    return Ok(result);
411                }
412            }
413        }
414
415        // 6. Throw a TypeError exception.
416        context.throw_type_error("cannot convert object to primitive value")
417    }
418
419    /// Return `true` if it is a native object and the native type is `T`.
420    ///
421    /// # Panics
422    ///
423    /// Panics if the object is currently mutably borrowed.
424    #[inline]
425    #[track_caller]
426    pub fn is<T>(&self) -> bool
427    where
428        T: NativeObject,
429    {
430        self.borrow().is::<T>()
431    }
432
433    /// Downcast a reference to the object,
434    /// if the object is type native object type `T`.
435    ///
436    /// # Panics
437    ///
438    /// Panics if the object is currently mutably borrowed.
439    #[inline]
440    #[track_caller]
441    pub fn downcast_ref<T>(&self) -> Option<Ref<'_, T>>
442    where
443        T: NativeObject,
444    {
445        let object = self.borrow();
446        if object.is::<T>() {
447            Some(Ref::map(object, |x| x.downcast_ref::<T>().unwrap()))
448        } else {
449            None
450        }
451    }
452
453    /// Downcast a mutable reference to the object,
454    /// if the object is type native object type `T`.
455    ///
456    /// # Panics
457    ///
458    /// Panics if the object is currently borrowed.
459    #[inline]
460    #[track_caller]
461    pub fn downcast_mut<T>(&mut self) -> Option<RefMut<'_, Object, T>>
462    where
463        T: NativeObject,
464    {
465        let object = self.borrow_mut();
466        if object.is::<T>() {
467            Some(RefMut::map(object, |x| x.downcast_mut::<T>().unwrap()))
468        } else {
469            None
470        }
471    }
472
473    /// Get the prototype of the object.
474    ///
475    /// # Panics
476    ///
477    /// Panics if the object is currently mutably borrowed.
478    #[inline]
479    #[track_caller]
480    pub fn prototype_instance(&self) -> JsValue {
481        self.borrow().prototype_instance().clone()
482    }
483
484    /// Set the prototype of the object.
485    ///
486    /// # Panics
487    ///
488    /// Panics if the object is currently mutably borrowed
489    /// or if th prototype is not an object or undefined.
490    #[inline]
491    #[track_caller]
492    pub fn set_prototype_instance(&self, prototype: JsValue) -> bool {
493        self.borrow_mut().set_prototype_instance(prototype)
494    }
495
496    /// Checks if it an `Array` object.
497    ///
498    /// # Panics
499    ///
500    /// Panics if the object is currently mutably borrowed.
501    #[inline]
502    #[track_caller]
503    pub fn is_array(&self) -> bool {
504        self.borrow().is_array()
505    }
506
507    /// Checks if it is an `ArrayIterator` object.
508    ///
509    /// # Panics
510    ///
511    /// Panics if the object is currently mutably borrowed.
512    #[inline]
513    #[track_caller]
514    pub fn is_array_iterator(&self) -> bool {
515        self.borrow().is_array_iterator()
516    }
517
518    /// Checks if it is a `Map` object.pub
519    ///
520    /// # Panics
521    ///
522    /// Panics if the object is currently mutably borrowed.
523    #[inline]
524    #[track_caller]
525    pub fn is_map(&self) -> bool {
526        self.borrow().is_map()
527    }
528
529    /// Checks if it a `String` object.
530    ///
531    /// # Panics
532    ///
533    /// Panics if the object is currently mutably borrowed.
534    #[inline]
535    #[track_caller]
536    pub fn is_string(&self) -> bool {
537        self.borrow().is_string()
538    }
539
540    /// Checks if it a `Function` object.
541    ///
542    /// # Panics
543    ///
544    /// Panics if the object is currently mutably borrowed.
545    #[inline]
546    #[track_caller]
547    pub fn is_function(&self) -> bool {
548        self.borrow().is_function()
549    }
550
551    /// Checks if it a Symbol object.
552    ///
553    /// # Panics
554    ///
555    /// Panics if the object is currently mutably borrowed.
556    #[inline]
557    #[track_caller]
558    pub fn is_symbol(&self) -> bool {
559        self.borrow().is_symbol()
560    }
561
562    /// Checks if it an Error object.
563    ///
564    /// # Panics
565    ///
566    /// Panics if the object is currently mutably borrowed.
567    #[inline]
568    #[track_caller]
569    pub fn is_error(&self) -> bool {
570        self.borrow().is_error()
571    }
572
573    /// Checks if it a Boolean object.
574    ///
575    /// # Panics
576    ///
577    /// Panics if the object is currently mutably borrowed.
578    #[inline]
579    #[track_caller]
580    pub fn is_boolean(&self) -> bool {
581        self.borrow().is_boolean()
582    }
583
584    /// Checks if it a `Number` object.
585    ///
586    /// # Panics
587    ///
588    /// Panics if the object is currently mutably borrowed.
589    #[inline]
590    #[track_caller]
591    pub fn is_number(&self) -> bool {
592        self.borrow().is_number()
593    }
594
595    /// Checks if it a `BigInt` object.
596    ///
597    /// # Panics
598    ///
599    /// Panics if the object is currently mutably borrowed.
600    #[inline]
601    #[track_caller]
602    pub fn is_bigint(&self) -> bool {
603        self.borrow().is_bigint()
604    }
605
606    /// Checks if it a `RegExp` object.
607    ///
608    /// # Panics
609    ///
610    /// Panics if the object is currently mutably borrowed.
611    #[inline]
612    #[track_caller]
613    pub fn is_regexp(&self) -> bool {
614        self.borrow().is_regexp()
615    }
616
617    /// Checks if it an ordinary object.
618    ///
619    /// # Panics
620    ///
621    /// Panics if the object is currently mutably borrowed.
622    #[inline]
623    #[track_caller]
624    pub fn is_ordinary(&self) -> bool {
625        self.borrow().is_ordinary()
626    }
627
628    /// Returns `true` if it holds an Rust type that implements `NativeObject`.
629    ///
630    /// # Panics
631    ///
632    /// Panics if the object is currently mutably borrowed.
633    #[inline]
634    #[track_caller]
635    pub fn is_native_object(&self) -> bool {
636        self.borrow().is_native_object()
637    }
638
639    /// Determines if `value` inherits from the instance object inheritance path.
640    ///
641    /// More information:
642    /// - [EcmaScript reference][spec]
643    ///
644    /// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance
645    #[inline]
646    pub(crate) fn ordinary_has_instance(
647        &self,
648        context: &mut Context,
649        value: &JsValue,
650    ) -> JsResult<bool> {
651        // 1. If IsCallable(C) is false, return false.
652        if !self.is_callable() {
653            return Ok(false);
654        }
655
656        // TODO: 2. If C has a [[BoundTargetFunction]] internal slot, then
657        //         a. Let BC be C.[[BoundTargetFunction]].
658        //         b.  Return ? InstanceofOperator(O, BC).
659
660        // 3. If Type(O) is not Object, return false.
661        if let Some(object) = value.as_object() {
662            // 4. Let P be ? Get(C, "prototype").
663            // 5. If Type(P) is not Object, throw a TypeError exception.
664            if let Some(prototype) = self.get("prototype", context)?.as_object() {
665                // 6. Repeat,
666                //      a. Set O to ? O.[[GetPrototypeOf]]().
667                //      b. If O is null, return false.
668                let mut object = object.__get_prototype_of__(context)?;
669                while let Some(object_prototype) = object.as_object() {
670                    //     c. If SameValue(P, O) is true, return true.
671                    if JsObject::equals(&prototype, &object_prototype) {
672                        return Ok(true);
673                    }
674                    // a. Set O to ? O.[[GetPrototypeOf]]().
675                    object = object_prototype.__get_prototype_of__(context)?;
676                }
677
678                Ok(false)
679            } else {
680                Err(context
681                    .construct_type_error("function has non-object prototype in instanceof check"))
682            }
683        } else {
684            Ok(false)
685        }
686    }
687
688    pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
689        // 1 is implemented on the method `to_property_descriptor` of value
690
691        // 2. Let desc be a new Property Descriptor that initially has no fields.
692        let mut desc = PropertyDescriptor::builder();
693
694        // 3. Let hasEnumerable be ? HasProperty(Obj, "enumerable").
695        // 4. If hasEnumerable is true, then ...
696        if self.has_property("enumerable", context)? {
697            // a. Let enumerable be ! ToBoolean(? Get(Obj, "enumerable")).
698            // b. Set desc.[[Enumerable]] to enumerable.
699            desc = desc.enumerable(self.get("enumerable", context)?.to_boolean());
700        }
701
702        // 5. Let hasConfigurable be ? HasProperty(Obj, "configurable").
703        // 6. If hasConfigurable is true, then ...
704        if self.has_property("configurable", context)? {
705            // a. Let configurable be ! ToBoolean(? Get(Obj, "configurable")).
706            // b. Set desc.[[Configurable]] to configurable.
707            desc = desc.configurable(self.get("configurable", context)?.to_boolean());
708        }
709
710        // 7. Let hasValue be ? HasProperty(Obj, "value").
711        // 8. If hasValue is true, then ...
712        if self.has_property("value", context)? {
713            // a. Let value be ? Get(Obj, "value").
714            // b. Set desc.[[Value]] to value.
715            desc = desc.value(self.get("value", context)?);
716        }
717
718        // 9. Let hasWritable be ? HasProperty(Obj, ).
719        // 10. If hasWritable is true, then ...
720        if self.has_property("writable", context)? {
721            // a. Let writable be ! ToBoolean(? Get(Obj, "writable")).
722            // b. Set desc.[[Writable]] to writable.
723            desc = desc.writable(self.get("writable", context)?.to_boolean());
724        }
725
726        // 11. Let hasGet be ? HasProperty(Obj, "get").
727        // 12. If hasGet is true, then
728        let get = if self.has_property("get", context)? {
729            // a. Let getter be ? Get(Obj, "get").
730            let getter = self.get("get", context)?;
731            // b. If IsCallable(getter) is false and getter is not undefined, throw a TypeError exception.
732            // todo: extract IsCallable to be callable from Value
733            if !getter.is_undefined() && getter.as_object().map_or(true, |o| !o.is_callable()) {
734                return Err(
735                    context.construct_type_error("Property descriptor getter must be callable")
736                );
737            }
738            // c. Set desc.[[Get]] to getter.
739            Some(getter)
740        } else {
741            None
742        };
743
744        // 13. Let hasSet be ? HasProperty(Obj, "set").
745        // 14. If hasSet is true, then
746        let set = if self.has_property("set", context)? {
747            // 14.a. Let setter be ? Get(Obj, "set").
748            let setter = self.get("set", context)?;
749            // 14.b. If IsCallable(setter) is false and setter is not undefined, throw a TypeError exception.
750            // todo: extract IsCallable to be callable from Value
751            if !setter.is_undefined() && setter.as_object().map_or(true, |o| !o.is_callable()) {
752                return Err(
753                    context.construct_type_error("Property descriptor setter must be callable")
754                );
755            }
756            // 14.c. Set desc.[[Set]] to setter.
757            Some(setter)
758        } else {
759            None
760        };
761
762        // 15. If desc.[[Get]] is present or desc.[[Set]] is present, then ...
763        // a. If desc.[[Value]] is present or desc.[[Writable]] is present, throw a TypeError exception.
764        if get.as_ref().or_else(|| set.as_ref()).is_some() && desc.inner().is_data_descriptor() {
765            return Err(context.construct_type_error(
766                "Invalid property descriptor.\
767            Cannot both specify accessors and a value or writable attribute",
768            ));
769        }
770
771        desc = desc.maybe_get(get).maybe_set(set);
772
773        // 16. Return desc.
774        Ok(desc.build())
775    }
776
777    /// `7.3.25 CopyDataProperties ( target, source, excludedItems )`
778    ///
779    /// More information:
780    ///  - [ECMAScript][spec]
781    ///
782    /// [spec]: https://tc39.es/ecma262/#sec-copydataproperties
783    #[inline]
784    pub fn copy_data_properties<K>(
785        &mut self,
786        source: &JsValue,
787        excluded_keys: Vec<K>,
788        context: &mut Context,
789    ) -> JsResult<()>
790    where
791        K: Into<PropertyKey>,
792    {
793        // 1. Assert: Type(target) is Object.
794        // 2. Assert: excludedItems is a List of property keys.
795        // 3. If source is undefined or null, return target.
796        if source.is_null_or_undefined() {
797            return Ok(());
798        }
799
800        // 4. Let from be ! ToObject(source).
801        let from = source
802            .to_object(context)
803            .expect("function ToObject should never complete abruptly here");
804
805        // 5. Let keys be ? from.[[OwnPropertyKeys]]().
806        // 6. For each element nextKey of keys, do
807        let excluded_keys: Vec<PropertyKey> = excluded_keys.into_iter().map(|e| e.into()).collect();
808        for key in from.__own_property_keys__(context)? {
809            // a. Let excluded be false.
810            let mut excluded = false;
811
812            // b. For each element e of excludedItems, do
813            for e in &excluded_keys {
814                // i. If SameValue(e, nextKey) is true, then
815                if *e == key {
816                    // 1. Set excluded to true.
817                    excluded = true;
818                    break;
819                }
820            }
821            // c. If excluded is false, then
822            if !excluded {
823                // i. Let desc be ? from.[[GetOwnProperty]](nextKey).
824                let desc = from.__get_own_property__(&key, context)?;
825
826                // ii. If desc is not undefined and desc.[[Enumerable]] is true, then
827                if let Some(desc) = desc {
828                    if let Some(enumerable) = desc.enumerable() {
829                        if enumerable {
830                            // 1. Let propValue be ? Get(from, nextKey).
831                            let prop_value = from.__get__(&key, from.clone().into(), context)?;
832
833                            // 2. Perform ! CreateDataPropertyOrThrow(target, nextKey, propValue).
834                            self.create_data_property_or_throw(key, prop_value, context)
835                                .expect(
836                                    "CreateDataPropertyOrThrow should never complete abruptly here",
837                                );
838                        }
839                    }
840                }
841            }
842        }
843
844        // 7. Return target.
845        Ok(())
846    }
847
848    /// Helper function for property insertion.
849    #[inline]
850    #[track_caller]
851    pub(crate) fn insert<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
852    where
853        K: Into<PropertyKey>,
854        P: Into<PropertyDescriptor>,
855    {
856        self.borrow_mut().insert(key, property)
857    }
858
859    /// Inserts a field in the object `properties` without checking if it's writable.
860    ///
861    /// If a field was already in the object with the same name that a `Some` is returned
862    /// with that field, otherwise None is returned.
863    #[inline]
864    pub fn insert_property<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
865    where
866        K: Into<PropertyKey>,
867        P: Into<PropertyDescriptor>,
868    {
869        self.insert(key.into(), property)
870    }
871
872    /// It determines if Object is a callable function with a `[[Call]]` internal method.
873    ///
874    /// More information:
875    /// - [EcmaScript reference][spec]
876    ///
877    /// [spec]: https://tc39.es/ecma262/#sec-iscallable
878    #[inline]
879    #[track_caller]
880    pub fn is_callable(&self) -> bool {
881        self.borrow().is_callable()
882    }
883
884    /// It determines if Object is a function object with a `[[Construct]]` internal method.
885    ///
886    /// More information:
887    /// - [EcmaScript reference][spec]
888    ///
889    /// [spec]: https://tc39.es/ecma262/#sec-isconstructor
890    #[inline]
891    #[track_caller]
892    pub fn is_constructable(&self) -> bool {
893        self.borrow().is_constructable()
894    }
895
896    /// Returns true if the GcObject is the global for a Realm
897    pub fn is_global(&self) -> bool {
898        matches!(
899            self.borrow().data,
900            ObjectData {
901                kind: ObjectKind::Global,
902                ..
903            }
904        )
905    }
906}
907
908impl AsRef<GcCell<Object>> for JsObject {
909    #[inline]
910    fn as_ref(&self) -> &GcCell<Object> {
911        &*self.0
912    }
913}
914
915/// An error returned by [`JsObject::try_borrow`](struct.JsObject.html#method.try_borrow).
916#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
917pub struct BorrowError;
918
919impl Display for BorrowError {
920    #[inline]
921    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
922        Display::fmt("Object already mutably borrowed", f)
923    }
924}
925
926impl Error for BorrowError {}
927
928/// An error returned by [`JsObject::try_borrow_mut`](struct.JsObject.html#method.try_borrow_mut).
929#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
930pub struct BorrowMutError;
931
932impl Display for BorrowMutError {
933    #[inline]
934    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
935        Display::fmt("Object already borrowed", f)
936    }
937}
938
939impl Error for BorrowMutError {}
940
941#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
942enum RecursionValueState {
943    /// This value is "live": there's an active RecursionLimiter that hasn't been dropped.
944    Live,
945    /// This value has been seen before, but the recursion limiter has been dropped.
946    /// For example:
947    /// ```javascript
948    /// let b = [];
949    /// JSON.stringify([ // Create a recursion limiter for the root here
950    ///    b,            // state for b's &JsObject here is None
951    ///    b,            // state for b's &JsObject here is Visited
952    /// ]);
953    /// ```
954    Visited,
955}
956
957/// Prevents infinite recursion during `Debug::fmt`, `JSON.stringify`, and other conversions.
958/// This uses a thread local, so is not safe to use where the object graph will be traversed by
959/// multiple threads!
960#[derive(Debug)]
961pub struct RecursionLimiter {
962    /// If this was the first `JsObject` in the tree.
963    top_level: bool,
964    /// The ptr being kept in the HashSet, so we can delete it when we drop.
965    ptr: usize,
966    /// If this JsObject has been visited before in the graph, but not in the current branch.
967    pub visited: bool,
968    /// If this JsObject has been visited in the current branch of the graph.
969    pub live: bool,
970}
971
972impl Drop for RecursionLimiter {
973    fn drop(&mut self) {
974        if self.top_level {
975            // When the top level of the graph is dropped, we can free the entire map for the next traversal.
976            Self::SEEN.with(|hm| hm.borrow_mut().clear());
977        } else if !self.live {
978            // This was the first RL for this object to become live, so it's no longer live now that it's dropped.
979            Self::SEEN.with(|hm| {
980                hm.borrow_mut()
981                    .insert(self.ptr, RecursionValueState::Visited)
982            });
983        }
984    }
985}
986
987impl RecursionLimiter {
988    thread_local! {
989        /// The map of pointers to `JsObject` that have been visited during the current `Debug::fmt` graph,
990        /// and the current state of their RecursionLimiter (dropped or live -- see `RecursionValueState`)
991        static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new());
992    }
993
994    /// Determines if the specified `JsObject` has been visited, and returns a struct that will free it when dropped.
995    ///
996    /// This is done by maintaining a thread-local hashset containing the pointers of `JsObject` values that have been
997    /// visited. The first `JsObject` visited will clear the hashset, while any others will check if they are contained
998    /// by the hashset.
999    pub fn new(o: &JsObject) -> Self {
1000        // We shouldn't have to worry too much about this being moved during Debug::fmt.
1001        let ptr = (o.as_ref() as *const _) as usize;
1002        let (top_level, visited, live) = Self::SEEN.with(|hm| {
1003            let mut hm = hm.borrow_mut();
1004            let top_level = hm.is_empty();
1005            let old_state = hm.insert(ptr, RecursionValueState::Live);
1006
1007            (
1008                top_level,
1009                old_state == Some(RecursionValueState::Visited),
1010                old_state == Some(RecursionValueState::Live),
1011            )
1012        });
1013
1014        Self {
1015            top_level,
1016            ptr,
1017            visited,
1018            live,
1019        }
1020    }
1021}
1022
1023impl Debug for JsObject {
1024    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1025        let limiter = RecursionLimiter::new(self);
1026
1027        // Typically, using `!limiter.live` would be good enough here.
1028        // However, the JS object hierarchy involves quite a bit of repitition, and the sheer amount of data makes
1029        // understanding the Debug output impossible; limiting the usefulness of it.
1030        //
1031        // Instead, we check if the object has appeared before in the entire graph. This means that objects will appear
1032        // at most once, hopefully making things a bit clearer.
1033        if !limiter.visited && !limiter.live {
1034            f.debug_tuple("JsObject").field(&self.0).finish()
1035        } else {
1036            f.write_str("{ ... }")
1037        }
1038    }
1039}