diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index aaa8c132684..79cb03fed4d 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -875,36 +875,20 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill pub(crate) fn fill(this: &Value, args: &[Value], context: &mut Context) -> Result { - let len: isize = this.get_field("length").to_length(context)?.try_into()?; + let len = this.get_field("length").to_length(context)?; let default_value = Value::undefined(); let value = args.get(0).unwrap_or(&default_value); - let relative_start_val = args.get(1).unwrap_or(&default_value); - let relative_start = if relative_start_val.is_undefined() { - 0.0 - } else { - relative_start_val.to_number(context)? - }; - let relative_end_val = args.get(2).unwrap_or(&default_value); - let relative_end = if relative_end_val.is_undefined() { - len as f64 - } else { - relative_end_val.to_number(context)? - }; - let start = if !relative_start.is_finite() { - 0 - } else if relative_start < 0.0 { - max(len + f64_to_isize(relative_start)?, 0) - } else { - min(f64_to_isize(relative_start)?, len) - }; - let fin = if !relative_end.is_finite() { - 0 - } else if relative_end < 0.0 { - max(len + f64_to_isize(relative_end)?, 0) - } else { - min(f64_to_isize(relative_end)?, len) - }; + let start = args + .get(1) + .unwrap_or(&default_value) + .to_integer_or_infinity(context)? + .as_relative_start(len); + let fin = args + .get(2) + .unwrap_or(&default_value) + .to_integer_or_infinity(context)? + .as_relative_end(len); for i in start..fin { this.set_field(i, value.clone()); @@ -959,37 +943,26 @@ impl Array { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice pub(crate) fn slice(this: &Value, args: &[Value], context: &mut Context) -> Result { let new_array = Self::new_array(context)?; - let len: isize = this.get_field("length").to_length(context)?.try_into()?; - let start = match args.get(0) { - Some(v) => v.to_integer(context)?, - None => 0.0, - }; - let end = match args.get(1) { - Some(v) => v.to_integer(context)?, - None => len as f64, - }; - - let from = if !start.is_finite() { - 0 - } else if start < 0.0 { - max(len.wrapping_add(f64_to_isize(start)?), 0) - } else { - min(f64_to_isize(start)?, len) - }; - let to = if !end.is_finite() { - 0 - } else if end < 0.0 { - max(len.wrapping_add(f64_to_isize(end)?), 0) - } else { - min(f64_to_isize(end)?, len) - }; + let len = this.get_field("length").to_length(context)?; - let span = max(to.wrapping_sub(from), 0); + let default_value = Value::undefined(); + let from = args + .get(0) + .unwrap_or(&default_value) + .to_integer_or_infinity(context)? + .as_relative_start(len); + let to = args + .get(1) + .unwrap_or(&default_value) + .to_integer_or_infinity(context)? + .as_relative_end(len); + + let span = max(to.saturating_sub(from), 0); let mut new_array_len: i32 = 0; - for i in from..from.wrapping_add(span) { + for i in from..from.saturating_add(span) { new_array.set_field(new_array_len, this.get_field(i)); - new_array_len = new_array_len.wrapping_add(1); + new_array_len = new_array_len.saturating_add(1); } new_array.set_field("length", Value::from(new_array_len)); Ok(new_array) diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 1793e55acb4..bba628b8fd3 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -32,6 +32,7 @@ mod operations; mod rcbigint; mod rcstring; mod rcsymbol; +mod to_integer_or_infinity; mod r#type; pub use conversions::*; @@ -43,6 +44,7 @@ pub use r#type::Type; pub use rcbigint::RcBigInt; pub use rcstring::RcString; pub use rcsymbol::RcSymbol; +pub use to_integer_or_infinity::*; /// A Javascript value #[derive(Trace, Finalize, Debug, Clone)] diff --git a/boa/src/value/tests.rs b/boa/src/value/tests.rs index 0d8420cd6d2..9ee959aead9 100644 --- a/boa/src/value/tests.rs +++ b/boa/src/value/tests.rs @@ -519,6 +519,81 @@ toString: { ); } +#[test] +fn to_integer_or_infinity() { + let mut context = Context::new(); + + assert_eq!( + Value::undefined().to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Undefined) + ); + assert_eq!( + Value::from(NAN).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Integer(0)) + ); + assert_eq!( + Value::from(0.0).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Integer(0)) + ); + assert_eq!( + Value::from(-0.0).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Integer(0)) + ); + + assert_eq!( + Value::from(f64::INFINITY).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::PositiveInfinity) + ); + assert_eq!( + Value::from(f64::NEG_INFINITY).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::NegativeInfinity) + ); + + assert_eq!( + Value::from(10).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Integer(10)) + ); + assert_eq!( + Value::from(11.0).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Integer(11)) + ); + assert_eq!( + Value::from("12").to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Integer(12)) + ); + assert_eq!( + Value::from(true).to_integer_or_infinity(&mut context), + Ok(IntegerOrInfinity::Integer(1)) + ); + + assert_eq!(IntegerOrInfinity::Undefined.as_relative_start(10), 0); + assert_eq!(IntegerOrInfinity::NegativeInfinity.as_relative_start(10), 0); + assert_eq!( + IntegerOrInfinity::PositiveInfinity.as_relative_start(10), + 10 + ); + assert_eq!(IntegerOrInfinity::Integer(-1).as_relative_start(10), 9); + assert_eq!(IntegerOrInfinity::Integer(1).as_relative_start(10), 1); + assert_eq!(IntegerOrInfinity::Integer(-11).as_relative_start(10), 0); + assert_eq!(IntegerOrInfinity::Integer(11).as_relative_start(10), 10); + assert_eq!( + IntegerOrInfinity::Integer(isize::MIN).as_relative_start(10), + 0 + ); + + assert_eq!(IntegerOrInfinity::Undefined.as_relative_end(10), 10); + assert_eq!(IntegerOrInfinity::NegativeInfinity.as_relative_end(10), 0); + assert_eq!(IntegerOrInfinity::PositiveInfinity.as_relative_end(10), 10); + assert_eq!(IntegerOrInfinity::Integer(-1).as_relative_end(10), 9); + assert_eq!(IntegerOrInfinity::Integer(1).as_relative_end(10), 1); + assert_eq!(IntegerOrInfinity::Integer(-11).as_relative_end(10), 0); + assert_eq!(IntegerOrInfinity::Integer(11).as_relative_end(10), 10); + assert_eq!( + IntegerOrInfinity::Integer(isize::MIN).as_relative_end(10), + 0 + ); +} + /// Test cyclic conversions that previously caused stack overflows /// Relevant mitigations for these are in `GcObject::ordinary_to_primitive` and /// `GcObject::to_json` diff --git a/boa/src/value/to_integer_or_infinity.rs b/boa/src/value/to_integer_or_infinity.rs new file mode 100644 index 00000000000..e45adbac157 --- /dev/null +++ b/boa/src/value/to_integer_or_infinity.rs @@ -0,0 +1,99 @@ +use super::*; + +use std::convert::TryFrom; + +/// Represents the result of ToIntegerOrInfinity operation +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IntegerOrInfinity { + Integer(isize), + Undefined, + PositiveInfinity, + NegativeInfinity, +} + +impl IntegerOrInfinity { + /// Represents the algorithm to calculate `relativeStart` (or `k`) in array functions. + pub fn as_relative_start(self, len: usize) -> usize { + match self { + // 1. If relativeStart is -∞, let k be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 2. Else if relativeStart < 0, let k be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => Self::offset(len, i), + // 3. Else, let k be min(relativeStart, len). + IntegerOrInfinity::Integer(i) => (i as usize).min(len), + + // Special case - postive infinity. `len` is always smaller than +inf, thus from (3) + IntegerOrInfinity::PositiveInfinity => len, + // Special case - `undefined` is treated like 0 + IntegerOrInfinity::Undefined => 0, + } + } + + /// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions. + pub fn as_relative_end(self, len: usize) -> usize { + match self { + // 1. If end is undefined, let relativeEnd be len + IntegerOrInfinity::Undefined => len, + + // 1. cont, else let relativeEnd be ? ToIntegerOrInfinity(end). + + // 2. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => 0, + // 3. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => Self::offset(len, i), + // 4. Else, let final be min(relativeEnd, len). + IntegerOrInfinity::Integer(i) => (i as usize).min(len), + + // Special case - postive infinity. `len` is always smaller than +inf, thus from (4) + IntegerOrInfinity::PositiveInfinity => len, + } + } + + fn offset(len: usize, i: isize) -> usize { + debug_assert!(i < 0); + if i == isize::MIN { + len.saturating_sub(isize::MAX as usize).saturating_sub(1) + } else { + len.saturating_sub(i.saturating_neg() as usize) + } + } +} + +impl Value { + /// Converts argument to an integer, +∞, or -∞. + /// + /// See: + pub fn to_integer_or_infinity(&self, context: &mut Context) -> Result { + // Special case - `undefined` + if self.is_undefined() { + return Ok(IntegerOrInfinity::Undefined); + } + + // 1. Let number be ? ToNumber(argument). + let number = self.to_number(context)?; + + // 2. If number is NaN, +0𝔽, or -0𝔽, return 0. + if number.is_nan() || number == 0.0 || number == -0.0 { + Ok(IntegerOrInfinity::Integer(0)) + } else if number.is_infinite() && number.is_sign_positive() { + // 3. If number is +∞𝔽, return +∞. + Ok(IntegerOrInfinity::PositiveInfinity) + } else if number.is_infinite() && number.is_sign_negative() { + // 4. If number is -∞𝔽, return -∞. + Ok(IntegerOrInfinity::NegativeInfinity) + } else { + // 5. Let integer be floor(abs(ℝ(number))). + let integer = number.abs().floor(); + let integer = integer.min(Number::MAX_SAFE_INTEGER) as i64; + let integer = isize::try_from(integer)?; + + // 6. If number < +0𝔽, set integer to -integer. + // 7. Return integer. + if number < 0.0 { + Ok(IntegerOrInfinity::Integer(-integer)) + } else { + Ok(IntegerOrInfinity::Integer(integer)) + } + } + } +}