From 4cede758ede1a023112bbb4dbe9a36034157d9b7 Mon Sep 17 00:00:00 2001 From: tofpie <75836434+tofpie@users.noreply.github.com> Date: Thu, 31 Dec 2020 23:22:03 +0100 Subject: [PATCH] Implement property accessors (#987) Co-authored-by: tofpie --- boa/src/builtins/array/array_iterator.rs | 13 +- boa/src/builtins/array/mod.rs | 224 ++++++++++-------- boa/src/builtins/error/eval.rs | 2 +- boa/src/builtins/error/mod.rs | 6 +- boa/src/builtins/error/range.rs | 2 +- boa/src/builtins/error/reference.rs | 2 +- boa/src/builtins/error/syntax.rs | 2 +- boa/src/builtins/error/type.rs | 2 +- boa/src/builtins/error/uri.rs | 2 +- boa/src/builtins/function/mod.rs | 9 +- boa/src/builtins/iterable/mod.rs | 35 +-- boa/src/builtins/json/mod.rs | 37 +-- boa/src/builtins/json/tests.rs | 41 +++- boa/src/builtins/map/map_iterator.rs | 5 +- boa/src/builtins/map/mod.rs | 40 ++-- boa/src/builtins/object/mod.rs | 11 +- boa/src/builtins/object/tests.rs | 1 - boa/src/builtins/regexp/mod.rs | 12 +- boa/src/builtins/string/mod.rs | 2 +- boa/src/builtins/string/string_iterator.rs | 5 +- boa/src/context.rs | 85 +++---- .../environment/object_environment_record.rs | 6 +- boa/src/exec/tests.rs | 2 +- boa/src/object/gcobject.rs | 28 ++- boa/src/object/internal_methods.rs | 29 ++- boa/src/property/mod.rs | 6 +- boa/src/realm.rs | 3 +- boa/src/syntax/ast/node/call/mod.rs | 12 +- .../declaration/arrow_function_decl/mod.rs | 2 +- .../ast/node/declaration/function_decl/mod.rs | 4 +- .../ast/node/declaration/function_expr/mod.rs | 4 +- .../ast/node/field/get_const_field/mod.rs | 2 +- .../syntax/ast/node/field/get_field/mod.rs | 2 +- boa/src/syntax/ast/node/object/mod.rs | 55 +++-- .../syntax/ast/node/operator/assign/mod.rs | 4 +- .../syntax/ast/node/operator/bin_op/mod.rs | 4 +- boa/src/value/display.rs | 25 +- boa/src/value/mod.rs | 42 ++-- boa/src/value/tests.rs | 29 ++- 39 files changed, 449 insertions(+), 348 deletions(-) diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 8a7bf513544..729f012b933 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -50,7 +50,7 @@ impl ArrayIterator { array: Value, kind: ArrayIterationKind, ) -> Result { - let array_iterator = Value::new_object(Some(context.global_object())); + let array_iterator = Value::new_object(context); array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind))); array_iterator .as_object() @@ -77,7 +77,7 @@ impl ArrayIterator { } let len = array_iterator .array - .get_field("length") + .get_field("length", context)? .as_number() .ok_or_else(|| context.construct_type_error("Not an array"))? as u32; @@ -91,13 +91,13 @@ impl ArrayIterator { Ok(create_iter_result_object(context, index.into(), false)) } ArrayIterationKind::Value => { - let element_value = array_iterator.array.get_field(index); + let element_value = array_iterator.array.get_field(index, context)?; Ok(create_iter_result_object(context, element_value, false)) } ArrayIterationKind::KeyAndValue => { - let element_value = array_iterator.array.get_field(index); + let element_value = array_iterator.array.get_field(index, context)?; let result = Array::constructor( - &Value::new_object(Some(context.global_object())), + &Value::new_object(context), &[index.into(), element_value], context, )?; @@ -119,11 +119,10 @@ impl ArrayIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value { - let global = context.global_object(); let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let array_iterator = Value::new_object(Some(global)); + let array_iterator = Value::new_object(context); make_builtin_fn(Self::next, "next", &array_iterator, 0, context); array_iterator .as_object() diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 2cf29826309..dafb17ec0fb 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -17,7 +17,7 @@ use crate::{ builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}, builtins::BuiltIn, gc::GcObject, - object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, + object::{ConstructorBuilder, FunctionBuilder, ObjectData}, property::{Attribute, DataDescriptor}, value::{same_value_zero, IntegerOrInfinity, Value}, BoaProfiler, Context, Result, @@ -144,13 +144,17 @@ impl Array { let array = Array::array_create(this, 0, Some(prototype), context)?; if !length.is_number() { - array.set_field(0, Value::from(length)); - array.set_field("length", Value::from(1)); + array.set_field(0, Value::from(length), context)?; + array.set_field("length", Value::from(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())); + array.set_field( + "length", + Value::from(length.to_u32(context).unwrap()), + context, + )?; } Ok(array) @@ -172,7 +176,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()); + array.set_field(k, item.clone(), context)?; } Ok(array) @@ -213,13 +217,7 @@ impl Array { /// Creates a new `Array` instance. pub(crate) fn new_array(context: &Context) -> Result { - let array = Value::new_object(Some( - &context - .realm() - .environment - .get_global_object() - .expect("Could not get global object"), - )); + let array = Value::new_object(context); array.set_data(ObjectData::Array); array .as_object() @@ -245,7 +243,7 @@ impl Array { let array_obj_ptr = array_obj.clone(); // Wipe existing contents of the array object - let orig_length = array_obj.get_field("length").to_length(context)?; + let orig_length = array_obj.get_field("length", context)?.to_length(context)?; for n in 0..orig_length { array_obj_ptr.remove_property(n); } @@ -258,7 +256,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); + array_obj_ptr.set_field(n, value, context)?; } Ok(array_obj_ptr) } @@ -270,17 +268,18 @@ impl Array { add_values: &[Value], context: &mut Context, ) -> Result { - let orig_length = array_ptr.get_field("length").to_length(context)?; + let orig_length = array_ptr.get_field("length", context)?.to_length(context)?; for (n, value) in add_values.iter().enumerate() { let new_index = orig_length.wrapping_add(n); - array_ptr.set_field(new_index, value); + array_ptr.set_field(new_index, value, context)?; } array_ptr.set_field( "length", Value::from(orig_length.wrapping_add(add_values.len())), - ); + context, + )?; Ok(array_ptr.clone()) } @@ -325,15 +324,17 @@ impl Array { // one) let mut new_values: Vec = Vec::new(); - let this_length = this.get_field("length").to_length(context)?; + let this_length = this.get_field("length", context)?.to_length(context)?; for n in 0..this_length { - new_values.push(this.get_field(n)); + new_values.push(this.get_field(n, context)?); } for concat_array in args { - let concat_length = concat_array.get_field("length").to_length(context)?; + let concat_length = concat_array + .get_field("length", context)? + .to_length(context)?; for n in 0..concat_length { - new_values.push(concat_array.get_field(n)); + new_values.push(concat_array.get_field(n, context)?); } } @@ -354,7 +355,7 @@ impl Array { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push pub(crate) fn push(this: &Value, args: &[Value], context: &mut Context) -> Result { let new_array = Self::add_to_array_object(this, args, context)?; - Ok(new_array.get_field("length")) + Ok(new_array.get_field("length", context)?) } /// `Array.prototype.pop()` @@ -368,15 +369,15 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop pub(crate) fn pop(this: &Value, _: &[Value], context: &mut Context) -> Result { - let curr_length = this.get_field("length").to_length(context)?; + let curr_length = this.get_field("length", context)?.to_length(context)?; if curr_length < 1 { return Ok(Value::undefined()); } let pop_index = curr_length.wrapping_sub(1); - let pop_value: Value = this.get_field(pop_index.to_string()); + let pop_value: Value = this.get_field(pop_index.to_string(), context)?; this.remove_property(pop_index); - this.set_field("length", Value::from(pop_index)); + this.set_field("length", Value::from(pop_index), context)?; Ok(pop_value) } @@ -398,10 +399,10 @@ impl Array { let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").to_length(context)?; + let length = this.get_field("length", context)?.to_length(context)?; for i in 0..length { - let element = this.get_field(i); + let element = this.get_field(i, context)?; let arguments = [element, Value::from(i), this.clone()]; context.call(callback_arg, &this_arg, &arguments)?; @@ -433,9 +434,9 @@ impl Array { }; let mut elem_strs = Vec::new(); - let length = this.get_field("length").to_length(context)?; + let length = this.get_field("length", context)?.to_length(context)?; for n in 0..length { - let elem_str = this.get_field(n).to_string(context)?.to_string(); + let elem_str = this.get_field(n, context)?.to_string(context)?.to_string(); elem_strs.push(elem_str); } @@ -459,14 +460,15 @@ impl Array { let method_name = "join"; let mut arguments = vec![Value::from(",")]; // 2. - let mut method = this.get_field(method_name); + let mut method = this.get_field(method_name, context)?; // 3. if !method.is_function() { - method = context - .global_object() - .get_field("Object") - .get_field(PROTOTYPE) - .get_field("toString"); + let object_prototype: Value = context + .standard_objects() + .object_object() + .prototype() + .into(); + method = object_prototype.get_field("toString", context)?; arguments = Vec::new(); } @@ -495,7 +497,7 @@ impl Array { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse #[allow(clippy::else_if_without_else)] pub(crate) fn reverse(this: &Value, _: &[Value], context: &mut Context) -> Result { - let len = this.get_field("length").to_length(context)?; + let len = this.get_field("length", context)?.to_length(context)?; let middle = len.wrapping_div(2); @@ -505,17 +507,17 @@ impl Array { let upper_exists = this.has_field(upper); let lower_exists = this.has_field(lower); - let upper_value = this.get_field(upper); - let lower_value = this.get_field(lower); + let upper_value = this.get_field(upper, context)?; + let lower_value = this.get_field(lower, context)?; if upper_exists && lower_exists { - this.set_field(upper, lower_value); - this.set_field(lower, upper_value); + this.set_field(upper, lower_value, context)?; + this.set_field(lower, upper_value, context)?; } else if upper_exists { - this.set_field(lower, upper_value); + this.set_field(lower, upper_value, context)?; this.remove_property(upper); } else if lower_exists { - this.set_field(upper, lower_value); + this.set_field(upper, lower_value, context)?; this.remove_property(lower); } } @@ -534,30 +536,30 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift pub(crate) fn shift(this: &Value, _: &[Value], context: &mut Context) -> Result { - let len = this.get_field("length").to_length(context)?; + let len = this.get_field("length", context)?.to_length(context)?; if len == 0 { - this.set_field("length", 0); + this.set_field("length", 0, context)?; return Ok(Value::undefined()); } - let first: Value = this.get_field(0); + let first: Value = this.get_field(0, context)?; for k in 1..len { let from = k; let to = k.wrapping_sub(1); - let from_value = this.get_field(from); + let from_value = this.get_field(from, context)?; if from_value.is_undefined() { this.remove_property(to); } else { - this.set_field(to, from_value); + this.set_field(to, from_value, context)?; } } let final_index = len.wrapping_sub(1); this.remove_property(final_index); - this.set_field("length", Value::from(final_index)); + this.set_field("length", Value::from(final_index), context)?; Ok(first) } @@ -575,7 +577,7 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift pub(crate) fn unshift(this: &Value, args: &[Value], context: &mut Context) -> Result { - let len = this.get_field("length").to_length(context)?; + let len = this.get_field("length", context)?.to_length(context)?; let arg_c = args.len(); @@ -584,20 +586,24 @@ impl Array { let from = k.wrapping_sub(1); let to = k.wrapping_add(arg_c).wrapping_sub(1); - let from_value = this.get_field(from); + let from_value = this.get_field(from, context)?; if from_value.is_undefined() { this.remove_property(to); } else { - this.set_field(to, from_value); + this.set_field(to, from_value, context)?; } } for j in 0..arg_c { - this.set_field(j, args.get(j).expect("Could not get argument").clone()); + this.set_field( + j, + args.get(j).expect("Could not get argument").clone(), + context, + )?; } } let temp = len.wrapping_add(arg_c); - this.set_field("length", Value::from(temp)); + this.set_field("length", Value::from(temp), context)?; Ok(Value::from(temp)) } @@ -627,16 +633,19 @@ impl Array { Value::undefined() }; let mut i = 0; - let max_len = this.get_field("length").to_length(context)?; + let max_len = this.get_field("length", context)?.to_length(context)?; let mut len = max_len; while i < len { - let element = this.get_field(i); + let element = this.get_field(i, context)?; let arguments = [element, Value::from(i), this.clone()]; let result = context.call(callback, &this_arg, &arguments)?; if !result.to_boolean() { return Ok(Value::from(false)); } - len = min(max_len, this.get_field("length").to_length(context)?); + len = min( + max_len, + this.get_field("length", context)?.to_length(context)?, + ); i += 1; } Ok(Value::from(true)) @@ -663,7 +672,7 @@ impl Array { let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").to_length(context)?; + let length = this.get_field("length", context)?.to_length(context)?; if length > 2usize.pow(32) - 1 { return context.throw_range_error("Invalid array length"); @@ -671,16 +680,14 @@ impl Array { let new = Self::new_array(context)?; - let values: Vec = (0..length) + let values = (0..length) .map(|idx| { - let element = this.get_field(idx); + let element = this.get_field(idx, context)?; let args = [element, Value::from(idx), new.clone()]; - context - .call(&callback, &this_val, &args) - .unwrap_or_else(|_| Value::undefined()) + context.call(&callback, &this_val, &args) }) - .collect(); + .collect::>>()?; Self::construct_array(&new, &values, context) } @@ -711,7 +718,7 @@ impl Array { } let search_element = args[0].clone(); - let len = this.get_field("length").to_length(context)?; + let len = this.get_field("length", context)?.to_length(context)?; let mut idx = match args.get(1) { Some(from_idx_ptr) => { @@ -731,7 +738,7 @@ impl Array { }; while idx < len { - let check_element = this.get_field(idx).clone(); + let check_element = this.get_field(idx, context)?.clone(); if check_element.strict_equals(&search_element) { return Ok(Value::from(idx)); @@ -773,7 +780,7 @@ impl Array { let search_element = args[0].clone(); let len: isize = this - .get_field("length") + .get_field("length", context)? .to_length(context)? .try_into() .map_err(interror_to_value)?; @@ -794,7 +801,7 @@ impl Array { }; while idx >= 0 { - let check_element = this.get_field(idx).clone(); + let check_element = this.get_field(idx, context)?.clone(); if check_element.strict_equals(&search_element) { return Ok(Value::from(i32::try_from(idx).map_err(interror_to_value)?)); @@ -826,9 +833,9 @@ impl Array { } let callback = &args[0]; let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - let len = this.get_field("length").to_length(context)?; + let len = this.get_field("length", context)?.to_length(context)?; for i in 0..len { - let element = this.get_field(i); + let element = this.get_field(i, context)?; let arguments = [element.clone(), Value::from(i), this.clone()]; let result = context.call(callback, &this_arg, &arguments)?; if result.to_boolean() { @@ -861,10 +868,10 @@ impl Array { let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").to_length(context)?; + let length = this.get_field("length", context)?.to_length(context)?; for i in 0..length { - let element = this.get_field(i); + let element = this.get_field(i, context)?; let arguments = [element, Value::from(i), this.clone()]; let result = context.call(predicate_arg, &this_arg, &arguments)?; @@ -890,7 +897,7 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill pub(crate) fn fill(this: &Value, args: &[Value], context: &mut Context) -> Result { - let len = this.get_field("length").to_length(context)?; + let len = this.get_field("length", context)?.to_length(context)?; let default_value = Value::undefined(); let value = args.get(0).unwrap_or(&default_value); @@ -898,7 +905,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()); + this.set_field(i, value.clone(), context)?; } Ok(this.clone()) @@ -921,10 +928,10 @@ impl Array { ) -> Result { let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").to_length(context)?; + let length = this.get_field("length", context)?.to_length(context)?; for idx in 0..length { - let check_element = this.get_field(idx).clone(); + let check_element = this.get_field(idx, context)?.clone(); if same_value_zero(&check_element, &search_element) { return Ok(Value::from(true)); @@ -951,17 +958,20 @@ impl Array { pub(crate) fn slice(this: &Value, args: &[Value], context: &mut Context) -> Result { let new_array = Self::new_array(context)?; - let len = this.get_field("length").to_length(context)?; + let len = this.get_field("length", context)?.to_length(context)?; let from = Self::get_relative_start(context, args.get(0), len)?; let to = Self::get_relative_end(context, args.get(1), len)?; let span = max(to.saturating_sub(from), 0); + if span > 2usize.pow(32) - 1 { + return context.throw_range_error("Invalid array length"); + } 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)); + new_array.set_field(new_array_len, this.get_field(i, context)?, context)?; new_array_len = new_array_len.saturating_add(1); } - new_array.set_field("length", Value::from(new_array_len)); + new_array.set_field("length", Value::from(new_array_len), context)?; Ok(new_array) } @@ -986,27 +996,26 @@ impl Array { let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").to_length(context)?; + let length = this.get_field("length", context)?.to_length(context)?; let new = Self::new_array(context)?; let values = (0..length) - .filter_map(|idx| { - let element = this.get_field(idx); + .map(|idx| { + let element = this.get_field(idx, context)?; let args = [element.clone(), Value::from(idx), new.clone()]; - let callback_result = context - .call(&callback, &this_val, &args) - .unwrap_or_else(|_| Value::undefined()); + let callback_result = context.call(&callback, &this_val, &args)?; if callback_result.to_boolean() { - Some(element) + Ok(Some(element)) } else { - None + Ok(None) } }) - .collect::>(); + .collect::>>>()?; + let values = values.into_iter().filter_map(|v| v).collect::>(); Self::construct_array(&new, &values, context) } @@ -1039,17 +1048,20 @@ impl Array { Value::undefined() }; let mut i = 0; - let max_len = this.get_field("length").to_length(context)?; + let max_len = this.get_field("length", context)?.to_length(context)?; let mut len = max_len; while i < len { - let element = this.get_field(i); + let element = this.get_field(i, context)?; let arguments = [element, Value::from(i), this.clone()]; let result = context.call(callback, &this_arg, &arguments)?; if result.to_boolean() { return Ok(Value::from(true)); } // the length of the array must be updated because the callback can mutate it. - len = min(max_len, this.get_field("length").to_length(context)?); + len = min( + max_len, + this.get_field("length", context)?.to_length(context)?, + ); i += 1; } Ok(Value::from(false)) @@ -1073,7 +1085,7 @@ impl Array { _ => return context.throw_type_error("Reduce was called without a callback"), }; let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined); - let mut length = this.get_field("length").to_length(context)?; + let mut length = this.get_field("length", context)?.to_length(context)?; if length == 0 && initial_value.is_undefined() { return context .throw_type_error("Reduce was called on an empty array and with no initial value"); @@ -1093,7 +1105,7 @@ impl Array { "Reduce was called on an empty array and with no initial value", ); } - let result = this.get_field(k); + let result = this.get_field(k, context)?; k += 1; result } else { @@ -1101,12 +1113,20 @@ impl Array { }; while k < length { if this.has_field(k) { - let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()]; + let arguments = [ + accumulator, + this.get_field(k, context)?, + Value::from(k), + this.clone(), + ]; accumulator = context.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 delete array elements. See: https://github.com/boa-dev/boa/issues/557 */ - length = min(length, this.get_field("length").to_length(context)?); + length = min( + length, + this.get_field("length", context)?.to_length(context)?, + ); } k += 1; } @@ -1135,7 +1155,7 @@ impl Array { _ => return context.throw_type_error("reduceRight was called without a callback"), }; let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined); - let mut length = this.get_field("length").to_length(context)?; + let mut length = this.get_field("length", context)?.to_length(context)?; if length == 0 { return if initial_value.is_undefined() { context.throw_type_error( @@ -1165,7 +1185,7 @@ impl Array { "reduceRight was called on an empty array and with no initial value", ); } - let result = this.get_field(k); + let result = this.get_field(k, context)?; k = k.overflowing_sub(1).0; result } else { @@ -1174,12 +1194,20 @@ impl Array { // usize::MAX is bigger than the maximum array size so we can use it check for integer undeflow while k != usize::MAX { if this.has_field(k) { - let arguments = [accumulator, this.get_field(k), Value::from(k), this.clone()]; + let arguments = [ + accumulator, + this.get_field(k, context)?, + Value::from(k), + this.clone(), + ]; accumulator = context.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 delete array elements. See: https://github.com/boa-dev/boa/issues/557 */ - length = min(length, this.get_field("length").to_length(context)?); + length = min( + length, + this.get_field("length", context)?.to_length(context)?, + ); // move k to the last defined element if necessary or return if the length was set to 0 if k >= length { diff --git a/boa/src/builtins/error/eval.rs b/boa/src/builtins/error/eval.rs index fb2d4a6b548..e1ca438670f 100644 --- a/boa/src/builtins/error/eval.rs +++ b/boa/src/builtins/error/eval.rs @@ -62,7 +62,7 @@ impl EvalError { context: &mut Context, ) -> Result { if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?); + this.set_field("message", message.to_string(context)?, context)?; } // This value is used by console.log and other routines to match Object type diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index a6e4a0af981..505d51c9d01 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -79,7 +79,7 @@ impl Error { context: &mut Context, ) -> Result { if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?); + this.set_field("message", message.to_string(context)?, context)?; } // This value is used by console.log and other routines to match Object type @@ -103,7 +103,7 @@ impl Error { if !this.is_object() { return context.throw_type_error("'this' is not an Object"); } - let name = this.get_field("name"); + let name = this.get_field("name", context)?; let name_to_string; let name = if name.is_undefined() { "Error" @@ -112,7 +112,7 @@ impl Error { name_to_string.as_str() }; - let message = this.get_field("message"); + let message = this.get_field("message", context)?; let message_to_string; let message = if message.is_undefined() { "" diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 55e7776894f..0a9211cddf3 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -60,7 +60,7 @@ impl RangeError { context: &mut Context, ) -> Result { if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?); + this.set_field("message", message.to_string(context)?, context)?; } // This value is used by console.log and other routines to match Object type diff --git a/boa/src/builtins/error/reference.rs b/boa/src/builtins/error/reference.rs index d77a9775bdf..c606d71e696 100644 --- a/boa/src/builtins/error/reference.rs +++ b/boa/src/builtins/error/reference.rs @@ -59,7 +59,7 @@ impl ReferenceError { context: &mut Context, ) -> Result { if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?); + this.set_field("message", message.to_string(context)?, context)?; } // This value is used by console.log and other routines to match Object type diff --git a/boa/src/builtins/error/syntax.rs b/boa/src/builtins/error/syntax.rs index 92ca95d1500..b61f9fde3a0 100644 --- a/boa/src/builtins/error/syntax.rs +++ b/boa/src/builtins/error/syntax.rs @@ -62,7 +62,7 @@ impl SyntaxError { context: &mut Context, ) -> Result { if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?); + this.set_field("message", message.to_string(context)?, context)?; } // This value is used by console.log and other routines to match Object type diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index f450c607e1f..497dc89154a 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -65,7 +65,7 @@ impl TypeError { context: &mut Context, ) -> Result { if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?); + this.set_field("message", message.to_string(context)?, context)?; } // This value is used by console.log and other routines to match Object type diff --git a/boa/src/builtins/error/uri.rs b/boa/src/builtins/error/uri.rs index 0fdbc407b77..2d8a944b119 100644 --- a/boa/src/builtins/error/uri.rs +++ b/boa/src/builtins/error/uri.rs @@ -61,7 +61,7 @@ impl UriError { context: &mut Context, ) -> Result { if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?); + this.set_field("message", message.to_string(context)?, context)?; } // This value is used by console.log and other routines to match Object type diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 016287475af..286666ff162 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -232,9 +232,10 @@ pub fn make_builtin_fn( let mut function = Object::function( Function::BuiltIn(function.into(), FunctionFlags::CALLABLE), interpreter - .global_object() - .get_field("Function") - .get_field("prototype"), + .standard_objects() + .function_object() + .prototype() + .into(), ); function.insert_property("length", length, Attribute::all()); @@ -304,7 +305,7 @@ impl BuiltInFunctionObject { return context.call(this, &this_arg, &[]); } let arg_list = context - .extract_array_properties(&arg_array) + .extract_array_properties(&arg_array)? .map_err(|()| arg_array)?; // TODO?: 5. PrepareForTailCall context.call(this, &this_arg, &arg_list) diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index 0609d2b3569..d99ee418cd6 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -58,7 +58,7 @@ impl IteratorPrototypes { /// /// Generates an object supporting the IteratorResult interface. pub fn create_iter_result_object(context: &mut Context, value: Value, done: bool) -> Value { - let object = Value::new_object(Some(context.global_object())); + let object = Value::new_object(context); // TODO: Fix attributes of value and done let value_property = DataDescriptor::new(value, Attribute::all()); let done_property = DataDescriptor::new(done, Attribute::all()); @@ -69,16 +69,16 @@ pub fn create_iter_result_object(context: &mut Context, value: Value, done: bool /// Get an iterator record pub fn get_iterator(context: &mut Context, iterable: Value) -> Result { - // TODO: Fix the accessor handling - let iterator_function = iterable - .get_property(context.well_known_symbols().iterator_symbol()) - .map(|p| p.as_data_descriptor().unwrap().value()) - .ok_or_else(|| context.construct_type_error("Not an iterable"))?; + let iterator_function = + iterable.get_field(context.well_known_symbols().iterator_symbol(), context)?; + if iterator_function.is_null_or_undefined() { + return Err(context.construct_type_error("Not an iterable")); + } let iterator_object = context.call(&iterator_function, &iterable, &[])?; - let next_function = iterator_object - .get_property("next") - .map(|p| p.as_data_descriptor().unwrap().value()) - .ok_or_else(|| context.construct_type_error("Could not find property `next`"))?; + let next_function = iterator_object.get_field("next", context)?; + if next_function.is_null_or_undefined() { + return Err(context.construct_type_error("Could not find property `next`")); + } Ok(IteratorRecord::new(iterator_object, next_function)) } @@ -125,18 +125,9 @@ impl IteratorRecord { /// [spec]: https://tc39.es/ecma262/#sec-iteratornext pub(crate) fn next(&self, context: &mut Context) -> Result { let next = context.call(&self.next_function, &self.iterator_object, &[])?; - // FIXME: handle accessor descriptors - let done = next - .get_property("done") - .map(|p| p.as_data_descriptor().unwrap().value()) - .and_then(|v| v.as_boolean()) - .ok_or_else(|| context.construct_type_error("Could not find property `done`"))?; - - // FIXME: handle accessor descriptors - let next_result = next - .get_property("value") - .map(|p| p.as_data_descriptor().unwrap().value()) - .unwrap_or_default(); + let done = next.get_field("done", context)?.to_boolean(); + + let next_result = next.get_field("value", context)?; Ok(IteratorResult::new(next_result, done)) } } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 000b3378fa1..85d766f30e2 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -13,6 +13,7 @@ //! [json]: https://www.json.org/json-en.html //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON +use crate::object::Object; use crate::{ builtins::BuiltIn, object::ObjectInitializer, @@ -74,8 +75,8 @@ impl Json { let j = Value::from_json(json, context); match args.get(1) { Some(reviver) if reviver.is_function() => { - let mut holder = Value::new_object(None); - holder.set_field("", j); + let mut holder = Value::object(Object::default()); + holder.set_field("", j, context)?; Self::walk(reviver, context, &mut holder, &PropertyKey::from("")) } _ => Ok(j), @@ -97,7 +98,7 @@ impl Json { holder: &mut Value, key: &PropertyKey, ) -> Result { - let value = holder.get_field(key.clone()); + let value = holder.get_field(key.clone(), context)?; if let Value::Object(ref object) = value { let keys: Vec<_> = object.borrow().keys().collect(); @@ -106,7 +107,7 @@ impl Json { let v = Self::walk(reviver, context, &mut value.clone(), &key); match v { Ok(v) if !v.is_undefined() => { - value.set_field(key, v); + value.set_field(key, v, context)?; } Ok(_) => { value.remove_property(key); @@ -191,13 +192,9 @@ impl Json { object .as_object() .map(|obj| { - let object_to_return = Value::new_object(None); - for (key, val) in obj - .borrow() - .iter() - // FIXME: handle accessor descriptors - .map(|(k, v)| (k, v.as_data_descriptor().unwrap().value())) - { + let object_to_return = Value::object(Object::default()); + for key in obj.borrow().keys() { + let val = obj.get(&key, context)?; let this_arg = object.clone(); object_to_return.set_property( key.to_owned(), @@ -224,16 +221,20 @@ impl Json { if key == "length" { None } else { - Some(replacer.get_field(key)) + Some( + replacer + .get_property(key) + .as_ref() + .and_then(|p| p.as_data_descriptor()) + .map(|d| d.value()) + .unwrap_or_else(Value::undefined), + ) } }); for field in fields { - if let Some(value) = object - .get_property(field.to_string(context)?) - // FIXME: handle accessor descriptors - .map(|prop| prop.as_data_descriptor().unwrap().value().to_json(context)) - .transpose()? - { + let v = object.get_field(field.to_string(context)?, context)?; + if !v.is_undefined() { + let value = v.to_json(context)?; obj_to_return.insert(field.to_string(context)?.to_string(), value); } } diff --git a/boa/src/builtins/json/tests.rs b/boa/src/builtins/json/tests.rs index c08c5bba84e..abf4f5a1144 100644 --- a/boa/src/builtins/json/tests.rs +++ b/boa/src/builtins/json/tests.rs @@ -1,4 +1,4 @@ -use crate::{forward, forward_val, object::PROTOTYPE, value::same_value, Context}; +use crate::{forward, forward_val, value::same_value, Context, Value}; #[test] fn json_sanity() { @@ -341,19 +341,35 @@ fn json_parse_array_with_reviver() { ) .unwrap(); assert_eq!( - result.get_field("0").to_number(&mut context).unwrap() as u8, + result + .get_field("0", &mut context) + .unwrap() + .to_number(&mut context) + .unwrap() as u8, 2u8 ); assert_eq!( - result.get_field("1").to_number(&mut context).unwrap() as u8, + result + .get_field("1", &mut context) + .unwrap() + .to_number(&mut context) + .unwrap() as u8, 4u8 ); assert_eq!( - result.get_field("2").to_number(&mut context).unwrap() as u8, + result + .get_field("2", &mut context) + .unwrap() + .to_number(&mut context) + .unwrap() as u8, 6u8 ); assert_eq!( - result.get_field("3").to_number(&mut context).unwrap() as u8, + result + .get_field("3", &mut context) + .unwrap() + .to_number(&mut context) + .unwrap() as u8, 8u8 ); } @@ -405,14 +421,13 @@ fn json_parse_sets_prototypes() { .as_object() .unwrap() .prototype_instance(); - let global_object_prototype = context - .global_object() - .get_field("Object") - .get_field(PROTOTYPE); - let global_array_prototype = context - .global_object() - .get_field("Array") - .get_field(PROTOTYPE); + let global_object_prototype: Value = context + .standard_objects() + .object_object() + .prototype() + .into(); + let global_array_prototype: Value = + context.standard_objects().array_object().prototype().into(); assert_eq!( same_value(&object_prototype, &global_object_prototype), true diff --git a/boa/src/builtins/map/map_iterator.rs b/boa/src/builtins/map/map_iterator.rs index 900484a2586..a02456d34d7 100644 --- a/boa/src/builtins/map/map_iterator.rs +++ b/boa/src/builtins/map/map_iterator.rs @@ -50,7 +50,7 @@ impl MapIterator { map: Value, kind: MapIterationKind, ) -> Result { - let map_iterator = Value::new_object(Some(context.global_object())); + let map_iterator = Value::new_object(context); map_iterator.set_data(ObjectData::MapIterator(Self::new(map, kind))); map_iterator .as_object() @@ -139,11 +139,10 @@ impl MapIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value { - let global = context.global_object(); let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let map_iterator = Value::new_object(Some(global)); + let map_iterator = Value::new_object(context); make_builtin_fn(Self::next, "next", &map_iterator, 0, context); map_iterator .as_object() diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index 253d3647920..f8bf5542cb7 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -77,8 +77,9 @@ impl Map { // Set Prototype let prototype = context .global_object() - .get_field("Map") - .get_field(PROTOTYPE); + .clone() + .get_field("Map", context)? + .get_field(PROTOTYPE, context)?; this.as_object() .expect("this is map object") @@ -96,14 +97,15 @@ impl Map { map } else if object.is_array() { let mut map = OrderedMap::new(); - let len = args[0].get_field("length").to_integer(context)? as i32; + let len = args[0].get_field("length", context)?.to_integer(context)? as i32; for i in 0..len { - let val = &args[0].get_field(i.to_string()); - let (key, value) = Self::get_key_value(val).ok_or_else(|| { - context.construct_type_error( - "iterable for Map should have array-like objects", - ) - })?; + let val = &args[0].get_field(i, context)?; + let (key, value) = + Self::get_key_value(val, context)?.ok_or_else(|| { + context.construct_type_error( + "iterable for Map should have array-like objects", + ) + })?; map.insert(key, value); } map @@ -354,17 +356,21 @@ impl Map { } /// Helper function to get a key-value pair from an array. - fn get_key_value(value: &Value) -> Option<(Value, Value)> { + fn get_key_value(value: &Value, context: &mut Context) -> Result> { if let Value::Object(object) = value { if object.is_array() { - let (key, value) = match value.get_field("length").as_number().unwrap() as i32 { - 0 => (Value::Undefined, Value::Undefined), - 1 => (value.get_field("0"), Value::Undefined), - _ => (value.get_field("0"), value.get_field("1")), - }; - return Some((key, value)); + let (key, value) = + match value.get_field("length", context)?.as_number().unwrap() as i32 { + 0 => (Value::Undefined, Value::Undefined), + 1 => (value.get_field("0", context)?, Value::Undefined), + _ => ( + value.get_field("0", context)?, + value.get_field("1", context)?, + ), + }; + return Ok(Some((key, value))); } } - None + Ok(None) } } diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index c13eff66afb..09af264cc6c 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -83,9 +83,7 @@ impl Object { return Ok(arg.to_object(context)?.into()); } } - let global = context.global_object(); - - Ok(Value::new_object(Some(global))) + Ok(Value::new_object(context)) } /// `Object.create( proto, [propertiesObject] )` @@ -301,7 +299,7 @@ impl Object { return context.throw_type_error("Property description must be an object"); }; obj.set_property(prop, desc); - Ok(Value::undefined()) + Ok(obj.clone()) } /// `Object.defineProperties( proto, [propertiesObject] )` @@ -359,7 +357,10 @@ impl Object { } }; - let tag = o.get(&context.well_known_symbols().to_string_tag_symbol().into()); + let tag = o.get( + &context.well_known_symbols().to_string_tag_symbol().into(), + context, + )?; let tag_str = tag.as_string().map(|s| s.as_str()).unwrap_or(builtin_tag); diff --git a/boa/src/builtins/object/tests.rs b/boa/src/builtins/object/tests.rs index e59f7398339..3ef47a30d23 100644 --- a/boa/src/builtins/object/tests.rs +++ b/boa/src/builtins/object/tests.rs @@ -92,7 +92,6 @@ fn object_is() { assert_eq!(forward(&mut context, "Object.is()"), "true"); assert_eq!(forward(&mut context, "Object.is(undefined)"), "true"); assert!(context.global_object().is_global()); - assert!(!context.global_object().get_field("Object").is_global()); } #[test] fn object_has_own_property() { diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 934251a8fe0..070695d20e3 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -314,7 +314,7 @@ impl RegExp { .get(0) .expect("could not get argument") .to_string(context)?; - let mut last_index = this.get_field("lastIndex").to_index(context)?; + let mut last_index = this.get_field("lastIndex", context)?.to_index(context)?; let result = if let Some(object) = this.as_object() { let object = object.borrow(); let regex = object.as_regexp().unwrap(); @@ -334,7 +334,7 @@ impl RegExp { } else { panic!("object is not a regexp") }; - this.set_field("lastIndex", Value::from(last_index)); + this.set_field("lastIndex", Value::from(last_index), context)?; result } @@ -355,7 +355,7 @@ impl RegExp { .get(0) .expect("could not get argument") .to_string(context)?; - let mut last_index = this.get_field("lastIndex").to_index(context)?; + let mut last_index = this.get_field("lastIndex", context)?.to_index(context)?; let result = if let Some(object) = this.as_object() { let object = object.borrow(); let regex = object.as_regexp().unwrap(); @@ -391,7 +391,7 @@ impl RegExp { } else { panic!("object is not a regexp") }; - this.set_field("lastIndex", Value::from(last_index)); + this.set_field("lastIndex", Value::from(last_index), context)?; result } @@ -463,7 +463,7 @@ impl RegExp { /// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@matchAll // TODO: it's returning an array, it should return an iterator - pub(crate) fn match_all(this: &Value, arg_str: String) -> Result { + pub(crate) fn match_all(this: &Value, arg_str: String, context: &mut Context) -> Result { let matches = if let Some(object) = this.as_object() { let object = object.borrow(); let regex = object.as_regexp().unwrap(); @@ -499,7 +499,7 @@ impl RegExp { let length = matches.len(); let result = Value::from(matches); - result.set_field("length", Value::from(length)); + result.set_field("length", Value::from(length), context)?; result.set_data(ObjectData::Array); Ok(result) diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 8edf12ba184..a9bdc22f29b 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -1219,7 +1219,7 @@ impl String { ), }?; - RegExp::match_all(&re, this.to_string(context)?.to_string()) + RegExp::match_all(&re, this.to_string(context)?.to_string(), context) } pub(crate) fn iterator(this: &Value, _: &[Value], context: &mut Context) -> Result { diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs index 12bb25f6629..e1a56748015 100644 --- a/boa/src/builtins/string/string_iterator.rs +++ b/boa/src/builtins/string/string_iterator.rs @@ -23,7 +23,7 @@ impl StringIterator { } pub fn create_string_iterator(context: &mut Context, string: Value) -> Result { - let string_iterator = Value::new_object(Some(context.global_object())); + let string_iterator = Value::new_object(context); string_iterator.set_data(ObjectData::StringIterator(Self::new(string))); string_iterator .as_object() @@ -70,11 +70,10 @@ impl StringIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value { - let global = context.global_object(); let _timer = BoaProfiler::global().start_event("String Iterator", "init"); // Create prototype - let array_iterator = Value::new_object(Some(global)); + let array_iterator = Value::new_object(context); make_builtin_fn(Self::next, "next", &array_iterator, 0, context); array_iterator .as_object() diff --git a/boa/src/context.rs b/boa/src/context.rs index b5a05e46fbd..c56e7170d73 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -316,10 +316,7 @@ impl Context { /// Construct an empty object. #[inline] pub fn construct_object(&self) -> GcObject { - let object_prototype = self - .global_object() - .get_field("Object") - .get_field(PROTOTYPE); + let object_prototype: Value = self.standard_objects().object_object().prototype().into(); GcObject::new(Object::create(object_prototype)) } @@ -480,18 +477,16 @@ impl Context { params: P, body: B, flags: FunctionFlags, - ) -> Value + ) -> Result where P: Into>, B: Into, { - let function_prototype = self - .global_object() - .get_field("Function") - .get_field(PROTOTYPE); + let function_prototype: Value = + self.standard_objects().function_object().prototype().into(); // Every new function has a prototype property pre-made - let proto = Value::new_object(Some(self.global_object())); + let proto = Value::new_object(self); let params = params.into(); let params_len = params.len(); @@ -507,12 +502,12 @@ impl Context { let val = Value::from(new_func); // Set constructor field to the newly created Value (function object) - proto.set_field("constructor", val.clone()); + proto.set_field("constructor", val.clone(), self)?; - val.set_field(PROTOTYPE, proto); - val.set_field("length", Value::from(params_len)); + val.set_field(PROTOTYPE, proto, self)?; + val.set_field("length", Value::from(params_len), self)?; - val + Ok(val) } /// Create a new builin function. @@ -522,20 +517,17 @@ impl Context { length: usize, body: NativeFunction, ) -> Result { - let function_prototype = self - .global_object() - .get_field("Function") - .get_field(PROTOTYPE); + let function_prototype: Value = self.standard_objects().object_object().prototype().into(); // Every new function has a prototype property pre-made - let proto = Value::new_object(Some(self.global_object())); + let proto = Value::new_object(self); let mut function = GcObject::new(Object::function( Function::BuiltIn(body.into(), FunctionFlags::CALLABLE), function_prototype, )); - function.set(PROTOTYPE.into(), proto); - function.set("length".into(), length.into()); - function.set("name".into(), name.into()); + function.set(PROTOTYPE.into(), proto, self)?; + function.set("length".into(), length.into(), self)?; + function.set("name".into(), name.into(), self)?; Ok(function) } @@ -549,7 +541,11 @@ impl Context { body: NativeFunction, ) -> Result<()> { let function = self.create_builtin_function(name, length, body)?; - self.global_object().set_field(name, function); + let global = self.global_object(); + global + .as_object() + .unwrap() + .insert_property(name, function, Attribute::all()); Ok(()) } @@ -557,15 +553,18 @@ impl Context { /// /// This is useful for the spread operator, for any other object an `Err` is returned /// TODO: Not needed for spread of arrays. Check in the future for Map and remove if necessary - pub(crate) fn extract_array_properties(&mut self, value: &Value) -> StdResult, ()> { + pub(crate) fn extract_array_properties( + &mut self, + value: &Value, + ) -> Result, ()>> { if let Value::Object(ref x) = value { // Check if object is array if let ObjectData::Array = x.borrow().data { - let length = value.get_field("length").as_number().unwrap() as i32; + let length = value.get_field("length", self)?.as_number().unwrap() as i32; let values = (0..length) - .map(|idx| value.get_field(idx.to_string())) - .collect(); - return Ok(values); + .map(|idx| value.get_field(idx, self)) + .collect::>>()?; + return Ok(Ok(values)); } // Check if object is a Map else if let ObjectData::Map(ref map) = x.borrow().data { @@ -573,34 +572,28 @@ impl Context { .iter() .map(|(key, value)| { // Construct a new array containing the key-value pair - let array = Value::new_object(Some( - &self - .realm() - .environment - .get_global_object() - .expect("Could not get global object"), - )); + let array = Value::new_object(self); array.set_data(ObjectData::Array); array.as_object().expect("object").set_prototype_instance( self.realm() .environment .get_binding_value("Array") .expect("Array was not initialized") - .get_field(PROTOTYPE), + .get_field(PROTOTYPE, self)?, ); - array.set_field("0", key); - array.set_field("1", value); - array.set_field("length", Value::from(2)); - array + array.set_field(0, key, self)?; + array.set_field(1, value, self)?; + array.set_field("length", Value::from(2), self)?; + Ok(array) }) - .collect(); - return Ok(values); + .collect::>>()?; + return Ok(Ok(values)); } - return Err(()); + return Ok(Err(())); } - Err(()) + Ok(Err(())) } /// @@ -626,11 +619,11 @@ impl Context { Node::GetConstField(ref get_const_field_node) => Ok(get_const_field_node .obj() .run(self)? - .set_field(get_const_field_node.field(), value)), + .set_field(get_const_field_node.field(), value, self)?), Node::GetField(ref get_field) => { let field = get_field.field().run(self)?; let key = field.to_property_key(self)?; - Ok(get_field.obj().run(self)?.set_field(key, value)) + Ok(get_field.obj().run(self)?.set_field(key, value, self)?) } _ => panic!("TypeError: invalid assignment to {}", node), } diff --git a/boa/src/environment/object_environment_record.rs b/boa/src/environment/object_environment_record.rs index eeb2b49bbc0..c694d8b782f 100644 --- a/boa/src/environment/object_environment_record.rs +++ b/boa/src/environment/object_environment_record.rs @@ -7,6 +7,7 @@ //! More info: [Object Records](https://tc39.es/ecma262/#sec-object-environment-records) use super::*; +use crate::property::PropertyDescriptor; use crate::{ environment::{ environment_record_trait::EnvironmentRecordTrait, @@ -80,7 +81,10 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { fn get_binding_value(&self, name: &str, strict: bool) -> Result { if self.bindings.has_field(name) { - Ok(self.bindings.get_field(name)) + match self.bindings.get_property(name) { + Some(PropertyDescriptor::Data(ref d)) => Ok(d.value()), + _ => Ok(Value::undefined()), + } } else if strict { Err(ErrorKind::new_reference_error(format!( "{} has no binding", diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 7cf7b0da2fd..e95bbd729fb 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -782,7 +782,7 @@ mod in_operator { let foo_val = forward_val(&mut context, "Foo").unwrap(); assert!(bar_obj .prototype_instance() - .strict_equals(&foo_val.get_field("prototype"))); + .strict_equals(&foo_val.get_field("prototype", &mut context).unwrap())); } } diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 28fa9e7709b..b1129ba1a73 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()); + let proto = self.get(&PROTOTYPE.into(), context)?; let proto = if proto.is_object() { proto } else { @@ -272,7 +272,7 @@ impl GcObject { } } } else { - let name = this.get_field("name").display().to_string(); + let name = this.get_field("name", context)?.display().to_string(); return context.throw_type_error(format!("{} is not a constructor", name)); } } else { @@ -350,7 +350,7 @@ impl GcObject { let this = Value::from(self.clone()); for name in &method_names { // a. Let method be ? Get(O, name). - let method: Value = this.get_field(*name); + let method: Value = this.get_field(*name, context)?; // b. If IsCallable(method) is true, then if method.is_function() { // i. Let result be ? Call(method, O). @@ -377,7 +377,7 @@ impl GcObject { let mut arr: Vec = Vec::with_capacity(keys.len()); let this = Value::from(self.clone()); for key in keys { - let value = this.get_field(key); + let value = this.get_field(key, context)?; if value.is_undefined() || value.is_function() || value.is_symbol() { arr.push(JSONValue::Null); } else { @@ -390,7 +390,7 @@ impl GcObject { let this = Value::from(self.clone()); for k in self.borrow().keys() { let key = k.clone(); - let value = this.get_field(k.to_string()); + let value = this.get_field(k.to_string(), context)?; if !value.is_undefined() && !value.is_function() && !value.is_symbol() { new_obj.insert(key.to_string(), value.to_json(context)?); } @@ -408,26 +408,28 @@ impl GcObject { let mut attribute = Attribute::empty(); let enumerable_key = PropertyKey::from("enumerable"); - if self.has_property(&enumerable_key) && self.get(&enumerable_key).to_boolean() { + if self.has_property(&enumerable_key) && self.get(&enumerable_key, context)?.to_boolean() { attribute |= Attribute::ENUMERABLE; } let configurable_key = PropertyKey::from("configurable"); - if self.has_property(&configurable_key) && self.get(&configurable_key).to_boolean() { + if self.has_property(&configurable_key) + && self.get(&configurable_key, context)?.to_boolean() + { attribute |= Attribute::CONFIGURABLE; } let mut value = None; let value_key = PropertyKey::from("value"); if self.has_property(&value_key) { - value = Some(self.get(&value_key)); + value = Some(self.get(&value_key, 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).to_boolean() { + if self.get(&writable_key, context)?.to_boolean() { attribute |= Attribute::WRITABLE; } } @@ -435,7 +437,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); + let getter = self.get(&get_key, context)?; match getter { Value::Object(ref object) if object.is_callable() => { get = Some(object.clone()); @@ -451,7 +453,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); + let setter = self.get(&set_key, context)?; match setter { Value::Object(ref object) if object.is_callable() => { set = Some(object.clone()); @@ -707,7 +709,7 @@ impl GcObject { K: Into, { let key = key.into(); - let value = self.get(&key); + let value = self.get(&key, context)?; if value.is_null_or_undefined() { return Ok(None); @@ -741,7 +743,7 @@ impl GcObject { // Return ? InstanceofOperator(O, BC). if let Some(object) = value.as_object() { - if let Some(prototype) = self.get(&"prototype".into()).as_object() { + if let Some(prototype) = self.get(&"prototype".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) { diff --git a/boa/src/object/internal_methods.rs b/boa/src/object/internal_methods.rs index d8bf656d8f1..e49e850a28d 100644 --- a/boa/src/object/internal_methods.rs +++ b/boa/src/object/internal_methods.rs @@ -71,28 +71,30 @@ impl GcObject { /// `[[Get]]` /// - pub fn get(&self, key: &PropertyKey) -> Value { + pub fn get(&self, key: &PropertyKey, 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 Value::undefined(); + return Ok(Value::undefined()); } - parent.get_field(key.clone()) + Ok(parent.get_field(key.clone(), context)?) } Some(ref desc) => match desc { - PropertyDescriptor::Data(desc) => desc.value(), - // TODO: Add accessors - PropertyDescriptor::Accessor(_) => Value::undefined(), + PropertyDescriptor::Data(desc) => Ok(desc.value()), + PropertyDescriptor::Accessor(AccessorDescriptor { get: Some(get), .. }) => { + get.call(&Value::from(self.clone()), &[], context) + } + _ => Ok(Value::undefined()), }, } } /// `[[Set]]` /// - pub fn set(&mut self, key: PropertyKey, val: Value) -> bool { + pub fn set(&mut self, key: PropertyKey, val: Value, context: &mut Context) -> Result { let _timer = BoaProfiler::global().start_event("Object::set", "object"); // Fetch property key @@ -113,14 +115,17 @@ impl GcObject { match &own_desc { PropertyDescriptor::Data(desc) => { if !desc.writable() { - return false; + return Ok(false); } let desc = DataDescriptor::new(val, own_desc.attributes()).into(); - self.define_own_property(key, desc) + Ok(self.define_own_property(key, desc)) + } + PropertyDescriptor::Accessor(AccessorDescriptor { set: Some(set), .. }) => { + set.call(&Value::from(self.clone()), &[val], context)?; + Ok(true) } - // TODO: Add accessors - PropertyDescriptor::Accessor(_) => false, + _ => Ok(false), } } @@ -260,7 +265,7 @@ impl GcObject { 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); + let desc_obj = props.get(&next_key, context)?; let desc = desc_obj.to_property_descriptor(context)?; descriptors.push((next_key, desc)); } diff --git a/boa/src/property/mod.rs b/boa/src/property/mod.rs index 7e5db7a3c8d..f74dd439928 100644 --- a/boa/src/property/mod.rs +++ b/boa/src/property/mod.rs @@ -118,11 +118,11 @@ impl From for PropertyDescriptor { #[derive(Debug, Clone, Trace, Finalize)] pub struct AccessorDescriptor { /// The function serving as getter. - get: Option, + pub(crate) get: Option, /// The function serving as setter. - set: Option, + pub(crate) set: Option, /// The attributes of the accessor descriptor. - attributes: Attribute, + pub(crate) attributes: Attribute, } impl AccessorDescriptor { diff --git a/boa/src/realm.rs b/boa/src/realm.rs index ef8e87f0c8c..cfa23c38277 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -4,6 +4,7 @@ //! //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec. +use crate::object::Object; use crate::{ environment::{ declarative_environment_record::DeclarativeEnvironmentRecord, @@ -31,7 +32,7 @@ impl Realm { let _timer = BoaProfiler::global().start_event("Realm::create", "realm"); // Create brand new global object // Global has no prototype to pass None to new_obj - let global = Value::new_object(None); + let global = Value::from(Object::default()); // Allow identification of the global object easily global.set_data(crate::object::ObjectData::Global); diff --git a/boa/src/syntax/ast/node/call/mod.rs b/boa/src/syntax/ast/node/call/mod.rs index dcdd5323704..3640422058d 100644 --- a/boa/src/syntax/ast/node/call/mod.rs +++ b/boa/src/syntax/ast/node/call/mod.rs @@ -65,12 +65,18 @@ impl Executable for Call { if obj.get_type() != Type::Object { obj = Value::Object(obj.to_object(context)?); } - (obj.clone(), obj.get_field(get_const_field.field())) + ( + obj.clone(), + obj.get_field(get_const_field.field(), context)?, + ) } Node::GetField(ref get_field) => { let obj = get_field.obj().run(context)?; let field = get_field.field().run(context)?; - (obj.clone(), obj.get_field(field.to_property_key(context)?)) + ( + obj.clone(), + obj.get_field(field.to_property_key(context)?, context)?, + ) } _ => (context.global_object().clone(), self.expr().run(context)?), // 'this' binding should come from the function's self-contained environment }; @@ -78,7 +84,7 @@ impl Executable for Call { for arg in self.args() { if let Node::Spread(ref x) = arg { let val = x.run(context)?; - let mut vals = context.extract_array_properties(&val).unwrap(); + let mut vals = context.extract_array_properties(&val)?.unwrap(); v_args.append(&mut vals); break; // after spread we don't accept any new arguments } diff --git a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs index 78702c7dc08..f1306209d20 100644 --- a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -74,7 +74,7 @@ impl Executable for ArrowFunctionDecl { FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE | FunctionFlags::LEXICAL_THIS_MODE, - )) + )?) } } diff --git a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs index c7e024803b5..9309ce2e1e9 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -90,10 +90,10 @@ impl Executable for FunctionDecl { self.parameters().to_vec(), self.body().to_vec(), FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, - ); + )?; // Set the name and assign it in the current environment - val.set_field("name", self.name()); + val.set_field("name", self.name(), context)?; context .realm_mut() .environment diff --git a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs index f76bbd11d8e..6f56d54cf6c 100644 --- a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -90,10 +90,10 @@ impl Executable for FunctionExpr { self.parameters().to_vec(), self.body().to_vec(), FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, - ); + )?; if let Some(name) = self.name() { - val.set_field("name", Value::from(name)); + val.set_field("name", Value::from(name), context)?; } Ok(val) diff --git a/boa/src/syntax/ast/node/field/get_const_field/mod.rs b/boa/src/syntax/ast/node/field/get_const_field/mod.rs index 43fc3a29259..80287d076e5 100644 --- a/boa/src/syntax/ast/node/field/get_const_field/mod.rs +++ b/boa/src/syntax/ast/node/field/get_const_field/mod.rs @@ -69,7 +69,7 @@ impl Executable for GetConstField { obj = Value::Object(obj.to_object(context)?); } - Ok(obj.get_field(self.field())) + Ok(obj.get_field(self.field(), context)?) } } diff --git a/boa/src/syntax/ast/node/field/get_field/mod.rs b/boa/src/syntax/ast/node/field/get_field/mod.rs index 564ece18b36..e85ac02333a 100644 --- a/boa/src/syntax/ast/node/field/get_field/mod.rs +++ b/boa/src/syntax/ast/node/field/get_field/mod.rs @@ -69,7 +69,7 @@ impl Executable for GetField { } let field = self.field().run(context)?; - Ok(obj.get_field(field.to_property_key(context)?)) + Ok(obj.get_field(field.to_property_key(context)?, context)?) } } diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index 0e72e36d4bf..cb522892d30 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -3,6 +3,7 @@ use crate::{ exec::Executable, gc::{Finalize, Trace}, + property::{AccessorDescriptor, Attribute, PropertyDescriptor}, syntax::ast::node::{MethodDefinitionKind, Node, PropertyDefinition}, Context, Result, Value, }; @@ -72,27 +73,53 @@ impl Object { impl Executable for Object { fn run(&self, context: &mut Context) -> Result { - let global_val = &context - .realm() - .environment - .get_global_object() - .expect("Could not get the global object"); - let obj = Value::new_object(Some(global_val)); + let obj = Value::new_object(context); // TODO: Implement the rest of the property types. for property in self.properties().iter() { match property { PropertyDefinition::Property(key, value) => { - obj.set_field(key.clone(), value.run(context)?); + obj.set_field(key.clone(), value.run(context)?, context)?; } - PropertyDefinition::MethodDefinition(kind, name, func) => { - if let MethodDefinitionKind::Ordinary = kind { - obj.set_field(name.clone(), func.run(context)?); - } else { - // TODO: Implement other types of MethodDefinitionKinds. - //unimplemented!("other types of property method definitions."); + PropertyDefinition::MethodDefinition(kind, name, func) => match kind { + MethodDefinitionKind::Ordinary => { + obj.set_field(name.clone(), func.run(context)?, context)?; } - } + MethodDefinitionKind::Get => { + let set = obj + .get_property(name.clone()) + .as_ref() + .and_then(|p| p.as_accessor_descriptor()) + .and_then(|a| a.setter().cloned()); + obj.set_property( + name.clone(), + PropertyDescriptor::Accessor(AccessorDescriptor { + get: func.run(context)?.as_object(), + set, + attributes: Attribute::WRITABLE + | Attribute::ENUMERABLE + | Attribute::CONFIGURABLE, + }), + ) + } + MethodDefinitionKind::Set => { + let get = obj + .get_property(name.clone()) + .as_ref() + .and_then(|p| p.as_accessor_descriptor()) + .and_then(|a| a.getter().cloned()); + obj.set_property( + name.clone(), + PropertyDescriptor::Accessor(AccessorDescriptor { + get, + set: func.run(context)?.as_object(), + attributes: Attribute::WRITABLE + | Attribute::ENUMERABLE + | Attribute::CONFIGURABLE, + }), + ) + } + }, _ => {} //unimplemented!("{:?} type of property", i), } } diff --git a/boa/src/syntax/ast/node/operator/assign/mod.rs b/boa/src/syntax/ast/node/operator/assign/mod.rs index 1f3fb637ee3..0696ba81a3c 100644 --- a/boa/src/syntax/ast/node/operator/assign/mod.rs +++ b/boa/src/syntax/ast/node/operator/assign/mod.rs @@ -81,13 +81,13 @@ impl Executable for Assign { } Node::GetConstField(ref get_const_field) => { let val_obj = get_const_field.obj().run(context)?; - val_obj.set_field(get_const_field.field(), val.clone()); + val_obj.set_field(get_const_field.field(), val.clone(), context)?; } Node::GetField(ref get_field) => { let object = get_field.obj().run(context)?; let field = get_field.field().run(context)?; let key = field.to_property_key(context)?; - object.set_field(key, val.clone()); + object.set_field(key, val.clone(), context)?; } _ => (), } diff --git a/boa/src/syntax/ast/node/operator/bin_op/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs index 46419e3985f..118f3ea9c59 100644 --- a/boa/src/syntax/ast/node/operator/bin_op/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -201,9 +201,9 @@ impl Executable for BinOp { } Node::GetConstField(ref get_const_field) => { let v_r_a = get_const_field.obj().run(context)?; - let v_a = v_r_a.get_field(get_const_field.field()); + let v_a = v_r_a.get_field(get_const_field.field(), context)?; let value = Self::run_assign(op, v_a, self.rhs(), context)?; - v_r_a.set_field(get_const_field.field(), value.clone()); + v_r_a.set_field(get_const_field.field(), value.clone(), context)?; Ok(value) } _ => Ok(Value::undefined()), diff --git a/boa/src/value/display.rs b/boa/src/value/display.rs index c8b10ae64ba..a161e4f56e9 100644 --- a/boa/src/value/display.rs +++ b/boa/src/value/display.rs @@ -53,7 +53,6 @@ macro_rules! print_obj_value { .as_data_descriptor() .unwrap() .value(); - format!( "{:>width$}: {}", key, @@ -61,8 +60,14 @@ macro_rules! print_obj_value { width = $indent, ) } else { - // FIXME: handle accessor descriptors - "".to_string() + let accessor = val.as_accessor_descriptor().unwrap(); + let display = match (accessor.setter().is_some(), accessor.getter().is_some()) { + (true, true) => "Getter & Setter", + (true, false) => "Setter", + (false, true) => "Getter", + _ => "No Getter/Setter" + }; + format!("{:>width$}: {}", key, display, width = $indent) } }) }; @@ -189,8 +194,18 @@ pub(crate) fn display_obj(v: &Value, print_internals: bool) -> String { if let Value::Object(object) = v { if object.borrow().is_error() { - let name = v.get_field("name"); - let message = v.get_field("message"); + let name = v + .get_property("name") + .as_ref() + .and_then(|p| p.as_data_descriptor()) + .map(|d| d.value()) + .unwrap_or_else(Value::undefined); + let message = v + .get_property("message") + .as_ref() + .and_then(|p| p.as_data_descriptor()) + .map(|d| d.value()) + .unwrap_or_else(Value::undefined); return format!("{}: {}", name.display(), message.display()); } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index dcc1a64384a..2e79a1e65b9 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -10,7 +10,7 @@ use crate::{ number::{f64_to_int32, f64_to_uint32}, BigInt, Number, }, - object::{GcObject, Object, ObjectData, PROTOTYPE}, + object::{GcObject, Object, ObjectData}, property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, BoaProfiler, Context, Result, }; @@ -158,17 +158,9 @@ impl Value { } /// Returns a new empty object - pub fn new_object(global: Option<&Value>) -> Self { + pub fn new_object(context: &Context) -> Self { let _timer = BoaProfiler::global().start_event("new_object", "value"); - - if let Some(global) = global { - let object_prototype = global.get_field("Object").get_field(PROTOTYPE); - - let object = Object::create(object_prototype); - Self::object(object) - } else { - Self::object(Object::default()) - } + context.construct_object().into() } /// Convert from a JSON value to a JS value @@ -205,7 +197,7 @@ impl Value { new_obj } JSONValue::Object(obj) => { - let new_obj = Value::new_object(Some(context.global_object())); + let new_obj = Value::new_object(context); for (key, json) in obj.into_iter() { let value = Self::from_json(json, context); new_obj.set_property( @@ -224,7 +216,7 @@ impl Value { /// Converts the `Value` to `JSON`. pub fn to_json(&self, context: &mut Context) -> Result { - let to_json = self.get_field("toJSON"); + let to_json = self.get_field("toJSON", context)?; if to_json.is_function() { let json_value = context.call(&to_json, self, &[])?; return json_value.to_json(context); @@ -456,19 +448,15 @@ impl Value { /// 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 receives 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, key: K) -> Self + pub fn get_field(&self, key: K, context: &mut Context) -> Result where K: Into, { let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); - let key = key.into(); - match self.get_property(key) { - Some(ref desc) => match desc { - // TODO: Add accessors - PropertyDescriptor::Accessor(_) => Value::undefined(), - PropertyDescriptor::Data(desc) => desc.value(), - }, - None => Value::undefined(), + if let Self::Object(ref obj) = *self { + obj.clone().get(&key.into(), context) + } else { + Ok(Value::undefined()) } } @@ -486,7 +474,7 @@ impl Value { /// Set the field in the value #[inline] - pub fn set_field(&self, key: K, value: V) -> Value + pub fn set_field(&self, key: K, value: V, context: &mut Context) -> Result where K: Into, V: Into, @@ -497,15 +485,15 @@ impl Value { if let Self::Object(ref obj) = *self { if let PropertyKey::Index(index) = key { if obj.is_array() { - let len = self.get_field("length").as_number().unwrap() as u32; + let len = self.get_field("length", context)?.as_number().unwrap() as u32; if len < index + 1 { - self.set_field("length", index + 1); + self.set_field("length", index + 1, context)?; } } } - obj.clone().set(key, value.clone()); + obj.clone().set(key, value.clone(), context)?; } - value + Ok(value) } /// Set the kind of an object. diff --git a/boa/src/value/tests.rs b/boa/src/value/tests.rs index 8cafe5320fd..0fef0eb1f4c 100644 --- a/boa/src/value/tests.rs +++ b/boa/src/value/tests.rs @@ -8,7 +8,8 @@ use std::hash::{Hash, Hasher}; #[test] fn is_object() { - let val = Value::new_object(None); + let context = Context::new(); + let val = Value::new_object(&context); assert_eq!(val.is_object(), true); } @@ -29,11 +30,18 @@ fn undefined() { #[test] fn get_set_field() { - let obj = Value::new_object(None); + let mut context = Context::new(); + let obj = Value::new_object(&context); // Create string and convert it to a Value let s = Value::from("bar"); - obj.set_field("foo", s); - assert_eq!(obj.get_field("foo").display().to_string(), "\"bar\""); + obj.set_field("foo", s, &mut context).unwrap(); + assert_eq!( + obj.get_field("foo", &mut context) + .unwrap() + .display() + .to_string(), + "\"bar\"" + ); } #[test] @@ -598,6 +606,19 @@ fn to_integer_or_infinity() { ); } +#[test] +fn test_accessors() { + let mut context = Context::new(); + let src = r#" + let arr = []; + let a = { get b() { return "c" }, set b(value) { arr = arr.concat([value]) }} ; + a.b = "a"; + "#; + context.eval(src).unwrap(); + assert_eq!(forward(&mut context, "a.b"), r#""c""#); + assert_eq!(forward(&mut context, "arr"), r#"[ "a" ]"#); +} + /// Test cyclic conversions that previously caused stack overflows /// Relevant mitigations for these are in `GcObject::ordinary_to_primitive` and /// `GcObject::to_json`