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

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

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

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

@ -62,7 +62,7 @@ impl EvalError {
context: &mut Context,
) -> Result<Value> {
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

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

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

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

@ -60,7 +60,7 @@ impl RangeError {
context: &mut Context,
) -> Result<Value> {
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

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

@ -59,7 +59,7 @@ impl ReferenceError {
context: &mut Context,
) -> Result<Value> {
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

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

@ -62,7 +62,7 @@ impl SyntaxError {
context: &mut Context,
) -> Result<Value> {
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

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

@ -65,7 +65,7 @@ impl TypeError {
context: &mut Context,
) -> Result<Value> {
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

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

@ -61,7 +61,7 @@ impl UriError {
context: &mut Context,
) -> Result<Value> {
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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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> {

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

85
boa/src/context.rs

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

6
boa/src/environment/object_environment_record.rs

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

2
boa/src/exec/tests.rs

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

28
boa/src/object/gcobject.rs

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

29
boa/src/object/internal_methods.rs

@ -71,28 +71,30 @@ impl GcObject {
/// `[[Get]]`
/// <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) {
None => {
// parent will either be null or an Object
let parent = self.get_prototype_of();
if parent.is_null() {
return Value::undefined();
return Ok(Value::undefined());
}
parent.get_field(key.clone())
Ok(parent.get_field(key.clone(), context)?)
}
Some(ref desc) => match desc {
PropertyDescriptor::Data(desc) => desc.value(),
// TODO: Add accessors
PropertyDescriptor::Accessor(_) => Value::undefined(),
PropertyDescriptor::Data(desc) => Ok(desc.value()),
PropertyDescriptor::Accessor(AccessorDescriptor { get: Some(get), .. }) => {
get.call(&Value::from(self.clone()), &[], context)
}
_ => Ok(Value::undefined()),
},
}
}
/// `[[Set]]`
/// <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");
// Fetch property key
@ -113,14 +115,17 @@ impl GcObject {
match &own_desc {
PropertyDescriptor::Data(desc) => {
if !desc.writable() {
return false;
return Ok(false);
}
let desc = DataDescriptor::new(val, own_desc.attributes()).into();
self.define_own_property(key, desc)
Ok(self.define_own_property(key, desc))
}
PropertyDescriptor::Accessor(AccessorDescriptor { set: Some(set), .. }) => {
set.call(&Value::from(self.clone()), &[val], context)?;
Ok(true)
}
// TODO: Add accessors
PropertyDescriptor::Accessor(_) => false,
_ => Ok(false),
}
}
@ -260,7 +265,7 @@ impl GcObject {
for next_key in keys {
if let Some(prop_desc) = props.get_own_property(&next_key) {
if prop_desc.enumerable() {
let desc_obj = props.get(&next_key);
let desc_obj = props.get(&next_key, context)?;
let desc = desc_obj.to_property_descriptor(context)?;
descriptors.push((next_key, desc));
}

6
boa/src/property/mod.rs

@ -118,11 +118,11 @@ impl From<DataDescriptor> for PropertyDescriptor {
#[derive(Debug, Clone, Trace, Finalize)]
pub struct AccessorDescriptor {
/// The function serving as getter.
get: Option<GcObject>,
pub(crate) get: Option<GcObject>,
/// The function serving as setter.
set: Option<GcObject>,
pub(crate) set: Option<GcObject>,
/// The attributes of the accessor descriptor.
attributes: Attribute,
pub(crate) attributes: Attribute,
}
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.
use crate::object::Object;
use crate::{
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
@ -31,7 +32,7 @@ impl Realm {
let _timer = BoaProfiler::global().start_event("Realm::create", "realm");
// Create brand new global object
// Global has no prototype to pass None to new_obj
let global = Value::new_object(None);
let global = Value::from(Object::default());
// Allow identification of the global object easily
global.set_data(crate::object::ObjectData::Global);

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

@ -65,12 +65,18 @@ impl Executable for Call {
if obj.get_type() != Type::Object {
obj = Value::Object(obj.to_object(context)?);
}
(obj.clone(), obj.get_field(get_const_field.field()))
(
obj.clone(),
obj.get_field(get_const_field.field(), context)?,
)
}
Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?;
let field = get_field.field().run(context)?;
(obj.clone(), obj.get_field(field.to_property_key(context)?))
(
obj.clone(),
obj.get_field(field.to_property_key(context)?, context)?,
)
}
_ => (context.global_object().clone(), self.expr().run(context)?), // 'this' binding should come from the function's self-contained environment
};
@ -78,7 +84,7 @@ impl Executable for Call {
for arg in self.args() {
if let Node::Spread(ref x) = arg {
let val = x.run(context)?;
let mut vals = context.extract_array_properties(&val).unwrap();
let mut vals = context.extract_array_properties(&val)?.unwrap();
v_args.append(&mut vals);
break; // after spread we don't accept any new arguments
}

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

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

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

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

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

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

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

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

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

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) => {
let v_r_a = get_const_field.obj().run(context)?;
let v_a = v_r_a.get_field(get_const_field.field());
let v_a = v_r_a.get_field(get_const_field.field(), context)?;
let value = Self::run_assign(op, v_a, self.rhs(), context)?;
v_r_a.set_field(get_const_field.field(), value.clone());
v_r_a.set_field(get_const_field.field(), value.clone(), context)?;
Ok(value)
}
_ => Ok(Value::undefined()),

25
boa/src/value/display.rs

@ -53,7 +53,6 @@ macro_rules! print_obj_value {
.as_data_descriptor()
.unwrap()
.value();
format!(
"{:>width$}: {}",
key,
@ -61,8 +60,14 @@ macro_rules! print_obj_value {
width = $indent,
)
} else {
// FIXME: handle accessor descriptors
"".to_string()
let accessor = val.as_accessor_descriptor().unwrap();
let display = match (accessor.setter().is_some(), accessor.getter().is_some()) {
(true, true) => "Getter & Setter",
(true, false) => "Setter",
(false, true) => "Getter",
_ => "No Getter/Setter"
};
format!("{:>width$}: {}", key, display, width = $indent)
}
})
};
@ -189,8 +194,18 @@ pub(crate) fn display_obj(v: &Value, print_internals: bool) -> String {
if let Value::Object(object) = v {
if object.borrow().is_error() {
let name = v.get_field("name");
let message = v.get_field("message");
let name = v
.get_property("name")
.as_ref()
.and_then(|p| p.as_data_descriptor())
.map(|d| d.value())
.unwrap_or_else(Value::undefined);
let message = v
.get_property("message")
.as_ref()
.and_then(|p| p.as_data_descriptor())
.map(|d| d.value())
.unwrap_or_else(Value::undefined);
return format!("{}: {}", name.display(), message.display());
}
}

42
boa/src/value/mod.rs

@ -10,7 +10,7 @@ use crate::{
number::{f64_to_int32, f64_to_uint32},
BigInt, Number,
},
object::{GcObject, Object, ObjectData, PROTOTYPE},
object::{GcObject, Object, ObjectData},
property::{Attribute, DataDescriptor, PropertyDescriptor, PropertyKey},
BoaProfiler, Context, Result,
};
@ -158,17 +158,9 @@ impl Value {
}
/// Returns a new empty object
pub fn new_object(global: Option<&Value>) -> Self {
pub fn new_object(context: &Context) -> Self {
let _timer = BoaProfiler::global().start_event("new_object", "value");
if let Some(global) = global {
let object_prototype = global.get_field("Object").get_field(PROTOTYPE);
let object = Object::create(object_prototype);
Self::object(object)
} else {
Self::object(Object::default())
}
context.construct_object().into()
}
/// Convert from a JSON value to a JS value
@ -205,7 +197,7 @@ impl Value {
new_obj
}
JSONValue::Object(obj) => {
let new_obj = Value::new_object(Some(context.global_object()));
let new_obj = Value::new_object(context);
for (key, json) in obj.into_iter() {
let value = Self::from_json(json, context);
new_obj.set_property(
@ -224,7 +216,7 @@ impl Value {
/// Converts the `Value` to `JSON`.
pub fn to_json(&self, context: &mut Context) -> Result<JSONValue> {
let to_json = self.get_field("toJSON");
let to_json = self.get_field("toJSON", context)?;
if to_json.is_function() {
let json_value = context.call(&to_json, self, &[])?;
return json_value.to_json(context);
@ -456,19 +448,15 @@ impl Value {
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
/// get_field receives a Property from get_prop(). It should then return the `[[Get]]` result value if that's set, otherwise fall back to `[[Value]]`
/// TODO: this function should use the get Value if its set
pub fn get_field<K>(&self, key: K) -> Self
pub fn get_field<K>(&self, key: K, context: &mut Context) -> Result<Self>
where
K: Into<PropertyKey>,
{
let _timer = BoaProfiler::global().start_event("Value::get_field", "value");
let key = key.into();
match self.get_property(key) {
Some(ref desc) => match desc {
// TODO: Add accessors
PropertyDescriptor::Accessor(_) => Value::undefined(),
PropertyDescriptor::Data(desc) => desc.value(),
},
None => Value::undefined(),
if let Self::Object(ref obj) = *self {
obj.clone().get(&key.into(), context)
} else {
Ok(Value::undefined())
}
}
@ -486,7 +474,7 @@ impl Value {
/// Set the field in the value
#[inline]
pub fn set_field<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
K: Into<PropertyKey>,
V: Into<Value>,
@ -497,15 +485,15 @@ impl Value {
if let Self::Object(ref obj) = *self {
if let PropertyKey::Index(index) = key {
if obj.is_array() {
let len = self.get_field("length").as_number().unwrap() as u32;
let len = self.get_field("length", context)?.as_number().unwrap() as u32;
if len < index + 1 {
self.set_field("length", index + 1);
self.set_field("length", index + 1, context)?;
}
}
}
obj.clone().set(key, value.clone());
obj.clone().set(key, value.clone(), context)?;
}
value
Ok(value)
}
/// Set the kind of an object.

29
boa/src/value/tests.rs

@ -8,7 +8,8 @@ use std::hash::{Hash, Hasher};
#[test]
fn is_object() {
let val = Value::new_object(None);
let context = Context::new();
let val = Value::new_object(&context);
assert_eq!(val.is_object(), true);
}
@ -29,11 +30,18 @@ fn undefined() {
#[test]
fn get_set_field() {
let obj = Value::new_object(None);
let mut context = Context::new();
let obj = Value::new_object(&context);
// Create string and convert it to a Value
let s = Value::from("bar");
obj.set_field("foo", s);
assert_eq!(obj.get_field("foo").display().to_string(), "\"bar\"");
obj.set_field("foo", s, &mut context).unwrap();
assert_eq!(
obj.get_field("foo", &mut context)
.unwrap()
.display()
.to_string(),
"\"bar\""
);
}
#[test]
@ -598,6 +606,19 @@ fn to_integer_or_infinity() {
);
}
#[test]
fn test_accessors() {
let mut context = Context::new();
let src = r#"
let arr = [];
let a = { get b() { return "c" }, set b(value) { arr = arr.concat([value]) }} ;
a.b = "a";
"#;
context.eval(src).unwrap();
assert_eq!(forward(&mut context, "a.b"), r#""c""#);
assert_eq!(forward(&mut context, "arr"), r#"[ "a" ]"#);
}
/// Test cyclic conversions that previously caused stack overflows
/// Relevant mitigations for these are in `GcObject::ordinary_to_primitive` and
/// `GcObject::to_json`

Loading…
Cancel
Save