Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce the number of Array-related panics #919

Merged
merged 15 commits into from Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
223 changes: 115 additions & 108 deletions boa/src/builtins/array/mod.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion boa/src/builtins/function/mod.rs
Expand Up @@ -119,7 +119,7 @@ impl Function {
) {
// Create array of values
let array = Array::new_array(context).unwrap();
Array::add_to_array_object(&array, &args_list[index..]).unwrap();
Array::add_to_array_object(&array, &args_list[index..], context).unwrap();

// Create binding
local_env
Expand Down
1 change: 1 addition & 0 deletions boa/src/builtins/map/map_iterator.rs
Expand Up @@ -106,6 +106,7 @@ impl MapIterator {
let result = Array::construct_array(
&Array::new_array(context)?,
&[key.clone(), value.clone()],
context,
)?;
return Ok(create_iter_result_object(
context, result, false,
Expand Down
2 changes: 1 addition & 1 deletion boa/src/syntax/ast/node/array/mod.rs
Expand Up @@ -61,7 +61,7 @@ impl Executable for ArrayDecl {
}
}

Array::add_to_array_object(&array, &elements)?;
Array::add_to_array_object(&array, &elements, context)?;
Ok(array)
}
}
Expand Down
6 changes: 6 additions & 0 deletions boa/src/value/conversions.rs
Expand Up @@ -186,3 +186,9 @@ where
}
}
}

impl From<std::num::TryFromIntError> for Value {
fn from(err: std::num::TryFromIntError) -> Self {
Value::string(format!("{}", err))
}
}
jakubfijalkowski marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions boa/src/value/mod.rs
Expand Up @@ -32,6 +32,7 @@ mod operations;
mod rcbigint;
mod rcstring;
mod rcsymbol;
mod to_integer_or_infinity;
jakubfijalkowski marked this conversation as resolved.
Show resolved Hide resolved
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
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
@@ -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),
jakubfijalkowski marked this conversation as resolved.
Show resolved Hide resolved
Undefined,
jakubfijalkowski marked this conversation as resolved.
Show resolved Hide resolved
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 {
jakubfijalkowski marked this conversation as resolved.
Show resolved Hide resolved
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);
}
jakubfijalkowski marked this conversation as resolved.
Show resolved Hide resolved

// 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))
}
}
}
}