Skip to content

Commit

Permalink
Reduce the number of Array-related panics (#919)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubfijalkowski committed Dec 15, 2020
1 parent 90cd480 commit 8f388d5
Show file tree
Hide file tree
Showing 7 changed files with 394 additions and 111 deletions.
289 changes: 180 additions & 109 deletions boa/src/builtins/array/mod.rs

Large diffs are not rendered by default.

124 changes: 124 additions & 0 deletions boa/src/builtins/array/tests.rs
@@ -1,3 +1,5 @@
use super::Array;
use crate::builtins::Number;
use crate::{forward, Context, Value};

#[test]
Expand Down Expand Up @@ -1237,3 +1239,125 @@ fn array_spread_non_iterable() {
"#;
assert_eq!(forward(&mut context, init), "true");
}

#[test]
fn get_relative_start() {
let mut context = Context::new();

assert_eq!(Array::get_relative_start(&mut context, None, 10), Ok(0));
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::undefined()), 10),
Ok(0)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(f64::NEG_INFINITY)), 10),
Ok(0)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(f64::INFINITY)), 10),
Ok(10)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(-1)), 10),
Ok(9)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(1)), 10),
Ok(1)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(-11)), 10),
Ok(0)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(11)), 10),
Ok(10)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(f64::MIN)), 10),
Ok(0)
);
assert_eq!(
Array::get_relative_start(
&mut context,
Some(&Value::from(Number::MIN_SAFE_INTEGER)),
10
),
Ok(0)
);
assert_eq!(
Array::get_relative_start(&mut context, Some(&Value::from(f64::MAX)), 10),
Ok(10)
);

// This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32)
assert_eq!(
Array::get_relative_start(
&mut context,
Some(&Value::from(Number::MAX_SAFE_INTEGER)),
10
),
Ok(10)
);
}

#[test]
fn get_relative_end() {
let mut context = Context::new();

assert_eq!(Array::get_relative_end(&mut context, None, 10), Ok(10));
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::undefined()), 10),
Ok(10)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(f64::NEG_INFINITY)), 10),
Ok(0)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(f64::INFINITY)), 10),
Ok(10)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(-1)), 10),
Ok(9)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(1)), 10),
Ok(1)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(-11)), 10),
Ok(0)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(11)), 10),
Ok(10)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(f64::MIN)), 10),
Ok(0)
);
assert_eq!(
Array::get_relative_end(
&mut context,
Some(&Value::from(Number::MIN_SAFE_INTEGER)),
10
),
Ok(0)
);
assert_eq!(
Array::get_relative_end(&mut context, Some(&Value::from(f64::MAX)), 10),
Ok(10)
);

// This test is relevant only on 32-bit archs (where usize == u32 thus `len` is u32)
assert_eq!(
Array::get_relative_end(
&mut context,
Some(&Value::from(Number::MAX_SAFE_INTEGER)),
10
),
Ok(10)
);
}
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
39 changes: 39 additions & 0 deletions boa/src/value/mod.rs
Expand Up @@ -67,6 +67,14 @@ pub enum Value {
Symbol(RcSymbol),
}

/// Represents the result of ToIntegerOrInfinity operation
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntegerOrInfinity {
Integer(i64),
PositiveInfinity,
NegativeInfinity,
}

impl Value {
/// Creates a new `undefined` value.
#[inline]
Expand Down Expand Up @@ -863,6 +871,37 @@ impl Value {
Err(context.construct_type_error("Property description must be an object"))
}
}

/// 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> {
// 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;

// 6. If number < +0𝔽, set integer to -integer.
// 7. Return integer.
if number < 0.0 {
Ok(IntegerOrInfinity::Integer(-integer))
} else {
Ok(IntegerOrInfinity::Integer(integer))
}
}
}
}

impl Default for Value {
Expand Down
48 changes: 48 additions & 0 deletions boa/src/value/tests.rs
Expand Up @@ -519,6 +519,54 @@ toString: {
);
}

#[test]
fn to_integer_or_infinity() {
let mut context = Context::new();

assert_eq!(
Value::undefined().to_integer_or_infinity(&mut context),
Ok(IntegerOrInfinity::Integer(0))
);
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))
);
}

/// Test cyclic conversions that previously caused stack overflows
/// Relevant mitigations for these are in `GcObject::ordinary_to_primitive` and
/// `GcObject::to_json`
Expand Down

0 comments on commit 8f388d5

Please sign in to comment.