Browse Source

Object specialization (#419)

pull/499/head
HalidOdat 4 years ago committed by GitHub
parent
commit
df13272fc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 53
      boa/src/builtins/array/mod.rs
  2. 44
      boa/src/builtins/bigint/mod.rs
  3. 96
      boa/src/builtins/boolean/mod.rs
  4. 5
      boa/src/builtins/console/mod.rs
  5. 27
      boa/src/builtins/error/mod.rs
  6. 27
      boa/src/builtins/error/range.rs
  7. 27
      boa/src/builtins/error/type.rs
  8. 116
      boa/src/builtins/function/mod.rs
  9. 33
      boa/src/builtins/global_this/mod.rs
  10. 269
      boa/src/builtins/json/mod.rs
  11. 999
      boa/src/builtins/math/mod.rs
  12. 50
      boa/src/builtins/mod.rs
  13. 32
      boa/src/builtins/nan/mod.rs
  14. 138
      boa/src/builtins/number/mod.rs
  15. 12
      boa/src/builtins/number/tests.rs
  16. 215
      boa/src/builtins/object/internal_methods.rs
  17. 634
      boa/src/builtins/object/mod.rs
  18. 49
      boa/src/builtins/regexp/mod.rs
  19. 88
      boa/src/builtins/string/mod.rs
  20. 172
      boa/src/builtins/symbol/mod.rs
  21. 2
      boa/src/builtins/symbol/tests.rs
  22. 4
      boa/src/builtins/value/conversions.rs
  23. 46
      boa/src/builtins/value/display.rs
  24. 7
      boa/src/builtins/value/equality.rs
  25. 54
      boa/src/builtins/value/hash.rs
  26. 361
      boa/src/builtins/value/mod.rs
  27. 64
      boa/src/builtins/value/tests.rs
  28. 8
      boa/src/environment/lexical_environment.rs
  29. 42
      boa/src/exec/exception.rs
  30. 15
      boa/src/exec/expression/mod.rs
  31. 197
      boa/src/exec/mod.rs

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

@ -15,7 +15,7 @@ mod tests;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property, property::Property,
value::{same_value_zero, ResultValue, Value, ValueData}, value::{same_value_zero, ResultValue, Value, ValueData},
}, },
@ -25,7 +25,6 @@ use crate::{
use std::{ use std::{
borrow::Borrow, borrow::Borrow,
cmp::{max, min}, cmp::{max, min},
ops::Deref,
}; };
/// JavaScript `Array` built-in implementation. /// JavaScript `Array` built-in implementation.
@ -33,6 +32,12 @@ use std::{
pub(crate) struct Array; pub(crate) struct Array;
impl Array { impl Array {
/// The name of the object.
pub(crate) const NAME: &'static str = "Array";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// Creates a new `Array` instance. /// Creates a new `Array` instance.
pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue {
let array = Value::new_object(Some( let array = Value::new_object(Some(
@ -42,7 +47,7 @@ impl Array {
.get_global_object() .get_global_object()
.expect("Could not get global object"), .expect("Could not get global object"),
)); ));
array.set_kind(ObjectKind::Array); array.set_data(ObjectData::Array);
array.borrow().set_internal_slot( array.borrow().set_internal_slot(
INSTANCE_PROTOTYPE, INSTANCE_PROTOTYPE,
interpreter interpreter
@ -117,7 +122,7 @@ impl Array {
this.set_internal_slot(INSTANCE_PROTOTYPE, prototype); this.set_internal_slot(INSTANCE_PROTOTYPE, prototype);
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Array); this.set_data(ObjectData::Array);
// add our arguments in // add our arguments in
let mut length = args.len() as i32; let mut length = args.len() as i32;
@ -167,25 +172,9 @@ impl Array {
args: &[Value], args: &[Value],
_interpreter: &mut Interpreter, _interpreter: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let value_true = Value::boolean(true); match args.get(0).and_then(|x| x.as_object()) {
let value_false = Value::boolean(false); Some(object) => Ok(Value::from(object.is_array())),
None => Ok(Value::from(false)),
match args.get(0) {
Some(arg) => {
match arg.data() {
// 1.
ValueData::Object(ref obj) => {
// 2.
if (*obj).deref().borrow().kind == ObjectKind::Array {
return Ok(value_true);
}
Ok(value_false)
}
// 3.
_ => Ok(value_false),
}
}
None => Ok(value_false),
} }
} }
@ -1008,7 +997,7 @@ impl Array {
let prototype = Value::new_object(None); let prototype = Value::new_object(None);
let length = Property::default().value(Value::from(0)); let length = Property::default().value(Value::from(0));
prototype.set_property_slice("length", length); prototype.set_property("length", length);
make_builtin_fn(Self::concat, "concat", &prototype, 1); make_builtin_fn(Self::concat, "concat", &prototype, 1);
make_builtin_fn(Self::push, "push", &prototype, 1); make_builtin_fn(Self::push, "push", &prototype, 1);
@ -1031,7 +1020,14 @@ impl Array {
make_builtin_fn(Self::slice, "slice", &prototype, 2); make_builtin_fn(Self::slice, "slice", &prototype, 2);
make_builtin_fn(Self::some, "some", &prototype, 2); make_builtin_fn(Self::some, "some", &prototype, 2);
let array = make_constructor_fn("Array", 1, Self::make_array, global, prototype, true); let array = make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_array,
global,
prototype,
true,
);
// Static Methods // Static Methods
make_builtin_fn(Self::is_array, "isArray", &array, 1); make_builtin_fn(Self::is_array, "isArray", &array, 1);
@ -1041,8 +1037,9 @@ impl Array {
/// Initialise the `Array` object on the global object. /// Initialise the `Array` object on the global object.
#[inline] #[inline]
pub(crate) fn init(global: &Value) { pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("array", "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
global.set_field("Array", Self::create(global));
(Self::NAME, Self::create(global))
} }
} }

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

@ -15,6 +15,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
function::{make_builtin_fn, make_constructor_fn}, function::{make_builtin_fn, make_constructor_fn},
object::ObjectData,
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
}, },
exec::Interpreter, exec::Interpreter,
@ -43,6 +44,12 @@ mod tests;
pub struct BigInt(num_bigint::BigInt); pub struct BigInt(num_bigint::BigInt);
impl BigInt { impl BigInt {
/// The name of the object.
pub(crate) const NAME: &'static str = "BigInt";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// The abstract operation thisBigIntValue takes argument value. /// The abstract operation thisBigIntValue takes argument value.
/// ///
/// The phrase “this BigInt value” within the specification of a method refers to the /// The phrase “this BigInt value” within the specification of a method refers to the
@ -62,9 +69,8 @@ impl BigInt {
// 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then // 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
// a. Assert: Type(value.[[BigIntData]]) is BigInt. // a. Assert: Type(value.[[BigIntData]]) is BigInt.
// b. Return value.[[BigIntData]]. // b. Return value.[[BigIntData]].
ValueData::Object(_) => { ValueData::Object(ref object) => {
let bigint = value.get_internal_slot("BigIntData"); if let ObjectData::BigInt(ref bigint) = object.borrow().data {
if let ValueData::BigInt(bigint) = bigint.data() {
return Ok(bigint.clone()); return Ok(bigint.clone());
} }
} }
@ -72,8 +78,7 @@ impl BigInt {
} }
// 3. Throw a TypeError exception. // 3. Throw a TypeError exception.
ctx.throw_type_error("'this' is not a BigInt")?; Err(ctx.construct_type_error("'this' is not a BigInt"))
unreachable!();
} }
/// `BigInt()` /// `BigInt()`
@ -86,16 +91,12 @@ impl BigInt {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-bigint-objects /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
pub(crate) fn make_bigint( pub(crate) fn make_bigint(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
_this: &mut Value,
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
let data = match args.get(0) { let data = match args.get(0) {
Some(ref value) => Value::from(ctx.to_bigint(value)?), Some(ref value) => ctx.to_bigint(value)?,
None => Value::from(Self::from(0)), None => Self::from(0),
}; };
Ok(data) Ok(Value::from(data))
} }
/// `BigInt.prototype.toString( [radix] )` /// `BigInt.prototype.toString( [radix] )`
@ -213,12 +214,18 @@ impl BigInt {
/// Create a new `Number` object /// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value { pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("BigIntData", Value::from(Self::from(0)));
make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
let big_int = make_constructor_fn("BigInt", 1, Self::make_bigint, global, prototype, false); let big_int = make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_bigint,
global,
prototype,
false,
);
make_builtin_fn(Self::as_int_n, "asIntN", &big_int, 2); make_builtin_fn(Self::as_int_n, "asIntN", &big_int, 2);
make_builtin_fn(Self::as_uint_n, "asUintN", &big_int, 2); make_builtin_fn(Self::as_uint_n, "asUintN", &big_int, 2);
@ -228,9 +235,10 @@ impl BigInt {
/// Initialise the `BigInt` object on the global object. /// Initialise the `BigInt` object on the global object.
#[inline] #[inline]
pub(crate) fn init(global: &Value) { pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("bigint", "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
global.set_field("BigInt", Self::create(global));
(Self::NAME, Self::create(global))
} }
} }

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

@ -15,19 +15,45 @@ mod tests;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
object::{internal_methods_trait::ObjectInternalMethods, ObjectKind}, object::ObjectData,
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
}, },
exec::Interpreter, exec::Interpreter,
BoaProfiler, BoaProfiler,
}; };
use std::{borrow::Borrow, ops::Deref};
/// Boolean implementation. /// Boolean implementation.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct Boolean; pub(crate) struct Boolean;
impl Boolean { impl Boolean {
/// The name of the object.
pub(crate) const NAME: &'static str = "Boolean";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// An Utility function used to get the internal [[BooleanData]].
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue
fn this_boolean_value(value: &Value, ctx: &mut Interpreter) -> Result<bool, Value> {
match value.data() {
ValueData::Boolean(boolean) => return Ok(*boolean),
ValueData::Object(ref object) => {
let object = object.borrow();
if let Some(boolean) = object.as_boolean() {
return Ok(boolean);
}
}
_ => {}
}
Err(ctx.construct_type_error("'this' is not a boolean"))
}
/// `[[Construct]]` Create a new boolean object /// `[[Construct]]` Create a new boolean object
/// ///
/// `[[Call]]` Creates a new boolean primitive /// `[[Call]]` Creates a new boolean primitive
@ -36,19 +62,11 @@ impl Boolean {
args: &[Value], args: &[Value],
_: &mut Interpreter, _: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
this.set_kind(ObjectKind::Boolean);
// Get the argument, if any // Get the argument, if any
if let Some(ref value) = args.get(0) { let data = args.get(0).map(|x| x.to_boolean()).unwrap_or(false);
this.set_internal_slot("BooleanData", Self::to_boolean(value)); this.set_data(ObjectData::Boolean(data));
} else {
this.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false)));
}
match args.get(0) { Ok(Value::from(data))
Some(ref value) => Ok(Self::to_boolean(value)),
None => Ok(Self::to_boolean(&Value::from(false))),
}
} }
/// The `toString()` method returns a string representing the specified `Boolean` object. /// The `toString()` method returns a string representing the specified `Boolean` object.
@ -60,9 +78,9 @@ impl Boolean {
/// [spec]: https://tc39.es/ecma262/#sec-boolean-object /// [spec]: https://tc39.es/ecma262/#sec-boolean-object
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let b = Self::this_boolean_value(this); let boolean = Self::this_boolean_value(this, ctx)?;
Ok(Value::from(b.to_string())) Ok(Value::from(boolean.to_string()))
} }
/// The valueOf() method returns the primitive value of a `Boolean` object. /// The valueOf() method returns the primitive value of a `Boolean` object.
@ -73,37 +91,9 @@ impl Boolean {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf
pub(crate) fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { #[inline]
Ok(Self::this_boolean_value(this)) pub(crate) fn value_of(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
} Ok(Value::from(Self::this_boolean_value(this, ctx)?))
// === Utility Functions ===
/// [toBoolean](https://tc39.es/ecma262/#sec-toboolean)
/// Creates a new boolean value from the input
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_boolean(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Object(_) => Value::from(true),
ValueData::String(ref s) if !s.is_empty() => Value::from(true),
ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true),
ValueData::Integer(n) if n != 0 => Value::from(true),
ValueData::Boolean(v) => Value::from(v),
_ => Value::from(false),
}
}
/// An Utility function used to get the internal BooleanData.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue
pub(crate) fn this_boolean_value(value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Boolean(v) => Value::from(v),
ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"),
_ => Value::from(false),
}
} }
/// Create a new `Boolean` object. /// Create a new `Boolean` object.
@ -111,14 +101,13 @@ impl Boolean {
// Create Prototype // Create Prototype
// https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object // https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false)));
make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
make_constructor_fn( make_constructor_fn(
"Boolean", Self::NAME,
1, Self::LENGTH,
Self::construct_boolean, Self::construct_boolean,
global, global,
prototype, prototype,
@ -128,8 +117,9 @@ impl Boolean {
/// Initialise the `Boolean` object on the global object. /// Initialise the `Boolean` object on the global object.
#[inline] #[inline]
pub(crate) fn init(global: &Value) { pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("boolean", "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
global.set_field("Boolean", Self::create(global));
(Self::NAME, Self::create(global))
} }
} }

5
boa/src/builtins/console/mod.rs

@ -554,7 +554,8 @@ pub fn create(global: &Value) -> Value {
/// Initialise the `console` object on the global object. /// Initialise the `console` object on the global object.
#[inline] #[inline]
pub fn init(global: &Value) { pub fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("console", "init"); let _timer = BoaProfiler::global().start_event("console", "init");
global.set_field("console", create(global));
("console", create(global))
} }

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

@ -13,7 +13,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
function::{make_builtin_fn, make_constructor_fn}, function::{make_builtin_fn, make_constructor_fn},
object::ObjectKind, object::ObjectData,
value::{ResultValue, Value}, value::{ResultValue, Value},
}, },
exec::Interpreter, exec::Interpreter,
@ -35,6 +35,12 @@ pub(crate) use self::range::RangeError;
pub(crate) struct Error; pub(crate) struct Error;
impl Error { impl Error {
/// The name of the object.
pub(crate) const NAME: &'static str = "Error";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() { if !args.is_empty() {
@ -49,7 +55,7 @@ impl Error {
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error); this.set_data(ObjectData::Error);
Err(this.clone()) Err(this.clone())
} }
@ -77,12 +83,21 @@ impl Error {
make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_constructor_fn("Error", 1, Self::make_error, global, prototype, true) make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_error,
global,
prototype,
true,
)
} }
/// Initialise the global object with the `Error` object. /// Initialise the global object with the `Error` object.
pub(crate) fn init(global: &Value) { #[inline]
let _timer = BoaProfiler::global().start_event("error", "init"); pub(crate) fn init(global: &Value) -> (&str, Value) {
global.set_field("Error", Self::create(global)); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
(Self::NAME, Self::create(global))
} }
} }

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

@ -13,7 +13,7 @@ use crate::{
builtins::{ builtins::{
function::make_builtin_fn, function::make_builtin_fn,
function::make_constructor_fn, function::make_constructor_fn,
object::ObjectKind, object::ObjectData,
value::{ResultValue, Value}, value::{ResultValue, Value},
}, },
exec::Interpreter, exec::Interpreter,
@ -25,6 +25,12 @@ use crate::{
pub(crate) struct RangeError; pub(crate) struct RangeError;
impl RangeError { impl RangeError {
/// The name of the object.
pub(crate) const NAME: &'static str = "RangeError";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() { if !args.is_empty() {
@ -39,7 +45,7 @@ impl RangeError {
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error); this.set_data(ObjectData::Error);
Err(this.clone()) Err(this.clone())
} }
@ -67,12 +73,21 @@ impl RangeError {
make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_constructor_fn("RangeError", 1, Self::make_error, global, prototype, true) make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_error,
global,
prototype,
true,
)
} }
/// Initialise the global object with the `RangeError` object. /// Initialise the global object with the `RangeError` object.
pub(crate) fn init(global: &Value) { #[inline]
let _timer = BoaProfiler::global().start_event("rangeerror", "init"); pub(crate) fn init(global: &Value) -> (&str, Value) {
global.set_field("RangeError", Self::create(global)); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
(Self::NAME, Self::create(global))
} }
} }

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

@ -19,10 +19,11 @@ use crate::{
builtins::{ builtins::{
function::make_builtin_fn, function::make_builtin_fn,
function::make_constructor_fn, function::make_constructor_fn,
object::ObjectKind, object::ObjectData,
value::{ResultValue, Value}, value::{ResultValue, Value},
}, },
exec::Interpreter, exec::Interpreter,
BoaProfiler,
}; };
/// JavaScript `TypeError` implementation. /// JavaScript `TypeError` implementation.
@ -30,6 +31,12 @@ use crate::{
pub(crate) struct TypeError; pub(crate) struct TypeError;
impl TypeError { impl TypeError {
/// The name of the object.
pub(crate) const NAME: &'static str = "TypeError";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
if !args.is_empty() { if !args.is_empty() {
@ -45,7 +52,7 @@ impl TypeError {
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Error); this.set_data(ObjectData::Error);
Err(this.clone()) Err(this.clone())
} }
@ -73,11 +80,21 @@ impl TypeError {
make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_constructor_fn("TypeError", 1, Self::make_error, global, prototype, true) make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_error,
global,
prototype,
true,
)
} }
/// Initialise the global object with the `RangeError` object. /// Initialise the global object with the `RangeError` object.
pub(crate) fn init(global: &Value) { #[inline]
global.set_field("TypeError", Self::create(global)); pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
(Self::NAME, Self::create(global))
} }
} }

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

