From 357c7d07f7d5b106281e271e48805333daeeccbd Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Fri, 3 Jul 2020 00:08:09 +0200 Subject: [PATCH] Fix all `Value` operations and add unsigned shift right (#520) --- boa/src/builtins/array/mod.rs | 14 +- boa/src/builtins/boolean/tests.rs | 8 +- boa/src/builtins/number/conversions.rs | 86 +++++ boa/src/builtins/number/mod.rs | 65 +++- boa/src/builtins/value/conversions.rs | 2 +- boa/src/builtins/value/display.rs | 2 +- boa/src/builtins/value/mod.rs | 33 +- boa/src/builtins/value/operations.rs | 432 ++++++++++++++++++++----- boa/src/builtins/value/tests.rs | 140 +++++++- boa/src/exec/conditional/mod.rs | 2 +- boa/src/exec/iteration/mod.rs | 6 +- boa/src/exec/mod.rs | 67 +++- boa/src/exec/operator/mod.rs | 93 +++--- boa/src/exec/tests.rs | 183 ++++++++++- 14 files changed, 924 insertions(+), 209 deletions(-) create mode 100644 boa/src/builtins/number/conversions.rs diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 37c7d4740b..0e9bf0aa41 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -514,8 +514,8 @@ impl Array { while i < len { let element = this.get_field(i.to_string()); let arguments = [element, Value::from(i), this.clone()]; - let result = interpreter.call(callback, &this_arg, &arguments)?.is_true(); - if !result { + let result = interpreter.call(callback, &this_arg, &arguments)?; + if !result.to_boolean() { return Ok(Value::from(false)); } len = min(max_len, i32::from(&this.get_field("length"))); @@ -695,7 +695,7 @@ impl Array { let element = this.get_field(i.to_string()); let arguments = [element.clone(), Value::from(i), this.clone()]; let result = interpreter.call(callback, &this_arg, &arguments)?; - if result.is_true() { + if result.to_boolean() { return Ok(element); } } @@ -737,7 +737,7 @@ impl Array { let result = interpreter.call(predicate_arg, &this_arg, &arguments)?; - if result.is_true() { + if result.to_boolean() { return Ok(Value::rational(f64::from(i))); } } @@ -902,7 +902,7 @@ impl Array { .call(&callback, &this_val, &args) .unwrap_or_else(|_| Value::undefined()); - if callback_result.is_true() { + if callback_result.to_boolean() { Some(element) } else { None @@ -946,8 +946,8 @@ impl Array { while i < len { let element = this.get_field(i.to_string()); let arguments = [element, Value::from(i), this.clone()]; - let result = interpreter.call(callback, &this_arg, &arguments)?.is_true(); - if result { + let result = interpreter.call(callback, &this_arg, &arguments)?; + if result.to_boolean() { return Ok(Value::from(true)); } // the length of the array must be updated because the callback can mutate it. diff --git a/boa/src/builtins/boolean/tests.rs b/boa/src/builtins/boolean/tests.rs index b036948726..6c5cf22c38 100644 --- a/boa/src/builtins/boolean/tests.rs +++ b/boa/src/builtins/boolean/tests.rs @@ -50,10 +50,10 @@ fn constructor_gives_true_instance() { assert_eq!(true_bool.is_object(), true); // Values should all be truthy - assert_eq!(true_val.is_true(), true); - assert_eq!(true_num.is_true(), true); - assert_eq!(true_string.is_true(), true); - assert_eq!(true_bool.is_true(), true); + assert_eq!(true_val.to_boolean(), true); + assert_eq!(true_num.to_boolean(), true); + assert_eq!(true_string.to_boolean(), true); + assert_eq!(true_bool.to_boolean(), true); } #[test] diff --git a/boa/src/builtins/number/conversions.rs b/boa/src/builtins/number/conversions.rs new file mode 100644 index 0000000000..c05c37c1e6 --- /dev/null +++ b/boa/src/builtins/number/conversions.rs @@ -0,0 +1,86 @@ +/// Converts a 64-bit floating point number to an `i32` according to the [`ToInt32`][ToInt32] algorithm. +/// +/// [ToInt32]: https://tc39.es/ecma262/#sec-toint32 +#[inline] +#[allow(clippy::float_cmp)] +pub(crate) fn f64_to_int32(number: f64) -> i32 { + const SIGN_MASK: u64 = 0x8000000000000000; + const EXPONENT_MASK: u64 = 0x7FF0000000000000; + const SIGNIFICAND_MASK: u64 = 0x000FFFFFFFFFFFFF; + const HIDDEN_BIT: u64 = 0x0010000000000000; + const PHYSICAL_SIGNIFICAND_SIZE: i32 = 52; // Excludes the hidden bit. + const SIGNIFICAND_SIZE: i32 = 53; + + const EXPONENT_BIAS: i32 = 0x3FF + PHYSICAL_SIGNIFICAND_SIZE; + const DENORMAL_EXPONENT: i32 = -EXPONENT_BIAS + 1; + + #[inline] + fn is_denormal(number: f64) -> bool { + (number.to_bits() & EXPONENT_MASK) == 0 + } + + #[inline] + fn exponent(number: f64) -> i32 { + if is_denormal(number) { + return DENORMAL_EXPONENT; + } + + let d64 = number.to_bits(); + let biased_e = ((d64 & EXPONENT_MASK) >> PHYSICAL_SIGNIFICAND_SIZE) as i32; + + biased_e - EXPONENT_BIAS + } + + #[inline] + fn significand(number: f64) -> u64 { + let d64 = number.to_bits(); + let significand = d64 & SIGNIFICAND_MASK; + + if !is_denormal(number) { + significand + HIDDEN_BIT + } else { + significand + } + } + + #[inline] + fn sign(number: f64) -> i64 { + if (number.to_bits() & SIGN_MASK) == 0 { + 1 + } else { + -1 + } + } + + if number.is_finite() && number <= f64::from(i32::MAX) && number >= f64::from(i32::MIN) { + let i = number as i32; + if f64::from(i) == number { + return i; + } + } + + let exponent = exponent(number); + let bits = if exponent < 0 { + if exponent <= -SIGNIFICAND_SIZE { + return 0; + } + + significand(number) >> -exponent + } else { + if exponent > 31 { + return 0; + } + + (significand(number) << exponent) & 0xFFFFFFFF + }; + + (sign(number) * (bits as i64)) as i32 +} + +/// Converts a 64-bit floating point number to an `u32` according to the [`ToUint32`][ToUint32] algorithm. +/// +// [ToInt32]: https://tc39.es/ecma262/#sec-touint32 +#[inline] +pub(crate) fn f64_to_uint32(number: f64) -> u32 { + f64_to_int32(number) as u32 +} diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 8252de5ca9..6422bc244d 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -13,9 +13,6 @@ //! [spec]: https://tc39.es/ecma262/#sec-number-object //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number -#[cfg(test)] -mod tests; - use super::{ function::{make_builtin_fn, make_constructor_fn}, object::ObjectData, @@ -27,6 +24,13 @@ use crate::{ }; use num_traits::float::FloatCore; +mod conversions; + +pub(crate) use conversions::{f64_to_int32, f64_to_uint32}; + +#[cfg(test)] +mod tests; + const BUF_SIZE: usize = 2200; /// `Number` implementation. @@ -46,6 +50,52 @@ impl Number { /// The amount of arguments this function object takes. pub(crate) const LENGTH: usize = 1; + /// The `Number.MAX_SAFE_INTEGER` constant represents the maximum safe integer in JavaScript (`2^53 - 1`). + /// + /// /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.max_safe_integer + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER + pub(crate) const MAX_SAFE_INTEGER: f64 = 9_007_199_254_740_991_f64; + + /// The `Number.MIN_SAFE_INTEGER` constant represents the minimum safe integer in JavaScript (`-(253 - 1)`). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.min_safe_integer + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER + pub(crate) const MIN_SAFE_INTEGER: f64 = -9_007_199_254_740_991_f64; + + /// The `Number.MAX_VALUE` property represents the maximum numeric value representable in JavaScript. + /// + /// The `MAX_VALUE` property has a value of approximately `1.79E+308`, or `2^1024`. + /// Values larger than `MAX_VALUE` are represented as `Infinity`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.max_value + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_VALUE + pub(crate) const MAX_VALUE: f64 = f64::MAX; + + /// The `Number.MIN_VALUE` property represents the smallest positive numeric value representable in JavaScript. + /// + /// The `MIN_VALUE` property is the number closest to `0`, not the most negative number, that JavaScript can represent. + /// It has a value of approximately `5e-324`. Values smaller than `MIN_VALUE` ("underflow values") are converted to `0`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.min_value + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_VALUE + pub(crate) const MIN_VALUE: f64 = f64::MIN; + /// This function returns a `Result` of the number `Value`. /// /// If the `Value` is a `Number` primitive of `Number` object the number is returned. @@ -53,7 +103,6 @@ impl Number { /// /// 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 { @@ -539,10 +588,10 @@ impl Number { { let mut properties = number.as_object_mut().expect("'Number' object"); properties.insert_field("EPSILON", Value::from(f64::EPSILON)); - properties.insert_field("MAX_SAFE_INTEGER", Value::from(9_007_199_254_740_991_f64)); - properties.insert_field("MIN_SAFE_INTEGER", Value::from(-9_007_199_254_740_991_f64)); - properties.insert_field("MAX_VALUE", Value::from(f64::MAX)); - properties.insert_field("MIN_VALUE", Value::from(f64::MIN)); + properties.insert_field("MAX_SAFE_INTEGER", Value::from(Self::MAX_SAFE_INTEGER)); + properties.insert_field("MIN_SAFE_INTEGER", Value::from(Self::MIN_SAFE_INTEGER)); + properties.insert_field("MAX_VALUE", Value::from(Self::MAX_VALUE)); + properties.insert_field("MIN_VALUE", Value::from(Self::MIN_VALUE)); 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)); diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 72937a1b11..e2c8659aae 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -120,7 +120,7 @@ impl From for Value { impl From<&Value> for bool { fn from(value: &Value) -> Self { - value.is_true() + value.to_boolean() } } diff --git a/boa/src/builtins/value/display.rs b/boa/src/builtins/value/display.rs index 93d858295f..5637c04abb 100644 --- a/boa/src/builtins/value/display.rs +++ b/boa/src/builtins/value/display.rs @@ -131,7 +131,7 @@ pub(crate) fn display_obj(v: &Value, print_internals: bool) -> String { ) -> String { if let Value::Object(ref v) = *data { // The in-memory address of the current object - let addr = address_of(v.borrow().deref()); + let addr = address_of(v.as_ref()); // We need not continue if this object has already been // printed up the current chain diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index b317af06c7..8d5447dfe1 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -28,7 +28,6 @@ use std::{ convert::TryFrom, f64::NAN, fmt::{self, Display}, - ops::{Add, BitAnd, BitOr, BitXor, Deref, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub}, str::FromStr, }; @@ -159,14 +158,6 @@ impl Value { Self::Symbol(RcSymbol::from(symbol)) } - /// Helper function to convert the `Value` to a number and compute its power. - pub fn as_num_to_power(&self, other: Self) -> Self { - match (self, other) { - (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(a.as_inner().clone().pow(b)), - (a, b) => Self::rational(a.to_number().powf(b.to_number())), - } - } - /// Returns a new empty object pub fn new_object(global: Option<&Value>) -> Self { let _timer = BoaProfiler::global().start_event("new_object", "value"); @@ -413,21 +404,6 @@ impl Value { } } - /// Returns true if the value is true. - /// - /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) - pub fn is_true(&self) -> bool { - match *self { - 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::Boolean(v) => v, - Self::BigInt(ref n) if *n.as_inner() != 0 => true, - _ => false, - } - } - /// Converts the value into a 64-bit floating point number pub fn to_number(&self) -> f64 { match *self { @@ -473,7 +449,12 @@ impl Value { } } - /// Creates a new boolean value from the input + /// Converts the value to a `bool` type. + /// + /// More information: + /// - [ECMAScript][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-toboolean pub fn to_boolean(&self) -> bool { match *self { Self::Undefined | Self::Null => false, @@ -713,7 +694,7 @@ impl Value { #[inline] pub fn set_data(&self, data: ObjectData) { if let Self::Object(ref obj) = *self { - (*obj.deref().borrow_mut()).data = data; + obj.borrow_mut().data = data; } } diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index 95c6c2e1c6..7f24323429 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -1,132 +1,389 @@ use super::*; +use crate::builtins::number::{f64_to_int32, f64_to_uint32}; +use crate::exec::PreferredType; -impl Add for Value { - type Output = Self; - fn add(self, other: Self) -> Self { - match (self, other) { - (Self::String(ref s), ref o) => { - Self::string(format!("{}{}", s.clone(), &o.to_string())) - } +impl Value { + #[inline] + pub fn add(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::rational(f64::from(*x) + f64::from(*y)), + (Self::Rational(x), Self::Rational(y)) => Self::rational(x + y), + (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) + y), + (Self::Rational(x), Self::Integer(y)) => Self::rational(x + f64::from(*y)), + + (Self::String(ref x), ref y) => Self::string(format!("{}{}", x, ctx.to_string(y)?)), + (ref x, Self::String(ref y)) => Self::string(format!("{}{}", ctx.to_string(x)?, y)), (Self::BigInt(ref n1), Self::BigInt(ref n2)) => { Self::bigint(n1.as_inner().clone() + n2.as_inner().clone()) } - (ref s, Self::String(ref o)) => Self::string(format!("{}{}", s.to_string(), o)), - (ref s, ref o) => Self::rational(s.to_number() + o.to_number()), - } + + // Slow path: + (_, _) => match ( + ctx.to_primitive(self, PreferredType::Default)?, + ctx.to_primitive(other, PreferredType::Default)?, + ) { + (Self::String(ref x), ref y) => Self::string(format!("{}{}", x, ctx.to_string(y)?)), + (ref x, Self::String(ref y)) => Self::string(format!("{}{}", ctx.to_string(x)?, y)), + (x, y) => match (ctx.to_numeric(&x)?, ctx.to_numeric(&y)?) { + (Self::Rational(x), Self::Rational(y)) => Self::rational(x + y), + (Self::BigInt(ref n1), Self::BigInt(ref n2)) => { + Self::bigint(n1.as_inner().clone() + n2.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ) + } + }, + }, + }) } -} -impl Sub for Value { - type Output = Self; - fn sub(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn sub(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::rational(f64::from(*x) - f64::from(*y)), + (Self::Rational(x), Self::Rational(y)) => Self::rational(x - y), + (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) - y), + (Self::Rational(x), Self::Integer(y)) => Self::rational(x - f64::from(*y)), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() - b.as_inner().clone()) } - (a, b) => Self::rational(a.to_number() - b.to_number()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => Self::rational(a - b), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone() - b.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl Mul for Value { - type Output = Self; - fn mul(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn mul(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::rational(f64::from(*x) * f64::from(*y)), + (Self::Rational(x), Self::Rational(y)) => Self::rational(x * y), + (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) * y), + (Self::Rational(x), Self::Integer(y)) => Self::rational(x * f64::from(*y)), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() * b.as_inner().clone()) } - (a, b) => Self::rational(a.to_number() * b.to_number()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => Self::rational(a * b), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone() * b.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl Div for Value { - type Output = Self; - fn div(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn div(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::rational(f64::from(*x) / f64::from(*y)), + (Self::Rational(x), Self::Rational(y)) => Self::rational(x / y), + (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) / y), + (Self::Rational(x), Self::Integer(y)) => Self::rational(x / f64::from(*y)), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() / b.as_inner().clone()) } - (a, b) => Self::rational(a.to_number() / b.to_number()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => Self::rational(a / b), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone() / b.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl Rem for Value { - type Output = Self; - fn rem(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn rem(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::integer(x % *y), + (Self::Rational(x), Self::Rational(y)) => Self::rational(x % y), + (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) % y), + (Self::Rational(x), Self::Integer(y)) => Self::rational(x % f64::from(*y)), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() % b.as_inner().clone()) } - (a, b) => Self::rational(a.to_number() % b.to_number()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => Self::rational(a % b), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone() % b.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl BitAnd for Value { - type Output = Self; - fn bitand(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn pow(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::rational(f64::from(*x).powi(*y)), + (Self::Rational(x), Self::Rational(y)) => Self::rational(x.powf(*y)), + (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x).powf(*y)), + (Self::Rational(x), Self::Integer(y)) => Self::rational(x.powi(*y)), + + (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(a.as_inner().clone().pow(b)), + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => Self::rational(a.powf(b)), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone().pow(b)) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) + } + + #[inline] + pub fn bitand(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::integer(x & y), + (Self::Rational(x), Self::Rational(y)) => { + Self::integer(f64_to_int32(*x) & f64_to_int32(*y)) + } + (Self::Integer(x), Self::Rational(y)) => Self::integer(x & f64_to_int32(*y)), + (Self::Rational(x), Self::Integer(y)) => Self::integer(f64_to_int32(*x) & y), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() & b.as_inner().clone()) } - (a, b) => Self::integer(a.to_integer() & b.to_integer()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => { + Self::integer(f64_to_int32(a) & f64_to_int32(b)) + } + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone() & b.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl BitOr for Value { - type Output = Self; - fn bitor(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn bitor(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::integer(x | y), + (Self::Rational(x), Self::Rational(y)) => { + Self::integer(f64_to_int32(*x) | f64_to_int32(*y)) + } + (Self::Integer(x), Self::Rational(y)) => Self::integer(x | f64_to_int32(*y)), + (Self::Rational(x), Self::Integer(y)) => Self::integer(f64_to_int32(*x) | y), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() | b.as_inner().clone()) } - (a, b) => Self::integer(a.to_integer() | b.to_integer()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => { + Self::integer(f64_to_int32(a) | f64_to_int32(b)) + } + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone() | b.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl BitXor for Value { - type Output = Self; - fn bitxor(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn bitxor(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::integer(x ^ y), + (Self::Rational(x), Self::Rational(y)) => { + Self::integer(f64_to_int32(*x) ^ f64_to_int32(*y)) + } + (Self::Integer(x), Self::Rational(y)) => Self::integer(x ^ f64_to_int32(*y)), + (Self::Rational(x), Self::Integer(y)) => Self::integer(f64_to_int32(*x) ^ y), + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() ^ b.as_inner().clone()) } - (a, b) => Self::integer(a.to_integer() ^ b.to_integer()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(a), Self::Rational(b)) => { + Self::integer(f64_to_int32(a) ^ f64_to_int32(b)) + } + (Self::BigInt(ref a), Self::BigInt(ref b)) => { + Self::bigint(a.as_inner().clone() ^ b.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl Shl for Value { - type Output = Self; - fn shl(self, other: Self) -> Self { - match (self, other) { + #[inline] + pub fn shl(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::integer(x.wrapping_shl(*y as u32)), + (Self::Rational(x), Self::Rational(y)) => { + Self::integer(f64_to_int32(*x).wrapping_shl(f64_to_uint32(*y))) + } + (Self::Integer(x), Self::Rational(y)) => { + Self::integer(x.wrapping_shl(f64_to_uint32(*y))) + } + (Self::Rational(x), Self::Integer(y)) => { + Self::integer(f64_to_int32(*x).wrapping_shl(*y as u32)) + } + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() << b.as_inner().clone()) } - (a, b) => Self::integer(a.to_integer() << b.to_integer()), - } + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(x), Self::Rational(y)) => { + Self::integer(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) + } + (Self::BigInt(ref x), Self::BigInt(ref y)) => { + Self::bigint(x.as_inner().clone() << y.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl Shr for Value { - type Output = Self; - fn shr(self, other: Self) -> Self { - match (self, other) { + + #[inline] + pub fn shr(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => Self::integer(x.wrapping_shr(*y as u32)), + (Self::Rational(x), Self::Rational(y)) => { + Self::integer(f64_to_int32(*x).wrapping_shr(f64_to_uint32(*y))) + } + (Self::Integer(x), Self::Rational(y)) => { + Self::integer(x.wrapping_shr(f64_to_uint32(*y))) + } + (Self::Rational(x), Self::Integer(y)) => { + Self::integer(f64_to_int32(*x).wrapping_shr(*y as u32)) + } + (Self::BigInt(ref a), Self::BigInt(ref b)) => { Self::bigint(a.as_inner().clone() >> b.as_inner().clone()) } - (a, b) => Self::integer(a.to_integer() >> b.to_integer()), - } - } -} -impl Not for Value { - type Output = Self; - fn not(self) -> Self { - Self::boolean(!self.is_true()) + + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(x), Self::Rational(y)) => { + Self::integer(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) + } + (Self::BigInt(ref x), Self::BigInt(ref y)) => { + Self::bigint(x.as_inner().clone() >> y.as_inner().clone()) + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) } -} -impl Neg for Value { - type Output = Self; + #[inline] + pub fn ushr(&self, other: &Self, ctx: &mut Interpreter) -> ResultValue { + Ok(match (self, other) { + // Fast path: + (Self::Integer(x), Self::Integer(y)) => { + Self::rational((*x as u32).wrapping_shr(*y as u32)) + } + (Self::Rational(x), Self::Rational(y)) => { + Self::rational(f64_to_uint32(*x).wrapping_shr(f64_to_uint32(*y))) + } + (Self::Integer(x), Self::Rational(y)) => { + Self::rational((*x as u32).wrapping_shr(f64_to_uint32(*y))) + } + (Self::Rational(x), Self::Integer(y)) => { + Self::rational(f64_to_uint32(*x).wrapping_shr(*y as u32)) + } - fn neg(self) -> Self::Output { - match self { + // Slow path: + (_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { + (Self::Rational(x), Self::Rational(y)) => { + Self::rational(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y))) + } + (Self::BigInt(_), Self::BigInt(_)) => { + return ctx + .throw_type_error("BigInts have no unsigned right shift, use >> instead"); + } + (_, _) => { + return ctx.throw_type_error( + "cannot mix BigInt and other types, use explicit conversions", + ); + } + }, + }) + } + + #[inline] + pub fn neg(&self, _: &mut Interpreter) -> ResultValue { + Ok(match *self { Self::Object(_) | Self::Symbol(_) | Self::Undefined => Self::rational(NAN), Self::String(ref str) => Self::rational(match f64::from_str(str) { Ok(num) => -num, @@ -137,6 +394,11 @@ impl Neg for Value { Self::Boolean(true) => Self::integer(1), Self::Boolean(false) | Self::Null => Self::integer(0), Self::BigInt(ref num) => Self::bigint(-num.as_inner().clone()), - } + }) + } + + #[inline] + pub fn not(&self, _: &mut Interpreter) -> ResultValue { + Ok(Self::boolean(!self.to_boolean())) } } diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs index c3bd570ca2..16323fd0e8 100644 --- a/boa/src/builtins/value/tests.rs +++ b/boa/src/builtins/value/tests.rs @@ -5,13 +5,13 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; #[test] -fn check_is_object() { +fn is_object() { let val = Value::new_object(None); assert_eq!(val.is_object(), true); } #[test] -fn check_string_to_value() { +fn string_to_value() { let s = String::from("Hello"); let v = Value::from(s); assert_eq!(v.is_string(), true); @@ -19,14 +19,14 @@ fn check_string_to_value() { } #[test] -fn check_undefined() { +fn undefined() { let u = Value::Undefined; assert_eq!(u.get_type(), Type::Undefined); assert_eq!(u.to_string(), "undefined"); } #[test] -fn check_get_set_field() { +fn get_set_field() { let obj = Value::new_object(None); // Create string and convert it to a Value let s = Value::from("bar"); @@ -35,20 +35,20 @@ fn check_get_set_field() { } #[test] -fn check_integer_is_true() { - assert_eq!(Value::from(1).is_true(), true); - assert_eq!(Value::from(0).is_true(), false); - assert_eq!(Value::from(-1).is_true(), true); +fn integer_is_true() { + assert_eq!(Value::from(1).to_boolean(), true); + assert_eq!(Value::from(0).to_boolean(), false); + assert_eq!(Value::from(-1).to_boolean(), true); } #[test] -fn check_number_is_true() { - assert_eq!(Value::from(1.0).is_true(), true); - assert_eq!(Value::from(0.1).is_true(), true); - assert_eq!(Value::from(0.0).is_true(), false); - assert_eq!(Value::from(-0.0).is_true(), false); - assert_eq!(Value::from(-1.0).is_true(), true); - assert_eq!(Value::from(NAN).is_true(), false); +fn number_is_true() { + assert_eq!(Value::from(1.0).to_boolean(), true); + assert_eq!(Value::from(0.1).to_boolean(), true); + assert_eq!(Value::from(0.0).to_boolean(), false); + assert_eq!(Value::from(-0.0).to_boolean(), false); + assert_eq!(Value::from(-1.0).to_boolean(), true); + assert_eq!(Value::from(NAN).to_boolean(), false); } // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness @@ -213,3 +213,113 @@ fn get_types() { Type::Symbol ); } + +#[test] +fn add_number_and_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "1 + 2").unwrap(); + let value = engine.to_int32(&value).unwrap(); + assert_eq!(value, 3); +} + +#[test] +fn add_number_and_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "1 + \" + 2 = 3\"").unwrap(); + let value = engine.to_string(&value).unwrap(); + assert_eq!(value, "1 + 2 = 3"); +} + +#[test] +fn add_string_and_string() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "\"Hello\" + \", world\"").unwrap(); + let value = engine.to_string(&value).unwrap(); + assert_eq!(value, "Hello, world"); +} + +#[test] +fn add_number_object_and_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "new Number(10) + 6").unwrap(); + let value = engine.to_int32(&value).unwrap(); + assert_eq!(value, 16); +} + +#[test] +fn add_number_object_and_string_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "new Number(10) + new String(\"0\")").unwrap(); + let value = engine.to_string(&value).unwrap(); + assert_eq!(value, "100"); +} + +#[test] +fn sub_number_and_number() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "1 - 999").unwrap(); + let value = engine.to_int32(&value).unwrap(); + assert_eq!(value, -998); +} + +#[test] +fn sub_number_object_and_number_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "new Number(1) - new Number(999)").unwrap(); + let value = engine.to_int32(&value).unwrap(); + assert_eq!(value, -998); +} + +#[test] +fn sub_string_and_number_object() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "'Hello' - new Number(999)").unwrap(); + let value = engine.to_number(&value).unwrap(); + assert!(value.is_nan()); +} + +#[test] +fn bitand_integer_and_integer() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "0xFFFF & 0xFF").unwrap(); + let value = engine.to_int32(&value).unwrap(); + assert_eq!(value, 255); +} + +#[test] +fn bitand_integer_and_rational() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "0xFFFF & 255.5").unwrap(); + let value = engine.to_int32(&value).unwrap(); + assert_eq!(value, 255); +} + +#[test] +fn bitand_rational_and_rational() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + let value = forward_val(&mut engine, "255.772 & 255.5").unwrap(); + let value = engine.to_int32(&value).unwrap(); + assert_eq!(value, 255); +} diff --git a/boa/src/exec/conditional/mod.rs b/boa/src/exec/conditional/mod.rs index 1649ef7768..7279fd8792 100644 --- a/boa/src/exec/conditional/mod.rs +++ b/boa/src/exec/conditional/mod.rs @@ -7,7 +7,7 @@ use std::borrow::Borrow; impl Executable for If { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { - Ok(if self.cond().run(interpreter)?.borrow().is_true() { + Ok(if self.cond().run(interpreter)?.borrow().to_boolean() { self.body().run(interpreter)? } else if let Some(ref else_e) = self.else_node() { else_e.run(interpreter)? diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs index d0acad30dd..8835207ec9 100644 --- a/boa/src/exec/iteration/mod.rs +++ b/boa/src/exec/iteration/mod.rs @@ -29,7 +29,7 @@ impl Executable for ForLoop { while self .condition() - .map(|cond| cond.run(interpreter).map(|v| v.is_true())) + .map(|cond| cond.run(interpreter).map(|v| v.to_boolean())) .transpose()? .unwrap_or(true) { @@ -66,7 +66,7 @@ impl Executable for ForLoop { impl Executable for WhileLoop { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { let mut result = Value::undefined(); - while self.cond().run(interpreter)?.borrow().is_true() { + while self.cond().run(interpreter)?.borrow().to_boolean() { result = self.expr().run(interpreter)?; match interpreter.get_current_state() { InterpreterState::Break(_label) => { @@ -107,7 +107,7 @@ impl Executable for DoWhileLoop { } } - while self.cond().run(interpreter)?.borrow().is_true() { + while self.cond().run(interpreter)?.borrow().to_boolean() { result = self.body().run(interpreter)?; match interpreter.get_current_state() { InterpreterState::Break(_label) => { diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 11ec55613e..8999040ff9 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -24,6 +24,7 @@ mod try_node; use crate::{ builtins::{ function::{Function as FunctionObject, FunctionBody, ThisMode}, + number::{f64_to_int32, f64_to_uint32}, object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{RcBigInt, RcString, ResultValue, Type, Value}, @@ -231,29 +232,83 @@ impl Interpreter { let integer_index = self.to_integer(value)?; - if integer_index < 0 { + if integer_index < 0.0 { return Err(self.construct_range_error("Integer index must be >= 0")); } - if integer_index > 2i64.pow(53) - 1 { + if integer_index > Number::MAX_SAFE_INTEGER { return Err(self.construct_range_error("Integer index must be less than 2**(53) - 1")); } Ok(integer_index as usize) } - /// Converts a value to an integral 64 bit signed integer. + /// Converts a value to an integral Number value. /// /// See: https://tc39.es/ecma262/#sec-tointeger #[allow(clippy::wrong_self_convention)] - pub fn to_integer(&mut self, value: &Value) -> Result { + pub fn to_integer(&mut self, value: &Value) -> Result { + // 1. Let number be ? ToNumber(argument). let number = self.to_number(value)?; - if number.is_nan() { + // 2. If number is +∞ or -∞, return number. + if !number.is_finite() { + // 3. If number is NaN, +0, or -0, return +0. + if number.is_nan() { + return Ok(0.0); + } + return Ok(number); + } + + // 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)). + // 5. If integer is -0, return +0. + // 6. Return integer. + Ok(number.trunc() + 0.0) // We add 0.0 to convert -0.0 to +0.0 + } + + /// Converts a value to an integral 32 bit signed integer. + /// + /// See: https://tc39.es/ecma262/#sec-toint32 + #[allow(clippy::wrong_self_convention)] + pub fn to_int32(&mut self, value: &Value) -> Result { + // This is the fast path, if the value is Integer we can just return it. + if let Value::Integer(number) = *value { + return Ok(number); + } + let number = self.to_number(value)?; + + Ok(f64_to_int32(number)) + } + + /// Converts a value to an integral 32 bit unsigned integer. + /// + /// See: https://tc39.es/ecma262/#sec-toint32 + #[allow(clippy::wrong_self_convention)] + pub fn to_uint32(&mut self, value: &Value) -> Result { + // This is the fast path, if the value is Integer we can just return it. + if let Value::Integer(number) = *value { + return Ok(number as u32); + } + let number = self.to_number(value)?; + + Ok(f64_to_uint32(number)) + } + + /// Converts argument to an integer suitable for use as the length of an array-like object. + /// + /// See: https://tc39.es/ecma262/#sec-tolength + #[allow(clippy::wrong_self_convention)] + pub fn to_length(&mut self, value: &Value) -> Result { + // 1. Let len be ? ToInteger(argument). + let len = self.to_integer(value)?; + + // 2. If len ≤ +0, return +0. + if len < 0.0 { return Ok(0); } - Ok(number as i64) + // 3. Return min(len, 2^53 - 1). + Ok(len.min(Number::MAX_SAFE_INTEGER) as usize) } /// Converts a value to a double precision floating point. diff --git a/boa/src/exec/operator/mod.rs b/boa/src/exec/operator/mod.rs index e7643902a8..1c9fe4623e 100644 --- a/boa/src/exec/operator/mod.rs +++ b/boa/src/exec/operator/mod.rs @@ -52,29 +52,28 @@ impl Executable for BinOp { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { match self.op() { op::BinOp::Num(op) => { - let v_a = self.lhs().run(interpreter)?; - let v_b = self.rhs().run(interpreter)?; - Ok(match op { - NumOp::Add => v_a + v_b, - NumOp::Sub => v_a - v_b, - NumOp::Mul => v_a * v_b, - NumOp::Exp => v_a.as_num_to_power(v_b), - NumOp::Div => v_a / v_b, - NumOp::Mod => v_a % v_b, - }) + let x = self.lhs().run(interpreter)?; + let y = self.rhs().run(interpreter)?; + match op { + NumOp::Add => x.add(&y, interpreter), + NumOp::Sub => x.sub(&y, interpreter), + NumOp::Mul => x.mul(&y, interpreter), + NumOp::Exp => x.pow(&y, interpreter), + NumOp::Div => x.div(&y, interpreter), + NumOp::Mod => x.rem(&y, interpreter), + } } op::BinOp::Bit(op) => { - let v_a = self.lhs().run(interpreter)?; - let v_b = self.rhs().run(interpreter)?; - Ok(match op { - BitOp::And => v_a & v_b, - BitOp::Or => v_a | v_b, - BitOp::Xor => v_a ^ v_b, - BitOp::Shl => v_a << v_b, - BitOp::Shr => v_a >> v_b, - // TODO Fix - BitOp::UShr => v_a >> v_b, - }) + let x = self.lhs().run(interpreter)?; + let y = self.rhs().run(interpreter)?; + match op { + BitOp::And => x.bitand(&y, interpreter), + BitOp::Or => x.bitor(&y, interpreter), + BitOp::Xor => x.bitxor(&y, interpreter), + BitOp::Shl => x.shl(&y, interpreter), + BitOp::Shr => x.shr(&y, interpreter), + BitOp::UShr => x.ushr(&y, interpreter), + } } op::BinOp::Comp(op) => { let v_a = self.lhs().run(interpreter)?; @@ -122,7 +121,7 @@ impl Executable for BinOp { .get_binding_value(name.as_ref()) .ok_or_else(|| interpreter.construct_reference_error(name.as_ref()))?; let v_b = self.rhs().run(interpreter)?; - let value = Self::run_assign(op, v_a, v_b); + let value = Self::run_assign(op, v_a, v_b, interpreter)?; interpreter.realm.environment.set_mutable_binding( name.as_ref(), value.clone(), @@ -134,7 +133,7 @@ impl Executable for BinOp { let v_r_a = get_const_field.obj().run(interpreter)?; let v_a = v_r_a.get_field(get_const_field.field()); let v_b = self.rhs().run(interpreter)?; - let value = Self::run_assign(op, v_a, v_b); + let value = Self::run_assign(op, v_a, v_b, interpreter)?; v_r_a.set_field(get_const_field.field(), value.clone()); Ok(value) } @@ -146,49 +145,49 @@ impl Executable for BinOp { impl BinOp { /// Runs the assignment operators. - fn run_assign(op: AssignOp, v_a: Value, v_b: Value) -> Value { + fn run_assign(op: AssignOp, x: Value, y: Value, interpreter: &mut Interpreter) -> ResultValue { match op { - AssignOp::Add => v_a + v_b, - AssignOp::Sub => v_a - v_b, - AssignOp::Mul => v_a * v_b, - AssignOp::Exp => v_a.as_num_to_power(v_b), - AssignOp::Div => v_a / v_b, - AssignOp::Mod => v_a % v_b, - AssignOp::And => v_a & v_b, - AssignOp::Or => v_a | v_b, - AssignOp::Xor => v_a ^ v_b, - AssignOp::Shl => v_a << v_b, - AssignOp::Shr => v_a << v_b, + AssignOp::Add => x.add(&y, interpreter), + AssignOp::Sub => x.sub(&y, interpreter), + AssignOp::Mul => x.mul(&y, interpreter), + AssignOp::Exp => x.pow(&y, interpreter), + AssignOp::Div => x.div(&y, interpreter), + AssignOp::Mod => x.rem(&y, interpreter), + AssignOp::And => x.bitand(&y, interpreter), + AssignOp::Or => x.bitor(&y, interpreter), + AssignOp::Xor => x.bitxor(&y, interpreter), + AssignOp::Shl => x.shl(&y, interpreter), + AssignOp::Shr => x.shr(&y, interpreter), } } } impl Executable for UnaryOp { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { - let v_a = self.target().run(interpreter)?; + let x = self.target().run(interpreter)?; Ok(match self.op() { - op::UnaryOp::Minus => -v_a, - op::UnaryOp::Plus => Value::from(v_a.to_number()), + op::UnaryOp::Minus => x.neg(interpreter)?, + op::UnaryOp::Plus => Value::from(x.to_number()), op::UnaryOp::IncrementPost => { - let ret = v_a.clone(); - interpreter.set_value(self.target(), Value::from(v_a.to_number() + 1.0))?; + let ret = x.clone(); + interpreter.set_value(self.target(), Value::from(x.to_number() + 1.0))?; ret } op::UnaryOp::IncrementPre => { - interpreter.set_value(self.target(), Value::from(v_a.to_number() + 1.0))? + interpreter.set_value(self.target(), Value::from(x.to_number() + 1.0))? } op::UnaryOp::DecrementPost => { - let ret = v_a.clone(); - interpreter.set_value(self.target(), Value::from(v_a.to_number() - 1.0))?; + let ret = x.clone(); + interpreter.set_value(self.target(), Value::from(x.to_number() - 1.0))?; ret } op::UnaryOp::DecrementPre => { - interpreter.set_value(self.target(), Value::from(v_a.to_number() - 1.0))? + interpreter.set_value(self.target(), Value::from(x.to_number() - 1.0))? } - op::UnaryOp::Not => !v_a, + op::UnaryOp::Not => x.not(interpreter)?, op::UnaryOp::Tilde => { - let num_v_a = v_a.to_number(); + let num_v_a = x.to_number(); // NOTE: possible UB: https://github.com/rust-lang/rust/issues/10184 Value::from(if num_v_a.is_nan() { -1 @@ -221,7 +220,7 @@ impl Executable for UnaryOp { | Node::UnaryOp(_) => Value::boolean(true), _ => panic!("SyntaxError: wrong delete argument {}", self), }, - op::UnaryOp::TypeOf => Value::from(v_a.get_type().as_str()), + op::UnaryOp::TypeOf => Value::from(x.get_type().as_str()), }) } } diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index 439bcedf56..484f3a8aa9 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -1,4 +1,10 @@ -use crate::{builtins::Value, exec, exec::Interpreter, forward, realm::Realm}; +use crate::{ + builtins::{Number, Value}, + exec, + exec::Interpreter, + forward, + realm::Realm, +}; #[test] fn function_declaration_returns_undefined() { @@ -895,10 +901,177 @@ fn to_integer() { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - assert_eq!(engine.to_integer(&Value::number(f64::NAN)).unwrap(), 0); - assert_eq!(engine.to_integer(&Value::number(0.0f64)).unwrap(), 0); - assert_eq!(engine.to_integer(&Value::number(20.9)).unwrap(), 20); - assert_eq!(engine.to_integer(&Value::number(-20.9)).unwrap(), -20); + assert!(Number::equal( + engine.to_integer(&Value::number(f64::NAN)).unwrap(), + 0.0 + )); + assert!(Number::equal( + engine + .to_integer(&Value::number(f64::NEG_INFINITY)) + .unwrap(), + f64::NEG_INFINITY + )); + assert!(Number::equal( + engine.to_integer(&Value::number(f64::INFINITY)).unwrap(), + f64::INFINITY + )); + assert!(Number::equal( + engine.to_integer(&Value::number(0.0)).unwrap(), + 0.0 + )); + let number = engine.to_integer(&Value::number(-0.0)).unwrap(); + assert!(!number.is_sign_negative()); + assert!(Number::equal(number, 0.0)); + assert!(Number::equal( + engine.to_integer(&Value::number(20.9)).unwrap(), + 20.0 + )); + assert!(Number::equal( + engine.to_integer(&Value::number(-20.9)).unwrap(), + -20.0 + )); +} + +#[test] +fn to_length() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!(engine.to_length(&Value::number(f64::NAN)).unwrap(), 0); + assert_eq!( + engine.to_length(&Value::number(f64::NEG_INFINITY)).unwrap(), + 0 + ); + assert_eq!( + engine.to_length(&Value::number(f64::INFINITY)).unwrap(), + Number::MAX_SAFE_INTEGER as usize + ); + assert_eq!(engine.to_length(&Value::number(0.0)).unwrap(), 0); + assert_eq!(engine.to_length(&Value::number(-0.0)).unwrap(), 0); + assert_eq!(engine.to_length(&Value::number(20.9)).unwrap(), 20); + assert_eq!(engine.to_length(&Value::number(-20.9)).unwrap(), 0); + assert_eq!( + engine.to_length(&Value::number(100000000000.0)).unwrap(), + 100000000000 + ); + assert_eq!( + engine.to_length(&Value::number(4010101101.0)).unwrap(), + 4010101101 + ); +} + +#[test] +fn to_int32() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + macro_rules! check_to_int32 { + ($from:expr => $to:expr) => { + assert_eq!(engine.to_int32(&Value::number($from)).unwrap(), $to); + }; + }; + + check_to_int32!(f64::NAN => 0); + check_to_int32!(f64::NEG_INFINITY => 0); + check_to_int32!(f64::INFINITY => 0); + check_to_int32!(0 => 0); + check_to_int32!(-0.0 => 0); + + check_to_int32!(20.9 => 20); + check_to_int32!(-20.9 => -20); + + check_to_int32!(Number::MIN_VALUE => 0); + check_to_int32!(-Number::MIN_VALUE => 0); + check_to_int32!(0.1 => 0); + check_to_int32!(-0.1 => 0); + check_to_int32!(1 => 1); + check_to_int32!(1.1 => 1); + check_to_int32!(-1 => -1); + check_to_int32!(0.6 => 0); + check_to_int32!(1.6 => 1); + check_to_int32!(-0.6 => 0); + check_to_int32!(-1.6 => -1); + + check_to_int32!(2147483647.0 => 2147483647); + check_to_int32!(2147483648.0 => -2147483648); + check_to_int32!(2147483649.0 => -2147483647); + + check_to_int32!(4294967295.0 => -1); + check_to_int32!(4294967296.0 => 0); + check_to_int32!(4294967297.0 => 1); + + check_to_int32!(-2147483647.0 => -2147483647); + check_to_int32!(-2147483648.0 => -2147483648); + check_to_int32!(-2147483649.0 => 2147483647); + + check_to_int32!(-4294967295.0 => 1); + check_to_int32!(-4294967296.0 => 0); + check_to_int32!(-4294967297.0 => -1); + + check_to_int32!(2147483648.25 => -2147483648); + check_to_int32!(2147483648.5 => -2147483648); + check_to_int32!(2147483648.75 => -2147483648); + check_to_int32!(4294967295.25 => -1); + check_to_int32!(4294967295.5 => -1); + check_to_int32!(4294967295.75 => -1); + check_to_int32!(3000000000.25 => -1294967296); + check_to_int32!(3000000000.5 => -1294967296); + check_to_int32!(3000000000.75 => -1294967296); + + check_to_int32!(-2147483648.25 => -2147483648); + check_to_int32!(-2147483648.5 => -2147483648); + check_to_int32!(-2147483648.75 => -2147483648); + check_to_int32!(-4294967295.25 => 1); + check_to_int32!(-4294967295.5 => 1); + check_to_int32!(-4294967295.75 => 1); + check_to_int32!(-3000000000.25 => 1294967296); + check_to_int32!(-3000000000.5 => 1294967296); + check_to_int32!(-3000000000.75 => 1294967296); + + let base = 2f64.powf(64.0); + check_to_int32!(base + 0.0 => 0); + check_to_int32!(base + 1117.0 => 0); + check_to_int32!(base + 2234.0 => 4096); + check_to_int32!(base + 3351.0 => 4096); + check_to_int32!(base + 4468.0 => 4096); + check_to_int32!(base + 5585.0 => 4096); + check_to_int32!(base + 6702.0 => 8192); + check_to_int32!(base + 7819.0 => 8192); + check_to_int32!(base + 8936.0 => 8192); + check_to_int32!(base + 10053.0 => 8192); + check_to_int32!(base + 11170.0 => 12288); + check_to_int32!(base + 12287.0 => 12288); + check_to_int32!(base + 13404.0 => 12288); + check_to_int32!(base + 14521.0 => 16384); + check_to_int32!(base + 15638.0 => 16384); + check_to_int32!(base + 16755.0 => 16384); + check_to_int32!(base + 17872.0 => 16384); + check_to_int32!(base + 18989.0 => 20480); + check_to_int32!(base + 20106.0 => 20480); + check_to_int32!(base + 21223.0 => 20480); + check_to_int32!(base + 22340.0 => 20480); + check_to_int32!(base + 23457.0 => 24576); + check_to_int32!(base + 24574.0 => 24576); + check_to_int32!(base + 25691.0 => 24576); + check_to_int32!(base + 26808.0 => 28672); + check_to_int32!(base + 27925.0 => 28672); + check_to_int32!(base + 29042.0 => 28672); + check_to_int32!(base + 30159.0 => 28672); + check_to_int32!(base + 31276.0 => 32768); + + // bignum is (2^53 - 1) * 2^31 - highest number with bit 31 set. + let bignum = 2f64.powf(84.0) - 2f64.powf(31.0); + check_to_int32!(bignum => -2147483648); + check_to_int32!(-bignum => -2147483648); + check_to_int32!(2.0 * bignum => 0); + check_to_int32!(-(2.0 * bignum) => 0); + check_to_int32!(bignum - 2f64.powf(31.0) => 0); + check_to_int32!(-(bignum - 2f64.powf(31.0)) => 0); + + // max_fraction is largest number below 1. + let max_fraction = 1.0 - 2f64.powf(-53.0); + check_to_int32!(max_fraction => 0); + check_to_int32!(-max_fraction => 0); } #[test]