diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index dafb17ec0fb..63936e31718 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -144,17 +144,13 @@ impl Array { let array = Array::array_create(this, 0, Some(prototype), context)?; if !length.is_number() { - array.set_field(0, Value::from(length), context)?; - array.set_field("length", Value::from(1), context)?; + array.set_property(0, DataDescriptor::new(length, Attribute::all())); + array.set_field("length", 1, context)?; } else { if length.is_double() { return context.throw_range_error("Invalid array length"); } - array.set_field( - "length", - Value::from(length.to_u32(context).unwrap()), - context, - )?; + array.set_field("length", length.to_u32(context).unwrap(), context)?; } Ok(array) @@ -176,7 +172,7 @@ impl Array { let array = Array::array_create(this, items_len, Some(prototype), context)?; for (k, item) in items.iter().enumerate() { - array.set_field(k, item.clone(), context)?; + array.set_property(k, DataDescriptor::new(item.clone(), Attribute::all())); } Ok(array) @@ -256,7 +252,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, value, context)?; + array_obj_ptr.set_property(n, DataDescriptor::new(value, Attribute::all())); } Ok(array_obj_ptr) } @@ -272,7 +268,7 @@ impl Array { for (n, value) in add_values.iter().enumerate() { let new_index = orig_length.wrapping_add(n); - array_ptr.set_field(new_index, value, context)?; + array_ptr.set_property(new_index, DataDescriptor::new(value, Attribute::all())); } array_ptr.set_field( @@ -511,13 +507,13 @@ impl Array { let lower_value = this.get_field(lower, context)?; if upper_exists && lower_exists { - this.set_field(upper, lower_value, context)?; - this.set_field(lower, upper_value, context)?; + this.set_property(upper, DataDescriptor::new(lower_value, Attribute::all())); + this.set_property(lower, DataDescriptor::new(upper_value, Attribute::all())); } else if upper_exists { - this.set_field(lower, upper_value, context)?; + this.set_property(lower, DataDescriptor::new(upper_value, Attribute::all())); this.remove_property(upper); } else if lower_exists { - this.set_field(upper, lower_value, context)?; + this.set_property(upper, DataDescriptor::new(lower_value, Attribute::all())); this.remove_property(lower); } } @@ -553,7 +549,7 @@ impl Array { if from_value.is_undefined() { this.remove_property(to); } else { - this.set_field(to, from_value, context)?; + this.set_property(to, DataDescriptor::new(from_value, Attribute::all())); } } @@ -590,15 +586,17 @@ impl Array { if from_value.is_undefined() { this.remove_property(to); } else { - this.set_field(to, from_value, context)?; + this.set_property(to, DataDescriptor::new(from_value, Attribute::all())); } } for j in 0..arg_c { - this.set_field( + this.set_property( j, - args.get(j).expect("Could not get argument").clone(), - context, - )?; + DataDescriptor::new( + args.get(j).expect("Could not get argument").clone(), + Attribute::all(), + ), + ); } } @@ -905,7 +903,7 @@ impl Array { let fin = Self::get_relative_end(context, args.get(2), len)?; for i in start..fin { - this.set_field(i, value.clone(), context)?; + this.set_property(i, DataDescriptor::new(value.clone(), Attribute::all())); } Ok(this.clone()) @@ -968,7 +966,10 @@ impl Array { } let mut new_array_len: i32 = 0; for i in from..from.saturating_add(span) { - new_array.set_field(new_array_len, this.get_field(i, context)?, context)?; + new_array.set_property( + new_array_len, + DataDescriptor::new(this.get_field(i, context)?, Attribute::all()), + ); new_array_len = new_array_len.saturating_add(1); } new_array.set_field("length", Value::from(new_array_len), context)?; diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index c63a81940a5..59be8e2b38a 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -184,7 +184,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("length", length.into()); + obj.ordinary_define_own_property("length", length.into()); let mut index: usize = 0; while index < len { let val = arguments_list.get(index).expect("Could not get argument"); diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 85d766f30e2..3c2313a40d0 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -194,7 +194,7 @@ impl Json { .map(|obj| { let object_to_return = Value::object(Object::default()); for key in obj.borrow().keys() { - let val = obj.get(&key, context)?; + let val = obj.get(&key, obj.clone().into(), context)?; let this_arg = object.clone(); object_to_return.set_property( key.to_owned(), diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index a42369329a1..9e310211279 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -296,7 +296,7 @@ impl Object { let status = obj .as_object() .expect("obj was not an object") - .set_prototype_instance(proto); + .set_prototype_of(proto); // 5. If status is false, throw a TypeError exception. if !status { @@ -413,6 +413,7 @@ impl Object { let tag = o.get( &context.well_known_symbols().to_string_tag_symbol().into(), + o.clone().into(), context, )?; diff --git a/boa/src/context.rs b/boa/src/context.rs index 0c835132d89..7e9080a0ab1 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -531,9 +531,14 @@ impl Context { Function::BuiltIn(body.into(), FunctionFlags::CALLABLE), function_prototype, )); - function.set(PROTOTYPE.into(), proto, self)?; - function.set("length".into(), length.into(), self)?; - function.set("name".into(), name.into(), self)?; + function.set(PROTOTYPE.into(), proto, function.clone().into(), self)?; + function.set( + "length".into(), + length.into(), + function.clone().into(), + self, + )?; + function.set("name".into(), name.into(), function.clone().into(), self)?; Ok(function) } diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index d9b92ea7228..4b682c24c86 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -204,7 +204,7 @@ impl GcObject { // prototype as prototype for the new object // see // see - let proto = self.get(&PROTOTYPE.into(), context)?; + let proto = self.get(&PROTOTYPE.into(), self.clone().into(), context)?; let proto = if proto.is_object() { proto } else { @@ -408,13 +408,19 @@ impl GcObject { let mut attribute = Attribute::empty(); let enumerable_key = PropertyKey::from("enumerable"); - if self.has_property(&enumerable_key) && self.get(&enumerable_key, context)?.to_boolean() { + if self.has_property(&enumerable_key) + && self + .get(&enumerable_key, self.clone().into(), context)? + .to_boolean() + { attribute |= Attribute::ENUMERABLE; } let configurable_key = PropertyKey::from("configurable"); if self.has_property(&configurable_key) - && self.get(&configurable_key, context)?.to_boolean() + && self + .get(&configurable_key, self.clone().into(), context)? + .to_boolean() { attribute |= Attribute::CONFIGURABLE; } @@ -422,14 +428,17 @@ impl GcObject { let mut value = None; let value_key = PropertyKey::from("value"); if self.has_property(&value_key) { - value = Some(self.get(&value_key, context)?); + value = Some(self.get(&value_key, self.clone().into(), context)?); } let mut has_writable = false; let writable_key = PropertyKey::from("writable"); if self.has_property(&writable_key) { has_writable = true; - if self.get(&writable_key, context)?.to_boolean() { + if self + .get(&writable_key, self.clone().into(), context)? + .to_boolean() + { attribute |= Attribute::WRITABLE; } } @@ -437,7 +446,7 @@ impl GcObject { let mut get = None; let get_key = PropertyKey::from("get"); if self.has_property(&get_key) { - let getter = self.get(&get_key, context)?; + let getter = self.get(&get_key, self.clone().into(), context)?; match getter { Value::Object(ref object) if object.is_callable() => { get = Some(object.clone()); @@ -453,7 +462,7 @@ impl GcObject { let mut set = None; let set_key = PropertyKey::from("set"); if self.has_property(&set_key) { - let setter = self.get(&set_key, context)?; + let setter = self.get(&set_key, self.clone().into(), context)?; match setter { Value::Object(ref object) if object.is_callable() => { set = Some(object.clone()); @@ -709,7 +718,7 @@ impl GcObject { K: Into, { let key = key.into(); - let value = self.get(&key, context)?; + let value = self.get(&key, self.clone().into(), context)?; if value.is_null_or_undefined() { return Ok(None); @@ -743,7 +752,10 @@ impl GcObject { // Return ? InstanceofOperator(O, BC). if let Some(object) = value.as_object() { - if let Some(prototype) = self.get(&"prototype".into(), context)?.as_object() { + if let Some(prototype) = self + .get(&"prototype".into(), self.clone().into(), context)? + .as_object() + { let mut object = object.get_prototype_of(); while let Some(object_prototype) = object.as_object() { if GcObject::equals(&prototype, &object_prototype) { @@ -791,7 +803,7 @@ impl GcObject { let key = key.into(); let desc = desc.into(); - let success = self.define_own_property(key.clone(), desc); + let success = self.define_own_property(key.clone(), desc, context)?; if !success { Err(context.construct_type_error(format!("Cannot redefine property: {}", key))) } else { diff --git a/boa/src/object/internal_methods.rs b/boa/src/object/internal_methods.rs index 56b0505ff0d..98f4a48cb25 100644 --- a/boa/src/object/internal_methods.rs +++ b/boa/src/object/internal_methods.rs @@ -71,21 +71,20 @@ impl GcObject { /// `[[Get]]` /// - pub fn get(&self, key: &PropertyKey, context: &mut Context) -> Result { + pub fn get(&self, key: &PropertyKey, receiver: Value, context: &mut Context) -> Result { match self.get_own_property(key) { None => { // parent will either be null or an Object - let parent = self.get_prototype_of(); - if parent.is_null() { - return Ok(Value::undefined()); + if let Some(parent) = self.get_prototype_of().as_object() { + Ok(parent.get(key, receiver, context)?) + } else { + Ok(Value::undefined()) } - - Ok(parent.get_field(key.clone(), context)?) } Some(ref desc) => match desc { PropertyDescriptor::Data(desc) => Ok(desc.value()), PropertyDescriptor::Accessor(AccessorDescriptor { get: Some(get), .. }) => { - get.call(&Value::from(self.clone()), &[], context) + get.call(&receiver, &[], context) } _ => Ok(Value::undefined()), }, @@ -94,22 +93,22 @@ impl GcObject { /// `[[Set]]` /// - pub fn set(&mut self, key: PropertyKey, val: Value, context: &mut Context) -> Result { + pub fn set( + &mut self, + key: PropertyKey, + val: Value, + receiver: Value, + context: &mut Context, + ) -> Result { let _timer = BoaProfiler::global().start_event("Object::set", "object"); // Fetch property key let own_desc = if let Some(desc) = self.get_own_property(&key) { desc + } else if let Some(ref mut parent) = self.get_prototype_of().as_object() { + return Ok(parent.set(key, val, receiver, context)?); } else { - let parent = self.get_prototype_of(); - if !parent.is_null() { - // TODO: come back to this - } - DataDescriptor::new( - Value::undefined(), - Attribute::WRITABLE | Attribute::ENUMERABLE | Attribute::CONFIGURABLE, - ) - .into() + DataDescriptor::new(Value::undefined(), Attribute::all()).into() }; match &own_desc { @@ -117,12 +116,35 @@ impl GcObject { if !desc.writable() { return Ok(false); } - - let desc = DataDescriptor::new(val, own_desc.attributes()).into(); - Ok(self.define_own_property(key, desc)) + if let Some(ref mut receiver) = receiver.as_object() { + if let Some(ref existing_desc) = receiver.get_own_property(&key) { + match existing_desc { + PropertyDescriptor::Accessor(_) => Ok(false), + PropertyDescriptor::Data(existing_data_desc) => { + if !existing_data_desc.writable() { + return Ok(false); + } + receiver.define_own_property( + key, + DataDescriptor::new(val, existing_data_desc.attributes()) + .into(), + context, + ) + } + } + } else { + receiver.define_own_property( + key, + DataDescriptor::new(val, Attribute::all()).into(), + context, + ) + } + } else { + Ok(false) + } } PropertyDescriptor::Accessor(AccessorDescriptor { set: Some(set), .. }) => { - set.call(&Value::from(self.clone()), &[val], context)?; + set.call(&receiver, &[val], context)?; Ok(true) } _ => Ok(false), @@ -135,7 +157,29 @@ impl GcObject { /// - [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, key: K, desc: PropertyDescriptor) -> bool + pub fn define_own_property( + &mut self, + key: K, + desc: PropertyDescriptor, + context: &mut Context, + ) -> Result + where + K: Into, + { + if self.is_array() { + self.array_define_own_property(key, desc, context) + } else { + Ok(self.ordinary_define_own_property(key, desc)) + } + } + + /// Define an own property for an ordinary object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty + pub fn ordinary_define_own_property(&mut self, key: K, desc: PropertyDescriptor) -> bool where K: Into, { @@ -217,6 +261,124 @@ impl GcObject { true } + /// Define an own property for an array. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc + fn array_define_own_property( + &mut self, + key: K, + desc: PropertyDescriptor, + context: &mut Context, + ) -> Result + where + K: Into, + { + let key = key.into(); + match key { + PropertyKey::String(ref s) if s == "length" => { + match desc { + PropertyDescriptor::Accessor(_) => { + return Ok(self.ordinary_define_own_property("length", desc)) + } + PropertyDescriptor::Data(ref d) => { + if d.value().is_undefined() { + return Ok(self.ordinary_define_own_property("length", desc)); + } + let new_len = d.value().to_u32(context)?; + let number_len = d.value().to_number(context)?; + #[allow(clippy::float_cmp)] + if new_len as f64 != number_len { + return Err(context.construct_range_error("bad length for array")); + } + let mut new_len_desc = + PropertyDescriptor::Data(DataDescriptor::new(new_len, d.attributes())); + let old_len_desc = self.get_own_property(&"length".into()).unwrap(); + let old_len_desc = old_len_desc.as_data_descriptor().unwrap(); + let old_len = old_len_desc.value(); + if new_len >= old_len.to_u32(context)? { + return Ok(self.ordinary_define_own_property("length", new_len_desc)); + } + if !old_len_desc.writable() { + return Ok(false); + } + let new_writable = if new_len_desc.attributes().writable() { + true + } else { + let mut new_attributes = new_len_desc.attributes(); + new_attributes.set_writable(true); + new_len_desc = PropertyDescriptor::Data(DataDescriptor::new( + new_len, + new_attributes, + )); + false + }; + if !self.ordinary_define_own_property("length", new_len_desc.clone()) { + return Ok(false); + } + let keys_to_delete = { + let obj = self.borrow(); + let mut keys = obj + .index_property_keys() + .filter(|&&k| k >= new_len) + .cloned() + .collect::>(); + keys.sort_unstable(); + keys + }; + for key in keys_to_delete.into_iter().rev() { + if !self.delete(&key.into()) { + let mut new_len_desc_attribute = new_len_desc.attributes(); + if !new_writable { + new_len_desc_attribute.set_writable(false); + } + let new_len_desc = PropertyDescriptor::Data(DataDescriptor::new( + key + 1, + new_len_desc_attribute, + )); + self.ordinary_define_own_property("length", new_len_desc); + return Ok(false); + } + } + if !new_writable { + let mut new_desc_attr = new_len_desc.attributes(); + new_desc_attr.set_writable(false); + let new_desc = PropertyDescriptor::Data(DataDescriptor::new( + new_len, + new_desc_attr, + )); + self.ordinary_define_own_property("length", new_desc); + } + } + } + Ok(true) + } + PropertyKey::Index(index) => { + let old_len_desc = self.get_own_property(&"length".into()).unwrap(); + let old_len_data_desc = old_len_desc.as_data_descriptor().unwrap(); + let old_len = old_len_data_desc.value().to_u32(context)?; + if index >= old_len && !old_len_data_desc.writable() { + return Ok(false); + } + if self.ordinary_define_own_property(key, desc) { + if index >= old_len && index < std::u32::MAX { + let desc = PropertyDescriptor::Data(DataDescriptor::new( + index + 1, + old_len_data_desc.attributes(), + )); + self.ordinary_define_own_property("length", desc); + } + Ok(true) + } else { + Ok(false) + } + } + _ => Ok(self.ordinary_define_own_property(key, desc)), + } + } + /// The specification returns a Property Descriptor or Undefined. /// /// These are 2 separate types and we can't do that here. @@ -259,23 +421,23 @@ impl GcObject { /// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties #[inline] pub fn define_properties(&mut self, props: Value, context: &mut Context) -> Result<()> { - let props = props.to_object(context)?; + let props = &props.to_object(context)?; let keys = props.own_property_keys(); let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new(); for next_key in keys { if let Some(prop_desc) = props.get_own_property(&next_key) { if prop_desc.enumerable() { - let desc_obj = props.get(&next_key, context)?; + let desc_obj = props.get(&next_key, props.clone().into(), context)?; let desc = desc_obj.to_property_descriptor(context)?; descriptors.push((next_key, desc)); } } } - descriptors.into_iter().for_each(|(p, d)| { - self.define_own_property(p, d); - }); + for (p, d) in descriptors { + self.define_own_property(p, d, context)?; + } Ok(()) } @@ -292,34 +454,32 @@ impl GcObject { /// [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 #[inline] - pub fn set_prototype_of(&mut self, _val: Value) -> bool { - // debug_assert!(val.is_object() || val.is_null()); - // let current = self.prototype.clone(); - // if same_value(¤t, &val) { - // return true; - // } - // if !self.is_extensible() { - // return false; - // } - // let mut p = val.clone(); - // let mut done = false; - // while !done { - // if p.is_null() { - // done = true - // } else if same_value(&Value::from(self.clone()), &p) { - // return false; - // } else { - // let prototype = p - // .as_object() - // .expect("prototype should be null or object") - // .prototype - // .clone(); - // p = prototype; - // } - // } - // self.prototype = val; - // true - todo!("Object.setPropertyOf(obj, prototype)") + pub fn set_prototype_of(&mut self, val: Value) -> bool { + debug_assert!(val.is_object() || val.is_null()); + let current = self.get_prototype_of(); + if same_value(¤t, &val) { + return true; + } + if !self.is_extensible() { + return false; + } + let mut p = val.clone(); + let mut done = false; + while !done { + if p.is_null() { + done = true + } else if same_value(&Value::from(self.clone()), &p) { + return false; + } else { + let prototype = p + .as_object() + .expect("prototype should be null or object") + .get_prototype_of(); + p = prototype; + } + } + self.set_prototype_instance(val); + true } /// Returns either the prototype or null diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 58a7bc997cc..747f91f7f85 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -738,7 +738,7 @@ impl<'context> FunctionBuilder<'context> { .prototype() .into(), ); - let attribute = Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; + let attribute = Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT; if let Some(name) = self.name.take() { object.insert_property("name", name, attribute); } else { diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index cb522892d30..4919c937755 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -3,7 +3,7 @@ use crate::{ exec::Executable, gc::{Finalize, Trace}, - property::{AccessorDescriptor, Attribute, PropertyDescriptor}, + property::{AccessorDescriptor, Attribute, DataDescriptor, PropertyDescriptor}, syntax::ast::node::{MethodDefinitionKind, Node, PropertyDefinition}, Context, Result, Value, }; @@ -79,11 +79,23 @@ impl Executable for Object { for property in self.properties().iter() { match property { PropertyDefinition::Property(key, value) => { - obj.set_field(key.clone(), value.run(context)?, context)?; + obj.set_property( + key.clone(), + PropertyDescriptor::Data(DataDescriptor::new( + value.run(context)?, + Attribute::all(), + )), + ); } PropertyDefinition::MethodDefinition(kind, name, func) => match kind { MethodDefinitionKind::Ordinary => { - obj.set_field(name.clone(), func.run(context)?, context)?; + obj.set_property( + name.clone(), + PropertyDescriptor::Data(DataDescriptor::new( + func.run(context)?, + Attribute::all(), + )), + ); } MethodDefinitionKind::Get => { let set = obj diff --git a/boa/src/value/display.rs b/boa/src/value/display.rs index d554c9292e5..801d8b22357 100644 --- a/boa/src/value/display.rs +++ b/boa/src/value/display.rs @@ -124,12 +124,9 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children: // which are part of the Array log_string_from( &v.get_own_property(&i.into()) - // TODO: do this in a better way "unwrap" - .unwrap() // FIXME: handle accessor descriptors - .as_data_descriptor() - .unwrap() - .value(), + .and_then(|p| p.as_data_descriptor().map(|d| d.value())) + .unwrap_or_default(), print_internals, false, ) diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index ae5cce3ebf6..3bd74922f4b 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -454,7 +454,7 @@ impl Value { { let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); if let Self::Object(ref obj) = *self { - obj.clone().get(&key.into(), context) + obj.clone().get(&key.into(), obj.clone().into(), context) } else { Ok(Value::undefined()) } @@ -483,15 +483,8 @@ 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::Index(index) = key { - if obj.is_array() { - let len = self.get_field("length", context)?.as_number().unwrap() as u32; - if len < index + 1 { - self.set_field("length", index + 1, context)?; - } - } - } - obj.clone().set(key, value.clone(), context)?; + obj.clone() + .set(key, value.clone(), obj.clone().into(), context)?; } Ok(value) } diff --git a/test_ignore.txt b/test_ignore.txt index 418d48333e7..377c213575e 100644 --- a/test_ignore.txt +++ b/test_ignore.txt @@ -53,4 +53,4 @@ S15.1.3.3_A1.3_T1 S15.1.3.4_A1.3_T1 // This one seems to terminate the process somehow: -arg-length-near-integer-limit \ No newline at end of file +arg-length-near-integer-limit