@ -14,7 +14,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
array::Array, array::Array,
object::{Object, ObjectInternalMethods, ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property, property::Property,
value::{ResultValue, Value}, value::{ResultValue, Value},
}, },
@ -40,7 +40,8 @@ pub enum ConstructorKind {
/// Defines how this references are interpreted within the formal parameters and code body of the function. /// Defines how this references are interpreted within the formal parameters and code body of the function.
/// ///
/// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical /// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical
#[derive(Copy, Finalize, Debug, Clone)]
#[derive(Debug, Copy, Finalize, Clone, PartialEq, PartialOrd, Hash)]
pub enum ThisMode { pub enum ThisMode {
Lexical, Lexical,
NonLexical, NonLexical,
@ -60,12 +61,24 @@ pub enum FunctionBody {
impl Debug for FunctionBody { impl Debug for FunctionBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::BuiltIn(_) => write!(f, "native code"), Self::BuiltIn(_) => write!(f, "[native]"),
Self::Ordinary(statements) => write!(f, "{:?}", statements), Self::Ordinary(statements) => write!(f, "{:?}", statements),
} }
} }
} }
impl PartialEq for FunctionBody {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::BuiltIn(a), Self::BuiltIn(b)) => std::ptr::eq(a, b),
(Self::Ordinary(a), Self::Ordinary(b)) => a == b,
(_, _) => false,
}
}
}
impl Eq for FunctionBody {}
/// `Trace` implementation for `FunctionBody`. /// `Trace` implementation for `FunctionBody`.
/// ///
/// This is indeed safe, but we need to mark this as an empty trace because neither /// This is indeed safe, but we need to mark this as an empty trace because neither
@ -164,20 +177,20 @@ impl Function {
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist> /// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
pub fn call( pub fn call(
&self, &self,
this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object)
this: &mut Value,
args_list: &[Value], args_list: &[Value],
interpreter: &mut Interpreter, interpreter: &mut Interpreter,
this_obj: &mut Value,
) -> ResultValue { ) -> ResultValue {
let _timer = BoaProfiler::global().start_event("function::call", "function"); let _timer = BoaProfiler::global().start_event("function::call", "function");
if self.callable { if self.callable {
match self.body { match self.body {
FunctionBody::BuiltIn(func) => func(this_obj, args_list, interpreter), FunctionBody::BuiltIn(func) => func(this, args_list, interpreter),
FunctionBody::Ordinary(ref body) => { FunctionBody::Ordinary(ref body) => {
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment( let local_env = new_function_environment(
this.clone(), function,
None, None,
Some(self.environment.as_ref().unwrap().clone()), Some(self.environment.as_ref().unwrap().clone()),
BindingStatus::Uninitialized, BindingStatus::Uninitialized,
@ -223,23 +236,23 @@ impl Function {
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget> /// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
pub fn construct( pub fn construct(
&self, &self,
this: &mut Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object)
this: &mut Value,
args_list: &[Value], args_list: &[Value],
interpreter: &mut Interpreter, interpreter: &mut Interpreter,
this_obj: &mut Value,
) -> ResultValue { ) -> ResultValue {
if self.constructable { if self.constructable {
match self.body { match self.body {
FunctionBody::BuiltIn(func) => { FunctionBody::BuiltIn(func) => {
func(this_obj, args_list, interpreter)?; func(this, args_list, interpreter)?;
Ok(this_obj.clone()) Ok(this.clone())
} }
FunctionBody::Ordinary(ref body) => { FunctionBody::Ordinary(ref body) => {
// Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall> // <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = new_function_environment( let local_env = new_function_environment(
this.clone(), function,
Some(this_obj.clone()), Some(this.clone()),
Some(self.environment.as_ref().unwrap().clone()), Some(self.environment.as_ref().unwrap().clone()),
BindingStatus::Initialized, BindingStatus::Initialized,
); );
@ -364,7 +377,7 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value {
.writable(true) .writable(true)
.configurable(true); .configurable(true);
obj.properties.insert(index.to_string(), prop); obj.properties_mut().insert(index.to_string(), prop);
index += 1; index += 1;
} }
@ -375,7 +388,10 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value {
/// ///
// This gets called when a new Function() is created. // This gets called when a new Function() is created.
pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub fn make_function(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
this.set_kind(ObjectKind::Function); this.set_data(ObjectData::Function(Function::builtin(
Vec::new(),
|_, _, _| Ok(Value::undefined()),
)));
Ok(this.clone()) Ok(this.clone())
} }
@ -391,46 +407,55 @@ pub fn create(global: &Value) -> Value {
/// So far this is only used by internal functions /// So far this is only used by internal functions
pub fn make_constructor_fn( pub fn make_constructor_fn(
name: &str, name: &str,
length: i32, length: usize,
body: NativeFunctionData, body: NativeFunctionData,
global: &Value, global: &Value,
proto: Value, prototype: Value,
constructable: bool, constructable: bool,
) -> Value { ) -> Value {
let _timer =
BoaProfiler::global().start_event(&format!("make_constructor_fn: {}", name), "init");
// Create the native function // Create the native function
let mut constructor_fn = Function::builtin(Vec::new(), body); let mut function = Function::builtin(Vec::new(), body);
function.constructable = constructable;
constructor_fn.constructable = constructable; let mut constructor = Object::function(function);
// Get reference to Function.prototype // Get reference to Function.prototype
let func_prototype = global.get_field("Function").get_field(PROTOTYPE);
// Create the function object and point its instance prototype to Function.prototype // Create the function object and point its instance prototype to Function.prototype
let mut constructor_obj = Object::function(); constructor.set_internal_slot(
constructor_obj.set_func(constructor_fn); INSTANCE_PROTOTYPE,
global.get_field("Function").get_field(PROTOTYPE),
constructor_obj.set_internal_slot(INSTANCE_PROTOTYPE, func_prototype); );
let constructor_val = Value::from(constructor_obj);
// Set proto.constructor -> constructor_obj
proto.set_field("constructor", constructor_val.clone());
constructor_val.set_field(PROTOTYPE, proto);
let length = Property::new() let length = Property::new()
.value(Value::from(length)) .value(Value::from(length))
.writable(false) .writable(false)
.configurable(false) .configurable(false)
.enumerable(false); .enumerable(false);
constructor_val.set_property_slice("length", length); constructor.insert_property("length", length);
let name = Property::new() let name = Property::new()
.value(Value::from(name)) .value(Value::from(name))
.writable(false) .writable(false)
.configurable(false) .configurable(false)
.enumerable(false); .enumerable(false);
constructor_val.set_property_slice("name", name); constructor.insert_property("name", name);
let constructor = Value::from(constructor);
prototype
.as_object_mut()
.unwrap()
.insert_field("constructor", constructor.clone());
constructor_val constructor
.as_object_mut()
.expect("constructor object")
.insert_field(PROTOTYPE, prototype);
constructor
} }
/// Creates a new member function of a `Object` or `prototype`. /// Creates a new member function of a `Object` or `prototype`.
@ -451,27 +476,26 @@ pub fn make_constructor_fn(
/// some other number of arguments. /// some other number of arguments.
/// ///
/// If no length is provided, the length will be set to 0. /// If no length is provided, the length will be set to 0.
pub fn make_builtin_fn<N>(function: NativeFunctionData, name: N, parent: &Value, length: i32) pub fn make_builtin_fn<N>(function: NativeFunctionData, name: N, parent: &Value, length: usize)
where where
N: Into<String>, N: Into<String>,
{ {
let name_copy: String = name.into(); let name = name.into();
let label = format!("{}{}", String::from("make_builtin_fn: "), &name_copy); let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
let _timer = BoaProfiler::global().start_event(&label, "init");
let func = Function::builtin(Vec::new(), function);
let mut new_func = Object::function();
new_func.set_func(func);
let new_func_obj = Value::from(new_func); let mut function = Object::function(Function::builtin(Vec::new(), function));
new_func_obj.set_field("length", length); function.insert_field("length", Value::from(length));
parent.set_field(Value::from(name_copy), new_func_obj); parent
.as_object_mut()
.unwrap()
.insert_field(name, Value::from(function));
} }
/// Initialise the `Function` object on the global object. /// Initialise the `Function` object on the global object.
#[inline] #[inline]
pub fn init(global: &Value) { pub fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("function", "init"); let _timer = BoaProfiler::global().start_event("function", "init");
global.set_field("Function", create(global));
("Function", create(global))
} }

33
boa/src/builtins/global_this/mod.rs

@ -1,11 +1,32 @@
//! This module implements the global `globalThis` property.
//!
//! The global globalThis property contains the global this value,
//! which is akin to the global object.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-globalthis
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
use crate::{builtins::value::Value, BoaProfiler};
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use crate::{builtins::value::Value, BoaProfiler}; /// The JavaScript `globalThis`.
pub(crate) struct GlobalThis;
impl GlobalThis {
/// The binding name of the property.
pub(crate) const NAME: &'static str = "globalThis";
/// Initialize the `globalThis` property on the global object.
#[inline]
pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
/// Initialize the `globalThis` property on the global object. (Self::NAME, global.clone())
#[inline] }
pub fn init(global: &Value) {
let _timer = BoaProfiler::global().start_event("globalThis", "init");
global.set_field("globalThis", global.clone());
} }

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

@ -15,7 +15,6 @@
use crate::builtins::{ use crate::builtins::{
function::make_builtin_fn, function::make_builtin_fn,
object::ObjectKind,
property::Property, property::Property,
value::{ResultValue, Value}, value::{ResultValue, Value},
}; };
@ -25,155 +24,165 @@ use serde_json::{self, Value as JSONValue};
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/// `JSON.parse( text[, reviver] )` /// JavaScript `JSON` global object.
/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string. pub(crate) struct Json;
///
/// An optional `reviver` function can be provided to perform a transformation on the resulting object before it is returned. impl Json {
/// /// The name of the object.
/// More information: pub(crate) const NAME: &'static str = "JSON";
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn] /// `JSON.parse( text[, reviver] )`
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-json.parse /// This `JSON` method parses a JSON string, constructing the JavaScript value or object described by the string.
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse ///
pub fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { /// An optional `reviver` function can be provided to perform a transformation on the resulting object before it is returned.
match serde_json::from_str::<JSONValue>( ///
&ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?, /// More information:
) { /// - [ECMAScript reference][spec]
Ok(json) => { /// - [MDN documentation][mdn]
let j = Value::from_json(json, ctx); ///
match args.get(1) { /// [spec]: https://tc39.es/ecma262/#sec-json.parse
Some(reviver) if reviver.is_function() => { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
let mut holder = Value::new_object(None); pub(crate) fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
holder.set_field(Value::from(""), j); match serde_json::from_str::<JSONValue>(
walk(reviver, ctx, &mut holder, Value::from("")) &ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?,
) {
Ok(json) => {
let j = Value::from_json(json, ctx);
match args.get(1) {
Some(reviver) if reviver.is_function() => {
let mut holder = Value::new_object(None);
holder.set_field(Value::from(""), j);
Self::walk(reviver, ctx, &mut holder, Value::from(""))
}
_ => Ok(j),
} }
_ => Ok(j),
} }
Err(err) => Err(Value::from(err.to_string())),
} }
Err(err) => Err(Value::from(err.to_string())),
} }
}
/// This is a translation of the [Polyfill implementation][polyfill] /// This is a translation of the [Polyfill implementation][polyfill]
/// ///
/// This function recursively walks the structure, passing each key-value pair to the reviver function /// This function recursively walks the structure, passing each key-value pair to the reviver function
/// for possible transformation. /// for possible transformation.
/// ///
/// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse /// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) -> ResultValue { fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) -> ResultValue {
let mut value = holder.get_field(key.clone()); let mut value = holder.get_field(key.clone());
let obj = value.as_object().as_deref().cloned(); let obj = value.as_object().as_deref().cloned();
if let Some(obj) = obj { if let Some(obj) = obj {
for key in obj.properties.keys() { for key in obj.properties().keys() {
let v = walk(reviver, ctx, &mut value, Value::from(key.as_str())); let v = Self::walk(reviver, ctx, &mut value, Value::from(key.as_str()));
match v { match v {
Ok(v) if !v.is_undefined() => { Ok(v) if !v.is_undefined() => {
value.set_field(Value::from(key.as_str()), v); value.set_field(Value::from(key.as_str()), v);
}
Ok(_) => {
value.remove_property(key.as_str());
}
Err(_v) => {}
} }
Ok(_) => {
value.remove_property(key.as_str());
}
Err(_v) => {}
} }
} }
ctx.call(reviver, holder, &[key, value])
} }
ctx.call(reviver, holder, &[key, value])
}
/// `JSON.stringify( value[, replacer[, space]] )` /// `JSON.stringify( value[, replacer[, space]] )`
/// ///
/// This `JSON` method converts a JavaScript object or value to a JSON string. /// This `JSON` method converts a JavaScript object or value to a JSON string.
/// ///
/// This medhod optionally replaces values if a `replacer` function is specified or /// This medhod optionally replaces values if a `replacer` function is specified or
/// optionally including only the specified properties if a replacer array is specified. /// optionally including only the specified properties if a replacer array is specified.
/// ///
/// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert /// An optional `space` argument can be supplied of type `String` or `Number` that's used to insert
/// white space into the output JSON string for readability purposes. /// white space into the output JSON string for readability purposes.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn] /// - [MDN documentation][mdn]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-json.stringify /// [spec]: https://tc39.es/ecma262/#sec-json.stringify
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
pub fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let object = match args.get(0) { let object = match args.get(0) {
Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()), Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()),
None => return Ok(Value::undefined()), None => return Ok(Value::undefined()),
Some(obj) => obj, Some(obj) => obj,
}; };
let replacer = match args.get(1) { let replacer = match args.get(1) {
Some(replacer) if replacer.is_object() => replacer, Some(replacer) if replacer.is_object() => replacer,
_ => return Ok(Value::from(object.to_json(ctx)?.to_string())), _ => return Ok(Value::from(object.to_json(ctx)?.to_string())),
}; };
let replacer_as_object = replacer let replacer_as_object = replacer
.as_object()
.expect("JSON.stringify replacer was an object");
if replacer_as_object.is_callable() {
object
.as_object() .as_object()
.map(|obj| { .expect("JSON.stringify replacer was an object");
let object_to_return = Value::new_object(None); if replacer_as_object.is_callable() {
for (key, val) in obj object
.properties .as_object()
.iter() .map(|obj| {
.filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value))) let object_to_return = Value::new_object(None);
for (key, val) in obj
.properties()
.iter()
.filter_map(|(k, v)| v.value.as_ref().map(|value| (k, value)))
{
let mut this_arg = object.clone();
object_to_return.set_property(
key.to_owned(),
Property::default().value(ctx.call(
replacer,
&mut this_arg,
&[Value::string(key), val.clone()],
)?),
);
}
Ok(Value::from(object_to_return.to_json(ctx)?.to_string()))
})
.ok_or_else(Value::undefined)?
} else if replacer_as_object.is_array() {
let mut obj_to_return =
serde_json::Map::with_capacity(replacer_as_object.properties().len() - 1);
let fields = replacer_as_object.properties().keys().filter_map(|key| {
if key == "length" {
None
} else {
Some(replacer.get_field(key.to_owned()))
}
});
for field in fields {
if let Some(value) = object
.get_property(&ctx.to_string(&field)?)
.and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx)))
.transpose()?
{ {
let mut this_arg = object.clone(); obj_to_return.insert(field.to_string(), value);
object_to_return.set_property(
key.to_owned(),
Property::default().value(ctx.call(
replacer,
&mut this_arg,
&[Value::string(key), val.clone()],
)?),
);
} }
Ok(Value::from(object_to_return.to_json(ctx)?.to_string()))
})
.ok_or_else(Value::undefined)?
} else if replacer_as_object.kind == ObjectKind::Array {
let mut obj_to_return =
serde_json::Map::with_capacity(replacer_as_object.properties.len() - 1);
let fields = replacer_as_object.properties.keys().filter_map(|key| {
if key == "length" {
None
} else {
Some(replacer.get_field(key.to_owned()))
}
});
for field in fields {
if let Some(value) = object
.get_property(&ctx.to_string(&field)?)
.and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx)))
.transpose()?
{
obj_to_return.insert(field.to_string(), value);
} }
Ok(Value::from(JSONValue::Object(obj_to_return).to_string()))
} else {
Ok(Value::from(object.to_json(ctx)?.to_string()))
} }
Ok(Value::from(JSONValue::Object(obj_to_return).to_string()))
} else {
Ok(Value::from(object.to_json(ctx)?.to_string()))
} }
}
/// Create a new `JSON` object. /// Create a new `JSON` object.
pub fn create(global: &Value) -> Value { pub(crate) fn create(global: &Value) -> Value {
let json = Value::new_object(Some(global)); let json = Value::new_object(Some(global));
make_builtin_fn(parse, "parse", &json, 2); make_builtin_fn(Self::parse, "parse", &json, 2);
make_builtin_fn(stringify, "stringify", &json, 3); make_builtin_fn(Self::stringify, "stringify", &json, 3);
json json
} }
/// Initialise the `JSON` object on the global object.
#[inline]
pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
/// Initialise the `JSON` object on the global object. (Self::NAME, Self::create(global))
#[inline] }
pub fn init(global: &Value) {
let _timer = BoaProfiler::global().start_event("json", "init");
global.set_field("JSON", create(global));
} }

999
boa/src/builtins/math/mod.rs

File diff suppressed because it is too large Load Diff

50
boa/src/builtins/mod.rs

