Browse Source

Implement property accessors (#987)

Co-authored-by: tofpie <tofpie@users.noreply.github.com>
pull/1005/head
tofpie 4 years ago committed by GitHub
parent
commit
4cede758ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      boa/src/builtins/array/array_iterator.rs
  2. 224
      boa/src/builtins/array/mod.rs
  3. 2
      boa/src/builtins/error/eval.rs
  4. 6
      boa/src/builtins/error/mod.rs
  5. 2
      boa/src/builtins/error/range.rs
  6. 2
      boa/src/builtins/error/reference.rs
  7. 2
      boa/src/builtins/error/syntax.rs
  8. 2
      boa/src/builtins/error/type.rs
  9. 2
      boa/src/builtins/error/uri.rs
  10. 9
      boa/src/builtins/function/mod.rs
  11. 35
      boa/src/builtins/iterable/mod.rs
  12. 37
      boa/src/builtins/json/mod.rs
  13. 41
      boa/src/builtins/json/tests.rs
  14. 5
      boa/src/builtins/map/map_iterator.rs
  15. 40
      boa/src/builtins/map/mod.rs
  16. 11
      boa/src/builtins/object/mod.rs
  17. 1
      boa/src/builtins/object/tests.rs
  18. 12
      boa/src/builtins/regexp/mod.rs
  19. 2
      boa/src/builtins/string/mod.rs
  20. 5
      boa/src/builtins/string/string_iterator.rs
  21. 85
      boa/src/context.rs
  22. 6
      boa/src/environment/object_environment_record.rs
  23. 2
      boa/src/exec/tests.rs
  24. 28
      boa/src/object/gcobject.rs
  25. 29
      boa/src/object/internal_methods.rs
  26. 6
      boa/src/property/mod.rs
  27. 3
      boa/src/realm.rs
  28. 12
      boa/src/syntax/ast/node/call/mod.rs
  29. 2
      boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs
  30. 4
      boa/src/syntax/ast/node/declaration/function_decl/mod.rs
  31. 4
      boa/src/syntax/ast/node/declaration/function_expr/mod.rs
  32. 2
      boa/src/syntax/ast/node/field/get_const_field/mod.rs
  33. 2
      boa/src/syntax/ast/node/field/get_field/mod.rs
  34. 55
      boa/src/syntax/ast/node/object/mod.rs
  35. 4
      boa/src/syntax/ast/node/operator/assign/mod.rs
  36. 4
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  37. 25
      boa/src/value/display.rs
  38. 42
      boa/src/value/mod.rs
  39. 29
      boa/src/value/tests.rs

13
boa/src/builtins/array/array_iterator.rs

@ -50,7 +50,7 @@ impl ArrayIterator {
array: Value, array: Value,
kind: ArrayIterationKind, kind: ArrayIterationKind,
) -> Result<Value> { ) -> Result<Value> {
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.set_data(ObjectData::ArrayIterator(Self::new(array, kind)));
array_iterator array_iterator
.as_object() .as_object()
@ -77,7 +77,7 @@ impl ArrayIterator {
} }
let len = array_iterator let len = array_iterator
.array .array
.get_field("length") .get_field("length", context)?
.as_number() .as_number()
.ok_or_else(|| context.construct_type_error("Not an array"))? .ok_or_else(|| context.construct_type_error("Not an array"))?
as u32; as u32;
@ -91,13 +91,13 @@ impl ArrayIterator {
Ok(create_iter_result_object(context, index.into(), false)) Ok(create_iter_result_object(context, index.into(), false))
} }
ArrayIterationKind::Value => { 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)) Ok(create_iter_result_object(context, element_value, false))
} }
ArrayIterationKind::KeyAndValue => { 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( let result = Array::constructor(
&Value::new_object(Some(context.global_object())), &Value::new_object(context),
&[index.into(), element_value], &[index.into(), element_value],
context, context,
)?; )?;
@ -119,11 +119,10 @@ impl ArrayIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value { 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"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // 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); make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
array_iterator array_iterator
.as_object() .as_object()

224
boa/src/builtins/array/mod.rs

@ -17,7 +17,7 @@ use crate::{
builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}, builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator},
builtins::BuiltIn, builtins::BuiltIn,
gc::GcObject, gc::GcObject,
object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, object::{ConstructorBuilder, FunctionBuilder, ObjectData},
property::{Attribute, DataDescriptor}, property::{Attribute, DataDescriptor},
value::{same_value_zero, IntegerOrInfinity, Value}, value::{same_value_zero, IntegerOrInfinity, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
@ -144,13 +144,17 @@ impl Array {
let array = Array::array_create(this, 0, Some(prototype), context)?; let array = Array::array_create(this, 0, Some(prototype), context)?;
if !length.is_number() { if !length.is_number() {
array.set_field(0, Value::from(length)); array.set_field(0, Value::from(length), context)?;
array.set_field("length", Value::from(1)); array.set_field("length", Value::from(1), context)?;
} else { } else {
if length.is_double() { if length.is_double() {
return context.throw_range_error("Invalid array length"); 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) Ok(array)
@ -172,7 +176,7 @@ impl Array {
let array = Array::array_create(this, items_len, Some(prototype), context)?; let array = Array::array_create(this, items_len, Some(prototype), context)?;
for (k, item) in items.iter().enumerate() { for (k, item) in items.iter().enumerate() {
array.set_field(k, item.clone()); array.set_field(k, item.clone(), context)?;
} }
Ok(array) Ok(array)
@ -213,13 +217,7 @@ impl Array {
/// Creates a new `Array` instance. /// Creates a new `Array` instance.
pub(crate) fn new_array(context: &Context) -> Result<Value> { pub(crate) fn new_array(context: &Context) -> Result<Value> {
let array = Value::new_object(Some( let array = Value::new_object(context);
&context
.realm()
.environment
.get_global_object()
.expect("Could not get global object"),
));
array.set_data(ObjectData::Array); array.set_data(ObjectData::Array);
array array
.as_object() .as_object()
@ -245,7 +243,7 @@ impl Array {
let array_obj_ptr = array_obj.clone(); let array_obj_ptr = array_obj.clone();
// Wipe existing contents of the array object // 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 { for n in 0..orig_length {
array_obj_ptr.remove_property(n); array_obj_ptr.remove_property(n);
} }
@ -258,7 +256,7 @@ impl Array {
array_obj_ptr.set_property("length".to_string(), length); array_obj_ptr.set_property("length".to_string(), length);
for (n, value) in array_contents.iter().enumerate() { 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) Ok(array_obj_ptr)
} }
@ -270,17 +268,18 @@ impl Array {
add_values: &[Value], add_values: &[Value],
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
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() { for (n, value) in add_values.iter().enumerate() {
let new_index = orig_length.wrapping_add(n); 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( array_ptr.set_field(
"length", "length",
Value::from(orig_length.wrapping_add(add_values.len())), Value::from(orig_length.wrapping_add(add_values.len())),
); context,
)?;
Ok(array_ptr.clone()) Ok(array_ptr.clone())
} }
@ -325,15 +324,17 @@ impl Array {
// one) // one)
let mut new_values: Vec<Value> = Vec::new(); let mut new_values: Vec<Value> = 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 { 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 { 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 { 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 /// [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<Value> { pub(crate) fn push(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let new_array = Self::add_to_array_object(this, args, context)?; 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()` /// `Array.prototype.pop()`
@ -368,15 +369,15 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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<Value> { pub(crate) fn pop(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
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 { if curr_length < 1 {
return Ok(Value::undefined()); return Ok(Value::undefined());
} }
let pop_index = curr_length.wrapping_sub(1); 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.remove_property(pop_index);
this.set_field("length", Value::from(pop_index)); this.set_field("length", Value::from(pop_index), context)?;
Ok(pop_value) Ok(pop_value)
} }
@ -398,10 +399,10 @@ impl Array {
let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); 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 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 { 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 arguments = [element, Value::from(i), this.clone()];
context.call(callback_arg, &this_arg, &arguments)?; context.call(callback_arg, &this_arg, &arguments)?;
@ -433,9 +434,9 @@ impl Array {
}; };
let mut elem_strs = Vec::new(); 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 { 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); elem_strs.push(elem_str);
} }
@ -459,14 +460,15 @@ impl Array {
let method_name = "join"; let method_name = "join";
let mut arguments = vec![Value::from(",")]; let mut arguments = vec![Value::from(",")];
// 2. // 2.
let mut method = this.get_field(method_name); let mut method = this.get_field(method_name, context)?;
// 3. // 3.
if !method.is_function() { if !method.is_function() {
method = context let object_prototype: Value = context
.global_object() .standard_objects()
.get_field("Object") .object_object()
.get_field(PROTOTYPE) .prototype()
.get_field("toString"); .into();
method = object_prototype.get_field("toString", context)?;
arguments = Vec::new(); arguments = Vec::new();
} }
@ -495,7 +497,7 @@ impl Array {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
#[allow(clippy::else_if_without_else)] #[allow(clippy::else_if_without_else)]
pub(crate) fn reverse(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { pub(crate) fn reverse(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
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); let middle = len.wrapping_div(2);
@ -505,17 +507,17 @@ impl Array {
let upper_exists = this.has_field(upper); let upper_exists = this.has_field(upper);
let lower_exists = this.has_field(lower); let lower_exists = this.has_field(lower);
let upper_value = this.get_field(upper); let upper_value = this.get_field(upper, context)?;
let lower_value = this.get_field(lower); let lower_value = this.get_field(lower, context)?;
if upper_exists && lower_exists { if upper_exists && lower_exists {
this.set_field(upper, lower_value); this.set_field(upper, lower_value, context)?;
this.set_field(lower, upper_value); this.set_field(lower, upper_value, context)?;
} else if upper_exists { } else if upper_exists {
this.set_field(lower, upper_value); this.set_field(lower, upper_value, context)?;
this.remove_property(upper); this.remove_property(upper);
} else if lower_exists { } else if lower_exists {
this.set_field(upper, lower_value); this.set_field(upper, lower_value, context)?;
this.remove_property(lower); this.remove_property(lower);
} }
} }
@ -534,30 +536,30 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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<Value> { pub(crate) fn shift(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
let len = this.get_field("length").to_length(context)?; let len = this.get_field("length", context)?.to_length(context)?;
if len == 0 { if len == 0 {
this.set_field("length", 0); this.set_field("length", 0, context)?;
return Ok(Value::undefined()); return Ok(Value::undefined());
} }
let first: Value = this.get_field(0); let first: Value = this.get_field(0, context)?;
for k in 1..len { for k in 1..len {
let from = k; let from = k;
let to = k.wrapping_sub(1); 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() { if from_value.is_undefined() {
this.remove_property(to); this.remove_property(to);
} else { } else {
this.set_field(to, from_value); this.set_field(to, from_value, context)?;
} }
} }
let final_index = len.wrapping_sub(1); let final_index = len.wrapping_sub(1);
this.remove_property(final_index); this.remove_property(final_index);
this.set_field("length", Value::from(final_index)); this.set_field("length", Value::from(final_index), context)?;
Ok(first) Ok(first)
} }
@ -575,7 +577,7 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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<Value> { pub(crate) fn unshift(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let len = this.get_field("length").to_length(context)?; let len = this.get_field("length", context)?.to_length(context)?;
let arg_c = args.len(); let arg_c = args.len();
@ -584,20 +586,24 @@ impl Array {
let from = k.wrapping_sub(1); let from = k.wrapping_sub(1);
let to = k.wrapping_add(arg_c).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() { if from_value.is_undefined() {
this.remove_property(to); this.remove_property(to);
} else { } else {
this.set_field(to, from_value); this.set_field(to, from_value, context)?;
} }
} }
for j in 0..arg_c { 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); 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)) Ok(Value::from(temp))
} }
@ -627,16 +633,19 @@ impl Array {
Value::undefined() Value::undefined()
}; };
let mut i = 0; 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; let mut len = max_len;
while i < 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 arguments = [element, Value::from(i), this.clone()];
let result = context.call(callback, &this_arg, &arguments)?; let result = context.call(callback, &this_arg, &arguments)?;
if !result.to_boolean() { if !result.to_boolean() {
return Ok(Value::from(false)); 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; i += 1;
} }
Ok(Value::from(true)) Ok(Value::from(true))
@ -663,7 +672,7 @@ impl Array {
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
let this_val = args.get(1).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 { if length > 2usize.pow(32) - 1 {
return context.throw_range_error("Invalid array length"); return context.throw_range_error("Invalid array length");
@ -671,16 +680,14 @@ impl Array {
let new = Self::new_array(context)?; let new = Self::new_array(context)?;
let values: Vec<Value> = (0..length) let values = (0..length)
.map(|idx| { .map(|idx| {
let element = this.get_field(idx); let element = this.get_field(idx, context)?;
let args = [element, Value::from(idx), new.clone()]; let args = [element, Value::from(idx), new.clone()];
context context.call(&callback, &this_val, &args)
.call(&callback, &this_val, &args)
.unwrap_or_else(|_| Value::undefined())
}) })
.collect(); .collect::<Result<Vec<Value>>>()?;
Self::construct_array(&new, &values, context) Self::construct_array(&new, &values, context)
} }
@ -711,7 +718,7 @@ impl Array {
} }
let search_element = args[0].clone(); 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) { let mut idx = match args.get(1) {
Some(from_idx_ptr) => { Some(from_idx_ptr) => {
@ -731,7 +738,7 @@ impl Array {
}; };
while idx < len { 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) { if check_element.strict_equals(&search_element) {
return Ok(Value::from(idx)); return Ok(Value::from(idx));
@ -773,7 +780,7 @@ impl Array {
let search_element = args[0].clone(); let search_element = args[0].clone();
let len: isize = this let len: isize = this
.get_field("length") .get_field("length", context)?
.to_length(context)? .to_length(context)?
.try_into() .try_into()
.map_err(interror_to_value)?; .map_err(interror_to_value)?;
@ -794,7 +801,7 @@ impl Array {
}; };
while idx >= 0 { 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) { if check_element.strict_equals(&search_element) {
return Ok(Value::from(i32::try_from(idx).map_err(interror_to_value)?)); return Ok(Value::from(i32::try_from(idx).map_err(interror_to_value)?));
@ -826,9 +833,9 @@ impl Array {
} }
let callback = &args[0]; let callback = &args[0];
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); 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 { 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 arguments = [element.clone(), Value::from(i), this.clone()];
let result = context.call(callback, &this_arg, &arguments)?; let result = context.call(callback, &this_arg, &arguments)?;
if result.to_boolean() { if result.to_boolean() {
@ -861,10 +868,10 @@ impl Array {
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); 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 { 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 arguments = [element, Value::from(i), this.clone()];
let result = context.call(predicate_arg, &this_arg, &arguments)?; let result = context.call(predicate_arg, &this_arg, &arguments)?;
@ -890,7 +897,7 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/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<Value> { pub(crate) fn fill(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
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 default_value = Value::undefined();
let value = args.get(0).unwrap_or(&default_value); 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)?; let fin = Self::get_relative_end(context, args.get(2), len)?;
for i in start..fin { for i in start..fin {
this.set_field(i, value.clone()); this.set_field(i, value.clone(), context)?;
} }
Ok(this.clone()) Ok(this.clone())
@ -921,10 +928,10 @@ impl Array {
) -> Result<Value> { ) -> Result<Value> {
let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined); 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 { 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) { if same_value_zero(&check_element, &search_element) {
return Ok(Value::from(true)); return Ok(Value::from(true));
@ -951,17 +958,20 @@ impl Array {
pub(crate) fn slice(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> { pub(crate) fn slice(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
let new_array = Self::new_array(context)?; 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 from = Self::get_relative_start(context, args.get(0), len)?;
let to = Self::get_relative_end(context, args.get(1), len)?; let to = Self::get_relative_end(context, args.get(1), len)?;
let span = max(to.saturating_sub(from), 0); 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; let mut new_array_len: i32 = 0;
for i in from..from.saturating_add(span) { 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_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) Ok(new_array)
} }
@ -986,27 +996,26 @@ impl Array {
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
let this_val = args.get(1).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 new = Self::new_array(context)?;
let values = (0..length) let values = (0..length)
.filter_map(|idx| { .map(|idx| {
let element = this.get_field(idx); let element = this.get_field(idx, context)?;
let args = [element.clone(), Value::from(idx), new.clone()]; let args = [element.clone(), Value::from(idx), new.clone()];
let callback_result = context let callback_result = context.call(&callback, &this_val, &args)?;
.call(&callback, &this_val, &args)
.unwrap_or_else(|_| Value::undefined());
if callback_result.to_boolean() { if callback_result.to_boolean() {
Some(element) Ok(Some(element))
} else { } else {
None Ok(None)
} }
}) })
.collect::<Vec<Value>>(); .collect::<Result<Vec<Option<Value>>>>()?;
let values = values.into_iter().filter_map(|v| v).collect::<Vec<_>>();
Self::construct_array(&new, &values, context) Self::construct_array(&new, &values, context)
} }
@ -1039,17 +1048,20 @@ impl Array {
Value::undefined() Value::undefined()
}; };
let mut i = 0; 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; let mut len = max_len;
while i < 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 arguments = [element, Value::from(i), this.clone()];
let result = context.call(callback, &this_arg, &arguments)?; let result = context.call(callback, &this_arg, &arguments)?;
if result.to_boolean() { if result.to_boolean() {
return Ok(Value::from(true)); return Ok(Value::from(true));
} }
// the length of the array must be updated because the callback can mutate it. // 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; i += 1;
} }
Ok(Value::from(false)) Ok(Value::from(false))
@ -1073,7 +1085,7 @@ impl Array {
_ => return context.throw_type_error("Reduce was called without a callback"), _ => return context.throw_type_error("Reduce was called without a callback"),
}; };
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined); 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() { if length == 0 && initial_value.is_undefined() {
return context return context
.throw_type_error("Reduce was called on an empty array and with no initial value"); .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", "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; k += 1;
result result
} else { } else {
@ -1101,12 +1113,20 @@ impl Array {
}; };
while k < length { while k < length {
if this.has_field(k) { 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)?; accumulator = context.call(&callback, &Value::undefined(), &arguments)?;
/* We keep track of possibly shortened length in order to prevent unnecessary iteration. /* 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 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 */ 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; k += 1;
} }
@ -1135,7 +1155,7 @@ impl Array {
_ => return context.throw_type_error("reduceRight was called without a callback"), _ => return context.throw_type_error("reduceRight was called without a callback"),
}; };
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined); 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 { if length == 0 {
return if initial_value.is_undefined() { return if initial_value.is_undefined() {
context.throw_type_error( context.throw_type_error(
@ -1165,7 +1185,7 @@ impl Array {
"reduceRight was called on an empty array and with no initial value", "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; k = k.overflowing_sub(1).0;
result result
} else { } 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 // usize::MAX is bigger than the maximum array size so we can use it check for integer undeflow
while k != usize::MAX { while k != usize::MAX {
if this.has_field(k) { 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)?; accumulator = context.call(&callback, &Value::undefined(), &arguments)?;
/* We keep track of possibly shortened length in order to prevent unnecessary iteration. /* 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 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 */ 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 // move k to the last defined element if necessary or return if the length was set to 0
if k >= length { if k >= length {

2
boa/src/builtins/error/eval.rs

@ -62,7 +62,7 @@ impl EvalError {
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if let Some(message) = args.get(0) { 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 // This value is used by console.log and other routines to match Object type

6
boa/src/builtins/error/mod.rs

@ -79,7 +79,7 @@ impl Error {
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if let Some(message) = args.get(0) { 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 // This value is used by console.log and other routines to match Object type
@ -103,7 +103,7 @@ impl Error {
if !this.is_object() { if !this.is_object() {
return context.throw_type_error("'this' is not an 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_to_string;
let name = if name.is_undefined() { let name = if name.is_undefined() {
"Error" "Error"
@ -112,7 +112,7 @@ impl Error {
name_to_string.as_str() name_to_string.as_str()
}; };
let message = this.get_field("message"); let message = this.get_field("message", context)?;
let message_to_string; let message_to_string;
let message = if message.is_undefined() { let message = if message.is_undefined() {
"" ""

2
boa/src/builtins/error/range.rs

@ -60,7 +60,7 @@ impl RangeError {
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if let Some(message) = args.get(0) { 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 // This value is used by console.log and other routines to match Object type

2
boa/src/builtins/error/reference.rs

@ -59,7 +59,7 @@ impl ReferenceError {
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if let Some(message) = args.get(0) { 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 // This value is used by console.log and other routines to match Object type

2
boa/src/builtins/error/syntax.rs

@ -62,7 +62,7 @@ impl SyntaxError {
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if let Some(message) = args.get(0) { 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 // This value is used by console.log and other routines to match Object type

2
boa/src/builtins/error/type.rs

@ -65,7 +65,7 @@ impl TypeError {
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if let Some(message) = args.get(0) { 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 // This value is used by console.log and other routines to match Object type

2
boa/src/builtins/error/uri.rs

@ -61,7 +61,7 @@ impl UriError {
context: &mut Context, context: &mut Context,
) -> Result<Value> { ) -> Result<Value> {
if let Some(message) = args.get(0) { 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 // This value is used by console.log and other routines to match Object type

9
boa/src/builtins/function/mod.rs

@ -232,9 +232,10 @@ pub fn make_builtin_fn<N>(
let mut function = Object::function( let mut function = Object::function(
Function::BuiltIn(function.into(), FunctionFlags::CALLABLE), Function::BuiltIn(function.into(), FunctionFlags::CALLABLE),
interpreter interpreter
.global_object() .standard_objects()
.get_field("Function") .function_object()
.get_field("prototype"), .prototype()
.into(),
); );
function.insert_property("length", length, Attribute::all()); function.insert_property("length", length, Attribute::all());
@ -304,7 +305,7 @@ impl BuiltInFunctionObject {
return context.call(this, &this_arg, &[]); return context.call(this, &this_arg, &[]);
} }
let arg_list = context let arg_list = context
.extract_array_properties(&arg_array) .extract_array_properties(&arg_array)?
.map_err(|()| arg_array)?; .map_err(|()| arg_array)?;
// TODO?: 5. PrepareForTailCall // TODO?: 5. PrepareForTailCall
context.call(this, &this_arg, &arg_list) context.call(this, &this_arg, &arg_list)

35
boa/src/builtins/iterable/mod.rs

@ -58,7 +58,7 @@ impl IteratorPrototypes {
/// ///
/// Generates an object supporting the IteratorResult interface. /// Generates an object supporting the IteratorResult interface.
pub fn create_iter_result_object(context: &mut Context, value: Value, done: bool) -> Value { 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 // TODO: Fix attributes of value and done
let value_property = DataDescriptor::new(value, Attribute::all()); let value_property = DataDescriptor::new(value, Attribute::all());
let done_property = DataDescriptor::new(done, 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 /// Get an iterator record
pub fn get_iterator(context: &mut Context, iterable: Value) -> Result<IteratorRecord> { pub fn get_iterator(context: &mut Context, iterable: Value) -> Result<IteratorRecord> {
// TODO: Fix the accessor handling let iterator_function =
let iterator_function = iterable iterable.get_field(context.well_known_symbols().iterator_symbol(), context)?;
.get_property(context.well_known_symbols().iterator_symbol()) if iterator_function.is_null_or_undefined() {
.map(|p| p.as_data_descriptor().unwrap().value()) return Err(context.construct_type_error("Not an iterable"));
.ok_or_else(|| context.construct_type_error("Not an iterable"))?; }
let iterator_object = context.call(&iterator_function, &iterable, &[])?; let iterator_object = context.call(&iterator_function, &iterable, &[])?;
let next_function = iterator_object let next_function = iterator_object.get_field("next", context)?;
.get_property("next") if next_function.is_null_or_undefined() {
.map(|p| p.as_data_descriptor().unwrap().value()) return Err(context.construct_type_error("Could not find property `next`"));
.ok_or_else(|| context.construct_type_error("Could not find property `next`"))?; }
Ok(IteratorRecord::new(iterator_object, next_function)) Ok(IteratorRecord::new(iterator_object, next_function))
} }
@ -125,18 +125,9 @@ impl IteratorRecord {
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext /// [spec]: https://tc39.es/ecma262/#sec-iteratornext
pub(crate) fn next(&self, context: &mut Context) -> Result<IteratorResult> { pub(crate) fn next(&self, context: &mut Context) -> Result<IteratorResult> {
let next = context.call(&self.next_function, &self.iterator_object, &[])?; let next = context.call(&self.next_function, &self.iterator_object, &[])?;
// FIXME: handle accessor descriptors let done = next.get_field("done", context)?.to_boolean();
let done = next
.get_property("done") let next_result = next.get_field("value", context)?;
.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();
Ok(IteratorResult::new(next_result, done)) Ok(IteratorResult::new(next_result, done))
} }
} }

37
boa/src/builtins/json/mod.rs

@ -13,6 +13,7 @@
//! [json]: https://www.json.org/json-en.html //! [json]: https://www.json.org/json-en.html
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
use crate::object::Object;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
object::ObjectInitializer, object::ObjectInitializer,
@ -74,8 +75,8 @@ impl Json {
let j = Value::from_json(json, context); let j = Value::from_json(json, context);
match args.get(1) { match args.get(1) {
Some(reviver) if reviver.is_function() => { Some(reviver) if reviver.is_function() => {
let mut holder = Value::new_object(None); let mut holder = Value::object(Object::default());
holder.set_field("", j); holder.set_field("", j, context)?;
Self::walk(reviver, context, &mut holder, &PropertyKey::from("")) Self::walk(reviver, context, &mut holder, &PropertyKey::from(""))
} }
_ => Ok(j), _ => Ok(j),
@ -97,7 +98,7 @@ impl Json {
holder: &mut Value, holder: &mut Value,
key: &PropertyKey, key: &PropertyKey,
) -> Result<Value> { ) -> Result<Value> {
let value = holder.get_field(key.clone()); let value = holder.get_field(key.clone(), context)?;
if let Value::Object(ref object) = value { if let Value::Object(ref object) = value {
let keys: Vec<_> = object.borrow().keys().collect(); let keys: Vec<_> = object.borrow().keys().collect();
@ -106,7 +107,7 @@ impl Json {
let v = Self::walk(reviver, context, &mut value.clone(), &key); let v = Self::walk(reviver, context, &mut value.clone(), &key);
match v { match v {
Ok(v) if !v.is_undefined() => { Ok(v) if !v.is_undefined() => {
value.set_field(key, v); value.set_field(key, v, context)?;
} }
Ok(_) => { Ok(_) => {
value.remove_property(key); value.remove_property(key);
@ -191,13 +192,9 @@ impl Json {
object object
.as_object() .as_object()
.map(|obj| { .map(|obj| {
let object_to_return = Value::new_object(None); let object_to_return = Value::object(Object::default());
for (key, val) in obj for key in obj.borrow().keys() {
.borrow() let val = obj.get(&key, context)?;
.iter()
// FIXME: handle accessor descriptors
.map(|(k, v)| (k, v.as_data_descriptor().unwrap().value()))
{
let this_arg = object.clone(); let this_arg = object.clone();
object_to_return.set_property( object_to_return.set_property(
key.to_owned(), key.to_owned(),
@ -224,16 +221,20 @@ impl Json {
if key == "length" { if key == "length" {
None None
} else { } 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 { for field in fields {
if let Some(value) = object let v = object.get_field(field.to_string(context)?, context)?;
.get_property(field.to_string(context)?) if !v.is_undefined() {
// FIXME: handle accessor descriptors let value = v.to_json(context)?;
.map(|prop| prop.as_data_descriptor().unwrap().value().to_json(context))
.transpose()?
{
obj_to_return.insert(field.to_string(context)?.to_string(), value); obj_to_return.insert(field.to_string(context)?.to_string(), value);
} }
} }

41
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] #[test]
fn json_sanity() { fn json_sanity() {
@ -341,19 +341,35 @@ fn json_parse_array_with_reviver() {
) )
.unwrap(); .unwrap();
assert_eq!( 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 2u8
); );
assert_eq!( 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 4u8
); );
assert_eq!( 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 6u8
); );
assert_eq!( 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 8u8
); );
} }
@ -405,14 +421,13 @@ fn json_parse_sets_prototypes() {
.as_object() .as_object()
.unwrap() .unwrap()
.prototype_instance(); .prototype_instance();
let global_object_prototype = context let global_object_prototype: Value = context
.global_object() .standard_objects()
.get_field("Object") .object_object()
.get_field(PROTOTYPE); .prototype()
let global_array_prototype = context .into();
.global_object() let global_array_prototype: Value =
.get_field("Array") context.standard_objects().array_object().prototype().into();
.get_field(PROTOTYPE);
assert_eq!( assert_eq!(
same_value(&object_prototype, &global_object_prototype), same_value(&object_prototype, &global_object_prototype),
true true

5
boa/src/builtins/map/map_iterator.rs

@ -50,7 +50,7 @@ impl MapIterator {
map: Value, map: Value,
kind: MapIterationKind, kind: MapIterationKind,
) -> Result<Value> { ) -> Result<Value> {
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.set_data(ObjectData::MapIterator(Self::new(map, kind)));
map_iterator map_iterator
.as_object() .as_object()
@ -139,11 +139,10 @@ impl MapIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value { 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"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // 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); make_builtin_fn(Self::next, "next", &map_iterator, 0, context);
map_iterator map_iterator
.as_object() .as_object()

40
boa/src/builtins/map/mod.rs

@ -77,8 +77,9 @@ impl Map {
// Set Prototype // Set Prototype
let prototype = context let prototype = context
.global_object() .global_object()
.get_field("Map") .clone()
.get_field(PROTOTYPE); .get_field("Map", context)?
.get_field(PROTOTYPE, context)?;
this.as_object() this.as_object()
.expect("this is map object") .expect("this is map object")
@ -96,14 +97,15 @@ impl Map {
map map
} else if object.is_array() { } else if object.is_array() {
let mut map = OrderedMap::new(); 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 { for i in 0..len {
let val = &args[0].get_field(i.to_string()); let val = &args[0].get_field(i, context)?;
let (key, value) = Self::get_key_value(val).ok_or_else(|| { let (key, value) =
context.construct_type_error( Self::get_key_value(val, context)?.ok_or_else(|| {
"iterable for Map should have array-like objects", context.construct_type_error(
) "iterable for Map should have array-like objects",
})?; )
})?;
map.insert(key, value); map.insert(key, value);
} }
map map
@ -354,17 +356,21 @@ impl Map {
} }
/// Helper function to get a key-value pair from an array. /// 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<Option<(Value, Value)>> {
if let Value::Object(object) = value { if let Value::Object(object) = value {
if object.is_array() { if object.is_array() {
let (key, value) = match value.get_field("length").as_number().unwrap() as i32 { let (key, value) =
0 => (Value::Undefined, Value::Undefined), match value.get_field("length", context)?.as_number().unwrap() as i32 {
1 => (value.get_field("0"), Value::Undefined), 0 => (Value::Undefined, Value::Undefined),
_ => (value.get_field("0"), value.get_field("1")), 1 => (value.get_field("0", context)?, Value::Undefined),
}; _ => (
return Some((key, value)); value.get_field("0", context)?,
value.get_field("1", context)?,
),
};
return Ok(Some((key, value)));
} }
} }
None Ok(None)
} }
} }

11
boa/src/builtins/object/mod.rs

@ -83,9 +83,7 @@ impl Object {
return Ok(arg.to_object(context)?.into()); return Ok(arg.to_object(context)?.into());
} }
} }
let global = context.global_object(); Ok(Value::new_object(context))
Ok(Value::new_object(Some(global)))
} }
/// `Object.create( proto, [propertiesObject] )` /// `Object.create( proto, [propertiesObject] )`
@ -301,7 +299,7 @@ impl Object {
return context.throw_type_error("Property description must be an object"); return context.throw_type_error("Property description must be an object");
}; };
obj.set_property(prop, desc); obj.set_property(prop, desc);
Ok(Value::undefined()) Ok(obj.clone())
} }
/// `Object.defineProperties( proto, [propertiesObject] )` /// `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); let tag_str = tag.as_string().map(|s| s.as_str()).unwrap_or(builtin_tag);

1
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()"), "true");
assert_eq!(forward(&mut context, "Object.is(undefined)"), "true"); assert_eq!(forward(&mut context, "Object.is(undefined)"), "true");
assert!(context.global_object().is_global()); assert!(context.global_object().is_global());
assert!(!context.global_object().get_field("Object").is_global());
} }
#[test] #[test]
fn object_has_own_property() { fn object_has_own_property() {

12
boa/src/builtins/regexp/mod.rs

@ -314,7 +314,7 @@ impl RegExp {
.get(0) .get(0)
.expect("could not get argument") .expect("could not get argument")
.to_string(context)?; .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 result = if let Some(object) = this.as_object() {
let object = object.borrow(); let object = object.borrow();
let regex = object.as_regexp().unwrap(); let regex = object.as_regexp().unwrap();
@ -334,7 +334,7 @@ impl RegExp {
} else { } else {
panic!("object is not a regexp") panic!("object is not a regexp")
}; };
this.set_field("lastIndex", Value::from(last_index)); this.set_field("lastIndex", Value::from(last_index), context)?;
result result
} }
@ -355,7 +355,7 @@ impl RegExp {
.get(0) .get(0)
.expect("could not get argument") .expect("could not get argument")
.to_string(context)?; .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 result = if let Some(object) = this.as_object() {
let object = object.borrow(); let object = object.borrow();
let regex = object.as_regexp().unwrap(); let regex = object.as_regexp().unwrap();
@ -391,7 +391,7 @@ impl RegExp {
} else { } else {
panic!("object is not a regexp") panic!("object is not a regexp")
}; };
this.set_field("lastIndex", Value::from(last_index)); this.set_field("lastIndex", Value::from(last_index), context)?;
result result
} }
@ -463,7 +463,7 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall /// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@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 // TODO: it's returning an array, it should return an iterator
pub(crate) fn match_all(this: &Value, arg_str: String) -> Result<Value> { pub(crate) fn match_all(this: &Value, arg_str: String, context: &mut Context) -> Result<Value> {
let matches = if let Some(object) = this.as_object() { let matches = if let Some(object) = this.as_object() {
let object = object.borrow(); let object = object.borrow();
let regex = object.as_regexp().unwrap(); let regex = object.as_regexp().unwrap();
@ -499,7 +499,7 @@ impl RegExp {
let length = matches.len(); let length = matches.len();
let result = Value::from(matches); 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); result.set_data(ObjectData::Array);
Ok(result) Ok(result)

2
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<Value> { pub(crate) fn iterator(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {

5
boa/src/builtins/string/string_iterator.rs

@ -23,7 +23,7 @@ impl StringIterator {
} }
pub fn create_string_iterator(context: &mut Context, string: Value) -> Result<Value> { pub fn create_string_iterator(context: &mut Context, string: Value) -> Result<Value> {
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.set_data(ObjectData::StringIterator(Self::new(string)));
string_iterator string_iterator
.as_object() .as_object()
@ -70,11 +70,10 @@ impl StringIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value { 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"); let _timer = BoaProfiler::global().start_event("String Iterator", "init");
// Create prototype // 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); make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
array_iterator array_iterator
.as_object() .as_object()

85
boa/src/context.rs

@ -316,10 +316,7 @@ impl Context {
/// Construct an empty object. /// Construct an empty object.
#[inline] #[inline]
pub fn construct_object(&self) -> GcObject { pub fn construct_object(&self) -> GcObject {
let object_prototype = self let object_prototype: Value = self.standard_objects().object_object().prototype().into();
.global_object()
.get_field("Object")
.get_field(PROTOTYPE);
GcObject::new(Object::create(object_prototype)) GcObject::new(Object::create(object_prototype))
} }
@ -480,18 +477,16 @@ impl Context {
params: P, params: P,
body: B, body: B,
flags: FunctionFlags, flags: FunctionFlags,
) -> Value ) -> Result<Value>
where where
P: Into<Box<[FormalParameter]>>, P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>, B: Into<StatementList>,
{ {
let function_prototype = self let function_prototype: Value =
.global_object() self.standard_objects().function_object().prototype().into();
.get_field("Function")
.get_field(PROTOTYPE);
// Every new function has a prototype property pre-made // 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 = params.into();
let params_len = params.len(); let params_len = params.len();
@ -507,12 +502,12 @@ impl Context {
let val = Value::from(new_func); let val = Value::from(new_func);
// Set constructor field to the newly created Value (function object) // 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(PROTOTYPE, proto, self)?;
val.set_field("length", Value::from(params_len)); val.set_field("length", Value::from(params_len), self)?;
val Ok(val)
} }
/// Create a new builin function. /// Create a new builin function.
@ -522,20 +517,17 @@ impl Context {
length: usize, length: usize,
body: NativeFunction, body: NativeFunction,
) -> Result<GcObject> { ) -> Result<GcObject> {
let function_prototype = self let function_prototype: Value = self.standard_objects().object_object().prototype().into();
.global_object()
.get_field("Function")
.get_field(PROTOTYPE);
// Every new function has a prototype property pre-made // 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( let mut function = GcObject::new(Object::function(
Function::BuiltIn(body.into(), FunctionFlags::CALLABLE), Function::BuiltIn(body.into(), FunctionFlags::CALLABLE),
function_prototype, function_prototype,
)); ));
function.set(PROTOTYPE.into(), proto); function.set(PROTOTYPE.into(), proto, self)?;
function.set("length".into(), length.into()); function.set("length".into(), length.into(), self)?;
function.set("name".into(), name.into()); function.set("name".into(), name.into(), self)?;
Ok(function) Ok(function)
} }
@ -549,7 +541,11 @@ impl Context {
body: NativeFunction, body: NativeFunction,
) -> Result<()> { ) -> Result<()> {
let function = self.create_builtin_function(name, length, body)?; 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(()) Ok(())
} }
@ -557,15 +553,18 @@ impl Context {
/// ///
/// This is useful for the spread operator, for any other object an `Err` is returned /// 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 /// 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<Vec<Value>, ()> { pub(crate) fn extract_array_properties(
&mut self,
value: &Value,
) -> Result<StdResult<Vec<Value>, ()>> {
if let Value::Object(ref x) = value { if let Value::Object(ref x) = value {
// Check if object is array // Check if object is array
if let ObjectData::Array = x.borrow().data { 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) let values = (0..length)
.map(|idx| value.get_field(idx.to_string())) .map(|idx| value.get_field(idx, self))
.collect(); .collect::<Result<Vec<_>>>()?;
return Ok(values); return Ok(Ok(values));
} }
// Check if object is a Map // Check if object is a Map
else if let ObjectData::Map(ref map) = x.borrow().data { else if let ObjectData::Map(ref map) = x.borrow().data {
@ -573,34 +572,28 @@ impl Context {
.iter() .iter()
.map(|(key, value)| { .map(|(key, value)| {
// Construct a new array containing the key-value pair // Construct a new array containing the key-value pair
let array = Value::new_object(Some( let array = Value::new_object(self);
&self
.realm()
.environment
.get_global_object()
.expect("Could not get global object"),
));
array.set_data(ObjectData::Array); array.set_data(ObjectData::Array);
array.as_object().expect("object").set_prototype_instance( array.as_object().expect("object").set_prototype_instance(
self.realm() self.realm()
.environment .environment
.get_binding_value("Array") .get_binding_value("Array")
.expect("Array was not initialized") .expect("Array was not initialized")
.get_field(PROTOTYPE), .get_field(PROTOTYPE, self)?,
); );
array.set_field("0", key); array.set_field(0, key, self)?;
array.set_field("1", value); array.set_field(1, value, self)?;
array.set_field("length", Value::from(2)); array.set_field("length", Value::from(2), self)?;
array Ok(array)
}) })
.collect(); .collect::<Result<Vec<_>>>()?;
return Ok(values); return Ok(Ok(values));
} }
return Err(()); return Ok(Err(()));
} }
Err(()) Ok(Err(()))
} }
/// <https://tc39.es/ecma262/#sec-hasproperty> /// <https://tc39.es/ecma262/#sec-hasproperty>
@ -626,11 +619,11 @@ impl Context {
Node::GetConstField(ref get_const_field_node) => Ok(get_const_field_node Node::GetConstField(ref get_const_field_node) => Ok(get_const_field_node
.obj() .obj()
.run(self)? .run(self)?
.set_field(get_const_field_node.field(), value)), .set_field(get_const_field_node.field(), value, self)?),
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let field = get_field.field().run(self)?; let field = get_field.field().run(self)?;
let key = field.to_property_key(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), _ => panic!("TypeError: invalid assignment to {}", node),
} }

6
boa/src/environment/object_environment_record.rs

@ -7,6 +7,7 @@
//! More info: [Object Records](https://tc39.es/ecma262/#sec-object-environment-records) //! More info: [Object Records](https://tc39.es/ecma262/#sec-object-environment-records)
use super::*; use super::*;
use crate::property::PropertyDescriptor;
use crate::{ use crate::{
environment::{ environment::{
environment_record_trait::EnvironmentRecordTrait, environment_record_trait::EnvironmentRecordTrait,
@ -80,7 +81,10 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord {
fn get_binding_value(&self, name: &str, strict: bool) -> Result<Value, ErrorKind> { fn get_binding_value(&self, name: &str, strict: bool) -> Result<Value, ErrorKind> {
if self.bindings.has_field(name) { 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 { } else if strict {
Err(ErrorKind::new_reference_error(format!( Err(ErrorKind::new_reference_error(format!(
"{} has no binding", "{} has no binding",

2
boa/src/exec/tests.rs

@ -782,7 +782,7 @@ mod in_operator {
let foo_val = forward_val(&mut context, "Foo").unwrap(); let foo_val = forward_val(&mut context, "Foo").unwrap();
assert!(bar_obj assert!(bar_obj
.prototype_instance() .prototype_instance()
.strict_equals(&foo_val.get_field("prototype"))); .strict_equals(&foo_val.get_field("prototype", &mut context).unwrap()));
} }
} }

28
boa/src/object/gcobject.rs

@ -204,7 +204,7 @@ impl GcObject {
// prototype as prototype for the new object // prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor> // see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor> // see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = self.get(&PROTOTYPE.into()); let proto = self.get(&PROTOTYPE.into(), context)?;
let proto = if proto.is_object() { let proto = if proto.is_object() {
proto proto
} else { } else {
@ -272,7 +272,7 @@ impl GcObject {
} }
} }
} else { } 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)); return context.throw_type_error(format!("{} is not a constructor", name));
} }
} else { } else {
@ -350,7 +350,7 @@ impl GcObject {
let this = Value::from(self.clone()); let this = Value::from(self.clone());
for name in &method_names { for name in &method_names {
// a. Let method be ? Get(O, name). // 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 // b. If IsCallable(method) is true, then
if method.is_function() { if method.is_function() {
// i. Let result be ? Call(method, O). // i. Let result be ? Call(method, O).
@ -377,7 +377,7 @@ impl GcObject {
let mut arr: Vec<JSONValue> = Vec::with_capacity(keys.len()); let mut arr: Vec<JSONValue> = Vec::with_capacity(keys.len());
let this = Value::from(self.clone()); let this = Value::from(self.clone());
for key in keys { 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() { if value.is_undefined() || value.is_function() || value.is_symbol() {
arr.push(JSONValue::Null); arr.push(JSONValue::Null);
} else { } else {
@ -390,7 +390,7 @@ impl GcObject {
let this = Value::from(self.clone()); let this = Value::from(self.clone());
for k in self.borrow().keys() { for k in self.borrow().keys() {
let key = k.clone(); 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() { if !value.is_undefined() && !value.is_function() && !value.is_symbol() {
new_obj.insert(key.to_string(), value.to_json(context)?); new_obj.insert(key.to_string(), value.to_json(context)?);
} }
@ -408,26 +408,28 @@ impl GcObject {
let mut attribute = Attribute::empty(); let mut attribute = Attribute::empty();
let enumerable_key = PropertyKey::from("enumerable"); 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; attribute |= Attribute::ENUMERABLE;
} }
let configurable_key = PropertyKey::from("configurable"); 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; attribute |= Attribute::CONFIGURABLE;
} }
let mut value = None; let mut value = None;
let value_key = PropertyKey::from("value"); let value_key = PropertyKey::from("value");
if self.has_property(&value_key) { 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 mut has_writable = false;
let writable_key = PropertyKey::from("writable"); let writable_key = PropertyKey::from("writable");
if self.has_property(&writable_key) { if self.has_property(&writable_key) {
has_writable = true; has_writable = true;
if self.get(&writable_key).to_boolean() { if self.get(&writable_key, context)?.to_boolean() {
attribute |= Attribute::WRITABLE; attribute |= Attribute::WRITABLE;
} }
} }
@ -435,7 +437,7 @@ impl GcObject {
let mut get = None; let mut get = None;
let get_key = PropertyKey::from("get"); let get_key = PropertyKey::from("get");
if self.has_property(&get_key) { if self.has_property(&get_key) {
let getter = self.get(&get_key); let getter = self.get(&get_key, context)?;
match getter { match getter {
Value::Object(ref object) if object.is_callable() => { Value::Object(ref object) if object.is_callable() => {
get = Some(object.clone()); get = Some(object.clone());
@ -451,7 +453,7 @@ impl GcObject {
let mut set = None; let mut set = None;
let set_key = PropertyKey::from("set"); let set_key = PropertyKey::from("set");
if self.has_property(&set_key) { if self.has_property(&set_key) {
let setter = self.get(&set_key); let setter = self.get(&set_key, context)?;
match setter { match setter {
Value::Object(ref object) if object.is_callable() => { Value::Object(ref object) if object.is_callable() => {
set = Some(object.clone()); set = Some(object.clone());
@ -707,7 +709,7 @@ impl GcObject {
K: Into<PropertyKey>, K: Into<PropertyKey>,
{ {
let key = key.into(); let key = key.into();
let value = self.get(&key); let value = self.get(&key, context)?;
if value.is_null_or_undefined() { if value.is_null_or_undefined() {
return Ok(None); return Ok(None);
@ -741,7 +743,7 @@ impl GcObject {
// Return ? InstanceofOperator(O, BC). // Return ? InstanceofOperator(O, BC).
if let Some(object) = value.as_object() { 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(); let mut object = object.get_prototype_of();
while let Some(object_prototype) = object.as_object() { while let Some(object_prototype) = object.as_object() {
if GcObject::equals(&prototype, &object_prototype) { if GcObject::equals(&prototype, &object_prototype) {

29
boa/src/object/internal_methods.rs

@ -71,28 +71,30 @@ impl GcObject {
/// `[[Get]]` /// `[[Get]]`
/// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver> /// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver>
pub fn get(&self, key: &PropertyKey) -> Value { pub fn get(&self, key: &PropertyKey, context: &mut Context) -> Result<Value> {
match self.get_own_property(key) { match self.get_own_property(key) {
None => { None => {
// parent will either be null or an Object // parent will either be null or an Object
let parent = self.get_prototype_of(); let parent = self.get_prototype_of();
if parent.is_null() { 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 { Some(ref desc) => match desc {
PropertyDescriptor::Data(desc) => desc.value(), PropertyDescriptor::Data(desc) => Ok(desc.value()),
// TODO: Add accessors PropertyDescriptor::Accessor(AccessorDescriptor { get: Some(get), .. }) => {
PropertyDescriptor::Accessor(_) => Value::undefined(), get.call(&Value::from(self.clone()), &[], context)
}
_ => Ok(Value::undefined()),
}, },
} }
} }
/// `[[Set]]` /// `[[Set]]`
/// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver> /// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver>
pub fn set(&mut self, key: PropertyKey, val: Value) -> bool { pub fn set(&mut self, key: PropertyKey, val: Value, context: &mut Context) -> Result<bool> {
let _timer = BoaProfiler::global().start_event("Object::set", "object"); let _timer = BoaProfiler::global().start_event("Object::set", "object");
// Fetch property key // Fetch property key
@ -113,14 +115,17 @@ impl GcObject {
match &own_desc { match &own_desc {
PropertyDescriptor::Data(desc) => { PropertyDescriptor::Data(desc) => {
if !desc.writable() { if !desc.writable() {
return false; return Ok(false);
} }
let desc = DataDescriptor::new(val, own_desc.attributes()).into(); 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 _ => Ok(false),
PropertyDescriptor::Accessor(_) => false,
} }
} }
@ -260,7 +265,7 @@ impl GcObject {
for next_key in keys { for next_key in keys {
if let Some(prop_desc) = props.get_own_property(&next_key) { if let Some(prop_desc) = props.get_own_property(&next_key) {
if prop_desc.enumerable() { 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)?; let desc = desc_obj.to_property_descriptor(context)?;
descriptors.push((next_key, desc)); descriptors.push((next_key, desc));
} }

6
boa/src/property/mod.rs

@ -118,11 +118,11 @@ impl From<DataDescriptor> for PropertyDescriptor {
#[derive(Debug, Clone, Trace, Finalize)] #[derive(Debug, Clone, Trace, Finalize)]
pub struct AccessorDescriptor { pub struct AccessorDescriptor {
/// The function serving as getter. /// The function serving as getter.
get: Option<GcObject>, pub(crate) get: Option<GcObject>,
/// The function serving as setter. /// The function serving as setter.
set: Option<GcObject>, pub(crate) set: Option<GcObject>,
/// The attributes of the accessor descriptor. /// The attributes of the accessor descriptor.
attributes: Attribute, pub(crate) attributes: Attribute,
} }
impl AccessorDescriptor { impl AccessorDescriptor {

3
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. //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec.
use crate::object::Object;
use crate::{ use crate::{
environment::{ environment::{
declarative_environment_record::DeclarativeEnvironmentRecord, declarative_environment_record::DeclarativeEnvironmentRecord,
@ -31,7 +32,7 @@ impl Realm {
let _timer = BoaProfiler::global().start_event("Realm::create", "realm"); let _timer = BoaProfiler::global().start_event("Realm::create", "realm");
// Create brand new global object // Create brand new global object
// Global has no prototype to pass None to new_obj // 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 // Allow identification of the global object easily
global.set_data(crate::object::ObjectData::Global); global.set_data(crate::object::ObjectData::Global);

12
boa/src/syntax/ast/node/call/mod.rs

@ -65,12 +65,18 @@ impl Executable for Call {
if obj.get_type() != Type::Object { if obj.get_type() != Type::Object {
obj = Value::Object(obj.to_object(context)?); 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) => { Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?; let obj = get_field.obj().run(context)?;
let field = get_field.field().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 _ => (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() { for arg in self.args() {
if let Node::Spread(ref x) = arg { if let Node::Spread(ref x) = arg {
let val = x.run(context)?; 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); v_args.append(&mut vals);
break; // after spread we don't accept any new arguments break; // after spread we don't accept any new arguments
} }

2
boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs

@ -74,7 +74,7 @@ impl Executable for ArrowFunctionDecl {
FunctionFlags::CALLABLE FunctionFlags::CALLABLE
| FunctionFlags::CONSTRUCTABLE | FunctionFlags::CONSTRUCTABLE
| FunctionFlags::LEXICAL_THIS_MODE, | FunctionFlags::LEXICAL_THIS_MODE,
)) )?)
} }
} }

4
boa/src/syntax/ast/node/declaration/function_decl/mod.rs

@ -90,10 +90,10 @@ impl Executable for FunctionDecl {
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().to_vec(),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
); )?;
// Set the name and assign it in the current environment // Set the name and assign it in the current environment
val.set_field("name", self.name()); val.set_field("name", self.name(), context)?;
context context
.realm_mut() .realm_mut()
.environment .environment

4
boa/src/syntax/ast/node/declaration/function_expr/mod.rs

@ -90,10 +90,10 @@ impl Executable for FunctionExpr {
self.parameters().to_vec(), self.parameters().to_vec(),
self.body().to_vec(), self.body().to_vec(),
FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE,
); )?;
if let Some(name) = self.name() { if let Some(name) = self.name() {
val.set_field("name", Value::from(name)); val.set_field("name", Value::from(name), context)?;
} }
Ok(val) Ok(val)

2
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)?); obj = Value::Object(obj.to_object(context)?);
} }
Ok(obj.get_field(self.field())) Ok(obj.get_field(self.field(), context)?)
} }
} }

2
boa/src/syntax/ast/node/field/get_field/mod.rs

@ -69,7 +69,7 @@ impl Executable for GetField {
} }
let field = self.field().run(context)?; 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)?)
} }
} }

55
boa/src/syntax/ast/node/object/mod.rs

@ -3,6 +3,7 @@
use crate::{ use crate::{
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
property::{AccessorDescriptor, Attribute, PropertyDescriptor},
syntax::ast::node::{MethodDefinitionKind, Node, PropertyDefinition}, syntax::ast::node::{MethodDefinitionKind, Node, PropertyDefinition},
Context, Result, Value, Context, Result, Value,
}; };
@ -72,27 +73,53 @@ impl Object {
impl Executable for Object { impl Executable for Object {
fn run(&self, context: &mut Context) -> Result<Value> { fn run(&self, context: &mut Context) -> Result<Value> {
let global_val = &context let obj = Value::new_object(context);
.realm()
.environment
.get_global_object()
.expect("Could not get the global object");
let obj = Value::new_object(Some(global_val));
// TODO: Implement the rest of the property types. // TODO: Implement the rest of the property types.
for property in self.properties().iter() { for property in self.properties().iter() {
match property { match property {
PropertyDefinition::Property(key, value) => { 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) => { PropertyDefinition::MethodDefinition(kind, name, func) => match kind {
if let MethodDefinitionKind::Ordinary = kind { MethodDefinitionKind::Ordinary => {
obj.set_field(name.clone(), func.run(context)?); obj.set_field(name.clone(), func.run(context)?, context)?;
} else {
// TODO: Implement other types of MethodDefinitionKinds.
//unimplemented!("other types of property method definitions.");
} }
} 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), _ => {} //unimplemented!("{:?} type of property", i),
} }
} }

4
boa/src/syntax/ast/node/operator/assign/mod.rs

@ -81,13 +81,13 @@ impl Executable for Assign {
} }
Node::GetConstField(ref get_const_field) => { Node::GetConstField(ref get_const_field) => {
let val_obj = get_const_field.obj().run(context)?; 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) => { Node::GetField(ref get_field) => {
let object = get_field.obj().run(context)?; let object = get_field.obj().run(context)?;
let field = get_field.field().run(context)?; let field = get_field.field().run(context)?;
let key = field.to_property_key(context)?; let key = field.to_property_key(context)?;
object.set_field(key, val.clone()); object.set_field(key, val.clone(), context)?;
} }
_ => (), _ => (),
} }

4
boa/src/syntax/ast/node/operator/bin_op/mod.rs

@ -201,9 +201,9 @@ impl Executable for BinOp {
} }
Node::GetConstField(ref get_const_field) => { Node::GetConstField(ref get_const_field) => {
let v_r_a = get_const_field.obj().run(context)?; 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)?; 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)
} }
_ => Ok(Value::undefined()), _ => Ok(Value::undefined()),

25
boa/src/value/display.rs

@ -53,7 +53,6 @@ macro_rules! print_obj_value {
.as_data_descriptor() .as_data_descriptor()
.unwrap() .unwrap()
.value(); .value();
format!( format!(
"{:>width$}: {}", "{:>width$}: {}",
key, key,
@ -61,8 +60,14 @@ macro_rules! print_obj_value {
width = $indent, width = $indent,
) )
} else { } else {
// FIXME: handle accessor descriptors let accessor = val.as_accessor_descriptor().unwrap();
"".to_string() 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 let Value::Object(object) = v {
if object.borrow().is_error() { if object.borrow().is_error() {
let name = v.get_field("name"); let name = v
let message = v.get_field("message"); .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()); return format!("{}: {}", name.display(), message.display());
} }
} }

42
boa/src/value/mod.rs

@ -10,7 +10,7 @@ use crate::{
number::{f64_to_int32, f64_to_uint32}, number::{f64_to_int32, f64_to_uint32},
BigInt, Number, BigInt, Number,
}, },
object::{GcObject, Object, ObjectData, PROTOTYPE}, object::{GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey}, property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
}; };
@ -158,17 +158,9 @@ impl Value {
} }
/// Returns a new empty object /// 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"); let _timer = BoaProfiler::global().start_event("new_object", "value");
context.construct_object().into()
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())
}
} }
/// Convert from a JSON value to a JS value /// Convert from a JSON value to a JS value
@ -205,7 +197,7 @@ impl Value {
new_obj new_obj
} }
JSONValue::Object(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() { for (key, json) in obj.into_iter() {
let value = Self::from_json(json, context); let value = Self::from_json(json, context);
new_obj.set_property( new_obj.set_property(
@ -224,7 +216,7 @@ impl Value {
/// Converts the `Value` to `JSON`. /// Converts the `Value` to `JSON`.
pub fn to_json(&self, context: &mut Context) -> Result<JSONValue> { pub fn to_json(&self, context: &mut Context) -> Result<JSONValue> {
let to_json = self.get_field("toJSON"); let to_json = self.get_field("toJSON", context)?;
if to_json.is_function() { if to_json.is_function() {
let json_value = context.call(&to_json, self, &[])?; let json_value = context.call(&to_json, self, &[])?;
return json_value.to_json(context); 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 /// 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]]` /// 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 /// TODO: this function should use the get Value if its set
pub fn get_field<K>(&self, key: K) -> Self pub fn get_field<K>(&self, key: K, context: &mut Context) -> Result<Self>
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
{ {
let _timer = BoaProfiler::global().start_event("Value::get_field", "value"); let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
let key = key.into(); if let Self::Object(ref obj) = *self {
match self.get_property(key) { obj.clone().get(&key.into(), context)
Some(ref desc) => match desc { } else {
// TODO: Add accessors Ok(Value::undefined())
PropertyDescriptor::Accessor(_) => Value::undefined(),
PropertyDescriptor::Data(desc) => desc.value(),
},
None => Value::undefined(),
} }
} }
@ -486,7 +474,7 @@ impl Value {
/// Set the field in the value /// Set the field in the value
#[inline] #[inline]
pub fn set_field<K, V>(&self, key: K, value: V) -> Value pub fn set_field<K, V>(&self, key: K, value: V, context: &mut Context) -> Result<Value>
where where
K: Into<PropertyKey>, K: Into<PropertyKey>,
V: Into<Value>, V: Into<Value>,
@ -497,15 +485,15 @@ impl Value {
if let Self::Object(ref obj) = *self { if let Self::Object(ref obj) = *self {
if let PropertyKey::Index(index) = key { if let PropertyKey::Index(index) = key {
if obj.is_array() { 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 { 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. /// Set the kind of an object.

29
boa/src/value/tests.rs

@ -8,7 +8,8 @@ use std::hash::{Hash, Hasher};
#[test] #[test]
fn is_object() { 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); assert_eq!(val.is_object(), true);
} }
@ -29,11 +30,18 @@ fn undefined() {
#[test] #[test]
fn get_set_field() { 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 // Create string and convert it to a Value
let s = Value::from("bar"); let s = Value::from("bar");
obj.set_field("foo", s); obj.set_field("foo", s, &mut context).unwrap();
assert_eq!(obj.get_field("foo").display().to_string(), "\"bar\""); assert_eq!(
obj.get_field("foo", &mut context)
.unwrap()
.display()
.to_string(),
"\"bar\""
);
} }
#[test] #[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 /// Test cyclic conversions that previously caused stack overflows
/// Relevant mitigations for these are in `GcObject::ordinary_to_primitive` and /// Relevant mitigations for these are in `GcObject::ordinary_to_primitive` and
/// `GcObject::to_json` /// `GcObject::to_json`

Loading…
Cancel
Save