From a987e76b9f2d71206932cda79657df0521b2f00c Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 11 Jun 2020 00:28:13 +0200 Subject: [PATCH] Add BigInt.asIntN() and BigInt.asUintN() functions (#468) Co-authored-by: HalidOdat --- Cargo.lock | 1 + boa/Cargo.toml | 1 + boa/src/builtins/bigint/mod.rs | 71 +++++++++++- boa/src/builtins/bigint/operations.rs | 14 +++ boa/src/builtins/bigint/tests.rs | 148 ++++++++++++++++++++++++++ boa/src/exec/mod.rs | 69 ++++++++++++ boa/src/exec/tests.rs | 20 ++++ 7 files changed, 323 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6414d7994b..95f01ee1bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "jemallocator", "measureme", "num-bigint", + "num-integer", "num-traits", "once_cell", "rand", diff --git a/boa/Cargo.toml b/boa/Cargo.toml index f3284e54e6..a392d143e0 100644 --- a/boa/Cargo.toml +++ b/boa/Cargo.toml @@ -21,6 +21,7 @@ num-traits = "0.2.11" regex = "1.3.9" rustc-hash = "1.1.0" num-bigint = { version = "0.2.6", features = ["serde"] } +num-integer = "0.1.42" bitflags = "1.2.1" # Optional Dependencies diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index 1417fbbb60..a685d1f6e8 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -146,6 +146,70 @@ impl BigInt { Ok(Value::from(Self::this_bigint_value(this, ctx)?)) } + /// `BigInt.asIntN()` + /// + /// The `BigInt.asIntN()` method wraps the value of a `BigInt` to a signed integer between `-2**(width - 1)` and `2**(width-1) - 1`. + /// + /// [spec]: https://tc39.es/ecma262/#sec-bigint.asintn + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asIntN + #[allow(clippy::wrong_self_convention)] + pub(crate) fn as_int_n( + _this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + let (modulo, bits) = Self::calculate_as_uint_n(args, ctx)?; + + if bits > 0 && modulo >= BigInt::from(2).pow(&BigInt::from(bits as i64 - 1)) { + Ok(Value::from( + modulo - BigInt::from(2).pow(&BigInt::from(bits as i64)), + )) + } else { + Ok(Value::from(modulo)) + } + } + + /// `BigInt.asUintN()` + /// + /// The `BigInt.asUintN()` method wraps the value of a `BigInt` to an unsigned integer between `0` and `2**(width) - 1`. + /// + /// [spec]: https://tc39.es/ecma262/#sec-bigint.asuintn + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/asUintN + #[allow(clippy::wrong_self_convention)] + pub(crate) fn as_uint_n( + _this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + let (modulo, _) = Self::calculate_as_uint_n(args, ctx)?; + + Ok(Value::from(modulo)) + } + + /// Helper function to wrap the value of a `BigInt` to an unsigned integer. + /// + /// This function expects the same arguments as `as_uint_n` and wraps the value of a `BigInt`. + /// Additionally to the wrapped unsigned value it returns the converted `bits` argument, so it + /// can be reused from the `as_int_n` method. + fn calculate_as_uint_n(args: &[Value], ctx: &mut Interpreter) -> Result<(BigInt, u32), Value> { + use std::convert::TryFrom; + + let undefined_value = Value::undefined(); + + let bits_arg = args.get(0).unwrap_or(&undefined_value); + let bigint_arg = args.get(1).unwrap_or(&undefined_value); + + let bits = ctx.to_index(bits_arg)?; + let bits = u32::try_from(bits).unwrap_or(u32::MAX); + + let bigint = ctx.to_bigint(bigint_arg)?; + + Ok(( + bigint.mod_floor(&BigInt::from(2).pow(&BigInt::from(bits as i64))), + bits, + )) + } + /// Create a new `Number` object pub(crate) fn create(global: &Value) -> Value { let prototype = Value::new_object(Some(global)); @@ -154,7 +218,12 @@ impl BigInt { make_builtin_fn(Self::to_string, "toString", &prototype, 1); make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); - make_constructor_fn("BigInt", 1, Self::make_bigint, global, prototype, false) + let big_int = make_constructor_fn("BigInt", 1, Self::make_bigint, global, prototype, false); + + make_builtin_fn(Self::as_int_n, "asIntN", &big_int, 2); + make_builtin_fn(Self::as_uint_n, "asUintN", &big_int, 2); + + big_int } /// Initialise the `BigInt` object on the global object. diff --git a/boa/src/builtins/bigint/operations.rs b/boa/src/builtins/bigint/operations.rs index 27a1cd4e1a..56f28cd164 100644 --- a/boa/src/builtins/bigint/operations.rs +++ b/boa/src/builtins/bigint/operations.rs @@ -17,6 +17,20 @@ impl BigInt { ), ) } + + /// Floored integer modulo. + /// + /// # Examples + /// ``` + /// # use num_integer::Integer; + /// assert_eq!((8).mod_floor(&3), 2); + /// assert_eq!((8).mod_floor(&-3), -1); + /// ``` + #[inline] + pub fn mod_floor(self, other: &Self) -> Self { + use num_integer::Integer; + Self(self.0.mod_floor(&other.0)) + } } macro_rules! impl_bigint_operator { diff --git a/boa/src/builtins/bigint/tests.rs b/boa/src/builtins/bigint/tests.rs index 7dafb618f4..92a230890e 100644 --- a/boa/src/builtins/bigint/tests.rs +++ b/boa/src/builtins/bigint/tests.rs @@ -219,3 +219,151 @@ fn to_string() { assert_eq!(forward(&mut engine, "255n.toString(16)"), "ff"); assert_eq!(forward(&mut engine, "1000n.toString(36)"), "rs"); } + +#[test] +fn as_int_n() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!(forward(&mut engine, "BigInt.asIntN(0, 1n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asIntN(1, 1n)"), "-1n"); + assert_eq!(forward(&mut engine, "BigInt.asIntN(3, 10n)"), "2n"); + assert_eq!(forward(&mut engine, "BigInt.asIntN({}, 1n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asIntN(2, 0n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asIntN(2, -0n)"), "0n"); + + assert_eq!( + forward(&mut engine, "BigInt.asIntN(2, -123456789012345678901n)"), + "-1n" + ); + assert_eq!( + forward(&mut engine, "BigInt.asIntN(2, -123456789012345678900n)"), + "0n" + ); + + assert_eq!( + forward(&mut engine, "BigInt.asIntN(2, 123456789012345678900n)"), + "0n" + ); + assert_eq!( + forward(&mut engine, "BigInt.asIntN(2, 123456789012345678901n)"), + "1n" + ); + + assert_eq!( + forward( + &mut engine, + "BigInt.asIntN(200, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)" + ), + "-1n" + ); + assert_eq!( + forward( + &mut engine, + "BigInt.asIntN(201, 0xcffffffffffffffffffffffffffffffffffffffffffffffffffn)" + ), + "1606938044258990275541962092341162602522202993782792835301375n" + ); + + assert_eq!( + forward( + &mut engine, + "BigInt.asIntN(200, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)" + ), + "-741470203160010616172516490008037905920749803227695190508460n" + ); + assert_eq!( + forward( + &mut engine, + "BigInt.asIntN(201, 0xc89e081df68b65fedb32cffea660e55df9605650a603ad5fc54n)" + ), + "865467841098979659369445602333124696601453190555097644792916n" + ); +} + +#[test] +fn as_int_n_errors() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_throws(&mut engine, "BigInt.asIntN(-1, 0n)", "RangeError"); + assert_throws(&mut engine, "BigInt.asIntN(-2.5, 0n)", "RangeError"); + assert_throws( + &mut engine, + "BigInt.asIntN(9007199254740992, 0n)", + "RangeError", + ); + assert_throws(&mut engine, "BigInt.asIntN(0n, 0n)", "TypeError"); +} + +#[test] +fn as_uint_n() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!(forward(&mut engine, "BigInt.asUintN(0, -2n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(0, -1n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(0, 0n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(0, 1n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(0, 2n)"), "0n"); + + assert_eq!(forward(&mut engine, "BigInt.asUintN(1, -3n)"), "1n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(1, -2n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(1, -1n)"), "1n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 0n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 1n)"), "1n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 2n)"), "0n"); + assert_eq!(forward(&mut engine, "BigInt.asUintN(1, 3n)"), "1n"); + + assert_eq!( + forward(&mut engine, "BigInt.asUintN(1, -123456789012345678901n)"), + "1n" + ); + assert_eq!( + forward(&mut engine, "BigInt.asUintN(1, -123456789012345678900n)"), + "0n" + ); + assert_eq!( + forward(&mut engine, "BigInt.asUintN(1, 123456789012345678900n)"), + "0n" + ); + assert_eq!( + forward(&mut engine, "BigInt.asUintN(1, 123456789012345678901n)"), + "1n" + ); + + assert_eq!( + forward( + &mut engine, + "BigInt.asUintN(200, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)" + ), + "1606938044258990275541962092341162602522202993782792835301375n" + ); + assert_eq!( + forward( + &mut engine, + "BigInt.asUintN(201, 0xbffffffffffffffffffffffffffffffffffffffffffffffffffn)" + ), + "3213876088517980551083924184682325205044405987565585670602751n" + ); +} + +#[test] +fn as_uint_n_errors() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_throws(&mut engine, "BigInt.asUintN(-1, 0n)", "RangeError"); + assert_throws(&mut engine, "BigInt.asUintN(-2.5, 0n)", "RangeError"); + assert_throws( + &mut engine, + "BigInt.asUintN(9007199254740992, 0n)", + "RangeError", + ); + assert_throws(&mut engine, "BigInt.asUintN(0n, 0n)", "TypeError"); +} + +fn assert_throws(engine: &mut Interpreter, src: &str, error_type: &str) { + let result = forward(engine, src); + assert!(result.contains(error_type)); +} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 09d3dc5830..bc03543013 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -207,6 +207,74 @@ impl Interpreter { } } + /// Converts a value to a non-negative integer if it is a valid integer index value. + /// + /// See: https://tc39.es/ecma262/#sec-toindex + #[allow(clippy::wrong_self_convention)] + pub fn to_index(&mut self, value: &Value) -> Result { + if value.is_undefined() { + return Ok(0); + } + + let integer_index = self.to_integer(value)?; + + if integer_index < 0 { + self.throw_range_error("Integer index must be >= 0")?; + unreachable!(); + } + + if integer_index > 2i64.pow(53) - 1 { + self.throw_range_error("Integer index must be less than 2**(53) - 1")?; + unreachable!() + } + + Ok(integer_index as usize) + } + + /// Converts a value to an integral 64 bit signed integer. + /// + /// See: https://tc39.es/ecma262/#sec-tointeger + #[allow(clippy::wrong_self_convention)] + pub fn to_integer(&mut self, value: &Value) -> Result { + let number = self.to_number(value)?; + + if number.is_nan() { + return Ok(0); + } + + Ok(number as i64) + } + + /// Converts a value to a double precision floating point. + /// + /// See: https://tc39.es/ecma262/#sec-tonumber + #[allow(clippy::wrong_self_convention)] + pub fn to_number(&mut self, value: &Value) -> Result { + match *value.deref().borrow() { + ValueData::Null => Ok(0.0), + ValueData::Undefined => Ok(f64::NAN), + ValueData::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }), + ValueData::String(ref string) => match string.parse::() { + Ok(number) => Ok(number), + 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::Integer(integer) => Ok(f64::from(integer)), + ValueData::Symbol(_) => { + self.throw_type_error("argument must not be a symbol")?; + unreachable!() + } + ValueData::BigInt(_) => { + self.throw_type_error("argument must not be a bigint")?; + unreachable!() + } + ValueData::Object(_) => { + let prim_value = self.to_primitive(&mut (value.clone()), Some("number")); + self.to_number(&prim_value) + } + } + } + /// 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 @@ -382,6 +450,7 @@ impl Interpreter { .parse::() .expect("cannot parse value to f64") } + ValueData::Undefined => f64::NAN, _ => { // TODO: Make undefined? f64::from(0) diff --git a/boa/src/exec/tests.rs b/boa/src/exec/tests.rs index a332c9f975..e12ffca31c 100644 --- a/boa/src/exec/tests.rs +++ b/boa/src/exec/tests.rs @@ -804,6 +804,26 @@ fn to_bigint() { assert!(engine.to_bigint(&Value::string("100")).is_ok()); } +#[test] +fn to_index() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!(engine.to_index(&Value::undefined()).unwrap(), 0); + assert!(engine.to_index(&Value::integer(-1)).is_err()); +} + +#[test] +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); +} + #[test] fn to_string() { let realm = Realm::create();