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}