diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index abb5512ec26..5f90c3e6215 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -71,7 +71,7 @@ impl Array { // Wipe existing contents of the array object let orig_length = i32::from(&array_obj.get_field("length")); for n in 0..orig_length { - array_obj_ptr.remove_property(&n.to_string()); + array_obj_ptr.remove_property(n); } // Create length @@ -82,7 +82,7 @@ impl Array { array_obj_ptr.set_property("length".to_string(), length); for (n, value) in array_contents.iter().enumerate() { - array_obj_ptr.set_field(n.to_string(), value); + array_obj_ptr.set_field(n, value); } Ok(array_obj_ptr) } @@ -94,7 +94,7 @@ impl Array { for (n, value) in add_values.iter().enumerate() { let new_index = orig_length.wrapping_add(n as i32); - array_ptr.set_field(new_index.to_string(), value); + array_ptr.set_field(new_index, value); } array_ptr.set_field( @@ -127,7 +127,7 @@ impl Array { length = i32::from(&args[0]); // TODO: It should not create an array of undefineds, but an empty array ("holy" array in V8) with length `n`. for n in 0..length { - this.set_field(n.to_string(), Value::undefined()); + this.set_field(n, Value::undefined()); } } 1 if args[0].is_double() => { @@ -135,7 +135,7 @@ impl Array { } _ => { for (n, value) in args.iter().enumerate() { - this.set_field(n.to_string(), value.clone()); + this.set_field(n, value.clone()); } } } @@ -197,13 +197,13 @@ impl Array { let this_length = i32::from(&this.get_field("length")); for n in 0..this_length { - new_values.push(this.get_field(n.to_string())); + new_values.push(this.get_field(n)); } for concat_array in args { let concat_length = i32::from(&concat_array.get_field("length")); for n in 0..concat_length { - new_values.push(concat_array.get_field(n.to_string())); + new_values.push(concat_array.get_field(n)); } } @@ -244,7 +244,7 @@ impl Array { } let pop_index = curr_length.wrapping_sub(1); let pop_value: Value = this.get_field(pop_index.to_string()); - this.remove_property(&pop_index.to_string()); + this.remove_property(pop_index); this.set_field("length", Value::from(pop_index)); Ok(pop_value) } @@ -274,7 +274,7 @@ impl Array { let length = i32::from(&this.get_field("length")); for i in 0..length { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; interpreter.call(callback_arg, &this_arg, &arguments)?; @@ -306,7 +306,7 @@ impl Array { let mut elem_strs = Vec::new(); let length = i32::from(&this.get_field("length")); for n in 0..length { - let elem_str = ctx.to_string(&this.get_field(n.to_string()))?.to_string(); + let elem_str = ctx.to_string(&this.get_field(n))?.to_string(); elem_strs.push(elem_str); } @@ -373,21 +373,21 @@ impl Array { for lower in 0..middle { let upper = len.wrapping_sub(lower).wrapping_sub(1); - let upper_exists = this.has_field(&upper.to_string()); - let lower_exists = this.has_field(&lower.to_string()); + let upper_exists = this.has_field(upper); + let lower_exists = this.has_field(lower); - let upper_value = this.get_field(upper.to_string()); - let lower_value = this.get_field(lower.to_string()); + let upper_value = this.get_field(upper); + let lower_value = this.get_field(lower); if upper_exists && lower_exists { - this.set_field(upper.to_string(), lower_value); - this.set_field(lower.to_string(), upper_value); + this.set_field(upper, lower_value); + this.set_field(lower, upper_value); } else if upper_exists { - this.set_field(lower.to_string(), upper_value); - this.remove_property(&upper.to_string()); + this.set_field(lower, upper_value); + this.remove_property(upper); } else if lower_exists { - this.set_field(upper.to_string(), lower_value); - this.remove_property(&lower.to_string()); + this.set_field(upper, lower_value); + this.remove_property(lower); } } @@ -410,25 +410,25 @@ impl Array { if len == 0 { this.set_field("length", 0); // Since length is 0, this will be an Undefined value - return Ok(this.get_field(0.to_string())); + return Ok(this.get_field(0)); } - let first: Value = this.get_field(0.to_string()); + let first: Value = this.get_field(0); for k in 1..len { - let from = k.to_string(); - let to = (k.wrapping_sub(1)).to_string(); + let from = k; + let to = k.wrapping_sub(1); let from_value = this.get_field(from); if from_value.is_undefined() { - this.remove_property(&to); + this.remove_property(to); } else { this.set_field(to, from_value); } } let final_index = len.wrapping_sub(1); - this.remove_property(&(final_index).to_string()); + this.remove_property(final_index); this.set_field("length", Value::from(final_index)); Ok(first) @@ -452,19 +452,19 @@ impl Array { if arg_c > 0 { for k in (1..=len).rev() { - let from = (k.wrapping_sub(1)).to_string(); - let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string(); + let from = k.wrapping_sub(1); + let to = k.wrapping_add(arg_c).wrapping_sub(1); let from_value = this.get_field(from); if from_value.is_undefined() { - this.remove_property(&to); + this.remove_property(to); } else { this.set_field(to, from_value); } } for j in 0..arg_c { this.set_field( - j.to_string(), + j, args.get(j as usize) .expect("Could not get argument") .clone(), @@ -510,7 +510,7 @@ impl Array { let max_len = i32::from(&this.get_field("length")); let mut len = max_len; while i < len { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; let result = interpreter.call(callback, &this_arg, &arguments)?; if !result.to_boolean() { @@ -549,7 +549,7 @@ impl Array { let values: Vec = (0..length) .map(|idx| { - let element = this.get_field(idx.to_string()); + let element = this.get_field(idx); let args = [element, Value::from(idx), new.clone()]; interpreter @@ -603,7 +603,7 @@ impl Array { }; while idx < len { - let check_element = this.get_field(idx.to_string()).clone(); + let check_element = this.get_field(idx).clone(); if check_element.strict_equals(&search_element) { return Ok(Value::from(idx)); @@ -656,7 +656,7 @@ impl Array { }; while idx >= 0 { - let check_element = this.get_field(idx.to_string()).clone(); + let check_element = this.get_field(idx).clone(); if check_element.strict_equals(&search_element) { return Ok(Value::from(idx)); @@ -690,7 +690,7 @@ impl Array { let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); let len = i32::from(&this.get_field("length")); for i in 0..len { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element.clone(), Value::from(i), this.clone()]; let result = interpreter.call(callback, &this_arg, &arguments)?; if result.to_boolean() { @@ -730,7 +730,7 @@ impl Array { let length = i32::from(&this.get_field("length")); for i in 0..length { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; let result = interpreter.call(predicate_arg, &this_arg, &arguments)?; @@ -777,7 +777,7 @@ impl Array { }; for i in start..fin { - this.set_field(i.to_string(), value.clone()); + this.set_field(i, value.clone()); } Ok(this.clone()) @@ -799,7 +799,7 @@ impl Array { let length = i32::from(&this.get_field("length")); for idx in 0..length { - let check_element = this.get_field(idx.to_string()).clone(); + let check_element = this.get_field(idx).clone(); if same_value_zero(&check_element, &search_element) { return Ok(Value::from(true)); @@ -854,7 +854,7 @@ impl Array { let span = max(to.wrapping_sub(from), 0); let mut new_array_len: i32 = 0; for i in from..from.wrapping_add(span) { - new_array.set_field(new_array_len.to_string(), this.get_field(i.to_string())); + new_array.set_field(new_array_len, this.get_field(i)); new_array_len = new_array_len.wrapping_add(1); } new_array.set_field("length", Value::from(new_array_len)); @@ -892,7 +892,7 @@ impl Array { let values = (0..length) .filter_map(|idx| { - let element = this.get_field(idx.to_string()); + let element = this.get_field(idx); let args = [element.clone(), Value::from(idx), new.clone()]; @@ -942,7 +942,7 @@ impl Array { let max_len = i32::from(&this.get_field("length")); let mut len = max_len; while i < len { - let element = this.get_field(i.to_string()); + let element = this.get_field(i); let arguments = [element, Value::from(i), this.clone()]; let result = interpreter.call(callback, &this_arg, &arguments)?; if result.to_boolean() { @@ -986,7 +986,7 @@ impl Array { let mut accumulator = if initial_value.is_undefined() { let mut k_present = false; while k < length { - if this.has_field(&k.to_string()) { + if this.has_field(k) { k_present = true; break; } @@ -997,20 +997,15 @@ impl Array { "Reduce was called on an empty array and with no initial value", ); } - let result = this.get_field(k.to_string()); + let result = this.get_field(k); k += 1; result } else { initial_value }; while k < length { - if this.has_field(&k.to_string()) { - let arguments = [ - accumulator, - this.get_field(k.to_string()), - Value::from(k), - this.clone(), - ]; + if this.has_field(k) { + let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()]; accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?; /* We keep track of possibly shortened length in order to prevent unnecessary iteration. It may also be necessary to do this since shortening the array length does not @@ -1059,7 +1054,7 @@ impl Array { let mut accumulator = if initial_value.is_undefined() { let mut k_present = false; loop { - if this.has_field(&k.to_string()) { + if this.has_field(k) { k_present = true; break; } @@ -1074,20 +1069,15 @@ impl Array { "reduceRight was called on an empty array and with no initial value", ); } - let result = this.get_field(k.to_string()); + let result = this.get_field(k); k -= 1; result } else { initial_value }; loop { - if this.has_field(&k.to_string()) { - let arguments = [ - accumulator, - this.get_field(k.to_string()), - Value::from(k), - this.clone(), - ]; + if this.has_field(k) { + let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()]; accumulator = interpreter.call(&callback, &Value::undefined(), &arguments)?; /* We keep track of possibly shortened length in order to prevent unnecessary iteration. It may also be necessary to do this since shortening the array length does not diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 51d4e3763c6..fdc8fb787f4 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -14,8 +14,8 @@ use crate::{ builtins::{ object::{Object, ObjectData, PROTOTYPE}, - property::{Attribute, Property, PropertyKey}, - value::{RcString, ResultValue, Value}, + property::{Attribute, Property}, + value::{ResultValue, Value}, Array, }, environment::function_environment_record::BindingStatus, @@ -415,7 +415,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ); // Define length as a property - obj.define_own_property(&PropertyKey::from(RcString::from("length")), length); + obj.define_own_property("length".into(), length); let mut index: usize = 0; while index < len { let val = arguments_list.get(index).expect("Could not get argument"); @@ -424,8 +424,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, ); - obj.properties_mut() - .insert(RcString::from(index.to_string()), prop); + obj.insert_property(index, prop); index += 1; } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index adc570fefdd..d047546caa1 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -15,7 +15,7 @@ use crate::builtins::{ function::make_builtin_fn, - property::Property, + property::{Property, PropertyKey}, value::{ResultValue, Value}, }; use crate::{exec::Interpreter, BoaProfiler}; @@ -54,7 +54,7 @@ impl Json { Some(reviver) if reviver.is_function() => { let mut holder = Value::new_object(None); holder.set_field("", j); - Self::walk(reviver, ctx, &mut holder, Value::from("")) + Self::walk(reviver, ctx, &mut holder, &"".into()) } _ => Ok(j), } @@ -69,25 +69,30 @@ impl Json { /// 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 { + fn walk( + reviver: &Value, + ctx: &mut Interpreter, + holder: &mut Value, + key: &PropertyKey, + ) -> 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 = Self::walk(reviver, ctx, &mut value, Value::from(key.as_str())); + for key in obj.keys() { + let v = Self::walk(reviver, ctx, &mut value, &key); match v { Ok(v) if !v.is_undefined() => { - value.set_field(key.as_str(), v); + value.set_field(key.clone(), v); } Ok(_) => { - value.remove_property(key.as_str()); + value.remove_property(key.clone()); } Err(_v) => {} } } } - ctx.call(reviver, holder, &[key, value]) + ctx.call(reviver, holder, &[key.into(), value]) } /// `JSON.stringify( value[, replacer[, space]] )` @@ -128,7 +133,6 @@ impl Json { .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))) { @@ -148,16 +152,16 @@ impl Json { } 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| { + let fields = replacer_as_object.keys().filter_map(|key| { if key == "length" { None } else { - Some(replacer.get_field(key.to_owned())) + Some(replacer.get_field(key)) } }); for field in fields { if let Some(value) = object - .get_property(&ctx.to_string(&field)?) + .get_property(ctx.to_string(&field)?) .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx))) .transpose()? { diff --git a/boa/src/builtins/object/internal_methods.rs b/boa/src/builtins/object/internal_methods.rs index c86aa6f7353..369104ca6f7 100644 --- a/boa/src/builtins/object/internal_methods.rs +++ b/boa/src/builtins/object/internal_methods.rs @@ -8,7 +8,7 @@ use crate::builtins::{ object::Object, property::{Attribute, Property, PropertyKey}, - value::{same_value, RcString, Value}, + value::{same_value, Value}, }; use crate::BoaProfiler; @@ -72,7 +72,7 @@ impl Object { return true; } if desc.configurable_or(false) { - self.remove_property(&property_key.to_string()); + self.remove_property(&property_key); return true; } @@ -96,9 +96,7 @@ impl Object { return Value::undefined(); } - let parent_obj = Object::from(&parent).expect("Failed to get object"); - - return parent_obj.get(property_key); + return parent.get_field(property_key.clone()); } if desc.is_data_descriptor() { @@ -116,11 +114,11 @@ impl Object { /// [[Set]] /// - pub fn set(&mut self, property_key: &PropertyKey, val: Value) -> bool { + pub fn set(&mut self, property_key: PropertyKey, val: Value) -> bool { let _timer = BoaProfiler::global().start_event("Object::set", "object"); // Fetch property key - let mut own_desc = self.get_own_property(property_key); + let mut own_desc = self.get_own_property(&property_key); // [2] if own_desc.is_none() { let parent = self.get_prototype_of(); @@ -158,10 +156,10 @@ impl Object { /// - [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: &PropertyKey, desc: Property) -> bool { + pub fn define_own_property(&mut self, property_key: PropertyKey, desc: Property) -> bool { let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); - let mut current = self.get_own_property(property_key); + let mut current = self.get_own_property(&property_key); let extensible = self.is_extensible(); // https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor @@ -211,6 +209,7 @@ impl Object { } self.insert_property(property_key, current); + return true; // 7 } else if current.is_data_descriptor() && desc.is_data_descriptor() { // a @@ -280,21 +279,38 @@ impl Object { d }) } - PropertyKey::Symbol(ref symbol) => self - .symbol_properties() - .get(&symbol.hash()) - .map_or_else(Property::empty, |v| { - let mut d = Property::empty(); - if v.is_data_descriptor() { - d.value = v.value.clone(); - } else { - debug_assert!(v.is_accessor_descriptor()); - d.get = v.get.clone(); - d.set = v.set.clone(); - } - d.attribute = v.attribute; - d - }), + PropertyKey::Symbol(ref symbol) => { + self.symbol_properties() + .get(symbol) + .map_or_else(Property::empty, |v| { + let mut d = Property::empty(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.attribute = v.attribute; + d + }) + } + PropertyKey::Index(index) => { + self.indexed_properties + .get(&index) + .map_or_else(Property::empty, |v| { + let mut d = Property::empty(); + if v.is_data_descriptor() { + d.value = v.value.clone(); + } else { + debug_assert!(v.is_accessor_descriptor()); + d.get = v.get.clone(); + d.set = v.set.clone(); + } + d.attribute = v.attribute; + d + }) + } } } @@ -352,17 +368,27 @@ impl Object { /// Helper function for property insertion. #[inline] - pub(crate) fn insert_property(&mut self, name: N, p: Property) + pub(crate) fn insert_property(&mut self, key: Key, property: Property) -> Option where - N: Into, + Key: Into, { - self.properties.insert(name.into(), p); + match key.into() { + PropertyKey::Index(index) => self.indexed_properties.insert(index, property), + PropertyKey::String(ref string) => self.properties.insert(string.clone(), property), + PropertyKey::Symbol(ref symbol) => { + self.symbol_properties.insert(symbol.clone(), property) + } + } } /// Helper function for property removal. #[inline] - pub(crate) fn remove_property(&mut self, name: &str) { - self.properties.remove(name); + pub(crate) fn remove_property(&mut self, key: &PropertyKey) -> Option { + match key { + PropertyKey::Index(index) => self.indexed_properties.remove(&index), + PropertyKey::String(ref string) => self.properties.remove(string), + PropertyKey::Symbol(ref symbol) => self.symbol_properties.remove(symbol), + } } /// Inserts a field in the object `properties` without checking if it's writable. @@ -370,25 +396,16 @@ impl Object { /// 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(crate) fn insert_field(&mut self, name: N, value: Value) -> Option + pub(crate) fn insert_field(&mut self, key: Key, value: Value) -> Option where - N: Into, + Key: Into, { - self.properties.insert( - name.into(), + self.insert_property( + key.into(), Property::data_descriptor( value, Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, ), ) } - - /// 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 77ac66274db..2a487636e42 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -17,7 +17,7 @@ use crate::{ builtins::{ function::Function, map::ordered_map::OrderedMap, - property::Property, + property::{Property, PropertyKey}, value::{RcBigInt, RcString, RcSymbol, ResultValue, Value}, BigInt, RegExp, }, @@ -26,7 +26,9 @@ use crate::{ }; use gc::{Finalize, Trace}; use rustc_hash::FxHashMap; +use std::collections::hash_map; use std::fmt::{Debug, Display, Error, Formatter}; +use std::iter::FusedIterator; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::builtins::value::same_value; @@ -52,10 +54,11 @@ pub static PROTOTYPE: &str = "prototype"; pub struct Object { /// The type of the object. pub data: ObjectData, + indexed_properties: FxHashMap, /// Properties properties: FxHashMap, /// Symbol Properties - symbol_properties: FxHashMap, + symbol_properties: FxHashMap, /// Instance prototype `__proto__`. prototype: Value, /// Some rust object that stores internal state @@ -108,6 +111,7 @@ impl Default for Object { fn default() -> Self { Self { data: ObjectData::Ordinary, + indexed_properties: FxHashMap::default(), properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), @@ -129,6 +133,7 @@ impl Object { Self { data: ObjectData::Function(function), + indexed_properties: FxHashMap::default(), properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype, @@ -154,6 +159,7 @@ impl Object { pub fn boolean(value: bool) -> Self { Self { data: ObjectData::Boolean(value), + indexed_properties: FxHashMap::default(), properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), @@ -166,6 +172,7 @@ impl Object { pub fn number(value: f64) -> Self { Self { data: ObjectData::Number(value), + indexed_properties: FxHashMap::default(), properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), @@ -181,6 +188,7 @@ impl Object { { Self { data: ObjectData::String(value.into()), + indexed_properties: FxHashMap::default(), properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), @@ -193,6 +201,7 @@ impl Object { pub fn bigint(value: RcBigInt) -> Self { Self { data: ObjectData::BigInt(value), + indexed_properties: FxHashMap::default(), properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), @@ -406,12 +415,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 } @@ -433,8 +442,94 @@ impl Object { assert!(prototype.is_null() || prototype.is_object()); self.prototype = prototype } + + pub fn iter(&self) -> Iter<'_> { + Iter { + indexed_properties: self.indexed_properties.iter(), + properties: self.properties.iter(), + } + } + + pub fn keys(&self) -> Keys<'_> { + Keys { inner: self.iter() } + } + + pub fn values(&self) -> Values<'_> { + Values { inner: self.iter() } + } +} + +#[derive(Debug, Clone)] +pub struct Iter<'a> { + indexed_properties: hash_map::Iter<'a, u32, Property>, + properties: hash_map::Iter<'a, RcString, Property>, +} + +impl<'a> Iterator for Iter<'a> { + type Item = (PropertyKey, &'a Property); + fn next(&mut self) -> Option { + if let Some((key, value)) = self.indexed_properties.next() { + Some(((*key).into(), value)) + } else { + let (key, value) = self.properties.next()?; + Some((key.clone().into(), value)) + } + } +} + +impl ExactSizeIterator for Iter<'_> { + #[inline] + fn len(&self) -> usize { + self.indexed_properties.len() + self.properties.len() + } +} + +impl FusedIterator for Iter<'_> {} + +#[derive(Debug, Clone)] +pub struct Keys<'a> { + inner: Iter<'a>, +} + +impl<'a> Iterator for Keys<'a> { + type Item = PropertyKey; + fn next(&mut self) -> Option { + let (key, _) = self.inner.next()?; + Some(key) + } +} + +impl ExactSizeIterator for Keys<'_> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } } +impl FusedIterator for Keys<'_> {} + +#[derive(Debug, Clone)] +pub struct Values<'a> { + inner: Iter<'a>, +} + +impl<'a> Iterator for Values<'a> { + type Item = &'a Property; + fn next(&mut self) -> Option { + let (_, value) = self.inner.next()?; + Some(value) + } +} + +impl ExactSizeIterator for Values<'_> { + #[inline] + fn len(&self) -> usize { + self.inner.len() + } +} + +impl FusedIterator for Values<'_> {} + /// Create a new object. pub fn make_object(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { if let Some(arg) = args.get(0) { diff --git a/boa/src/builtins/property/mod.rs b/boa/src/builtins/property/mod.rs index 0cfaa49b962..9ae980164c8 100644 --- a/boa/src/builtins/property/mod.rs +++ b/boa/src/builtins/property/mod.rs @@ -18,6 +18,7 @@ use crate::builtins::value::rcstring::RcString; use crate::builtins::value::rcsymbol::RcSymbol; use crate::builtins::Value; use gc::{Finalize, Trace}; +use std::convert::TryFrom; use std::fmt; pub mod attribute; @@ -290,33 +291,50 @@ impl<'a> From<&'a Value> for Property { pub enum PropertyKey { String(RcString), Symbol(RcSymbol), + Index(u32), } impl From for PropertyKey { #[inline] fn from(string: RcString) -> PropertyKey { - PropertyKey::String(string) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string) + } } } impl From<&str> for PropertyKey { #[inline] fn from(string: &str) -> PropertyKey { - PropertyKey::String(string.into()) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string.into()) + } } } impl From for PropertyKey { #[inline] fn from(string: String) -> PropertyKey { - PropertyKey::String(string.into()) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string.into()) + } } } impl From> for PropertyKey { #[inline] fn from(string: Box) -> PropertyKey { - PropertyKey::String(string.into()) + if let Ok(index) = string.parse() { + PropertyKey::Index(index) + } else { + PropertyKey::String(string.into()) + } } } @@ -333,19 +351,21 @@ impl fmt::Display for PropertyKey { match self { PropertyKey::String(ref string) => string.fmt(f), PropertyKey::Symbol(ref symbol) => symbol.fmt(f), + PropertyKey::Index(index) => index.fmt(f), } } } -impl From<&PropertyKey> for RcString { - #[inline] - fn from(property_key: &PropertyKey) -> RcString { - match property_key { - PropertyKey::String(ref string) => string.clone(), - PropertyKey::Symbol(ref symbol) => symbol.to_string().into(), - } - } -} +// impl From<&PropertyKey> for RcString { +// #[inline] +// fn from(property_key: &PropertyKey) -> RcString { +// match property_key { +// PropertyKey::String(ref string) => string.clone(), +// PropertyKey::Symbol(ref symbol) => symbol.to_string().into(), +// PropertyKey:: +// } +// } +// } impl From<&PropertyKey> for Value { #[inline] @@ -353,6 +373,13 @@ impl From<&PropertyKey> for Value { match property_key { PropertyKey::String(ref string) => string.clone().into(), PropertyKey::Symbol(ref symbol) => symbol.clone().into(), + PropertyKey::Index(index) => { + if let Ok(integer) = i32::try_from(*index) { + Value::integer(integer) + } else { + Value::number(*index) + } + } } } } @@ -363,6 +390,70 @@ impl From for Value { match property_key { PropertyKey::String(ref string) => string.clone().into(), PropertyKey::Symbol(ref symbol) => symbol.clone().into(), + PropertyKey::Index(index) => { + if let Ok(integer) = i32::try_from(index) { + Value::integer(integer) + } else { + Value::number(index) + } + } + } + } +} + +impl From for PropertyKey { + fn from(value: u8) -> Self { + PropertyKey::Index(value.into()) + } +} + +impl From for PropertyKey { + fn from(value: u16) -> Self { + PropertyKey::Index(value.into()) + } +} + +impl From for PropertyKey { + fn from(value: u32) -> Self { + PropertyKey::Index(value) + } +} + +impl From for PropertyKey { + fn from(value: usize) -> Self { + if let Ok(index) = u32::try_from(value) { + PropertyKey::Index(index) + } else { + PropertyKey::String(RcString::from(value.to_string())) + } + } +} + +impl From for PropertyKey { + fn from(value: isize) -> Self { + if let Ok(index) = u32::try_from(value) { + PropertyKey::Index(index) + } else { + PropertyKey::String(RcString::from(value.to_string())) + } + } +} + +impl From for PropertyKey { + fn from(value: i32) -> Self { + if let Ok(index) = u32::try_from(value) { + PropertyKey::Index(index) + } else { + PropertyKey::String(RcString::from(value.to_string())) + } + } +} + +impl PartialEq<&str> for PropertyKey { + fn eq(&self, other: &&str) -> bool { + match self { + PropertyKey::String(ref string) => string == other, + _ => false, } } } diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 3a0a04872fa..96b824d8d72 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -172,10 +172,7 @@ where fn from(value: Vec) -> Self { let mut array = Object::default(); for (i, item) in value.into_iter().enumerate() { - array.properties_mut().insert( - RcString::from(i.to_string()), - Property::default().value(item.into()), - ); + array.insert_property(i, 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 8b0f1e3a2cf..ac226f21653 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -107,9 +107,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: // which are part of the Array log_string_from( &v.borrow() - .properties() - .get(i.to_string().as_str()) - .unwrap() + .get_own_property(&i.into()) .value .clone() .expect("Could not borrow value"), diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index e78e9226230..444cd25473d 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -239,7 +239,7 @@ impl Value { Self::Object(ref obj) => { if obj.borrow().is_array() { let mut arr: Vec = Vec::new(); - for k in obj.borrow().properties().keys() { + for k in obj.borrow().keys() { if k != "length" { let value = self.get_field(k.to_string()); if value.is_undefined() || value.is_function() || value.is_symbol() { @@ -252,7 +252,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().keys() { let key = k.clone(); let value = self.get_field(k.to_string()); if !value.is_undefined() && !value.is_function() && !value.is_symbol() { @@ -471,30 +471,33 @@ impl Value { /// 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. - pub fn remove_property(&self, field: &str) -> bool { + pub fn remove_property(&self, key: Key) -> bool + where + Key: Into, + { self.as_object_mut() - .and_then(|mut x| x.properties_mut().remove(field)) + .map(|mut x| x.remove_property(&key.into())) .is_some() } /// Resolve the property in the object. /// /// A copy of the Property is returned. - pub fn get_property(&self, field: &str) -> Option { + pub fn get_property(&self, key: Key) -> Option + where + Key: Into, + { + let key = key.into(); 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 match self { - Self::Undefined => None, - Self::String(ref s) if field == "length" => { - Some(Property::default().value(Value::from(s.chars().count()))) - } Self::Object(ref object) => { let object = object.borrow(); - match object.properties().get(field) { - Some(value) => Some(value.clone()), - None => object.prototype().get_property(field), + let property = object.get_own_property(&key); + if !property.is_none() { + return Some(property); } + + object.prototype().get_property(key) } _ => None, } @@ -508,25 +511,23 @@ impl Value { let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); if let Some(ref mut object) = self.as_object_mut() { - // Use value, or walk up the prototype chain - if let Some(property) = object.properties_mut().get_mut(field) { - *property = new_property; - } + object.insert_property(field, new_property); } } /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist /// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]] /// TODO: this function should use the get Value if its set - pub fn get_field(&self, field: F) -> Self + pub fn get_field(&self, key: Key) -> Self where - F: Into, + Key: Into, { let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); - match field.into() { + let key = key.into(); + match key { // Our field will either be a String or a Symbol - Self::String(ref s) => { - match self.get_property(s) { + PropertyKey::String(_) | PropertyKey::Index(_) => { + match self.get_property(key) { Some(prop) => { // If the Property has [[Get]] set to a function, we should run that and return the Value let prop_getter = match prop.get { @@ -548,8 +549,7 @@ impl Value { None => Value::undefined(), } } - Self::Symbol(_) => unimplemented!(), - _ => Value::undefined(), + PropertyKey::Symbol(_) => unimplemented!(), } } @@ -615,9 +615,14 @@ impl Value { /// Check to see if the Value has the field, mainly used by environment records. #[inline] - pub fn has_field(&self, field: &str) -> bool { + pub fn has_field(&self, key: Key) -> bool + where + Key: Into, + { let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); - self.get_property(field).is_some() + self.as_object() + .map(|object| object.has_property(&key.into())) + .unwrap_or(false) } /// Set the field in the value @@ -631,19 +636,15 @@ impl Value { let value = value.into(); let _timer = BoaProfiler::global().start_event("Value::set_field", "value"); if let Self::Object(ref obj) = *self { - if let PropertyKey::String(ref string) = field { + if let PropertyKey::Index(index) = field { if obj.borrow().is_array() { - if let Ok(num) = string.parse::() { - if num > 0 { - let len = i32::from(&self.get_field("length")); - if len < (num + 1) as i32 { - self.set_field("length", num + 1); - } - } + let len = i32::from(&self.get_field("length")) as u32; + if len < index + 1 { + self.set_field("length", index + 1); } } } - obj.borrow_mut().set(&field, value.clone()); + obj.borrow_mut().set(field, value.clone()); } value } @@ -657,14 +658,12 @@ impl Value { } /// Set the property in the value. - pub fn set_property(&self, field: S, property: Property) -> Property + pub fn set_property(&self, key: Key, property: Property) -> Property where - S: Into, + Key: Into, { if let Some(mut object) = self.as_object_mut() { - object - .properties_mut() - .insert(field.into(), property.clone()); + object.insert_property(key.into(), property.clone()); } property } diff --git a/boa/src/environment/global_environment_record.rs b/boa/src/environment/global_environment_record.rs index 312bb670ae3..24c6ce7e7db 100644 --- a/boa/src/environment/global_environment_record.rs +++ b/boa/src/environment/global_environment_record.rs @@ -56,7 +56,7 @@ impl GlobalEnvironmentRecord { pub fn create_global_var_binding(&mut self, name: String, deletion: bool) { let obj_rec = &mut self.object_record; let global_object = &obj_rec.bindings; - let has_property = global_object.has_field(&name); + let has_property = global_object.has_field(name.as_str()); let extensible = global_object.is_extensible(); if !has_property && extensible { obj_rec.create_mutable_binding(name.clone(), deletion); @@ -71,7 +71,7 @@ impl GlobalEnvironmentRecord { pub fn create_global_function_binding(&mut self, name: &str, value: Value, deletion: bool) { let global_object = &mut self.object_record.bindings; - let existing_prop = global_object.get_property(&name); + let existing_prop = global_object.get_property(name); if let Some(prop) = existing_prop { if prop.value.is_none() || prop.configurable_or(false) { let mut property = diff --git a/boa/src/environment/object_environment_record.rs b/boa/src/environment/object_environment_record.rs index e00f5044dbf..876241ad007 100644 --- a/boa/src/environment/object_environment_record.rs +++ b/boa/src/environment/object_environment_record.rs @@ -63,8 +63,6 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { } fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) { - debug_assert!(value.is_object() || value.is_function()); - let mut property = Property::data_descriptor(value, Attribute::ENUMERABLE); property.set_configurable(strict); self.bindings.update_property(name, property); diff --git a/boa/src/exec/call/mod.rs b/boa/src/exec/call/mod.rs index 363c165327a..14599d0cf15 100644 --- a/boa/src/exec/call/mod.rs +++ b/boa/src/exec/call/mod.rs @@ -21,7 +21,10 @@ impl Executable for Call { Node::GetField(ref get_field) => { let obj = get_field.obj().run(interpreter)?; let field = get_field.field().run(interpreter)?; - (obj.clone(), obj.get_field(field.to_string())) + ( + obj.clone(), + obj.get_field(interpreter.to_property_key(&field)?), + ) } _ => ( interpreter.realm().global_obj.clone(), diff --git a/boa/src/exec/field/mod.rs b/boa/src/exec/field/mod.rs index 45e0a2ab424..03e20ba9ca2 100644 --- a/boa/src/exec/field/mod.rs +++ b/boa/src/exec/field/mod.rs @@ -7,7 +7,7 @@ use crate::{ impl Executable for GetConstField { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { let mut obj = self.obj().run(interpreter)?; - if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol { + if obj.get_type() != Type::Object { obj = interpreter.to_object(&obj)?; } @@ -18,11 +18,11 @@ impl Executable for GetConstField { impl Executable for GetField { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { let mut obj = self.obj().run(interpreter)?; - if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol { + if obj.get_type() != Type::Object { obj = interpreter.to_object(&obj)?; } let field = self.field().run(interpreter)?; - Ok(obj.get_field(interpreter.to_string(&field)?)) + Ok(obj.get_field(interpreter.to_property_key(&field)?)) } } diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 0a4b089e918..2ffc21c180c 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -622,7 +622,7 @@ fn unary_delete() { const c = delete a.c + ''; a.b + c "#; - assert_eq!(&exec(delete_not_existing_prop), "\"5false\""); + assert_eq!(&exec(delete_not_existing_prop), "\"5true\""); let delete_field = r#" const a = { b: 5 };