mirror of https://github.com/boa-dev/boa.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
964 lines
34 KiB
964 lines
34 KiB
//! This module implements the `GcObject` structure. |
|
//! |
|
//! The `GcObject` is a garbage collected Object. |
|
|
|
use super::{NativeObject, Object, PROTOTYPE}; |
|
use crate::{ |
|
builtins::function::{ |
|
create_unmapped_arguments_object, BuiltInFunction, Function, NativeFunction, |
|
}, |
|
environment::{ |
|
function_environment_record::BindingStatus, lexical_environment::new_function_environment, |
|
}, |
|
property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, |
|
syntax::ast::node::RcStatementList, |
|
value::PreferredType, |
|
Context, Executable, Result, Value, |
|
}; |
|
use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; |
|
use serde_json::{map::Map, Value as JSONValue}; |
|
use std::{ |
|
cell::RefCell, |
|
collections::HashMap, |
|
error::Error, |
|
fmt::{self, Debug, Display}, |
|
result::Result as StdResult, |
|
}; |
|
|
|
/// A wrapper type for an immutably borrowed type T. |
|
pub type Ref<'a, T> = GcCellRef<'a, T>; |
|
|
|
/// A wrapper type for a mutably borrowed type T. |
|
pub type RefMut<'a, T> = GcCellRefMut<'a, T>; |
|
|
|
/// Garbage collected `Object`. |
|
#[derive(Trace, Finalize, Clone, Default)] |
|
pub struct GcObject(Gc<GcCell<Object>>); |
|
|
|
/// The body of a JavaScript function. |
|
/// |
|
/// This is needed for the call method since we cannot mutate the function itself since we |
|
/// already borrow it so we get the function body clone it then drop the borrow and run the body |
|
enum FunctionBody { |
|
BuiltInFunction(NativeFunction), |
|
BuiltInConstructor(NativeFunction), |
|
Ordinary(RcStatementList), |
|
} |
|
|
|
impl GcObject { |
|
/// Create a new `GcObject` from a `Object`. |
|
#[inline] |
|
pub fn new(object: Object) -> Self { |
|
Self(Gc::new(GcCell::new(object))) |
|
} |
|
|
|
/// Immutably borrows the `Object`. |
|
/// |
|
/// The borrow lasts until the returned `Ref` exits scope. |
|
/// Multiple immutable borrows can be taken out at the same time. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn borrow(&self) -> Ref<'_, Object> { |
|
self.try_borrow().expect("Object already mutably borrowed") |
|
} |
|
|
|
/// Mutably borrows the Object. |
|
/// |
|
/// The borrow lasts until the returned `RefMut` exits scope. |
|
/// The object cannot be borrowed while this borrow is active. |
|
/// |
|
///# Panics |
|
/// Panics if the object is currently borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn borrow_mut(&self) -> RefMut<'_, Object> { |
|
self.try_borrow_mut().expect("Object already borrowed") |
|
} |
|
|
|
/// Immutably borrows the `Object`, returning an error if the value is currently mutably borrowed. |
|
/// |
|
/// The borrow lasts until the returned `GcCellRef` exits scope. |
|
/// Multiple immutable borrows can be taken out at the same time. |
|
/// |
|
/// This is the non-panicking variant of [`borrow`](#method.borrow). |
|
#[inline] |
|
pub fn try_borrow(&self) -> StdResult<Ref<'_, Object>, BorrowError> { |
|
self.0.try_borrow().map_err(|_| BorrowError) |
|
} |
|
|
|
/// Mutably borrows the object, returning an error if the value is currently borrowed. |
|
/// |
|
/// The borrow lasts until the returned `GcCellRefMut` exits scope. |
|
/// The object be borrowed while this borrow is active. |
|
/// |
|
/// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). |
|
#[inline] |
|
pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_, Object>, BorrowMutError> { |
|
self.0.try_borrow_mut().map_err(|_| BorrowMutError) |
|
} |
|
|
|
/// Checks if the garbage collected memory is the same. |
|
#[inline] |
|
pub fn equals(lhs: &Self, rhs: &Self) -> bool { |
|
std::ptr::eq(lhs.as_ref(), rhs.as_ref()) |
|
} |
|
|
|
/// Call this object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> |
|
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist> |
|
#[track_caller] |
|
pub fn call(&self, this: &Value, args: &[Value], context: &mut Context) -> Result<Value> { |
|
let this_function_object = self.clone(); |
|
let f_body = if let Some(function) = self.borrow().as_function() { |
|
if function.is_callable() { |
|
match function { |
|
Function::BuiltIn(BuiltInFunction(function), flags) => { |
|
if flags.is_constructable() { |
|
FunctionBody::BuiltInConstructor(*function) |
|
} else { |
|
FunctionBody::BuiltInFunction(*function) |
|
} |
|
} |
|
Function::Ordinary { |
|
body, |
|
params, |
|
environment, |
|
flags, |
|
} => { |
|
// Create a new Function environment whose parent is set to the scope of the function declaration (self.environment) |
|
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> |
|
let local_env = new_function_environment( |
|
this_function_object, |
|
if flags.is_lexical_this_mode() { |
|
None |
|
} else { |
|
Some(this.clone()) |
|
}, |
|
Some(environment.clone()), |
|
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records |
|
if flags.is_lexical_this_mode() { |
|
BindingStatus::Lexical |
|
} else { |
|
BindingStatus::Uninitialized |
|
}, |
|
Value::undefined(), |
|
); |
|
|
|
// Add argument bindings to the function environment |
|
for (i, param) in params.iter().enumerate() { |
|
// Rest Parameters |
|
if param.is_rest_param() { |
|
function.add_rest_param(param, i, args, context, &local_env); |
|
break; |
|
} |
|
|
|
let value = args.get(i).cloned().unwrap_or_else(Value::undefined); |
|
function.add_arguments_to_environment(param, value, &local_env); |
|
} |
|
|
|
// Add arguments object |
|
let arguments_obj = create_unmapped_arguments_object(args); |
|
local_env |
|
.borrow_mut() |
|
.create_mutable_binding("arguments".to_string(), false, true) |
|
.map_err(|e| e.to_error(context))?; |
|
local_env |
|
.borrow_mut() |
|
.initialize_binding("arguments", arguments_obj) |
|
.map_err(|e| e.to_error(context))?; |
|
|
|
context.realm_mut().environment.push(local_env); |
|
|
|
FunctionBody::Ordinary(body.clone()) |
|
} |
|
} |
|
} else { |
|
return context.throw_type_error("function object is not callable"); |
|
} |
|
} else { |
|
return context.throw_type_error("not a function"); |
|
}; |
|
|
|
match f_body { |
|
FunctionBody::BuiltInFunction(func) => func(this, args, context), |
|
FunctionBody::BuiltInConstructor(func) => func(&Value::undefined(), args, context), |
|
FunctionBody::Ordinary(body) => { |
|
let result = body.run(context); |
|
context.realm_mut().environment.pop(); |
|
|
|
result |
|
} |
|
} |
|
} |
|
|
|
/// Construct an instance of this object with the specified arguments. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget> |
|
#[track_caller] |
|
pub fn construct( |
|
&self, |
|
args: &[Value], |
|
new_target: Value, |
|
context: &mut Context, |
|
) -> Result<Value> { |
|
let this_function_object = self.clone(); |
|
let body = if let Some(function) = self.borrow().as_function() { |
|
if function.is_constructable() { |
|
match function { |
|
Function::BuiltIn(BuiltInFunction(function), _) => { |
|
FunctionBody::BuiltInConstructor(*function) |
|
} |
|
Function::Ordinary { |
|
body, |
|
params, |
|
environment, |
|
flags, |
|
} => { |
|
// If the prototype of the constructor is not an object, then use the default object |
|
// prototype as prototype for the new object |
|
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor> |
|
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor> |
|
let proto = new_target.as_object().unwrap().get( |
|
&PROTOTYPE.into(), |
|
new_target.clone(), |
|
context, |
|
)?; |
|
let proto = if proto.is_object() { |
|
proto |
|
} else { |
|
context |
|
.standard_objects() |
|
.object_object() |
|
.prototype() |
|
.into() |
|
}; |
|
let this = Value::from(Object::create(proto)); |
|
|
|
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) |
|
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> |
|
let local_env = new_function_environment( |
|
this_function_object, |
|
Some(this), |
|
Some(environment.clone()), |
|
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records |
|
if flags.is_lexical_this_mode() { |
|
BindingStatus::Lexical |
|
} else { |
|
BindingStatus::Uninitialized |
|
}, |
|
new_target.clone(), |
|
); |
|
|
|
// Add argument bindings to the function environment |
|
for (i, param) in params.iter().enumerate() { |
|
// Rest Parameters |
|
if param.is_rest_param() { |
|
function.add_rest_param(param, i, args, context, &local_env); |
|
break; |
|
} |
|
|
|
let value = args.get(i).cloned().unwrap_or_else(Value::undefined); |
|
function.add_arguments_to_environment(param, value, &local_env); |
|
} |
|
|
|
// Add arguments object |
|
let arguments_obj = create_unmapped_arguments_object(args); |
|
local_env |
|
.borrow_mut() |
|
.create_mutable_binding("arguments".to_string(), false, true) |
|
.map_err(|e| e.to_error(context))?; |
|
local_env |
|
.borrow_mut() |
|
.initialize_binding("arguments", arguments_obj) |
|
.map_err(|e| e.to_error(context))?; |
|
|
|
context.realm_mut().environment.push(local_env); |
|
|
|
FunctionBody::Ordinary(body.clone()) |
|
} |
|
} |
|
} else { |
|
let name = self |
|
.get(&"name".into(), self.clone().into(), context)? |
|
.display() |
|
.to_string(); |
|
return context.throw_type_error(format!("{} is not a constructor", name)); |
|
} |
|
} else { |
|
return context.throw_type_error("not a function"); |
|
}; |
|
|
|
match body { |
|
FunctionBody::BuiltInConstructor(function) => function(&new_target, args, context), |
|
FunctionBody::Ordinary(body) => { |
|
let _ = body.run(context); |
|
|
|
// local_env gets dropped here, its no longer needed |
|
let binding = context.realm_mut().environment.get_this_binding(); |
|
binding.map_err(|e| e.to_error(context)) |
|
} |
|
FunctionBody::BuiltInFunction(_) => unreachable!("Cannot have a function in construct"), |
|
} |
|
} |
|
|
|
/// Converts an object to a primitive. |
|
/// |
|
/// Diverges from the spec to prevent a stack overflow when the object is recursive. |
|
/// For example, |
|
/// ```javascript |
|
/// let a = [1]; |
|
/// a[1] = a; |
|
/// console.log(a.toString()); // We print "1," |
|
/// ``` |
|
/// The spec doesn't mention what to do in this situation, but a naive implementation |
|
/// would overflow the stack recursively calling `toString()`. We follow v8 and SpiderMonkey |
|
/// instead by returning a default value for the given `hint` -- either `0.` or `""`. |
|
/// Example in v8: <https://repl.it/repls/IvoryCircularCertification#index.js> |
|
/// |
|
/// More information: |
|
/// - [ECMAScript][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinarytoprimitive |
|
pub(crate) fn ordinary_to_primitive( |
|
&self, |
|
context: &mut Context, |
|
hint: PreferredType, |
|
) -> Result<Value> { |
|
// 1. Assert: Type(O) is Object. |
|
// Already is GcObject by type. |
|
// 2. Assert: Type(hint) is String and its value is either "string" or "number". |
|
debug_assert!(hint == PreferredType::String || hint == PreferredType::Number); |
|
|
|
// Diverge from the spec here to make sure we aren't going to overflow the stack by converting |
|
// a recursive structure |
|
// We can follow v8 & SpiderMonkey's lead and return a default value for the hint in this situation |
|
// (see https://repl.it/repls/IvoryCircularCertification#index.js) |
|
let recursion_limiter = RecursionLimiter::new(&self); |
|
if recursion_limiter.live { |
|
// we're in a recursive object, bail |
|
return Ok(match hint { |
|
PreferredType::Number => Value::from(0), |
|
PreferredType::String => Value::from(""), |
|
PreferredType::Default => unreachable!("checked type hint in step 2"), |
|
}); |
|
} |
|
|
|
// 3. If hint is "string", then |
|
// a. Let methodNames be « "toString", "valueOf" ». |
|
// 4. Else, |
|
// a. Let methodNames be « "valueOf", "toString" ». |
|
let method_names = if hint == PreferredType::String { |
|
["toString", "valueOf"] |
|
} else { |
|
["valueOf", "toString"] |
|
}; |
|
|
|
// 5. For each name in methodNames in List order, do |
|
let this = Value::from(self.clone()); |
|
for name in &method_names { |
|
// a. Let method be ? Get(O, name). |
|
let method: Value = this.get_field(*name, context)?; |
|
// b. If IsCallable(method) is true, then |
|
if method.is_function() { |
|
// i. Let result be ? Call(method, O). |
|
let result = context.call(&method, &this, &[])?; |
|
// ii. If Type(result) is not Object, return result. |
|
if !result.is_object() { |
|
return Ok(result); |
|
} |
|
} |
|
} |
|
|
|
// 6. Throw a TypeError exception. |
|
context.throw_type_error("cannot convert object to primitive value") |
|
} |
|
|
|
/// Converts an object to JSON, checking for reference cycles and throwing a TypeError if one is found |
|
pub(crate) fn to_json(&self, context: &mut Context) -> Result<JSONValue> { |
|
let rec_limiter = RecursionLimiter::new(self); |
|
if rec_limiter.live { |
|
Err(context.construct_type_error("cyclic object value")) |
|
} else if self.is_array() { |
|
let mut keys: Vec<u32> = self.borrow().index_property_keys().cloned().collect(); |
|
keys.sort_unstable(); |
|
let mut arr: Vec<JSONValue> = Vec::with_capacity(keys.len()); |
|
let this = Value::from(self.clone()); |
|
for key in keys { |
|
let value = this.get_field(key, context)?; |
|
if value.is_undefined() || value.is_function() || value.is_symbol() { |
|
arr.push(JSONValue::Null); |
|
} else { |
|
arr.push(value.to_json(context)?); |
|
} |
|
} |
|
Ok(JSONValue::Array(arr)) |
|
} else { |
|
let mut new_obj = Map::new(); |
|
let this = Value::from(self.clone()); |
|
for k in self.borrow().keys() { |
|
let key = k.clone(); |
|
let value = this.get_field(k.to_string(), context)?; |
|
if !value.is_undefined() && !value.is_function() && !value.is_symbol() { |
|
new_obj.insert(key.to_string(), value.to_json(context)?); |
|
} |
|
} |
|
Ok(JSONValue::Object(new_obj)) |
|
} |
|
} |
|
|
|
/// Convert the object to a `PropertyDescritptor` |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
pub fn to_property_descriptor(&self, context: &mut Context) -> Result<PropertyDescriptor> { |
|
let mut attribute = Attribute::empty(); |
|
|
|
let enumerable_key = PropertyKey::from("enumerable"); |
|
if self.has_property(&enumerable_key) |
|
&& self |
|
.get(&enumerable_key, self.clone().into(), context)? |
|
.to_boolean() |
|
{ |
|
attribute |= Attribute::ENUMERABLE; |
|
} |
|
|
|
let configurable_key = PropertyKey::from("configurable"); |
|
if self.has_property(&configurable_key) |
|
&& self |
|
.get(&configurable_key, self.clone().into(), context)? |
|
.to_boolean() |
|
{ |
|
attribute |= Attribute::CONFIGURABLE; |
|
} |
|
|
|
let mut value = None; |
|
let value_key = PropertyKey::from("value"); |
|
if self.has_property(&value_key) { |
|
value = Some(self.get(&value_key, self.clone().into(), context)?); |
|
} |
|
|
|
let mut has_writable = false; |
|
let writable_key = PropertyKey::from("writable"); |
|
if self.has_property(&writable_key) { |
|
has_writable = true; |
|
if self |
|
.get(&writable_key, self.clone().into(), context)? |
|
.to_boolean() |
|
{ |
|
attribute |= Attribute::WRITABLE; |
|
} |
|
} |
|
|
|
let mut get = None; |
|
let get_key = PropertyKey::from("get"); |
|
if self.has_property(&get_key) { |
|
let getter = self.get(&get_key, self.clone().into(), context)?; |
|
match getter { |
|
Value::Object(ref object) if object.is_callable() => { |
|
get = Some(object.clone()); |
|
} |
|
_ => { |
|
return Err( |
|
context.construct_type_error("Property descriptor getter must be callable") |
|
); |
|
} |
|
} |
|
} |
|
|
|
let mut set = None; |
|
let set_key = PropertyKey::from("set"); |
|
if self.has_property(&set_key) { |
|
let setter = self.get(&set_key, self.clone().into(), context)?; |
|
match setter { |
|
Value::Object(ref object) if object.is_callable() => { |
|
set = Some(object.clone()); |
|
} |
|
_ => { |
|
return Err( |
|
context.construct_type_error("Property descriptor setter must be callable") |
|
); |
|
} |
|
}; |
|
} |
|
|
|
if get.is_some() || set.is_some() { |
|
if value.is_some() || has_writable { |
|
return Err(context.construct_type_error("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute")); |
|
} |
|
|
|
Ok(AccessorDescriptor::new(get, set, attribute).into()) |
|
} else { |
|
Ok(DataDescriptor::new(value.unwrap_or_else(Value::undefined), attribute).into()) |
|
} |
|
} |
|
|
|
/// Reeturn `true` if it is a native object and the native type is `T`. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is<T>(&self) -> bool |
|
where |
|
T: NativeObject, |
|
{ |
|
self.borrow().is::<T>() |
|
} |
|
|
|
/// Downcast a reference to the object, |
|
/// if the object is type native object type `T`. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn downcast_ref<T>(&self) -> Option<Ref<'_, T>> |
|
where |
|
T: NativeObject, |
|
{ |
|
let object = self.borrow(); |
|
if object.is::<T>() { |
|
Some(Ref::map(object, |x| x.downcast_ref::<T>().unwrap())) |
|
} else { |
|
None |
|
} |
|
} |
|
|
|
/// Downcast a mutable reference to the object, |
|
/// if the object is type native object type `T`. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn downcast_mut<T>(&mut self) -> Option<RefMut<'_, T>> |
|
where |
|
T: NativeObject, |
|
{ |
|
let object = self.borrow_mut(); |
|
if object.is::<T>() { |
|
Some(RefMut::map(object, |x| x.downcast_mut::<T>().unwrap())) |
|
} else { |
|
None |
|
} |
|
} |
|
|
|
/// Get the prototype of the object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn prototype_instance(&self) -> Value { |
|
self.borrow().prototype_instance().clone() |
|
} |
|
|
|
/// Set the prototype of the object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed |
|
/// or if th prototype is not an object or undefined. |
|
#[inline] |
|
#[track_caller] |
|
pub fn set_prototype_instance(&mut self, prototype: Value) -> bool { |
|
self.borrow_mut().set_prototype_instance(prototype) |
|
} |
|
|
|
/// Checks if it an `Array` object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_array(&self) -> bool { |
|
self.borrow().is_array() |
|
} |
|
|
|
/// Checks if it is an `ArrayIterator` object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_array_iterator(&self) -> bool { |
|
self.borrow().is_array_iterator() |
|
} |
|
|
|
/// Checks if it is a `Map` object.pub |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_map(&self) -> bool { |
|
self.borrow().is_map() |
|
} |
|
|
|
/// Checks if it a `String` object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_string(&self) -> bool { |
|
self.borrow().is_string() |
|
} |
|
|
|
/// Checks if it a `Function` object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_function(&self) -> bool { |
|
self.borrow().is_function() |
|
} |
|
|
|
/// Checks if it a Symbol object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_symbol(&self) -> bool { |
|
self.borrow().is_symbol() |
|
} |
|
|
|
/// Checks if it an Error object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_error(&self) -> bool { |
|
self.borrow().is_error() |
|
} |
|
|
|
/// Checks if it a Boolean object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_boolean(&self) -> bool { |
|
self.borrow().is_boolean() |
|
} |
|
|
|
/// Checks if it a `Number` object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_number(&self) -> bool { |
|
self.borrow().is_number() |
|
} |
|
|
|
/// Checks if it a `BigInt` object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_bigint(&self) -> bool { |
|
self.borrow().is_bigint() |
|
} |
|
|
|
/// Checks if it a `RegExp` object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_regexp(&self) -> bool { |
|
self.borrow().is_regexp() |
|
} |
|
|
|
/// Checks if it an ordinary object. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_ordinary(&self) -> bool { |
|
self.borrow().is_ordinary() |
|
} |
|
|
|
/// Returns `true` if it holds an Rust type that implements `NativeObject`. |
|
/// |
|
/// # Panics |
|
/// |
|
/// Panics if the object is currently mutably borrowed. |
|
#[inline] |
|
#[track_caller] |
|
pub fn is_native_object(&self) -> bool { |
|
self.borrow().is_native_object() |
|
} |
|
|
|
/// Retrieves value of specific property, when the value of the property is expected to be a function. |
|
/// |
|
/// More information: |
|
/// - [EcmaScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-getmethod |
|
#[inline] |
|
pub fn get_method<K>(&self, context: &mut Context, key: K) -> Result<Option<GcObject>> |
|
where |
|
K: Into<PropertyKey>, |
|
{ |
|
let key = key.into(); |
|
let value = self.get(&key, self.clone().into(), context)?; |
|
|
|
if value.is_null_or_undefined() { |
|
return Ok(None); |
|
} |
|
|
|
match value.as_object() { |
|
Some(object) if object.is_callable() => Ok(Some(object)), |
|
_ => Err(context |
|
.construct_type_error("value returned for property of object is not a function")), |
|
} |
|
} |
|
|
|
/// Determines if `value` inherits from the instance object inheritance path. |
|
/// |
|
/// More information: |
|
/// - [EcmaScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasinstance |
|
#[inline] |
|
pub(crate) fn ordinary_has_instance( |
|
&self, |
|
context: &mut Context, |
|
value: &Value, |
|
) -> Result<bool> { |
|
if !self.is_callable() { |
|
return Ok(false); |
|
} |
|
|
|
// TODO: If C has a [[BoundTargetFunction]] internal slot, then |
|
// Let BC be C.[[BoundTargetFunction]]. |
|
// Return ? InstanceofOperator(O, BC). |
|
|
|
if let Some(object) = value.as_object() { |
|
if let Some(prototype) = self |
|
.get(&"prototype".into(), self.clone().into(), context)? |
|
.as_object() |
|
{ |
|
let mut object = object.get_prototype_of(); |
|
while let Some(object_prototype) = object.as_object() { |
|
if GcObject::equals(&prototype, &object_prototype) { |
|
return Ok(true); |
|
} |
|
object = object_prototype.get_prototype_of(); |
|
} |
|
|
|
Ok(false) |
|
} else { |
|
Err(context |
|
.construct_type_error("function has non-object prototype in instanceof check")) |
|
} |
|
} else { |
|
Ok(false) |
|
} |
|
} |
|
#[inline] |
|
#[track_caller] |
|
pub fn has_own_property<K>(&self, key: K) -> bool |
|
where |
|
K: Into<PropertyKey>, |
|
{ |
|
let key = key.into(); |
|
self.get_own_property(&key).is_some() |
|
} |
|
|
|
/// Defines the property or throws a `TypeError` if the operation fails. |
|
/// |
|
/// More information: |
|
/// - [EcmaScript reference][spec] |
|
/// |
|
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow |
|
#[inline] |
|
pub(crate) fn define_property_or_throw<K, P>( |
|
&mut self, |
|
key: K, |
|
desc: P, |
|
context: &mut Context, |
|
) -> Result<()> |
|
where |
|
K: Into<PropertyKey>, |
|
P: Into<PropertyDescriptor>, |
|
{ |
|
let key = key.into(); |
|
let desc = desc.into(); |
|
|
|
let success = self.define_own_property(key.clone(), desc, context)?; |
|
if !success { |
|
Err(context.construct_type_error(format!("Cannot redefine property: {}", key))) |
|
} else { |
|
Ok(()) |
|
} |
|
} |
|
} |
|
|
|
impl AsRef<GcCell<Object>> for GcObject { |
|
#[inline] |
|
fn as_ref(&self) -> &GcCell<Object> { |
|
&*self.0 |
|
} |
|
} |
|
|
|
/// An error returned by [`GcObject::try_borrow`](struct.GcObject.html#method.try_borrow). |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
|
pub struct BorrowError; |
|
|
|
impl Display for BorrowError { |
|
#[inline] |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
Display::fmt("Object already mutably borrowed", f) |
|
} |
|
} |
|
|
|
impl Error for BorrowError {} |
|
|
|
/// An error returned by [`GcObject::try_borrow_mut`](struct.GcObject.html#method.try_borrow_mut). |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
|
pub struct BorrowMutError; |
|
|
|
impl Display for BorrowMutError { |
|
#[inline] |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
|
Display::fmt("Object already borrowed", f) |
|
} |
|
} |
|
|
|
impl Error for BorrowMutError {} |
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
|
enum RecursionValueState { |
|
/// This value is "live": there's an active RecursionLimiter that hasn't been dropped. |
|
Live, |
|
/// This value has been seen before, but the recursion limiter has been dropped. |
|
/// For example: |
|
/// ```javascript |
|
/// let b = []; |
|
/// JSON.stringify([ // Create a recursion limiter for the root here |
|
/// b, // state for b's &GcObject here is None |
|
/// b, // state for b's &GcObject here is Visited |
|
/// ]); |
|
/// ``` |
|
Visited, |
|
} |
|
|
|
/// Prevents infinite recursion during `Debug::fmt`, `JSON.stringify`, and other conversions. |
|
/// This uses a thread local, so is not safe to use where the object graph will be traversed by |
|
/// multiple threads! |
|
#[derive(Debug)] |
|
pub struct RecursionLimiter { |
|
/// If this was the first `GcObject` in the tree. |
|
top_level: bool, |
|
/// The ptr being kept in the HashSet, so we can delete it when we drop. |
|
ptr: usize, |
|
/// If this GcObject has been visited before in the graph, but not in the current branch. |
|
pub visited: bool, |
|
/// If this GcObject has been visited in the current branch of the graph. |
|
pub live: bool, |
|
} |
|
|
|
impl Drop for RecursionLimiter { |
|
fn drop(&mut self) { |
|
if self.top_level { |
|
// When the top level of the graph is dropped, we can free the entire map for the next traversal. |
|
Self::SEEN.with(|hm| hm.borrow_mut().clear()); |
|
} else if !self.live { |
|
// This was the first RL for this object to become live, so it's no longer live now that it's dropped. |
|
Self::SEEN.with(|hm| { |
|
hm.borrow_mut() |
|
.insert(self.ptr, RecursionValueState::Visited) |
|
}); |
|
} |
|
} |
|
} |
|
|
|
impl RecursionLimiter { |
|
thread_local! { |
|
/// The map of pointers to `GcObject` that have been visited during the current `Debug::fmt` graph, |
|
/// and the current state of their RecursionLimiter (dropped or live -- see `RecursionValueState`) |
|
static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new()); |
|
} |
|
|
|
/// Determines if the specified `GcObject` has been visited, and returns a struct that will free it when dropped. |
|
/// |
|
/// This is done by maintaining a thread-local hashset containing the pointers of `GcObject` values that have been |
|
/// visited. The first `GcObject` visited will clear the hashset, while any others will check if they are contained |
|
/// by the hashset. |
|
pub fn new(o: &GcObject) -> Self { |
|
// We shouldn't have to worry too much about this being moved during Debug::fmt. |
|
let ptr = (o.as_ref() as *const _) as usize; |
|
let (top_level, visited, live) = Self::SEEN.with(|hm| { |
|
let mut hm = hm.borrow_mut(); |
|
let top_level = hm.is_empty(); |
|
let old_state = hm.insert(ptr, RecursionValueState::Live); |
|
|
|
( |
|
top_level, |
|
old_state == Some(RecursionValueState::Visited), |
|
old_state == Some(RecursionValueState::Live), |
|
) |
|
}); |
|
|
|
Self { |
|
top_level, |
|
ptr, |
|
visited, |
|
live, |
|
} |
|
} |
|
} |
|
|
|
impl Debug for GcObject { |
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { |
|
let limiter = RecursionLimiter::new(&self); |
|
|
|
// Typically, using `!limiter.live` would be good enough here. |
|
// However, the JS object hierarchy involves quite a bit of repitition, and the sheer amount of data makes |
|
// understanding the Debug output impossible; limiting the usefulness of it. |
|
// |
|
// Instead, we check if the object has appeared before in the entire graph. This means that objects will appear |
|
// at most once, hopefully making things a bit clearer. |
|
if !limiter.visited && !limiter.live { |
|
f.debug_tuple("GcObject").field(&self.0).finish() |
|
} else { |
|
f.write_str("{ ... }") |
|
} |
|
} |
|
}
|
|
|