diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index fb701dd6b10..e240802340f 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -93,14 +93,14 @@ unsafe impl Trace for FunctionBody { bitflags! { #[derive(Finalize, Default)] - struct FunctionFlags: u8 { + pub(crate) struct FunctionFlags: u8 { const CALLABLE = 0b0000_0001; const CONSTRUCTABLE = 0b0000_0010; } } impl FunctionFlags { - fn from_parameters(callable: bool, constructable: bool) -> Self { + pub(crate) fn from_parameters(callable: bool, constructable: bool) -> Self { let mut flags = Self::default(); if callable { @@ -142,7 +142,7 @@ pub struct Function { // Environment, built-in functions don't need Environments pub environment: Option, /// Is it constructable or - flags: FunctionFlags, + pub(crate) flags: FunctionFlags, } impl Function { diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 1393b483856..779da952bb4 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -70,12 +70,13 @@ impl Json { /// /// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) -> ResultValue { - let mut value = holder.get_field(key.clone()); + let value = holder.get_field(key.clone()); - let obj = value.as_object().as_deref().cloned(); - if let Some(obj) = obj { - for key in obj.properties().keys() { - let v = Self::walk(reviver, ctx, &mut value, Value::from(key.as_str())); + if let Value::Object(ref object) = value { + let keys: Vec<_> = object.borrow().properties().keys().cloned().collect(); + + for key in keys { + let v = Self::walk(reviver, ctx, &mut value.clone(), Value::from(key.as_str())); match v { Ok(v) if !v.is_undefined() => { value.set_field(key.as_str(), v); diff --git a/boa/src/builtins/object/internal_methods.rs b/boa/src/builtins/object/internal_methods.rs index c86aa6f7353..d8eb58eb67d 100644 --- a/boa/src/builtins/object/internal_methods.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -96,9 +96,7 @@ impl Object { return Value::undefined(); } - let parent_obj = Object::from(&parent).expect("Failed to get object"); - - return parent_obj.get(property_key); + return parent.get_field(property_key); } if desc.is_data_descriptor() { @@ -298,45 +296,45 @@ impl Object { } } - /// `Object.setPropertyOf(obj, prototype)` - /// - /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) - /// of a specified object to another object or `null`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf - pub fn set_prototype_of(&mut self, val: Value) -> bool { - debug_assert!(val.is_object() || val.is_null()); - let current = self.prototype.clone(); - if same_value(¤t, &val) { - return true; - } - if !self.is_extensible() { - return false; - } - let mut p = val.clone(); - let mut done = false; - while !done { - if p.is_null() { - done = true - } else if same_value(&Value::from(self.clone()), &p) { - return false; - } else { - let prototype = p - .as_object() - .expect("prototype should be null or object") - .prototype - .clone(); - p = prototype; - } - } - self.prototype = val; - true - } + // /// `Object.setPropertyOf(obj, prototype)` + // /// + // /// This method sets the prototype (i.e., the internal `[[Prototype]]` property) + // /// of a specified object to another object or `null`. + // /// + // /// More information: + // /// - [ECMAScript reference][spec] + // /// - [MDN documentation][mdn] + // /// + // /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v + // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf + // pub fn set_prototype_of(&mut self, val: Value) -> bool { + // debug_assert!(val.is_object() || val.is_null()); + // let current = self.prototype.clone(); + // if same_value(¤t, &val) { + // return true; + // } + // if !self.is_extensible() { + // return false; + // } + // let mut p = val.clone(); + // let mut done = false; + // while !done { + // if p.is_null() { + // done = true + // } else if same_value(&Value::from(self.clone()), &p) { + // return false; + // } else { + // let prototype = p + // .as_object() + // .expect("prototype should be null or object") + // .prototype + // .clone(); + // p = prototype; + // } + // } + // self.prototype = val; + // true + // } /// Returns either the prototype or null /// diff --git a/boa/src/builtins/object/internal_state.rs b/boa/src/builtins/object/internal_state.rs deleted file mode 100644 index 06955258cc5..00000000000 --- a/boa/src/builtins/object/internal_state.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Implementations for storing normal rust structs inside any object as internal state. - -use std::{ - any::Any, - fmt::{self, Debug}, - ops::{Deref, DerefMut}, - rc::Rc, -}; - -use gc::{unsafe_empty_trace, Finalize, Trace}; - -/// Wrapper around `Rc` to implement `Trace` and `Finalize`. -#[derive(Clone)] -pub struct InternalStateCell { - /// The internal state. - state: Rc, -} - -impl Finalize for InternalStateCell {} - -unsafe impl Trace for InternalStateCell { - unsafe_empty_trace!(); -} - -impl Deref for InternalStateCell { - type Target = dyn Any; - fn deref(&self) -> &Self::Target { - Deref::deref(&self.state) - } -} - -impl DerefMut for InternalStateCell { - fn deref_mut(&mut self) -> &mut Self::Target { - Rc::get_mut(&mut self.state).expect("failed to get mutable") - } -} - -/// The derived version would print 'InternalStateCell { state: ... }', this custom implementation -/// only prints the actual internal state. -impl Debug for InternalStateCell { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Debug::fmt(&self.state, f) - } -} - -impl InternalStateCell { - /// Create new `InternalStateCell` from a value. - pub fn new(value: T) -> Self { - Self { - state: Rc::new(value), - } - } - /// Get a reference to the stored value and cast it to `T`. - pub fn downcast_ref(&self) -> Option<&T> { - self.deref().downcast_ref::() - } - /// Get a mutable reference to the stored value and cast it to `T`. - pub fn downcast_mut(&mut self) -> Option<&mut T> { - self.deref_mut().downcast_mut::() - } -} - -/// This trait must be implemented by all structs used for internal state. -pub trait InternalState: Debug {} diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 89d6af50942..76eb8ea412b 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -17,7 +17,7 @@ use crate::{ builtins::{ function::Function, map::ordered_map::OrderedMap, - property::Property, + property::{Attribute, Property}, value::{RcBigInt, RcString, RcSymbol, ResultValue, Value}, BigInt, Date, RegExp, }, @@ -26,15 +26,14 @@ use crate::{ }; use gc::{Finalize, Trace}; use rustc_hash::FxHashMap; +use std::any::Any; use std::fmt::{Debug, Display, Error, Formatter}; -use super::function::{make_builtin_fn, make_constructor_fn}; +use super::function::{make_builtin_fn, make_constructor_fn, FunctionFlags, NativeFunctionData}; use crate::builtins::value::same_value; -pub use internal_state::{InternalState, InternalStateCell}; pub mod gcobject; pub mod internal_methods; -mod internal_state; pub use gcobject::GcObject; @@ -47,8 +46,140 @@ pub static PROTOTYPE: &str = "prototype"; // /// Static `__proto__`, usually set on Object instances as a key to point to their respective prototype object. // pub static INSTANCE_PROTOTYPE: &str = "__proto__"; +pub trait NativeObject: Debug + Any + Trace { + fn as_any(&self) -> &dyn Any; + fn as_mut_any(&mut self) -> &mut dyn Any; +} + +impl NativeObject for T { + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + + fn as_mut_any(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } +} + +pub trait Class: NativeObject { + const NAME: &'static str; + const LENGTH: usize = 0; + + fn constructor(_: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue; + fn methods(class: &mut ClassBuilder<'_>) -> ResultValue; +} + +#[derive(Debug)] +pub struct ClassBuilder<'context> { + context: &'context mut Interpreter, + object: GcObject, + prototype: GcObject, +} + +impl<'context> ClassBuilder<'context> { + pub(crate) fn new(context: &'context mut Interpreter) -> Self + where + T: Class, + { + let global = context.global(); + + let prototype = { + let object_prototype = global.get_field("Object").get_field(PROTOTYPE); + + let object = Object::create(object_prototype); + GcObject::new(object) + }; + // Create the native function + let mut function = Function::builtin(Vec::new(), T::constructor); + function.flags = FunctionFlags::from_parameters(false, true); + + // Get reference to Function.prototype + // Create the function object and point its instance prototype to Function.prototype + let mut constructor = + Object::function(function, global.get_field("Function").get_field(PROTOTYPE)); + + let length = Property::data_descriptor( + T::LENGTH.into(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ); + constructor.insert_property("length", length); + + let name = Property::data_descriptor( + T::NAME.into(), + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, + ); + constructor.insert_property("name", name); + + let constructor = GcObject::new(constructor); + + prototype + .borrow_mut() + .insert_field("constructor", constructor.clone().into()); + + constructor + .borrow_mut() + .insert_field(PROTOTYPE, prototype.clone().into()); + + Self { + context, + object: constructor, + prototype, + } + } + + pub(crate) fn build(self) -> GcObject { + self.object + } + + pub fn method(&mut self, name: N, length: usize, function: NativeFunctionData) + where + N: Into, + { + let name = name.into(); + let mut function = Object::function( + Function::builtin(Vec::new(), function), + self.context + .global() + .get_field("Function") + .get_field("prototype"), + ); + + function.insert_field("length", Value::from(length)); + function.insert_field("name", Value::from(name.as_str())); + + self.prototype + .borrow_mut() + .insert_field(name, Value::from(function)); + } + + pub fn static_method(&mut self, name: N, length: usize, function: NativeFunctionData) + where + N: Into, + { + let name = name.into(); + let mut function = Object::function( + Function::builtin(Vec::new(), function), + self.context + .global() + .get_field("Function") + .get_field("prototype"), + ); + + function.insert_field("length", Value::from(length)); + function.insert_field("name", Value::from(name.as_str())); + + self.object + .borrow_mut() + .insert_field(name, Value::from(function)); + } + + pub fn context(&mut self) -> &'_ mut Interpreter { + self.context + } +} + /// The internal representation of an JavaScript object. -#[derive(Debug, Trace, Finalize, Clone)] +#[derive(Debug, Trace, Finalize)] pub struct Object { /// The type of the object. pub data: ObjectData, @@ -58,14 +189,12 @@ pub struct Object { symbol_properties: FxHashMap, /// Instance prototype `__proto__`. prototype: Value, - /// Some rust object that stores internal state - state: Option, /// Whether it can have new properties added to it. extensible: bool, } /// Defines the different types of objects. -#[derive(Debug, Trace, Finalize, Clone)] +#[derive(Debug, Trace, Finalize)] pub enum ObjectData { Array, Map(OrderedMap), @@ -80,6 +209,7 @@ pub enum ObjectData { Ordinary, Date(Date), Global, + NativeObject(Box), } impl Display for ObjectData { @@ -101,6 +231,7 @@ impl Display for ObjectData { Self::BigInt(_) => "BigInt", Self::Date(_) => "Date", Self::Global => "Global", + Self::NativeObject(_) => "NativeObject", } ) } @@ -115,7 +246,6 @@ impl Default for Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -136,7 +266,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype, - state: None, extensible: true, } } @@ -161,7 +290,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -173,7 +301,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -188,7 +315,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -200,26 +326,20 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } - /// Converts the `Value` to an `Object` type. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-toobject - pub fn from(value: &Value) -> Result { - match *value { - Value::Boolean(a) => Ok(Self::boolean(a)), - Value::Rational(a) => Ok(Self::number(a)), - Value::Integer(a) => Ok(Self::number(f64::from(a))), - Value::String(ref a) => Ok(Self::string(a.clone())), - Value::BigInt(ref bigint) => Ok(Self::bigint(bigint.clone())), - Value::Object(ref obj) => Ok(obj.borrow().clone()), - _ => Err(()), + pub fn native_object(value: T) -> Self + where + T: NativeObject, + { + Self { + data: ObjectData::NativeObject(Box::new(value)), + properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), + prototype: Value::null(), + extensible: true, } } @@ -419,16 +539,6 @@ impl Object { &mut self.symbol_properties } - #[inline] - pub fn state(&self) -> &Option { - &self.state - } - - #[inline] - pub fn state_mut(&mut self) -> &mut Option { - &mut self.state - } - pub fn prototype(&self) -> &Value { &self.prototype } @@ -437,20 +547,56 @@ impl Object { assert!(prototype.is_null() || prototype.is_object()); self.prototype = prototype } + + pub fn is_native_object(&self) -> bool { + matches!(self.data, ObjectData::NativeObject(_)) + } + + pub fn is(&self) -> bool + where + T: NativeObject, + { + use std::ops::Deref; + match self.data { + ObjectData::NativeObject(ref object) => object.deref().as_any().is::(), + _ => false, + } + } + + pub fn downcast_ref(&self) -> Option<&T> + where + T: NativeObject, + { + use std::ops::Deref; + match self.data { + ObjectData::NativeObject(ref object) => object.deref().as_any().downcast_ref::(), + _ => None, + } + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> + where + T: NativeObject, + { + use std::ops::DerefMut; + match self.data { + ObjectData::NativeObject(ref mut object) => { + object.deref_mut().as_mut_any().downcast_mut::() + } + _ => None, + } + } } /// Create a new object. pub fn make_object(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { if let Some(arg) = args.get(0) { if !arg.is_null_or_undefined() { - return Ok(Value::object(Object::from(arg).unwrap())); + return ctx.to_object(arg); } } - let global = &ctx.realm.global_obj; - - let object = Value::new_object(Some(global)); - Ok(object) + Ok(Value::new_object(Some(ctx.global()))) } /// `Object.create( proto, [propertiesObject] )` diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 579ec609308..6746709590f 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -14,7 +14,7 @@ use regex::Regex; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{InternalState, ObjectData}, + object::ObjectData, property::Property, value::{RcString, ResultValue, Value}, }, @@ -64,8 +64,6 @@ unsafe impl Trace for RegExp { unsafe_empty_trace!(); } -impl InternalState for RegExp {} - impl RegExp { /// The name of the object. pub(crate) const NAME: &'static str = "RegExp"; diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index b2016f31495..df0d6a2206d 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -7,7 +7,7 @@ mod tests; use crate::builtins::{ function::Function, - object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE}, + object::{GcObject, Object, ObjectData, PROTOTYPE}, property::{Attribute, Property, PropertyKey}, BigInt, Symbol, }; @@ -16,7 +16,6 @@ use crate::BoaProfiler; use gc::{Finalize, GcCellRef, GcCellRefMut, Trace}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use std::{ - any::Any, collections::HashSet, convert::TryFrom, f64::NAN, @@ -568,66 +567,6 @@ impl Value { } } - /// Check whether an object has an internal state set. - #[inline] - pub fn has_internal_state(&self) -> bool { - matches!(self.as_object(), Some(object) if object.state().is_some()) - } - - /// Get the internal state of an object. - pub fn get_internal_state(&self) -> Option { - self.as_object() - .and_then(|object| object.state().as_ref().cloned()) - } - - /// Run a function with a reference to the internal state. - /// - /// # Panics - /// - /// This will panic if this value doesn't have an internal state or if the internal state doesn't - /// have the concrete type `S`. - pub fn with_internal_state_ref(&self, f: F) -> R - where - S: Any + InternalState, - F: FnOnce(&S) -> R, - { - if let Some(object) = self.as_object() { - let state = object - .state() - .as_ref() - .expect("no state") - .downcast_ref() - .expect("wrong state type"); - f(state) - } else { - panic!("not an object"); - } - } - - /// Run a function with a mutable reference to the internal state. - /// - /// # Panics - /// - /// This will panic if this value doesn't have an internal state or if the internal state doesn't - /// have the concrete type `S`. - pub fn with_internal_state_mut(&self, f: F) -> R - where - S: Any + InternalState, - F: FnOnce(&mut S) -> R, - { - if let Some(mut object) = self.as_object_mut() { - let state = object - .state_mut() - .as_mut() - .expect("no state") - .downcast_mut() - .expect("wrong state type"); - f(state) - } else { - panic!("not an object"); - } - } - /// Check to see if the Value has the field, mainly used by environment records. #[inline] pub fn has_field(&self, field: &str) -> bool { @@ -684,13 +623,6 @@ impl Value { property } - /// Set internal state of an Object. Discards the previous state if it was set. - pub fn set_internal_state(&self, state: T) { - if let Some(mut object) = self.as_object_mut() { - object.state_mut().replace(InternalStateCell::new(state)); - } - } - /// Consume the function and return a Value pub fn from_func(function: Function) -> Value { // Get Length diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index c33117cb748..109b3d71d5e 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -27,7 +27,7 @@ use crate::{ builtins::{ function::{Function as FunctionObject, FunctionBody, ThisMode}, number::{f64_to_int32, f64_to_uint32}, - object::{Object, ObjectData, PROTOTYPE}, + object::{Class, ClassBuilder, Object, ObjectData, PROTOTYPE}, property::PropertyKey, value::{RcBigInt, RcString, ResultValue, Type, Value}, BigInt, Console, Number, @@ -115,7 +115,7 @@ impl Interpreter { /// Retrieves the global object of the `Realm` of this executor. #[inline] - pub(crate) fn global(&self) -> &Value { + pub fn global(&self) -> &Value { &self.realm.global_obj } @@ -675,6 +675,18 @@ impl Interpreter { pub(crate) fn console_mut(&mut self) -> &mut Console { &mut self.console } + + pub fn register_global_class(&mut self) -> ResultValue + where + T: Class, + { + let mut class_builder = ClassBuilder::new::(self); + T::methods(&mut class_builder)?; + + let class = class_builder.build(); + self.global().set_field(T::NAME, class); + Ok(Value::undefined()) + } } impl Executable for Node { diff --git a/boa/src/lib.rs b/boa/src/lib.rs index 4314d77f794..8ee24ba4cc6 100644 --- a/boa/src/lib.rs +++ b/boa/src/lib.rs @@ -49,6 +49,8 @@ pub use crate::{ syntax::{lexer::Lexer, parser::Parser}, }; +pub use gc::{custom_trace, unsafe_empty_trace, Finalize, Trace}; + fn parser_expr(src: &str) -> Result { let mut lexer = Lexer::new(src); lexer.lex().map_err(|e| format!("Syntax Error: {}", e))?;