Browse Source

Proposal of new `Object` design (#1354)

* Implement `InternalObjectMethods` struct for `Object`

* Implement remaining string internal methods

* Split object internal methods and operations in modules

* Store internal object methods as static struct

* Document `ObjectData` and order indices on `[[OwnPropertyKeys]]`

* Document and rearrange internal object methods
pull/1518/head
jedel1043 3 years ago committed by GitHub
parent
commit
8afd50fb22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 2
      boa/src/builtins/array/array_iterator.rs
  3. 10
      boa/src/builtins/array/mod.rs
  4. 4
      boa/src/builtins/array/tests.rs
  5. 10
      boa/src/builtins/bigint/mod.rs
  6. 2
      boa/src/builtins/boolean/mod.rs
  7. 44
      boa/src/builtins/date/mod.rs
  8. 4
      boa/src/builtins/date/tests.rs
  9. 2
      boa/src/builtins/error/eval.rs
  10. 2
      boa/src/builtins/error/mod.rs
  11. 2
      boa/src/builtins/error/range.rs
  12. 2
      boa/src/builtins/error/reference.rs
  13. 2
      boa/src/builtins/error/syntax.rs
  14. 2
      boa/src/builtins/error/type.rs
  15. 2
      boa/src/builtins/error/uri.rs
  16. 20
      boa/src/builtins/function/mod.rs
  17. 2
      boa/src/builtins/map/map_iterator.rs
  18. 4
      boa/src/builtins/map/mod.rs
  19. 2
      boa/src/builtins/number/mod.rs
  20. 8
      boa/src/builtins/object/for_in_iterator.rs
  21. 102
      boa/src/builtins/object/mod.rs
  22. 55
      boa/src/builtins/reflect/mod.rs
  23. 2
      boa/src/builtins/regexp/mod.rs
  24. 2
      boa/src/builtins/regexp/regexp_string_iterator.rs
  25. 4
      boa/src/builtins/set/mod.rs
  26. 2
      boa/src/builtins/set/set_iterator.rs
  27. 48
      boa/src/builtins/string/mod.rs
  28. 2
      boa/src/builtins/string/string_iterator.rs
  29. 2
      boa/src/class.rs
  30. 6
      boa/src/context.rs
  31. 197
      boa/src/object/gcobject.rs
  32. 983
      boa/src/object/internal_methods.rs
  33. 248
      boa/src/object/internal_methods/array.rs
  34. 861
      boa/src/object/internal_methods/mod.rs
  35. 189
      boa/src/object/internal_methods/string.rs
  36. 521
      boa/src/object/mod.rs
  37. 487
      boa/src/object/operations.rs
  38. 2
      boa/src/realm.rs
  39. 30
      boa/src/string.rs
  40. 2
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  41. 4
      boa/src/syntax/ast/node/operator/unary_op/mod.rs
  42. 29
      boa/src/value/display.rs
  43. 76
      boa/src/value/mod.rs
  44. 6
      boa/src/value/tests.rs
  45. 3
      boa/src/vm/mod.rs

1
.gitignore vendored

@ -24,6 +24,7 @@ tests/js/test.js
*.string_index
*.events
chrome_profiler.json
*.mm_profdata
# Logs
*.log

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

@ -45,7 +45,7 @@ impl ArrayIterator {
kind: PropertyNameKind,
) -> JsValue {
let array_iterator = JsValue::new_object(context);
array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind)));
array_iterator.set_data(ObjectData::array_iterator(Self::new(array, kind)));
array_iterator
.as_object()
.expect("array iterator object")

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

