From 81b79053fe8436cd0aa091e8b8f2ede001ada2ce Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Mon, 25 May 2020 22:53:08 +0200 Subject: [PATCH 1/9] Specialized Number, String, Boolean, BigInt, Function, Symbol objects - Added helper functions for Object and ValueData - Added `Hash` trait to `Value` - Abstracted Object field access - Deprecated `ObjectInternalMethods` - Optimized Realm creation - Fixed `Symbol` primitive --- boa/src/builtins/array/mod.rs | 14 +- boa/src/builtins/bigint/mod.rs | 26 +- boa/src/builtins/boolean/mod.rs | 86 +-- boa/src/builtins/console/mod.rs | 7 +- boa/src/builtins/error/mod.rs | 8 +- boa/src/builtins/error/range.rs | 11 +- boa/src/builtins/error/type.rs | 7 +- boa/src/builtins/function/mod.rs | 111 +-- boa/src/builtins/json/mod.rs | 15 +- boa/src/builtins/math/mod.rs | 19 +- boa/src/builtins/mod.rs | 7 +- boa/src/builtins/number/mod.rs | 58 +- ...l_methods_trait.rs => internal_methods.rs} | 213 ++++-- boa/src/builtins/object/mod.rs | 634 +++++++----------- boa/src/builtins/regexp/mod.rs | 33 +- boa/src/builtins/string/mod.rs | 72 +- boa/src/builtins/symbol/mod.rs | 150 +++-- boa/src/builtins/symbol/tests.rs | 2 +- boa/src/builtins/value/conversions.rs | 4 +- boa/src/builtins/value/display.rs | 46 +- boa/src/builtins/value/equality.rs | 7 +- boa/src/builtins/value/hash.rs | 52 ++ boa/src/builtins/value/mod.rs | 346 +++++----- boa/src/builtins/value/tests.rs | 63 ++ boa/src/environment/lexical_environment.rs | 8 +- boa/src/exec/expression/mod.rs | 15 +- boa/src/exec/mod.rs | 99 ++- 27 files changed, 1138 insertions(+), 975 deletions(-) rename boa/src/builtins/object/{internal_methods_trait.rs => internal_methods.rs} (55%) create mode 100644 boa/src/builtins/value/hash.rs diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 646011e8418..e7fc9f3de7a 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -15,7 +15,7 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{same_value_zero, ResultValue, Value, ValueData}, }, @@ -42,7 +42,7 @@ impl Array { .get_global_object() .expect("Could not get global object"), )); - array.set_kind(ObjectKind::Array); + array.set_data(ObjectData::Array); array.borrow().set_internal_slot( INSTANCE_PROTOTYPE, interpreter @@ -117,7 +117,7 @@ impl Array { this.set_internal_slot(INSTANCE_PROTOTYPE, prototype); // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Array); + this.set_data(ObjectData::Array); // add our arguments in let mut length = args.len() as i32; @@ -176,7 +176,7 @@ impl Array { // 1. ValueData::Object(ref obj) => { // 2. - if (*obj).deref().borrow().kind == ObjectKind::Array { + if let ObjectData::Array = (*obj).deref().borrow().data { return Ok(value_true); } Ok(value_false) @@ -1008,7 +1008,7 @@ impl Array { let prototype = Value::new_object(None); let length = Property::default().value(Value::from(0)); - prototype.set_property_slice("length", length); + prototype.set_property("length", length); make_builtin_fn(Self::concat, "concat", &prototype, 1); make_builtin_fn(Self::push, "push", &prototype, 1); @@ -1043,6 +1043,8 @@ impl Array { #[inline] pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("array", "init"); - global.set_field("Array", Self::create(global)); + + let array = Self::create(global); + global.as_object_mut().unwrap().insert_field("Array", array); } } diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index a685d1f6e8f..e3aff02ee77 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -15,6 +15,7 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, + object::ObjectData, value::{ResultValue, Value, ValueData}, }, exec::Interpreter, @@ -62,9 +63,8 @@ impl BigInt { // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // a. Assert: Type(value.[[BigIntData]]) is BigInt. // b. Return value.[[BigIntData]]. - ValueData::Object(_) => { - let bigint = value.get_internal_slot("BigIntData"); - if let ValueData::BigInt(bigint) = bigint.data() { + ValueData::Object(ref object) => { + if let ObjectData::BigInt(ref bigint) = object.borrow().data { return Ok(bigint.clone()); } } @@ -86,16 +86,12 @@ impl BigInt { /// /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt - pub(crate) fn make_bigint( - _this: &mut Value, - args: &[Value], - ctx: &mut Interpreter, - ) -> ResultValue { + pub(crate) fn make_bigint(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let data = match args.get(0) { - Some(ref value) => Value::from(ctx.to_bigint(value)?), - None => Value::from(Self::from(0)), + Some(ref value) => ctx.to_bigint(value)?, + None => Self::from(0), }; - Ok(data) + Ok(Value::from(data)) } /// `BigInt.prototype.toString( [radix] )` @@ -213,7 +209,6 @@ impl BigInt { /// Create a new `Number` object pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BigIntData", Value::from(Self::from(0))); make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); @@ -230,7 +225,12 @@ impl BigInt { #[inline] pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("bigint", "init"); - global.set_field("BigInt", Self::create(global)); + + let bigint = Self::create(global); + global + .as_object_mut() + .unwrap() + .insert_field("BigInt", bigint); } } diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 76012a40e7d..0f0ce2fbce2 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -15,19 +15,40 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{internal_methods_trait::ObjectInternalMethods, ObjectKind}, + object::ObjectData, value::{ResultValue, Value, ValueData}, }, exec::Interpreter, BoaProfiler, }; -use std::{borrow::Borrow, ops::Deref}; /// Boolean implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct Boolean; impl Boolean { + /// An Utility function used to get the internal [[BooleanData]]. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue + fn this_boolean_value(value: &Value, ctx: &mut Interpreter) -> Result { + match value.data() { + ValueData::Boolean(boolean) => return Ok(*boolean), + ValueData::Object(ref object) => { + let object = object.borrow(); + if let Some(boolean) = object.as_boolean() { + return Ok(boolean); + } + } + _ => {} + } + + ctx.throw_type_error("'this' is not a boolean")?; + unreachable!(); + } + /// `[[Construct]]` Create a new boolean object /// /// `[[Call]]` Creates a new boolean primitive @@ -36,19 +57,11 @@ impl Boolean { args: &[Value], _: &mut Interpreter, ) -> ResultValue { - this.set_kind(ObjectKind::Boolean); - // Get the argument, if any - if let Some(ref value) = args.get(0) { - this.set_internal_slot("BooleanData", Self::to_boolean(value)); - } else { - this.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); - } + let data = args.get(0).map(|x| x.to_boolean()).unwrap_or(false); + this.set_data(ObjectData::Boolean(data)); - match args.get(0) { - Some(ref value) => Ok(Self::to_boolean(value)), - None => Ok(Self::to_boolean(&Value::from(false))), - } + Ok(Value::from(data)) } /// The `toString()` method returns a string representing the specified `Boolean` object. @@ -60,9 +73,9 @@ impl Boolean { /// [spec]: https://tc39.es/ecma262/#sec-boolean-object /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let b = Self::this_boolean_value(this); - Ok(Value::from(b.to_string())) + pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let boolean = Self::this_boolean_value(this, ctx)?; + Ok(Value::from(boolean.to_string())) } /// The valueOf() method returns the primitive value of a `Boolean` object. @@ -73,37 +86,8 @@ impl Boolean { /// /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf - pub(crate) fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Self::this_boolean_value(this)) - } - - // === Utility Functions === - /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) - /// Creates a new boolean value from the input - #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_boolean(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Object(_) => Value::from(true), - ValueData::String(ref s) if !s.is_empty() => Value::from(true), - ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true), - ValueData::Integer(n) if n != 0 => Value::from(true), - ValueData::Boolean(v) => Value::from(v), - _ => Value::from(false), - } - } - - /// An Utility function used to get the internal BooleanData. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue - pub(crate) fn this_boolean_value(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Boolean(v) => Value::from(v), - ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"), - _ => Value::from(false), - } + pub(crate) fn value_of(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + Ok(Value::from(Self::this_boolean_value(this, ctx)?)) } /// Create a new `Boolean` object. @@ -111,7 +95,6 @@ impl Boolean { // Create Prototype // https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); @@ -130,6 +113,11 @@ impl Boolean { #[inline] pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("boolean", "init"); - global.set_field("Boolean", Self::create(global)); + + let boolean = Self::create(global); + global + .as_object_mut() + .unwrap() + .insert_field("Boolean", boolean); } } diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index aaf8261abe4..5a726ad3ad6 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -556,5 +556,10 @@ pub fn create(global: &Value) -> Value { #[inline] pub fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("console", "init"); - global.set_field("console", create(global)); + + let console = create(global); + global + .as_object_mut() + .unwrap() + .insert_field("console", console); } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index a6df97025be..483de7d3b54 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{ function::{make_builtin_fn, make_constructor_fn}, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, @@ -49,7 +49,7 @@ impl Error { } // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Err(this.clone()) } @@ -83,6 +83,8 @@ impl Error { /// Initialise the global object with the `Error` object. pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("error", "init"); - global.set_field("Error", Self::create(global)); + + let error = Self::create(global); + global.as_object_mut().unwrap().insert_field("Error", error); } } diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 0af07ff45ad..43fd40a8782 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{ function::make_builtin_fn, function::make_constructor_fn, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, @@ -39,7 +39,7 @@ impl RangeError { } // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Err(this.clone()) } @@ -73,6 +73,11 @@ impl RangeError { /// Initialise the global object with the `RangeError` object. pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("rangeerror", "init"); - global.set_field("RangeError", Self::create(global)); + + let range_error = Self::create(global); + global + .as_object_mut() + .unwrap() + .insert_field("RangeError", range_error); } } diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index f15c39cd7f8..c536175beb7 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -19,10 +19,11 @@ use crate::{ builtins::{ function::make_builtin_fn, function::make_constructor_fn, - object::ObjectKind, + object::ObjectData, value::{ResultValue, Value}, }, exec::Interpreter, + BoaProfiler, }; /// JavaScript `TypeError` implementation. @@ -45,7 +46,7 @@ impl TypeError { // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); + this.set_data(ObjectData::Error); Err(this.clone()) } @@ -78,6 +79,8 @@ impl TypeError { /// Initialise the global object with the `RangeError` object. pub(crate) fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("typeerror", "init"); + global.set_field("TypeError", Self::create(global)); } } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 5ad1ed724e5..09b8b8a9768 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -14,7 +14,7 @@ use crate::{ builtins::{ array::Array, - object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{ResultValue, Value}, }, @@ -40,7 +40,8 @@ pub enum ConstructorKind { /// Defines how this references are interpreted within the formal parameters and code body of the function. /// /// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical -#[derive(Copy, Finalize, Debug, Clone)] + +#[derive(Debug, Copy, Finalize, Clone, PartialEq, PartialOrd, Hash)] pub enum ThisMode { Lexical, NonLexical, @@ -60,12 +61,24 @@ pub enum FunctionBody { impl Debug for FunctionBody { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::BuiltIn(_) => write!(f, "native code"), + Self::BuiltIn(_) => write!(f, "[native]"), Self::Ordinary(statements) => write!(f, "{:?}", statements), } } } +impl PartialEq for FunctionBody { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::BuiltIn(a), Self::BuiltIn(b)) => std::ptr::eq(a, b), + (Self::Ordinary(a), Self::Ordinary(b)) => a == b, + (_, _) => false, + } + } +} + +impl Eq for FunctionBody {} + /// `Trace` implementation for `FunctionBody`. /// /// This is indeed safe, but we need to mark this as an empty trace because neither @@ -164,20 +177,20 @@ impl Function { /// pub fn call( &self, - this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + this: &mut Value, args_list: &[Value], interpreter: &mut Interpreter, - this_obj: &mut Value, ) -> ResultValue { let _timer = BoaProfiler::global().start_event("function::call", "function"); if self.callable { match self.body { - FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), + FunctionBody::BuiltIn(func) => func(this, args_list, interpreter), FunctionBody::Ordinary(ref body) => { // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // let local_env = new_function_environment( - this.clone(), + function, None, Some(self.environment.as_ref().unwrap().clone()), BindingStatus::Uninitialized, @@ -223,23 +236,23 @@ impl Function { /// pub fn construct( &self, - this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) + this: &mut Value, args_list: &[Value], interpreter: &mut Interpreter, - this_obj: &mut Value, ) -> ResultValue { if self.constructable { match self.body { FunctionBody::BuiltIn(func) => { - func(this_obj, args_list, interpreter)?; - Ok(this_obj.clone()) + func(this, args_list, interpreter)?; + Ok(this.clone()) } FunctionBody::Ordinary(ref body) => { // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // let local_env = new_function_environment( - this.clone(), - Some(this_obj.clone()), + function, + Some(this.clone()), Some(self.environment.as_ref().unwrap().clone()), BindingStatus::Initialized, ); @@ -364,7 +377,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { .writable(true) .configurable(true); - obj.properties.insert(index.to_string(), prop); + obj.properties_mut().insert(index.to_string(), prop); index += 1; } @@ -375,7 +388,10 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { /// // This gets called when a new Function() is created. pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.set_kind(ObjectKind::Function); + this.set_data(ObjectData::Function(Function::builtin( + Vec::new(), + |_, _, _| Ok(Value::undefined()), + ))); Ok(this.clone()) } @@ -394,43 +410,49 @@ pub fn make_constructor_fn( length: i32, body: NativeFunctionData, global: &Value, - proto: Value, + prototype: Value, constructable: bool, ) -> Value { // Create the native function - let mut constructor_fn = Function::builtin(Vec::new(), body); + let mut function = Function::builtin(Vec::new(), body); + function.constructable = constructable; - constructor_fn.constructable = constructable; + let mut constructor = Object::function(function); // Get reference to Function.prototype - let func_prototype = global.get_field("Function").get_field(PROTOTYPE); - // Create the function object and point its instance prototype to Function.prototype - let mut constructor_obj = Object::function(); - constructor_obj.set_func(constructor_fn); - - constructor_obj.set_internal_slot(INSTANCE_PROTOTYPE, func_prototype); - let constructor_val = Value::from(constructor_obj); - - // Set proto.constructor -> constructor_obj - proto.set_field("constructor", constructor_val.clone()); - constructor_val.set_field(PROTOTYPE, proto); + constructor.set_internal_slot( + INSTANCE_PROTOTYPE, + global.get_field("Function").get_field(PROTOTYPE), + ); let length = Property::new() .value(Value::from(length)) .writable(false) .configurable(false) .enumerable(false); - constructor_val.set_property_slice("length", length); + constructor.insert_property("length", length); let name = Property::new() .value(Value::from(name)) .writable(false) .configurable(false) .enumerable(false); - constructor_val.set_property_slice("name", name); + constructor.insert_property("name", name); + + let constructor = Value::from(constructor); + + prototype + .as_object_mut() + .unwrap() + .insert_field("constructor", constructor.clone()); - constructor_val + constructor + .as_object_mut() + .unwrap() + .insert_field(PROTOTYPE, prototype); + + constructor } /// Creates a new member function of a `Object` or `prototype`. @@ -455,23 +477,26 @@ pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, where N: Into, { - let name_copy: String = name.into(); - let label = format!("{}{}", String::from("make_builtin_fn: "), &name_copy); - let _timer = BoaProfiler::global().start_event(&label, "init"); - let func = Function::builtin(Vec::new(), function); - - let mut new_func = Object::function(); - new_func.set_func(func); + let name = name.into(); + let _timer = BoaProfiler::global().start_event(&name, "make_builtin_fn"); - let new_func_obj = Value::from(new_func); - new_func_obj.set_field("length", length); + let mut function = Object::function(Function::builtin(Vec::new(), function)); + function.insert_field("length", Value::from(length)); - parent.set_field(Value::from(name_copy), new_func_obj); + parent + .as_object_mut() + .unwrap() + .insert_field(name, Value::from(function)); } /// Initialise the `Function` object on the global object. #[inline] pub fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("function", "init"); - global.set_field("Function", create(global)); + + let function = create(global); + global + .as_object_mut() + .unwrap() + .insert_field("Function", function); } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index d0edaeca66e..9f39044e14f 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -15,7 +15,6 @@ use crate::builtins::{ function::make_builtin_fn, - object::ObjectKind, property::Property, value::{ResultValue, Value}, }; @@ -67,7 +66,7 @@ fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) let obj = value.as_object().as_deref().cloned(); if let Some(obj) = obj { - for key in obj.properties.keys() { + for key in obj.properties().keys() { let v = walk(reviver, ctx, &mut value, Value::from(key.as_str())); match v { Ok(v) if !v.is_undefined() => { @@ -119,7 +118,7 @@ pub fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Result .map(|obj| { let object_to_return = Value::new_object(None); for (key, val) in obj - .properties + .properties() .iter() .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) { @@ -136,10 +135,10 @@ pub fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Result Ok(Value::from(object_to_return.to_json(ctx)?.to_string())) }) .ok_or_else(Value::undefined)? - } else if replacer_as_object.kind == ObjectKind::Array { + } else if replacer_as_object.is_array() { let mut obj_to_return = - serde_json::Map::with_capacity(replacer_as_object.properties.len() - 1); - let fields = replacer_as_object.properties.keys().filter_map(|key| { + serde_json::Map::with_capacity(replacer_as_object.properties().len() - 1); + let fields = replacer_as_object.properties().keys().filter_map(|key| { if key == "length" { None } else { @@ -175,5 +174,7 @@ pub fn create(global: &Value) -> Value { #[inline] pub fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("json", "init"); - global.set_field("JSON", create(global)); + + let json = create(global); + global.as_object_mut().unwrap().insert_field("JSON", json); } diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 775642ae299..7cfb4c77baf 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -511,14 +511,17 @@ pub fn create(global: &Value) -> Value { let _timer = BoaProfiler::global().start_event("math:create", "init"); let math = Value::new_object(Some(global)); - math.set_field("E", Value::from(f64::consts::E)); - math.set_field("LN2", Value::from(f64::consts::LN_2)); - math.set_field("LN10", Value::from(f64::consts::LN_10)); - math.set_field("LOG2E", Value::from(f64::consts::LOG2_E)); - math.set_field("LOG10E", Value::from(f64::consts::LOG10_E)); - math.set_field("SQRT1_2", Value::from(0.5_f64.sqrt())); - math.set_field("SQRT2", Value::from(f64::consts::SQRT_2)); - math.set_field("PI", Value::from(f64::consts::PI)); + { + let mut properties = math.as_object_mut().unwrap(); + properties.insert_field("E", Value::from(f64::consts::E)); + properties.insert_field("LN2", Value::from(f64::consts::LN_2)); + properties.insert_field("LN10", Value::from(f64::consts::LN_10)); + properties.insert_field("LOG2E", Value::from(f64::consts::LOG2_E)); + properties.insert_field("LOG10E", Value::from(f64::consts::LOG10_E)); + properties.insert_field("SQRT1_2", Value::from(0.5_f64.sqrt())); + properties.insert_field("SQRT2", Value::from(f64::consts::SQRT_2)); + properties.insert_field("PI", Value::from(f64::consts::PI)); + } make_builtin_fn(abs, "abs", &math, 1); make_builtin_fn(acos, "acos", &math, 1); make_builtin_fn(acosh, "acosh", &math, 1); diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index d61d9c6909a..e45ca3754cd 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -23,16 +23,17 @@ pub(crate) use self::{ bigint::BigInt, boolean::Boolean, error::{Error, RangeError, TypeError}, - function::Function, number::Number, regexp::RegExp, string::String, + symbol::Symbol, value::{ResultValue, Value}, }; /// Initializes builtin objects and functions #[inline] pub fn init(global: &Value) { + function::init(global); Array::init(global); BigInt::init(global); Boolean::init(global); @@ -42,11 +43,11 @@ pub fn init(global: &Value) { nan::init(global); Number::init(global); object::init(global); - function::init(global); RegExp::init(global); String::init(global); - symbol::init(global); + Symbol::init(global); console::init(global); + Error::init(global); RangeError::init(global); TypeError::init(global); diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index b2cd26b5f77..d37b4165980 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -18,18 +18,15 @@ mod tests; use super::{ function::{make_builtin_fn, make_constructor_fn}, - object::ObjectKind, + object::ObjectData, }; use crate::{ - builtins::{ - object::internal_methods_trait::ObjectInternalMethods, - value::{ResultValue, Value, ValueData}, - }, + builtins::value::{ResultValue, Value, ValueData}, exec::Interpreter, BoaProfiler, }; use num_traits::float::FloatCore; -use std::{borrow::Borrow, ops::Deref}; +use std::ops::Deref; const BUF_SIZE: usize = 2200; @@ -47,19 +44,22 @@ impl Number { /// Helper function that converts a Value to a Number. #[allow(clippy::wrong_self_convention)] fn to_number(value: &Value) -> Value { - match *value.deref().borrow() { + match value.data() { ValueData::Boolean(b) => { - if b { + if *b { Value::from(1) } else { Value::from(0) } } ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN), - ValueData::Integer(i) => Value::from(f64::from(i)), - ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"), + ValueData::Integer(i) => Value::from(f64::from(*i)), + ValueData::Object(ref o) => match (o).deref().borrow().data { + ObjectData::Number(num) => Value::from(num), + _ => unreachable!(), + }, ValueData::Null => Value::from(0), - ValueData::Rational(n) => Value::from(n), + ValueData::Rational(n) => Value::from(*n), ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()), ValueData::String(ref s) => match s.parse::() { Ok(n) => Value::from(n), @@ -86,13 +86,12 @@ impl Number { _ctx: &mut Interpreter, ) -> ResultValue { let data = match args.get(0) { - Some(ref value) => Self::to_number(value), - None => Self::to_number(&Value::from(0)), + Some(ref value) => Self::to_number(value).to_number(), + None => 0.0, }; - this.set_kind(ObjectKind::Number); - this.set_internal_slot("NumberData", data.clone()); + this.set_data(ObjectData::Number(data)); - Ok(data) + Ok(Value::from(data)) } /// `Number.prototype.toExponential( [fractionDigits] )` @@ -530,7 +529,6 @@ impl Number { /// Create a new `Number` object pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("NumberData", Value::from(0)); make_builtin_fn(Self::to_exponential, "toExponential", &prototype, 1); make_builtin_fn(Self::to_fixed, "toFixed", &prototype, 1); @@ -556,14 +554,17 @@ impl Number { // Constants from: // https://tc39.es/ecma262/#sec-properties-of-the-number-constructor - number.set_field("EPSILON", Value::from(f64::EPSILON)); - number.set_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); - number.set_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); - number.set_field("MAX_VALUE", Value::from(f64::MAX)); - number.set_field("MIN_VALUE", Value::from(f64::MIN)); - number.set_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY)); - number.set_field("POSITIVE_INFINITY", Value::from(f64::INFINITY)); - number.set_field("NaN", Value::from(f64::NAN)); + { + let mut properties = number.as_object_mut().unwrap(); + properties.insert_field("EPSILON", Value::from(f64::EPSILON)); + properties.insert_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); + properties.insert_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); + properties.insert_field("MAX_VALUE", Value::from(f64::MAX)); + properties.insert_field("MIN_VALUE", Value::from(f64::MIN)); + properties.insert_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY)); + properties.insert_field("POSITIVE_INFINITY", Value::from(f64::INFINITY)); + properties.insert_field("NaN", Value::from(f64::NAN)); + } number } @@ -572,7 +573,12 @@ impl Number { #[inline] pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("number", "init"); - global.set_field("Number", Self::create(global)); + + let number = Self::create(global); + global + .as_object_mut() + .unwrap() + .insert_field("Number", number); } /// The abstract operation Number::equal takes arguments diff --git a/boa/src/builtins/object/internal_methods_trait.rs b/boa/src/builtins/object/internal_methods.rs similarity index 55% rename from boa/src/builtins/object/internal_methods_trait.rs rename to boa/src/builtins/object/internal_methods.rs index 47b21dd2827..be47ba6ac99 100644 --- a/boa/src/builtins/object/internal_methods_trait.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -1,38 +1,27 @@ -//! This module defines the `ObjectInternalMethods` trait. +//! This module defines the object internal methods. //! //! More information: //! - [ECMAScript reference][spec] //! //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -use crate::{ - builtins::{ - object::{Object, INSTANCE_PROTOTYPE}, - property::Property, - value::{same_value, Value, ValueData}, - }, - BoaProfiler, +use crate::builtins::{ + object::{Object, INSTANCE_PROTOTYPE, PROTOTYPE}, + property::Property, + value::{same_value, Value, ValueData}, }; +use crate::BoaProfiler; use std::borrow::Borrow; use std::ops::Deref; -/// Here lies the internal methods for ordinary objects. -/// -/// Most objects make use of these methods, including exotic objects like functions. -/// So thats why this is a trait -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots -pub trait ObjectInternalMethods { - /// Check if has property. +impl Object { + /// Check if object has property. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p - fn has_property(&self, val: &Value) -> bool { + pub fn has_property(&self, val: &Value) -> bool { debug_assert!(Property::is_property_key(val)); let prop = self.get_own_property(val); if prop.value.is_none() { @@ -57,7 +46,8 @@ pub trait ObjectInternalMethods { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible - fn is_extensible(&self) -> bool { + #[inline] + pub fn is_extensible(&self) -> bool { let val = self.get_internal_slot("extensible"); match *val.deref().borrow() { ValueData::Boolean(b) => b, @@ -71,13 +61,14 @@ pub trait ObjectInternalMethods { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions - fn prevent_extensions(&mut self) -> bool { + #[inline] + pub fn prevent_extensions(&mut self) -> bool { self.set_internal_slot("extensible", Value::from(false)); true } /// Delete property. - fn delete(&mut self, prop_key: &Value) -> bool { + pub fn delete(&mut self, prop_key: &Value) -> bool { debug_assert!(Property::is_property_key(prop_key)); let desc = self.get_own_property(prop_key); if desc @@ -97,7 +88,7 @@ pub trait ObjectInternalMethods { } // [[Get]] - fn get(&self, val: &Value) -> Value { + pub fn get(&self, val: &Value) -> Value { debug_assert!(Property::is_property_key(val)); let desc = self.get_own_property(val); if desc.value.clone().is_none() @@ -127,13 +118,13 @@ pub trait ObjectInternalMethods { return Value::undefined(); } - // TODO!!!!! Call getter from here + // TODO: Call getter from here! Value::undefined() } /// [[Set]] /// - fn set(&mut self, field: Value, val: Value) -> bool { + pub fn set(&mut self, field: Value, val: Value) -> bool { let _timer = BoaProfiler::global().start_event("Object::set", "object"); // [1] debug_assert!(Property::is_property_key(&field)); @@ -171,8 +162,15 @@ pub trait ObjectInternalMethods { } } - fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { + /// Define an own property. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc + pub fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); + let mut current = self.get_own_property(&Value::from(property_key.to_string())); let extensible = self.is_extensible(); @@ -282,25 +280,162 @@ pub trait ObjectInternalMethods { true } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p - /// The specification returns a Property Descriptor or Undefined. These are 2 separate types and we can't do that here. - fn get_own_property(&self, prop: &Value) -> Property; + /// The specification returns a Property Descriptor or Undefined. + /// + /// These are 2 separate types and we can't do that here. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p + pub fn get_own_property(&self, prop: &Value) -> Property { + let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); + + debug_assert!(Property::is_property_key(prop)); + // Prop could either be a String or Symbol + match *(*prop) { + ValueData::String(ref st) => { + match self.properties.get(st) { + // If O does not have an own property with key P, return undefined. + // In this case we return a new empty Property + None => Property::default(), + Some(ref v) => { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + } + } + } + ValueData::Symbol(ref symbol) => { + match self.symbol_properties().get(&symbol.hash()) { + // If O does not have an own property with key P, return undefined. + // In this case we return a new empty Property + None => Property::default(), + Some(ref v) => { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + } + } + } + _ => Property::default(), + } + } - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v - fn set_prototype_of(&mut self, val: Value) -> bool; + /// `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.get_internal_slot(PROTOTYPE); + if same_value(¤t, &val, false) { + return true; + } + let extensible = self.get_internal_slot("extensible"); + if extensible.is_null() { + 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, false) { + return false; + } else { + p = p.get_internal_slot(PROTOTYPE); + } + } + self.set_internal_slot(PROTOTYPE, val); + true + } /// Returns either the prototype or null - /// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof - fn get_prototype_of(&self) -> Value { + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof + #[inline] + pub fn get_prototype_of(&self) -> Value { self.get_internal_slot(INSTANCE_PROTOTYPE) } - /// Utility function to get an immutable internal slot or Null - fn get_internal_slot(&self, name: &str) -> Value; + /// Helper function to get an immutable internal slot or `Null`. + #[inline] + pub fn get_internal_slot(&self, name: &str) -> Value { + let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object"); + match self.internal_slots.get(name) { + Some(v) => v.clone(), + None => Value::null(), + } + } - fn set_internal_slot(&mut self, name: &str, val: Value); + /// Helper function to set an internal slot. + #[inline] + pub fn set_internal_slot(&mut self, name: &str, val: Value) { + self.internal_slots.insert(name.to_string(), val); + } - fn insert_property(&mut self, name: String, p: Property); + /// Helper function for property insertion. + #[inline] + pub fn insert_property(&mut self, name: N, p: Property) + where + N: Into, + { + self.properties.insert(name.into(), p); + } - fn remove_property(&mut self, name: &str); + /// Helper function for property removal. + #[inline] + pub fn remove_property(&mut self, name: &str) { + self.properties.remove(name); + } + + #[inline] + pub fn insert_field(&mut self, name: N, value: Value) -> Option + where + N: Into, + { + self.properties.insert( + name.into(), + Property::default() + .value(value) + .writable(true) + .configurable(true) + .enumerable(true), + ) + } + + #[inline] + pub fn get_field(&self, name: &str) -> Option<&Value> { + self.properties.get(name).and_then(|x| x.value.as_ref()) + } } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index c367fdab636..3e8875ba4c0 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -17,24 +17,23 @@ use crate::{ builtins::{ function::Function, property::Property, - value::{same_value, ResultValue, Value, ValueData}, + value::{ResultValue, Value, ValueData}, + BigInt, Symbol, }, exec::Interpreter, BoaProfiler, }; -use gc::{unsafe_empty_trace, Finalize, Trace}; +use gc::{Finalize, Trace}; use rustc_hash::FxHashMap; use std::{ - borrow::Borrow, - fmt::{self, Debug, Display, Error, Formatter}, + fmt::{Debug, Display, Error, Formatter}, ops::Deref, }; use super::function::{make_builtin_fn, make_constructor_fn}; -pub use internal_methods_trait::ObjectInternalMethods; pub use internal_state::{InternalState, InternalStateCell}; -pub mod internal_methods_trait; +pub mod internal_methods; mod internal_state; #[cfg(test)] @@ -47,322 +46,85 @@ pub static PROTOTYPE: &str = "prototype"; pub static INSTANCE_PROTOTYPE: &str = "__proto__"; /// The internal representation of an JavaScript object. -#[derive(Trace, Finalize, Clone)] +#[derive(Debug, Trace, Finalize, Clone)] pub struct Object { /// The type of the object. - pub kind: ObjectKind, + pub data: ObjectData, /// Internal Slots - pub internal_slots: FxHashMap, + internal_slots: FxHashMap, /// Properties - pub properties: FxHashMap, + properties: FxHashMap, /// Symbol Properties - pub sym_properties: FxHashMap, + symbol_properties: FxHashMap, /// Some rust object that stores internal state - pub state: Option, - /// Function - pub func: Option, + state: Option, } -impl Debug for Object { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - writeln!(f, "{{")?; - writeln!(f, "\tkind: {}", self.kind)?; - writeln!(f, "\tstate: {:?}", self.state)?; - writeln!(f, "\tfunc: {:?}", self.func)?; - writeln!(f, "\tproperties: {{")?; - for (key, _) in self.properties.iter() { - writeln!(f, "\t\t{}", key)?; - } - writeln!(f, "\t }}")?; - write!(f, "}}") - } +/// Defines the different types of objects. +#[derive(Debug, Trace, Finalize, Clone)] +pub enum ObjectData { + Array, + BigInt(BigInt), + Boolean(bool), + Function(Function), + String(String), + Number(f64), + Symbol(Symbol), + Error, + Ordinary, } -impl ObjectInternalMethods for 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 - fn set_prototype_of(&mut self, val: Value) -> bool { - debug_assert!(val.is_object() || val.is_null()); - let current = self.get_internal_slot(PROTOTYPE); - if same_value(¤t, &val, false) { - return true; - } - let extensible = self.get_internal_slot("extensible"); - if extensible.is_null() { - 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, false) { - return false; - } else { - p = p.get_internal_slot(PROTOTYPE); - } - } - self.set_internal_slot(PROTOTYPE, val); - true - } - - /// Helper function for property insertion. - fn insert_property(&mut self, name: String, p: Property) { - self.properties.insert(name, p); - } - - /// Helper function for property removal. - fn remove_property(&mut self, name: &str) { - self.properties.remove(name); - } - - /// Helper function to set an internal slot - fn set_internal_slot(&mut self, name: &str, val: Value) { - self.internal_slots.insert(name.to_string(), val); - } - - /// Helper function to get an immutable internal slot or Null - fn get_internal_slot(&self, name: &str) -> Value { - let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object"); - match self.internal_slots.get(name) { - Some(v) => v.clone(), - None => Value::null(), - } - } - - /// The specification returns a Property Descriptor or Undefined. - /// - /// These are 2 separate types and we can't do that here. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p - fn get_own_property(&self, prop: &Value) -> Property { - let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object"); - debug_assert!(Property::is_property_key(prop)); - // Prop could either be a String or Symbol - match *(*prop) { - ValueData::String(ref st) => { - match self.properties.get(st) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { - let mut d = Property::default(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - d.writable = v.writable; - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d - } - } - } - ValueData::Symbol(ref sym) => { - let sym_id = (**sym) - .borrow() - .get_internal_slot("SymbolData") - .to_string() - .parse::() - .expect("Could not get Symbol ID"); - match self.sym_properties.get(&sym_id) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { - let mut d = Property::default(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - d.writable = v.writable; - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d - } - } - } - _ => Property::default(), - } - } - - /// Define an own property. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc - #[allow(clippy::option_unwrap_used)] - fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { - let mut current = self.get_own_property(&Value::from(property_key.to_string())); - let extensible = self.is_extensible(); - - // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor - // There currently isn't a property, lets create a new one - if current.value.is_none() || current.value.as_ref().expect("failed").is_undefined() { - if !extensible { - return false; - } - if desc.value.is_some() && desc.value.clone().unwrap().is_symbol() { - let sym_id = desc - .value - .clone() - .unwrap() - .to_string() - .parse::() - .expect("parsing failed"); - self.sym_properties.insert(sym_id, desc); - } else { - self.properties.insert(property_key, desc); - } - return true; - } - // If every field is absent we don't need to set anything - if desc.is_none() { - return true; - } - - // 4 - if !current.configurable.unwrap_or(false) { - if desc.configurable.is_some() && desc.configurable.unwrap() { - return false; - } - - if desc.enumerable.is_some() - && (desc.enumerable.as_ref().unwrap() != current.enumerable.as_ref().unwrap()) - { - return false; - } - } - - // 5 - if desc.is_generic_descriptor() { - // 6 - } else if current.is_data_descriptor() != desc.is_data_descriptor() { - // a - if !current.configurable.unwrap() { - return false; - } - // b - if current.is_data_descriptor() { - // Convert to accessor - current.value = None; - current.writable = None; - } else { - // c - // convert to data - current.get = None; - current.set = None; - } - - if current.value.is_some() && current.value.clone().unwrap().is_symbol() { - let sym_id = current - .value - .clone() - .unwrap() - .to_string() - .parse::() - .expect("parsing failed"); - self.sym_properties.insert(sym_id, current); - } else { - self.properties.insert(property_key.clone(), current); - } - // 7 - } else if current.is_data_descriptor() && desc.is_data_descriptor() { - // a - if !current.configurable.unwrap() && !current.writable.unwrap() { - if desc.writable.is_some() && desc.writable.unwrap() { - return false; - } - - if desc.value.is_some() - && !same_value( - &desc.value.clone().unwrap(), - ¤t.value.clone().unwrap(), - false, - ) - { - return false; - } - - return true; - } - // 8 - } else { - if !current.configurable.unwrap() { - if desc.set.is_some() - && !same_value( - &desc.set.clone().unwrap(), - ¤t.set.clone().unwrap(), - false, - ) - { - return false; - } - - if desc.get.is_some() - && !same_value( - &desc.get.clone().unwrap(), - ¤t.get.clone().unwrap(), - false, - ) - { - return false; - } +impl Display for ObjectData { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + write!( + f, + "{}", + match self { + Self::Function(_) => "Function", + Self::Array => "Array", + Self::String(_) => "String", + Self::Symbol(_) => "Symbol", + Self::Error => "Error", + Self::Ordinary => "Ordinary", + Self::Boolean(_) => "Boolean", + Self::Number(_) => "Number", + Self::BigInt(_) => "BigInt", } - - return true; - } - // 9 - self.properties.insert(property_key, desc); - true + ) } } -impl Object { +impl Default for Object { /// Return a new ObjectData struct, with `kind` set to Ordinary - pub fn default() -> Self { + fn default() -> Self { let mut object = Self { - kind: ObjectKind::Ordinary, + data: ObjectData::Ordinary, internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, }; object.set_internal_slot("extensible", Value::from(true)); object } +} + +impl Object { + pub fn new() -> Self { + Default::default() + } /// Return a new ObjectData struct, with `kind` set to Ordinary - pub fn function() -> Self { + pub fn function(function: Function) -> Self { let _timer = BoaProfiler::global().start_event("Object::Function", "object"); + let mut object = Self { - kind: ObjectKind::Function, + data: ObjectData::Function(function), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, }; object.set_internal_slot("extensible", Value::from(true)); @@ -385,73 +147,48 @@ impl Object { obj } - /// Set the function this object wraps - pub fn set_func(&mut self, val: Function) { - self.func = Some(val); - } - /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. - fn from_boolean(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::Boolean, + pub fn boolean(value: bool) -> Self { + Self { + data: ObjectData::Boolean(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("BooleanData".to_string(), argument.clone()); - obj + } } /// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument. - fn from_number(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::Number, + pub fn number(value: f64) -> Self { + Self { + data: ObjectData::Number(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("NumberData".to_string(), argument.clone()); - obj + } } /// Return a new `String` object whose `[[StringData]]` internal slot is set to argument. - fn from_string(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::String, + pub fn string(value: String) -> Self { + Self { + data: ObjectData::String(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("StringData".to_string(), argument.clone()); - obj + } } /// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument. - fn from_bigint(argument: &Value) -> Self { - let mut obj = Self { - kind: ObjectKind::BigInt, + pub fn bigint(value: BigInt) -> Self { + Self { + data: ObjectData::BigInt(value), internal_slots: FxHashMap::default(), properties: FxHashMap::default(), - sym_properties: FxHashMap::default(), + symbol_properties: FxHashMap::default(), state: None, - func: None, - }; - - obj.internal_slots - .insert("BigIntData".to_string(), argument.clone()); - obj + } } /// Converts the `Value` to an `Object` type. @@ -461,11 +198,12 @@ impl Object { /// /// [spec]: https://tc39.es/ecma262/#sec-toobject pub fn from(value: &Value) -> Result { - match *value.deref().borrow() { - ValueData::Boolean(_) => Ok(Self::from_boolean(value)), - ValueData::Rational(_) => Ok(Self::from_number(value)), - ValueData::String(_) => Ok(Self::from_string(value)), - ValueData::BigInt(_) => Ok(Self::from_bigint(value)), + match *value.data() { + ValueData::Boolean(a) => Ok(Self::boolean(a)), + ValueData::Rational(a) => Ok(Self::number(a)), + ValueData::Integer(a) => Ok(Self::number(f64::from(a))), + ValueData::String(ref a) => Ok(Self::string(a.clone())), + ValueData::BigInt(ref bigint) => Ok(Self::bigint(bigint.clone())), ValueData::Object(ref obj) => Ok((*obj).deref().borrow().clone()), _ => Err(()), } @@ -477,11 +215,9 @@ impl Object { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-iscallable + #[inline] pub fn is_callable(&self) -> bool { - match self.func { - Some(ref function) => function.is_callable(), - None => false, - } + matches!(self.data, ObjectData::Function(ref f) if f.is_callable()) } /// It determines if Object is a function object with a [[Construct]] internal method. @@ -490,58 +226,168 @@ impl Object { /// - [EcmaScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-isconstructor + #[inline] pub fn is_constructable(&self) -> bool { - match self.func { - Some(ref function) => function.is_constructable(), - None => false, + matches!(self.data, ObjectData::Function(ref f) if f.is_constructable()) + } + + /// Checks if it an `Array` object. + #[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, } } -} -/// Defines the different types of objects. -#[derive(Finalize, Debug, Copy, Clone, Eq, PartialEq)] -pub enum ObjectKind { - Function, - Array, - String, - Symbol, - Error, - Ordinary, - Boolean, - Number, - BigInt, -} + /// Checks if it a `String` object. + #[inline] + pub fn is_string(&self) -> bool { + matches!(self.data, ObjectData::String(_)) + } -impl Display for ObjectKind { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - write!( - f, - "{}", - match self { - Self::Function => "Function", - Self::Array => "Array", - Self::String => "String", - Self::Symbol => "Symbol", - Self::Error => "Error", - Self::Ordinary => "Ordinary", - Self::Boolean => "Boolean", - Self::Number => "Number", - Self::BigInt => "BigInt", - } - ) + #[inline] + pub fn as_string(&self) -> Option<&String> { + match self.data { + ObjectData::String(ref string) => Some(string), + _ => None, + } } -} -/// `Trace` implementation for `ObjectKind`. -/// -/// This is indeed safe, but we need to mark this as an empty trace because neither -// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to -/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`. -/// -/// -/// Waiting on until we can derive Copy -unsafe impl Trace for ObjectKind { - unsafe_empty_trace!(); + /// Checks if it a `Function` object. + #[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, + } + } + + /// Checks if it a Symbol object. + #[inline] + pub fn is_symbol(&self) -> bool { + matches!(self.data, ObjectData::Symbol(_)) + } + + #[inline] + pub fn as_symbol(&self) -> Option<&Symbol> { + match self.data { + ObjectData::Symbol(ref symbol) => Some(symbol), + _ => None, + } + } + + /// Checks if it an Error object. + #[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, + } + } + + /// Checks if it a Boolean object. + #[inline] + pub fn is_boolean(&self) -> bool { + matches!(self.data, ObjectData::Boolean(_)) + } + + #[inline] + pub fn as_boolean(&self) -> Option { + match self.data { + ObjectData::Boolean(boolean) => Some(boolean), + _ => None, + } + } + + /// Checks if it a `Number` object. + #[inline] + pub fn is_number(&self) -> bool { + matches!(self.data, ObjectData::Number(_)) + } + + #[inline] + pub fn as_number(&self) -> Option { + match self.data { + ObjectData::Number(number) => Some(number), + _ => None, + } + } + + /// Checks if it a `BigInt` object. + #[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, + } + } + + /// Checks if it an ordinary object. + #[inline] + pub fn is_ordinary(&self) -> bool { + matches!(self.data, ObjectData::Ordinary) + } + + #[inline] + pub fn internal_slots(&self) -> &FxHashMap { + &self.internal_slots + } + + #[inline] + pub fn internal_slots_mut(&mut self) -> &mut FxHashMap { + &mut self.internal_slots + } + + #[inline] + pub fn properties(&self) -> &FxHashMap { + &self.properties + } + + #[inline] + pub fn properties_mut(&mut self) -> &mut FxHashMap { + &mut self.properties + } + + #[inline] + pub fn symbol_properties(&self) -> &FxHashMap { + &self.symbol_properties + } + + #[inline] + pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap { + &mut self.symbol_properties + } + + #[inline] + pub fn state(&self) -> &Option { + &self.state + } + + #[inline] + pub fn state_mut(&mut self) -> &mut Option { + &mut self.state + } } /// Create a new object. @@ -633,7 +479,6 @@ pub fn create(global: &Value) -> Value { let object = make_constructor_fn("Object", 1, make_object, global, prototype, true); - object.set_field("length", Value::from(1)); make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2); make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1); make_builtin_fn(define_property, "defineProperty", &object, 3); @@ -645,5 +490,10 @@ pub fn create(global: &Value) -> Value { #[inline] pub fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("object", "init"); - global.set_field("Object", create(global)); + + let object = create(global); + global + .as_object_mut() + .unwrap() + .insert_field("Object", object); } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 684763ff0f4..887c1f6f87c 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -16,7 +16,7 @@ use regex::Regex; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{InternalState, ObjectKind}, + object::{InternalState, ObjectData}, property::Property, value::{ResultValue, Value, ValueData}, }, @@ -79,7 +79,8 @@ impl RegExp { regex_body = body.into(); } ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; + let obj = obj.borrow(); + let slots = obj.internal_slots(); if slots.get("RegExpMatcher").is_some() { // first argument is another `RegExp` object, so copy its pattern and flags if let Some(body) = slots.get("OriginalSource") { @@ -160,7 +161,7 @@ impl RegExp { // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Ordinary); + this.set_data(ObjectData::Ordinary); this.set_internal_slot("RegExpMatcher", Value::undefined()); this.set_internal_slot("OriginalSource", Value::from(regex_body)); this.set_internal_slot("OriginalFlags", Value::from(regex_flags)); @@ -354,9 +355,8 @@ impl RegExp { } let result = Value::from(result); - result - .set_property_slice("index", Property::default().value(Value::from(m.start()))); - result.set_property_slice("input", Property::default().value(Value::from(arg_str))); + result.set_property("index", Property::default().value(Value::from(m.start()))); + result.set_property("input", Property::default().value(Value::from(arg_str))); result } else { if regex.use_last_index { @@ -441,11 +441,9 @@ impl RegExp { let match_val = Value::from(match_vec); - match_val.set_property_slice( - "index", - Property::default().value(Value::from(m.start())), - ); - match_val.set_property_slice( + match_val + .set_property("index", Property::default().value(Value::from(m.start()))); + match_val.set_property( "input", Property::default().value(Value::from(arg_str.clone())), ); @@ -463,7 +461,7 @@ impl RegExp { let length = matches.len(); let result = Value::from(matches); result.set_field("length", Value::from(length)); - result.set_kind(ObjectKind::Array); + result.set_data(ObjectData::Array); Ok(result) } @@ -472,7 +470,10 @@ impl RegExp { pub(crate) fn create(global: &Value) -> Value { // Create prototype let prototype = Value::new_object(Some(global)); - prototype.set_field("lastIndex", Value::from(0)); + prototype + .as_object_mut() + .unwrap() + .insert_field("lastIndex", Value::from(0)); make_builtin_fn(Self::test, "test", &prototype, 1); make_builtin_fn(Self::exec, "exec", &prototype, 1); @@ -493,6 +494,10 @@ impl RegExp { #[inline] pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("regexp", "init"); - global.set_field("RegExp", Self::create(global)); + let regexp = Self::create(global); + global + .as_object_mut() + .unwrap() + .insert_field("RegExp", regexp); } } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 1632c4855a0..6e13aaedef5 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -15,7 +15,7 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{Object, ObjectKind}, + object::{Object, ObjectData}, property::Property, value::{ResultValue, Value, ValueData}, RegExp, @@ -36,6 +36,22 @@ use std::{ pub(crate) struct String; impl String { + fn this_string_value(this: &Value, ctx: &mut Interpreter) -> Result { + match this.data() { + ValueData::String(ref string) => return Ok(string.clone()), + ValueData::Object(ref object) => { + let object = object.borrow(); + if let Some(string) = object.as_string() { + return Ok(string.clone()); + } + } + _ => {} + } + + ctx.throw_type_error("'this' is not a string")?; + unreachable!(); + } + /// [[Construct]] - Creates a new instance `this` /// /// [[Call]] - Returns a new native `string` @@ -43,39 +59,29 @@ impl String { pub(crate) fn make_string( this: &mut Value, args: &[Value], - _: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // to its Javascript Identifier (global constructor method name) - let s = args.get(0).unwrap_or(&Value::string("")).clone(); - let length_str = s.to_string().chars().count(); - - this.set_field("length", Value::from(length_str as i32)); + let string = match args.get(0) { + Some(ref value) => ctx.to_string(value)?, + None => StdString::new(), + }; - this.set_kind(ObjectKind::String); - this.set_internal_slot("StringData", s); + let length = string.chars().count(); - let arg = match args.get(0) { - Some(v) => v.clone(), - None => Value::undefined(), - }; + this.set_field("length", Value::from(length as i32)); - if arg.is_undefined() { - return Ok("".into()); - } + this.set_data(ObjectData::String(string.clone())); - Ok(Value::from(arg.to_string())) + Ok(Value::from(string)) } /// Get the string value to a primitive string #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { // Get String from String Object and send it back as a new value - match this.get_internal_slot("StringData").data() { - ValueData::String(ref string) => Ok(Value::from(string.clone())), - // Throw expection here: - _ => ctx.throw_type_error("'this' is not a string"), - } + Ok(Value::from(Self::this_string_value(this, ctx)?)) } /// `String.prototype.charAt( index )` @@ -183,14 +189,14 @@ impl String { pub(crate) fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { // First we get it the actual string a private field stored on the object only the engine has access to. // Then we convert it into a Rust String by wrapping it in from_value - let mut new_str = ctx.to_string(this)?; + let object = ctx.require_object_coercible(this)?; + let mut string = ctx.to_string(object)?; for arg in args { - let concat_str = arg.to_string(); - new_str.push_str(&concat_str); + string.push_str(&ctx.to_string(arg)?); } - Ok(Value::from(new_str)) + Ok(Value::from(string)) } /// `String.prototype.repeat( count )` @@ -402,10 +408,11 @@ impl String { match value.deref() { ValueData::String(ref body) => body.into(), ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; - if slots.get("RegExpMatcher").is_some() { + let obj = obj.borrow(); + + if obj.internal_slots().get("RegExpMatcher").is_some() { // first argument is another `RegExp` object, so copy its pattern and flags - if let Some(body) = slots.get("OriginalSource") { + if let Some(body) = obj.internal_slots().get("OriginalSource") { return body.to_string(); } } @@ -1046,7 +1053,8 @@ impl String { let prototype = Value::new_object(Some(global)); let length = Property::default().value(Value::from(0)); - prototype.set_property_slice("length", length); + prototype.set_property("length", length); + make_builtin_fn(Self::char_at, "charAt", &prototype, 1); make_builtin_fn(Self::char_code_at, "charCodeAt", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 0); @@ -1079,6 +1087,10 @@ impl String { #[inline] pub(crate) fn init(global: &Value) { let _timer = BoaProfiler::global().start_event("string", "init"); - global.set_field("String", Self::create(global)); + let string = Self::create(global); + global + .as_object_mut() + .unwrap() + .insert_field("String", string); } } diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index bbf915bfb44..9036ca75208 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -20,87 +20,97 @@ mod tests; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ - builtins::{ - object::{ - internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, - PROTOTYPE, - }, - value::{ResultValue, Value, ValueData}, - }, + builtins::value::{ResultValue, Value, ValueData}, exec::Interpreter, BoaProfiler, }; -use gc::{Gc, GcCell}; +use gc::{Finalize, Trace}; use rand::random; -/// Creates Symbol instances. -/// -/// Symbol instances are ordinary objects that inherit properties from the Symbol prototype object. -/// Symbol instances have a `[[SymbolData]]` internal slot. -/// The `[[SymbolData]]` internal slot is the Symbol value represented by this Symbol object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-symbol-description -pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // From an implementation and specificaition perspective Symbols are similar to Objects. - // They have internal slots to hold the SymbolData and Description, they also have methods and a prototype. - // So we start by creating an Object - // TODO: Set prototype to Symbol.prototype (by changing to Object::create(), use interpreter to get Symbol.prototype) - let mut sym_instance = Object::default(); - sym_instance.kind = ObjectKind::Symbol; +#[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Symbol(Option, i32); - // Set description which should either be undefined or a string - let desc_string = match args.get(0) { - Some(value) => Value::from(value.to_string()), - None => Value::undefined(), - }; +impl Symbol { + /// Returns the `Symbol`s description. + pub fn description(&self) -> Option<&str> { + self.0.as_deref() + } - sym_instance.set_internal_slot("Description", desc_string); - sym_instance.set_internal_slot("SymbolData", Value::from(random::())); + /// Returns the `Symbol`s hash. + pub fn hash(&self) -> i32 { + self.1 + } - // Set __proto__ internal slot - let proto = ctx - .realm - .global_obj - .get_field("Symbol") - .get_field(PROTOTYPE); - sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto); + fn this_symbol_value(value: &Value, ctx: &mut Interpreter) -> Result { + match value.data() { + ValueData::Symbol(ref symbol) => return Ok(symbol.clone()), + ValueData::Object(ref object) => { + let object = object.borrow(); + if let Some(symbol) = object.as_symbol() { + return Ok(symbol.clone()); + } + } + _ => {} + } - Ok(Value(Gc::new(ValueData::Symbol(Box::new(GcCell::new( - sym_instance, - )))))) -} + ctx.throw_type_error("'this' is not a Symbol")?; + unreachable!(); + } -/// `Symbol.prototype.toString()` -/// -/// This method returns a string representing the specified `Symbol` object. -/// -/// /// More information: -/// - [MDN documentation][mdn] -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString -pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let s: Value = this.get_internal_slot("Description"); - let full_string = format!(r#"Symbol({})"#, s.to_string()); - Ok(Value::from(full_string)) -} + /// The `Symbol()` constructor returns a value of type symbol. + /// + /// It is incomplete as a constructor because it does not support + /// the syntax `new Symbol()` and it is not intended to be subclassed. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-symbol-description + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol + pub(crate) fn call(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let description = match args.get(0) { + Some(ref value) if !value.is_undefined() => Some(ctx.to_string(value)?), + _ => None, + }; -/// Create a new `Symbol` object. -pub fn create(global: &Value) -> Value { - // Create prototype object - let prototype = Value::new_object(Some(global)); + Ok(Value::symbol(Symbol(description, random::()))) + } - make_builtin_fn(to_string, "toString", &prototype, 0); - make_constructor_fn("Symbol", 1, call_symbol, global, prototype, false) -} + /// `Symbol.prototype.toString()` + /// + /// This method returns a string representing the specified `Symbol` object. + /// + /// /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let symbol = Self::this_symbol_value(this, ctx)?; + let description = symbol.description().unwrap_or(""); + Ok(Value::from(format!("Symbol({})", description))) + } + + /// Create a new `Symbol` object. + pub(crate) fn create(global: &Value) -> Value { + // Create prototype object + let prototype = Value::new_object(Some(global)); + + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_constructor_fn("Symbol", 1, Self::call, global, prototype, false) + } -/// Initialise the `Symbol` object on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("symbol", "init"); - global.set_field("Symbol", create(global)); + /// Initialise the `Symbol` object on the global object. + #[inline] + pub fn init(global: &Value) { + let _timer = BoaProfiler::global().start_event("symbol", "init"); + let symbol = Self::create(global); + global + .as_object_mut() + .unwrap() + .insert_field("Symbol", symbol); + } } diff --git a/boa/src/builtins/symbol/tests.rs b/boa/src/builtins/symbol/tests.rs index ca72edb1f53..16cb6c23a0b 100644 --- a/boa/src/builtins/symbol/tests.rs +++ b/boa/src/builtins/symbol/tests.rs @@ -4,7 +4,7 @@ use crate::{exec::Interpreter, forward, forward_val, realm::Realm}; #[test] fn check_symbol_constructor_is_function() { let global = Value::new_object(None); - let symbol_constructor = create(&global); + let symbol_constructor = Symbol::create(&global); assert_eq!(symbol_constructor.is_function(), true); } diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 997ea9651e6..36392f204ad 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -119,7 +119,7 @@ where fn from(value: &[T]) -> Self { let mut array = Object::default(); for (i, item) in value.iter().enumerate() { - array.properties.insert( + array.properties_mut().insert( i.to_string(), Property::default().value(item.clone().into()), ); @@ -136,7 +136,7 @@ where let mut array = Object::default(); for (i, item) in value.into_iter().enumerate() { array - .properties + .properties_mut() .insert(i.to_string(), Property::default().value(item.into())); } Value::from(array) diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index b035bb35bf2..39f1a256038 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -57,7 +57,7 @@ macro_rules! print_obj_value { (impl $field:ident, $v:expr, $f:expr) => { $v .borrow() - .$field + .$field() .iter() .map($f) .collect::>() @@ -70,26 +70,13 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { ValueData::Object(ref v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing - match v.borrow().kind { - ObjectKind::String => match v - .borrow() - .internal_slots - .get("StringData") - .expect("Cannot get primitive value from String") - .data() - { - ValueData::String(ref string) => format!("\"{}\"", string), - _ => unreachable!("[[StringData]] should always contain String"), - }, - ObjectKind::Boolean => { - let bool_data = v.borrow().get_internal_slot("BooleanData").to_string(); - - format!("Boolean {{ {} }}", bool_data) - } - ObjectKind::Array => { + match v.borrow().data { + ObjectData::String(ref string) => format!("String {{ \"{}\" }}", string), + ObjectData::Boolean(boolean) => format!("Boolean {{ {} }}", boolean), + ObjectData::Array => { let len = i32::from( &v.borrow() - .properties + .properties() .get("length") .unwrap() .value @@ -107,7 +94,7 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { // which are part of the Array log_string_from( &v.borrow() - .properties + .properties() .get(&i.to_string()) .unwrap() .value @@ -124,14 +111,10 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { _ => display_obj(&x, print_internals), } } - ValueData::Symbol(ref sym) => { - let desc: Value = sym.borrow().get_internal_slot("Description"); - match *desc { - ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()), - _ => String::from("Symbol()"), - } - } - + ValueData::Symbol(ref symbol) => match symbol.description() { + Some(description) => format!("Symbol({})", description), + None => String::from("Symbol()"), + }, _ => format!("{}", x), } } @@ -198,10 +181,9 @@ impl Display for ValueData { Self::Null => write!(f, "null"), Self::Undefined => write!(f, "undefined"), Self::Boolean(v) => write!(f, "{}", v), - Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { - // If a description exists use it - Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), - _ => write!(f, "Symbol()"), + Self::Symbol(ref symbol) => match symbol.description() { + Some(description) => write!(f, "Symbol({})", description), + None => write!(f, "Symbol()"), }, Self::String(ref v) => write!(f, "{}", v), Self::Rational(v) => write!( diff --git a/boa/src/builtins/value/equality.rs b/boa/src/builtins/value/equality.rs index 155814ac445..8e0254dacda 100644 --- a/boa/src/builtins/value/equality.rs +++ b/boa/src/builtins/value/equality.rs @@ -197,14 +197,13 @@ pub fn same_value_zero(x: &Value, y: &Value) -> bool { } } -pub fn same_value_non_numeric(x: &Value, y: &Value) -> bool { +fn same_value_non_numeric(x: &Value, y: &Value) -> bool { debug_assert!(x.get_type() == y.get_type()); match x.get_type() { - Type::Undefined => true, - Type::Null => true, + Type::Null | Type::Undefined => true, Type::String => x.to_string() == y.to_string(), Type::Boolean => bool::from(x) == bool::from(y), - Type::Object => std::ptr::eq(x, y), + Type::Object => std::ptr::eq(x.data(), y.data()), _ => false, } } diff --git a/boa/src/builtins/value/hash.rs b/boa/src/builtins/value/hash.rs new file mode 100644 index 00000000000..fc0462f0730 --- /dev/null +++ b/boa/src/builtins/value/hash.rs @@ -0,0 +1,52 @@ +use super::*; + +use crate::builtins::Number; +use std::hash::{Hash, Hasher}; + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + same_value_zero(self, other) + } +} + +impl Eq for Value {} + +#[derive(PartialEq, Eq, Hash)] +struct UndefinedHashable; + +#[derive(PartialEq, Eq, Hash)] +struct NullHashable; + +struct RationalHashable(f64); + +impl PartialEq for RationalHashable { + #[inline] + fn eq(&self, other: &Self) -> bool { + Number::same_value(self.0, other.0) + } +} + +impl Eq for RationalHashable {} + +impl Hash for RationalHashable { + #[inline] + fn hash(&self, state: &mut H) { + self.0.to_bits().hash(state); + } +} + +impl Hash for Value { + fn hash(&self, state: &mut H) { + let data = self.data(); + match data { + ValueData::Undefined => UndefinedHashable.hash(state), + ValueData::Null => NullHashable.hash(state), + ValueData::String(ref string) => string.hash(state), + ValueData::Boolean(boolean) => boolean.hash(state), + ValueData::Integer(integer) => integer.hash(state), + ValueData::BigInt(ref bigint) => bigint.hash(state), + ValueData::Rational(rational) => RationalHashable(*rational).hash(state), + ValueData::Symbol(_) | ValueData::Object(_) => std::ptr::hash(data, state), + } + } +} diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 12661598f01..d73aa26ae2e 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -10,17 +10,14 @@ pub mod val_type; pub use crate::builtins::value::val_type::Type; use crate::builtins::{ - object::{ - internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, - ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE, - }, + function::Function, + object::{InternalState, InternalStateCell, Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, - BigInt, Function, + BigInt, Symbol, }; -use crate::BoaProfiler; - use crate::exec::Interpreter; -use gc::{Finalize, Gc, GcCell, GcCellRef, Trace}; +use crate::BoaProfiler; +use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use std::{ any::Any, @@ -28,18 +25,20 @@ use std::{ convert::TryFrom, f64::NAN, fmt::{self, Display}, - ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, + ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, str::FromStr, }; pub mod conversions; pub mod display; pub mod equality; +pub mod hash; pub mod operations; pub use conversions::*; pub(crate) use display::display_obj; pub use equality::*; +pub use hash::*; pub use operations::*; /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) @@ -48,7 +47,7 @@ pub type ResultValue = Result; /// A Garbage-collected Javascript value as represented in the interpreter. #[derive(Debug, Clone, Trace, Finalize, Default)] -pub struct Value(pub(crate) Gc); +pub struct Value(Gc); impl Value { /// Creates a new `undefined` value. @@ -63,6 +62,12 @@ impl Value { Self(Gc::new(ValueData::Null)) } + /// Creates a new number with `NaN` value. + #[inline] + pub fn nan() -> Self { + Self::number(NAN) + } + /// Creates a new string value. #[inline] pub fn string(value: S) -> Self @@ -117,6 +122,12 @@ impl Value { Self(Gc::new(ValueData::Object(Box::new(GcCell::new(object))))) } + /// Creates a new symbol value. + #[inline] + pub fn symbol(symbol: Symbol) -> Self { + Self(Gc::new(ValueData::Symbol(symbol))) + } + /// Gets the underlying `ValueData` structure. #[inline] pub fn data(&self) -> &ValueData { @@ -145,12 +156,12 @@ impl Value { } /// Similar to `new_object`, but you can pass a prototype to create from, plus a kind - pub fn new_object_from_prototype(proto: Value, kind: ObjectKind) -> Self { + pub fn new_object_from_prototype(proto: Value, data: ObjectData) -> Self { let mut object = Object::default(); - object.kind = kind; + object.data = data; object - .internal_slots + .internal_slots_mut() .insert(INSTANCE_PROTOTYPE.to_string(), proto); Self::object(object) @@ -175,7 +186,7 @@ impl Value { .get_field("Array") .get_field(PROTOTYPE); let new_obj = - Value::new_object_from_prototype(global_array_prototype, ObjectKind::Array); + Value::new_object_from_prototype(global_array_prototype, ObjectData::Array); let length = vs.len(); for (idx, json) in vs.into_iter().enumerate() { new_obj.set_property( @@ -216,9 +227,9 @@ impl Value { ValueData::Null => Ok(JSONValue::Null), ValueData::Boolean(b) => Ok(JSONValue::Bool(b)), ValueData::Object(ref obj) => { - if obj.borrow().kind == ObjectKind::Array { + if obj.borrow().is_array() { let mut arr: Vec = Vec::new(); - for k in obj.borrow().properties.keys() { + for k in obj.borrow().properties().keys() { if k != "length" { let value = self.get_field(k.to_string()); if value.is_undefined() || value.is_function() { @@ -231,7 +242,7 @@ impl Value { Ok(JSONValue::Array(arr)) } else { let mut new_obj = Map::new(); - for k in obj.borrow().properties.keys() { + for k in obj.borrow().properties().keys() { let key = k.clone(); let value = self.get_field(k.to_string()); if !value.is_undefined() && !value.is_function() { @@ -283,8 +294,8 @@ pub enum ValueData { BigInt(BigInt), /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. Object(Box>), - /// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots. - Symbol(Box>), + /// `Symbol` - A Symbol Primitive type. + Symbol(Symbol), } impl ValueData { @@ -302,65 +313,65 @@ impl ValueData { } /// Returns true if the value is an object + #[inline] pub fn is_object(&self) -> bool { + matches!(self, Self::Object(_)) + } + + #[inline] + pub fn as_object(&self) -> Option> { match *self { - Self::Object(_) => true, - _ => false, + Self::Object(ref o) => Some(o.borrow()), + _ => None, } } - /// Returns true if the value is a symbol - pub fn is_symbol(&self) -> bool { + #[inline] + pub fn as_object_mut(&self) -> Option> { match *self { - Self::Symbol(_) => true, - _ => false, + Self::Object(ref o) => Some(o.borrow_mut()), + _ => None, } } + /// Returns true if the value is a symbol. + #[inline] + pub fn is_symbol(&self) -> bool { + matches!(self, Self::Symbol(_)) + } + /// Returns true if the value is a function + #[inline] pub fn is_function(&self) -> bool { - match *self { - Self::Object(ref o) => { - let borrowed_obj = o.borrow(); - borrowed_obj.is_callable() || borrowed_obj.is_constructable() - } - _ => false, - } + matches!(self, Self::Object(o) if o.borrow().is_function()) } /// Returns true if the value is undefined. + #[inline] pub fn is_undefined(&self) -> bool { - match *self { - Self::Undefined => true, - _ => false, - } + matches!(self, Self::Undefined) } /// Returns true if the value is null. + #[inline] pub fn is_null(&self) -> bool { - match *self { - Self::Null => true, - _ => false, - } + matches!(self, Self::Null) } /// Returns true if the value is null or undefined. + #[inline] pub fn is_null_or_undefined(&self) -> bool { - match *self { - Self::Null | Self::Undefined => true, - _ => false, - } + matches!(self, Self::Null | Self::Undefined) } /// Returns true if the value is a 64-bit floating-point number. + #[inline] pub fn is_double(&self) -> bool { - match *self { - Self::Rational(_) => true, - _ => false, - } + matches!(self, Self::Rational(_)) } /// Returns true if the value is integer. + #[inline] #[allow(clippy::float_cmp)] pub fn is_integer(&self) -> bool { // If it can fit in a i32 and the trucated version is @@ -374,41 +385,32 @@ impl ValueData { } } - /// Returns true if the value is a number + /// Returns true if the value is a number. + #[inline] pub fn is_number(&self) -> bool { - match self { - Self::Rational(_) | Self::Integer(_) => true, - _ => false, - } + matches!(self, Self::Rational(_) | Self::Integer(_)) } - /// Returns true if the value is a string + /// Returns true if the value is a string. + #[inline] pub fn is_string(&self) -> bool { - match *self { - Self::String(_) => true, - _ => false, - } + matches!(self, Self::String(_)) } - /// Returns true if the value is a boolean + /// Returns true if the value is a boolean. + #[inline] pub fn is_boolean(&self) -> bool { - match *self { - Self::Boolean(_) => true, - _ => false, - } + matches!(self, Self::Boolean(_)) } - /// Returns true if the value is a bigint + /// Returns true if the value is a bigint. pub fn is_bigint(&self) -> bool { - match *self { - Self::BigInt(_) => true, - _ => false, - } + matches!(self, Self::BigInt(_)) } - /// Returns true if the value is true + /// Returns true if the value is true. /// - /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) + /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean pub fn is_true(&self) -> bool { match *self { Self::Object(_) => true, @@ -466,23 +468,27 @@ impl ValueData { } } - pub fn as_object(&self) -> Option> { + /// Creates a new boolean value from the input + pub fn to_boolean(&self) -> bool { match *self { - ValueData::Object(ref o) => Some(o.borrow()), - _ => None, + Self::Undefined | Self::Null => false, + Self::Symbol(_) | Self::Object(_) => true, + Self::String(ref s) if !s.is_empty() => true, + Self::Rational(n) if n != 0.0 && !n.is_nan() => true, + Self::Integer(n) if n != 0 => true, + Self::BigInt(ref n) if *n != 0 => true, + Self::Boolean(v) => v, + _ => false, } } /// Removes a property from a Value object. /// - /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned + /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned. pub fn remove_property(&self, field: &str) -> bool { - let removed = match *self { - Self::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), - _ => None, - }; - - removed.is_some() + self.as_object_mut() + .and_then(|mut x| x.properties_mut().remove(field)) + .is_some() } /// Resolve the property in the object. @@ -492,36 +498,22 @@ impl ValueData { let _timer = BoaProfiler::global().start_event("Value::get_property", "value"); // Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154 // This is only for primitive strings, String() objects have their lengths calculated in string.rs - if self.is_string() && field == "length" { - if let Self::String(ref s) = *self { - return Some(Property::default().value(Value::from(s.len()))); - } - } - - if self.is_undefined() { - return None; - } - - let obj: Object = match *self { - Self::Object(ref obj) => { - let hash = obj.clone(); - // TODO: This will break, we should return a GcCellRefMut instead - // into_inner will consume the wrapped value and remove it from the hashmap - hash.into_inner() + match self { + Self::Undefined => None, + Self::String(ref s) if field == "length" => { + Some(Property::default().value(Value::from(s.chars().count()))) } - Self::Symbol(ref obj) => { - let hash = obj.clone(); - hash.into_inner() + Self::Object(ref object) => { + let object = object.borrow(); + match object.properties().get(field) { + Some(value) => Some(value.clone()), + None => match object.internal_slots().get(INSTANCE_PROTOTYPE) { + Some(value) => value.get_property(field), + None => None, + }, + } } - _ => return None, - }; - - match obj.properties.get(field) { - Some(val) => Some(val.clone()), - None => match obj.internal_slots.get(&INSTANCE_PROTOTYPE.to_string()) { - Some(value) => value.get_property(field), - None => None, - }, + _ => None, } } @@ -537,18 +529,14 @@ impl ValueData { configurable: Option, ) { let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); - let obj: Option = match self { - Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()), - _ => None, - }; - if let Some(mut obj_data) = obj { + if let Some(ref mut object) = self.as_object_mut() { // Use value, or walk up the prototype chain - if let Some(ref mut prop) = obj_data.properties.get_mut(field) { - prop.value = value; - prop.enumerable = enumerable; - prop.writable = writable; - prop.configurable = configurable; + if let Some(ref mut property) = object.properties_mut().get_mut(field) { + property.value = value; + property.enumerable = enumerable; + property.writable = writable; + property.configurable = configurable; } } } @@ -558,20 +546,16 @@ impl ValueData { /// Returns a copy of the Property. pub fn get_internal_slot(&self, field: &str) -> Value { let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value"); - let obj: Object = match *self { - Self::Object(ref obj) => { - let hash = obj.clone(); - hash.into_inner() - } - Self::Symbol(ref obj) => { - let hash = obj.clone(); - hash.into_inner() - } - _ => return Value::undefined(), - }; - match obj.internal_slots.get(field) { - Some(val) => val.clone(), + let property = self + .as_object() + .and_then(|x| match x.internal_slots().get(field) { + Some(value) => Some(value.clone()), + None => None, + }); + + match property { + Some(value) => value, None => Value::undefined(), } } @@ -616,19 +600,17 @@ impl ValueData { /// Check whether an object has an internal state set. pub fn has_internal_state(&self) -> bool { - if let Self::Object(ref obj) = *self { - obj.borrow().state.is_some() - } else { - false + match self.as_object() { + Some(object) => object.state().is_some(), + None => false, } } /// Get the internal state of an object. pub fn get_internal_state(&self) -> Option { - if let Self::Object(ref obj) = *self { - obj.borrow().state.as_ref().cloned() - } else { - None + match self.as_object() { + Some(object) => object.state().clone(), + None => None, } } @@ -638,14 +620,14 @@ impl ValueData { /// /// 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 R>( - &self, - f: F, - ) -> R { - if let Self::Object(ref obj) = *self { - let o = obj.borrow(); - let state = o - .state + 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() @@ -662,14 +644,14 @@ impl ValueData { /// /// 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 R>( - &self, - f: F, - ) -> R { - if let Self::Object(ref obj) = *self { - let mut o = obj.borrow_mut(); - let state = o - .state + 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() @@ -698,7 +680,7 @@ impl ValueData { let val = val.into(); if let Self::Object(ref obj) = *self { - if obj.borrow().kind == ObjectKind::Array { + if obj.borrow().is_array() { if let Ok(num) = field.to_string().parse::() { if num > 0 { let len = i32::from(&self.get_field("length")); @@ -722,53 +704,49 @@ impl ValueData { } /// Set the private field in the value - pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { + pub fn set_internal_slot(&self, field: &str, value: Value) -> Value { let _timer = BoaProfiler::global().start_event("Value::set_internal_slot", "exec"); - if let Self::Object(ref obj) = *self { - obj.borrow_mut() - .internal_slots - .insert(field.to_string(), val.clone()); + if let Some(mut object) = self.as_object_mut() { + object + .internal_slots_mut() + .insert(field.to_string(), value.clone()); } - val + value } /// Set the kind of an object - pub fn set_kind(&self, kind: ObjectKind) { + pub fn set_data(&self, data: ObjectData) { if let Self::Object(ref obj) = *self { - (*obj.deref().borrow_mut()).kind = kind; + (*obj.deref().borrow_mut()).data = data; } } - /// Set the property in the value - pub fn set_property(&self, field: String, prop: Property) -> Property { - if let Self::Object(ref obj) = *self { - obj.borrow_mut().properties.insert(field, prop.clone()); + /// Set the property in the value. + pub fn set_property(&self, field: S, property: Property) -> Property + where + S: Into, + { + if let Some(mut object) = self.as_object_mut() { + object + .properties_mut() + .insert(field.into(), property.clone()); } - prop - } - - /// Set the property in the value - pub fn set_property_slice(&self, field: &str, prop: Property) -> Property { - self.set_property(field.to_string(), prop) + 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 Self::Object(ref obj) = *self { - obj.borrow_mut() - .state - .replace(InternalStateCell::new(state)); + 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(native_func: Function) -> Value { - // Object with Kind set to function - let mut new_func = crate::builtins::object::Object::function(); + pub fn from_func(function: Function) -> Value { // Get Length - let length = native_func.params.len(); - // Set [[Call]] internal slot - new_func.set_func(native_func); + let length = function.params.len(); + // Object with Kind set to function + let new_func = Object::function(function); // Wrap Object in GC'd Value let new_func_val = Value::from(new_func); // Set length to parameters diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs index 03975069c03..bfe22614d9f 100644 --- a/boa/src/builtins/value/tests.rs +++ b/boa/src/builtins/value/tests.rs @@ -1,6 +1,9 @@ use super::*; use crate::{forward, forward_val, Interpreter, Realm}; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; + #[test] fn check_is_object() { let val = Value::new_object(None); @@ -91,6 +94,66 @@ fn abstract_equality_comparison() { assert_eq!(forward(&mut engine, "0 == NaN"), "false"); assert_eq!(forward(&mut engine, "'foo' == NaN"), "false"); assert_eq!(forward(&mut engine, "NaN == NaN"), "false"); + + assert_eq!( + forward( + &mut engine, + "Number.POSITIVE_INFINITY === Number.POSITIVE_INFINITY" + ), + "true" + ); + assert_eq!( + forward( + &mut engine, + "Number.NEGAVIVE_INFINITY === Number.NEGAVIVE_INFINITY" + ), + "true" + ); +} + +fn hash_value(value: &Value) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +#[test] +fn hash_undefined() { + let value1 = Value::undefined(); + let value_clone = value1.clone(); + assert_eq!(value1, value_clone); + + let value2 = Value::undefined(); + assert_eq!(value1, value2); + + assert_eq!(hash_value(&value1), hash_value(&value_clone)); + assert_eq!(hash_value(&value2), hash_value(&value_clone)); +} + +#[test] +fn hash_rational() { + let value1 = Value::rational(1.0); + let value2 = Value::rational(1.0); + assert_eq!(value1, value2); + assert_eq!(hash_value(&value1), hash_value(&value2)); + + let nan = Value::nan(); + assert_eq!(nan, nan); + assert_eq!(hash_value(&nan), hash_value(&nan)); + assert_ne!(hash_value(&nan), hash_value(&Value::rational(1.0))); +} + +#[test] +fn hash_object() { + let object1 = Value::object(Object::default()); + assert_eq!(object1, object1); + assert_eq!(object1, object1.clone()); + + let object2 = Value::object(Object::default()); + assert_ne!(object1, object2); + + assert_eq!(hash_value(&object1), hash_value(&object1.clone())); + assert_ne!(hash_value(&object1), hash_value(&object2)); } #[test] diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 8e32c099457..3d6ff1c0a6c 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -25,7 +25,7 @@ pub type Environment = Gc>>; /// Give each environment an easy way to declare its own type /// This helps with comparisons -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum EnvironmentType { Declarative, Function, @@ -34,7 +34,7 @@ pub enum EnvironmentType { } /// The scope of a given variable -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum VariableScope { /// The variable declaration is scoped to the current block (`let` and `const`) Block, @@ -42,13 +42,13 @@ pub enum VariableScope { Function, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct LexicalEnvironment { environment_stack: VecDeque, } /// An error that occurred during lexing or compiling of the source input. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct EnvironmentError { details: String, } diff --git a/boa/src/exec/expression/mod.rs b/boa/src/exec/expression/mod.rs index 80d65236f5e..59a59e1b5e4 100644 --- a/boa/src/exec/expression/mod.rs +++ b/boa/src/exec/expression/mod.rs @@ -3,7 +3,7 @@ use super::{Executable, Interpreter, InterpreterState}; use crate::{ builtins::{ - object::{INSTANCE_PROTOTYPE, PROTOTYPE}, + object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, value::{ResultValue, Type, Value, ValueData}, }, syntax::ast::node::{Call, New, Node}, @@ -71,12 +71,13 @@ impl Executable for New { this.set_internal_slot(INSTANCE_PROTOTYPE, func_object.get_field(PROTOTYPE)); match func_object.data() { - ValueData::Object(ref o) => o.clone().borrow_mut().func.as_ref().unwrap().construct( - &mut func_object.clone(), - &v_args, - interpreter, - &mut this, - ), + ValueData::Object(ref obj) => { + let obj = (**obj).borrow(); + if let ObjectData::Function(ref func) = obj.data { + return func.construct(func_object.clone(), &mut this, &v_args, interpreter); + } + interpreter.throw_type_error("not a constructor") + } _ => Ok(Value::undefined()), } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 407b6a1c9e9..bba0c7fb3ca 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -23,10 +23,7 @@ mod try_node; use crate::{ builtins::{ function::{Function as FunctionObject, FunctionBody, ThisMode}, - object::{ - internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE, - PROTOTYPE, - }, + object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{ResultValue, Type, Value, ValueData}, BigInt, Number, @@ -127,8 +124,8 @@ impl Interpreter { callable, ); - let mut new_func = Object::function(); - new_func.set_func(func); + let new_func = Object::function(func); + let val = Value::from(new_func); val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); val.set_field(PROTOTYPE, proto); @@ -147,8 +144,10 @@ impl Interpreter { match *f.data() { ValueData::Object(ref obj) => { let obj = (**obj).borrow(); - let func = obj.func.as_ref().expect("Expected function"); - func.call(&mut f.clone(), arguments_list, self, this) + if let ObjectData::Function(ref func) = obj.data { + return func.call(f.clone(), this, arguments_list, self); + } + self.throw_type_error("not a function") } _ => Err(Value::undefined()), } @@ -288,7 +287,7 @@ impl Interpreter { pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result, ()> { if let ValueData::Object(ref x) = *value.deref().borrow() { // Check if object is array - if x.deref().borrow().kind == ObjectKind::Array { + if let ObjectData::Array = x.deref().borrow().data { let length: i32 = self.value_to_rust_number(&value.get_field("length")) as i32; let values: Vec = (0..length) .map(|idx| value.get_field(idx.to_string())) @@ -389,58 +388,76 @@ impl Interpreter { pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue { match value.data() { ValueData::Undefined | ValueData::Null => Err(Value::undefined()), - ValueData::Integer(_) => { + ValueData::Boolean(boolean) => { let proto = self .realm .environment - .get_binding_value("Number") + .get_binding_value("Boolean") .get_field(PROTOTYPE); - let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); - number_obj.set_internal_slot("NumberData", value.clone()); - Ok(number_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Boolean(*boolean), + )) } - ValueData::Boolean(_) => { + ValueData::Integer(integer) => { let proto = self .realm .environment - .get_binding_value("Boolean") + .get_binding_value("Number") .get_field(PROTOTYPE); - - let bool_obj = Value::new_object_from_prototype(proto, ObjectKind::Boolean); - bool_obj.set_internal_slot("BooleanData", value.clone()); - Ok(bool_obj) + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Number(f64::from(*integer)), + )) } - ValueData::Rational(_) => { + ValueData::Rational(rational) => { let proto = self .realm .environment .get_binding_value("Number") .get_field(PROTOTYPE); - let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); - number_obj.set_internal_slot("NumberData", value.clone()); - Ok(number_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Number(*rational), + )) } - ValueData::String(_) => { + ValueData::String(ref string) => { let proto = self .realm .environment .get_binding_value("String") .get_field(PROTOTYPE); - let string_obj = Value::new_object_from_prototype(proto, ObjectKind::String); - string_obj.set_internal_slot("StringData", value.clone()); - Ok(string_obj) + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::String(string.clone()), + )) } - ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()), - ValueData::BigInt(_) => { + ValueData::Symbol(ref symbol) => { + let proto = self + .realm + .environment + .get_binding_value("Symbol") + .get_field(PROTOTYPE); + + Ok(Value::new_object_from_prototype( + proto, + ObjectData::Symbol(symbol.clone()), + )) + } + ValueData::BigInt(ref bigint) => { let proto = self .realm .environment .get_binding_value("BigInt") .get_field(PROTOTYPE); - let bigint_obj = Value::new_object_from_prototype(proto, ObjectKind::BigInt); - bigint_obj.set_internal_slot("BigIntData", value.clone()); + let bigint_obj = + Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone())); Ok(bigint_obj) } + ValueData::Object(_) => Ok(value.clone()), } } @@ -500,6 +517,24 @@ impl Interpreter { pub(crate) fn get_current_state(&self) -> &InterpreterState { &self.current_state } + /// Check if the `Value` can be converted to an `Object` + /// + /// The abstract operation `RequireObjectCoercible` takes argument argument. + /// It throws an error if argument is a value that cannot be converted to an Object using `ToObject`. + /// It is defined by [Table 15][table] + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [table]: https://tc39.es/ecma262/#table-14 + /// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible + pub fn require_object_coercible<'a>(&mut self, value: &'a Value) -> Result<&'a Value, Value> { + if value.is_null_or_undefined() { + self.throw_type_error("cannot convert null or undefined to Object")?; + unreachable!(); + } + Ok(value) + } } impl Executable for Node { From 16ca21af2413ba8a7d4c7824721f3b106790743f Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 9 Jun 2020 17:32:13 +0200 Subject: [PATCH 2/9] Made `Symbol` unique hash be an incremented value instead of pointer hash The problem with using the pointer as the hash, it can become a security vulnerability. --- boa/src/builtins/object/mod.rs | 6 +++--- boa/src/builtins/symbol/mod.rs | 7 +++---- boa/src/builtins/value/hash.rs | 3 ++- boa/src/exec/mod.rs | 19 +++++++++++++++---- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 3e8875ba4c0..667aec0ea4f 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -55,7 +55,7 @@ pub struct Object { /// Properties properties: FxHashMap, /// Symbol Properties - symbol_properties: FxHashMap, + symbol_properties: FxHashMap, /// Some rust object that stores internal state state: Option, } @@ -370,12 +370,12 @@ impl Object { } #[inline] - pub fn symbol_properties(&self) -> &FxHashMap { + pub fn symbol_properties(&self) -> &FxHashMap { &self.symbol_properties } #[inline] - pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap { + pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap { &mut self.symbol_properties } diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 9036ca75208..2a8f1df75d4 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -25,10 +25,9 @@ use crate::{ BoaProfiler, }; use gc::{Finalize, Trace}; -use rand::random; #[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Symbol(Option, i32); +pub struct Symbol(Option, u32); impl Symbol { /// Returns the `Symbol`s description. @@ -37,7 +36,7 @@ impl Symbol { } /// Returns the `Symbol`s hash. - pub fn hash(&self) -> i32 { + pub fn hash(&self) -> u32 { self.1 } @@ -74,7 +73,7 @@ impl Symbol { _ => None, }; - Ok(Value::symbol(Symbol(description, random::()))) + Ok(Value::symbol(Symbol(description, ctx.generate_hash()))) } /// `Symbol.prototype.toString()` diff --git a/boa/src/builtins/value/hash.rs b/boa/src/builtins/value/hash.rs index fc0462f0730..c32f9afc7dd 100644 --- a/boa/src/builtins/value/hash.rs +++ b/boa/src/builtins/value/hash.rs @@ -46,7 +46,8 @@ impl Hash for Value { ValueData::Integer(integer) => integer.hash(state), ValueData::BigInt(ref bigint) => bigint.hash(state), ValueData::Rational(rational) => RationalHashable(*rational).hash(state), - ValueData::Symbol(_) | ValueData::Object(_) => std::ptr::hash(data, state), + ValueData::Symbol(ref symbol) => Hash::hash(symbol, state), + ValueData::Object(_) => std::ptr::hash(data, state), } } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index bba0c7fb3ca..a13964b7908 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -59,18 +59,23 @@ pub enum PreferredType { /// A Javascript intepreter #[derive(Debug)] pub struct Interpreter { - current_state: InterpreterState, + /// the current state of the interpreter. + state: InterpreterState, /// realm holds both the global object and the environment pub realm: Realm, + + /// This is for generating an unique internal `Symbol` hash. + symbol_count: u32, } impl Interpreter { /// Creates a new interpreter. pub fn new(realm: Realm) -> Self { Self { - current_state: InterpreterState::Executing, + state: InterpreterState::Executing, realm, + symbol_count: 0, } } @@ -84,6 +89,12 @@ impl Interpreter { &mut self.realm } + pub(crate) fn generate_hash(&mut self) -> u32 { + let hash = self.symbol_count; + self.symbol_count += 1; + hash + } + /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions pub(crate) fn create_function( &mut self, @@ -511,11 +522,11 @@ impl Interpreter { } pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) { - self.current_state = new_state + self.state = new_state } pub(crate) fn get_current_state(&self) -> &InterpreterState { - &self.current_state + &self.state } /// Check if the `Value` can be converted to an `Object` /// From 6c82241ffb5c1cccf4846b7dabdb52bebfef3ed6 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 9 Jun 2020 18:01:05 +0200 Subject: [PATCH 3/9] Changed `Symbol` description type from `String` to `Box` This type change removes 8-bytes from Symbol type size. --- boa/src/builtins/symbol/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 2a8f1df75d4..d7a41e08ee4 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -27,7 +27,7 @@ use crate::{ use gc::{Finalize, Trace}; #[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Symbol(Option, u32); +pub struct Symbol(Option>, u32); impl Symbol { /// Returns the `Symbol`s description. @@ -69,7 +69,9 @@ impl Symbol { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol pub(crate) fn call(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { let description = match args.get(0) { - Some(ref value) if !value.is_undefined() => Some(ctx.to_string(value)?), + Some(ref value) if !value.is_undefined() => { + Some(ctx.to_string(value)?.into_boxed_str()) + } _ => None, }; From 5eb1a39fd464e3523d3f7887c3274f8c20038ce1 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Thu, 11 Jun 2020 16:00:58 +0200 Subject: [PATCH 4/9] Changed `init` functions to return a `(&str, Value)` --- boa/src/builtins/array/mod.rs | 5 ++-- boa/src/builtins/bigint/mod.rs | 8 ++---- boa/src/builtins/boolean/mod.rs | 8 ++---- boa/src/builtins/console/mod.rs | 8 ++---- boa/src/builtins/error/mod.rs | 5 ++-- boa/src/builtins/error/range.rs | 8 ++---- boa/src/builtins/error/type.rs | 4 +-- boa/src/builtins/function/mod.rs | 8 ++---- boa/src/builtins/global_this/mod.rs | 32 +++++++++++++++++----- boa/src/builtins/json/mod.rs | 5 ++-- boa/src/builtins/math/mod.rs | 5 ++-- boa/src/builtins/mod.rs | 42 +++++++++++++++++------------ boa/src/builtins/nan/mod.rs | 29 ++++++++++++++++---- boa/src/builtins/number/mod.rs | 8 ++---- boa/src/builtins/object/mod.rs | 8 ++---- boa/src/builtins/regexp/mod.rs | 9 +++---- boa/src/builtins/string/mod.rs | 9 +++---- boa/src/builtins/symbol/mod.rs | 9 +++---- 18 files changed, 109 insertions(+), 101 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index e7fc9f3de7a..d8eae6f3a7d 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -1041,10 +1041,9 @@ impl Array { /// Initialise the `Array` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("array", "init"); - let array = Self::create(global); - global.as_object_mut().unwrap().insert_field("Array", array); + ("Array", Self::create(global)) } } diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index e3aff02ee77..afbe6a303c5 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -223,14 +223,10 @@ impl BigInt { /// Initialise the `BigInt` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("bigint", "init"); - let bigint = Self::create(global); - global - .as_object_mut() - .unwrap() - .insert_field("BigInt", bigint); + ("BigInt", Self::create(global)) } } diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 0f0ce2fbce2..11ec0d4e478 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -111,13 +111,9 @@ impl Boolean { /// Initialise the `Boolean` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("boolean", "init"); - let boolean = Self::create(global); - global - .as_object_mut() - .unwrap() - .insert_field("Boolean", boolean); + ("Boolean", Self::create(global)) } } diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index 5a726ad3ad6..39bcb43b41b 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -554,12 +554,8 @@ pub fn create(global: &Value) -> Value { /// Initialise the `console` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("console", "init"); - let console = create(global); - global - .as_object_mut() - .unwrap() - .insert_field("console", console); + ("console", create(global)) } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 483de7d3b54..409ebdfd53b 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -81,10 +81,9 @@ impl Error { } /// Initialise the global object with the `Error` object. - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("error", "init"); - let error = Self::create(global); - global.as_object_mut().unwrap().insert_field("Error", error); + ("Error", Self::create(global)) } } diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 43fd40a8782..924d93ac0cf 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -71,13 +71,9 @@ impl RangeError { } /// Initialise the global object with the `RangeError` object. - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("rangeerror", "init"); - let range_error = Self::create(global); - global - .as_object_mut() - .unwrap() - .insert_field("RangeError", range_error); + ("RangeError", Self::create(global)) } } diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index c536175beb7..6122185f659 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -78,9 +78,9 @@ impl TypeError { } /// Initialise the global object with the `RangeError` object. - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("typeerror", "init"); - global.set_field("TypeError", Self::create(global)); + ("TypeError", Self::create(global)) } } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 09b8b8a9768..f87cf65ba7f 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -491,12 +491,8 @@ where /// Initialise the `Function` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("function", "init"); - let function = create(global); - global - .as_object_mut() - .unwrap() - .insert_field("Function", function); + ("Function", create(global)) } diff --git a/boa/src/builtins/global_this/mod.rs b/boa/src/builtins/global_this/mod.rs index c668313332f..1ede053dcf8 100644 --- a/boa/src/builtins/global_this/mod.rs +++ b/boa/src/builtins/global_this/mod.rs @@ -1,11 +1,31 @@ +//! This module implements the global `globalThis` property. +//! +//! The global globalThis property contains the global this value, +//! which is akin to the global object. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-globalthis +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis + +use crate::{builtins::value::Value, BoaProfiler}; + #[cfg(test)] mod tests; -use crate::{builtins::value::Value, BoaProfiler}; +/// The JavaScript `globalThis`. +pub(crate) struct GlobalThis; + +impl GlobalThis { + pub(crate) const NAME: &'static str = "globalThis"; + + /// Initialize the `globalThis` property on the global object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); -/// Initialize the `globalThis` property on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("globalThis", "init"); - global.set_field("globalThis", global.clone()); + (Self::NAME, global.clone()) + } } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 9f39044e14f..5ef3a8ee63e 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -172,9 +172,8 @@ pub fn create(global: &Value) -> Value { /// Initialise the `JSON` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("json", "init"); - let json = create(global); - global.as_object_mut().unwrap().insert_field("JSON", json); + ("JSON", create(global)) } diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 7cfb4c77baf..5bac5ee3e58 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -557,7 +557,8 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Math` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("math", "init"); - global.set_field("Math", create(global)); + + ("Math", create(global)) } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index e45ca3754cd..3e350b4fa15 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -23,6 +23,8 @@ pub(crate) use self::{ bigint::BigInt, boolean::Boolean, error::{Error, RangeError, TypeError}, + global_this::GlobalThis, + nan::NaN, number::Number, regexp::RegExp, string::String, @@ -33,22 +35,28 @@ pub(crate) use self::{ /// Initializes builtin objects and functions #[inline] pub fn init(global: &Value) { - function::init(global); - Array::init(global); - BigInt::init(global); - Boolean::init(global); - global_this::init(global); - json::init(global); - math::init(global); - nan::init(global); - Number::init(global); - object::init(global); - RegExp::init(global); - String::init(global); - Symbol::init(global); - console::init(global); + let globals = vec![ + function::init(global), + Array::init(global), + BigInt::init(global), + Boolean::init(global), + json::init(global), + math::init(global), + Number::init(global), + NaN::init(global), + GlobalThis::init(global), + object::init(global), + RegExp::init(global), + String::init(global), + Symbol::init(global), + console::init(global), + Error::init(global), + RangeError::init(global), + TypeError::init(global), + ]; - Error::init(global); - RangeError::init(global); - TypeError::init(global); + let mut global_object = global.as_object_mut().expect("global object"); + for (name, value) in globals { + global_object.insert_field(name, value); + } } diff --git a/boa/src/builtins/nan/mod.rs b/boa/src/builtins/nan/mod.rs index 41ddfcca829..a797e13e41d 100644 --- a/boa/src/builtins/nan/mod.rs +++ b/boa/src/builtins/nan/mod.rs @@ -1,11 +1,30 @@ +//! This module implements the global `NaN` property. +//! +//! The global `NaN` is a property of the global object. In other words, +//! it is a variable in global scope. +//! +//! More information: +//! - [MDN documentation][mdn] +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-nan +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN + #[cfg(test)] mod tests; use crate::{builtins::value::Value, BoaProfiler}; -/// Initialize the `NaN` property on the global object. -#[inline] -pub fn init(global: &Value) { - let _timer = BoaProfiler::global().start_event("NaN", "init"); - global.set_field("NaN", Value::from(f64::NAN)); +/// JavaScript global `NaN` property. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct NaN; + +impl NaN { + /// Initialize the `NaN` property on the global object. + #[inline] + pub(crate) fn init(_: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event("NaN", "init"); + + ("NaN", Value::from(f64::NAN)) + } } diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index d37b4165980..43af7ee6b1d 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -571,14 +571,10 @@ impl Number { /// Initialise the `Number` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("number", "init"); - let number = Self::create(global); - global - .as_object_mut() - .unwrap() - .insert_field("Number", number); + ("Number", Self::create(global)) } /// The abstract operation Number::equal takes arguments diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 667aec0ea4f..5a2d60d043c 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -488,12 +488,8 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Object` object on the global object. #[inline] -pub fn init(global: &Value) { +pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("object", "init"); - let object = create(global); - global - .as_object_mut() - .unwrap() - .insert_field("Object", object); + ("Object", create(global)) } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 887c1f6f87c..4c5d0faf189 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -492,12 +492,9 @@ impl RegExp { /// Initialise the `RegExp` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("regexp", "init"); - let regexp = Self::create(global); - global - .as_object_mut() - .unwrap() - .insert_field("RegExp", regexp); + + ("RegExp", Self::create(global)) } } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 6e13aaedef5..e8307570aeb 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -1085,12 +1085,9 @@ impl String { /// Initialise the `String` object on the global object. #[inline] - pub(crate) fn init(global: &Value) { + pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("string", "init"); - let string = Self::create(global); - global - .as_object_mut() - .unwrap() - .insert_field("String", string); + + ("String", Self::create(global)) } } diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index d7a41e08ee4..ed6c3ed47ed 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -106,12 +106,9 @@ impl Symbol { /// Initialise the `Symbol` object on the global object. #[inline] - pub fn init(global: &Value) { + pub fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event("symbol", "init"); - let symbol = Self::create(global); - global - .as_object_mut() - .unwrap() - .insert_field("Symbol", symbol); + + ("Symbol", Self::create(global)) } } From d165c6c7bad929401cd9b9de29c67c481ca4d723 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Thu, 11 Jun 2020 18:37:34 +0200 Subject: [PATCH 5/9] Made Number constructor and methods more specification compliant. --- boa/src/builtins/boolean/mod.rs | 5 +- boa/src/builtins/function/mod.rs | 2 +- boa/src/builtins/mod.rs | 2 + boa/src/builtins/number/mod.rs | 80 +++++++++++++++----------------- boa/src/builtins/number/tests.rs | 12 ++--- boa/src/builtins/string/mod.rs | 6 ++- boa/src/builtins/value/mod.rs | 8 ++++ boa/src/exec/mod.rs | 32 +++++++++++-- 8 files changed, 91 insertions(+), 56 deletions(-) diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 11ec0d4e478..c87d681c46e 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -45,8 +45,9 @@ impl Boolean { _ => {} } - ctx.throw_type_error("'this' is not a boolean")?; - unreachable!(); + Err(ctx + .throw_type_error("'this' is not a boolean") + .expect_err("throw_type_error() did not return an error")) } /// `[[Construct]]` Create a new boolean object diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index f87cf65ba7f..5a54fa76d58 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -449,7 +449,7 @@ pub fn make_constructor_fn( constructor .as_object_mut() - .unwrap() + .expect("constructor object") .insert_field(PROTOTYPE, prototype); constructor diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 3e350b4fa15..f366ea1cff8 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -36,6 +36,7 @@ pub(crate) use self::{ #[inline] pub fn init(global: &Value) { let globals = vec![ + // The `Function` global must be initialized before other types. function::init(global), Array::init(global), BigInt::init(global), @@ -50,6 +51,7 @@ pub fn init(global: &Value) { String::init(global), Symbol::init(global), console::init(global), + // Global error types. Error::init(global), RangeError::init(global), TypeError::init(global), diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 43af7ee6b1d..c8ec8ba7c91 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -26,7 +26,6 @@ use crate::{ BoaProfiler, }; use num_traits::float::FloatCore; -use std::ops::Deref; const BUF_SIZE: usize = 2200; @@ -41,31 +40,31 @@ const PARSE_INT_MAX_ARG_COUNT: usize = 2; const PARSE_FLOAT_MAX_ARG_COUNT: usize = 1; impl Number { - /// Helper function that converts a Value to a Number. - #[allow(clippy::wrong_self_convention)] - fn to_number(value: &Value) -> Value { - match value.data() { - ValueData::Boolean(b) => { - if *b { - Value::from(1) - } else { - Value::from(0) + /// This function returns a `Result` of the number `Value`. + /// + /// If the `Value` is a `Number` primitive of `Number` object the number is returned. + /// Otherwise an `TypeError` is thrown. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-thisnumbervalue + fn this_number_value(value: &Value, ctx: &mut Interpreter) -> Result { + match *value.data() { + ValueData::Integer(integer) => return Ok(f64::from(integer)), + ValueData::Rational(rational) => return Ok(rational), + ValueData::Object(ref object) => { + if let Some(number) = object.borrow().as_number() { + return Ok(number); } } - ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN), - ValueData::Integer(i) => Value::from(f64::from(*i)), - ValueData::Object(ref o) => match (o).deref().borrow().data { - ObjectData::Number(num) => Value::from(num), - _ => unreachable!(), - }, - ValueData::Null => Value::from(0), - ValueData::Rational(n) => Value::from(*n), - ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()), - ValueData::String(ref s) => match s.parse::() { - Ok(n) => Value::from(n), - Err(_) => Value::from(f64::NAN), - }, + _ => {} } + + Err(ctx + .throw_type_error("'this' is not a number") + .expect_err("throw_type_error() did not return an error")) } /// Helper function that formats a float as a ES6-style exponential number string. @@ -83,10 +82,10 @@ impl Number { pub(crate) fn make_number( this: &mut Value, args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { let data = match args.get(0) { - Some(ref value) => Self::to_number(value).to_number(), + Some(ref value) => ctx.to_numeric_number(value)?, None => 0.0, }; this.set_data(ObjectData::Number(data)); @@ -108,9 +107,9 @@ impl Number { pub(crate) fn to_exponential( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - let this_num = Self::to_number(this).to_number(); + let this_num = Self::this_number_value(this, ctx)?; let this_str_num = Self::num_to_exponential(this_num); Ok(Value::from(this_str_num)) } @@ -126,12 +125,8 @@ impl Number { /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed #[allow(clippy::wrong_self_convention)] - pub(crate) fn to_fixed( - this: &mut Value, - args: &[Value], - _ctx: &mut Interpreter, - ) -> ResultValue { - let this_num = Self::to_number(this).to_number(); + pub(crate) fn to_fixed(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let this_num = Self::this_number_value(this, ctx)?; let precision = match args.get(0) { Some(n) => match n.to_integer() { x if x > 0 => n.to_integer() as usize, @@ -160,9 +155,9 @@ impl Number { pub(crate) fn to_locale_string( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - let this_num = Self::to_number(this).to_number(); + let this_num = Self::this_number_value(this, ctx)?; let this_str_num = format!("{}", this_num); Ok(Value::from(this_str_num)) } @@ -181,10 +176,10 @@ impl Number { pub(crate) fn to_precision( this: &mut Value, args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - let this_num = Self::to_number(this); - let _num_str_len = format!("{}", this_num.to_number()).len(); + let this_num = Self::this_number_value(this, ctx)?; + let _num_str_len = format!("{}", this_num).len(); let _precision = match args.get(0) { Some(n) => match n.to_integer() { x if x > 0 => n.to_integer() as usize, @@ -352,7 +347,8 @@ impl Number { ctx: &mut Interpreter, ) -> ResultValue { // 1. Let x be ? thisNumberValue(this value). - let x = Self::to_number(this).to_number(); + let x = Self::this_number_value(this, ctx)?; + // 2. If radix is undefined, let radixNumber be 10. // 3. Else, let radixNumber be ? ToInteger(radix). let radix = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; @@ -405,9 +401,9 @@ impl Number { pub(crate) fn value_of( this: &mut Value, _args: &[Value], - _ctx: &mut Interpreter, + ctx: &mut Interpreter, ) -> ResultValue { - Ok(Self::to_number(this)) + Ok(Value::from(Self::this_number_value(this, ctx)?)) } /// Builtin javascript 'parseInt(str, radix)' function. @@ -555,7 +551,7 @@ impl Number { // Constants from: // https://tc39.es/ecma262/#sec-properties-of-the-number-constructor { - let mut properties = number.as_object_mut().unwrap(); + let mut properties = number.as_object_mut().expect("'Number' object"); properties.insert_field("EPSILON", Value::from(f64::EPSILON)); properties.insert_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); properties.insert_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index 63734b986e6..2696ee2f269 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -82,12 +82,12 @@ fn to_exponential() { let nan_exp = forward(&mut engine, "nan_exp"); let noop_exp = forward(&mut engine, "noop_exp"); - assert_eq!(default_exp, String::from("0e+0")); - assert_eq!(int_exp, String::from("5e+0")); - assert_eq!(float_exp, String::from("1.234e+0")); - assert_eq!(big_exp, String::from("1.234e+3")); - assert_eq!(nan_exp, String::from("NaN")); - assert_eq!(noop_exp, String::from("1.23e+2")); + assert_eq!(default_exp, "0e+0"); + assert_eq!(int_exp, "5e+0"); + assert_eq!(float_exp, "1.234e+0"); + assert_eq!(big_exp, "1.234e+3"); + assert_eq!(nan_exp, "NaN"); + assert_eq!(noop_exp, "1.23e+2"); } #[test] diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index e8307570aeb..860b101fcb2 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -48,8 +48,9 @@ impl String { _ => {} } - ctx.throw_type_error("'this' is not a string")?; - unreachable!(); + Err(ctx + .throw_type_error("'this' is not a string") + .expect_err("throw_type_error() did not return an error")) } /// [[Construct]] - Creates a new instance `this` @@ -79,6 +80,7 @@ impl String { /// Get the string value to a primitive string #[allow(clippy::wrong_self_convention)] + #[inline] pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { // Get String from String Object and send it back as a new value Ok(Value::from(Self::this_string_value(this, ctx)?)) diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index d73aa26ae2e..546901fee2b 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -408,6 +408,14 @@ impl ValueData { matches!(self, Self::BigInt(_)) } + /// Returns an optional reference to a `BigInt` if the value is a BigInt primitive. + pub fn as_bigint(&self) -> Option<&BigInt> { + match self { + Self::BigInt(bigint) => Some(bigint), + _ => None, + } + } + /// Returns true if the value is true. /// /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index a13964b7908..f45a692063f 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -267,13 +267,13 @@ impl Interpreter { /// See: https://tc39.es/ecma262/#sec-tonumber #[allow(clippy::wrong_self_convention)] pub fn to_number(&mut self, value: &Value) -> Result { - match *value.deref().borrow() { + match *value.data() { ValueData::Null => Ok(0.0), ValueData::Undefined => Ok(f64::NAN), ValueData::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - ValueData::String(ref string) => match string.parse::() { + ValueData::String(ref string) => match string.parse() { Ok(number) => Ok(number), - Err(_) => Ok(0.0), + Err(_) => Ok(f64::NAN), }, // this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type ValueData::Rational(number) => Ok(number), ValueData::Integer(integer) => Ok(f64::from(integer)), @@ -292,6 +292,32 @@ impl Interpreter { } } + /// It returns value converted to a numeric value of type Number or BigInt. + /// + /// See: https://tc39.es/ecma262/#sec-tonumeric + #[allow(clippy::wrong_self_convention)] + pub fn to_numeric(&mut self, value: &Value) -> ResultValue { + let primitive = self.to_primitive(&mut value.clone(), PreferredType::Number); + if primitive.is_bigint() { + return Ok(primitive); + } + Ok(Value::from(self.to_number(&primitive)?)) + } + + /// This is a more specialized version of `to_numeric`. + /// + /// It returns value converted to a numeric value of type `Number`. + /// + /// See: https://tc39.es/ecma262/#sec-tonumeric + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_numeric_number(&mut self, value: &Value) -> Result { + let primitive = self.to_primitive(&mut value.clone(), PreferredType::Number); + if let Some(ref bigint) = primitive.as_bigint() { + return Ok(bigint.to_f64()); + } + Ok(self.to_number(&primitive)?) + } + /// Converts an array object into a rust vector of values. /// /// This is useful for the spread operator, for any other object an `Err` is returned From e03d349f0050e0574cff3aae536bd666d28244e7 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Thu, 11 Jun 2020 19:41:44 +0200 Subject: [PATCH 6/9] Abstracted Math and Json objects --- boa/src/builtins/json/mod.rs | 264 ++++----- boa/src/builtins/math/mod.rs | 998 ++++++++++++++++++----------------- boa/src/builtins/mod.rs | 11 +- 3 files changed, 643 insertions(+), 630 deletions(-) diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 5ef3a8ee63e..db3262d6de7 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -24,156 +24,162 @@ use serde_json::{self, Value as JSONValue}; #[cfg(test)] mod tests; -/// `JSON.parse( text[, reviver] )` -/// -/// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string. -/// -/// An optional `reviver` function can be provided to perform a transformation on the resulting object before it is returned. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-json.parse -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse -pub fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - match serde_json::from_str::( - &ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?, - ) { - Ok(json) => { - let j = Value::from_json(json, ctx); - match args.get(1) { - Some(reviver) if reviver.is_function() => { - let mut holder = Value::new_object(None); - holder.set_field(Value::from(""), j); - walk(reviver, ctx, &mut holder, Value::from("")) +/// JavaScript `JSON` global object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Json; + +impl Json { + /// `JSON.parse( text[, reviver] )` + /// + /// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string. + /// + /// An optional `reviver` function can be provided to perform a transformation on the resulting object before it is returned. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-json.parse + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse + pub(crate) fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + match serde_json::from_str::( + &ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?, + ) { + Ok(json) => { + let j = Value::from_json(json, ctx); + match args.get(1) { + Some(reviver) if reviver.is_function() => { + let mut holder = Value::new_object(None); + holder.set_field(Value::from(""), j); + Self::walk(reviver, ctx, &mut holder, Value::from("")) + } + _ => Ok(j), } - _ => Ok(j), } + Err(err) => Err(Value::from(err.to_string())), } - Err(err) => Err(Value::from(err.to_string())), } -} -/// This is a translation of the [Polyfill implementation][polyfill] -/// -/// This function recursively walks the structure, passing each key-value pair to the reviver function -/// for possible transformation. -/// -/// [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()); + /// This is a translation of the [Polyfill implementation][polyfill] + /// + /// This function recursively walks the structure, passing each key-value pair to the reviver function + /// for possible transformation. + /// + /// [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 obj = value.as_object().as_deref().cloned(); - if let Some(obj) = obj { - for key in obj.properties().keys() { - let v = walk(reviver, ctx, &mut value, Value::from(key.as_str())); - match v { - Ok(v) if !v.is_undefined() => { - value.set_field(Value::from(key.as_str()), v); - } - Ok(_) => { - value.remove_property(key.as_str()); + 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())); + match v { + Ok(v) if !v.is_undefined() => { + value.set_field(Value::from(key.as_str()), v); + } + Ok(_) => { + value.remove_property(key.as_str()); + } + Err(_v) => {} } - Err(_v) => {} } } + ctx.call(reviver, holder, &[key, value]) } - ctx.call(reviver, holder, &[key, value]) -} -/// `JSON.stringify( value[, replacer[, space]] )` -/// -/// This `JSON` method converts a JavaScript object or value to a JSON string. -/// -/// This medhod optionally replaces values if a `replacer` function is specified or -/// optionally including only the specified properties if a replacer array is specified. -/// -/// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert -/// white space into the output JSON string for readability purposes. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-json.stringify -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify -pub fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let object = match args.get(0) { - Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()), - None => return Ok(Value::undefined()), - Some(obj) => obj, - }; - let replacer = match args.get(1) { - Some(replacer) if replacer.is_object() => replacer, - _ => return Ok(Value::from(object.to_json(ctx)?.to_string())), - }; + /// `JSON.stringify( value[, replacer[, space]] )` + /// + /// This `JSON` method converts a JavaScript object or value to a JSON string. + /// + /// This medhod optionally replaces values if a `replacer` function is specified or + /// optionally including only the specified properties if a replacer array is specified. + /// + /// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert + /// white space into the output JSON string for readability purposes. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-json.stringify + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify + pub(crate) fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let object = match args.get(0) { + Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()), + None => return Ok(Value::undefined()), + Some(obj) => obj, + }; + let replacer = match args.get(1) { + Some(replacer) if replacer.is_object() => replacer, + _ => return Ok(Value::from(object.to_json(ctx)?.to_string())), + }; - let replacer_as_object = replacer - .as_object() - .expect("JSON.stringify replacer was an object"); - if replacer_as_object.is_callable() { - object + let replacer_as_object = replacer .as_object() - .map(|obj| { - let object_to_return = Value::new_object(None); - for (key, val) in obj - .properties() - .iter() - .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) + .expect("JSON.stringify replacer was an object"); + if replacer_as_object.is_callable() { + object + .as_object() + .map(|obj| { + let object_to_return = Value::new_object(None); + for (key, val) in obj + .properties() + .iter() + .filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) + { + let mut this_arg = object.clone(); + object_to_return.set_property( + key.to_owned(), + Property::default().value(ctx.call( + replacer, + &mut this_arg, + &[Value::string(key), val.clone()], + )?), + ); + } + Ok(Value::from(object_to_return.to_json(ctx)?.to_string())) + }) + .ok_or_else(Value::undefined)? + } else if replacer_as_object.is_array() { + let mut obj_to_return = + serde_json::Map::with_capacity(replacer_as_object.properties().len() - 1); + let fields = replacer_as_object.properties().keys().filter_map(|key| { + if key == "length" { + None + } else { + Some(replacer.get_field(key.to_owned())) + } + }); + for field in fields { + if let Some(value) = object + .get_property(&ctx.to_string(&field)?) + .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx))) + .transpose()? { - let mut this_arg = object.clone(); - object_to_return.set_property( - key.to_owned(), - Property::default().value(ctx.call( - replacer, - &mut this_arg, - &[Value::string(key), val.clone()], - )?), - ); + obj_to_return.insert(field.to_string(), value); } - Ok(Value::from(object_to_return.to_json(ctx)?.to_string())) - }) - .ok_or_else(Value::undefined)? - } else if replacer_as_object.is_array() { - let mut obj_to_return = - serde_json::Map::with_capacity(replacer_as_object.properties().len() - 1); - let fields = replacer_as_object.properties().keys().filter_map(|key| { - if key == "length" { - None - } else { - Some(replacer.get_field(key.to_owned())) - } - }); - for field in fields { - if let Some(value) = object - .get_property(&ctx.to_string(&field)?) - .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx))) - .transpose()? - { - obj_to_return.insert(field.to_string(), value); } + Ok(Value::from(JSONValue::Object(obj_to_return).to_string())) + } else { + Ok(Value::from(object.to_json(ctx)?.to_string())) } - Ok(Value::from(JSONValue::Object(obj_to_return).to_string())) - } else { - Ok(Value::from(object.to_json(ctx)?.to_string())) } -} -/// Create a new `JSON` object. -pub fn create(global: &Value) -> Value { - let json = Value::new_object(Some(global)); + /// Create a new `JSON` object. + pub(crate) fn create(global: &Value) -> Value { + let json = Value::new_object(Some(global)); - make_builtin_fn(parse, "parse", &json, 2); - make_builtin_fn(stringify, "stringify", &json, 3); + make_builtin_fn(Self::parse, "parse", &json, 2); + make_builtin_fn(Self::stringify, "stringify", &json, 3); - json -} + json + } -/// Initialise the `JSON` object on the global object. -#[inline] -pub fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("json", "init"); + /// Initialise the `JSON` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event("json", "init"); - ("JSON", create(global)) + ("JSON", Self::create(global)) + } } diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 5bac5ee3e58..babb9a197a1 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -19,546 +19,550 @@ use crate::{ exec::Interpreter, BoaProfiler, }; -use rand::random; use std::f64; #[cfg(test)] mod tests; -/// Get the absolute value of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.abs -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs -pub fn abs(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).abs() - })) -} +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) struct Math; + +impl Math { + /// Get the absolute value of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.abs + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/abs + pub(crate) fn abs(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).abs() + })) + } -/// Get the arccos of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.acos -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos -pub fn acos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).acos() - })) -} + /// Get the arccos of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.acos + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos + pub(crate) fn acos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).acos() + })) + } -/// Get the hyperbolic arccos of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.acosh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh -pub fn acosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).acosh() - })) -} + /// Get the hyperbolic arccos of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.acosh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acosh + pub(crate) fn acosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).acosh() + })) + } -/// Get the arcsine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.asin -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin -pub fn asin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).asin() - })) -} + /// Get the arcsine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.asin + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin + pub(crate) fn asin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).asin() + })) + } -/// Get the hyperbolic arcsine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.asinh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh -pub fn asinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).asinh() - })) -} + /// Get the hyperbolic arcsine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.asinh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asinh + pub(crate) fn asinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).asinh() + })) + } -/// Get the arctangent of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.atan -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan -pub fn atan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).atan() - })) -} + /// Get the arctangent of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.atan + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan + pub(crate) fn atan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).atan() + })) + } -/// Get the hyperbolic arctangent of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.atanh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh -pub fn atanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).atanh() - })) -} + /// Get the hyperbolic arctangent of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.atanh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atanh + pub(crate) fn atanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).atanh() + })) + } -/// Get the arctangent of a numbers. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.atan2 -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2 -pub fn atan2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")) - .atan2(args.get(1).expect("Could not get argument").to_number()) - })) -} + /// Get the arctangent of a numbers. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.atan2 + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2 + pub(crate) fn atan2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")) + .atan2(args.get(1).expect("Could not get argument").to_number()) + })) + } -/// Get the cubic root of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.cbrt -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt -pub fn cbrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).cbrt() - })) -} + /// Get the cubic root of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.cbrt + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt + pub(crate) fn cbrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).cbrt() + })) + } -/// Get lowest integer above a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.ceil -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil -pub fn ceil(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).ceil() - })) -} + /// Get lowest integer above a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.ceil + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil + pub(crate) fn ceil(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).ceil() + })) + } -/// Get the cosine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.cos -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos -pub fn cos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).cos() - })) -} + /// Get the cosine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.cos + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos + pub(crate) fn cos(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).cos() + })) + } -/// Get the hyperbolic cosine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.cosh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh -pub fn cosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).cosh() - })) -} + /// Get the hyperbolic cosine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.cosh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh + pub(crate) fn cosh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).cosh() + })) + } -/// Get the power to raise the natural logarithm to get the number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.exp -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp -pub fn exp(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).exp() - })) -} + /// Get the power to raise the natural logarithm to get the number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.exp + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp + pub(crate) fn exp(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).exp() + })) + } -/// Get the highest integer below a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.floor -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor -pub fn floor(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).floor() - })) -} + /// Get the highest integer below a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.floor + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor + pub(crate) fn floor(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).floor() + })) + } -/// Get the natural logarithm of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.log -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log -pub fn log(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value <= 0.0 { + /// Get the natural logarithm of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.log + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log + pub(crate) fn log(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { f64::NAN } else { - value.log(f64::consts::E) - } - })) -} + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value <= 0.0 { + f64::NAN + } else { + value.log(f64::consts::E) + } + })) + } -/// Get the base 10 logarithm of the number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.log10 -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10 -pub fn log10(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value <= 0.0 { + /// Get the base 10 logarithm of the number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.log10 + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10 + pub(crate) fn log10(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { f64::NAN } else { - value.log10() - } - })) -} + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value <= 0.0 { + f64::NAN + } else { + value.log10() + } + })) + } -/// Get the base 2 logarithm of the number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.log2 -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2 -pub fn log2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value <= 0.0 { + /// Get the base 2 logarithm of the number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.log2 + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log2 + pub(crate) fn log2(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { f64::NAN } else { - value.log2() - } - })) -} + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value <= 0.0 { + f64::NAN + } else { + value.log2() + } + })) + } -/// Get the maximum of several numbers. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.max -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max -pub fn max(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let mut max = f64::NEG_INFINITY; - for arg in args { - let num = f64::from(arg); - max = max.max(num); + /// Get the maximum of several numbers. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.max + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max + pub(crate) fn max(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let mut max = f64::NEG_INFINITY; + for arg in args { + let num = f64::from(arg); + max = max.max(num); + } + Ok(Value::from(max)) } - Ok(Value::from(max)) -} -/// Get the minimum of several numbers. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.min -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min -pub fn min(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let mut max = f64::INFINITY; - for arg in args { - let num = f64::from(arg); - max = max.min(num); + /// Get the minimum of several numbers. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.min + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min + pub(crate) fn min(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let mut max = f64::INFINITY; + for arg in args { + let num = f64::from(arg); + max = max.min(num); + } + Ok(Value::from(max)) } - Ok(Value::from(max)) -} -/// Raise a number to a power. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.pow -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow -pub fn pow(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.len() >= 2 { - let num = f64::from(args.get(0).expect("Could not get argument")); - let power = f64::from(args.get(1).expect("Could not get argument")); - num.powf(power) - } else { - f64::NAN - })) -} + /// Raise a number to a power. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.pow + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow + pub(crate) fn pow(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.len() >= 2 { + let num = f64::from(args.get(0).expect("Could not get argument")); + let power = f64::from(args.get(1).expect("Could not get argument")); + num.powf(power) + } else { + f64::NAN + })) + } -/// Generate a random floating-point number between `0` and `1`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.random -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random -pub fn _random(_: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(random::())) -} + /// Generate a random floating-point number between `0` and `1`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.random + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random + pub(crate) fn random(_: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(rand::random::())) + } -/// Round a number to the nearest integer. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.round -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round -pub fn round(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).round() - })) -} + /// Round a number to the nearest integer. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.round + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round + pub(crate) fn round(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).round() + })) + } -/// Get the sign of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sign -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign -pub fn sign(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - let value = f64::from(args.get(0).expect("Could not get argument")); - - if value == 0.0 || value == -0.0 { - value + /// Get the sign of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sign + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign + pub(crate) fn sign(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN } else { - value.signum() - } - })) -} + let value = f64::from(args.get(0).expect("Could not get argument")); + + if value == 0.0 || value == -0.0 { + value + } else { + value.signum() + } + })) + } -/// Get the sine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sin -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin -pub fn sin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).sin() - })) -} + /// Get the sine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sin + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin + pub(crate) fn sin(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).sin() + })) + } -/// Get the hyperbolic sine of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sinh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh -pub fn sinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).sinh() - })) -} + /// Get the hyperbolic sine of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sinh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh + pub(crate) fn sinh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).sinh() + })) + } -/// Get the square root of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.sqrt -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt -pub fn sqrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).sqrt() - })) -} -/// Get the tangent of a number -pub fn tan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).tan() - })) -} + /// Get the square root of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.sqrt + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt + pub(crate) fn sqrt(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).sqrt() + })) + } + /// Get the tangent of a number + pub(crate) fn tan(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).tan() + })) + } -/// Get the hyperbolic tangent of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.tanh -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh -pub fn tanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).tanh() - })) -} + /// Get the hyperbolic tangent of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.tanh + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh + pub(crate) fn tanh(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).tanh() + })) + } -/// Get the integer part of a number. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-math.trunc -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc -pub fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(Value::from(if args.is_empty() { - f64::NAN - } else { - f64::from(args.get(0).expect("Could not get argument")).trunc() - })) -} + /// Get the integer part of a number. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-math.trunc + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc + pub(crate) fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Value::from(if args.is_empty() { + f64::NAN + } else { + f64::from(args.get(0).expect("Could not get argument")).trunc() + })) + } -/// Create a new `Math` object -pub fn create(global: &Value) -> Value { - let _timer = BoaProfiler::global().start_event("math:create", "init"); - let math = Value::new_object(Some(global)); - - { - let mut properties = math.as_object_mut().unwrap(); - properties.insert_field("E", Value::from(f64::consts::E)); - properties.insert_field("LN2", Value::from(f64::consts::LN_2)); - properties.insert_field("LN10", Value::from(f64::consts::LN_10)); - properties.insert_field("LOG2E", Value::from(f64::consts::LOG2_E)); - properties.insert_field("LOG10E", Value::from(f64::consts::LOG10_E)); - properties.insert_field("SQRT1_2", Value::from(0.5_f64.sqrt())); - properties.insert_field("SQRT2", Value::from(f64::consts::SQRT_2)); - properties.insert_field("PI", Value::from(f64::consts::PI)); + /// Create a new `Math` object + pub(crate) fn create(global: &Value) -> Value { + let _timer = BoaProfiler::global().start_event("math:create", "init"); + let math = Value::new_object(Some(global)); + + { + let mut properties = math.as_object_mut().unwrap(); + properties.insert_field("E", Value::from(f64::consts::E)); + properties.insert_field("LN2", Value::from(f64::consts::LN_2)); + properties.insert_field("LN10", Value::from(f64::consts::LN_10)); + properties.insert_field("LOG2E", Value::from(f64::consts::LOG2_E)); + properties.insert_field("LOG10E", Value::from(f64::consts::LOG10_E)); + properties.insert_field("SQRT1_2", Value::from(0.5_f64.sqrt())); + properties.insert_field("SQRT2", Value::from(f64::consts::SQRT_2)); + properties.insert_field("PI", Value::from(f64::consts::PI)); + } + make_builtin_fn(Self::abs, "abs", &math, 1); + make_builtin_fn(Self::acos, "acos", &math, 1); + make_builtin_fn(Self::acosh, "acosh", &math, 1); + make_builtin_fn(Self::asin, "asin", &math, 1); + make_builtin_fn(Self::asinh, "asinh", &math, 1); + make_builtin_fn(Self::atan, "atan", &math, 1); + make_builtin_fn(Self::atanh, "atanh", &math, 1); + make_builtin_fn(Self::atan2, "atan2", &math, 2); + make_builtin_fn(Self::cbrt, "cbrt", &math, 1); + make_builtin_fn(Self::ceil, "ceil", &math, 1); + make_builtin_fn(Self::cos, "cos", &math, 1); + make_builtin_fn(Self::cosh, "cosh", &math, 1); + make_builtin_fn(Self::exp, "exp", &math, 1); + make_builtin_fn(Self::floor, "floor", &math, 1); + make_builtin_fn(Self::log, "log", &math, 1); + make_builtin_fn(Self::log10, "log10", &math, 1); + make_builtin_fn(Self::log2, "log2", &math, 1); + make_builtin_fn(Self::max, "max", &math, 2); + make_builtin_fn(Self::min, "min", &math, 2); + make_builtin_fn(Self::pow, "pow", &math, 2); + make_builtin_fn(Self::random, "random", &math, 0); + make_builtin_fn(Self::round, "round", &math, 1); + make_builtin_fn(Self::sign, "sign", &math, 1); + make_builtin_fn(Self::sin, "sin", &math, 1); + make_builtin_fn(Self::sinh, "sinh", &math, 1); + make_builtin_fn(Self::sqrt, "sqrt", &math, 1); + make_builtin_fn(Self::tan, "tan", &math, 1); + make_builtin_fn(Self::tanh, "tanh", &math, 1); + make_builtin_fn(Self::trunc, "trunc", &math, 1); + + math } - make_builtin_fn(abs, "abs", &math, 1); - make_builtin_fn(acos, "acos", &math, 1); - make_builtin_fn(acosh, "acosh", &math, 1); - make_builtin_fn(asin, "asin", &math, 1); - make_builtin_fn(asinh, "asinh", &math, 1); - make_builtin_fn(atan, "atan", &math, 1); - make_builtin_fn(atanh, "atanh", &math, 1); - make_builtin_fn(atan2, "atan2", &math, 2); - make_builtin_fn(cbrt, "cbrt", &math, 1); - make_builtin_fn(ceil, "ceil", &math, 1); - make_builtin_fn(cos, "cos", &math, 1); - make_builtin_fn(cosh, "cosh", &math, 1); - make_builtin_fn(exp, "exp", &math, 1); - make_builtin_fn(floor, "floor", &math, 1); - make_builtin_fn(log, "log", &math, 1); - make_builtin_fn(log10, "log10", &math, 1); - make_builtin_fn(log2, "log2", &math, 1); - make_builtin_fn(max, "max", &math, 2); - make_builtin_fn(min, "min", &math, 2); - make_builtin_fn(pow, "pow", &math, 2); - make_builtin_fn(_random, "random", &math, 0); - make_builtin_fn(round, "round", &math, 1); - make_builtin_fn(sign, "sign", &math, 1); - make_builtin_fn(sin, "sin", &math, 1); - make_builtin_fn(sinh, "sinh", &math, 1); - make_builtin_fn(sqrt, "sqrt", &math, 1); - make_builtin_fn(tan, "tan", &math, 1); - make_builtin_fn(tanh, "tanh", &math, 1); - make_builtin_fn(trunc, "trunc", &math, 1); - - math -} -/// Initialise the `Math` object on the global object. -#[inline] -pub fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("math", "init"); + /// Initialise the `Math` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) -> (&str, Value) { + let _timer = BoaProfiler::global().start_event("math", "init"); - ("Math", create(global)) + ("Math", Self::create(global)) + } } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index f366ea1cff8..e0810a4374c 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -24,6 +24,8 @@ pub(crate) use self::{ boolean::Boolean, error::{Error, RangeError, TypeError}, global_this::GlobalThis, + json::Json, + math::Math, nan::NaN, number::Number, regexp::RegExp, @@ -41,11 +43,9 @@ pub fn init(global: &Value) { Array::init(global), BigInt::init(global), Boolean::init(global), - json::init(global), - math::init(global), + Json::init(global), + Math::init(global), Number::init(global), - NaN::init(global), - GlobalThis::init(global), object::init(global), RegExp::init(global), String::init(global), @@ -55,6 +55,9 @@ pub fn init(global: &Value) { Error::init(global), RangeError::init(global), TypeError::init(global), + // Global properties. + NaN::init(global), + GlobalThis::init(global), ]; let mut global_object = global.as_object_mut().expect("global object"); From 71f2389f9f981e41caf669bb463d05eeb3ade417 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Thu, 11 Jun 2020 21:35:44 +0200 Subject: [PATCH 7/9] Abstracted Object name and function object length. --- boa/src/builtins/array/mod.rs | 42 ++++++++++----------- boa/src/builtins/bigint/mod.rs | 19 ++++++++-- boa/src/builtins/boolean/mod.rs | 14 +++++-- boa/src/builtins/error/mod.rs | 19 ++++++++-- boa/src/builtins/error/range.rs | 19 ++++++++-- boa/src/builtins/error/type.rs | 19 ++++++++-- boa/src/builtins/json/mod.rs | 7 +++- boa/src/builtins/math/mod.rs | 7 +++- boa/src/builtins/nan/mod.rs | 7 +++- boa/src/builtins/number/mod.rs | 19 ++++++++-- boa/src/builtins/object/internal_methods.rs | 23 +++++++---- boa/src/builtins/object/mod.rs | 4 +- boa/src/builtins/regexp/mod.rs | 19 ++++++++-- boa/src/builtins/string/mod.rs | 21 +++++++++-- boa/src/builtins/symbol/mod.rs | 19 ++++++++-- boa/src/builtins/value/display.rs | 2 +- boa/src/builtins/value/hash.rs | 5 ++- boa/src/builtins/value/mod.rs | 35 ++++++----------- boa/src/exec/mod.rs | 9 +++-- 19 files changed, 211 insertions(+), 98 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index d8eae6f3a7d..3075073c04a 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -25,7 +25,6 @@ use crate::{ use std::{ borrow::Borrow, cmp::{max, min}, - ops::Deref, }; /// JavaScript `Array` built-in implementation. @@ -33,6 +32,12 @@ use std::{ pub(crate) struct Array; impl Array { + /// The name of the object. + pub(crate) const NAME: &'static str = "Array"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + /// Creates a new `Array` instance. pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { let array = Value::new_object(Some( @@ -167,25 +172,9 @@ impl Array { args: &[Value], _interpreter: &mut Interpreter, ) -> ResultValue { - let value_true = Value::boolean(true); - let value_false = Value::boolean(false); - - match args.get(0) { - Some(arg) => { - match arg.data() { - // 1. - ValueData::Object(ref obj) => { - // 2. - if let ObjectData::Array = (*obj).deref().borrow().data { - return Ok(value_true); - } - Ok(value_false) - } - // 3. - _ => Ok(value_false), - } - } - None => Ok(value_false), + match args.get(0).and_then(|x| x.as_object()) { + Some(object) => Ok(Value::from(object.is_array())), + None => Ok(Value::from(false)), } } @@ -1031,7 +1020,14 @@ impl Array { make_builtin_fn(Self::slice, "slice", &prototype, 2); make_builtin_fn(Self::some, "some", &prototype, 2); - let array = make_constructor_fn("Array", 1, Self::make_array, global, prototype, true); + let array = make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_array, + global, + prototype, + true, + ); // Static Methods make_builtin_fn(Self::is_array, "isArray", &array, 1); @@ -1042,8 +1038,8 @@ impl Array { /// Initialise the `Array` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("array", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("Array", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index afbe6a303c5..dbe456c911f 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -44,6 +44,12 @@ mod tests; pub struct BigInt(num_bigint::BigInt); impl BigInt { + /// The name of the object. + pub(crate) const NAME: &'static str = "BigInt"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + /// The abstract operation thisBigIntValue takes argument value. /// /// The phrase “this BigInt value” within the specification of a method refers to the @@ -213,7 +219,14 @@ impl BigInt { make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); - let big_int = make_constructor_fn("BigInt", 1, Self::make_bigint, global, prototype, false); + let big_int = make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_bigint, + global, + prototype, + false, + ); make_builtin_fn(Self::as_int_n, "asIntN", &big_int, 2); make_builtin_fn(Self::as_uint_n, "asUintN", &big_int, 2); @@ -224,9 +237,9 @@ impl BigInt { /// Initialise the `BigInt` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("bigint", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("BigInt", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index c87d681c46e..6f384da7ecf 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -27,6 +27,12 @@ use crate::{ pub(crate) struct Boolean; impl Boolean { + /// The name of the object. + pub(crate) const NAME: &'static str = "Boolean"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + /// An Utility function used to get the internal [[BooleanData]]. /// /// More information: @@ -101,8 +107,8 @@ impl Boolean { make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); make_constructor_fn( - "Boolean", - 1, + Self::NAME, + Self::LENGTH, Self::construct_boolean, global, prototype, @@ -113,8 +119,8 @@ impl Boolean { /// Initialise the `Boolean` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("boolean", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("Boolean", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 409ebdfd53b..49d3cafad2f 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -35,6 +35,12 @@ pub(crate) use self::range::RangeError; pub(crate) struct Error; impl Error { + /// The name of the object. + pub(crate) const NAME: &'static str = "Error"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { @@ -77,13 +83,20 @@ impl Error { make_builtin_fn(Self::to_string, "toString", &prototype, 0); - make_constructor_fn("Error", 1, Self::make_error, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_error, + global, + prototype, + true, + ) } /// Initialise the global object with the `Error` object. pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("error", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("Error", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 924d93ac0cf..e45ae14a830 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -25,6 +25,12 @@ use crate::{ pub(crate) struct RangeError; impl RangeError { + /// The name of the object. + pub(crate) const NAME: &'static str = "RangeError"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { @@ -67,13 +73,20 @@ impl RangeError { make_builtin_fn(Self::to_string, "toString", &prototype, 0); - make_constructor_fn("RangeError", 1, Self::make_error, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_error, + global, + prototype, + true, + ) } /// Initialise the global object with the `RangeError` object. pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("rangeerror", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("RangeError", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index 6122185f659..d61afae6d0a 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -31,6 +31,12 @@ use crate::{ pub(crate) struct TypeError; impl TypeError { + /// The name of the object. + pub(crate) const NAME: &'static str = "TypeError"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { if !args.is_empty() { @@ -74,13 +80,20 @@ impl TypeError { make_builtin_fn(Self::to_string, "toString", &prototype, 0); - make_constructor_fn("TypeError", 1, Self::make_error, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_error, + global, + prototype, + true, + ) } /// Initialise the global object with the `RangeError` object. pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("typeerror", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("TypeError", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index db3262d6de7..60f9231ef9f 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -29,6 +29,9 @@ mod tests; pub(crate) struct Json; impl Json { + /// The name of the object. + pub(crate) const NAME: &'static str = "JSON"; + /// `JSON.parse( text[, reviver] )` /// /// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string. @@ -178,8 +181,8 @@ impl Json { /// Initialise the `JSON` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("json", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("JSON", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index babb9a197a1..08db86df1d4 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -28,6 +28,9 @@ mod tests; pub(crate) struct Math; impl Math { + /// The name of the object. + pub(crate) const NAME: &'static str = "Math"; + /// Get the absolute value of a number. /// /// More information: @@ -561,8 +564,8 @@ impl Math { /// Initialise the `Math` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("math", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("Math", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/nan/mod.rs b/boa/src/builtins/nan/mod.rs index a797e13e41d..1e046499e46 100644 --- a/boa/src/builtins/nan/mod.rs +++ b/boa/src/builtins/nan/mod.rs @@ -20,11 +20,14 @@ use crate::{builtins::value::Value, BoaProfiler}; pub(crate) struct NaN; impl NaN { + /// The name of the property. + pub(crate) const NAME: &'static str = "NaN"; + /// Initialize the `NaN` property on the global object. #[inline] pub(crate) fn init(_: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("NaN", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("NaN", Value::from(f64::NAN)) + (Self::NAME, Value::from(f64::NAN)) } } diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index c8ec8ba7c91..e7cf809820f 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -40,6 +40,12 @@ const PARSE_INT_MAX_ARG_COUNT: usize = 2; const PARSE_FLOAT_MAX_ARG_COUNT: usize = 1; impl Number { + /// The name of the object. + pub(crate) const NAME: &'static str = "Number"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + /// This function returns a `Result` of the number `Value`. /// /// If the `Value` is a `Number` primitive of `Number` object the number is returned. @@ -546,7 +552,14 @@ impl Number { PARSE_FLOAT_MAX_ARG_COUNT as i32, ); - let number = make_constructor_fn("Number", 1, Self::make_number, global, prototype, true); + let number = make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_number, + global, + prototype, + true, + ); // Constants from: // https://tc39.es/ecma262/#sec-properties-of-the-number-constructor @@ -568,9 +581,9 @@ impl Number { /// Initialise the `Number` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("number", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("Number", Self::create(global)) + (Self::NAME, Self::create(global)) } /// The abstract operation Number::equal takes arguments diff --git a/boa/src/builtins/object/internal_methods.rs b/boa/src/builtins/object/internal_methods.rs index be47ba6ac99..9c809e829b2 100644 --- a/boa/src/builtins/object/internal_methods.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -392,10 +392,11 @@ impl Object { #[inline] pub fn get_internal_slot(&self, name: &str) -> Value { let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object"); - match self.internal_slots.get(name) { - Some(v) => v.clone(), - None => Value::null(), - } + + self.internal_slots() + .get(name) + .cloned() + .unwrap_or_else(Value::null) } /// Helper function to set an internal slot. @@ -406,7 +407,7 @@ impl Object { /// Helper function for property insertion. #[inline] - pub fn insert_property(&mut self, name: N, p: Property) + pub(crate) fn insert_property(&mut self, name: N, p: Property) where N: Into, { @@ -415,12 +416,16 @@ impl Object { /// Helper function for property removal. #[inline] - pub fn remove_property(&mut self, name: &str) { + pub(crate) fn remove_property(&mut self, name: &str) { self.properties.remove(name); } + /// Inserts a field in the object `properties` without checking if it's writable. + /// + /// If a field was already in the object with the same name that a `Some` is returned + /// with that field, otherwise None is retuned. #[inline] - pub fn insert_field(&mut self, name: N, value: Value) -> Option + pub(crate) fn insert_field(&mut self, name: N, value: Value) -> Option where N: Into, { @@ -434,6 +439,10 @@ impl Object { ) } + /// This function returns an Optional reference value to the objects field. + /// + /// if it exist `Some` is returned with a reference to that fields value. + /// Otherwise `None` is retuned. #[inline] pub fn get_field(&self, name: &str) -> Option<&Value> { self.properties.get(name).and_then(|x| x.value.as_ref()) diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 5a2d60d043c..24bdf573bac 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -252,9 +252,9 @@ impl Object { } #[inline] - pub fn as_string(&self) -> Option<&String> { + pub fn as_string(&self) -> Option<&str> { match self.data { - ObjectData::String(ref string) => Some(string), + ObjectData::String(ref string) => Some(string.as_str()), _ => None, } } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 4c5d0faf189..569e6dd56af 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -61,6 +61,12 @@ pub(crate) struct RegExp { impl InternalState for RegExp {} impl RegExp { + /// The name of the object. + pub(crate) const NAME: &'static str = "RegExp"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 2; + /// Create a new `RegExp` pub(crate) fn make_regexp( this: &mut Value, @@ -487,14 +493,21 @@ impl RegExp { make_builtin_fn(Self::get_sticky, "sticky", &prototype, 0); make_builtin_fn(Self::get_unicode, "unicode", &prototype, 0); - make_constructor_fn("RegExp", 1, Self::make_regexp, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_regexp, + global, + prototype, + true, + ) } /// Initialise the `RegExp` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("regexp", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("RegExp", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 860b101fcb2..d3da1ed3e7f 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -36,13 +36,19 @@ use std::{ pub(crate) struct String; impl String { + /// The name of the object. + pub(crate) const NAME: &'static str = "String"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 1; + fn this_string_value(this: &Value, ctx: &mut Interpreter) -> Result { match this.data() { ValueData::String(ref string) => return Ok(string.clone()), ValueData::Object(ref object) => { let object = object.borrow(); if let Some(string) = object.as_string() { - return Ok(string.clone()); + return Ok(string.to_owned()); } } _ => {} @@ -1082,14 +1088,21 @@ impl String { make_builtin_fn(Self::match_all, "matchAll", &prototype, 1); make_builtin_fn(Self::replace, "replace", &prototype, 2); - make_constructor_fn("String", 1, Self::make_string, global, prototype, true) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::make_string, + global, + prototype, + true, + ) } /// Initialise the `String` object on the global object. #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("string", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("String", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index ed6c3ed47ed..deb1d3553d5 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -30,6 +30,12 @@ use gc::{Finalize, Trace}; pub struct Symbol(Option>, u32); impl Symbol { + /// The name of the object. + pub(crate) const NAME: &'static str = "Symbol"; + + /// The amount of arguments this function object takes. + pub(crate) const LENGTH: i32 = 0; + /// Returns the `Symbol`s description. pub fn description(&self) -> Option<&str> { self.0.as_deref() @@ -101,14 +107,21 @@ impl Symbol { let prototype = Value::new_object(Some(global)); make_builtin_fn(Self::to_string, "toString", &prototype, 0); - make_constructor_fn("Symbol", 1, Self::call, global, prototype, false) + make_constructor_fn( + Self::NAME, + Self::LENGTH, + Self::call, + global, + prototype, + false, + ) } /// Initialise the `Symbol` object on the global object. #[inline] pub fn init(global: &Value) -> (&str, Value) { - let _timer = BoaProfiler::global().start_event("symbol", "init"); + let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); - ("Symbol", Self::create(global)) + (Self::NAME, Self::create(global)) } } diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index 39f1a256038..fef09251920 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -112,7 +112,7 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String { } } ValueData::Symbol(ref symbol) => match symbol.description() { - Some(description) => format!("Symbol({})", description), + Some(ref desc) => format!("Symbol({})", desc), None => String::from("Symbol()"), }, _ => format!("{}", x), diff --git a/boa/src/builtins/value/hash.rs b/boa/src/builtins/value/hash.rs index c32f9afc7dd..2ddc7dc3eb0 100644 --- a/boa/src/builtins/value/hash.rs +++ b/boa/src/builtins/value/hash.rs @@ -11,12 +11,13 @@ impl PartialEq for Value { impl Eq for Value {} -#[derive(PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct UndefinedHashable; -#[derive(PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct NullHashable; +#[derive(Debug, Clone, Copy)] struct RationalHashable(f64); impl PartialEq for RationalHashable { diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 546901fee2b..b69541374c2 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -418,7 +418,7 @@ impl ValueData { /// Returns true if the value is true. /// - /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean + /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) pub fn is_true(&self) -> bool { match *self { Self::Object(_) => true, @@ -515,10 +515,10 @@ impl ValueData { let object = object.borrow(); match object.properties().get(field) { Some(value) => Some(value.clone()), - None => match object.internal_slots().get(INSTANCE_PROTOTYPE) { - Some(value) => value.get_property(field), - None => None, - }, + None => object + .internal_slots() + .get(INSTANCE_PROTOTYPE) + .and_then(|value| value.get_property(field)), } } _ => None, @@ -555,17 +555,9 @@ impl ValueData { pub fn get_internal_slot(&self, field: &str) -> Value { let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value"); - let property = self - .as_object() - .and_then(|x| match x.internal_slots().get(field) { - Some(value) => Some(value.clone()), - None => None, - }); - - match property { - Some(value) => value, - None => Value::undefined(), - } + self.as_object() + .and_then(|x| x.internal_slots().get(field).cloned()) + .unwrap_or_else(Value::undefined) } /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist @@ -608,18 +600,13 @@ impl ValueData { /// Check whether an object has an internal state set. pub fn has_internal_state(&self) -> bool { - match self.as_object() { - Some(object) => object.state().is_some(), - None => false, - } + 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 { - match self.as_object() { - Some(object) => object.state().clone(), - None => None, - } + self.as_object() + .and_then(|object| object.state().as_ref().cloned()) } /// Run a function with a reference to the internal state. diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index f45a692063f..034f9f9e3eb 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -89,6 +89,9 @@ impl Interpreter { &mut self.realm } + /// Generates a new `Symbol` internal hash. + /// + /// This currently is an incremented value. pub(crate) fn generate_hash(&mut self) -> u32 { let hash = self.symbol_count; self.symbol_count += 1; @@ -271,10 +274,8 @@ impl Interpreter { ValueData::Null => Ok(0.0), ValueData::Undefined => Ok(f64::NAN), ValueData::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), - ValueData::String(ref string) => match string.parse() { - Ok(number) => Ok(number), - Err(_) => Ok(f64::NAN), - }, // this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type + // TODO: this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type + ValueData::String(ref string) => Ok(string.parse().unwrap_or(f64::NAN)), ValueData::Rational(number) => Ok(number), ValueData::Integer(integer) => Ok(f64::from(integer)), ValueData::Symbol(_) => { From c144cbd91fef7b28634caacc07879568493c7ef3 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Sun, 14 Jun 2020 17:53:16 +0200 Subject: [PATCH 8/9] Made `make_builtin_fn` and `make_constructor_fn` take a usize - Added `#[inline]` to some small functions --- boa/src/builtins/array/mod.rs | 2 +- boa/src/builtins/bigint/mod.rs | 2 +- boa/src/builtins/boolean/mod.rs | 3 +- boa/src/builtins/error/mod.rs | 3 +- boa/src/builtins/error/range.rs | 3 +- boa/src/builtins/error/type.rs | 3 +- boa/src/builtins/function/mod.rs | 4 +- boa/src/builtins/number/mod.rs | 11 ++--- boa/src/builtins/object/internal_methods.rs | 47 +++++++++------------ boa/src/builtins/object/mod.rs | 2 + boa/src/builtins/regexp/mod.rs | 2 +- boa/src/builtins/string/mod.rs | 2 +- boa/src/builtins/symbol/mod.rs | 2 +- boa/src/builtins/value/mod.rs | 10 ++++- boa/src/builtins/value/tests.rs | 1 + boa/src/exec/mod.rs | 7 +++ 16 files changed, 56 insertions(+), 48 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 3075073c04a..0517e0c3ccf 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -36,7 +36,7 @@ impl Array { pub(crate) const NAME: &'static str = "Array"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; /// Creates a new `Array` instance. pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index dbe456c911f..eb2a2cf89ec 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -48,7 +48,7 @@ impl BigInt { pub(crate) const NAME: &'static str = "BigInt"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; /// The abstract operation thisBigIntValue takes argument value. /// diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 6f384da7ecf..e2d5dd2ee3c 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -31,7 +31,7 @@ impl Boolean { pub(crate) const NAME: &'static str = "Boolean"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; /// An Utility function used to get the internal [[BooleanData]]. /// @@ -93,6 +93,7 @@ impl Boolean { /// /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf + #[inline] pub(crate) fn value_of(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { Ok(Value::from(Self::this_boolean_value(this, ctx)?)) } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 49d3cafad2f..7bdee31bc7c 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -39,7 +39,7 @@ impl Error { pub(crate) const NAME: &'static str = "Error"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { @@ -94,6 +94,7 @@ impl Error { } /// Initialise the global object with the `Error` object. + #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index e45ae14a830..c51df0b350b 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -29,7 +29,7 @@ impl RangeError { pub(crate) const NAME: &'static str = "RangeError"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { @@ -84,6 +84,7 @@ impl RangeError { } /// Initialise the global object with the `RangeError` object. + #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index d61afae6d0a..3a4f2de9775 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -35,7 +35,7 @@ impl TypeError { pub(crate) const NAME: &'static str = "TypeError"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; /// Create a new error object. pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { @@ -91,6 +91,7 @@ impl TypeError { } /// Initialise the global object with the `RangeError` object. + #[inline] pub(crate) fn init(global: &Value) -> (&str, Value) { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 5a54fa76d58..48d2c4c4645 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -407,7 +407,7 @@ pub fn create(global: &Value) -> Value { /// So far this is only used by internal functions pub fn make_constructor_fn( name: &str, - length: i32, + length: usize, body: NativeFunctionData, global: &Value, prototype: Value, @@ -473,7 +473,7 @@ pub fn make_constructor_fn( /// some other number of arguments. /// /// If no length is provided, the length will be set to 0. -pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, length: i32) +pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, length: usize) where N: Into, { diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index e7cf809820f..faf3e3c7228 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -44,7 +44,7 @@ impl Number { pub(crate) const NAME: &'static str = "Number"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; /// This function returns a `Result` of the number `Value`. /// @@ -539,17 +539,12 @@ impl Number { make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); - make_builtin_fn( - Self::parse_int, - "parseInt", - global, - PARSE_INT_MAX_ARG_COUNT as i32, - ); + make_builtin_fn(Self::parse_int, "parseInt", global, PARSE_INT_MAX_ARG_COUNT); make_builtin_fn( Self::parse_float, "parseFloat", global, - PARSE_FLOAT_MAX_ARG_COUNT as i32, + PARSE_FLOAT_MAX_ARG_COUNT, ); let number = make_constructor_fn( diff --git a/boa/src/builtins/object/internal_methods.rs b/boa/src/builtins/object/internal_methods.rs index 9c809e829b2..996fddc90a4 100644 --- a/boa/src/builtins/object/internal_methods.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -295,11 +295,9 @@ impl Object { // Prop could either be a String or Symbol match *(*prop) { ValueData::String(ref st) => { - match self.properties.get(st) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { + self.properties() + .get(st) + .map_or_else(Property::default, |v| { let mut d = Property::default(); if v.is_data_descriptor() { d.value = v.value.clone(); @@ -312,30 +310,25 @@ impl Object { d.enumerable = v.enumerable; d.configurable = v.configurable; d - } - } + }) } - ValueData::Symbol(ref symbol) => { - match self.symbol_properties().get(&symbol.hash()) { - // If O does not have an own property with key P, return undefined. - // In this case we return a new empty Property - None => Property::default(), - Some(ref v) => { - let mut d = Property::default(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - d.writable = v.writable; - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.enumerable = v.enumerable; - d.configurable = v.configurable; - d + ValueData::Symbol(ref symbol) => self + .symbol_properties() + .get(&symbol.hash()) + .map_or_else(Property::default, |v| { + let mut d = Property::default(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + d.writable = v.writable; + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); } - } - } + d.enumerable = v.enumerable; + d.configurable = v.configurable; + d + }), _ => Property::default(), } } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 24bdf573bac..0a7468f4e72 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -96,6 +96,7 @@ impl Display for ObjectData { impl Default for Object { /// Return a new ObjectData struct, with `kind` set to Ordinary + #[inline] fn default() -> Self { let mut object = Self { data: ObjectData::Ordinary, @@ -111,6 +112,7 @@ impl Default for Object { } impl Object { + #[inline] pub fn new() -> Self { Default::default() } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 569e6dd56af..c6970457788 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -65,7 +65,7 @@ impl RegExp { pub(crate) const NAME: &'static str = "RegExp"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 2; + pub(crate) const LENGTH: usize = 2; /// Create a new `RegExp` pub(crate) fn make_regexp( diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index d3da1ed3e7f..39b56c87379 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -40,7 +40,7 @@ impl String { pub(crate) const NAME: &'static str = "String"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 1; + pub(crate) const LENGTH: usize = 1; fn this_string_value(this: &Value, ctx: &mut Interpreter) -> Result { match this.data() { diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index deb1d3553d5..2c551c838d5 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -34,7 +34,7 @@ impl Symbol { pub(crate) const NAME: &'static str = "Symbol"; /// The amount of arguments this function object takes. - pub(crate) const LENGTH: i32 = 0; + pub(crate) const LENGTH: usize = 0; /// Returns the `Symbol`s description. pub fn description(&self) -> Option<&str> { diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index b69541374c2..73be83e9bbd 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -404,11 +404,13 @@ impl ValueData { } /// Returns true if the value is a bigint. + #[inline] pub fn is_bigint(&self) -> bool { matches!(self, Self::BigInt(_)) } /// Returns an optional reference to a `BigInt` if the value is a BigInt primitive. + #[inline] pub fn as_bigint(&self) -> Option<&BigInt> { match self { Self::BigInt(bigint) => Some(bigint), @@ -552,6 +554,7 @@ impl ValueData { /// Resolve the property in the object. /// /// Returns a copy of the Property. + #[inline] pub fn get_internal_slot(&self, field: &str) -> Value { let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value"); @@ -599,6 +602,7 @@ impl ValueData { } /// 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()) } @@ -657,7 +661,8 @@ impl ValueData { } } - /// Check to see if the Value has the field, mainly used by environment records + /// Check to see if the Value has the field, mainly used by environment records. + #[inline] pub fn has_field(&self, field: &str) -> bool { let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); self.get_property(field).is_some() @@ -709,7 +714,8 @@ impl ValueData { value } - /// Set the kind of an object + /// Set the kind of an object. + #[inline] pub fn set_data(&self, data: ObjectData) { if let Self::Object(ref obj) = *self { (*obj.deref().borrow_mut()).data = data; diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs index bfe22614d9f..008b04076ad 100644 --- a/boa/src/builtins/value/tests.rs +++ b/boa/src/builtins/value/tests.rs @@ -111,6 +111,7 @@ fn abstract_equality_comparison() { ); } +/// Helper function to get the hash of a `Value`. fn hash_value(value: &Value) -> u64 { let mut hasher = DefaultHasher::new(); value.hash(&mut hasher); diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 034f9f9e3eb..77e83ee912e 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -80,11 +80,13 @@ impl Interpreter { } /// Retrieves the `Realm` of this executor. + #[inline] pub(crate) fn realm(&self) -> &Realm { &self.realm } /// Retrieves the `Realm` of this executor as a mutable reference. + #[inline] pub(crate) fn realm_mut(&mut self) -> &mut Realm { &mut self.realm } @@ -92,6 +94,7 @@ impl Interpreter { /// Generates a new `Symbol` internal hash. /// /// This currently is an incremented value. + #[inline] pub(crate) fn generate_hash(&mut self) -> u32 { let hash = self.symbol_count; self.symbol_count += 1; @@ -548,13 +551,16 @@ impl Interpreter { } } + #[inline] pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) { self.state = new_state } + #[inline] pub(crate) fn get_current_state(&self) -> &InterpreterState { &self.state } + /// Check if the `Value` can be converted to an `Object` /// /// The abstract operation `RequireObjectCoercible` takes argument argument. @@ -566,6 +572,7 @@ impl Interpreter { /// /// [table]: https://tc39.es/ecma262/#table-14 /// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible + #[inline] pub fn require_object_coercible<'a>(&mut self, value: &'a Value) -> Result<&'a Value, Value> { if value.is_null_or_undefined() { self.throw_type_error("cannot convert null or undefined to Object")?; From 25572f5d38cbe5b64f308f2ef1a2f3730e41a70c Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Sun, 14 Jun 2020 21:44:06 +0200 Subject: [PATCH 9/9] `construct_type_error` and `construct_range_error` --- boa/src/builtins/bigint/mod.rs | 3 +-- boa/src/builtins/boolean/mod.rs | 4 +-- boa/src/builtins/function/mod.rs | 5 +++- boa/src/builtins/global_this/mod.rs | 1 + boa/src/builtins/nan/mod.rs | 2 +- boa/src/builtins/number/mod.rs | 4 +-- boa/src/builtins/string/mod.rs | 4 +-- boa/src/builtins/symbol/mod.rs | 3 +-- boa/src/builtins/value/mod.rs | 6 ++--- boa/src/exec/exception.rs | 42 ++++++++++++++++++++++++++--- boa/src/exec/mod.rs | 41 +++++++++------------------- 11 files changed, 65 insertions(+), 50 deletions(-) diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index eb2a2cf89ec..577f71e1184 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -78,8 +78,7 @@ impl BigInt { } // 3. Throw a TypeError exception. - ctx.throw_type_error("'this' is not a BigInt")?; - unreachable!(); + Err(ctx.construct_type_error("'this' is not a BigInt")) } /// `BigInt()` diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index e2d5dd2ee3c..652147e90c7 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -51,9 +51,7 @@ impl Boolean { _ => {} } - Err(ctx - .throw_type_error("'this' is not a boolean") - .expect_err("throw_type_error() did not return an error")) + Err(ctx.construct_type_error("'this' is not a boolean")) } /// `[[Construct]]` Create a new boolean object diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 48d2c4c4645..65668958056 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -413,6 +413,9 @@ pub fn make_constructor_fn( prototype: Value, constructable: bool, ) -> Value { + let _timer = + BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init"); + // Create the native function let mut function = Function::builtin(Vec::new(), body); function.constructable = constructable; @@ -478,7 +481,7 @@ where N: Into, { let name = name.into(); - let _timer = BoaProfiler::global().start_event(&name, "make_builtin_fn"); + let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); let mut function = Object::function(Function::builtin(Vec::new(), function)); function.insert_field("length", Value::from(length)); diff --git a/boa/src/builtins/global_this/mod.rs b/boa/src/builtins/global_this/mod.rs index 1ede053dcf8..2ef49347b9b 100644 --- a/boa/src/builtins/global_this/mod.rs +++ b/boa/src/builtins/global_this/mod.rs @@ -19,6 +19,7 @@ mod tests; pub(crate) struct GlobalThis; impl GlobalThis { + /// The binding name of the property. pub(crate) const NAME: &'static str = "globalThis"; /// Initialize the `globalThis` property on the global object. diff --git a/boa/src/builtins/nan/mod.rs b/boa/src/builtins/nan/mod.rs index 1e046499e46..a87e17c4035 100644 --- a/boa/src/builtins/nan/mod.rs +++ b/boa/src/builtins/nan/mod.rs @@ -20,7 +20,7 @@ use crate::{builtins::value::Value, BoaProfiler}; pub(crate) struct NaN; impl NaN { - /// The name of the property. + /// The binding name of the property. pub(crate) const NAME: &'static str = "NaN"; /// Initialize the `NaN` property on the global object. diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index faf3e3c7228..c06b22b7c62 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -68,9 +68,7 @@ impl Number { _ => {} } - Err(ctx - .throw_type_error("'this' is not a number") - .expect_err("throw_type_error() did not return an error")) + Err(ctx.construct_type_error("'this' is not a number")) } /// Helper function that formats a float as a ES6-style exponential number string. diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 39b56c87379..d9abe9ae68e 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -54,9 +54,7 @@ impl String { _ => {} } - Err(ctx - .throw_type_error("'this' is not a string") - .expect_err("throw_type_error() did not return an error")) + Err(ctx.construct_type_error("'this' is not a string")) } /// [[Construct]] - Creates a new instance `this` diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 2c551c838d5..b764199cfc5 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -58,8 +58,7 @@ impl Symbol { _ => {} } - ctx.throw_type_error("'this' is not a Symbol")?; - unreachable!(); + Err(ctx.construct_type_error("'this' is not a Symbol")) } /// The `Symbol()` constructor returns a value of type symbol. diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 73be83e9bbd..50f518b2e0d 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -257,9 +257,9 @@ impl Value { .map(JSONValue::Number) .unwrap_or(JSONValue::Null)), ValueData::Integer(val) => Ok(JSONValue::Number(JSONNumber::from(val))), - ValueData::BigInt(_) => Err(interpreter - .throw_type_error("BigInt value can't be serialized in JSON") - .expect_err("throw_type_error should always return an error")), + ValueData::BigInt(_) => { + Err(interpreter.construct_type_error("BigInt value can't be serialized in JSON")) + } ValueData::Symbol(_) | ValueData::Undefined => { unreachable!("Symbols and Undefined JSON Values depend on parent type"); } diff --git a/boa/src/exec/exception.rs b/boa/src/exec/exception.rs index ddef2e82ee1..d227662db4f 100644 --- a/boa/src/exec/exception.rs +++ b/boa/src/exec/exception.rs @@ -8,8 +8,8 @@ use crate::{ }; impl Interpreter { - /// Throws a `RangeError` with the specified message. - pub fn throw_range_error(&mut self, message: M) -> ResultValue + /// Constructs a `RangeError` with the specified message. + pub fn construct_range_error(&mut self, message: M) -> Value where M: Into, { @@ -19,10 +19,19 @@ impl Interpreter { vec![Const::from(message.into()).into()], )) .run(self) + .expect_err("RangeError should always throw") } - /// Throws a `TypeError` with the specified message. - pub fn throw_type_error(&mut self, message: M) -> ResultValue + /// Throws a `RangeError` with the specified message. + pub fn throw_range_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + Err(self.construct_range_error(message)) + } + + /// Constructs a `TypeError` with the specified message. + pub fn construct_type_error(&mut self, message: M) -> Value where M: Into, { @@ -32,5 +41,30 @@ impl Interpreter { vec![Const::from(message.into()).into()], )) .run(self) + .expect_err("TypeError should always throw") + } + + /// Throws a `TypeError` with the specified message. + pub fn throw_type_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + Err(self.construct_type_error(message)) + } + + /// Constructs a `ReferenceError` with the specified message. + pub fn construct_reference_error(&mut self, _message: M) -> Value + where + M: Into, + { + unimplemented!("ReferenceError: is not implemented"); + } + + /// Throws a `ReferenceError` with the specified message. + pub fn throw_reference_error(&mut self, message: M) -> ResultValue + where + M: Into, + { + Err(self.construct_reference_error(message)) } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 77e83ee912e..aca0cd67110 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -181,8 +181,7 @@ impl Interpreter { ValueData::Integer(integer) => Ok(integer.to_string()), ValueData::String(string) => Ok(string.clone()), ValueData::Symbol(_) => { - self.throw_type_error("can't convert symbol to string")?; - unreachable!(); + Err(self.construct_type_error("can't convert symbol to string")) } ValueData::BigInt(ref bigint) => Ok(bigint.to_string()), ValueData::Object(_) => { @@ -196,13 +195,9 @@ impl Interpreter { #[allow(clippy::wrong_self_convention)] pub fn to_bigint(&mut self, value: &Value) -> Result { match value.data() { - ValueData::Null => { - self.throw_type_error("cannot convert null to a BigInt")?; - unreachable!(); - } + ValueData::Null => Err(self.construct_type_error("cannot convert null to a BigInt")), ValueData::Undefined => { - self.throw_type_error("cannot convert undefined to a BigInt")?; - unreachable!(); + Err(self.construct_type_error("cannot convert undefined to a BigInt")) } ValueData::String(ref string) => Ok(BigInt::from_string(string, self)?), ValueData::Boolean(true) => Ok(BigInt::from(1)), @@ -212,11 +207,10 @@ impl Interpreter { if let Ok(bigint) = BigInt::try_from(*num) { return Ok(bigint); } - self.throw_type_error(format!( + Err(self.construct_type_error(format!( "The number {} cannot be converted to a BigInt because it is not an integer", num - ))?; - unreachable!(); + ))) } ValueData::BigInt(b) => Ok(b.clone()), ValueData::Object(_) => { @@ -224,8 +218,7 @@ impl Interpreter { self.to_bigint(&primitive) } ValueData::Symbol(_) => { - self.throw_type_error("cannot convert Symbol to a BigInt")?; - unreachable!(); + Err(self.construct_type_error("cannot convert Symbol to a BigInt")) } } } @@ -242,13 +235,11 @@ impl Interpreter { let integer_index = self.to_integer(value)?; if integer_index < 0 { - self.throw_range_error("Integer index must be >= 0")?; - unreachable!(); + return Err(self.construct_range_error("Integer index must be >= 0")); } if integer_index > 2i64.pow(53) - 1 { - self.throw_range_error("Integer index must be less than 2**(53) - 1")?; - unreachable!() + return Err(self.construct_range_error("Integer index must be less than 2**(53) - 1")); } Ok(integer_index as usize) @@ -281,14 +272,8 @@ impl Interpreter { ValueData::String(ref string) => Ok(string.parse().unwrap_or(f64::NAN)), ValueData::Rational(number) => Ok(number), ValueData::Integer(integer) => Ok(f64::from(integer)), - ValueData::Symbol(_) => { - self.throw_type_error("argument must not be a symbol")?; - unreachable!() - } - ValueData::BigInt(_) => { - self.throw_type_error("argument must not be a bigint")?; - unreachable!() - } + ValueData::Symbol(_) => Err(self.construct_type_error("argument must not be a symbol")), + ValueData::BigInt(_) => Err(self.construct_type_error("argument must not be a bigint")), ValueData::Object(_) => { let prim_value = self.to_primitive(&mut (value.clone()), PreferredType::Number); self.to_number(&prim_value) @@ -575,10 +560,10 @@ impl Interpreter { #[inline] pub fn require_object_coercible<'a>(&mut self, value: &'a Value) -> Result<&'a Value, Value> { if value.is_null_or_undefined() { - self.throw_type_error("cannot convert null or undefined to Object")?; - unreachable!(); + Err(self.construct_type_error("cannot convert null or undefined to Object")) + } else { + Ok(value) } - Ok(value) } }