@ -23,31 +23,45 @@ pub(crate) use self::{
bigint::BigInt, bigint::BigInt,
boolean::Boolean, boolean::Boolean,
error::{Error, RangeError, TypeError}, error::{Error, RangeError, TypeError},
function::Function, global_this::GlobalThis,
json::Json,
math::Math,
nan::NaN,
number::Number, number::Number,
regexp::RegExp, regexp::RegExp,
string::String, string::String,
symbol::Symbol,
value::{ResultValue, Value}, value::{ResultValue, Value},
}; };
/// Initializes builtin objects and functions /// Initializes builtin objects and functions
#[inline] #[inline]
pub fn init(global: &Value) { pub fn init(global: &Value) {
Array::init(global); let globals = vec![
BigInt::init(global); // The `Function` global must be initialized before other types.
Boolean::init(global); function::init(global),
global_this::init(global); Array::init(global),
json::init(global); BigInt::init(global),
math::init(global); Boolean::init(global),
nan::init(global); Json::init(global),
Number::init(global); Math::init(global),
object::init(global); Number::init(global),
function::init(global); object::init(global),
RegExp::init(global); RegExp::init(global),
String::init(global); String::init(global),
symbol::init(global); Symbol::init(global),
console::init(global); console::init(global),
Error::init(global); // Global error types.
RangeError::init(global); Error::init(global),
TypeError::init(global); RangeError::init(global),
TypeError::init(global),
// Global properties.
NaN::init(global),
GlobalThis::init(global),
];
let mut global_object = global.as_object_mut().expect("global object");
for (name, value) in globals {
global_object.insert_field(name, value);
}
} }

32
boa/src/builtins/nan/mod.rs

@ -1,11 +1,33 @@
//! This module implements the global `NaN` property.
//!
//! The global `NaN` is a property of the global object. In other words,
//! it is a variable in global scope.
//!
//! More information:
//! - [MDN documentation][mdn]
//! - [ECMAScript reference][spec]
//!
//! [spec]: https://tc39.es/ecma262/#sec-value-properties-of-the-global-object-nan
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use crate::{builtins::value::Value, BoaProfiler}; use crate::{builtins::value::Value, BoaProfiler};
/// Initialize the `NaN` property on the global object. /// JavaScript global `NaN` property.
#[inline] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub fn init(global: &Value) { pub(crate) struct NaN;
let _timer = BoaProfiler::global().start_event("NaN", "init");
global.set_field("NaN", Value::from(f64::NAN)); impl NaN {
/// The binding name of the property.
pub(crate) const NAME: &'static str = "NaN";
/// Initialize the `NaN` property on the global object.
#[inline]
pub(crate) fn init(_: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
(Self::NAME, Value::from(f64::NAN))
}
} }

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

