Skip to content

Commit

Permalink
Implement to_integer_or_infinity from the ECMA spec and use it in `…
Browse files Browse the repository at this point in the history
…fill` and `slice`
  • Loading branch information
jakubfijalkowski committed Nov 3, 2020
1 parent 6365cf3 commit a86ea85
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 54 deletions.
81 changes: 27 additions & 54 deletions boa/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Value> {
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());
Expand Down Expand Up @@ -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<Value> {
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)
Expand Down
2 changes: 2 additions & 0 deletions boa/src/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod operations;
mod rcbigint;
mod rcstring;
mod rcsymbol;
mod to_integer_or_infinity;
mod r#type;

pub use conversions::*;
Expand All @@ -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)]
Expand Down
75 changes: 75 additions & 0 deletions boa/src/value/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
99 changes: 99 additions & 0 deletions boa/src/value/to_integer_or_infinity.rs
Original file line number Diff line number Diff line change
@@ -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: <https://tc39.es/ecma262/#sec-tointegerorinfinity>
pub fn to_integer_or_infinity(&self, context: &mut Context) -> Result<IntegerOrInfinity> {
// 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))
}
}
}
}

0 comments on commit a86ea85

Please sign in to comment.