@ -227,11 +227,12 @@ impl Array {
array.set_prototype_instance(prototype.into());
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
array.borrow_mut().data = ObjectData::Array;
array.borrow_mut().data = ObjectData::array();
// 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
array.ordinary_define_own_property(
crate::object::internal_methods::ordinary_define_own_property(
&array,
"length".into(),
PropertyDescriptor::builder()
.value(length)
@ -239,7 +240,8 @@ impl Array {
.enumerable(false)
.configurable(false)
.build(),
);
context,
)?;
Ok(array)
}
@ -274,7 +276,7 @@ impl Array {
/// Creates a new `Array` instance.
pub(crate) fn new_array(context: &Context) -> JsValue {
let array = JsValue::new_object(context);
array.set_data(ObjectData::Array);
array.set_data(ObjectData::array());
array
.as_object()
.expect("'array' should be an object")

4
boa/src/builtins/array/tests.rs

@ -1542,9 +1542,9 @@ fn get_relative_end() {
#[test]
fn array_length_is_not_enumerable() {
let context = Context::new();
let mut context = Context::new();
let array = Array::new_array(&context);
let array = Array::new_array(&mut context);
let desc = array.get_property("length").unwrap();
assert!(!desc.expect_enumerable());
}

10
boa/src/builtins/bigint/mod.rs

@ -13,12 +13,8 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
use crate::{
builtins::BuiltIn,
object::{ConstructorBuilder, ObjectData},
property::Attribute,
symbol::WellKnownSymbols,
value::IntegerOrInfinity,
BoaProfiler, Context, JsBigInt, JsResult, JsValue,
builtins::BuiltIn, object::ConstructorBuilder, property::Attribute, symbol::WellKnownSymbols,
value::IntegerOrInfinity, BoaProfiler, Context, JsBigInt, JsResult, JsValue,
};
#[cfg(test)]
mod tests;
@ -105,7 +101,7 @@ impl BigInt {
// a. Assert: Type(value.[[BigIntData]]) is BigInt.
// b. Return value.[[BigIntData]].
JsValue::Object(ref object) => {
if let ObjectData::BigInt(ref bigint) = object.borrow().data {
if let Some(bigint) = object.borrow().as_bigint() {
return Ok(bigint.clone());
}
}

2
boa/src/builtins/boolean/mod.rs

@ -81,7 +81,7 @@ impl Boolean {
.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
boolean.set_data(ObjectData::Boolean(data));
boolean.set_data(ObjectData::boolean(data));
Ok(boolean)
}

44
boa/src/builtins/date/mod.rs

@ -386,7 +386,7 @@ impl Date {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
pub(crate) fn make_date_now(this: &JsValue) -> JsValue {
let date = Date::default();
this.set_data(ObjectData::Date(date));
this.set_data(ObjectData::date(date));
this.clone()
}
@ -428,7 +428,7 @@ impl Date {
let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
let date = Date(tv);
this.set_data(ObjectData::Date(date));
this.set_data(ObjectData::date(date));
Ok(this.clone())
}
@ -468,7 +468,7 @@ impl Date {
// If any of the args are infinity or NaN, return an invalid date.
if !check_normal_opt!(year, month, day, hour, min, sec, milli) {
let date = Date(None);
this.set_data(ObjectData::Date(date));
this.set_data(ObjectData::date(date));
return Ok(this.clone());
}
@ -494,7 +494,7 @@ impl Date {
Some(milli),
);
this.set_data(ObjectData::Date(date));
this.set_data(ObjectData::date(date));
Ok(this.clone())
}
@ -872,7 +872,7 @@ impl Date {
let u = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 6. Return u.
Ok(u.into())
@ -933,7 +933,7 @@ impl Date {
let u = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 9. Return u.
Ok(u.into())
@ -990,7 +990,7 @@ impl Date {
let u = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 9. Return u.
Ok(u.into())
@ -1028,7 +1028,7 @@ impl Date {
let u = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 6. Return u.
Ok(u.into())
@ -1080,7 +1080,7 @@ impl Date {
let u = t.get_time();
// 7. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 8. Return u.
Ok(u.into())
@ -1121,7 +1121,7 @@ impl Date {
let u = t.get_time();
// 6. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 7. Return u.
Ok(u.into())
@ -1166,7 +1166,7 @@ impl Date {
let u = t.get_time();
// 6. Set the [[DateValue]] internal slot of this Date object to u.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 7. Return u.
Ok(u.into())
@ -1204,7 +1204,7 @@ impl Date {
// 4. If y is NaN, then
if y.is_nan() {
// a. Set the [[DateValue]] internal slot of this Date object to NaN.
this.set_data(ObjectData::Date(Date(None)));
this.set_data(ObjectData::date(Date(None)));
// b. Return NaN.
return Ok(JsValue::nan());
@ -1222,7 +1222,7 @@ impl Date {
t.set_components(false, Some(y), None, None, None, None, None, None);
// 10. Set the [[DateValue]] internal slot of this Date object to TimeClip(date).
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 11. Return the value of the [[DateValue]] internal slot of this Date object.
Ok(t.get_time().into())
@ -1260,7 +1260,7 @@ impl Date {
let v = t.get_time();
// 4. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 5. Return v.
Ok(v.into())
@ -1298,7 +1298,7 @@ impl Date {
let v = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 6. Return v.
Ok(v.into())
@ -1359,7 +1359,7 @@ impl Date {
let v = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 9. Return v.
Ok(v.into())
@ -1420,7 +1420,7 @@ impl Date {
let v = t.get_time();
// 8. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 9. Return v.
Ok(v.into())
@ -1458,7 +1458,7 @@ impl Date {
let v = t.get_time();
// 5. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 6. Return v.
Ok(v.into())
@ -1514,7 +1514,7 @@ impl Date {
let v = t.get_time();
// 9. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 10. Return v.
Ok(v.into())
@ -1561,7 +1561,7 @@ impl Date {
let v = t.get_time();
// 7. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 8. Return v.
Ok(v.into())
@ -1608,7 +1608,7 @@ impl Date {
let v = t.get_time();
// 7. Set the [[DateValue]] internal slot of this Date object to v.
this.set_data(ObjectData::Date(t));
this.set_data(ObjectData::date(t));
// 8. Return v.
Ok(v.into())
@ -1928,7 +1928,7 @@ impl Date {
#[inline]
pub fn this_time_value(value: &JsValue, context: &mut Context) -> JsResult<Date> {
if let JsValue::Object(ref object) = value {
if let ObjectData::Date(ref date) = object.borrow().data {
if let Some(date) = object.borrow().as_date() {
return Ok(*date);
}
}

4
boa/src/builtins/date/tests.rs

@ -1,6 +1,6 @@
#![allow(clippy::zero_prefixed_literal)]
use crate::{forward, forward_val, object::ObjectData, Context, JsValue};
use crate::{forward, forward_val, Context, JsValue};
use chrono::prelude::*;
// NOTE: Javascript Uses 0-based months, where chrono uses 1-based months. Many of the assertions look wrong because of
@ -14,7 +14,7 @@ fn forward_dt_utc(context: &mut Context, src: &str) -> Option<NaiveDateTime> {
};
if let JsValue::Object(ref date_time) = date_time {
if let ObjectData::Date(ref date_time) = date_time.borrow().data {
if let Some(date_time) = date_time.borrow().as_date() {
date_time.0
} else {
panic!("expected date")

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

@ -82,7 +82,7 @@ impl EvalError {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
this.set_data(ObjectData::error());
Ok(this)
}
}

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

@ -98,7 +98,7 @@ impl Error {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
this.set_data(ObjectData::error());
Ok(this)
}

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

@ -79,7 +79,7 @@ impl RangeError {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
this.set_data(ObjectData::error());
Ok(this)
}
}

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

@ -78,7 +78,7 @@ impl ReferenceError {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
this.set_data(ObjectData::error());
Ok(this)
}
}

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

@ -81,7 +81,7 @@ impl SyntaxError {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
this.set_data(ObjectData::error());
Ok(this)
}
}

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

@ -84,7 +84,7 @@ impl TypeError {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
this.set_data(ObjectData::error());
Ok(this)
}
}

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

@ -80,7 +80,7 @@ impl UriError {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Error);
this.set_data(ObjectData::error());
Ok(this)
}
}

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

@ -22,6 +22,7 @@ use crate::{
BoaProfiler, Context, JsResult, JsValue,
};
use bitflags::bitflags;
use std::fmt::{self, Debug};
use std::rc::Rc;
@ -177,7 +178,10 @@ impl Function {
/// Arguments.
///
/// <https://tc39.es/ecma262/#sec-createunmappedargumentsobject>
pub fn create_unmapped_arguments_object(arguments_list: &[JsValue]) -> JsValue {
pub fn create_unmapped_arguments_object(
arguments_list: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let len = arguments_list.len();
let obj = JsObject::new(Object::default());
// Set length
@ -187,7 +191,12 @@ pub fn create_unmapped_arguments_object(arguments_list: &[JsValue]) -> JsValue {
.enumerable(false)
.configurable(true);
// Define length as a property
obj.ordinary_define_own_property("length".into(), length.into());
crate::object::internal_methods::ordinary_define_own_property(
&obj,
"length".into(),
length.into(),
context,
)?;
let mut index: usize = 0;
while index < len {
let val = arguments_list.get(index).expect("Could not get argument");
@ -201,7 +210,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[JsValue]) -> JsValue {
index += 1;
}
JsValue::new(obj)
Ok(JsValue::new(obj))
}
/// Creates a new member function of a `Object` or `prototype`.
@ -288,7 +297,7 @@ impl BuiltInFunctionObject {
.expect("this should be an object")
.set_prototype_instance(prototype.into());
this.set_data(ObjectData::Function(Function::Native {
this.set_data(ObjectData::function(Function::Native {
function: BuiltInFunction(|_, _, _| Ok(JsValue::undefined())),
constructable: true,
}));
@ -340,9 +349,6 @@ impl BuiltInFunctionObject {
// TODO?: 3.a. PrepareForTailCall
return context.call(this, &this_arg, &[]);
}
let arg_array = arg_array.as_object().ok_or_else(|| {
context.construct_type_error("argList must be null, undefined or an object")
})?;
let arg_list = arg_array.create_list_from_array_like(&[], context)?;
// TODO?: 5. PrepareForTailCall
context.call(this, &this_arg, &arg_list)

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

@ -50,7 +50,7 @@ impl MapIterator {
kind: PropertyNameKind,
) -> JsResult<JsValue> {
let map_iterator = JsValue::new_object(context);
map_iterator.set_data(ObjectData::MapIterator(Self::new(map, kind, context)?));
map_iterator.set_data(ObjectData::map_iterator(Self::new(map, kind, context)?));
map_iterator
.as_object()
.expect("map iterator object")

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

@ -167,7 +167,7 @@ impl Map {
// This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name)
this.set_data(ObjectData::Map(data));
this.set_data(ObjectData::map(data));
Ok(this)
}
@ -339,7 +339,7 @@ impl Map {
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.clear
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear
pub(crate) fn clear(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
this.set_data(ObjectData::Map(OrderedMap::new()));
this.set_data(ObjectData::map(OrderedMap::new()));
Self::set_size(this, 0);

2
boa/src/builtins/number/mod.rs

@ -179,7 +179,7 @@ impl Number {
this.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
this.set_data(ObjectData::Number(data));
this.set_data(ObjectData::number(data));
Ok(this)
}

8
boa/src/builtins/object/for_in_iterator.rs

@ -47,7 +47,7 @@ impl ForInIterator {
/// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
pub(crate) fn create_for_in_iterator(context: &Context, object: JsValue) -> JsValue {
let for_in_iterator = JsValue::new_object(context);
for_in_iterator.set_data(ObjectData::ForInIterator(Self::new(object)));
for_in_iterator.set_data(ObjectData::for_in_iterator(Self::new(object)));
for_in_iterator
.as_object()
.expect("for in iterator object")
@ -70,7 +70,7 @@ impl ForInIterator {
let mut object = iterator.object.to_object(context)?;
loop {
if !iterator.object_was_visited {
let keys = object.own_property_keys();
let keys = object.__own_property_keys__(context)?;
for k in keys {
match k {
PropertyKey::String(ref k) => {
@ -86,8 +86,8 @@ impl ForInIterator {
}
while let Some(r) = iterator.remaining_keys.pop_front() {
if !iterator.visited_keys.contains(&r) {
if let Some(desc) =
object.__get_own_property__(&PropertyKey::from(r.clone()))
if let Some(desc) = object
.__get_own_property__(&PropertyKey::from(r.clone()), context)?
{
iterator.visited_keys.insert(r.clone());
if desc.expect_enumerable() {

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

@ -16,9 +16,10 @@
use crate::{
builtins::BuiltIn,
object::{
ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer, PROTOTYPE,
ConstructorBuilder, JsObject, Object as BuiltinObject, ObjectData, ObjectInitializer,
ObjectKind, PROTOTYPE,
},
property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyNameKind},
property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::{JsValue, Type},
BoaProfiler, Context, JsResult,
@ -132,9 +133,9 @@ impl Object {
let properties = args.get(1).cloned().unwrap_or_else(JsValue::undefined);
let obj = match prototype {
JsValue::Object(_) | JsValue::Null => JsValue::new(BuiltinObject::with_prototype(
JsValue::Object(_) | JsValue::Null => JsObject::new(BuiltinObject::with_prototype(
prototype,
ObjectData::Ordinary,
ObjectData::ordinary(),
)),
_ => {
return context.throw_type_error(format!(
@ -145,10 +146,11 @@ impl Object {
};
if !properties.is_undefined() {
return Object::define_properties(&JsValue::undefined(), &[obj, properties], context);
object_define_properties(&obj, properties, context)?;
return Ok(obj.into());
}
Ok(obj)
Ok(obj.into())
}
/// `Object.getOwnPropertyDescriptor( object, property )`
@ -173,7 +175,7 @@ impl Object {
if let Some(key) = args.get(1) {
let key = key.to_property_key(context)?;
if let Some(desc) = object.__get_own_property__(&key) {
if let Some(desc) = object.__get_own_property__(&key, context)? {
return Ok(Self::from_property_descriptor(desc, context));
}
}
@ -205,7 +207,7 @@ impl Object {
for key in object.borrow().properties().keys() {
let descriptor = {
let desc = object
.__get_own_property__(&key)
.__get_own_property__(&key, context)?
.expect("Expected property to be on object.");
Self::from_property_descriptor(desc, context)
};
@ -328,7 +330,7 @@ impl Object {
let status = obj
.as_object()
.expect("obj was not an object")
.__set_prototype_of__(proto);
.__set_prototype_of__(proto, ctx)?;
// 5. If status is false, throw a TypeError exception.
if !status {
@ -413,9 +415,9 @@ impl Object {
) -> JsResult<JsValue> {
let arg = args.get(0).cloned().unwrap_or_default();
let arg_obj = arg.as_object();
if let Some(mut obj) = arg_obj {
if let Some(obj) = arg_obj {
let props = args.get(1).cloned().unwrap_or_else(JsValue::undefined);
obj.define_properties(props, context)?;
object_define_properties(&obj, props, context)?;
Ok(arg)
} else {
context.throw_type_error("Expected an object")
@ -471,16 +473,16 @@ impl Object {
// 14. Else, let builtinTag be "Object".
let builtin_tag = {
let o = o.borrow();
match &o.data {
ObjectData::Array => "Array",
match o.kind() {
ObjectKind::Array => "Array",
// TODO: Arguments Exotic Objects are currently not supported
ObjectData::Function(_) => "Function",
ObjectData::Error => "Error",
ObjectData::Boolean(_) => "Boolean",
ObjectData::Number(_) => "Number",
ObjectData::String(_) => "String",
ObjectData::Date(_) => "Date",
ObjectData::RegExp(_) => "RegExp",
ObjectKind::Function(_) => "Function",
ObjectKind::Error => "Error",
ObjectKind::Boolean(_) => "Boolean",
ObjectKind::Number(_) => "Number",
ObjectKind::String(_) => "String",
ObjectKind::Date(_) => "Date",
ObjectKind::RegExp(_) => "RegExp",
_ => "Object",
}
};
@ -542,7 +544,9 @@ impl Object {
};
let key = key.to_property_key(context)?;
let own_property = this.to_object(context)?.__get_own_property__(&key);
let own_property = this
.to_object(context)?
.__get_own_property__(&key, context)?;
Ok(own_property.map_or(JsValue::new(false), |own_prop| {
JsValue::new(own_prop.enumerable())
@ -580,11 +584,11 @@ impl Object {
// 3.a.i. Let from be ! ToObject(nextSource).
let from = source.to_object(context).unwrap();
// 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]]().
let keys = from.own_property_keys();
let keys = from.__own_property_keys__(context)?;
// 3.a.iii. For each element nextKey of keys, do
for key in keys {
// 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey).
if let Some(desc) = from.__get_own_property__(&key) {
if let Some(desc) = from.__get_own_property__(&key, context)? {
// 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then
if desc.expect_enumerable() {
// 3.a.iii.2.a. Let propValue be ? Get(from, nextKey).
@ -684,3 +688,55 @@ impl Object {
Ok(result.into())
}
}
/// The abstract operation ObjectDefineProperties
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
#[inline]
fn object_define_properties(
object: &JsObject,
props: JsValue,
context: &mut Context,
) -> JsResult<()> {
// 1. Assert: Type(O) is Object.
// 2. Let props be ? ToObject(Properties).
let props = &props.to_object(context)?;
// 3. Let keys be ? props.[[OwnPropertyKeys]]().
let keys = props.__own_property_keys__(context)?;
// 4. Let descriptors be a new empty List.
let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new();
// 5. For each element nextKey of keys, do
for next_key in keys {
// a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
// b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
if let Some(prop_desc) = props.__get_own_property__(&next_key, context)? {
if prop_desc.expect_enumerable() {
// i. Let descObj be ? Get(props, nextKey).
let desc_obj = props.get(next_key.clone(), context)?;
// ii. Let desc be ? ToPropertyDescriptor(descObj).
let desc = desc_obj.to_property_descriptor(context)?;
// iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors.
descriptors.push((next_key, desc));
}
}
}
// 6. For each element pair of descriptors, do
// a. Let P be the first element of pair.
// b. Let desc be the second element of pair.
for (p, d) in descriptors {
// c. Perform ? DefinePropertyOrThrow(O, P, desc).
object.define_property_or_throw(p, d, context)?;
}
// 7. Return O.
Ok(())
}

55
boa/src/builtins/reflect/mod.rs

@ -12,12 +12,14 @@
use crate::{
builtins::{self, BuiltIn},
object::{Object, ObjectData, ObjectInitializer},
property::{Attribute, PropertyDescriptor},
object::ObjectInitializer,
property::Attribute,
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsValue,
};
use super::Array;
#[cfg(test)]
mod tests;
@ -75,22 +77,18 @@ impl Reflect {
/// [spec]: https://tc39.es/ecma262/#sec-reflect.apply
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply
pub(crate) fn apply(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let undefined = JsValue::undefined();
let target = args
.get(0)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be a function"))?;
let this_arg = args.get(1).unwrap_or(&undefined);
let args_list = args
.get(2)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("args list must be an object"))?;
let this_arg = args.get(1).cloned().unwrap_or_default();
let args_list = args.get(2).cloned().unwrap_or_default();
if !target.is_callable() {
return context.throw_type_error("target must be a function");
}
let args = args_list.create_list_from_array_like(&[], context)?;
target.call(this_arg, &args, context)
target.call(&this_arg, &args, context)
}
/// Calls a target function as a constructor with arguments.
@ -110,10 +108,7 @@ impl Reflect {
.get(0)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be a function"))?;
let args_list = args
.get(1)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("args list must be an object"))?;
let args_list = args.get(1).cloned().unwrap_or_default();
if !target.is_constructable() {
return context.throw_type_error("target must be a constructor");
@ -182,7 +177,7 @@ impl Reflect {
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
let key = args.get(1).unwrap_or(&undefined).to_property_key(context)?;
Ok(target.__delete__(&key).into())
Ok(target.__delete__(&key, context)?.into())
}
/// Gets a property of an object.
@ -252,7 +247,7 @@ impl Reflect {
.get(0)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
Ok(target.__get_prototype_of__())
target.__get_prototype_of__(context)
}
/// Returns `true` if the object has the property, `false` otherwise.
@ -272,7 +267,7 @@ impl Reflect {
.get(1)
.unwrap_or(&JsValue::undefined())
.to_property_key(context)?;
Ok(target.__has_property__(&key).into())
Ok(target.__has_property__(&key, context)?.into())
}
/// Returns `true` if the object is extensible, `false` otherwise.
@ -292,7 +287,7 @@ impl Reflect {
.get(0)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
Ok(target.__is_extensible__().into())
Ok(target.__is_extensible__(context)?.into())
}
/// Returns an array of object own property keys.
@ -312,24 +307,14 @@ impl Reflect {
.get(0)
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
let array_prototype = context.standard_objects().array_object().prototype();
let result: JsValue =
Object::with_prototype(array_prototype.into(), ObjectData::Array).into();
result.set_property(
"length",
PropertyDescriptor::builder()
.value(0)
.writable(true)
.enumerable(false)
.configurable(false),
);
let keys = target.own_property_keys();
for (i, k) in keys.iter().enumerate() {
result.set_field(i, k, true, context)?;
}
let keys: Vec<JsValue> = target
.__own_property_keys__(context)?
.into_iter()
.map(|key| key.into())
.collect();
Ok(result)
Ok(Array::create_array_from_list(keys, context).into())
}
/// Prevents new properties from ever being added to an object.
@ -350,7 +335,7 @@ impl Reflect {
.and_then(|v| v.as_object())
.ok_or_else(|| context.construct_type_error("target must be an object"))?;
Ok(target.__prevent_extensions__().into())
Ok(target.__prevent_extensions__(context)?.into())
}
/// Sets a property of an object.
@ -401,6 +386,6 @@ impl Reflect {
if !proto.is_null() && !proto.is_object() {
return context.throw_type_error("proto must be an object or null");
}
Ok(target.__set_prototype_of__(proto.clone()).into())
Ok(target.__set_prototype_of__(proto.clone(), context)?.into())
}
}

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

@ -361,7 +361,7 @@ impl RegExp {
original_flags: f,
};
this.set_data(ObjectData::RegExp(Box::new(regexp)));
this.set_data(ObjectData::reg_exp(Box::new(regexp)));
// 16. Return obj.
Ok(this.clone())

2
boa/src/builtins/regexp/regexp_string_iterator.rs

@ -67,7 +67,7 @@ impl RegExpStringIterator {
// 5. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%).
let regexp_string_iterator = JsValue::new_object(context);
regexp_string_iterator.set_data(ObjectData::RegExpStringIterator(Self::new(
regexp_string_iterator.set_data(ObjectData::reg_exp_string_iterator(Self::new(
matcher.clone(),
string,
global,

4
boa/src/builtins/set/mod.rs

@ -137,7 +137,7 @@ impl Set {
let set = JsValue::new(obj);
// 3
set.set_data(ObjectData::Set(OrderedSet::default()));
set.set_data(ObjectData::set(OrderedSet::default()));
let iterable = args.get(0).cloned().unwrap_or_default();
// 4
@ -237,7 +237,7 @@ impl Set {
pub(crate) fn clear(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let Some(object) = this.as_object() {
if object.borrow().is_set() {
this.set_data(ObjectData::Set(OrderedSet::new()));
this.set_data(ObjectData::set(OrderedSet::new()));
Ok(JsValue::undefined())
} else {
context.throw_type_error("'this' is not a Set")

2
boa/src/builtins/set/set_iterator.rs

@ -49,7 +49,7 @@ impl SetIterator {
kind: PropertyNameKind,
) -> JsValue {
let set_iterator = JsValue::new_object(context);
set_iterator.set_data(ObjectData::SetIterator(Self::new(set, kind)));
set_iterator.set_data(ObjectData::set_iterator(Self::new(set, kind)));
set_iterator
.as_object()
.expect("set iterator object")

48
boa/src/builtins/string/mod.rs

@ -14,7 +14,7 @@ pub mod string_iterator;
mod tests;
use crate::builtins::Symbol;
use crate::object::PROTOTYPE;
use crate::object::{JsObject, PROTOTYPE};
use crate::{
builtins::{string::string_iterator::StringIterator, Array, BuiltIn, RegExp},
object::{ConstructorBuilder, ObjectData},
@ -176,6 +176,8 @@ impl String {
if new_target.is_undefined() {
return Ok(string.into());
}
// todo: extract `GetPrototypeFromConstructor` function
let prototype = new_target
.as_object()
.and_then(|obj| {
@ -185,24 +187,46 @@ impl String {
})
.transpose()?
.unwrap_or_else(|| context.standard_objects().object_object().prototype());
let this = JsValue::new_object(context);
this.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
Ok(Self::string_create(string, prototype, context).into())
}
this.set_property(
/// Abstract function `StringCreate( value, prototype )`.
///
/// Call this function if you want to create a `String` exotic object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-stringcreate
fn string_create(value: JsString, prototype: JsObject, context: &mut Context) -> JsObject {
// 7. Let length be the number of code unit elements in value.
let len = value.encode_utf16().count();
// 1. Let S be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[StringData]] »).
// 2. Set S.[[Prototype]] to prototype.
// 3. Set S.[[StringData]] to value.
// 4. Set S.[[GetOwnProperty]] as specified in 10.4.3.1.
// 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2.
// 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3.
let s = context.construct_object();
s.set_prototype_instance(prototype.into());
s.borrow_mut().data = ObjectData::string(value);
// 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length),
// [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }).
s.define_property_or_throw(
"length",
PropertyDescriptor::builder()
.value(string.encode_utf16().count())
.value(len)
.writable(false)
.enumerable(false)
.configurable(false),
);
this.set_data(ObjectData::String(string));
context,
)
.expect("length definition for a new string must not fail");
Ok(this)
// 9. Return S.
s
}
fn this_string_value(this: &JsValue, context: &mut Context) -> JsResult<JsString> {

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

@ -25,7 +25,7 @@ impl StringIterator {
pub fn create_string_iterator(context: &mut Context, string: JsValue) -> JsResult<JsValue> {
let string_iterator = JsValue::new_object(context);
string_iterator.set_data(ObjectData::StringIterator(Self::new(string)));
string_iterator.set_data(ObjectData::string_iterator(Self::new(string)));
string_iterator
.as_object()
.expect("array iterator object")

2
boa/src/class.rs

@ -141,7 +141,7 @@ impl<T: Class> ClassConstructor for T {
let native_instance = Self::constructor(this, args, context)?;
let object_instance = context.construct_object();
object_instance.set_prototype_instance(prototype.into());
object_instance.borrow_mut().data = ObjectData::NativeObject(Box::new(native_instance));
object_instance.borrow_mut().data = ObjectData::native_object(Box::new(native_instance));
Ok(object_instance.into())
}
}

6
boa/src/context.rs

@ -656,11 +656,11 @@ impl Context {
/// <https://tc39.es/ecma262/#sec-hasproperty>
#[inline]
pub(crate) fn has_property(&self, obj: &JsValue, key: &PropertyKey) -> bool {
pub(crate) fn has_property(&mut self, obj: &JsValue, key: &PropertyKey) -> JsResult<bool> {
if let Some(obj) = obj.as_object() {
obj.__has_property__(key)
obj.__has_property__(key, self)
} else {
false
Ok(false)
}
}

197
boa/src/object/gcobject.rs

@ -13,8 +13,8 @@ use crate::{
lexical_environment::Environment,
},
exec::InterpreterState,
object::{ObjectData, ObjectKind},
property::{PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols,
syntax::ast::node::RcStatementList,
value::PreferredType,
Context, Executable, JsResult, JsValue,
@ -123,7 +123,7 @@ impl JsObject {
/// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
/// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
#[track_caller]
fn call_construct(
pub(super) fn call_construct(
&self,
this_target: &JsValue,
args: &[JsValue],
@ -224,7 +224,7 @@ impl JsObject {
&& !body.function_declared_names().contains("arguments")))
{
// Add arguments object
let arguments_obj = create_unmapped_arguments_object(args);
let arguments_obj = create_unmapped_arguments_object(args, context)?;
local_env.create_mutable_binding(
"arguments".to_string(),
false,
@ -335,41 +335,6 @@ impl JsObject {
}
}
/// Call this object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
#[inline]
pub fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
self.call_construct(this, args, context, false)
}
/// Construct an instance of this object with the specified arguments.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
#[inline]
pub fn construct(
&self,
args: &[JsValue],
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
self.call_construct(new_target, args, context, true)
}
/// Converts an object to a primitive.
///
/// Diverges from the spec to prevent a stack overflow when the object is recursive.
@ -662,35 +627,6 @@ impl JsObject {
self.borrow().is_native_object()
}
/// Retrieves value of specific property, when the value of the property is expected to be a function.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
#[inline]
pub fn get_method<K>(&self, context: &mut Context, key: K) -> JsResult<Option<JsObject>>
where
K: Into<PropertyKey>,
{
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P).
let value = self.get(key, context)?;
// 3. If func is either undefined or null, return undefined.
if value.is_null_or_undefined() {
return Ok(None);
}
// 4. If IsCallable(func) is false, throw a TypeError exception.
// 5. Return func.
match value.as_object() {
Some(object) if object.is_callable() => Ok(Some(object)),
_ => Err(context
.construct_type_error("value returned for property of object is not a function")),
}
}
/// Determines if `value` inherits from the instance object inheritance path.
///
/// More information:
@ -720,14 +656,14 @@ impl JsObject {
// 6. Repeat,
// a. Set O to ? O.[[GetPrototypeOf]]().
// b. If O is null, return false.
let mut object = object.__get_prototype_of__();
let mut object = object.__get_prototype_of__(context)?;
while let Some(object_prototype) = object.as_object() {
// c. If SameValue(P, O) is true, return true.
if JsObject::equals(&prototype, &object_prototype) {
return Ok(true);
}
// a. Set O to ? O.[[GetPrototypeOf]]().
object = object_prototype.__get_prototype_of__();
object = object_prototype.__get_prototype_of__(context)?;
}
Ok(false)
@ -740,59 +676,6 @@ impl JsObject {
}
}
/// `7.3.22 SpeciesConstructor ( O, defaultConstructor )`
///
/// The abstract operation SpeciesConstructor takes arguments O (an Object) and defaultConstructor (a constructor).
/// It is used to retrieve the constructor that should be used to create new objects that are derived from O.
/// defaultConstructor is the constructor to use if a constructor @@species property cannot be found starting from O.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
pub(crate) fn species_constructor(
&self,
default_constructor: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Assert: Type(O) is Object.
// 2. Let C be ? Get(O, "constructor").
let c = self.clone().get("constructor", context)?;
// 3. If C is undefined, return defaultConstructor.
if c.is_undefined() {
return Ok(default_constructor);
}
// 4. If Type(C) is not Object, throw a TypeError exception.
let c = if let Some(c) = c.as_object() {
c
} else {
return context.throw_type_error("property 'constructor' is not an object");
};
// 5. Let S be ? Get(C, @@species).
let s = c.get(WellKnownSymbols::species(), context)?;
// 6. If S is either undefined or null, return defaultConstructor.
if s.is_null_or_undefined() {
return Ok(default_constructor);
}
// 7. If IsConstructor(S) is true, return S.
// 8. Throw a TypeError exception.
if let Some(obj) = s.as_object() {
if obj.is_constructable() {
Ok(s)
} else {
context.throw_type_error("property 'constructor' is not a constructor")
}
} else {
context.throw_type_error("property 'constructor' is not an object")
}
}
pub fn to_property_descriptor(&self, context: &mut Context) -> JsResult<PropertyDescriptor> {
// 1 is implemented on the method `to_property_descriptor` of value
@ -913,7 +796,7 @@ impl JsObject {
// 5. Let keys be ? from.[[OwnPropertyKeys]]().
// 6. For each element nextKey of keys, do
let excluded_keys: Vec<PropertyKey> = excluded_keys.into_iter().map(|e| e.into()).collect();
for key in from.own_property_keys() {
for key in from.__own_property_keys__(context)? {
// a. Let excluded be false.
let mut excluded = false;
@ -929,7 +812,7 @@ impl JsObject {
// c. If excluded is false, then
if !excluded {
// i. Let desc be ? from.[[GetOwnProperty]](nextKey).
let desc = from.__get_own_property__(&key);
let desc = from.__get_own_property__(&key, context)?;
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
if let Some(desc) = desc {
@ -952,6 +835,72 @@ impl JsObject {
// 7. Return target.
Ok(())
}
/// Helper function for property insertion.
#[inline]
#[track_caller]
pub(crate) fn insert<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.borrow_mut().insert(key, property)
}
/// Helper function for property removal.
#[inline]
#[track_caller]
pub(crate) fn remove(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
self.borrow_mut().remove(key)
}
/// Inserts a field in the object `properties` without checking if it's writable.
///
/// If a field was already in the object with the same name that a `Some` is returned
/// with that field, otherwise None is returned.
#[inline]
pub fn insert_property<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.insert(key.into(), property)
}
/// It determines if Object is a callable function with a `[[Call]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
#[track_caller]
pub fn is_callable(&self) -> bool {
self.borrow().is_callable()
}
/// It determines if Object is a function object with a `[[Construct]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
#[track_caller]
pub fn is_constructable(&self) -> bool {
self.borrow().is_constructable()
}
/// Returns true if the GcObject is the global for a Realm
pub fn is_global(&self) -> bool {
matches!(
self.borrow().data,
ObjectData {
kind: ObjectKind::Global,
..
}
)
}
}
impl AsRef<GcCell<Object>> for JsObject {

983
boa/src/object/internal_methods.rs

@ -1,983 +0,0 @@
//! This module defines the object internal methods.
//!
//! More information:
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
use crate::{
builtins::Array,
object::{JsObject, Object, ObjectData},
property::{DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind},
value::{JsValue, Type},
BoaProfiler, Context, JsResult,
};
impl JsObject {
/// Check if object has property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
// NOTE: for now context is not used but it will in the future.
#[inline]
pub fn has_property<K>(&self, key: K, _context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Return ? O.[[HasProperty]](P).
Ok(self.__has_property__(&key.into()))
}
/// Check if it is extensible.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isextensible-o
// NOTE: for now context is not used but it will in the future.
#[inline]
pub fn is_extensible(&self, _context: &mut Context) -> JsResult<bool> {
// 1. Assert: Type(O) is Object.
// 2. Return ? O.[[IsExtensible]]().
Ok(self.__is_extensible__())
}
/// Delete property, if deleted return `true`.
#[inline]
pub fn delete<K>(&self, key: K) -> bool
where
K: Into<PropertyKey>,
{
self.__delete__(&key.into())
}
/// Defines the property or throws a `TypeError` if the operation fails.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
#[inline]
pub fn delete_property_or_throw<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? O.[[Delete]](P).
let success = self.__delete__(&key);
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(context.construct_type_error(format!("cannot delete property: {}", key)));
}
// 5. Return success.
Ok(success)
}
/// Check if object has an own property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
#[inline]
pub fn has_own_property<K>(&self, key: K, _context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let desc be ? O.[[GetOwnProperty]](P).
let desc = self.__get_own_property__(&key);
// 4. If desc is undefined, return false.
// 5. Return true.
Ok(desc.is_some())
}
/// Get property from object or throw.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
#[inline]
pub fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
where
K: Into<PropertyKey>,
{
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Return ? O.[[Get]](P, O).
self.__get__(&key.into(), self.clone().into(), context)
}
/// set property of object or throw if bool flag is passed.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
#[inline]
pub fn set<K, V>(&self, key: K, value: V, throw: bool, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Assert: Type(Throw) is Boolean.
// 4. Let success be ? O.[[Set]](P, V, O).
let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?;
// 5. If success is false and Throw is true, throw a TypeError exception.
if !success && throw {
return Err(
context.construct_type_error(format!("cannot set non-writable property: {}", key))
);
}
// 6. Return success.
Ok(success)
}
/// Define property or throw.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
#[inline]
pub fn define_property_or_throw<K, P>(
&self,
key: K,
desc: P,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
let success = self.__define_own_property__(key.clone(), desc.into(), context)?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(context.construct_type_error(format!("cannot redefine property: {}", key)));
}
// 5. Return success.
Ok(success)
}
/// Create data property
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
pub fn create_data_property<K, V>(
&self,
key: K,
value: V,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
let new_desc = PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true);
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
self.__define_own_property__(key.into(), new_desc.into(), context)
}
/// Create data property or throw
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
pub fn create_data_property_or_throw<K, V>(
&self,
key: K,
value: V,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? CreateDataProperty(O, P, V).
let success = self.create_data_property(key.clone(), value, context)?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(context.construct_type_error(format!("cannot redefine property: {}", key)));
}
// 5. Return success.
Ok(success)
}
/// `[[hasProperty]]`
#[inline]
pub(crate) fn __has_property__(&self, key: &PropertyKey) -> bool {
let prop = self.__get_own_property__(key);
if prop.is_none() {
let parent = self.__get_prototype_of__();
return if let JsValue::Object(ref object) = parent {
object.__has_property__(key)
} else {
false
};
}
true
}
/// Check if it is extensible.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
#[inline]
pub(crate) fn __is_extensible__(&self) -> bool {
self.borrow().extensible
}
/// Disable extensibility.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
#[inline]
pub fn __prevent_extensions__(&mut self) -> bool {
self.borrow_mut().extensible = false;
true
}
/// Delete property.
#[inline]
pub(crate) fn __delete__(&self, key: &PropertyKey) -> bool {
match self.__get_own_property__(key) {
Some(desc) if desc.expect_configurable() => {
self.remove(key);
true
}
Some(_) => false,
None => true,
}
}
/// `[[Get]]`
pub fn __get__(
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
match self.__get_own_property__(key) {
None => {
// parent will either be null or an Object
if let Some(parent) = self.__get_prototype_of__().as_object() {
Ok(parent.__get__(key, receiver, context)?)
} else {
Ok(JsValue::undefined())
}
}
Some(ref desc) => match desc.kind() {
DescriptorKind::Data {
value: Some(value), ..
} => Ok(value.clone()),
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
context.call(get, &receiver, &[])
}
_ => Ok(JsValue::undefined()),
},
}
}
/// `[[Set]]`
pub fn __set__(
&self,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
) -> JsResult<bool> {
let _timer = BoaProfiler::global().start_event("Object::set", "object");
// Fetch property key
let own_desc = if let Some(desc) = self.__get_own_property__(&key) {
desc
} else if let Some(ref mut parent) = self.__get_prototype_of__().as_object() {
return parent.__set__(key, value, receiver, context);
} else {
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build()
};
if own_desc.is_data_descriptor() {
if !own_desc.expect_writable() {
return Ok(false);
}
let receiver = match receiver.as_object() {
Some(obj) => obj,
_ => return Ok(false),
};
if let Some(ref existing_desc) = receiver.__get_own_property__(&key) {
if existing_desc.is_accessor_descriptor() {
return Ok(false);
}
if !existing_desc.expect_writable() {
return Ok(false);
}
return receiver.__define_own_property__(
key,
PropertyDescriptor::builder().value(value).build(),
context,
);
} else {
return receiver.create_data_property(key, value, context);
}
}
match own_desc.set() {
Some(set) if !set.is_undefined() => {
context.call(set, &receiver, &[value])?;
Ok(true)
}
_ => Ok(false),
}
}
/// `[[defineOwnProperty]]`
pub fn __define_own_property__(
&self,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
if self.is_array() {
self.array_define_own_property(key, desc, context)
} else {
Ok(self.ordinary_define_own_property(key, desc))
}
}
/// Define an own property for an ordinary object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
pub fn ordinary_define_own_property(&self, key: PropertyKey, desc: PropertyDescriptor) -> bool {
let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object");
let extensible = self.__is_extensible__();
let mut current = if let Some(own) = self.__get_own_property__(&key) {
own
} else {
if !extensible {
return false;
}
self.insert(
key,
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
desc.into_data_defaulted()
} else {
desc.into_accessor_defaulted()
},
);
return true;
};
// 3
if desc.is_empty() {
return true;
}
// 4
if !current.expect_configurable() {
if matches!(desc.configurable(), Some(true)) {
return false;
}
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return false;
}
}
// 5
if desc.is_generic_descriptor() {
// no further validation required
} else if current.is_data_descriptor() != desc.is_data_descriptor() {
if !current.expect_configurable() {
return false;
}
if current.is_data_descriptor() {
current = current.into_accessor_defaulted();
} else {
current = current.into_data_defaulted();
}
} else if current.is_data_descriptor() && desc.is_data_descriptor() {
if !current.expect_configurable() && !current.expect_writable() {
if matches!(desc.writable(), Some(true)) {
return false;
}
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value()))
{
return false;
}
return true;
}
} else if !current.expect_configurable() {
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) {
return false;
}
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) {
return false;
}
return true;
}
current.fill_with(desc);
self.insert(key, current);
true
}
/// Define an own property for an array.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
fn array_define_own_property(
&self,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
match key {
PropertyKey::String(ref s) if s == "length" => {
let new_len_val = match desc.value() {
Some(value) => value,
_ => return Ok(self.ordinary_define_own_property("length".into(), desc)),
};
let new_len = new_len_val.to_u32(context)?;
let number_len = new_len_val.to_number(context)?;
#[allow(clippy::float_cmp)]
if new_len as f64 != number_len {
return Err(context.construct_range_error("bad length for array"));
}
let mut new_len_desc = PropertyDescriptor::builder()
.value(new_len)
.maybe_writable(desc.writable())
.maybe_enumerable(desc.enumerable())
.maybe_configurable(desc.configurable());
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap();
let old_len = old_len_desc.expect_value();
if new_len >= old_len.to_u32(context)? {
return Ok(
self.ordinary_define_own_property("length".into(), new_len_desc.build())
);
}
if !old_len_desc.expect_writable() {
return Ok(false);
}
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) {
true
} else {
new_len_desc = new_len_desc.writable(true);
false
};
if !self.ordinary_define_own_property("length".into(), new_len_desc.clone().build())
{
return Ok(false);
}
let max_value = self
.borrow()
.properties
.index_property_keys()
.max()
.copied();
if let Some(mut index) = max_value {
while index >= new_len {
let contains_index = self.borrow().properties.contains_key(&index.into());
if contains_index && !self.__delete__(&index.into()) {
new_len_desc = new_len_desc.value(index + 1);
if !new_writable {
new_len_desc = new_len_desc.writable(false);
}
self.ordinary_define_own_property(
"length".into(),
new_len_desc.build(),
);
return Ok(false);
}
index = if let Some(sub) = index.checked_sub(1) {
sub
} else {
break;
}
}
}
if !new_writable {
self.ordinary_define_own_property(
"length".into(),
PropertyDescriptor::builder().writable(false).build(),
);
}
Ok(true)
}
PropertyKey::Index(index) => {
let old_len_desc = self.__get_own_property__(&"length".into()).unwrap();
let old_len = old_len_desc.expect_value().to_u32(context)?;
if index >= old_len && !old_len_desc.expect_writable() {
return Ok(false);
}
if self.ordinary_define_own_property(key, desc) {
if index >= old_len && index < u32::MAX {
let desc = PropertyDescriptor::builder()
.value(index + 1)
.maybe_writable(old_len_desc.writable())
.maybe_enumerable(old_len_desc.enumerable())
.maybe_configurable(old_len_desc.configurable());
self.ordinary_define_own_property("length".into(), desc.into());
}
Ok(true)
} else {
Ok(false)
}
}
_ => Ok(self.ordinary_define_own_property(key, desc)),
}
}
/// Gets own property of 'Object'
///
#[inline]
pub fn __get_own_property__(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object");
let object = self.borrow();
match object.data {
ObjectData::String(_) => self.string_exotic_get_own_property(key),
_ => self.ordinary_get_own_property(key),
}
}
/// StringGetOwnProperty abstract operation
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
#[inline]
pub fn string_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
let object = self.borrow();
match key {
PropertyKey::Index(index) => {
let string = object.as_string().unwrap();
let pos = *index as usize;
if pos >= string.len() {
return None;
}
let result_str = string
.encode_utf16()
.nth(pos)
.map(|utf16_val| JsValue::from(String::from_utf16_lossy(&[utf16_val])))?;
let desc = PropertyDescriptor::builder()
.value(result_str)
.writable(false)
.enumerable(true)
.configurable(false)
.build();
Some(desc)
}
_ => None,
}
}
/// Gets own property of 'String' exotic object
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
#[inline]
pub fn string_exotic_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
let desc = self.ordinary_get_own_property(key);
if desc.is_some() {
desc
} else {
self.string_get_own_property(key)
}
}
/// The specification returns a Property Descriptor or Undefined.
///
/// These are 2 separate types and we can't do that here.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
#[inline]
pub fn ordinary_get_own_property(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
let object = self.borrow();
let property = object.properties.get(key);
property.cloned()
}
/// Essential internal method OwnPropertyKeys
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#table-essential-internal-methods
#[inline]
#[track_caller]
pub fn own_property_keys(&self) -> Vec<PropertyKey> {
self.borrow().properties.keys().collect()
}
/// The abstract operation ObjectDefineProperties
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-object.defineproperties
#[inline]
pub fn define_properties(&mut self, props: JsValue, context: &mut Context) -> JsResult<()> {
let props = &props.to_object(context)?;
let keys = props.own_property_keys();
let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new();
for next_key in keys {
if let Some(prop_desc) = props.__get_own_property__(&next_key) {
if prop_desc.expect_enumerable() {
let desc_obj = props.get(next_key.clone(), context)?;
let desc = desc_obj.to_property_descriptor(context)?;
descriptors.push((next_key, desc));
}
}
}
for (p, d) in descriptors {
self.define_property_or_throw(p, d, context)?;
}
Ok(())
}
/// `Object.setPropertyOf(obj, prototype)`
///
/// This method sets the prototype (i.e., the internal `[[Prototype]]` property)
/// of a specified object to another object or `null`.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
#[inline]
pub fn __set_prototype_of__(&mut self, val: JsValue) -> bool {
debug_assert!(val.is_object() || val.is_null());
let current = self.__get_prototype_of__();
if JsValue::same_value(&current, &val) {
return true;
}
if !self.__is_extensible__() {
return false;
}
let mut p = val.clone();
let mut done = false;
while !done {
if p.is_null() {
done = true
} else if JsValue::same_value(&JsValue::new(self.clone()), &p) {
return false;
} else {
let prototype = p
.as_object()
.expect("prototype should be null or object")
.__get_prototype_of__();
p = prototype;
}
}
self.set_prototype_instance(val);
true
}
/// Returns either the prototype or null
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf
#[inline]
#[track_caller]
pub fn __get_prototype_of__(&self) -> JsValue {
self.borrow().prototype.clone()
}
/// Helper function for property insertion.
#[inline]
#[track_caller]
pub(crate) fn insert<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.borrow_mut().insert(key, property)
}
/// Helper function for property removal.
#[inline]
#[track_caller]
pub(crate) fn remove(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
self.borrow_mut().remove(key)
}
/// Inserts a field in the object `properties` without checking if it's writable.
///
/// If a field was already in the object with the same name that a `Some` is returned
/// with that field, otherwise None is returned.
#[inline]
pub fn insert_property<K, P>(&self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.insert(key.into(), property)
}
/// It determines if Object is a callable function with a `[[Call]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
#[track_caller]
pub fn is_callable(&self) -> bool {
self.borrow().is_callable()
}
/// It determines if Object is a function object with a `[[Construct]]` internal method.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
#[track_caller]
pub fn is_constructable(&self) -> bool {
self.borrow().is_constructable()
}
/// Returns true if the JsObject is the global for a Realm
pub fn is_global(&self) -> bool {
matches!(self.borrow().data, ObjectData::Global)
}
/// It is used to create List value whose elements are provided by the indexed properties of
/// self.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
pub(crate) fn create_list_from_array_like(
&self,
element_types: &[Type],
context: &mut Context,
) -> JsResult<Vec<JsValue>> {
// 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
let types = if element_types.is_empty() {
&[
Type::Undefined,
Type::Null,
Type::Boolean,
Type::String,
Type::Symbol,
Type::Number,
Type::BigInt,
Type::Object,
]
} else {
element_types
};
// TODO: 2. If Type(obj) is not Object, throw a TypeError exception.
// 3. Let len be ? LengthOfArrayLike(obj).
let len = self.length_of_array_like(context)?;
// 4. Let list be a new empty List.
let mut list = Vec::with_capacity(len);
// 5. Let index be 0.
// 6. Repeat, while index < len,
for index in 0..len {
// a. Let indexName be ! ToString(𝔽(index)).
// b. Let next be ? Get(obj, indexName).
let next = self.get(index, context)?;
// c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
if !types.contains(&next.get_type()) {
return Err(context.construct_type_error("bad type"));
}
// d. Append next as the last element of list.
list.push(next.clone());
// e. Set index to index + 1.
}
// 7. Return list.
Ok(list)
}
/// It is used to iterate over names of object's keys.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
pub(crate) fn enumerable_own_property_names(
&self,
kind: PropertyNameKind,
context: &mut Context,
) -> JsResult<Vec<JsValue>> {
// 1. Assert: Type(O) is Object.
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
let own_keys = self.own_property_keys();
// 3. Let properties be a new empty List.
let mut properties = vec![];
// 4. For each element key of ownKeys, do
for key in own_keys {
// a. If Type(key) is String, then
let key_str = match &key {
PropertyKey::String(s) => Some(s.clone()),
PropertyKey::Index(i) => Some(i.to_string().into()),
_ => None,
};
if let Some(key_str) = key_str {
// i. Let desc be ? O.[[GetOwnProperty]](key).
let desc = self.__get_own_property__(&key);
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
if let Some(desc) = desc {
if desc.expect_enumerable() {
match kind {
// 1. If kind is key, append key to properties.
PropertyNameKind::Key => properties.push(key_str.into()),
// 2. Else,
// a. Let value be ? Get(O, key).
// b. If kind is value, append value to properties.
PropertyNameKind::Value => {
properties.push(self.get(key.clone(), context)?)
}
// c. Else,
// i. Assert: kind is key+value.
// ii. Let entry be ! CreateArrayFromList(« key, value »).
// iii. Append entry to properties.
PropertyNameKind::KeyAndValue => properties.push(
Array::create_array_from_list(
[key_str.into(), self.get(key.clone(), context)?],
context,
)
.into(),
),
}
}
}
}
}
// 5. Return properties.
Ok(properties)
}
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<usize> {
// 1. Assert: Type(obj) is Object.
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
self.get("length", context)?.to_length(context)
}
}
impl Object {
/// Helper function for property insertion.
#[inline]
pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.properties.insert(key.into(), property.into())
}
/// Helper function for property removal.
#[inline]
pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> {
self.properties.remove(key)
}
/// Inserts a field in the object `properties` without checking if it's writable.
///
/// If a field was already in the object with the same name that a `Some` is returned
/// with that field, otherwise None is retuned.
#[inline]
pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.insert(key, property)
}
}

248
boa/src/object/internal_methods/array.rs

@ -0,0 +1,248 @@
use crate::{
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
Context, JsResult,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for array exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects
pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__define_own_property__: array_exotic_define_own_property,
..ORDINARY_INTERNAL_METHODS
};
/// Define an own property for an array exotic object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
pub(crate) fn array_exotic_define_own_property(
obj: &JsObject,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
match key {
// 2. If P is "length", then
PropertyKey::String(ref s) if s == "length" => {
// a. Return ? ArraySetLength(A, Desc).
// Abstract operation `ArraySetLength ( A, Desc )`
//
// https://tc39.es/ecma262/#sec-arraysetlength
// 1. If Desc.[[Value]] is absent, then
let new_len_val = match desc.value() {
Some(value) => value,
_ => {
// a. Return OrdinaryDefineOwnProperty(A, "length", Desc).
return super::ordinary_define_own_property(
obj,
"length".into(),
desc,
context,
);
}
};
// 3. Let newLen be ? ToUint32(Desc.[[Value]]).
let new_len = new_len_val.to_u32(context)?;
// 4. Let numberLen be ? ToNumber(Desc.[[Value]]).
let number_len = new_len_val.to_number(context)?;
// 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception.
#[allow(clippy::float_cmp)]
if new_len as f64 != number_len {
return Err(context.construct_range_error("bad length for array"));
}
// 2. Let newLenDesc be a copy of Desc.
// 6. Set newLenDesc.[[Value]] to newLen.
let mut new_len_desc = PropertyDescriptor::builder()
.value(new_len)
.maybe_writable(desc.writable())
.maybe_enumerable(desc.enumerable())
.maybe_configurable(desc.configurable());
// 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
let old_len_desc =
super::ordinary_get_own_property(obj, &"length".into(), context)?.unwrap();
// 8. Assert: ! IsDataDescriptor(oldLenDesc) is true.
debug_assert!(old_len_desc.is_data_descriptor());
// 9. Assert: oldLenDesc.[[Configurable]] is false.
debug_assert!(!old_len_desc.expect_configurable());
// 10. Let oldLen be oldLenDesc.[[Value]].
let old_len = old_len_desc.expect_value();
// 11. If newLen ≥ oldLen, then
if new_len >= old_len.to_u32(context)? {
// a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc).
return super::ordinary_define_own_property(
obj,
"length".into(),
new_len_desc.build(),
context,
);
}
// 12. If oldLenDesc.[[Writable]] is false, return false.
if !old_len_desc.expect_writable() {
return Ok(false);
}
// 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
let new_writable = if new_len_desc.inner().writable().unwrap_or(true) {
true
}
// 14. Else,
else {
// a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any
// elements cannot be deleted.
// c. Set newLenDesc.[[Writable]] to true.
new_len_desc = new_len_desc.writable(true);
// b. Let newWritable be false.
false
};
// 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
// 16. If succeeded is false, return false.
if !super::ordinary_define_own_property(
obj,
"length".into(),
new_len_desc.clone().build(),
context,
)
.unwrap()
{
return Ok(false);
}
// 17. For each own property key P of A that is an array index, whose numeric value is
// greater than or equal to newLen, in descending numeric index order, do
let ordered_keys = {
let mut keys: Vec<_> = obj
.borrow()
.properties
.index_property_keys()
.filter(|idx| new_len <= **idx && **idx < u32::MAX)
.copied()
.collect();
keys.sort_unstable_by(|x, y| y.cmp(x));
keys
};
for index in ordered_keys {
// a. Let deleteSucceeded be ! A.[[Delete]](P).
// b. If deleteSucceeded is false, then
if !obj.__delete__(&index.into(), context)? {
// i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽.
new_len_desc = new_len_desc.value(index + 1);
// ii. If newWritable is false, set newLenDesc.[[Writable]] to false.
if !new_writable {
new_len_desc = new_len_desc.writable(false);
}
// iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc).
super::ordinary_define_own_property(
obj,
"length".into(),
new_len_desc.build(),
context,
)
.unwrap();
// iv. Return false.
return Ok(false);
}
}
// 18. If newWritable is false, then
if !new_writable {
// a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length",
// PropertyDescriptor { [[Writable]]: false }).
let succeeded = super::ordinary_define_own_property(
obj,
"length".into(),
PropertyDescriptor::builder().writable(false).build(),
context,
)?;
// b. Assert: succeeded is true.
debug_assert!(succeeded);
}
// 19. Return true.
Ok(true)
}
// 3. Else if P is an array index, then
PropertyKey::Index(index) if index < u32::MAX => {
// a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length").
let old_len_desc =
super::ordinary_get_own_property(obj, &"length".into(), context)?.unwrap();
// b. Assert: ! IsDataDescriptor(oldLenDesc) is true.
debug_assert!(old_len_desc.is_data_descriptor());
// c. Assert: oldLenDesc.[[Configurable]] is false.
debug_assert!(!old_len_desc.expect_configurable());
// d. Let oldLen be oldLenDesc.[[Value]].
// e. Assert: oldLen is a non-negative integral Number.
// f. Let index be ! ToUint32(P).
let old_len = old_len_desc.expect_value().to_u32(context).unwrap();
// g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false.
if index >= old_len && !old_len_desc.expect_writable() {
return Ok(false);
}
// h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
if super::ordinary_define_own_property(obj, key, desc, context)? {
// j. If index ≥ oldLen, then
if index >= old_len {
// i. Set oldLenDesc.[[Value]] to index + 1𝔽.
let old_len_desc = PropertyDescriptor::builder()
.value(index + 1)
.maybe_writable(old_len_desc.writable())
.maybe_enumerable(old_len_desc.enumerable())
.maybe_configurable(old_len_desc.configurable());
// ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc).
let succeeded = super::ordinary_define_own_property(
obj,
"length".into(),
old_len_desc.into(),
context,
)
.unwrap();
// iii. Assert: succeeded is true.
debug_assert!(succeeded);
}
// k. Return true.
Ok(true)
} else {
// i. If succeeded is false, return false.
Ok(false)
}
}
// 4. Return OrdinaryDefineOwnProperty(A, P, Desc).
_ => super::ordinary_define_own_property(obj, key, desc, context),
}
}

861
boa/src/object/internal_methods/mod.rs

@ -0,0 +1,861 @@
//! This module defines the object internal methods.
//!
//! More information:
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
use crate::{
object::JsObject,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
value::JsValue,
BoaProfiler, Context, JsResult,
};
pub(super) mod array;
pub(super) mod string;
impl JsObject {
/// Internal method `[[GetPrototypeOf]]`
///
/// Return either the prototype of this object or null.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
#[inline]
#[track_caller]
pub(crate) fn __get_prototype_of__(&self, context: &mut Context) -> JsResult<JsValue> {
let func = self.borrow().data.internal_methods.__get_prototype_of__;
func(self, context)
}
/// Internal method `[[SetPrototypeOf]]`
///
/// Set the property of a specified object to another object or `null`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
#[inline]
pub(crate) fn __set_prototype_of__(
&mut self,
val: JsValue,
context: &mut Context,
) -> JsResult<bool> {
let func = self.borrow().data.internal_methods.__set_prototype_of__;
func(self, val, context)
}
/// Internal method `[[IsExtensible]]`
///
/// Check if the object is extensible.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
#[inline]
pub(crate) fn __is_extensible__(&self, context: &mut Context) -> JsResult<bool> {
let func = self.borrow().data.internal_methods.__is_extensible__;
func(self, context)
}
/// Internal method `[[PreventExtensions]]`
///
/// Disable extensibility for this object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
#[inline]
pub(crate) fn __prevent_extensions__(&mut self, context: &mut Context) -> JsResult<bool> {
let func = self.borrow().data.internal_methods.__prevent_extensions__;
func(self, context)
}
/// Internal method `[[GetOwnProperty]]`
///
/// Get the specified property of this object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
#[inline]
pub(crate) fn __get_own_property__(
&self,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<Option<PropertyDescriptor>> {
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object");
let func = self.borrow().data.internal_methods.__get_own_property__;
func(self, key, context)
}
/// Internal method `[[DefineOwnProperty]]`
///
/// Define a new property of this object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
#[inline]
pub(crate) fn __define_own_property__(
&self,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
let func = self.borrow().data.internal_methods.__define_own_property__;
func(self, key, desc, context)
}
/// Internal method `[[hasProperty]]`.
///
/// Check if the object or its prototype has the required property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
#[inline]
pub(crate) fn __has_property__(
&self,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<bool> {
let func = self.borrow().data.internal_methods.__has_property__;
func(self, key, context)
}
/// Internal method `[[Get]]`
///
/// Get the specified property of this object or its prototype.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver
#[inline]
pub(crate) fn __get__(
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
let func = self.borrow().data.internal_methods.__get__;
func(self, key, receiver, context)
}
/// Internal method `[[Set]]`
///
/// Set the specified property of this object or its prototype to the provided value.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
#[inline]
pub(crate) fn __set__(
&self,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
) -> JsResult<bool> {
let _timer = BoaProfiler::global().start_event("Object::set", "object");
let func = self.borrow().data.internal_methods.__set__;
func(self, key, value, receiver, context)
}
/// Internal method `[[Delete]]`
///
/// Delete the specified own property of this object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p
#[inline]
pub(crate) fn __delete__(&self, key: &PropertyKey, context: &mut Context) -> JsResult<bool> {
let func = self.borrow().data.internal_methods.__delete__;
func(self, key, context)
}
/// Internal method `[[OwnPropertyKeys]]`
///
/// Get all the keys of the properties of this object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
#[inline]
#[track_caller]
pub(crate) fn __own_property_keys__(
&self,
context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
let func = self.borrow().data.internal_methods.__own_property_keys__;
func(self, context)
}
}
/// Definitions of the internal object methods for ordinary objects.
///
/// If you want to implement an exotic object, create a new `static InternalObjectMethods`
/// overriding the desired internal methods with the definitions of the spec
/// and set all other methods to the default ordinary values, if necessary.
///
/// E.g. `string::STRING_EXOTIC_INTERNAL_METHODS`
///
/// Then, reference this static in the creation phase of an `ObjectData`.
///
/// E.g. `ObjectData::string`
pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__get_prototype_of__: ordinary_get_prototype_of,
__set_prototype_of__: ordinary_set_prototype_of,
__is_extensible__: ordinary_is_extensible,
__prevent_extensions__: ordinary_prevent_extensions,
__get_own_property__: ordinary_get_own_property,
__define_own_property__: ordinary_define_own_property,
__has_property__: ordinary_has_property,
__get__: ordinary_get,
__set__: ordinary_set,
__delete__: ordinary_delete,
__own_property_keys__: ordinary_own_property_keys,
};
/// The internal representation of the internal methods of a `JsObject`.
///
/// This struct allows us to dynamically dispatch exotic objects with their
/// exclusive definitions of the internal methods, without having to
/// resort to `dyn Object`.
///
/// For a guide on how to implement exotic internal methods, see `ORDINARY_INTERNAL_METHODS`.
#[derive(Clone, Copy)]
pub(crate) struct InternalObjectMethods {
pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult<JsValue>,
pub(crate) __set_prototype_of__: fn(&JsObject, JsValue, &mut Context) -> JsResult<bool>,
pub(crate) __is_extensible__: fn(&JsObject, &mut Context) -> JsResult<bool>,
pub(crate) __prevent_extensions__: fn(&JsObject, &mut Context) -> JsResult<bool>,
pub(crate) __get_own_property__:
fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<Option<PropertyDescriptor>>,
pub(crate) __define_own_property__:
fn(&JsObject, PropertyKey, PropertyDescriptor, &mut Context) -> JsResult<bool>,
pub(crate) __has_property__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>,
pub(crate) __get__: fn(&JsObject, &PropertyKey, JsValue, &mut Context) -> JsResult<JsValue>,
pub(crate) __set__:
fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context) -> JsResult<bool>,
pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>,
pub(crate) __own_property_keys__: fn(&JsObject, &mut Context) -> JsResult<Vec<PropertyKey>>,
}
/// Abstract operation `OrdinaryGetPrototypeOf`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetprototypeof
#[inline]
pub(crate) fn ordinary_get_prototype_of(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<JsValue> {
// 1. Return O.[[Prototype]].
Ok(obj.borrow().prototype.clone())
}
/// Abstract operation `OrdinarySetPrototypeOf`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarysetprototypeof
#[inline]
pub(crate) fn ordinary_set_prototype_of(
obj: &JsObject,
val: JsValue,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: Either Type(V) is Object or Type(V) is Null.
debug_assert!(val.is_object() || val.is_null());
// 2. Let current be O.[[Prototype]].
let current = obj.__get_prototype_of__(context)?;
// 3. If SameValue(V, current) is true, return true.
if JsValue::same_value(&current, &val) {
return Ok(true);
}
// 4. Let extensible be O.[[Extensible]].
// 5. If extensible is false, return false.
if !obj.__is_extensible__(context)? {
return Ok(false);
}
// 6. Let p be V.
let mut p = val.clone();
// 7. Let done be false.
let mut done = false;
// 8. Repeat, while done is false,
while !done {
match p {
// a. If p is null, set done to true.
JsValue::Null => done = true,
JsValue::Object(ref proto) => {
// b. Else if SameValue(p, O) is true, return false.
if JsObject::equals(proto, obj) {
return Ok(false);
}
// c. Else,
// i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined
// in 10.1.1, set done to true.
else if proto.borrow().data.internal_methods.__get_prototype_of__ as usize
!= ordinary_get_prototype_of as usize
{
done = true;
}
// ii. Else, set p to p.[[Prototype]].
else {
p = proto.__get_prototype_of__(context)?;
}
}
_ => unreachable!(),
}
}
// 9. Set O.[[Prototype]] to V.
obj.borrow_mut().prototype = val;
// 10. Return true.
Ok(true)
}
/// Abstract operation `OrdinaryIsExtensible`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible
#[inline]
pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context) -> JsResult<bool> {
// 1. Return O.[[Extensible]].
Ok(obj.borrow().extensible)
}
/// Abstract operation `OrdinaryPreventExtensions.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarypreventextensions
#[inline]
pub(crate) fn ordinary_prevent_extensions(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<bool> {
// 1. Set O.[[Extensible]] to false.
obj.borrow_mut().extensible = false;
// 2. Return true.
Ok(true)
}
/// Abstract operation `OrdinaryGetOwnProperty`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarygetownproperty
#[inline]
pub(crate) fn ordinary_get_own_property(
obj: &JsObject,
key: &PropertyKey,
_context: &mut Context,
) -> JsResult<Option<PropertyDescriptor>> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. If O does not have an own property with key P, return undefined.
// 3. Let D be a newly created Property Descriptor with no fields.
// 4. Let X be O's own property whose key is P.
// 5. If X is a data property, then
// a. Set D.[[Value]] to the value of X's [[Value]] attribute.
// b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
// 6. Else,
// a. Assert: X is an accessor property.
// b. Set D.[[Get]] to the value of X's [[Get]] attribute.
// c. Set D.[[Set]] to the value of X's [[Set]] attribute.
// 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
// 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
// 9. Return D.
Ok(obj.borrow().properties.get(key).cloned())
}
/// Abstract operation `OrdinaryDefineOwnProperty`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydefineownproperty
#[inline]
pub(crate) fn ordinary_define_own_property(
obj: &JsObject,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
// 1. Let current be ? O.[[GetOwnProperty]](P).
let current = obj.__get_own_property__(&key, context)?;
// 2. Let extensible be ? IsExtensible(O).
let extensible = obj.__is_extensible__(context)?;
// 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
Ok(validate_and_apply_property_descriptor(
Some((obj, key)),
extensible,
desc,
current,
))
}
/// Abstract operation `OrdinaryHasProperty`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryhasproperty
#[inline]
pub(crate) fn ordinary_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let hasOwn be ? O.[[GetOwnProperty]](P).
// 3. If hasOwn is not undefined, return true.
if obj.__get_own_property__(key, context)?.is_some() {
Ok(true)
} else {
// 4. Let parent be ? O.[[GetPrototypeOf]]().
let parent = obj.__get_prototype_of__(context)?;
// 5. If parent is not null, then
if let JsValue::Object(ref object) = parent {
// a. Return ? parent.[[HasProperty]](P).
object.__has_property__(key, context)
} else {
// 6. Return false.
Ok(false)
}
}
}
/// Abstract operation `OrdinaryGet`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryget
#[inline]
pub(crate) fn ordinary_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let desc be ? O.[[GetOwnProperty]](P).
match obj.__get_own_property__(key, context)? {
// If desc is undefined, then
None => {
// a. Let parent be ? O.[[GetPrototypeOf]]().
if let Some(parent) = obj.__get_prototype_of__(context)?.as_object() {
// c. Return ? parent.[[Get]](P, Receiver).
parent.__get__(key, receiver, context)
}
// b. If parent is null, return undefined.
else {
Ok(JsValue::undefined())
}
}
Some(ref desc) => match desc.kind() {
// 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
DescriptorKind::Data {
value: Some(value), ..
} => Ok(value.clone()),
// 5. Assert: IsAccessorDescriptor(desc) is true.
// 6. Let getter be desc.[[Get]].
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
// 8. Return ? Call(getter, Receiver).
context.call(get, &receiver, &[])
}
// 7. If getter is undefined, return undefined.
_ => Ok(JsValue::undefined()),
},
}
}
/// Abstract operation `OrdinarySet`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryset
#[inline]
pub(crate) fn ordinary_set(
obj: &JsObject,
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
// 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
// OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc )
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-ordinarysetwithowndescriptor
// 1. Assert: IsPropertyKey(P) is true.
let own_desc = if let Some(desc) = obj.__get_own_property__(&key, context)? {
desc
}
// 2. If ownDesc is undefined, then
// a. Let parent be ? O.[[GetPrototypeOf]]().
// b. If parent is not null, then
else if let Some(ref mut parent) = obj.__get_prototype_of__(context)?.as_object() {
// i. Return ? parent.[[Set]](P, V, Receiver).
return parent.__set__(key, value, receiver, context);
}
// c. Else,
else {
// i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true,
// [[Enumerable]]: true, [[Configurable]]: true }.
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(true)
.build()
};
// 3. If IsDataDescriptor(ownDesc) is true, then
if own_desc.is_data_descriptor() {
// a. If ownDesc.[[Writable]] is false, return false.
if !own_desc.expect_writable() {
return Ok(false);
}
let receiver = match receiver.as_object() {
Some(obj) => obj,
// b. If Type(Receiver) is not Object, return false.
_ => return Ok(false),
};
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
// d. If existingDescriptor is not undefined, then
if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? {
// i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
if existing_desc.is_accessor_descriptor() {
return Ok(false);
}
// ii. If existingDescriptor.[[Writable]] is false, return false.
if !existing_desc.expect_writable() {
return Ok(false);
}
// iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
return receiver.__define_own_property__(
key,
PropertyDescriptor::builder().value(value).build(),
context,
);
}
// e. Else
else {
// i. Assert: Receiver does not currently have a property P.
// ii. Return ? CreateDataProperty(Receiver, P, V).
return receiver.create_data_property(key, value, context);
}
}
// 4. Assert: IsAccessorDescriptor(ownDesc) is true.
debug_assert!(own_desc.is_accessor_descriptor());
// 5. Let setter be ownDesc.[[Set]].
match own_desc.set() {
Some(set) if !set.is_undefined() => {
// 7. Perform ? Call(setter, Receiver, « V »).
context.call(set, &receiver, &[value])?;
// 8. Return true.
Ok(true)
}
// 6. If setter is undefined, return false.
_ => Ok(false),
}
}
/// Abstract operation `OrdinaryDelete`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinarydelete
#[inline]
pub(crate) fn ordinary_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
Ok(
// 2. Let desc be ? O.[[GetOwnProperty]](P).
match obj.__get_own_property__(key, context)? {
// 4. If desc.[[Configurable]] is true, then
Some(desc) if desc.expect_configurable() => {
// a. Remove the own property with name P from O.
obj.borrow_mut().remove(key);
// b. Return true.
true
}
// 5. Return false.
Some(_) => false,
// 3. If desc is undefined, return true.
None => true,
},
)
}
/// Abstract operation `OrdinaryOwnPropertyKeys`.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
#[inline]
pub(crate) fn ordinary_own_property_keys(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
// 1. Let keys be a new empty List.
let mut keys = Vec::new();
let ordered_indexes = {
let mut indexes: Vec<_> = obj
.borrow()
.properties
.index_property_keys()
.copied()
.collect();
indexes.sort_unstable();
indexes
};
// 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
// a. Add P as the last element of keys.
keys.extend(ordered_indexes.into_iter().map(|idx| idx.into()));
// 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
// a. Add P as the last element of keys.
keys.extend(
obj.borrow()
.properties
.string_property_keys()
.cloned()
.map(|s| s.into()),
);
// 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
// a. Add P as the last element of keys.
keys.extend(
obj.borrow()
.properties
.symbol_property_keys()
.cloned()
.map(|sym| sym.into()),
);
// 5. Return keys.
Ok(keys)
}
/// Abstract operation `IsCompatiblePropertyDescriptor`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iscompatiblepropertydescriptor
#[inline]
pub(crate) fn is_compatible_property_descriptor(
extensible: bool,
desc: PropertyDescriptor,
current: PropertyDescriptor,
) -> bool {
// 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current).
validate_and_apply_property_descriptor(None, extensible, desc, Some(current))
}
/// Abstract operation `ValidateAndApplyPropertyDescriptor`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
#[inline]
pub(crate) fn validate_and_apply_property_descriptor(
obj_and_key: Option<(&JsObject, PropertyKey)>,
extensible: bool,
desc: PropertyDescriptor,
current: Option<PropertyDescriptor>,
) -> bool {
// 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
let mut current = if let Some(own) = current {
own
}
// 2. If current is undefined, then
else {
// a. If extensible is false, return false.
if !extensible {
return false;
}
// b. Assert: extensible is true.
if let Some((obj, key)) = obj_and_key {
obj.borrow_mut().properties.insert(
key,
// c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
// i. If O is not undefined, create an own data property named P of
// object O whose [[Value]], [[Writable]], [[Enumerable]], and
// [[Configurable]] attribute values are described by Desc.
// If the value of an attribute field of Desc is absent, the attribute
// of the newly created property is set to its default value.
desc.into_data_defaulted()
}
// d. Else,
else {
// i. Assert: ! IsAccessorDescriptor(Desc) is true.
// ii. If O is not undefined, create an own accessor property named P
// of object O whose [[Get]], [[Set]], [[Enumerable]], and [[Configurable]]
// attribute values are described by Desc. If the value of an attribute field
// of Desc is absent, the attribute of the newly created property is set to
// its default value.
desc.into_accessor_defaulted()
},
);
}
// e. Return true.
return true;
};
// 3. If every field in Desc is absent, return true.
if desc.is_empty() {
return true;
}
// 4. If current.[[Configurable]] is false, then
if !current.expect_configurable() {
// a. If Desc.[[Configurable]] is present and its value is true, return false.
if matches!(desc.configurable(), Some(true)) {
return false;
}
// b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]])
// is false, return false.
if matches!(desc.enumerable(), Some(desc_enum) if desc_enum != current.expect_enumerable())
{
return false;
}
}
// 5. If ! IsGenericDescriptor(Desc) is true, then
if desc.is_generic_descriptor() {
// a. NOTE: No further validation is required.
}
// 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then
else if current.is_data_descriptor() != desc.is_data_descriptor() {
// a. If current.[[Configurable]] is false, return false.
if !current.expect_configurable() {
return false;
}
if obj_and_key.is_some() {
// b. If IsDataDescriptor(current) is true, then
if current.is_data_descriptor() {
// i. If O is not undefined, convert the property named P of object O from a data
// property to an accessor property. Preserve the existing values of the converted
// property's [[Configurable]] and [[Enumerable]] attributes and set the rest of
// the property's attributes to their default values.
current = current.into_accessor_defaulted();
}
// c. Else,
else {
// i. If O is not undefined, convert the property named P of object O from an
// accessor property to a data property. Preserve the existing values of the
// converted property's [[Configurable]] and [[Enumerable]] attributes and set
// the rest of the property's attributes to their default values.
current = current.into_data_defaulted();
}
}
}
// 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
else if current.is_data_descriptor() && desc.is_data_descriptor() {
// a. If current.[[Configurable]] is false and current.[[Writable]] is false, then
if !current.expect_configurable() && !current.expect_writable() {
// i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false.
if matches!(desc.writable(), Some(true)) {
return false;
}
// ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
if matches!(desc.value(), Some(value) if !JsValue::same_value(value, current.expect_value()))
{
return false;
}
// iii. Return true.
return true;
}
}
// 8. Else,
// a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true.
// b. If current.[[Configurable]] is false, then
else if !current.expect_configurable() {
// i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false.
if matches!(desc.set(), Some(set) if !JsValue::same_value(set, current.expect_set())) {
return false;
}
// ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false.
if matches!(desc.get(), Some(get) if !JsValue::same_value(get, current.expect_get())) {
return false;
}
// iii. Return true.
return true;
}
// 9. If O is not undefined, then
if let Some((obj, key)) = obj_and_key {
// a. For each field of Desc that is present, set the corresponding attribute of the
// property named P of object O to the value of the field.
current.fill_with(desc);
obj.borrow_mut().properties.insert(key, current);
}
// 10. Return true.
true
}

189
boa/src/object/internal_methods/string.rs

@ -0,0 +1,189 @@
use crate::{
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for string exotic objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects
pub(crate) static STRING_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods {
__get_own_property__: string_exotic_get_own_property,
__define_own_property__: string_exotic_define_own_property,
__own_property_keys__: string_exotic_own_property_keys,
..ORDINARY_INTERNAL_METHODS
};
/// Gets own property of 'String' exotic object
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
#[inline]
pub(crate) fn string_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
) -> JsResult<Option<PropertyDescriptor>> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let desc be OrdinaryGetOwnProperty(S, P).
let desc = super::ordinary_get_own_property(obj, key, context)?;
// 3. If desc is not undefined, return desc.
if desc.is_some() {
Ok(desc)
} else {
// 4. Return ! StringGetOwnProperty(S, P).
Ok(string_get_own_property(obj, key))
}
}
/// Defines own property of 'String' exotic object
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-defineownproperty-p-desc
#[inline]
pub(crate) fn string_exotic_define_own_property(
obj: &JsObject,
key: PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let stringDesc be ! StringGetOwnProperty(S, P).
let string_desc = string_get_own_property(obj, &key);
// 3. If stringDesc is not undefined, then
if let Some(string_desc) = string_desc {
// a. Let extensible be S.[[Extensible]].
let extensible = obj.borrow().extensible;
// b. Return ! IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc).
Ok(super::is_compatible_property_descriptor(
extensible,
desc,
string_desc,
))
} else {
// 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc).
super::ordinary_define_own_property(obj, key, desc, context)
}
}
/// Gets own property keys of 'String' exotic object
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects-ownpropertykeys
#[inline]
pub(crate) fn string_exotic_own_property_keys(
obj: &JsObject,
_context: &mut Context,
) -> JsResult<Vec<PropertyKey>> {
let obj = obj.borrow();
// 2. Let str be O.[[StringData]].
// 3. Assert: Type(str) is String.
let string = obj
.as_string()
.expect("string exotic method should only be callable from string objects");
// 4. Let len be the length of str.
let len = string.encode_utf16().count();
// 1. Let keys be a new empty List.
let mut keys = Vec::with_capacity(len);
// 5. For each integer i starting with 0 such that i < len, in ascending order, do
// a. Add ! ToString(𝔽(i)) as the last element of keys.
keys.extend((0..len).into_iter().map(|idx| idx.into()));
// 6. For each own property key P of O such that P is an array index
// and ! ToIntegerOrInfinity(P) ≥ len, in ascending numeric index order, do
// a. Add P as the last element of keys.
let mut remaining_indices: Vec<_> = obj
.properties
.index_property_keys()
.cloned()
.filter(|idx| (*idx as usize) >= len)
.collect();
remaining_indices.sort_unstable();
keys.extend(remaining_indices.into_iter().map(|idx| idx.into()));
// 7. For each own property key P of O such that Type(P) is String and P is not
// an array index, in ascending chronological order of property creation, do
// a. Add P as the last element of keys.
keys.extend(
obj.properties
.string_property_keys()
.cloned()
.map(|s| s.into()),
);
// 8. For each own property key P of O such that Type(P) is Symbol, in ascending
// chronological order of property creation, do
// a. Add P as the last element of keys.
keys.extend(
obj.properties
.symbol_property_keys()
.cloned()
.map(|sym| sym.into()),
);
// 9. Return keys.
Ok(keys)
}
/// StringGetOwnProperty abstract operation
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-stringgetownproperty
#[allow(clippy::float_cmp)]
#[inline]
fn string_get_own_property(obj: &JsObject, key: &PropertyKey) -> Option<PropertyDescriptor> {
// 1. Assert: S is an Object that has a [[StringData]] internal slot.
// 2. Assert: IsPropertyKey(P) is true.
// 3. If Type(P) is not String, return undefined.
// 4. Let index be ! CanonicalNumericIndexString(P).
// 5. If index is undefined, return undefined.
// 6. If IsIntegralNumber(index) is false, return undefined.
// 7. If index is -0𝔽, return undefined.
let pos = match key {
PropertyKey::Index(index) => *index as usize,
_ => return None,
};
// 8. Let str be S.[[StringData]].
// 9. Assert: Type(str) is String.
let string = obj
.borrow()
.as_string()
.expect("string exotic method should only be callable from string objects");
// 10. Let len be the length of str.
// 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined.
// 12. Let resultStr be the String value of length 1, containing one code unit from str, specifically the code unit at index ℝ(index).
let result_str = string
.encode_utf16()
.nth(pos)
.map(|c| JsValue::from(String::from_utf16_lossy(&[c])))?;
// 13. Return the PropertyDescriptor { [[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }.
let desc = PropertyDescriptor::builder()
.value(result_str)
.writable(false)
.enumerable(true)
.configurable(false)
.build();
Some(desc)
}

521
boa/src/object/mod.rs

@ -28,13 +28,20 @@ use std::{
mod tests;
mod gcobject;
mod internal_methods;
pub(crate) mod internal_methods;
mod operations;
mod property_map;
use crate::builtins::object::for_in_iterator::ForInIterator;
pub use gcobject::{JsObject, RecursionLimiter, Ref, RefMut};
use internal_methods::InternalObjectMethods;
pub use property_map::*;
use self::internal_methods::{
array::ARRAY_EXOTIC_INTERNAL_METHODS, string::STRING_EXOTIC_INTERNAL_METHODS,
ORDINARY_INTERNAL_METHODS,
};
/// Static `prototype`, usually set on constructors as a key to point to their respective prototype object.
pub static PROTOTYPE: &str = "prototype";
@ -73,9 +80,16 @@ pub struct Object {
extensible: bool,
}
/// Defines the kind of an object and its internal methods
#[derive(Trace, Finalize)]
pub struct ObjectData {
kind: ObjectKind,
internal_methods: &'static InternalObjectMethods,
}
/// Defines the different types of objects.
#[derive(Debug, Trace, Finalize)]
pub enum ObjectData {
pub enum ObjectKind {
Array,
ArrayIterator(ArrayIterator),
Map(OrderedMap<JsValue>),
@ -99,7 +113,177 @@ pub enum ObjectData {
NativeObject(Box<dyn NativeObject>),
}
impl Display for ObjectData {
impl ObjectData {
/// Create the `Array` object data and reference its exclusive internal methods
pub fn array() -> Self {
Self {
kind: ObjectKind::Array,
internal_methods: &ARRAY_EXOTIC_INTERNAL_METHODS,
}
}
/// Create the `ArrayIterator` object data
pub fn array_iterator(array_iterator: ArrayIterator) -> Self {
Self {
kind: ObjectKind::ArrayIterator(array_iterator),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Map` object data
pub fn map(map: OrderedMap<JsValue>) -> Self {
Self {
kind: ObjectKind::Map(map),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `MapIterator` object data
pub fn map_iterator(map_iterator: MapIterator) -> Self {
Self {
kind: ObjectKind::MapIterator(map_iterator),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `RegExp` object data
pub fn reg_exp(reg_exp: Box<RegExp>) -> Self {
Self {
kind: ObjectKind::RegExp(reg_exp),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `RegExpStringIterator` object data
pub fn reg_exp_string_iterator(reg_exp_string_iterator: RegExpStringIterator) -> Self {
Self {
kind: ObjectKind::RegExpStringIterator(reg_exp_string_iterator),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `BigInt` object data
pub fn big_int(big_int: JsBigInt) -> Self {
Self {
kind: ObjectKind::BigInt(big_int),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Boolean` object data
pub fn boolean(boolean: bool) -> Self {
Self {
kind: ObjectKind::Boolean(boolean),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `ForInIterator` object data
pub fn for_in_iterator(for_in_iterator: ForInIterator) -> Self {
Self {
kind: ObjectKind::ForInIterator(for_in_iterator),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Function` object data
pub fn function(function: Function) -> Self {
Self {
kind: ObjectKind::Function(function),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Set` object data
pub fn set(set: OrderedSet<JsValue>) -> Self {
Self {
kind: ObjectKind::Set(set),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `SetIterator` object data
pub fn set_iterator(set_iterator: SetIterator) -> Self {
Self {
kind: ObjectKind::SetIterator(set_iterator),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `String` object data and reference its exclusive internal methods
pub fn string(string: JsString) -> Self {
Self {
kind: ObjectKind::String(string),
internal_methods: &STRING_EXOTIC_INTERNAL_METHODS,
}
}
/// Create the `StringIterator` object data
pub fn string_iterator(string_iterator: StringIterator) -> Self {
Self {
kind: ObjectKind::StringIterator(string_iterator),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Number` object data
pub fn number(number: f64) -> Self {
Self {
kind: ObjectKind::Number(number),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Symbol` object data
pub fn symbol(symbol: JsSymbol) -> Self {
Self {
kind: ObjectKind::Symbol(symbol),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Error` object data
pub fn error() -> Self {
Self {
kind: ObjectKind::Error,
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Ordinary` object data
pub fn ordinary() -> Self {
Self {
kind: ObjectKind::Ordinary,
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Date` object data
pub fn date(date: Date) -> Self {
Self {
kind: ObjectKind::Date(date),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `Global` object data
pub fn global() -> Self {
Self {
kind: ObjectKind::Global,
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
/// Create the `NativeObject` object data
pub fn native_object(native_object: Box<dyn NativeObject>) -> Self {
Self {
kind: ObjectKind::NativeObject(native_object),
internal_methods: &ORDINARY_INTERNAL_METHODS,
}
}
}
impl Display for ObjectKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
@ -131,12 +315,21 @@ impl Display for ObjectData {
}
}
impl Debug for ObjectData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ObjectData")
.field("kind", &self.kind)
.field("internal_methods", &"internal_methods")
.finish()
}
}
impl Default for Object {
/// Return a new ObjectData struct, with `kind` set to Ordinary
#[inline]
fn default() -> Self {
Self {
data: ObjectData::Ordinary,
data: ObjectData::ordinary(),
properties: PropertyMap::default(),
prototype: JsValue::null(),
extensible: true,
@ -156,7 +349,7 @@ impl Object {
let _timer = BoaProfiler::global().start_event("Object::Function", "object");
Self {
data: ObjectData::Function(function),
data: ObjectData::function(function),
properties: PropertyMap::default(),
prototype,
extensible: true,
@ -181,7 +374,7 @@ impl Object {
#[inline]
pub fn boolean(value: bool) -> Self {
Self {
data: ObjectData::Boolean(value),
data: ObjectData::boolean(value),
properties: PropertyMap::default(),
prototype: JsValue::null(),
extensible: true,
@ -192,7 +385,7 @@ impl Object {
#[inline]
pub fn number(value: f64) -> Self {
Self {
data: ObjectData::Number(value),
data: ObjectData::number(value),
properties: PropertyMap::default(),
prototype: JsValue::null(),
extensible: true,
@ -206,7 +399,7 @@ impl Object {
S: Into<JsString>,
{
Self {
data: ObjectData::String(value.into()),
data: ObjectData::string(value.into()),
properties: PropertyMap::default(),
prototype: JsValue::null(),
extensible: true,
@ -217,7 +410,7 @@ impl Object {
#[inline]
pub fn bigint(value: JsBigInt) -> Self {
Self {
data: ObjectData::BigInt(value),
data: ObjectData::big_int(value),
properties: PropertyMap::default(),
prototype: JsValue::null(),
extensible: true,
@ -231,13 +424,18 @@ impl Object {
T: NativeObject,
{
Self {
data: ObjectData::NativeObject(Box::new(value)),
data: ObjectData::native_object(Box::new(value)),
properties: PropertyMap::default(),
prototype: JsValue::null(),
extensible: true,
}
}
#[inline]
pub fn kind(&self) -> &ObjectKind {
&self.data.kind
}
/// It determines if Object is a callable function with a `[[Call]]` internal method.
///
/// More information:
@ -248,7 +446,13 @@ impl Object {
// todo: functions are not the only objects that are callable.
// todo: e.g. https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
pub fn is_callable(&self) -> bool {
matches!(self.data, ObjectData::Function(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::Function(_),
..
}
)
}
/// It determines if Object is a function object with a `[[Construct]]` internal method.
@ -261,19 +465,28 @@ impl Object {
// todo: functions are not the only objects that are constructable.
// todo: e.g. https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget
pub fn is_constructable(&self) -> bool {
matches!(self.data, ObjectData::Function(ref f) if f.is_constructable())
matches!(self.data, ObjectData{kind: ObjectKind::Function(ref f), ..} if f.is_constructable())
}
/// Checks if it an `Array` object.
#[inline]
pub fn is_array(&self) -> bool {
matches!(self.data, ObjectData::Array)
matches!(
self.data,
ObjectData {
kind: ObjectKind::Array,
..
}
)
}
#[inline]
pub fn as_array(&self) -> Option<()> {
match self.data {
ObjectData::Array => Some(()),
ObjectData {
kind: ObjectKind::Array,
..
} => Some(()),
_ => None,
}
}
@ -281,13 +494,22 @@ impl Object {
/// Checks if it is an `ArrayIterator` object.
#[inline]
pub fn is_array_iterator(&self) -> bool {
matches!(self.data, ObjectData::ArrayIterator(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::ArrayIterator(_),
..
}
)
}
#[inline]
pub fn as_array_iterator(&self) -> Option<&ArrayIterator> {
match self.data {
ObjectData::ArrayIterator(ref iter) => Some(iter),
ObjectData {
kind: ObjectKind::ArrayIterator(ref iter),
..
} => Some(iter),
_ => None,
}
}
@ -295,7 +517,10 @@ impl Object {
#[inline]
pub fn as_array_iterator_mut(&mut self) -> Option<&mut ArrayIterator> {
match &mut self.data {
ObjectData::ArrayIterator(iter) => Some(iter),
ObjectData {
kind: ObjectKind::ArrayIterator(iter),
..
} => Some(iter),
_ => None,
}
}
@ -303,7 +528,10 @@ impl Object {
#[inline]
pub fn as_string_iterator_mut(&mut self) -> Option<&mut StringIterator> {
match &mut self.data {
ObjectData::StringIterator(iter) => Some(iter),
ObjectData {
kind: ObjectKind::StringIterator(iter),
..
} => Some(iter),
_ => None,
}
}
@ -311,7 +539,10 @@ impl Object {
#[inline]
pub fn as_regexp_string_iterator_mut(&mut self) -> Option<&mut RegExpStringIterator> {
match &mut self.data {
ObjectData::RegExpStringIterator(iter) => Some(iter),
ObjectData {
kind: ObjectKind::RegExpStringIterator(iter),
..
} => Some(iter),
_ => None,
}
}
@ -319,7 +550,10 @@ impl Object {
#[inline]
pub fn as_for_in_iterator(&self) -> Option<&ForInIterator> {
match &self.data {
ObjectData::ForInIterator(iter) => Some(iter),
ObjectData {
kind: ObjectKind::ForInIterator(iter),
..
} => Some(iter),
_ => None,
}
}
@ -327,7 +561,10 @@ impl Object {
#[inline]
pub fn as_for_in_iterator_mut(&mut self) -> Option<&mut ForInIterator> {
match &mut self.data {
ObjectData::ForInIterator(iter) => Some(iter),
ObjectData {
kind: ObjectKind::ForInIterator(iter),
..
} => Some(iter),
_ => None,
}
}
@ -335,13 +572,22 @@ impl Object {
/// Checks if it is a `Map` object.pub
#[inline]
pub fn is_map(&self) -> bool {
matches!(self.data, ObjectData::Map(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::Map(_),
..
}
)
}
#[inline]
pub fn as_map_ref(&self) -> Option<&OrderedMap<JsValue>> {
match self.data {
ObjectData::Map(ref map) => Some(map),
ObjectData {
kind: ObjectKind::Map(ref map),
..
} => Some(map),
_ => None,
}
}
@ -349,7 +595,10 @@ impl Object {
#[inline]
pub fn as_map_mut(&mut self) -> Option<&mut OrderedMap<JsValue>> {
match &mut self.data {
ObjectData::Map(map) => Some(map),
ObjectData {
kind: ObjectKind::Map(map),
..
} => Some(map),
_ => None,
}
}
@ -357,20 +606,32 @@ impl Object {
#[inline]
pub fn as_map_iterator_mut(&mut self) -> Option<&mut MapIterator> {
match &mut self.data {
ObjectData::MapIterator(iter) => Some(iter),
ObjectData {
kind: ObjectKind::MapIterator(iter),
..
} => Some(iter),
_ => None,
}
}
#[inline]
pub fn is_set(&self) -> bool {
matches!(self.data, ObjectData::Set(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::Set(_),
..
}
)
}
#[inline]
pub fn as_set_ref(&self) -> Option<&OrderedSet<JsValue>> {
match self.data {
ObjectData::Set(ref set) => Some(set),
ObjectData {
kind: ObjectKind::Set(ref set),
..
} => Some(set),
_ => None,
}
}
@ -378,7 +639,10 @@ impl Object {
#[inline]
pub fn as_set_mut(&mut self) -> Option<&mut OrderedSet<JsValue>> {
match &mut self.data {
ObjectData::Set(set) => Some(set),
ObjectData {
kind: ObjectKind::Set(set),
..
} => Some(set),
_ => None,
}
}
@ -386,7 +650,10 @@ impl Object {
#[inline]
pub fn as_set_iterator_mut(&mut self) -> Option<&mut SetIterator> {
match &mut self.data {
ObjectData::SetIterator(iter) => Some(iter),
ObjectData {
kind: ObjectKind::SetIterator(iter),
..
} => Some(iter),
_ => None,
}
}
@ -394,13 +661,22 @@ impl Object {
/// Checks if it a `String` object.
#[inline]
pub fn is_string(&self) -> bool {
matches!(self.data, ObjectData::String(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::String(_),
..
}
)
}
#[inline]
pub fn as_string(&self) -> Option<JsString> {
match self.data {
ObjectData::String(ref string) => Some(string.clone()),
ObjectData {
kind: ObjectKind::String(ref string),
..
} => Some(string.clone()),
_ => None,
}
}
@ -408,13 +684,22 @@ impl Object {
/// Checks if it a `Function` object.
#[inline]
pub fn is_function(&self) -> bool {
matches!(self.data, ObjectData::Function(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::Function(_),
..
}
)
}
#[inline]
pub fn as_function(&self) -> Option<&Function> {
match self.data {
ObjectData::Function(ref function) => Some(function),
ObjectData {
kind: ObjectKind::Function(ref function),
..
} => Some(function),
_ => None,
}
}
@ -422,13 +707,22 @@ impl Object {
/// Checks if it a Symbol object.
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self.data, ObjectData::Symbol(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::Symbol(_),
..
}
)
}
#[inline]
pub fn as_symbol(&self) -> Option<JsSymbol> {
match self.data {
ObjectData::Symbol(ref symbol) => Some(symbol.clone()),
ObjectData {
kind: ObjectKind::Symbol(ref symbol),
..
} => Some(symbol.clone()),
_ => None,
}
}
@ -436,13 +730,22 @@ impl Object {
/// Checks if it an Error object.
#[inline]
pub fn is_error(&self) -> bool {
matches!(self.data, ObjectData::Error)
matches!(
self.data,
ObjectData {
kind: ObjectKind::Error,
..
}
)
}
#[inline]
pub fn as_error(&self) -> Option<()> {
match self.data {
ObjectData::Error => Some(()),
ObjectData {
kind: ObjectKind::Error,
..
} => Some(()),
_ => None,
}
}
@ -450,13 +753,22 @@ impl Object {
/// Checks if it a Boolean object.
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self.data, ObjectData::Boolean(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::Boolean(_),
..
}
)
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self.data {
ObjectData::Boolean(boolean) => Some(boolean),
ObjectData {
kind: ObjectKind::Boolean(boolean),
..
} => Some(boolean),
_ => None,
}
}
@ -464,13 +776,22 @@ impl Object {
/// Checks if it a `Number` object.
#[inline]
pub fn is_number(&self) -> bool {
matches!(self.data, ObjectData::Number(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::Number(_),
..
}
)
}
#[inline]
pub fn as_number(&self) -> Option<f64> {
match self.data {
ObjectData::Number(number) => Some(number),
ObjectData {
kind: ObjectKind::Number(number),
..
} => Some(number),
_ => None,
}
}
@ -478,13 +799,43 @@ impl Object {
/// Checks if it a `BigInt` object.
#[inline]
pub fn is_bigint(&self) -> bool {
matches!(self.data, ObjectData::BigInt(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::BigInt(_),
..
}
)
}
#[inline]
pub fn as_bigint(&self) -> Option<&JsBigInt> {
match self.data {
ObjectData::BigInt(ref bigint) => Some(bigint),
ObjectData {
kind: ObjectKind::BigInt(ref bigint),
..
} => Some(bigint),
_ => None,
}
}
#[inline]
pub fn is_date(&self) -> bool {
matches!(
self.data,
ObjectData {
kind: ObjectKind::Date(_),
..
}
)
}
pub fn as_date(&self) -> Option<&Date> {
match self.data {
ObjectData {
kind: ObjectKind::Date(ref date),
..
} => Some(date),
_ => None,
}
}
@ -492,13 +843,22 @@ impl Object {
/// Checks if it a `RegExp` object.
#[inline]
pub fn is_regexp(&self) -> bool {
matches!(self.data, ObjectData::RegExp(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::RegExp(_),
..
}
)
}
#[inline]
pub fn as_regexp(&self) -> Option<&RegExp> {
match self.data {
ObjectData::RegExp(ref regexp) => Some(regexp),
ObjectData {
kind: ObjectKind::RegExp(ref regexp),
..
} => Some(regexp),
_ => None,
}
}
@ -506,7 +866,13 @@ impl Object {
/// Checks if it an ordinary object.
#[inline]
pub fn is_ordinary(&self) -> bool {
matches!(self.data, ObjectData::Ordinary)
matches!(
self.data,
ObjectData {
kind: ObjectKind::Ordinary,
..
}
)
}
#[inline]
@ -545,13 +911,22 @@ impl Object {
/// Returns `true` if it holds an Rust type that implements `NativeObject`.
#[inline]
pub fn is_native_object(&self) -> bool {
matches!(self.data, ObjectData::NativeObject(_))
matches!(
self.data,
ObjectData {
kind: ObjectKind::NativeObject(_),
..
}
)
}
#[inline]
pub fn as_native_object(&self) -> Option<&dyn NativeObject> {
match self.data {
ObjectData::NativeObject(ref object) => Some(object.as_ref()),
ObjectData {
kind: ObjectKind::NativeObject(ref object),
..
} => Some(object.as_ref()),
_ => None,
}
}
@ -563,7 +938,10 @@ impl Object {
T: NativeObject,
{
match self.data {
ObjectData::NativeObject(ref object) => object.deref().as_any().is::<T>(),
ObjectData {
kind: ObjectKind::NativeObject(ref object),
..
} => object.deref().as_any().is::<T>(),
_ => false,
}
}
@ -576,7 +954,10 @@ impl Object {
T: NativeObject,
{
match self.data {
ObjectData::NativeObject(ref object) => object.deref().as_any().downcast_ref::<T>(),
ObjectData {
kind: ObjectKind::NativeObject(ref object),
..
} => object.deref().as_any().downcast_ref::<T>(),
_ => None,
}
}
@ -589,9 +970,10 @@ impl Object {
T: NativeObject,
{
match self.data {
ObjectData::NativeObject(ref mut object) => {
object.deref_mut().as_mut_any().downcast_mut::<T>()
}
ObjectData {
kind: ObjectKind::NativeObject(ref mut object),
..
} => object.deref_mut().as_mut_any().downcast_mut::<T>(),
_ => None,
}
}
@ -600,6 +982,35 @@ impl Object {
pub fn properties(&self) -> &PropertyMap {
&self.properties
}
/// Helper function for property insertion.
#[inline]
pub(crate) fn insert<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.properties.insert(key.into(), property.into())
}
/// Helper function for property removal.
#[inline]
pub(crate) fn remove(&mut self, key: &PropertyKey) -> Option<PropertyDescriptor> {
self.properties.remove(key)
}
/// Inserts a field in the object `properties` without checking if it's writable.
///
/// If a field was already in the object with the same name that a `Some` is returned
/// with that field, otherwise None is retuned.
#[inline]
pub fn insert_property<K, P>(&mut self, key: K, property: P) -> Option<PropertyDescriptor>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
self.insert(key, property)
}
}
/// The functions binding.
@ -770,7 +1181,7 @@ impl<'context> FunctionBuilder<'context> {
/// Initializes the `Function.prototype` function object.
pub(crate) fn build_function_prototype(&mut self, object: &JsObject) {
let mut object = object.borrow_mut();
object.data = ObjectData::Function(self.function.take().unwrap());
object.data = ObjectData::function(self.function.take().unwrap());
object.set_prototype_instance(
self.context
.standard_objects()
@ -1169,7 +1580,7 @@ impl<'context> ConstructorBuilder<'context> {
{
let mut constructor = self.constructor_object.borrow_mut();
constructor.data = ObjectData::Function(function);
constructor.data = ObjectData::function(function);
constructor.insert("length", length);
constructor.insert("name", name);

487
boa/src/object/operations.rs

@ -0,0 +1,487 @@
use crate::{
builtins::Array,
property::{PropertyDescriptor, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols,
value::Type,
Context, JsResult, JsValue,
};
use super::JsObject;
impl JsObject {
/// Get property from object or throw.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-get-o-p
#[inline]
pub(crate) fn get<K>(&self, key: K, context: &mut Context) -> JsResult<JsValue>
where
K: Into<PropertyKey>,
{
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Return ? O.[[Get]](P, O).
self.__get__(&key.into(), self.clone().into(), context)
}
/// set property of object or throw if bool flag is passed.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-set-o-p-v-throw
#[inline]
pub(crate) fn set<K, V>(
&self,
key: K,
value: V,
throw: bool,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Assert: Type(Throw) is Boolean.
// 4. Let success be ? O.[[Set]](P, V, O).
let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?;
// 5. If success is false and Throw is true, throw a TypeError exception.
if !success && throw {
return Err(
context.construct_type_error(format!("cannot set non-writable property: {}", key))
);
}
// 6. Return success.
Ok(success)
}
/// Create data property
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
pub(crate) fn create_data_property<K, V>(
&self,
key: K,
value: V,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
let new_desc = PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true);
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
self.__define_own_property__(key.into(), new_desc.into(), context)
}
// todo: CreateMethodProperty
/// Create data property or throw
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-deletepropertyorthrow
pub(crate) fn create_data_property_or_throw<K, V>(
&self,
key: K,
value: V,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? CreateDataProperty(O, P, V).
let success = self.create_data_property(key.clone(), value, context)?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(context.construct_type_error(format!("cannot redefine property: {}", key)));
}
// 5. Return success.
Ok(success)
}
/// Define property or throw.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
#[inline]
pub(crate) fn define_property_or_throw<K, P>(
&self,
key: K,
desc: P,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
P: Into<PropertyDescriptor>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
let success = self.__define_own_property__(key.clone(), desc.into(), context)?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(context.construct_type_error(format!("cannot redefine property: {}", key)));
}
// 5. Return success.
Ok(success)
}
/// Defines the property or throws a `TypeError` if the operation fails.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-definepropertyorthrow
#[inline]
pub(crate) fn delete_property_or_throw<K>(
&self,
key: K,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? O.[[Delete]](P).
let success = self.__delete__(&key, context)?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(context.construct_type_error(format!("cannot delete property: {}", key)));
}
// 5. Return success.
Ok(success)
}
/// Retrieves value of specific property, when the value of the property is expected to be a function.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-getmethod
#[inline]
pub(crate) fn get_method<K>(&self, context: &mut Context, key: K) -> JsResult<Option<JsObject>>
where
K: Into<PropertyKey>,
{
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P).
let value = self.get(key, context)?;
// 3. If func is either undefined or null, return undefined.
if value.is_null_or_undefined() {
return Ok(None);
}
// 4. If IsCallable(func) is false, throw a TypeError exception.
// 5. Return func.
match value.as_object() {
Some(object) if object.is_callable() => Ok(Some(object)),
_ => Err(context
.construct_type_error("value returned for property of object is not a function")),
}
}
/// Check if object has property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hasproperty
// NOTE: for now context is not used but it will in the future.
#[inline]
pub(crate) fn has_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Return ? O.[[HasProperty]](P).
self.__has_property__(&key.into(), context)
}
/// Check if object has an own property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-hasownproperty
#[inline]
pub(crate) fn has_own_property<K>(&self, key: K, context: &mut Context) -> JsResult<bool>
where
K: Into<PropertyKey>,
{
let key = key.into();
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let desc be ? O.[[GetOwnProperty]](P).
let desc = self.__get_own_property__(&key, context)?;
// 4. If desc is undefined, return false.
// 5. Return true.
Ok(desc.is_some())
}
/// Call this object.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
#[inline]
pub(crate) fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
self.call_construct(this, args, context, false)
}
/// Construct an instance of this object with the specified arguments.
///
/// # Panics
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
#[inline]
pub(crate) fn construct(
&self,
args: &[JsValue],
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
self.call_construct(new_target, args, context, true)
}
// todo: SetIntegrityLevel
// todo: TestIntegrityLevel
pub(crate) fn length_of_array_like(&self, context: &mut Context) -> JsResult<usize> {
// 1. Assert: Type(obj) is Object.
// 2. Return ℝ(? ToLength(? Get(obj, "length"))).
self.get("length", context)?.to_length(context)
}
/// `7.3.22 SpeciesConstructor ( O, defaultConstructor )`
///
/// The abstract operation SpeciesConstructor takes arguments O (an Object) and defaultConstructor (a constructor).
/// It is used to retrieve the constructor that should be used to create new objects that are derived from O.
/// defaultConstructor is the constructor to use if a constructor @@species property cannot be found starting from O.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-speciesconstructor
pub(crate) fn species_constructor(
&self,
default_constructor: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. Assert: Type(O) is Object.
// 2. Let C be ? Get(O, "constructor").
let c = self.clone().get("constructor", context)?;
// 3. If C is undefined, return defaultConstructor.
if c.is_undefined() {
return Ok(default_constructor);
}
// 4. If Type(C) is not Object, throw a TypeError exception.
if !c.is_object() {
return context.throw_type_error("property 'constructor' is not an object");
}
// 5. Let S be ? Get(C, @@species).
let s = c.get_field(WellKnownSymbols::species(), context)?;
// 6. If S is either undefined or null, return defaultConstructor.
if s.is_null_or_undefined() {
return Ok(default_constructor);
}
// 7. If IsConstructor(S) is true, return S.
// 8. Throw a TypeError exception.
if let Some(obj) = s.as_object() {
if obj.is_constructable() {
Ok(s)
} else {
context.throw_type_error("property 'constructor' is not a constructor")
}
} else {
context.throw_type_error("property 'constructor' is not an object")
}
}
/// It is used to iterate over names of object's keys.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-enumerableownpropertynames
pub(crate) fn enumerable_own_property_names(
&self,
kind: PropertyNameKind,
context: &mut Context,
) -> JsResult<Vec<JsValue>> {
// 1. Assert: Type(O) is Object.
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
let own_keys = self.__own_property_keys__(context)?;
// 3. Let properties be a new empty List.
let mut properties = vec![];
// 4. For each element key of ownKeys, do
for key in own_keys {
// a. If Type(key) is String, then
let key_str = match &key {
PropertyKey::String(s) => Some(s.clone()),
PropertyKey::Index(i) => Some(i.to_string().into()),
_ => None,
};
if let Some(key_str) = key_str {
// i. Let desc be ? O.[[GetOwnProperty]](key).
let desc = self.__get_own_property__(&key, context)?;
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
if let Some(desc) = desc {
if desc.expect_enumerable() {
match kind {
// 1. If kind is key, append key to properties.
PropertyNameKind::Key => properties.push(key_str.into()),
// 2. Else,
// a. Let value be ? Get(O, key).
// b. If kind is value, append value to properties.
PropertyNameKind::Value => {
properties.push(self.get(key.clone(), context)?)
}
// c. Else,
// i. Assert: kind is key+value.
// ii. Let entry be ! CreateArrayFromList(« key, value »).
// iii. Append entry to properties.
PropertyNameKind::KeyAndValue => properties.push(
Array::create_array_from_list(
[key_str.into(), self.get(key.clone(), context)?],
context,
)
.into(),
),
}
}
}
}
}
// 5. Return properties.
Ok(properties)
}
// todo: GetFunctionRealm
// todo: CopyDataProperties
// todo: PrivateElementFind
// todo: PrivateFieldAdd
// todo: PrivateMethodOrAccessorAdd
// todo: PrivateGet
// todo: PrivateSet
// todo: DefineField
// todo: InitializeInstanceElements
}
impl JsValue {
// todo: GetV
// todo: GetMethod
/// It is used to create List value whose elements are provided by the indexed properties of
/// self.
///
/// More information:
/// - [EcmaScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createlistfromarraylike
pub(crate) fn create_list_from_array_like(
&self,
element_types: &[Type],
context: &mut Context,
) -> JsResult<Vec<JsValue>> {
// 1. If elementTypes is not present, set elementTypes to « Undefined, Null, Boolean, String, Symbol, Number, BigInt, Object ».
let types = if element_types.is_empty() {
&[
Type::Undefined,
Type::Null,
Type::Boolean,
Type::String,
Type::Symbol,
Type::Number,
Type::BigInt,
Type::Object,
]
} else {
element_types
};
// 2. If Type(obj) is not Object, throw a TypeError exception.
let obj = self
.as_object()
.ok_or_else(|| context.construct_type_error("cannot create list from a primitive"))?;
// 3. Let len be ? LengthOfArrayLike(obj).
let len = obj.length_of_array_like(context)?;
// 4. Let list be a new empty List.
let mut list = Vec::with_capacity(len);
// 5. Let index be 0.
// 6. Repeat, while index < len,
for index in 0..len {
// a. Let indexName be ! ToString(𝔽(index)).
// b. Let next be ? Get(obj, indexName).
let next = obj.get(index, context)?;
// c. If Type(next) is not an element of elementTypes, throw a TypeError exception.
if !types.contains(&next.get_type()) {
return Err(context.construct_type_error("bad type"));
}
// d. Append next as the last element of list.
list.push(next.clone());
// e. Set index to index + 1.
}
// 7. Return list.
Ok(list)
}
}

2
boa/src/realm.rs

@ -32,7 +32,7 @@ impl Realm {
let mut global = Object::default();
// Allow identification of the global object easily
global.data = ObjectData::Global;
global.data = ObjectData::global();
let gc_global = JsObject::new(global);

30
boa/src/string.rs

@ -1,4 +1,7 @@
use crate::gc::{empty_trace, Finalize, Trace};
use crate::{
builtins::string::is_trimmable_whitespace,
gc::{empty_trace, Finalize, Trace},
};
use std::{
alloc::{alloc, dealloc, Layout},
borrow::Borrow,
@ -236,6 +239,31 @@ impl JsString {
// 8. Return -1.
None
}
pub(crate) fn string_to_number(&self) -> f64 {
let string = self.trim_matches(is_trimmable_whitespace);
// TODO: write our own lexer to match syntax StrDecimalLiteral
match string {
"" => 0.0,
"Infinity" | "+Infinity" => f64::INFINITY,
"-Infinity" => f64::NEG_INFINITY,
_ if matches!(
string
.chars()
.take(4)
.collect::<String>()
.to_ascii_lowercase()
.as_str(),
"inf" | "+inf" | "-inf" | "nan" | "+nan" | "-nan"
) =>
{
// Prevent fast_float from parsing "inf", "+inf" as Infinity and "-inf" as -Infinity
f64::NAN
}
_ => fast_float::parse(string).unwrap_or(f64::NAN),
}
}
}
impl Finalize for JsString {}

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

@ -144,7 +144,7 @@ impl Executable for BinOp {
));
}
let key = x.to_property_key(context)?;
context.has_property(&y, &key)
context.has_property(&y, &key)?
}
CompOp::InstanceOf => {
if let Some(object) = y.as_object() {

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

@ -95,14 +95,14 @@ impl Executable for UnaryOp {
.obj()
.run(context)?
.to_object(context)?
.__delete__(&get_const_field.field().into()),
.__delete__(&get_const_field.field().into(), context)?,
),
Node::GetField(ref get_field) => {
let obj = get_field.obj().run(context)?;
let field = &get_field.field().run(context)?;
let res = obj
.to_object(context)?
.__delete__(&field.to_property_key(context)?);
.__delete__(&field.to_property_key(context)?, context)?;
return Ok(JsValue::new(res));
}
Node::Identifier(_) => JsValue::new(false),

29
boa/src/value/display.rs

@ -1,3 +1,5 @@
use crate::object::ObjectKind;
use super::*;
/// This object is used for displaying a `Value`.
@ -86,20 +88,22 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children
JsValue::Object(ref v) => {
// Can use the private "type" field of an Object to match on
// which type of Object it represents for special printing
match v.borrow().data {
ObjectData::String(ref string) => format!("String {{ \"{}\" }}", string),
ObjectData::Boolean(boolean) => format!("Boolean {{ {} }}", boolean),
ObjectData::Number(rational) => {
if rational.is_sign_negative() && rational == 0.0 {
match v.borrow().kind() {
ObjectKind::String(ref string) => format!("String {{ \"{}\" }}", string),
ObjectKind::Boolean(boolean) => format!("Boolean {{ {} }}", boolean),
ObjectKind::Number(rational) => {
if rational.is_sign_negative() && *rational == 0.0 {
"Number { -0 }".to_string()
} else {
let mut buffer = ryu_js::Buffer::new();
format!("Number {{ {} }}", buffer.format(rational))
format!("Number {{ {} }}", buffer.format(*rational))
}
}
ObjectData::Array => {
ObjectKind::Array => {
let len = v
.__get_own_property__(&PropertyKey::from("length"))
.borrow()
.properties()
.get(&PropertyKey::from("length"))
// TODO: do this in a better way `unwrap`
.unwrap()
// FIXME: handle accessor descriptors
@ -118,8 +122,9 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children
// Introduce recursive call to stringify any objects
// which are part of the Array
log_string_from(
v.__get_own_property__(&i.into())
.as_ref()
v.borrow()
.properties()
.get(&i.into())
// FIXME: handle accessor descriptors
.and_then(|p| p.value())
.unwrap_or(&JsValue::Undefined),
@ -135,7 +140,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children
format!("Array({})", len)
}
}
ObjectData::Map(ref map) => {
ObjectKind::Map(ref map) => {
let size = map.len();
if size == 0 {
return String::from("Map(0)");
@ -156,7 +161,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children
format!("Map({})", size)
}
}
ObjectData::Set(ref set) => {
ObjectKind::Set(ref set) => {
let size = set.size();
if size == 0 {

76
boa/src/value/mod.rs

@ -8,8 +8,7 @@ mod tests;
use crate::{
builtins::{
number::{f64_to_int32, f64_to_uint32},
string::is_trimmable_whitespace,
Number,
Array, Number,
},
object::{JsObject, Object, ObjectData},
property::{PropertyDescriptor, PropertyKey},
@ -129,30 +128,12 @@ impl JsValue {
JSONValue::String(v) => Self::new(v),
JSONValue::Bool(v) => Self::new(v),
JSONValue::Array(vs) => {
let array_prototype = context.standard_objects().array_object().prototype();
let new_obj: JsValue =
Object::with_prototype(array_prototype.into(), ObjectData::Array).into();
let length = vs.len();
for (idx, json) in vs.into_iter().enumerate() {
new_obj.set_property(
idx.to_string(),
PropertyDescriptor::builder()
.value(Self::from_json(json, context))
.writable(true)
.enumerable(true)
.configurable(true),
);
}
new_obj.set_property(
"length",
// TODO: Fix length attribute
PropertyDescriptor::builder()
.value(length)
.writable(true)
.enumerable(true)
.configurable(true),
);
new_obj
let vs: Vec<_> = vs
.into_iter()
.map(|json| Self::from_json(json, context))
.collect();
Array::create_array_from_list(vs, context).into()
}
JSONValue::Object(obj) => {
let new_obj = JsValue::new_object(context);
@ -357,7 +338,8 @@ impl JsValue {
let _timer = BoaProfiler::global().start_event("Value::get_property", "value");
match self {
Self::Object(ref object) => {
let property = object.__get_own_property__(&key);
// TODO: had to skip `__get_own_properties__` since we don't have context here
let property = object.borrow().properties().get(&key).cloned();
if property.is_some() {
return property;
}
@ -390,8 +372,9 @@ impl JsValue {
K: Into<PropertyKey>,
{
let _timer = BoaProfiler::global().start_event("Value::has_field", "value");
// todo: call `__has_property__` instead of directly getting from object
self.as_object()
.map(|object| object.__has_property__(&key.into()))
.map(|object| object.borrow().properties().contains_key(&key.into()))
.unwrap_or(false)
}
@ -597,21 +580,21 @@ impl JsValue {
let prototype = context.standard_objects().boolean_object().prototype();
Ok(JsObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Boolean(*boolean),
ObjectData::boolean(*boolean),
)))
}
JsValue::Integer(integer) => {
let prototype = context.standard_objects().number_object().prototype();
Ok(JsObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Number(f64::from(*integer)),
ObjectData::number(f64::from(*integer)),
)))
}
JsValue::Rational(rational) => {
let prototype = context.standard_objects().number_object().prototype();
Ok(JsObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Number(*rational),
ObjectData::number(*rational),
)))
}
JsValue::String(ref string) => {
@ -619,7 +602,7 @@ impl JsValue {
let object = JsObject::new(Object::with_prototype(
prototype.into(),
ObjectData::String(string.clone()),
ObjectData::string(string.clone()),
));
// Make sure the correct length is set on our new string object
object.insert_property(
@ -636,14 +619,14 @@ impl JsValue {
let prototype = context.standard_objects().symbol_object().prototype();
Ok(JsObject::new(Object::with_prototype(
prototype.into(),
ObjectData::Symbol(symbol.clone()),
ObjectData::symbol(symbol.clone()),
)))
}
JsValue::BigInt(ref bigint) => {
let prototype = context.standard_objects().bigint_object().prototype();
Ok(JsObject::new(Object::with_prototype(
prototype.into(),
ObjectData::BigInt(bigint.clone()),
ObjectData::big_int(bigint.clone()),
)))
}
JsValue::Object(jsobject) => Ok(jsobject.clone()),
@ -777,30 +760,7 @@ impl JsValue {
JsValue::Null => Ok(0.0),
JsValue::Undefined => Ok(f64::NAN),
JsValue::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
JsValue::String(ref string) => {
let string = string.trim_matches(is_trimmable_whitespace);
// TODO: write our own lexer to match syntax StrDecimalLiteral
match string {
"" => Ok(0.0),
"Infinity" | "+Infinity" => Ok(f64::INFINITY),
"-Infinity" => Ok(f64::NEG_INFINITY),
_ if matches!(
string
.chars()
.take(4)
.collect::<String>()
.to_ascii_lowercase()
.as_str(),
"inf" | "+inf" | "-inf" | "nan" | "+nan" | "-nan"
) =>
{
// Prevent fast_float from parsing "inf", "+inf" as Infinity and "-inf" as -Infinity
Ok(f64::NAN)
}
_ => Ok(fast_float::parse(string).unwrap_or(f64::NAN)),
}
}
JsValue::String(ref string) => Ok(string.string_to_number()),
JsValue::Rational(number) => Ok(number),
JsValue::Integer(integer) => Ok(f64::from(integer)),
JsValue::Symbol(_) => {

6
boa/src/value/tests.rs

@ -249,7 +249,8 @@ fn string_length_is_not_enumerable() {
let object = JsValue::new("foo").to_object(&mut context).unwrap();
let length_desc = object
.__get_own_property__(&PropertyKey::from("length"))
.__get_own_property__(&PropertyKey::from("length"), &mut context)
.unwrap()
.unwrap();
assert!(!length_desc.expect_enumerable());
}
@ -261,7 +262,8 @@ fn string_length_is_in_utf16_codeunits() {
// 😀 is one Unicode code point, but 2 UTF-16 code units
let object = JsValue::new("😀").to_object(&mut context).unwrap();
let length_desc = object
.__get_own_property__(&PropertyKey::from("length"))
.__get_own_property__(&PropertyKey::from("length"), &mut context)
.unwrap()
.unwrap();
assert_eq!(
length_desc

3
boa/src/vm/mod.rs

@ -192,7 +192,8 @@ impl<'a> Vm<'a> {
)));
}
let key = lhs.to_property_key(self.context)?;
self.push(self.context.has_property(&rhs, &key));
let has_property = self.context.has_property(&rhs, &key)?;
self.push(has_property);
}
Opcode::InstanceOf => {
let y = self.pop();

Loading…
Cancel
Save