@ -18,18 +18,14 @@ mod tests;
use super::{ use super::{
function::{make_builtin_fn, make_constructor_fn}, function::{make_builtin_fn, make_constructor_fn},
object::ObjectKind, object::ObjectData,
}; };
use crate::{ use crate::{
builtins::{ builtins::value::{ResultValue, Value, ValueData},
object::internal_methods_trait::ObjectInternalMethods,
value::{ResultValue, Value, ValueData},
},
exec::Interpreter, exec::Interpreter,
BoaProfiler, BoaProfiler,
}; };
use num_traits::float::FloatCore; use num_traits::float::FloatCore;
use std::{borrow::Borrow, ops::Deref};
const BUF_SIZE: usize = 2200; const BUF_SIZE: usize = 2200;
@ -44,28 +40,35 @@ const PARSE_INT_MAX_ARG_COUNT: usize = 2;
const PARSE_FLOAT_MAX_ARG_COUNT: usize = 1; const PARSE_FLOAT_MAX_ARG_COUNT: usize = 1;
impl Number { impl Number {
/// Helper function that converts a Value to a Number. /// The name of the object.
#[allow(clippy::wrong_self_convention)] pub(crate) const NAME: &'static str = "Number";
fn to_number(value: &Value) -> Value {
match *value.deref().borrow() { /// The amount of arguments this function object takes.
ValueData::Boolean(b) => { pub(crate) const LENGTH: usize = 1;
if b {
Value::from(1) /// This function returns a `Result` of the number `Value`.
} else { ///
Value::from(0) /// If the `Value` is a `Number` primitive of `Number` object the number is returned.
/// Otherwise an `TypeError` is thrown.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-thisnumbervalue
fn this_number_value(value: &Value, ctx: &mut Interpreter) -> Result<f64, Value> {
match *value.data() {
ValueData::Integer(integer) => return Ok(f64::from(integer)),
ValueData::Rational(rational) => return Ok(rational),
ValueData::Object(ref object) => {
if let Some(number) = object.borrow().as_number() {
return Ok(number);
} }
} }
ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN), _ => {}
ValueData::Integer(i) => Value::from(f64::from(i)),
ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"),
ValueData::Null => Value::from(0),
ValueData::Rational(n) => Value::from(n),
ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()),
ValueData::String(ref s) => match s.parse::<f64>() {
Ok(n) => Value::from(n),
Err(_) => Value::from(f64::NAN),
},
} }
Err(ctx.construct_type_error("'this' is not a number"))
} }
/// Helper function that formats a float as a ES6-style exponential number string. /// Helper function that formats a float as a ES6-style exponential number string.
@ -83,16 +86,15 @@ impl Number {
pub(crate) fn make_number( pub(crate) fn make_number(
this: &mut Value, this: &mut Value,
args: &[Value], args: &[Value],
_ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let data = match args.get(0) { let data = match args.get(0) {
Some(ref value) => Self::to_number(value), Some(ref value) => ctx.to_numeric_number(value)?,
None => Self::to_number(&Value::from(0)), None => 0.0,
}; };
this.set_kind(ObjectKind::Number); this.set_data(ObjectData::Number(data));
this.set_internal_slot("NumberData", data.clone());
Ok(data) Ok(Value::from(data))
} }
/// `Number.prototype.toExponential( [fractionDigits] )` /// `Number.prototype.toExponential( [fractionDigits] )`
@ -109,9 +111,9 @@ impl Number {
pub(crate) fn to_exponential( pub(crate) fn to_exponential(
this: &mut Value, this: &mut Value,
_args: &[Value], _args: &[Value],
_ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let this_num = Self::to_number(this).to_number(); let this_num = Self::this_number_value(this, ctx)?;
let this_str_num = Self::num_to_exponential(this_num); let this_str_num = Self::num_to_exponential(this_num);
Ok(Value::from(this_str_num)) Ok(Value::from(this_str_num))
} }
@ -127,12 +129,8 @@ impl Number {
/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_fixed( pub(crate) fn to_fixed(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
this: &mut Value, let this_num = Self::this_number_value(this, ctx)?;
args: &[Value],
_ctx: &mut Interpreter,
) -> ResultValue {
let this_num = Self::to_number(this).to_number();
let precision = match args.get(0) { let precision = match args.get(0) {
Some(n) => match n.to_integer() { Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize, x if x > 0 => n.to_integer() as usize,
@ -161,9 +159,9 @@ impl Number {
pub(crate) fn to_locale_string( pub(crate) fn to_locale_string(
this: &mut Value, this: &mut Value,
_args: &[Value], _args: &[Value],
_ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let this_num = Self::to_number(this).to_number(); let this_num = Self::this_number_value(this, ctx)?;
let this_str_num = format!("{}", this_num); let this_str_num = format!("{}", this_num);
Ok(Value::from(this_str_num)) Ok(Value::from(this_str_num))
} }
@ -182,10 +180,10 @@ impl Number {
pub(crate) fn to_precision( pub(crate) fn to_precision(
this: &mut Value, this: &mut Value,
args: &[Value], args: &[Value],
_ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let this_num = Self::to_number(this); let this_num = Self::this_number_value(this, ctx)?;
let _num_str_len = format!("{}", this_num.to_number()).len(); let _num_str_len = format!("{}", this_num).len();
let _precision = match args.get(0) { let _precision = match args.get(0) {
Some(n) => match n.to_integer() { Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize, x if x > 0 => n.to_integer() as usize,
@ -353,7 +351,8 @@ impl Number {
ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
// 1. Let x be ? thisNumberValue(this value). // 1. Let x be ? thisNumberValue(this value).
let x = Self::to_number(this).to_number(); let x = Self::this_number_value(this, ctx)?;
// 2. If radix is undefined, let radixNumber be 10. // 2. If radix is undefined, let radixNumber be 10.
// 3. Else, let radixNumber be ? ToInteger(radix). // 3. Else, let radixNumber be ? ToInteger(radix).
let radix = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; let radix = args.get(0).map_or(10, |arg| arg.to_integer()) as u8;
@ -406,9 +405,9 @@ impl Number {
pub(crate) fn value_of( pub(crate) fn value_of(
this: &mut Value, this: &mut Value,
_args: &[Value], _args: &[Value],
_ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
Ok(Self::to_number(this)) Ok(Value::from(Self::this_number_value(this, ctx)?))
} }
/// Builtin javascript 'parseInt(str, radix)' function. /// Builtin javascript 'parseInt(str, radix)' function.
@ -530,7 +529,6 @@ impl Number {
/// Create a new `Number` object /// Create a new `Number` object
pub(crate) fn create(global: &Value) -> Value { pub(crate) fn create(global: &Value) -> Value {
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
prototype.set_internal_slot("NumberData", Value::from(0));
make_builtin_fn(Self::to_exponential, "toExponential", &prototype, 1); make_builtin_fn(Self::to_exponential, "toExponential", &prototype, 1);
make_builtin_fn(Self::to_fixed, "toFixed", &prototype, 1); make_builtin_fn(Self::to_fixed, "toFixed", &prototype, 1);
@ -539,40 +537,46 @@ impl Number {
make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::to_string, "toString", &prototype, 1);
make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0);
make_builtin_fn( make_builtin_fn(Self::parse_int, "parseInt", global, PARSE_INT_MAX_ARG_COUNT);
Self::parse_int,
"parseInt",
global,
PARSE_INT_MAX_ARG_COUNT as i32,
);
make_builtin_fn( make_builtin_fn(
Self::parse_float, Self::parse_float,
"parseFloat", "parseFloat",
global, global,
PARSE_FLOAT_MAX_ARG_COUNT as i32, PARSE_FLOAT_MAX_ARG_COUNT,
); );
let number = make_constructor_fn("Number", 1, Self::make_number, global, prototype, true); let number = make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_number,
global,
prototype,
true,
);
// Constants from: // Constants from:
// https://tc39.es/ecma262/#sec-properties-of-the-number-constructor // https://tc39.es/ecma262/#sec-properties-of-the-number-constructor
number.set_field("EPSILON", Value::from(f64::EPSILON)); {
number.set_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); let mut properties = number.as_object_mut().expect("'Number' object");
number.set_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); properties.insert_field("EPSILON", Value::from(f64::EPSILON));
number.set_field("MAX_VALUE", Value::from(f64::MAX)); properties.insert_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64));
number.set_field("MIN_VALUE", Value::from(f64::MIN)); properties.insert_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64));
number.set_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY)); properties.insert_field("MAX_VALUE", Value::from(f64::MAX));
number.set_field("POSITIVE_INFINITY", Value::from(f64::INFINITY)); properties.insert_field("MIN_VALUE", Value::from(f64::MIN));
number.set_field("NaN", Value::from(f64::NAN)); properties.insert_field("NEGATIVE_INFINITY", Value::from(f64::NEG_INFINITY));
properties.insert_field("POSITIVE_INFINITY", Value::from(f64::INFINITY));
properties.insert_field("NaN", Value::from(f64::NAN));
}
number number
} }
/// Initialise the `Number` object on the global object. /// Initialise the `Number` object on the global object.
#[inline] #[inline]
pub(crate) fn init(global: &Value) { pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("number", "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
global.set_field("Number", Self::create(global));
(Self::NAME, Self::create(global))
} }
/// The abstract operation Number::equal takes arguments /// The abstract operation Number::equal takes arguments

12
boa/src/builtins/number/tests.rs

@ -82,12 +82,12 @@ fn to_exponential() {
let nan_exp = forward(&mut engine, "nan_exp"); let nan_exp = forward(&mut engine, "nan_exp");
let noop_exp = forward(&mut engine, "noop_exp"); let noop_exp = forward(&mut engine, "noop_exp");
assert_eq!(default_exp, String::from("0e+0")); assert_eq!(default_exp, "0e+0");
assert_eq!(int_exp, String::from("5e+0")); assert_eq!(int_exp, "5e+0");
assert_eq!(float_exp, String::from("1.234e+0")); assert_eq!(float_exp, "1.234e+0");
assert_eq!(big_exp, String::from("1.234e+3")); assert_eq!(big_exp, "1.234e+3");
assert_eq!(nan_exp, String::from("NaN")); assert_eq!(nan_exp, "NaN");
assert_eq!(noop_exp, String::from("1.23e+2")); assert_eq!(noop_exp, "1.23e+2");
} }
#[test] #[test]

215
boa/src/builtins/object/internal_methods_trait.rs → boa/src/builtins/object/internal_methods.rs

@ -1,38 +1,27 @@
//! This module defines the `ObjectInternalMethods` trait. //! This module defines the object internal methods.
//! //!
//! More information: //! More information:
//! - [ECMAScript reference][spec] //! - [ECMAScript reference][spec]
//! //!
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
use crate::{ use crate::builtins::{
builtins::{ object::{Object, INSTANCE_PROTOTYPE, PROTOTYPE},
object::{Object, INSTANCE_PROTOTYPE}, property::Property,
property::Property, value::{same_value, Value, ValueData},
value::{same_value, Value, ValueData},
},
BoaProfiler,
}; };
use crate::BoaProfiler;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::ops::Deref; use std::ops::Deref;
/// Here lies the internal methods for ordinary objects. impl Object {
/// /// Check if object has property.
/// Most objects make use of these methods, including exotic objects like functions.
/// So thats why this is a trait
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
pub trait ObjectInternalMethods {
/// Check if has property.
/// ///
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
fn has_property(&self, val: &Value) -> bool { pub fn has_property(&self, val: &Value) -> bool {
debug_assert!(Property::is_property_key(val)); debug_assert!(Property::is_property_key(val));
let prop = self.get_own_property(val); let prop = self.get_own_property(val);
if prop.value.is_none() { if prop.value.is_none() {
@ -57,7 +46,8 @@ pub trait ObjectInternalMethods {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
fn is_extensible(&self) -> bool { #[inline]
pub fn is_extensible(&self) -> bool {
let val = self.get_internal_slot("extensible"); let val = self.get_internal_slot("extensible");
match *val.deref().borrow() { match *val.deref().borrow() {
ValueData::Boolean(b) => b, ValueData::Boolean(b) => b,
@ -71,13 +61,14 @@ pub trait ObjectInternalMethods {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
fn prevent_extensions(&mut self) -> bool { #[inline]
pub fn prevent_extensions(&mut self) -> bool {
self.set_internal_slot("extensible", Value::from(false)); self.set_internal_slot("extensible", Value::from(false));
true true
} }
/// Delete property. /// Delete property.
fn delete(&mut self, prop_key: &Value) -> bool { pub fn delete(&mut self, prop_key: &Value) -> bool {
debug_assert!(Property::is_property_key(prop_key)); debug_assert!(Property::is_property_key(prop_key));
let desc = self.get_own_property(prop_key); let desc = self.get_own_property(prop_key);
if desc if desc
@ -97,7 +88,7 @@ pub trait ObjectInternalMethods {
} }
// [[Get]] // [[Get]]
fn get(&self, val: &Value) -> Value { pub fn get(&self, val: &Value) -> Value {
debug_assert!(Property::is_property_key(val)); debug_assert!(Property::is_property_key(val));
let desc = self.get_own_property(val); let desc = self.get_own_property(val);
if desc.value.clone().is_none() if desc.value.clone().is_none()
@ -127,13 +118,13 @@ pub trait ObjectInternalMethods {
return Value::undefined(); return Value::undefined();
} }
// TODO!!!!! Call getter from here // TODO: Call getter from here!
Value::undefined() Value::undefined()
} }
/// [[Set]] /// [[Set]]
/// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver> /// <https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver>
fn set(&mut self, field: Value, val: Value) -> bool { pub fn set(&mut self, field: Value, val: Value) -> bool {
let _timer = BoaProfiler::global().start_event("Object::set", "object"); let _timer = BoaProfiler::global().start_event("Object::set", "object");
// [1] // [1]
debug_assert!(Property::is_property_key(&field)); debug_assert!(Property::is_property_key(&field));
@ -171,8 +162,15 @@ pub trait ObjectInternalMethods {
} }
} }
fn define_own_property(&mut self, property_key: String, desc: Property) -> bool { /// Define an own property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
pub fn define_own_property(&mut self, property_key: String, desc: Property) -> bool {
let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object"); let _timer = BoaProfiler::global().start_event("Object::define_own_property", "object");
let mut current = self.get_own_property(&Value::from(property_key.to_string())); let mut current = self.get_own_property(&Value::from(property_key.to_string()));
let extensible = self.is_extensible(); let extensible = self.is_extensible();
@ -282,25 +280,164 @@ pub trait ObjectInternalMethods {
true true
} }
/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p /// The specification returns a Property Descriptor or Undefined.
/// The specification returns a Property Descriptor or Undefined. These are 2 separate types and we can't do that here. ///
fn get_own_property(&self, prop: &Value) -> Property; /// 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
pub fn get_own_property(&self, prop: &Value) -> Property {
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object");
debug_assert!(Property::is_property_key(prop));
// Prop could either be a String or Symbol
match *(*prop) {
ValueData::String(ref st) => {
self.properties()
.get(st)
.map_or_else(Property::default, |v| {
let mut d = Property::default();
if v.is_data_descriptor() {
d.value = v.value.clone();
d.writable = v.writable;
} else {
debug_assert!(v.is_accessor_descriptor());
d.get = v.get.clone();
d.set = v.set.clone();
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
})
}
ValueData::Symbol(ref symbol) => self
.symbol_properties()
.get(&symbol.hash())
.map_or_else(Property::default, |v| {
let mut d = Property::default();
if v.is_data_descriptor() {
d.value = v.value.clone();
d.writable = v.writable;
} else {
debug_assert!(v.is_accessor_descriptor());
d.get = v.get.clone();
d.set = v.set.clone();
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
}),
_ => Property::default(),
}
}
/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v /// `Object.setPropertyOf(obj, prototype)`
fn set_prototype_of(&mut self, val: Value) -> bool; ///
/// 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
pub fn set_prototype_of(&mut self, val: Value) -> bool {
debug_assert!(val.is_object() || val.is_null());
let current = self.get_internal_slot(PROTOTYPE);
if same_value(&current, &val, false) {
return true;
}
let extensible = self.get_internal_slot("extensible");
if extensible.is_null() {
return false;
}
let mut p = val.clone();
let mut done = false;
while !done {
if p.is_null() {
done = true
} else if same_value(&Value::from(self.clone()), &p, false) {
return false;
} else {
p = p.get_internal_slot(PROTOTYPE);
}
}
self.set_internal_slot(PROTOTYPE, val);
true
}
/// Returns either the prototype or null /// Returns either the prototype or null
/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof ///
fn get_prototype_of(&self) -> Value { /// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
#[inline]
pub fn get_prototype_of(&self) -> Value {
self.get_internal_slot(INSTANCE_PROTOTYPE) self.get_internal_slot(INSTANCE_PROTOTYPE)
} }
/// Utility function to get an immutable internal slot or Null /// Helper function to get an immutable internal slot or `Null`.
fn get_internal_slot(&self, name: &str) -> Value; #[inline]
pub fn get_internal_slot(&self, name: &str) -> Value {
let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object");
self.internal_slots()
.get(name)
.cloned()
.unwrap_or_else(Value::null)
}
/// Helper function to set an internal slot.
#[inline]
pub fn set_internal_slot(&mut self, name: &str, val: Value) {
self.internal_slots.insert(name.to_string(), val);
}
fn set_internal_slot(&mut self, name: &str, val: Value); /// Helper function for property insertion.
#[inline]
pub(crate) fn insert_property<N>(&mut self, name: N, p: Property)
where
N: Into<String>,
{
self.properties.insert(name.into(), p);
}
fn insert_property(&mut self, name: String, p: Property); /// Helper function for property removal.
#[inline]
pub(crate) fn remove_property(&mut self, name: &str) {
self.properties.remove(name);
}
fn remove_property(&mut self, name: &str); /// 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(crate) fn insert_field<N>(&mut self, name: N, value: Value) -> Option<Property>
where
N: Into<String>,
{
self.properties.insert(
name.into(),
Property::default()
.value(value)
.writable(true)
.configurable(true)
.enumerable(true),
)
}
/// This function returns an Optional reference value to the objects field.
///
/// if it exist `Some` is returned with a reference to that fields value.
/// Otherwise `None` is retuned.
#[inline]
pub fn get_field(&self, name: &str) -> Option<&Value> {
self.properties.get(name).and_then(|x| x.value.as_ref())
}
} }

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

@ -17,24 +17,23 @@ use crate::{
builtins::{ builtins::{
function::Function, function::Function,
property::Property, property::Property,
value::{same_value, ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
BigInt, Symbol,
}, },
exec::Interpreter, exec::Interpreter,
BoaProfiler, BoaProfiler,
}; };
use gc::{unsafe_empty_trace, Finalize, Trace}; use gc::{Finalize, Trace};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::{ use std::{
borrow::Borrow, fmt::{Debug, Display, Error, Formatter},
fmt::{self, Debug, Display, Error, Formatter},
ops::Deref, ops::Deref,
}; };
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
pub use internal_methods_trait::ObjectInternalMethods;
pub use internal_state::{InternalState, InternalStateCell}; pub use internal_state::{InternalState, InternalStateCell};
pub mod internal_methods_trait; pub mod internal_methods;
mod internal_state; mod internal_state;
#[cfg(test)] #[cfg(test)]
@ -47,322 +46,87 @@ pub static PROTOTYPE: &str = "prototype";
pub static INSTANCE_PROTOTYPE: &str = "__proto__"; pub static INSTANCE_PROTOTYPE: &str = "__proto__";
/// The internal representation of an JavaScript object. /// The internal representation of an JavaScript object.
#[derive(Trace, Finalize, Clone)] #[derive(Debug, Trace, Finalize, Clone)]
pub struct Object { pub struct Object {
/// The type of the object. /// The type of the object.
pub kind: ObjectKind, pub data: ObjectData,
/// Internal Slots /// Internal Slots
pub internal_slots: FxHashMap<String, Value>, internal_slots: FxHashMap<String, Value>,
/// Properties /// Properties
pub properties: FxHashMap<String, Property>, properties: FxHashMap<String, Property>,
/// Symbol Properties /// Symbol Properties
pub sym_properties: FxHashMap<i32, Property>, symbol_properties: FxHashMap<u32, Property>,
/// Some rust object that stores internal state /// Some rust object that stores internal state
pub state: Option<InternalStateCell>, state: Option<InternalStateCell>,
/// Function
pub func: Option<Function>,
} }
impl Debug for Object { /// Defines the different types of objects.
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { #[derive(Debug, Trace, Finalize, Clone)]
writeln!(f, "{{")?; pub enum ObjectData {
writeln!(f, "\tkind: {}", self.kind)?; Array,
writeln!(f, "\tstate: {:?}", self.state)?; BigInt(BigInt),
writeln!(f, "\tfunc: {:?}", self.func)?; Boolean(bool),
writeln!(f, "\tproperties: {{")?; Function(Function),
for (key, _) in self.properties.iter() { String(String),
writeln!(f, "\t\t{}", key)?; Number(f64),
} Symbol(Symbol),
writeln!(f, "\t }}")?; Error,
write!(f, "}}") Ordinary,
}
} }
impl ObjectInternalMethods for Object { impl Display for ObjectData {
/// `Object.setPropertyOf(obj, prototype)` fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
/// write!(
/// This method sets the prototype (i.e., the internal `[[Prototype]]` property) f,
/// of a specified object to another object or `null`. "{}",
/// match self {
/// More information: Self::Function(_) => "Function",
/// - [ECMAScript reference][spec] Self::Array => "Array",
/// - [MDN documentation][mdn] Self::String(_) => "String",
/// Self::Symbol(_) => "Symbol",
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v Self::Error => "Error",
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf Self::Ordinary => "Ordinary",
fn set_prototype_of(&mut self, val: Value) -> bool { Self::Boolean(_) => "Boolean",
debug_assert!(val.is_object() || val.is_null()); Self::Number(_) => "Number",
let current = self.get_internal_slot(PROTOTYPE); Self::BigInt(_) => "BigInt",
if same_value(&current, &val, false) {
return true;
}
let extensible = self.get_internal_slot("extensible");
if extensible.is_null() {
return false;
}
let mut p = val.clone();
let mut done = false;
while !done {
if p.is_null() {
done = true
} else if same_value(&Value::from(self.clone()), &p, false) {
return false;
} else {
p = p.get_internal_slot(PROTOTYPE);
}
}
self.set_internal_slot(PROTOTYPE, val);
true
}
/// Helper function for property insertion.
fn insert_property(&mut self, name: String, p: Property) {
self.properties.insert(name, p);
}
/// Helper function for property removal.
fn remove_property(&mut self, name: &str) {
self.properties.remove(name);
}
/// Helper function to set an internal slot
fn set_internal_slot(&mut self, name: &str, val: Value) {
self.internal_slots.insert(name.to_string(), val);
}
/// Helper function to get an immutable internal slot or Null
fn get_internal_slot(&self, name: &str) -> Value {
let _timer = BoaProfiler::global().start_event("Object::get_internal_slot", "object");
match self.internal_slots.get(name) {
Some(v) => v.clone(),
None => Value::null(),
}
}
/// 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
fn get_own_property(&self, prop: &Value) -> Property {
let _timer = BoaProfiler::global().start_event("Object::get_own_property", "object");
debug_assert!(Property::is_property_key(prop));
// Prop could either be a String or Symbol
match *(*prop) {
ValueData::String(ref st) => {
match self.properties.get(st) {
// If O does not have an own property with key P, return undefined.
// In this case we return a new empty Property
None => Property::default(),
Some(ref v) => {
let mut d = Property::default();
if v.is_data_descriptor() {
d.value = v.value.clone();
d.writable = v.writable;
} else {
debug_assert!(v.is_accessor_descriptor());
d.get = v.get.clone();
d.set = v.set.clone();
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
}
}
}
ValueData::Symbol(ref sym) => {
let sym_id = (**sym)
.borrow()
.get_internal_slot("SymbolData")
.to_string()
.parse::<i32>()
.expect("Could not get Symbol ID");
match self.sym_properties.get(&sym_id) {
// If O does not have an own property with key P, return undefined.
// In this case we return a new empty Property
None => Property::default(),
Some(ref v) => {
let mut d = Property::default();
if v.is_data_descriptor() {
d.value = v.value.clone();
d.writable = v.writable;
} else {
debug_assert!(v.is_accessor_descriptor());
d.get = v.get.clone();
d.set = v.set.clone();
}
d.enumerable = v.enumerable;
d.configurable = v.configurable;
d
}
}
}
_ => Property::default(),
}
}
/// Define an own property.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
#[allow(clippy::option_unwrap_used)]
fn define_own_property(&mut self, property_key: String, desc: Property) -> bool {
let mut current = self.get_own_property(&Value::from(property_key.to_string()));
let extensible = self.is_extensible();
// https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor
// There currently isn't a property, lets create a new one
if current.value.is_none() || current.value.as_ref().expect("failed").is_undefined() {
if !extensible {
return false;
}
if desc.value.is_some() && desc.value.clone().unwrap().is_symbol() {
let sym_id = desc
.value
.clone()
.unwrap()
.to_string()
.parse::<i32>()
.expect("parsing failed");
self.sym_properties.insert(sym_id, desc);
} else {
self.properties.insert(property_key, desc);
}
return true;
}
// If every field is absent we don't need to set anything
if desc.is_none() {
return true;
}
// 4
if !current.configurable.unwrap_or(false) {
if desc.configurable.is_some() && desc.configurable.unwrap() {
return false;
}
if desc.enumerable.is_some()
&& (desc.enumerable.as_ref().unwrap() != current.enumerable.as_ref().unwrap())
{
return false;
}
}
// 5
if desc.is_generic_descriptor() {
// 6
} else if current.is_data_descriptor() != desc.is_data_descriptor() {
// a
if !current.configurable.unwrap() {
return false;
}
// b
if current.is_data_descriptor() {
// Convert to accessor
current.value = None;
current.writable = None;
} else {
// c
// convert to data
current.get = None;
current.set = None;
}
if current.value.is_some() && current.value.clone().unwrap().is_symbol() {
let sym_id = current
.value
.clone()
.unwrap()
.to_string()
.parse::<i32>()
.expect("parsing failed");
self.sym_properties.insert(sym_id, current);
} else {
self.properties.insert(property_key.clone(), current);
}
// 7
} else if current.is_data_descriptor() && desc.is_data_descriptor() {
// a
if !current.configurable.unwrap() && !current.writable.unwrap() {
if desc.writable.is_some() && desc.writable.unwrap() {
return false;
}
if desc.value.is_some()
&& !same_value(
&desc.value.clone().unwrap(),
&current.value.clone().unwrap(),
false,
)
{
return false;
}
return true;
}
// 8
} else {
if !current.configurable.unwrap() {
if desc.set.is_some()
&& !same_value(
&desc.set.clone().unwrap(),
&current.set.clone().unwrap(),
false,
)
{
return false;
}
if desc.get.is_some()
&& !same_value(
&desc.get.clone().unwrap(),
&current.get.clone().unwrap(),
false,
)
{
return false;
}
} }
)
return true;
}
// 9
self.properties.insert(property_key, desc);
true
} }
} }
impl Object { impl Default for Object {
/// Return a new ObjectData struct, with `kind` set to Ordinary /// Return a new ObjectData struct, with `kind` set to Ordinary
pub fn default() -> Self { #[inline]
fn default() -> Self {
let mut object = Self { let mut object = Self {
kind: ObjectKind::Ordinary, data: ObjectData::Ordinary,
internal_slots: FxHashMap::default(), internal_slots: FxHashMap::default(),
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
state: None, state: None,
func: None,
}; };
object.set_internal_slot("extensible", Value::from(true)); object.set_internal_slot("extensible", Value::from(true));
object object
} }
}
impl Object {
#[inline]
pub fn new() -> Self {
Default::default()
}
/// Return a new ObjectData struct, with `kind` set to Ordinary /// Return a new ObjectData struct, with `kind` set to Ordinary
pub fn function() -> Self { pub fn function(function: Function) -> Self {
let _timer = BoaProfiler::global().start_event("Object::Function", "object"); let _timer = BoaProfiler::global().start_event("Object::Function", "object");
let mut object = Self { let mut object = Self {
kind: ObjectKind::Function, data: ObjectData::Function(function),
internal_slots: FxHashMap::default(), internal_slots: FxHashMap::default(),
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
state: None, state: None,
func: None,
}; };
object.set_internal_slot("extensible", Value::from(true)); object.set_internal_slot("extensible", Value::from(true));
@ -385,73 +149,48 @@ impl Object {
obj obj
} }
/// Set the function this object wraps
pub fn set_func(&mut self, val: Function) {
self.func = Some(val);
}
/// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument.
fn from_boolean(argument: &Value) -> Self { pub fn boolean(value: bool) -> Self {
let mut obj = Self { Self {
kind: ObjectKind::Boolean, data: ObjectData::Boolean(value),
internal_slots: FxHashMap::default(), internal_slots: FxHashMap::default(),
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
state: None, state: None,
func: None, }
};
obj.internal_slots
.insert("BooleanData".to_string(), argument.clone());
obj
} }
/// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument. /// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument.
fn from_number(argument: &Value) -> Self { pub fn number(value: f64) -> Self {
let mut obj = Self { Self {
kind: ObjectKind::Number, data: ObjectData::Number(value),
internal_slots: FxHashMap::default(), internal_slots: FxHashMap::default(),
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
state: None, state: None,
func: None, }
};
obj.internal_slots
.insert("NumberData".to_string(), argument.clone());
obj
} }
/// Return a new `String` object whose `[[StringData]]` internal slot is set to argument. /// Return a new `String` object whose `[[StringData]]` internal slot is set to argument.
fn from_string(argument: &Value) -> Self { pub fn string(value: String) -> Self {
let mut obj = Self { Self {
kind: ObjectKind::String, data: ObjectData::String(value),
internal_slots: FxHashMap::default(), internal_slots: FxHashMap::default(),
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
state: None, state: None,
func: None, }
};
obj.internal_slots
.insert("StringData".to_string(), argument.clone());
obj
} }
/// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument. /// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument.
fn from_bigint(argument: &Value) -> Self { pub fn bigint(value: BigInt) -> Self {
let mut obj = Self { Self {
kind: ObjectKind::BigInt, data: ObjectData::BigInt(value),
internal_slots: FxHashMap::default(), internal_slots: FxHashMap::default(),
properties: FxHashMap::default(), properties: FxHashMap::default(),
sym_properties: FxHashMap::default(), symbol_properties: FxHashMap::default(),
state: None, state: None,
func: None, }
};
obj.internal_slots
.insert("BigIntData".to_string(), argument.clone());
obj
} }
/// Converts the `Value` to an `Object` type. /// Converts the `Value` to an `Object` type.
@ -461,11 +200,12 @@ impl Object {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-toobject /// [spec]: https://tc39.es/ecma262/#sec-toobject
pub fn from(value: &Value) -> Result<Self, ()> { pub fn from(value: &Value) -> Result<Self, ()> {
match *value.deref().borrow() { match *value.data() {
ValueData::Boolean(_) => Ok(Self::from_boolean(value)), ValueData::Boolean(a) => Ok(Self::boolean(a)),
ValueData::Rational(_) => Ok(Self::from_number(value)), ValueData::Rational(a) => Ok(Self::number(a)),
ValueData::String(_) => Ok(Self::from_string(value)), ValueData::Integer(a) => Ok(Self::number(f64::from(a))),
ValueData::BigInt(_) => Ok(Self::from_bigint(value)), ValueData::String(ref a) => Ok(Self::string(a.clone())),
ValueData::BigInt(ref bigint) => Ok(Self::bigint(bigint.clone())),
ValueData::Object(ref obj) => Ok((*obj).deref().borrow().clone()), ValueData::Object(ref obj) => Ok((*obj).deref().borrow().clone()),
_ => Err(()), _ => Err(()),
} }
@ -477,11 +217,9 @@ impl Object {
/// - [EcmaScript reference][spec] /// - [EcmaScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-iscallable /// [spec]: https://tc39.es/ecma262/#sec-iscallable
#[inline]
pub fn is_callable(&self) -> bool { pub fn is_callable(&self) -> bool {
match self.func { matches!(self.data, ObjectData::Function(ref f) if f.is_callable())
Some(ref function) => function.is_callable(),
None => false,
}
} }
/// It determines if Object is a function object with a [[Construct]] internal method. /// It determines if Object is a function object with a [[Construct]] internal method.
@ -490,58 +228,168 @@ impl Object {
/// - [EcmaScript reference][spec] /// - [EcmaScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-isconstructor /// [spec]: https://tc39.es/ecma262/#sec-isconstructor
#[inline]
pub fn is_constructable(&self) -> bool { pub fn is_constructable(&self) -> bool {
match self.func { matches!(self.data, ObjectData::Function(ref f) if f.is_constructable())
Some(ref function) => function.is_constructable(), }
None => false,
/// Checks if it an `Array` object.
#[inline]
pub fn is_array(&self) -> bool {
matches!(self.data, ObjectData::Array)
}
#[inline]
pub fn as_array(&self) -> Option<()> {
match self.data {
ObjectData::Array => Some(()),
_ => None,
} }
} }
}
/// Defines the different types of objects. /// Checks if it a `String` object.
#[derive(Finalize, Debug, Copy, Clone, Eq, PartialEq)] #[inline]
pub enum ObjectKind { pub fn is_string(&self) -> bool {
Function, matches!(self.data, ObjectData::String(_))
Array, }
String,
Symbol,
Error,
Ordinary,
Boolean,
Number,
BigInt,
}
impl Display for ObjectKind { #[inline]
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { pub fn as_string(&self) -> Option<&str> {
write!( match self.data {
f, ObjectData::String(ref string) => Some(string.as_str()),
"{}", _ => None,
match self { }
Self::Function => "Function",
Self::Array => "Array",
Self::String => "String",
Self::Symbol => "Symbol",
Self::Error => "Error",
Self::Ordinary => "Ordinary",
Self::Boolean => "Boolean",
Self::Number => "Number",
Self::BigInt => "BigInt",
}
)
} }
}
/// `Trace` implementation for `ObjectKind`. /// Checks if it a `Function` object.
/// #[inline]
/// This is indeed safe, but we need to mark this as an empty trace because neither pub fn is_function(&self) -> bool {
// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to matches!(self.data, ObjectData::Function(_))
/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`. }
///
/// <https://github.com/Manishearth/rust-gc/blob/master/gc/src/trace.rs> #[inline]
/// Waiting on <https://github.com/Manishearth/rust-gc/issues/87> until we can derive Copy pub fn as_function(&self) -> Option<&Function> {
unsafe impl Trace for ObjectKind { match self.data {
unsafe_empty_trace!(); ObjectData::Function(ref function) => Some(function),
_ => None,
}
}
/// Checks if it a Symbol object.
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self.data, ObjectData::Symbol(_))
}
#[inline]
pub fn as_symbol(&self) -> Option<&Symbol> {
match self.data {
ObjectData::Symbol(ref symbol) => Some(symbol),
_ => None,
}
}
/// Checks if it an Error object.
#[inline]
pub fn is_error(&self) -> bool {
matches!(self.data, ObjectData::Error)
}
#[inline]
pub fn as_error(&self) -> Option<()> {
match self.data {
ObjectData::Error => Some(()),
_ => None,
}
}
/// Checks if it a Boolean object.
#[inline]
pub fn is_boolean(&self) -> bool {
matches!(self.data, ObjectData::Boolean(_))
}
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match self.data {
ObjectData::Boolean(boolean) => Some(boolean),
_ => None,
}
}
/// Checks if it a `Number` object.
#[inline]
pub fn is_number(&self) -> bool {
matches!(self.data, ObjectData::Number(_))
}
#[inline]
pub fn as_number(&self) -> Option<f64> {
match self.data {
ObjectData::Number(number) => Some(number),
_ => None,
}
}
/// Checks if it a `BigInt` object.
#[inline]
pub fn is_bigint(&self) -> bool {
matches!(self.data, ObjectData::BigInt(_))
}
#[inline]
pub fn as_bigint(&self) -> Option<&BigInt> {
match self.data {
ObjectData::BigInt(ref bigint) => Some(bigint),
_ => None,
}
}
/// Checks if it an ordinary object.
#[inline]
pub fn is_ordinary(&self) -> bool {
matches!(self.data, ObjectData::Ordinary)
}
#[inline]
pub fn internal_slots(&self) -> &FxHashMap<String, Value> {
&self.internal_slots
}
#[inline]
pub fn internal_slots_mut(&mut self) -> &mut FxHashMap<String, Value> {
&mut self.internal_slots
}
#[inline]
pub fn properties(&self) -> &FxHashMap<String, Property> {
&self.properties
}
#[inline]
pub fn properties_mut(&mut self) -> &mut FxHashMap<String, Property> {
&mut self.properties
}
#[inline]
pub fn symbol_properties(&self) -> &FxHashMap<u32, Property> {
&self.symbol_properties
}
#[inline]
pub fn symbol_properties_mut(&mut self) -> &mut FxHashMap<u32, Property> {
&mut self.symbol_properties
}
#[inline]
pub fn state(&self) -> &Option<InternalStateCell> {
&self.state
}
#[inline]
pub fn state_mut(&mut self) -> &mut Option<InternalStateCell> {
&mut self.state
}
} }
/// Create a new object. /// Create a new object.
@ -633,7 +481,6 @@ pub fn create(global: &Value) -> Value {
let object = make_constructor_fn("Object", 1, make_object, global, prototype, true); let object = make_constructor_fn("Object", 1, make_object, global, prototype, true);
object.set_field("length", Value::from(1));
make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2); make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2);
make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1); make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1);
make_builtin_fn(define_property, "defineProperty", &object, 3); make_builtin_fn(define_property, "defineProperty", &object, 3);
@ -643,7 +490,8 @@ pub fn create(global: &Value) -> Value {
/// Initialise the `Object` object on the global object. /// Initialise the `Object` object on the global object.
#[inline] #[inline]
pub fn init(global: &Value) { pub fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("object", "init"); let _timer = BoaProfiler::global().start_event("object", "init");
global.set_field("Object", create(global));
("Object", create(global))
} }

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

@ -16,7 +16,7 @@ use regex::Regex;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
object::{InternalState, ObjectKind}, object::{InternalState, ObjectData},
property::Property, property::Property,
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
}, },
@ -61,6 +61,12 @@ pub(crate) struct RegExp {
impl InternalState for RegExp {} impl InternalState for RegExp {}
impl RegExp { impl RegExp {
/// The name of the object.
pub(crate) const NAME: &'static str = "RegExp";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 2;
/// Create a new `RegExp` /// Create a new `RegExp`
pub(crate) fn make_regexp( pub(crate) fn make_regexp(
this: &mut Value, this: &mut Value,
@ -79,7 +85,8 @@ impl RegExp {
regex_body = body.into(); regex_body = body.into();
} }
ValueData::Object(ref obj) => { ValueData::Object(ref obj) => {
let slots = &obj.borrow().internal_slots; let obj = obj.borrow();
let slots = obj.internal_slots();
if slots.get("RegExpMatcher").is_some() { if slots.get("RegExpMatcher").is_some() {
// first argument is another `RegExp` object, so copy its pattern and flags // first argument is another `RegExp` object, so copy its pattern and flags
if let Some(body) = slots.get("OriginalSource") { if let Some(body) = slots.get("OriginalSource") {
@ -160,7 +167,7 @@ impl RegExp {
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
this.set_kind(ObjectKind::Ordinary); this.set_data(ObjectData::Ordinary);
this.set_internal_slot("RegExpMatcher", Value::undefined()); this.set_internal_slot("RegExpMatcher", Value::undefined());
this.set_internal_slot("OriginalSource", Value::from(regex_body)); this.set_internal_slot("OriginalSource", Value::from(regex_body));
this.set_internal_slot("OriginalFlags", Value::from(regex_flags)); this.set_internal_slot("OriginalFlags", Value::from(regex_flags));
@ -354,9 +361,8 @@ impl RegExp {
} }
let result = Value::from(result); let result = Value::from(result);
result result.set_property("index", Property::default().value(Value::from(m.start())));
.set_property_slice("index", Property::default().value(Value::from(m.start()))); result.set_property("input", Property::default().value(Value::from(arg_str)));
result.set_property_slice("input", Property::default().value(Value::from(arg_str)));
result result
} else { } else {
if regex.use_last_index { if regex.use_last_index {
@ -441,11 +447,9 @@ impl RegExp {
let match_val = Value::from(match_vec); let match_val = Value::from(match_vec);
match_val.set_property_slice( match_val
"index", .set_property("index", Property::default().value(Value::from(m.start())));
Property::default().value(Value::from(m.start())), match_val.set_property(
);
match_val.set_property_slice(
"input", "input",
Property::default().value(Value::from(arg_str.clone())), Property::default().value(Value::from(arg_str.clone())),
); );
@ -463,7 +467,7 @@ impl RegExp {
let length = matches.len(); let length = matches.len();
let result = Value::from(matches); let result = Value::from(matches);
result.set_field("length", Value::from(length)); result.set_field("length", Value::from(length));
result.set_kind(ObjectKind::Array); result.set_data(ObjectData::Array);
Ok(result) Ok(result)
} }
@ -472,7 +476,10 @@ impl RegExp {
pub(crate) fn create(global: &Value) -> Value { pub(crate) fn create(global: &Value) -> Value {
// Create prototype // Create prototype
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
prototype.set_field("lastIndex", Value::from(0)); prototype
.as_object_mut()
.unwrap()
.insert_field("lastIndex", Value::from(0));
make_builtin_fn(Self::test, "test", &prototype, 1); make_builtin_fn(Self::test, "test", &prototype, 1);
make_builtin_fn(Self::exec, "exec", &prototype, 1); make_builtin_fn(Self::exec, "exec", &prototype, 1);
@ -486,13 +493,21 @@ impl RegExp {
make_builtin_fn(Self::get_sticky, "sticky", &prototype, 0); make_builtin_fn(Self::get_sticky, "sticky", &prototype, 0);
make_builtin_fn(Self::get_unicode, "unicode", &prototype, 0); make_builtin_fn(Self::get_unicode, "unicode", &prototype, 0);
make_constructor_fn("RegExp", 1, Self::make_regexp, global, prototype, true) make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_regexp,
global,
prototype,
true,
)
} }
/// Initialise the `RegExp` object on the global object. /// Initialise the `RegExp` object on the global object.
#[inline] #[inline]
pub(crate) fn init(global: &Value) { pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("regexp", "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
global.set_field("RegExp", Self::create(global));
(Self::NAME, Self::create(global))
} }
} }

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

@ -15,7 +15,7 @@ mod tests;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::{
object::{Object, ObjectKind}, object::{Object, ObjectData},
property::Property, property::Property,
value::{ResultValue, Value, ValueData}, value::{ResultValue, Value, ValueData},
RegExp, RegExp,
@ -36,6 +36,27 @@ use std::{
pub(crate) struct String; pub(crate) struct String;
impl String { impl String {
/// The name of the object.
pub(crate) const NAME: &'static str = "String";
/// The amount of arguments this function object takes.
pub(crate) const LENGTH: usize = 1;
fn this_string_value(this: &Value, ctx: &mut Interpreter) -> Result<StdString, Value> {
match this.data() {
ValueData::String(ref string) => return Ok(string.clone()),
ValueData::Object(ref object) => {
let object = object.borrow();
if let Some(string) = object.as_string() {
return Ok(string.to_owned());
}
}
_ => {}
}
Err(ctx.construct_type_error("'this' is not a string"))
}
/// [[Construct]] - Creates a new instance `this` /// [[Construct]] - Creates a new instance `this`
/// ///
/// [[Call]] - Returns a new native `string` /// [[Call]] - Returns a new native `string`
@ -43,39 +64,30 @@ impl String {
pub(crate) fn make_string( pub(crate) fn make_string(
this: &mut Value, this: &mut Value,
args: &[Value], args: &[Value],
_: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
// This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe
// to its Javascript Identifier (global constructor method name) // to its Javascript Identifier (global constructor method name)
let s = args.get(0).unwrap_or(&Value::string("")).clone(); let string = match args.get(0) {
let length_str = s.to_string().chars().count(); Some(ref value) => ctx.to_string(value)?,
None => StdString::new(),
this.set_field("length", Value::from(length_str as i32)); };
this.set_kind(ObjectKind::String); let length = string.chars().count();
this.set_internal_slot("StringData", s);
let arg = match args.get(0) { this.set_field("length", Value::from(length as i32));
Some(v) => v.clone(),
None => Value::undefined(),
};
if arg.is_undefined() { this.set_data(ObjectData::String(string.clone()));
return Ok("".into());
}
Ok(Value::from(arg.to_string())) Ok(Value::from(string))
} }
/// Get the string value to a primitive string /// Get the string value to a primitive string
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
#[inline]
pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
// Get String from String Object and send it back as a new value // Get String from String Object and send it back as a new value
match this.get_internal_slot("StringData").data() { Ok(Value::from(Self::this_string_value(this, ctx)?))
ValueData::String(ref string) => Ok(Value::from(string.clone())),
// Throw expection here:
_ => ctx.throw_type_error("'this' is not a string"),
}
} }
/// `String.prototype.charAt( index )` /// `String.prototype.charAt( index )`
@ -183,14 +195,14 @@ impl String {
pub(crate) fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to. // First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value // Then we convert it into a Rust String by wrapping it in from_value
let mut new_str = ctx.to_string(this)?; let object = ctx.require_object_coercible(this)?;
let mut string = ctx.to_string(object)?;
for arg in args { for arg in args {
let concat_str = arg.to_string(); string.push_str(&ctx.to_string(arg)?);
new_str.push_str(&concat_str);
} }
Ok(Value::from(new_str)) Ok(Value::from(string))
} }
/// `String.prototype.repeat( count )` /// `String.prototype.repeat( count )`
@ -402,10 +414,11 @@ impl String {
match value.deref() { match value.deref() {
ValueData::String(ref body) => body.into(), ValueData::String(ref body) => body.into(),
ValueData::Object(ref obj) => { ValueData::Object(ref obj) => {
let slots = &obj.borrow().internal_slots; let obj = obj.borrow();
if slots.get("RegExpMatcher").is_some() {
if obj.internal_slots().get("RegExpMatcher").is_some() {
// first argument is another `RegExp` object, so copy its pattern and flags // first argument is another `RegExp` object, so copy its pattern and flags
if let Some(body) = slots.get("OriginalSource") { if let Some(body) = obj.internal_slots().get("OriginalSource") {
return body.to_string(); return body.to_string();
} }
} }
@ -1046,7 +1059,8 @@ impl String {
let prototype = Value::new_object(Some(global)); let prototype = Value::new_object(Some(global));
let length = Property::default().value(Value::from(0)); let length = Property::default().value(Value::from(0));
prototype.set_property_slice("length", length); prototype.set_property("length", length);
make_builtin_fn(Self::char_at, "charAt", &prototype, 1); make_builtin_fn(Self::char_at, "charAt", &prototype, 1);
make_builtin_fn(Self::char_code_at, "charCodeAt", &prototype, 1); make_builtin_fn(Self::char_code_at, "charCodeAt", &prototype, 1);
make_builtin_fn(Self::to_string, "toString", &prototype, 0); make_builtin_fn(Self::to_string, "toString", &prototype, 0);
@ -1072,13 +1086,21 @@ impl String {
make_builtin_fn(Self::match_all, "matchAll", &prototype, 1); make_builtin_fn(Self::match_all, "matchAll", &prototype, 1);
make_builtin_fn(Self::replace, "replace", &prototype, 2); make_builtin_fn(Self::replace, "replace", &prototype, 2);
make_constructor_fn("String", 1, Self::make_string, global, prototype, true) make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::make_string,
global,
prototype,
true,
)
} }
/// Initialise the `String` object on the global object. /// Initialise the `String` object on the global object.
#[inline] #[inline]
pub(crate) fn init(global: &Value) { pub(crate) fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event("string", "init"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
global.set_field("String", Self::create(global));
(Self::NAME, Self::create(global))
} }
} }

172
boa/src/builtins/symbol/mod.rs

@ -20,87 +20,107 @@ mod tests;
use super::function::{make_builtin_fn, make_constructor_fn}; use super::function::{make_builtin_fn, make_constructor_fn};
use crate::{ use crate::{
builtins::{ builtins::value::{ResultValue, Value, ValueData},
object::{
internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE,
PROTOTYPE,
},
value::{ResultValue, Value, ValueData},
},
exec::Interpreter, exec::Interpreter,
BoaProfiler, BoaProfiler,
}; };
use gc::{Gc, GcCell}; use gc::{Finalize, Trace};
use rand::random;
/// Creates Symbol instances.
///
/// Symbol instances are ordinary objects that inherit properties from the Symbol prototype object.
/// Symbol instances have a `[[SymbolData]]` internal slot.
/// The `[[SymbolData]]` internal slot is the Symbol value represented by this Symbol object.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-symbol-description
pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// From an implementation and specificaition perspective Symbols are similar to Objects.
// They have internal slots to hold the SymbolData and Description, they also have methods and a prototype.
// So we start by creating an Object
// TODO: Set prototype to Symbol.prototype (by changing to Object::create(), use interpreter to get Symbol.prototype)
let mut sym_instance = Object::default();
sym_instance.kind = ObjectKind::Symbol;
// Set description which should either be undefined or a string
let desc_string = match args.get(0) {
Some(value) => Value::from(value.to_string()),
None => Value::undefined(),
};
sym_instance.set_internal_slot("Description", desc_string);
sym_instance.set_internal_slot("SymbolData", Value::from(random::<i32>()));
// Set __proto__ internal slot
let proto = ctx
.realm
.global_obj
.get_field("Symbol")
.get_field(PROTOTYPE);
sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto);
Ok(Value(Gc::new(ValueData::Symbol(Box::new(GcCell::new(
sym_instance,
))))))
}
/// `Symbol.prototype.toString()` #[derive(Debug, Finalize, Trace, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// pub struct Symbol(Option<Box<str>>, u32);
/// This method returns a string representing the specified `Symbol` object.
///
/// /// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString
pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let s: Value = this.get_internal_slot("Description");
let full_string = format!(r#"Symbol({})"#, s.to_string());
Ok(Value::from(full_string))
}
/// Create a new `Symbol` object. impl Symbol {
pub fn create(global: &Value) -> Value { /// The name of the object.
// Create prototype object pub(crate) const NAME: &'static str = "Symbol";
let prototype = Value::new_object(Some(global));
make_builtin_fn(to_string, "toString", &prototype, 0); /// The amount of arguments this function object takes.
make_constructor_fn("Symbol", 1, call_symbol, global, prototype, false) pub(crate) const LENGTH: usize = 0;
}
/// Returns the `Symbol`s description.
pub fn description(&self) -> Option<&str> {
self.0.as_deref()
}
/// Returns the `Symbol`s hash.
pub fn hash(&self) -> u32 {
self.1
}
fn this_symbol_value(value: &Value, ctx: &mut Interpreter) -> Result<Self, Value> {
match value.data() {
ValueData::Symbol(ref symbol) => return Ok(symbol.clone()),
ValueData::Object(ref object) => {
let object = object.borrow();
if let Some(symbol) = object.as_symbol() {
return Ok(symbol.clone());
}
}
_ => {}
}
Err(ctx.construct_type_error("'this' is not a Symbol"))
}
/// The `Symbol()` constructor returns a value of type symbol.
///
/// It is incomplete as a constructor because it does not support
/// the syntax `new Symbol()` and it is not intended to be subclassed.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-symbol-description
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol
pub(crate) fn call(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let description = match args.get(0) {
Some(ref value) if !value.is_undefined() => {
Some(ctx.to_string(value)?.into_boxed_str())
}
_ => None,
};
Ok(Value::symbol(Symbol(description, ctx.generate_hash())))
}
/// `Symbol.prototype.toString()`
///
/// This method returns a string representing the specified `Symbol` object.
///
/// /// More information:
/// - [MDN documentation][mdn]
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-symbol.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let symbol = Self::this_symbol_value(this, ctx)?;
let description = symbol.description().unwrap_or("");
Ok(Value::from(format!("Symbol({})", description)))
}
/// Create a new `Symbol` object.
pub(crate) fn create(global: &Value) -> Value {
// Create prototype object
let prototype = Value::new_object(Some(global));
make_builtin_fn(Self::to_string, "toString", &prototype, 0);
make_constructor_fn(
Self::NAME,
Self::LENGTH,
Self::call,
global,
prototype,
false,
)
}
/// Initialise the `Symbol` object on the global object.
#[inline]
pub fn init(global: &Value) -> (&str, Value) {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
/// Initialise the `Symbol` object on the global object. (Self::NAME, Self::create(global))
#[inline] }
pub fn init(global: &Value) {
let _timer = BoaProfiler::global().start_event("symbol", "init");
global.set_field("Symbol", create(global));
} }

2
boa/src/builtins/symbol/tests.rs

@ -4,7 +4,7 @@ use crate::{exec::Interpreter, forward, forward_val, realm::Realm};
#[test] #[test]
fn check_symbol_constructor_is_function() { fn check_symbol_constructor_is_function() {
let global = Value::new_object(None); let global = Value::new_object(None);
let symbol_constructor = create(&global); let symbol_constructor = Symbol::create(&global);
assert_eq!(symbol_constructor.is_function(), true); assert_eq!(symbol_constructor.is_function(), true);
} }

4
boa/src/builtins/value/conversions.rs

@ -119,7 +119,7 @@ where
fn from(value: &[T]) -> Self { fn from(value: &[T]) -> Self {
let mut array = Object::default(); let mut array = Object::default();
for (i, item) in value.iter().enumerate() { for (i, item) in value.iter().enumerate() {
array.properties.insert( array.properties_mut().insert(
i.to_string(), i.to_string(),
Property::default().value(item.clone().into()), Property::default().value(item.clone().into()),
); );
@ -136,7 +136,7 @@ where
let mut array = Object::default(); let mut array = Object::default();
for (i, item) in value.into_iter().enumerate() { for (i, item) in value.into_iter().enumerate() {
array array
.properties .properties_mut()
.insert(i.to_string(), Property::default().value(item.into())); .insert(i.to_string(), Property::default().value(item.into()));
} }
Value::from(array) Value::from(array)

46
boa/src/builtins/value/display.rs

@ -57,7 +57,7 @@ macro_rules! print_obj_value {
(impl $field:ident, $v:expr, $f:expr) => { (impl $field:ident, $v:expr, $f:expr) => {
$v $v
.borrow() .borrow()
.$field .$field()
.iter() .iter()
.map($f) .map($f)
.collect::<Vec<String>>() .collect::<Vec<String>>()
@ -70,26 +70,13 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String {
ValueData::Object(ref v) => { ValueData::Object(ref v) => {
// Can use the private "type" field of an Object to match on // Can use the private "type" field of an Object to match on
// which type of Object it represents for special printing // which type of Object it represents for special printing
match v.borrow().kind { match v.borrow().data {
ObjectKind::String => match v ObjectData::String(ref string) => format!("String {{ \"{}\" }}", string),
.borrow() ObjectData::Boolean(boolean) => format!("Boolean {{ {} }}", boolean),
.internal_slots ObjectData::Array => {
.get("StringData")
.expect("Cannot get primitive value from String")
.data()
{
ValueData::String(ref string) => format!("\"{}\"", string),
_ => unreachable!("[[StringData]] should always contain String"),
},
ObjectKind::Boolean => {
let bool_data = v.borrow().get_internal_slot("BooleanData").to_string();
format!("Boolean {{ {} }}", bool_data)
}
ObjectKind::Array => {
let len = i32::from( let len = i32::from(
&v.borrow() &v.borrow()
.properties .properties()
.get("length") .get("length")
.unwrap() .unwrap()
.value .value
@ -107,7 +94,7 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String {
// which are part of the Array // which are part of the Array
log_string_from( log_string_from(
&v.borrow() &v.borrow()
.properties .properties()
.get(&i.to_string()) .get(&i.to_string())
.unwrap() .unwrap()
.value .value
@ -124,14 +111,10 @@ pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String {
_ => display_obj(&x, print_internals), _ => display_obj(&x, print_internals),
} }
} }
ValueData::Symbol(ref sym) => { ValueData::Symbol(ref symbol) => match symbol.description() {
let desc: Value = sym.borrow().get_internal_slot("Description"); Some(ref desc) => format!("Symbol({})", desc),
match *desc { None => String::from("Symbol()"),
ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()), },
_ => String::from("Symbol()"),
}
}
_ => format!("{}", x), _ => format!("{}", x),
} }
} }
@ -198,10 +181,9 @@ impl Display for ValueData {
Self::Null => write!(f, "null"), Self::Null => write!(f, "null"),
Self::Undefined => write!(f, "undefined"), Self::Undefined => write!(f, "undefined"),
Self::Boolean(v) => write!(f, "{}", v), Self::Boolean(v) => write!(f, "{}", v),
Self::Symbol(ref v) => match *v.borrow().get_internal_slot("Description") { Self::Symbol(ref symbol) => match symbol.description() {
// If a description exists use it Some(description) => write!(f, "Symbol({})", description),
Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)), None => write!(f, "Symbol()"),
_ => write!(f, "Symbol()"),
}, },
Self::String(ref v) => write!(f, "{}", v), Self::String(ref v) => write!(f, "{}", v),
Self::Rational(v) => write!( Self::Rational(v) => write!(

7
boa/src/builtins/value/equality.rs

@ -197,14 +197,13 @@ pub fn same_value_zero(x: &Value, y: &Value) -> bool {
} }
} }
pub fn same_value_non_numeric(x: &Value, y: &Value) -> bool { fn same_value_non_numeric(x: &Value, y: &Value) -> bool {
debug_assert!(x.get_type() == y.get_type()); debug_assert!(x.get_type() == y.get_type());
match x.get_type() { match x.get_type() {
Type::Undefined => true, Type::Null | Type::Undefined => true,
Type::Null => true,
Type::String => x.to_string() == y.to_string(), Type::String => x.to_string() == y.to_string(),
Type::Boolean => bool::from(x) == bool::from(y), Type::Boolean => bool::from(x) == bool::from(y),
Type::Object => std::ptr::eq(x, y), Type::Object => std::ptr::eq(x.data(), y.data()),
_ => false, _ => false,
} }
} }

54
boa/src/builtins/value/hash.rs

@ -0,0 +1,54 @@
use super::*;
use crate::builtins::Number;
use std::hash::{Hash, Hasher};
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
same_value_zero(self, other)
}
}
impl Eq for Value {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UndefinedHashable;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct NullHashable;
#[derive(Debug, Clone, Copy)]
struct RationalHashable(f64);
impl PartialEq for RationalHashable {
#[inline]
fn eq(&self, other: &Self) -> bool {
Number::same_value(self.0, other.0)
}
}
impl Eq for RationalHashable {}
impl Hash for RationalHashable {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
let data = self.data();
match data {
ValueData::Undefined => UndefinedHashable.hash(state),
ValueData::Null => NullHashable.hash(state),
ValueData::String(ref string) => string.hash(state),
ValueData::Boolean(boolean) => boolean.hash(state),
ValueData::Integer(integer) => integer.hash(state),
ValueData::BigInt(ref bigint) => bigint.hash(state),
ValueData::Rational(rational) => RationalHashable(*rational).hash(state),
ValueData::Symbol(ref symbol) => Hash::hash(symbol, state),
ValueData::Object(_) => std::ptr::hash(data, state),
}
}
}

361
boa/src/builtins/value/mod.rs

@ -10,17 +10,14 @@ pub mod val_type;
pub use crate::builtins::value::val_type::Type; pub use crate::builtins::value::val_type::Type;
use crate::builtins::{ use crate::builtins::{
object::{ function::Function,
internal_methods_trait::ObjectInternalMethods, InternalState, InternalStateCell, Object, object::{InternalState, InternalStateCell, Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE,
},
property::Property, property::Property,
BigInt, Function, BigInt, Symbol,
}; };
use crate::BoaProfiler;
use crate::exec::Interpreter; use crate::exec::Interpreter;
use gc::{Finalize, Gc, GcCell, GcCellRef, Trace}; use crate::BoaProfiler;
use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace};
use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue};
use std::{ use std::{
any::Any, any::Any,
@ -28,18 +25,20 @@ use std::{
convert::TryFrom, convert::TryFrom,
f64::NAN, f64::NAN,
fmt::{self, Display}, fmt::{self, Display},
ops::{Add, BitAnd, BitOr, BitXor, Deref, DerefMut, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub},
str::FromStr, str::FromStr,
}; };
pub mod conversions; pub mod conversions;
pub mod display; pub mod display;
pub mod equality; pub mod equality;
pub mod hash;
pub mod operations; pub mod operations;
pub use conversions::*; pub use conversions::*;
pub(crate) use display::display_obj; pub(crate) use display::display_obj;
pub use equality::*; pub use equality::*;
pub use hash::*;
pub use operations::*; pub use operations::*;
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
@ -48,7 +47,7 @@ pub type ResultValue = Result<Value, Value>;
/// A Garbage-collected Javascript value as represented in the interpreter. /// A Garbage-collected Javascript value as represented in the interpreter.
#[derive(Debug, Clone, Trace, Finalize, Default)] #[derive(Debug, Clone, Trace, Finalize, Default)]
pub struct Value(pub(crate) Gc<ValueData>); pub struct Value(Gc<ValueData>);
impl Value { impl Value {
/// Creates a new `undefined` value. /// Creates a new `undefined` value.
@ -63,6 +62,12 @@ impl Value {
Self(Gc::new(ValueData::Null)) Self(Gc::new(ValueData::Null))
} }
/// Creates a new number with `NaN` value.
#[inline]
pub fn nan() -> Self {
Self::number(NAN)
}
/// Creates a new string value. /// Creates a new string value.
#[inline] #[inline]
pub fn string<S>(value: S) -> Self pub fn string<S>(value: S) -> Self
@ -117,6 +122,12 @@ impl Value {
Self(Gc::new(ValueData::Object(Box::new(GcCell::new(object))))) Self(Gc::new(ValueData::Object(Box::new(GcCell::new(object)))))
} }
/// Creates a new symbol value.
#[inline]
pub fn symbol(symbol: Symbol) -> Self {
Self(Gc::new(ValueData::Symbol(symbol)))
}
/// Gets the underlying `ValueData` structure. /// Gets the underlying `ValueData` structure.
#[inline] #[inline]
pub fn data(&self) -> &ValueData { pub fn data(&self) -> &ValueData {
@ -145,12 +156,12 @@ impl Value {
} }
/// Similar to `new_object`, but you can pass a prototype to create from, plus a kind /// Similar to `new_object`, but you can pass a prototype to create from, plus a kind
pub fn new_object_from_prototype(proto: Value, kind: ObjectKind) -> Self { pub fn new_object_from_prototype(proto: Value, data: ObjectData) -> Self {
let mut object = Object::default(); let mut object = Object::default();
object.kind = kind; object.data = data;
object object
.internal_slots .internal_slots_mut()
.insert(INSTANCE_PROTOTYPE.to_string(), proto); .insert(INSTANCE_PROTOTYPE.to_string(), proto);
Self::object(object) Self::object(object)
@ -175,7 +186,7 @@ impl Value {
.get_field("Array") .get_field("Array")
.get_field(PROTOTYPE); .get_field(PROTOTYPE);
let new_obj = let new_obj =
Value::new_object_from_prototype(global_array_prototype, ObjectKind::Array); Value::new_object_from_prototype(global_array_prototype, ObjectData::Array);
let length = vs.len(); let length = vs.len();
for (idx, json) in vs.into_iter().enumerate() { for (idx, json) in vs.into_iter().enumerate() {
new_obj.set_property( new_obj.set_property(
@ -216,9 +227,9 @@ impl Value {
ValueData::Null => Ok(JSONValue::Null), ValueData::Null => Ok(JSONValue::Null),
ValueData::Boolean(b) => Ok(JSONValue::Bool(b)), ValueData::Boolean(b) => Ok(JSONValue::Bool(b)),
ValueData::Object(ref obj) => { ValueData::Object(ref obj) => {
if obj.borrow().kind == ObjectKind::Array { if obj.borrow().is_array() {
let mut arr: Vec<JSONValue> = Vec::new(); let mut arr: Vec<JSONValue> = Vec::new();
for k in obj.borrow().properties.keys() { for k in obj.borrow().properties().keys() {
if k != "length" { if k != "length" {
let value = self.get_field(k.to_string()); let value = self.get_field(k.to_string());
if value.is_undefined() || value.is_function() { if value.is_undefined() || value.is_function() {
@ -231,7 +242,7 @@ impl Value {
Ok(JSONValue::Array(arr)) Ok(JSONValue::Array(arr))
} else { } else {
let mut new_obj = Map::new(); let mut new_obj = Map::new();
for k in obj.borrow().properties.keys() { for k in obj.borrow().properties().keys() {
let key = k.clone(); let key = k.clone();
let value = self.get_field(k.to_string()); let value = self.get_field(k.to_string());
if !value.is_undefined() && !value.is_function() { if !value.is_undefined() && !value.is_function() {
@ -246,9 +257,9 @@ impl Value {
.map(JSONValue::Number) .map(JSONValue::Number)
.unwrap_or(JSONValue::Null)), .unwrap_or(JSONValue::Null)),
ValueData::Integer(val) => Ok(JSONValue::Number(JSONNumber::from(val))), ValueData::Integer(val) => Ok(JSONValue::Number(JSONNumber::from(val))),
ValueData::BigInt(_) => Err(interpreter ValueData::BigInt(_) => {
.throw_type_error("BigInt value can't be serialized in JSON") Err(interpreter.construct_type_error("BigInt value can't be serialized in JSON"))
.expect_err("throw_type_error should always return an error")), }
ValueData::Symbol(_) | ValueData::Undefined => { ValueData::Symbol(_) | ValueData::Undefined => {
unreachable!("Symbols and Undefined JSON Values depend on parent type"); unreachable!("Symbols and Undefined JSON Values depend on parent type");
} }
@ -283,8 +294,8 @@ pub enum ValueData {
BigInt(BigInt), BigInt(BigInt),
/// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values. /// `Object` - An object, such as `Math`, represented by a binary tree of string keys to Javascript values.
Object(Box<GcCell<Object>>), Object(Box<GcCell<Object>>),
/// `Symbol` - A Symbol Type - Internally Symbols are similar to objects, except there are no properties, only internal slots. /// `Symbol` - A Symbol Primitive type.
Symbol(Box<GcCell<Object>>), Symbol(Symbol),
} }
impl ValueData { impl ValueData {
@ -302,65 +313,65 @@ impl ValueData {
} }
/// Returns true if the value is an object /// Returns true if the value is an object
#[inline]
pub fn is_object(&self) -> bool { pub fn is_object(&self) -> bool {
matches!(self, Self::Object(_))
}
#[inline]
pub fn as_object(&self) -> Option<GcCellRef<'_, Object>> {
match *self { match *self {
Self::Object(_) => true, Self::Object(ref o) => Some(o.borrow()),
_ => false, _ => None,
} }
} }
/// Returns true if the value is a symbol #[inline]
pub fn is_symbol(&self) -> bool { pub fn as_object_mut(&self) -> Option<GcCellRefMut<'_, Object>> {
match *self { match *self {
Self::Symbol(_) => true, Self::Object(ref o) => Some(o.borrow_mut()),
_ => false, _ => None,
} }
} }
/// Returns true if the value is a symbol.
#[inline]
pub fn is_symbol(&self) -> bool {
matches!(self, Self::Symbol(_))
}
/// Returns true if the value is a function /// Returns true if the value is a function
#[inline]
pub fn is_function(&self) -> bool { pub fn is_function(&self) -> bool {
match *self { matches!(self, Self::Object(o) if o.borrow().is_function())
Self::Object(ref o) => {
let borrowed_obj = o.borrow();
borrowed_obj.is_callable() || borrowed_obj.is_constructable()
}
_ => false,
}
} }
/// Returns true if the value is undefined. /// Returns true if the value is undefined.
#[inline]
pub fn is_undefined(&self) -> bool { pub fn is_undefined(&self) -> bool {
match *self { matches!(self, Self::Undefined)
Self::Undefined => true,
_ => false,
}
} }
/// Returns true if the value is null. /// Returns true if the value is null.
#[inline]
pub fn is_null(&self) -> bool { pub fn is_null(&self) -> bool {
match *self { matches!(self, Self::Null)
Self::Null => true,
_ => false,
}
} }
/// Returns true if the value is null or undefined. /// Returns true if the value is null or undefined.
#[inline]
pub fn is_null_or_undefined(&self) -> bool { pub fn is_null_or_undefined(&self) -> bool {
match *self { matches!(self, Self::Null | Self::Undefined)
Self::Null | Self::Undefined => true,
_ => false,
}
} }
/// Returns true if the value is a 64-bit floating-point number. /// Returns true if the value is a 64-bit floating-point number.
#[inline]
pub fn is_double(&self) -> bool { pub fn is_double(&self) -> bool {
match *self { matches!(self, Self::Rational(_))
Self::Rational(_) => true,
_ => false,
}
} }
/// Returns true if the value is integer. /// Returns true if the value is integer.
#[inline]
#[allow(clippy::float_cmp)] #[allow(clippy::float_cmp)]
pub fn is_integer(&self) -> bool { pub fn is_integer(&self) -> bool {
// If it can fit in a i32 and the trucated version is // If it can fit in a i32 and the trucated version is
@ -374,39 +385,40 @@ impl ValueData {
} }
} }
/// Returns true if the value is a number /// Returns true if the value is a number.
#[inline]
pub fn is_number(&self) -> bool { pub fn is_number(&self) -> bool {
match self { matches!(self, Self::Rational(_) | Self::Integer(_))
Self::Rational(_) | Self::Integer(_) => true,
_ => false,
}
} }
/// Returns true if the value is a string /// Returns true if the value is a string.
#[inline]
pub fn is_string(&self) -> bool { pub fn is_string(&self) -> bool {
match *self { matches!(self, Self::String(_))
Self::String(_) => true,
_ => false,
}
} }
/// Returns true if the value is a boolean /// Returns true if the value is a boolean.
#[inline]
pub fn is_boolean(&self) -> bool { pub fn is_boolean(&self) -> bool {
match *self { matches!(self, Self::Boolean(_))
Self::Boolean(_) => true,
_ => false,
}
} }
/// Returns true if the value is a bigint /// Returns true if the value is a bigint.
#[inline]
pub fn is_bigint(&self) -> bool { pub fn is_bigint(&self) -> bool {
match *self { matches!(self, Self::BigInt(_))
Self::BigInt(_) => true, }
_ => false,
/// Returns an optional reference to a `BigInt` if the value is a BigInt primitive.
#[inline]
pub fn as_bigint(&self) -> Option<&BigInt> {
match self {
Self::BigInt(bigint) => Some(bigint),
_ => None,
} }
} }
/// Returns true if the value is true /// Returns true if the value is true.
/// ///
/// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean)
pub fn is_true(&self) -> bool { pub fn is_true(&self) -> bool {
@ -466,23 +478,27 @@ impl ValueData {
} }
} }
pub fn as_object(&self) -> Option<GcCellRef<'_, Object>> { /// Creates a new boolean value from the input
pub fn to_boolean(&self) -> bool {
match *self { match *self {
ValueData::Object(ref o) => Some(o.borrow()), Self::Undefined | Self::Null => false,
_ => None, Self::Symbol(_) | Self::Object(_) => true,
Self::String(ref s) if !s.is_empty() => true,
Self::Rational(n) if n != 0.0 && !n.is_nan() => true,
Self::Integer(n) if n != 0 => true,
Self::BigInt(ref n) if *n != 0 => true,
Self::Boolean(v) => v,
_ => false,
} }
} }
/// Removes a property from a Value object. /// Removes a property from a Value object.
/// ///
/// It will return a boolean based on if the value was removed, if there was no value to remove false is returned /// It will return a boolean based on if the value was removed, if there was no value to remove false is returned.
pub fn remove_property(&self, field: &str) -> bool { pub fn remove_property(&self, field: &str) -> bool {
let removed = match *self { self.as_object_mut()
Self::Object(ref obj) => obj.borrow_mut().deref_mut().properties.remove(field), .and_then(|mut x| x.properties_mut().remove(field))
_ => None, .is_some()
};
removed.is_some()
} }
/// Resolve the property in the object. /// Resolve the property in the object.
@ -492,36 +508,22 @@ impl ValueData {
let _timer = BoaProfiler::global().start_event("Value::get_property", "value"); let _timer = BoaProfiler::global().start_event("Value::get_property", "value");
// Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154 // Spidermonkey has its own GetLengthProperty: https://searchfox.org/mozilla-central/source/js/src/vm/Interpreter-inl.h#154
// This is only for primitive strings, String() objects have their lengths calculated in string.rs // This is only for primitive strings, String() objects have their lengths calculated in string.rs
if self.is_string() && field == "length" { match self {
if let Self::String(ref s) = *self { Self::Undefined => None,
return Some(Property::default().value(Value::from(s.len()))); Self::String(ref s) if field == "length" => {
} Some(Property::default().value(Value::from(s.chars().count())))
}
if self.is_undefined() {
return None;
}
let obj: Object = match *self {
Self::Object(ref obj) => {
let hash = obj.clone();
// TODO: This will break, we should return a GcCellRefMut instead
// into_inner will consume the wrapped value and remove it from the hashmap
hash.into_inner()
} }
Self::Symbol(ref obj) => { Self::Object(ref object) => {
let hash = obj.clone(); let object = object.borrow();
hash.into_inner() match object.properties().get(field) {
Some(value) => Some(value.clone()),
None => object
.internal_slots()
.get(INSTANCE_PROTOTYPE)
.and_then(|value| value.get_property(field)),
}
} }
_ => return None, _ => None,
};
match obj.properties.get(field) {
Some(val) => Some(val.clone()),
None => match obj.internal_slots.get(&INSTANCE_PROTOTYPE.to_string()) {
Some(value) => value.get_property(field),
None => None,
},
} }
} }
@ -537,18 +539,14 @@ impl ValueData {
configurable: Option<bool>, configurable: Option<bool>,
) { ) {
let _timer = BoaProfiler::global().start_event("Value::update_property", "value"); let _timer = BoaProfiler::global().start_event("Value::update_property", "value");
let obj: Option<Object> = match self {
Self::Object(ref obj) => Some(obj.borrow_mut().deref_mut().clone()),
_ => None,
};
if let Some(mut obj_data) = obj { if let Some(ref mut object) = self.as_object_mut() {
// Use value, or walk up the prototype chain // Use value, or walk up the prototype chain
if let Some(ref mut prop) = obj_data.properties.get_mut(field) { if let Some(ref mut property) = object.properties_mut().get_mut(field) {
prop.value = value; property.value = value;
prop.enumerable = enumerable; property.enumerable = enumerable;
prop.writable = writable; property.writable = writable;
prop.configurable = configurable; property.configurable = configurable;
} }
} }
} }
@ -556,24 +554,13 @@ impl ValueData {
/// Resolve the property in the object. /// Resolve the property in the object.
/// ///
/// Returns a copy of the Property. /// Returns a copy of the Property.
#[inline]
pub fn get_internal_slot(&self, field: &str) -> Value { pub fn get_internal_slot(&self, field: &str) -> Value {
let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value"); let _timer = BoaProfiler::global().start_event("Value::get_internal_slot", "value");
let obj: Object = match *self {
Self::Object(ref obj) => {
let hash = obj.clone();
hash.into_inner()
}
Self::Symbol(ref obj) => {
let hash = obj.clone();
hash.into_inner()
}
_ => return Value::undefined(),
};
match obj.internal_slots.get(field) { self.as_object()
Some(val) => val.clone(), .and_then(|x| x.internal_slots().get(field).cloned())
None => Value::undefined(), .unwrap_or_else(Value::undefined)
}
} }
/// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
@ -615,21 +602,15 @@ impl ValueData {
} }
/// Check whether an object has an internal state set. /// Check whether an object has an internal state set.
#[inline]
pub fn has_internal_state(&self) -> bool { pub fn has_internal_state(&self) -> bool {
if let Self::Object(ref obj) = *self { matches!(self.as_object(), Some(object) if object.state().is_some())
obj.borrow().state.is_some()
} else {
false
}
} }
/// Get the internal state of an object. /// Get the internal state of an object.
pub fn get_internal_state(&self) -> Option<InternalStateCell> { pub fn get_internal_state(&self) -> Option<InternalStateCell> {
if let Self::Object(ref obj) = *self { self.as_object()
obj.borrow().state.as_ref().cloned() .and_then(|object| object.state().as_ref().cloned())
} else {
None
}
} }
/// Run a function with a reference to the internal state. /// Run a function with a reference to the internal state.
@ -638,14 +619,14 @@ impl ValueData {
/// ///
/// This will panic if this value doesn't have an internal state or if the internal state doesn't /// This will panic if this value doesn't have an internal state or if the internal state doesn't
/// have the concrete type `S`. /// have the concrete type `S`.
pub fn with_internal_state_ref<S: Any + InternalState, R, F: FnOnce(&S) -> R>( pub fn with_internal_state_ref<S, R, F>(&self, f: F) -> R
&self, where
f: F, S: Any + InternalState,
) -> R { F: FnOnce(&S) -> R,
if let Self::Object(ref obj) = *self { {
let o = obj.borrow(); if let Some(object) = self.as_object() {
let state = o let state = object
.state .state()
.as_ref() .as_ref()
.expect("no state") .expect("no state")
.downcast_ref() .downcast_ref()
@ -662,14 +643,14 @@ impl ValueData {
/// ///
/// This will panic if this value doesn't have an internal state or if the internal state doesn't /// This will panic if this value doesn't have an internal state or if the internal state doesn't
/// have the concrete type `S`. /// have the concrete type `S`.
pub fn with_internal_state_mut<S: Any + InternalState, R, F: FnOnce(&mut S) -> R>( pub fn with_internal_state_mut<S, R, F>(&self, f: F) -> R
&self, where
f: F, S: Any + InternalState,
) -> R { F: FnOnce(&mut S) -> R,
if let Self::Object(ref obj) = *self { {
let mut o = obj.borrow_mut(); if let Some(mut object) = self.as_object_mut() {
let state = o let state = object
.state .state_mut()
.as_mut() .as_mut()
.expect("no state") .expect("no state")
.downcast_mut() .downcast_mut()
@ -680,7 +661,8 @@ impl ValueData {
} }
} }
/// Check to see if the Value has the field, mainly used by environment records /// Check to see if the Value has the field, mainly used by environment records.
#[inline]
pub fn has_field(&self, field: &str) -> bool { pub fn has_field(&self, field: &str) -> bool {
let _timer = BoaProfiler::global().start_event("Value::has_field", "value"); let _timer = BoaProfiler::global().start_event("Value::has_field", "value");
self.get_property(field).is_some() self.get_property(field).is_some()
@ -698,7 +680,7 @@ impl ValueData {
let val = val.into(); let val = val.into();
if let Self::Object(ref obj) = *self { if let Self::Object(ref obj) = *self {
if obj.borrow().kind == ObjectKind::Array { if obj.borrow().is_array() {
if let Ok(num) = field.to_string().parse::<usize>() { if let Ok(num) = field.to_string().parse::<usize>() {
if num > 0 { if num > 0 {
let len = i32::from(&self.get_field("length")); let len = i32::from(&self.get_field("length"));
@ -722,53 +704,50 @@ impl ValueData {
} }
/// Set the private field in the value /// Set the private field in the value
pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { pub fn set_internal_slot(&self, field: &str, value: Value) -> Value {
let _timer = BoaProfiler::global().start_event("Value::set_internal_slot", "exec"); let _timer = BoaProfiler::global().start_event("Value::set_internal_slot", "exec");
if let Self::Object(ref obj) = *self { if let Some(mut object) = self.as_object_mut() {
obj.borrow_mut() object
.internal_slots .internal_slots_mut()
.insert(field.to_string(), val.clone()); .insert(field.to_string(), value.clone());
} }
val value
} }
/// Set the kind of an object /// Set the kind of an object.
pub fn set_kind(&self, kind: ObjectKind) { #[inline]
pub fn set_data(&self, data: ObjectData) {
if let Self::Object(ref obj) = *self { if let Self::Object(ref obj) = *self {
(*obj.deref().borrow_mut()).kind = kind; (*obj.deref().borrow_mut()).data = data;
} }
} }
/// Set the property in the value /// Set the property in the value.
pub fn set_property(&self, field: String, prop: Property) -> Property { pub fn set_property<S>(&self, field: S, property: Property) -> Property
if let Self::Object(ref obj) = *self { where
obj.borrow_mut().properties.insert(field, prop.clone()); S: Into<String>,
{
if let Some(mut object) = self.as_object_mut() {
object
.properties_mut()
.insert(field.into(), property.clone());
} }
prop property
}
/// Set the property in the value
pub fn set_property_slice(&self, field: &str, prop: Property) -> Property {
self.set_property(field.to_string(), prop)
} }
/// Set internal state of an Object. Discards the previous state if it was set. /// Set internal state of an Object. Discards the previous state if it was set.
pub fn set_internal_state<T: Any + InternalState>(&self, state: T) { pub fn set_internal_state<T: Any + InternalState>(&self, state: T) {
if let Self::Object(ref obj) = *self { if let Some(mut object) = self.as_object_mut() {
obj.borrow_mut() object.state_mut().replace(InternalStateCell::new(state));
.state
.replace(InternalStateCell::new(state));
} }
} }
/// Consume the function and return a Value /// Consume the function and return a Value
pub fn from_func(native_func: Function) -> Value { pub fn from_func(function: Function) -> Value {
// Object with Kind set to function
let mut new_func = crate::builtins::object::Object::function();
// Get Length // Get Length
let length = native_func.params.len(); let length = function.params.len();
// Set [[Call]] internal slot // Object with Kind set to function
new_func.set_func(native_func); let new_func = Object::function(function);
// Wrap Object in GC'd Value // Wrap Object in GC'd Value
let new_func_val = Value::from(new_func); let new_func_val = Value::from(new_func);
// Set length to parameters // Set length to parameters

64
boa/src/builtins/value/tests.rs

@ -1,6 +1,9 @@
use super::*; use super::*;
use crate::{forward, forward_val, Interpreter, Realm}; use crate::{forward, forward_val, Interpreter, Realm};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[test] #[test]
fn check_is_object() { fn check_is_object() {
let val = Value::new_object(None); let val = Value::new_object(None);
@ -91,6 +94,67 @@ fn abstract_equality_comparison() {
assert_eq!(forward(&mut engine, "0 == NaN"), "false"); assert_eq!(forward(&mut engine, "0 == NaN"), "false");
assert_eq!(forward(&mut engine, "'foo' == NaN"), "false"); assert_eq!(forward(&mut engine, "'foo' == NaN"), "false");
assert_eq!(forward(&mut engine, "NaN == NaN"), "false"); assert_eq!(forward(&mut engine, "NaN == NaN"), "false");
assert_eq!(
forward(
&mut engine,
"Number.POSITIVE_INFINITY === Number.POSITIVE_INFINITY"
),
"true"
);
assert_eq!(
forward(
&mut engine,
"Number.NEGAVIVE_INFINITY === Number.NEGAVIVE_INFINITY"
),
"true"
);
}
/// Helper function to get the hash of a `Value`.
fn hash_value(value: &Value) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
#[test]
fn hash_undefined() {
let value1 = Value::undefined();
let value_clone = value1.clone();
assert_eq!(value1, value_clone);
let value2 = Value::undefined();
assert_eq!(value1, value2);
assert_eq!(hash_value(&value1), hash_value(&value_clone));
assert_eq!(hash_value(&value2), hash_value(&value_clone));
}
#[test]
fn hash_rational() {
let value1 = Value::rational(1.0);
let value2 = Value::rational(1.0);
assert_eq!(value1, value2);
assert_eq!(hash_value(&value1), hash_value(&value2));
let nan = Value::nan();
assert_eq!(nan, nan);
assert_eq!(hash_value(&nan), hash_value(&nan));
assert_ne!(hash_value(&nan), hash_value(&Value::rational(1.0)));
}
#[test]
fn hash_object() {
let object1 = Value::object(Object::default());
assert_eq!(object1, object1);
assert_eq!(object1, object1.clone());
let object2 = Value::object(Object::default());
assert_ne!(object1, object2);
assert_eq!(hash_value(&object1), hash_value(&object1.clone()));
assert_ne!(hash_value(&object1), hash_value(&object2));
} }
#[test] #[test]

8
boa/src/environment/lexical_environment.rs

@ -25,7 +25,7 @@ pub type Environment = Gc<GcCell<Box<dyn EnvironmentRecordTrait>>>;
/// Give each environment an easy way to declare its own type /// Give each environment an easy way to declare its own type
/// This helps with comparisons /// This helps with comparisons
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EnvironmentType { pub enum EnvironmentType {
Declarative, Declarative,
Function, Function,
@ -34,7 +34,7 @@ pub enum EnvironmentType {
} }
/// The scope of a given variable /// The scope of a given variable
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VariableScope { pub enum VariableScope {
/// The variable declaration is scoped to the current block (`let` and `const`) /// The variable declaration is scoped to the current block (`let` and `const`)
Block, Block,
@ -42,13 +42,13 @@ pub enum VariableScope {
Function, Function,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct LexicalEnvironment { pub struct LexicalEnvironment {
environment_stack: VecDeque<Environment>, environment_stack: VecDeque<Environment>,
} }
/// An error that occurred during lexing or compiling of the source input. /// An error that occurred during lexing or compiling of the source input.
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EnvironmentError { pub struct EnvironmentError {
details: String, details: String,
} }

42
boa/src/exec/exception.rs

@ -8,8 +8,8 @@ use crate::{
}; };
impl Interpreter { impl Interpreter {
/// Throws a `RangeError` with the specified message. /// Constructs a `RangeError` with the specified message.
pub fn throw_range_error<M>(&mut self, message: M) -> ResultValue pub fn construct_range_error<M>(&mut self, message: M) -> Value
where where
M: Into<String>, M: Into<String>,
{ {
@ -19,10 +19,19 @@ impl Interpreter {
vec![Const::from(message.into()).into()], vec![Const::from(message.into()).into()],
)) ))
.run(self) .run(self)
.expect_err("RangeError should always throw")
} }
/// Throws a `TypeError` with the specified message. /// Throws a `RangeError` with the specified message.
pub fn throw_type_error<M>(&mut self, message: M) -> ResultValue pub fn throw_range_error<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
Err(self.construct_range_error(message))
}
/// Constructs a `TypeError` with the specified message.
pub fn construct_type_error<M>(&mut self, message: M) -> Value
where where
M: Into<String>, M: Into<String>,
{ {
@ -32,5 +41,30 @@ impl Interpreter {
vec![Const::from(message.into()).into()], vec![Const::from(message.into()).into()],
)) ))
.run(self) .run(self)
.expect_err("TypeError should always throw")
}
/// Throws a `TypeError` with the specified message.
pub fn throw_type_error<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
Err(self.construct_type_error(message))
}
/// Constructs a `ReferenceError` with the specified message.
pub fn construct_reference_error<M>(&mut self, _message: M) -> Value
where
M: Into<String>,
{
unimplemented!("ReferenceError: is not implemented");
}
/// Throws a `ReferenceError` with the specified message.
pub fn throw_reference_error<M>(&mut self, message: M) -> ResultValue
where
M: Into<String>,
{
Err(self.construct_reference_error(message))
} }
} }

15
boa/src/exec/expression/mod.rs

@ -3,7 +3,7 @@
use super::{Executable, Interpreter, InterpreterState}; use super::{Executable, Interpreter, InterpreterState};
use crate::{ use crate::{
builtins::{ builtins::{
object::{INSTANCE_PROTOTYPE, PROTOTYPE}, object::{ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
value::{ResultValue, Type, Value, ValueData}, value::{ResultValue, Type, Value, ValueData},
}, },
syntax::ast::node::{Call, New, Node}, syntax::ast::node::{Call, New, Node},
@ -71,12 +71,13 @@ impl Executable for New {
this.set_internal_slot(INSTANCE_PROTOTYPE, func_object.get_field(PROTOTYPE)); this.set_internal_slot(INSTANCE_PROTOTYPE, func_object.get_field(PROTOTYPE));
match func_object.data() { match func_object.data() {
ValueData::Object(ref o) => o.clone().borrow_mut().func.as_ref().unwrap().construct( ValueData::Object(ref obj) => {
&mut func_object.clone(), let obj = (**obj).borrow();
&v_args, if let ObjectData::Function(ref func) = obj.data {
interpreter, return func.construct(func_object.clone(), &mut this, &v_args, interpreter);
&mut this, }
), interpreter.throw_type_error("not a constructor")
}
_ => Ok(Value::undefined()), _ => Ok(Value::undefined()),
} }
} }

197
boa/src/exec/mod.rs

@ -23,10 +23,7 @@ mod try_node;
use crate::{ use crate::{
builtins::{ builtins::{
function::{Function as FunctionObject, FunctionBody, ThisMode}, function::{Function as FunctionObject, FunctionBody, ThisMode},
object::{ object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
internal_methods_trait::ObjectInternalMethods, Object, ObjectKind, INSTANCE_PROTOTYPE,
PROTOTYPE,
},
property::Property, property::Property,
value::{ResultValue, Type, Value, ValueData}, value::{ResultValue, Type, Value, ValueData},
BigInt, Number, BigInt, Number,
@ -62,31 +59,48 @@ pub enum PreferredType {
/// A Javascript intepreter /// A Javascript intepreter
#[derive(Debug)] #[derive(Debug)]
pub struct Interpreter { pub struct Interpreter {
current_state: InterpreterState, /// the current state of the interpreter.
state: InterpreterState,
/// realm holds both the global object and the environment /// realm holds both the global object and the environment
pub realm: Realm, pub realm: Realm,
/// This is for generating an unique internal `Symbol` hash.
symbol_count: u32,
} }
impl Interpreter { impl Interpreter {
/// Creates a new interpreter. /// Creates a new interpreter.
pub fn new(realm: Realm) -> Self { pub fn new(realm: Realm) -> Self {
Self { Self {
current_state: InterpreterState::Executing, state: InterpreterState::Executing,
realm, realm,
symbol_count: 0,
} }
} }
/// Retrieves the `Realm` of this executor. /// Retrieves the `Realm` of this executor.
#[inline]
pub(crate) fn realm(&self) -> &Realm { pub(crate) fn realm(&self) -> &Realm {
&self.realm &self.realm
} }
/// Retrieves the `Realm` of this executor as a mutable reference. /// Retrieves the `Realm` of this executor as a mutable reference.
#[inline]
pub(crate) fn realm_mut(&mut self) -> &mut Realm { pub(crate) fn realm_mut(&mut self) -> &mut Realm {
&mut self.realm &mut self.realm
} }
/// Generates a new `Symbol` internal hash.
///
/// This currently is an incremented value.
#[inline]
pub(crate) fn generate_hash(&mut self) -> u32 {
let hash = self.symbol_count;
self.symbol_count += 1;
hash
}
/// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions
pub(crate) fn create_function<P, B>( pub(crate) fn create_function<P, B>(
&mut self, &mut self,
@ -127,8 +141,8 @@ impl Interpreter {
callable, callable,
); );
let mut new_func = Object::function(); let new_func = Object::function(func);
new_func.set_func(func);
let val = Value::from(new_func); let val = Value::from(new_func);
val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone());
val.set_field(PROTOTYPE, proto); val.set_field(PROTOTYPE, proto);
@ -147,8 +161,10 @@ impl Interpreter {
match *f.data() { match *f.data() {
ValueData::Object(ref obj) => { ValueData::Object(ref obj) => {
let obj = (**obj).borrow(); let obj = (**obj).borrow();
let func = obj.func.as_ref().expect("Expected function"); if let ObjectData::Function(ref func) = obj.data {
func.call(&mut f.clone(), arguments_list, self, this) return func.call(f.clone(), this, arguments_list, self);
}
self.throw_type_error("not a function")
} }
_ => Err(Value::undefined()), _ => Err(Value::undefined()),
} }
@ -165,8 +181,7 @@ impl Interpreter {
ValueData::Integer(integer) => Ok(integer.to_string()), ValueData::Integer(integer) => Ok(integer.to_string()),
ValueData::String(string) => Ok(string.clone()), ValueData::String(string) => Ok(string.clone()),
ValueData::Symbol(_) => { ValueData::Symbol(_) => {
self.throw_type_error("can't convert symbol to string")?; Err(self.construct_type_error("can't convert symbol to string"))
unreachable!();
} }
ValueData::BigInt(ref bigint) => Ok(bigint.to_string()), ValueData::BigInt(ref bigint) => Ok(bigint.to_string()),
ValueData::Object(_) => { ValueData::Object(_) => {
@ -180,13 +195,9 @@ impl Interpreter {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn to_bigint(&mut self, value: &Value) -> Result<BigInt, Value> { pub fn to_bigint(&mut self, value: &Value) -> Result<BigInt, Value> {
match value.data() { match value.data() {
ValueData::Null => { ValueData::Null => Err(self.construct_type_error("cannot convert null to a BigInt")),
self.throw_type_error("cannot convert null to a BigInt")?;
unreachable!();
}
ValueData::Undefined => { ValueData::Undefined => {
self.throw_type_error("cannot convert undefined to a BigInt")?; Err(self.construct_type_error("cannot convert undefined to a BigInt"))
unreachable!();
} }
ValueData::String(ref string) => Ok(BigInt::from_string(string, self)?), ValueData::String(ref string) => Ok(BigInt::from_string(string, self)?),
ValueData::Boolean(true) => Ok(BigInt::from(1)), ValueData::Boolean(true) => Ok(BigInt::from(1)),
@ -196,11 +207,10 @@ impl Interpreter {
if let Ok(bigint) = BigInt::try_from(*num) { if let Ok(bigint) = BigInt::try_from(*num) {
return Ok(bigint); return Ok(bigint);
} }
self.throw_type_error(format!( Err(self.construct_type_error(format!(
"The number {} cannot be converted to a BigInt because it is not an integer", "The number {} cannot be converted to a BigInt because it is not an integer",
num num
))?; )))
unreachable!();
} }
ValueData::BigInt(b) => Ok(b.clone()), ValueData::BigInt(b) => Ok(b.clone()),
ValueData::Object(_) => { ValueData::Object(_) => {
@ -208,8 +218,7 @@ impl Interpreter {
self.to_bigint(&primitive) self.to_bigint(&primitive)
} }
ValueData::Symbol(_) => { ValueData::Symbol(_) => {
self.throw_type_error("cannot convert Symbol to a BigInt")?; Err(self.construct_type_error("cannot convert Symbol to a BigInt"))
unreachable!();
} }
} }
} }
@ -226,13 +235,11 @@ impl Interpreter {
let integer_index = self.to_integer(value)?; let integer_index = self.to_integer(value)?;
if integer_index < 0 { if integer_index < 0 {
self.throw_range_error("Integer index must be >= 0")?; return Err(self.construct_range_error("Integer index must be >= 0"));
unreachable!();
} }
if integer_index > 2i64.pow(53) - 1 { if integer_index > 2i64.pow(53) - 1 {
self.throw_range_error("Integer index must be less than 2**(53) - 1")?; return Err(self.construct_range_error("Integer index must be less than 2**(53) - 1"));
unreachable!()
} }
Ok(integer_index as usize) Ok(integer_index as usize)
@ -257,24 +264,16 @@ impl Interpreter {
/// See: https://tc39.es/ecma262/#sec-tonumber /// See: https://tc39.es/ecma262/#sec-tonumber
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn to_number(&mut self, value: &Value) -> Result<f64, Value> { pub fn to_number(&mut self, value: &Value) -> Result<f64, Value> {
match *value.deref().borrow() { match *value.data() {
ValueData::Null => Ok(0.0), ValueData::Null => Ok(0.0),
ValueData::Undefined => Ok(f64::NAN), ValueData::Undefined => Ok(f64::NAN),
ValueData::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), ValueData::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
ValueData::String(ref string) => match string.parse::<f64>() { // TODO: this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
Ok(number) => Ok(number), ValueData::String(ref string) => Ok(string.parse().unwrap_or(f64::NAN)),
Err(_) => Ok(0.0),
}, // this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
ValueData::Rational(number) => Ok(number), ValueData::Rational(number) => Ok(number),
ValueData::Integer(integer) => Ok(f64::from(integer)), ValueData::Integer(integer) => Ok(f64::from(integer)),
ValueData::Symbol(_) => { ValueData::Symbol(_) => Err(self.construct_type_error("argument must not be a symbol")),
self.throw_type_error("argument must not be a symbol")?; ValueData::BigInt(_) => Err(self.construct_type_error("argument must not be a bigint")),
unreachable!()
}
ValueData::BigInt(_) => {
self.throw_type_error("argument must not be a bigint")?;
unreachable!()
}
ValueData::Object(_) => { ValueData::Object(_) => {
let prim_value = self.to_primitive(&mut (value.clone()), PreferredType::Number); let prim_value = self.to_primitive(&mut (value.clone()), PreferredType::Number);
self.to_number(&prim_value) self.to_number(&prim_value)
@ -282,13 +281,39 @@ impl Interpreter {
} }
} }
/// It returns value converted to a numeric value of type Number or BigInt.
///
/// See: https://tc39.es/ecma262/#sec-tonumeric
#[allow(clippy::wrong_self_convention)]
pub fn to_numeric(&mut self, value: &Value) -> ResultValue {
let primitive = self.to_primitive(&mut value.clone(), PreferredType::Number);
if primitive.is_bigint() {
return Ok(primitive);
}
Ok(Value::from(self.to_number(&primitive)?))
}
/// This is a more specialized version of `to_numeric`.
///
/// It returns value converted to a numeric value of type `Number`.
///
/// See: https://tc39.es/ecma262/#sec-tonumeric
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_numeric_number(&mut self, value: &Value) -> Result<f64, Value> {
let primitive = self.to_primitive(&mut value.clone(), PreferredType::Number);
if let Some(ref bigint) = primitive.as_bigint() {
return Ok(bigint.to_f64());
}
Ok(self.to_number(&primitive)?)
}
/// Converts an array object into a rust vector of values. /// Converts an array object into a rust vector of values.
/// ///
/// This is useful for the spread operator, for any other object an `Err` is returned /// This is useful for the spread operator, for any other object an `Err` is returned
pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result<Vec<Value>, ()> { pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result<Vec<Value>, ()> {
if let ValueData::Object(ref x) = *value.deref().borrow() { if let ValueData::Object(ref x) = *value.deref().borrow() {
// Check if object is array // Check if object is array
if x.deref().borrow().kind == ObjectKind::Array { if let ObjectData::Array = x.deref().borrow().data {
let length: i32 = self.value_to_rust_number(&value.get_field("length")) as i32; let length: i32 = self.value_to_rust_number(&value.get_field("length")) as i32;
let values: Vec<Value> = (0..length) let values: Vec<Value> = (0..length)
.map(|idx| value.get_field(idx.to_string())) .map(|idx| value.get_field(idx.to_string()))
@ -389,58 +414,76 @@ impl Interpreter {
pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue { pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue {
match value.data() { match value.data() {
ValueData::Undefined | ValueData::Null => Err(Value::undefined()), ValueData::Undefined | ValueData::Null => Err(Value::undefined()),
ValueData::Integer(_) => { ValueData::Boolean(boolean) => {
let proto = self let proto = self
.realm .realm
.environment .environment
.get_binding_value("Number") .get_binding_value("Boolean")
.get_field(PROTOTYPE); .get_field(PROTOTYPE);
let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number);
number_obj.set_internal_slot("NumberData", value.clone()); Ok(Value::new_object_from_prototype(
Ok(number_obj) proto,
ObjectData::Boolean(*boolean),
))
} }
ValueData::Boolean(_) => { ValueData::Integer(integer) => {
let proto = self let proto = self
.realm .realm
.environment .environment
.get_binding_value("Boolean") .get_binding_value("Number")
.get_field(PROTOTYPE); .get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
let bool_obj = Value::new_object_from_prototype(proto, ObjectKind::Boolean); proto,
bool_obj.set_internal_slot("BooleanData", value.clone()); ObjectData::Number(f64::from(*integer)),
Ok(bool_obj) ))
} }
ValueData::Rational(_) => { ValueData::Rational(rational) => {
let proto = self let proto = self
.realm .realm
.environment .environment
.get_binding_value("Number") .get_binding_value("Number")
.get_field(PROTOTYPE); .get_field(PROTOTYPE);
let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number);
number_obj.set_internal_slot("NumberData", value.clone()); Ok(Value::new_object_from_prototype(
Ok(number_obj) proto,
ObjectData::Number(*rational),
))
} }
ValueData::String(_) => { ValueData::String(ref string) => {
let proto = self let proto = self
.realm .realm
.environment .environment
.get_binding_value("String") .get_binding_value("String")
.get_field(PROTOTYPE); .get_field(PROTOTYPE);
let string_obj = Value::new_object_from_prototype(proto, ObjectKind::String);
string_obj.set_internal_slot("StringData", value.clone()); Ok(Value::new_object_from_prototype(
Ok(string_obj) proto,
ObjectData::String(string.clone()),
))
}
ValueData::Symbol(ref symbol) => {
let proto = self
.realm
.environment
.get_binding_value("Symbol")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Symbol(symbol.clone()),
))
} }
ValueData::Object(_) | ValueData::Symbol(_) => Ok(value.clone()), ValueData::BigInt(ref bigint) => {
ValueData::BigInt(_) => {
let proto = self let proto = self
.realm .realm
.environment .environment
.get_binding_value("BigInt") .get_binding_value("BigInt")
.get_field(PROTOTYPE); .get_field(PROTOTYPE);
let bigint_obj = Value::new_object_from_prototype(proto, ObjectKind::BigInt); let bigint_obj =
bigint_obj.set_internal_slot("BigIntData", value.clone()); Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone()));
Ok(bigint_obj) Ok(bigint_obj)
} }
ValueData::Object(_) => Ok(value.clone()),
} }
} }
@ -493,12 +536,34 @@ impl Interpreter {
} }
} }
#[inline]
pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) { pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) {
self.current_state = new_state self.state = new_state
} }
#[inline]
pub(crate) fn get_current_state(&self) -> &InterpreterState { pub(crate) fn get_current_state(&self) -> &InterpreterState {
&self.current_state &self.state
}
/// Check if the `Value` can be converted to an `Object`
///
/// The abstract operation `RequireObjectCoercible` takes argument argument.
/// It throws an error if argument is a value that cannot be converted to an Object using `ToObject`.
/// It is defined by [Table 15][table]
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [table]: https://tc39.es/ecma262/#table-14
/// [spec]: https://tc39.es/ecma262/#sec-requireobjectcoercible
#[inline]
pub fn require_object_coercible<'a>(&mut self, value: &'a Value) -> Result<&'a Value, Value> {
if value.is_null_or_undefined() {
Err(self.construct_type_error("cannot convert null or undefined to Object"))
} else {
Ok(value)
}
} }
} }

Loading…
Cancel
Save