use crate::{
builtins::{
array::array_iterator::ArrayIterator,
function::{BuiltInFunction, Function, FunctionFlags, NativeFunction},
map::map_iterator::MapIterator,
map::ordered_map::OrderedMap,
string::string_iterator::StringIterator,
BigInt, Date, RegExp,
},
context::StandardConstructor,
gc::{Finalize, Trace},
property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey},
value::{same_value, RcBigInt, RcString, RcSymbol, Value},
BoaProfiler, Context,
};
use rustc_hash::FxHashMap;
use std::{
any::Any,
fmt::{self, Debug, Display},
ops::{Deref, DerefMut},
};
#[cfg(test)]
mod tests;
mod gcobject;
mod internal_methods;
mod iter;
use crate::builtins::object::for_in_iterator::ForInIterator;
pub use gcobject::{GcObject, RecursionLimiter, Ref, RefMut};
pub use iter::*;
pub static PROTOTYPE: &str = "prototype";
pub trait NativeObject: Debug + Any + Trace {
fn as_any(&self) -> &dyn Any;
fn as_mut_any(&mut self) -> &mut dyn Any;
}
impl<T: Any + Debug + Trace> NativeObject for T {
#[inline]
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
#[inline]
fn as_mut_any(&mut self) -> &mut dyn Any {
self as &mut dyn Any
}
}
#[derive(Debug, Trace, Finalize)]
pub struct Object {
pub data: ObjectData,
indexed_properties: FxHashMap<u32, PropertyDescriptor>,
string_properties: FxHashMap<RcString, PropertyDescriptor>,
symbol_properties: FxHashMap<RcSymbol, PropertyDescriptor>,
prototype: Value,
extensible: bool,
}
#[derive(Debug, Trace, Finalize)]
pub enum ObjectData {
Array,
ArrayIterator(ArrayIterator),
Map(OrderedMap<Value, Value>),
MapIterator(MapIterator),
RegExp(Box<RegExp>),
BigInt(RcBigInt),
Boolean(bool),
ForInIterator(ForInIterator),
Function(Function),
String(RcString),
StringIterator(StringIterator),
Number(f64),
Symbol(RcSymbol),
Error,
Ordinary,
Date(Date),
Global,
NativeObject(Box<dyn NativeObject>),
}
impl Display for ObjectData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Array => "Array",
Self::ArrayIterator(_) => "ArrayIterator",
Self::ForInIterator(_) => "ForInIterator",
Self::Function(_) => "Function",
Self::RegExp(_) => "RegExp",
Self::Map(_) => "Map",
Self::MapIterator(_) => "MapIterator",
Self::String(_) => "String",
Self::StringIterator(_) => "StringIterator",
Self::Symbol(_) => "Symbol",
Self::Error => "Error",
Self::Ordinary => "Ordinary",
Self::Boolean(_) => "Boolean",
Self::Number(_) => "Number",
Self::BigInt(_) => "BigInt",
Self::Date(_) => "Date",
Self::Global => "Global",
Self::NativeObject(_) => "NativeObject",
}
)
}
}
impl Default for Object {
#[inline]
fn default() -> Self {
Self {
data: ObjectData::Ordinary,
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
}
impl Object {
#[inline]
pub fn new() -> Self {
Default::default()
}
#[inline]
pub fn function(function: Function, prototype: Value) -> Self {
let _timer = BoaProfiler::global().start_event("Object::Function", "object");
Self {
data: ObjectData::Function(function),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype,
extensible: true,
}
}
#[inline]
pub fn create(proto: Value) -> Self {
let mut obj = Self::new();
obj.prototype = proto;
obj
}
#[inline]
pub fn boolean(value: bool) -> Self {
Self {
data: ObjectData::Boolean(value),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
#[inline]
pub fn number(value: f64) -> Self {
Self {
data: ObjectData::Number(value),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
#[inline]
pub fn string<S>(value: S) -> Self
where
S: Into<RcString>,
{
Self {
data: ObjectData::String(value.into()),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
#[inline]
pub fn bigint(value: RcBigInt) -> Self {
Self {
data: ObjectData::BigInt(value),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
#[inline]
pub fn native_object<T>(value: T) -> Self
where
T: NativeObject,
{
Self {
data: ObjectData::NativeObject(Box::new(value)),
indexed_properties: FxHashMap::default(),
string_properties: FxHashMap::default(),
symbol_properties: FxHashMap::default(),
prototype: Value::null(),
extensible: true,
}
}
#[inline]
pub fn is_callable(&self) -> bool {
matches!(self.data, ObjectData::Function(ref f) if f.is_callable())
}
#[inline]
pub fn is_constructable(&self) -> bool {
matches!(self.data, ObjectData::Function(ref f) if f.is_constructable())
}
#[inline]
pub fn is_array(&self) -> bool {
matches!(self.data, ObjectData::Array)
}
#[inline]
pub fn as_array(&self) -> Option<()> {
match self.data {
ObjectData::Array => Some(()),
_ => None,
}
}
#[inline]
pub fn is_array_iterator(&self) -> bool {
matches!(self.data, ObjectData::ArrayIterator(_))
}
#[inline]
pub fn as_array_iterator(&self) -> Option<&ArrayIterator> {
match self.data {
ObjectData::ArrayIterator(ref iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn as_array_iterator_mut(&mut self) -> Option<&mut ArrayIterator> {
match &mut self.data {
ObjectData::ArrayIterator(iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn as_string_iterator_mut(&mut self) -> Option<&mut StringIterator> {
match &mut self.data {
ObjectData::StringIterator(iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn as_for_in_iterator(&self) -> Option<&ForInIterator> {
match &self.data {
ObjectData::ForInIterator(iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn as_for_in_iterator_mut(&mut self) -> Option<&mut ForInIterator> {
match &mut self.data {
ObjectData::ForInIterator(iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn is_map(&self) -> bool {
matches!(self.data, ObjectData::Map(_))
}
#[inline]
pub fn as_map_ref(&self) -> Option<&OrderedMap<Value, Value>> {
match self.data {
ObjectData::Map(ref map) => Some(map),
_ => None,
}
}
#[inline]
pub fn as_map_mut(&mut self) -> Option<&mut OrderedMap<Value, Value>> {
match &mut self.data {
ObjectData::Map(map) => Some(map),
_ => None,
}
}
#[inline]
pub fn as_map_iterator_mut(&mut self) -> Option<&mut MapIterator> {
match &mut self.data {
ObjectData::MapIterator(iter) => Some(iter),
_ => None,
}
}
#[inline]
pub fn is_string(&self) -> bool {
matches!(self.data, ObjectData::String(_))
}
#[inline]
pub fn as_string(&self) -> Option<RcString> {
match self.data {
ObjectData::String(ref string) => Some(string.clone()),
_ => None,
}
}
#[inline]
pub fn is_function(&self) -> bool {
matches!(self.data, ObjectData::Function(_))
}
#[inline]
pub fn as_function(&self) -> Option<&Function> {
match self.data {
ObjectData::Function(ref function) => Some(function),
_ => None,
}
}
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self.data, ObjectData::Symbol(_))
}
#[inline]
pub fn as_symbol(&self) -> Option<RcSymbol> {
match self.data {
ObjectData::Symbol(ref symbol) => Some(symbol.clone()),
_ => None,
}
}
#[inline]
pub fn is_error(&self) -> bool {
matches!(self.data, ObjectData::Error)
}
#[inline]
pub fn as_error(&self) -> Option<()> {
match self.data {
ObjectData::Error => Some(()),
_ => None,
}
}
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self.data, ObjectData::Boolean(_))
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self.data {
ObjectData::Boolean(boolean) => Some(boolean),
_ => None,
}
}
#[inline]
pub fn is_number(&self) -> bool {
matches!(self.data, ObjectData::Number(_))
}
#[inline]
pub fn as_number(&self) -> Option<f64> {
match self.data {
ObjectData::Number(number) => Some(number),
_ => None,
}
}
#[inline]
pub fn is_bigint(&self) -> bool {
matches!(self.data, ObjectData::BigInt(_))
}
#[inline]
pub fn as_bigint(&self) -> Option<&BigInt> {
match self.data {
ObjectData::BigInt(ref bigint) => Some(bigint),
_ => None,
}
}
#[inline]
pub fn is_regexp(&self) -> bool {
matches!(self.data, ObjectData::RegExp(_))
}
#[inline]
pub fn as_regexp(&self) -> Option<&RegExp> {
match self.data {
ObjectData::RegExp(ref regexp) => Some(regexp),
_ => None,
}
}
#[inline]
pub fn is_ordinary(&self) -> bool {
matches!(self.data, ObjectData::Ordinary)
}
#[inline]
pub fn prototype_instance(&self) -> &Value {
&self.prototype
}
#[inline]
#[track_caller]
pub fn set_prototype_instance(&mut self, prototype: Value) -> bool {
assert!(prototype.is_null() || prototype.is_object());
if self.extensible {
self.prototype = prototype;
true
} else {
same_value(&prototype, &self.prototype)
}
}
#[inline]
pub fn with_prototype(proto: Value, data: ObjectData) -> Object {
let mut object = Object::new();
object.data = data;
object.set_prototype_instance(proto);
object
}
#[inline]
pub fn is_native_object(&self) -> bool {
matches!(self.data, ObjectData::NativeObject(_))
}
#[inline]
pub fn as_native_object(&self) -> Option<&dyn NativeObject> {
match self.data {
ObjectData::NativeObject(ref object) => Some(object.as_ref()),
_ => None,
}
}
#[inline]
pub fn is<T>(&self) -> bool
where
T: NativeObject,
{
match self.data {
ObjectData::NativeObject(ref object) => object.deref().as_any().is::<T>(),
_ => false,
}
}
#[inline]
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: NativeObject,
{
match self.data {
ObjectData::NativeObject(ref object) => object.deref().as_any().downcast_ref::<T>(),
_ => None,
}
}
#[inline]
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject,
{
match self.data {
ObjectData::NativeObject(ref mut object) => {
object.deref_mut().as_mut_any().downcast_mut::<T>()
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct FunctionBinding {
binding: PropertyKey,
name: RcString,
}
impl From<&str> for FunctionBinding {
#[inline]
fn from(name: &str) -> Self {
let name: RcString = name.into();
Self {
binding: name.clone().into(),
name,
}
}
}
impl From<String> for FunctionBinding {
#[inline]
fn from(name: String) -> Self {
let name: RcString = name.into();
Self {
binding: name.clone().into(),
name,
}
}
}
impl From<RcString> for FunctionBinding {
#[inline]
fn from(name: RcString) -> Self {
Self {
binding: name.clone().into(),
name,
}
}
}
impl<B, N> From<(B, N)> for FunctionBinding
where
B: Into<PropertyKey>,
N: AsRef<str>,
{
#[inline]
fn from((binding, name): (B, N)) -> Self {
Self {
binding: binding.into(),
name: name.as_ref().into(),
}
}
}
#[derive(Debug)]
pub struct FunctionBuilder<'context> {
context: &'context mut Context,
function: BuiltInFunction,
name: Option<String>,
length: usize,
callable: bool,
constructable: bool,
}
impl<'context> FunctionBuilder<'context> {
#[inline]
pub fn new(context: &'context mut Context, function: NativeFunction) -> Self {
Self {
context,
function: function.into(),
name: None,
length: 0,
callable: true,
constructable: false,
}
}
#[inline]
pub fn name<N>(&mut self, name: N) -> &mut Self
where
N: AsRef<str>,
{
self.name = Some(name.as_ref().into());
self
}
#[inline]
pub fn length(&mut self, length: usize) -> &mut Self {
self.length = length;
self
}
#[inline]
pub fn callable(&mut self, yes: bool) -> &mut Self {
self.callable = yes;
self
}
#[inline]
pub fn constructable(&mut self, yes: bool) -> &mut Self {
self.constructable = yes;
self
}
#[inline]
pub fn build(&mut self) -> GcObject {
let mut function = Object::function(
Function::BuiltIn(
self.function,
FunctionFlags::from_parameters(self.callable, self.constructable),
),
self.context
.standard_objects()
.function_object()
.prototype()
.into(),
);
let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE;
if let Some(name) = self.name.take() {
function.insert_property("name", name, attribute);
} else {
function.insert_property("name", "", attribute);
}
function.insert_property("length", self.length, attribute);
GcObject::new(function)
}
pub(crate) fn build_function_prototype(&mut self, object: &GcObject) {
let mut object = object.borrow_mut();
object.data = ObjectData::Function(Function::BuiltIn(
self.function,
FunctionFlags::from_parameters(self.callable, self.constructable),
));
object.set_prototype_instance(
self.context
.standard_objects()
.object_object()
.prototype()
.into(),
);
let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT;
if let Some(name) = self.name.take() {
object.insert_property("name", name, attribute);
} else {
object.insert_property("name", "", attribute);
}
object.insert_property("length", self.length, attribute);
}
}
#[derive(Debug)]
pub struct ObjectInitializer<'context> {
context: &'context mut Context,
object: GcObject,
}
impl<'context> ObjectInitializer<'context> {
#[inline]
pub fn new(context: &'context mut Context) -> Self {
let object = context.construct_object();
Self { context, object }
}
#[inline]
pub fn function<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
where
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = FunctionBuilder::new(self.context, function)
.name(binding.name)
.length(length)
.callable(true)
.constructable(false)
.build();
self.object.borrow_mut().insert_property(
binding.binding,
function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
self
}
#[inline]
pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
where
K: Into<PropertyKey>,
V: Into<Value>,
{
let property = DataDescriptor::new(value, attribute);
self.object.borrow_mut().insert(key, property);
self
}
#[inline]
pub fn build(&mut self) -> GcObject {
self.object.clone()
}
}
pub struct ConstructorBuilder<'context> {
context: &'context mut Context,
constructor_function: NativeFunction,
constructor_object: GcObject,
prototype: GcObject,
name: Option<String>,
length: usize,
callable: bool,
constructable: bool,
inherit: Option<Value>,
}
impl Debug for ConstructorBuilder<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ConstructorBuilder")
.field("name", &self.name)
.field("length", &self.length)
.field("constructor", &self.constructor_object)
.field("prototype", &self.prototype)
.field("inherit", &self.inherit)
.field("callable", &self.callable)
.field("constructable", &self.constructable)
.finish()
}
}
impl<'context> ConstructorBuilder<'context> {
#[inline]
pub fn new(context: &'context mut Context, constructor: NativeFunction) -> Self {
Self {
context,
constructor_function: constructor,
constructor_object: GcObject::new(Object::default()),
prototype: GcObject::new(Object::default()),
length: 0,
name: None,
callable: true,
constructable: true,
inherit: None,
}
}
#[inline]
pub(crate) fn with_standard_object(
context: &'context mut Context,
constructor: NativeFunction,
object: StandardConstructor,
) -> Self {
Self {
context,
constructor_function: constructor,
constructor_object: object.constructor,
prototype: object.prototype,
length: 0,
name: None,
callable: true,
constructable: true,
inherit: None,
}
}
#[inline]
pub fn method<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
where
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = FunctionBuilder::new(self.context, function)
.name(binding.name)
.length(length)
.callable(true)
.constructable(false)
.build();
self.prototype.borrow_mut().insert_property(
binding.binding,
function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
self
}
#[inline]
pub fn static_method<B>(
&mut self,
function: NativeFunction,
binding: B,
length: usize,
) -> &mut Self
where
B: Into<FunctionBinding>,
{
let binding = binding.into();
let function = FunctionBuilder::new(self.context, function)
.name(binding.name)
.length(length)
.callable(true)
.constructable(false)
.build();
self.constructor_object.borrow_mut().insert_property(
binding.binding,
function,
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
self
}
#[inline]
pub fn property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
where
K: Into<PropertyKey>,
V: Into<Value>,
{
let property = DataDescriptor::new(value, attribute);
self.prototype.borrow_mut().insert(key, property);
self
}
#[inline]
pub fn static_property<K, V>(&mut self, key: K, value: V, attribute: Attribute) -> &mut Self
where
K: Into<PropertyKey>,
V: Into<Value>,
{
let property = DataDescriptor::new(value, attribute);
self.constructor_object.borrow_mut().insert(key, property);
self
}
#[inline]
pub fn length(&mut self, length: usize) -> &mut Self {
self.length = length;
self
}
#[inline]
pub fn name<N>(&mut self, name: N) -> &mut Self
where
N: AsRef<str>,
{
self.name = Some(name.as_ref().into());
self
}
#[inline]
pub fn callable(&mut self, callable: bool) -> &mut Self {
self.callable = callable;
self
}
#[inline]
pub fn constructable(&mut self, constructable: bool) -> &mut Self {
self.constructable = constructable;
self
}
#[inline]
pub fn inherit(&mut self, prototype: Value) -> &mut Self {
assert!(prototype.is_object() || prototype.is_null());
self.inherit = Some(prototype);
self
}
#[inline]
pub fn context(&mut self) -> &'_ mut Context {
self.context
}
pub fn build(&mut self) -> GcObject {
let function = Function::BuiltIn(
self.constructor_function.into(),
FunctionFlags::from_parameters(self.callable, self.constructable),
);
let length = DataDescriptor::new(
self.length,
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
let name = DataDescriptor::new(
self.name.take().unwrap_or_else(|| String::from("[object]")),
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
{
let mut constructor = self.constructor_object.borrow_mut();
constructor.data = ObjectData::Function(function);
constructor.insert("length", length);
constructor.insert("name", name);
constructor.set_prototype_instance(
self.context
.standard_objects()
.function_object()
.prototype()
.into(),
);
constructor.insert_property(
PROTOTYPE,
self.prototype.clone(),
Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT,
);
}
{
let mut prototype = self.prototype.borrow_mut();
prototype.insert_property(
"constructor",
self.constructor_object.clone(),
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
);
if let Some(proto) = self.inherit.take() {
prototype.set_prototype_instance(proto);
} else {
prototype.set_prototype_instance(
self.context
.standard_objects()
.object_object()
.prototype()
.into(),
);
}
}
self.constructor_object.clone()
}
}