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,
};
pub type Ref<'a, T> = GcCellRef<'a, T>;
pub type RefMut<'a, T> = GcCellRefMut<'a, T>;
#[derive(Trace, Finalize, Clone, Default)]
pub struct GcObject(Gc<GcCell<Object>>);
enum FunctionBody {
BuiltInFunction(NativeFunction),
BuiltInConstructor(NativeFunction),
Ordinary(RcStatementList),
}
impl GcObject {
#[inline]
pub fn new(object: Object) -> Self {
Self(Gc::new(GcCell::new(object)))
}
#[inline]
#[track_caller]
pub fn borrow(&self) -> Ref<'_, Object> {
self.try_borrow().expect("Object already mutably borrowed")
}
#[inline]
#[track_caller]
pub fn borrow_mut(&self) -> RefMut<'_, Object> {
self.try_borrow_mut().expect("Object already borrowed")
}
#[inline]
pub fn try_borrow(&self) -> StdResult<Ref<'_, Object>, BorrowError> {
self.0.try_borrow().map_err(|_| BorrowError)
}
#[inline]
pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_, Object>, BorrowMutError> {
self.0.try_borrow_mut().map_err(|_| BorrowMutError)
}
#[inline]
pub fn equals(lhs: &Self, rhs: &Self) -> bool {
std::ptr::eq(lhs.as_ref(), rhs.as_ref())
}
#[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,
} => {
let local_env = new_function_environment(
this_function_object,
if flags.is_lexical_this_mode() {
None
} else {
Some(this.clone())
},
Some(environment.clone()),
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
Value::undefined(),
);
for (i, param) in params.iter().enumerate() {
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);
}
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
}
}
}
#[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,
} => {
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));
let local_env = new_function_environment(
this_function_object,
Some(this),
Some(environment.clone()),
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
new_target.clone(),
);
for (i, param) in params.iter().enumerate() {
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);
}
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);
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"),
}
}
pub(crate) fn ordinary_to_primitive(
&self,
context: &mut Context,
hint: PreferredType,
) -> Result<Value> {
debug_assert!(hint == PreferredType::String || hint == PreferredType::Number);
let recursion_limiter = RecursionLimiter::new(&self);
if recursion_limiter.live {
return Ok(match hint {
PreferredType::Number => Value::from(0),
PreferredType::String => Value::from(""),
PreferredType::Default => unreachable!("checked type hint in step 2"),
});
}
let method_names = if hint == PreferredType::String {
["toString", "valueOf"]
} else {
["valueOf", "toString"]
};
let this = Value::from(self.clone());
for name in &method_names {
let method: Value = this.get_field(*name, context)?;
if method.is_function() {
let result = context.call(&method, &this, &[])?;
if !result.is_object() {
return Ok(result);
}
}
}
context.throw_type_error("cannot convert object to primitive value")
}
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))
}
}
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())
}
}
#[inline]
#[track_caller]
pub fn is<T>(&self) -> bool
where
T: NativeObject,
{
self.borrow().is::<T>()
}
#[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
}
}
#[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
}
}
#[inline]
#[track_caller]
pub fn prototype_instance(&self) -> Value {
self.borrow().prototype_instance().clone()
}
#[inline]
#[track_caller]
pub fn set_prototype_instance(&mut self, prototype: Value) -> bool {
self.borrow_mut().set_prototype_instance(prototype)
}
#[inline]
#[track_caller]
pub fn is_array(&self) -> bool {
self.borrow().is_array()
}
#[inline]
#[track_caller]
pub fn is_array_iterator(&self) -> bool {
self.borrow().is_array_iterator()
}
#[inline]
#[track_caller]
pub fn is_map(&self) -> bool {
self.borrow().is_map()
}
#[inline]
#[track_caller]
pub fn is_string(&self) -> bool {
self.borrow().is_string()
}
#[inline]
#[track_caller]
pub fn is_function(&self) -> bool {
self.borrow().is_function()
}
#[inline]
#[track_caller]
pub fn is_symbol(&self) -> bool {
self.borrow().is_symbol()
}
#[inline]
#[track_caller]
pub fn is_error(&self) -> bool {
self.borrow().is_error()
}
#[inline]
#[track_caller]
pub fn is_boolean(&self) -> bool {
self.borrow().is_boolean()
}
#[inline]
#[track_caller]
pub fn is_number(&self) -> bool {
self.borrow().is_number()
}
#[inline]
#[track_caller]
pub fn is_bigint(&self) -> bool {
self.borrow().is_bigint()
}
#[inline]
#[track_caller]
pub fn is_regexp(&self) -> bool {
self.borrow().is_regexp()
}
#[inline]
#[track_caller]
pub fn is_ordinary(&self) -> bool {
self.borrow().is_ordinary()
}
#[inline]
#[track_caller]
pub fn is_native_object(&self) -> bool {
self.borrow().is_native_object()
}
#[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")),
}
}
#[inline]
pub(crate) fn ordinary_has_instance(
&self,
context: &mut Context,
value: &Value,
) -> Result<bool> {
if !self.is_callable() {
return Ok(false);
}
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()
}
#[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
}
}
#[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 {}
#[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 {
Live,
Visited,
}
#[derive(Debug)]
pub struct RecursionLimiter {
top_level: bool,
ptr: usize,
pub visited: bool,
pub live: bool,
}
impl Drop for RecursionLimiter {
fn drop(&mut self) {
if self.top_level {
Self::SEEN.with(|hm| hm.borrow_mut().clear());
} else if !self.live {
Self::SEEN.with(|hm| {
hm.borrow_mut()
.insert(self.ptr, RecursionValueState::Visited)
});
}
}
}
impl RecursionLimiter {
thread_local! {
static SEEN: RefCell<HashMap<usize, RecursionValueState>> = RefCell::new(HashMap::new());
}
pub fn new(o: &GcObject) -> Self {
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);
if !limiter.visited && !limiter.live {
f.debug_tuple("GcObject").field(&self.0).finish()
} else {
f.write_str("{ ... }")
}
}
}