From 8f388d501d0aed14914873ea01f11426b718cd43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Fija=C5=82kowski?= Date: Tue, 15 Dec 2020 09:17:44 +0100 Subject: [PATCH] Reduce the number of `Array`-related panics (#919) --- boa/src/builtins/array/mod.rs | 289 +++++++++++++++++---------- boa/src/builtins/array/tests.rs | 124 ++++++++++++ boa/src/builtins/function/mod.rs | 2 +- boa/src/builtins/map/map_iterator.rs | 1 + boa/src/syntax/ast/node/array/mod.rs | 2 +- boa/src/value/mod.rs | 39 ++++ boa/src/value/tests.rs | 48 +++++ 7 files changed, 394 insertions(+), 111 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 1041a747f4..f4bf412b2b 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -19,10 +19,14 @@ use crate::{ gc::GcObject, object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, property::{Attribute, DataDescriptor}, - value::{same_value_zero, Value}, + value::{same_value_zero, IntegerOrInfinity, Value}, BoaProfiler, Context, Result, }; -use std::cmp::{max, min}; +use num_traits::*; +use std::{ + cmp::{max, min}, + convert::{TryFrom, TryInto}, +}; /// JavaScript `Array` built-in implementation. #[derive(Debug, Clone, Copy)] @@ -160,7 +164,8 @@ impl Array { context: &mut Context, ) -> Result { let prototype = context.standard_objects().array_object().prototype(); - let array = Array::array_create(this, items.len() as u32, Some(prototype), context)?; + let items_len = items.len().try_into().map_err(interror_to_value)?; + let array = Array::array_create(this, items_len, Some(prototype), context)?; for (k, item) in items.iter().enumerate() { array.set_field(k, item.clone()); @@ -224,11 +229,15 @@ impl Array { /// /// `array_obj` can be any array with prototype already set (it will be wiped and /// recreated from `array_contents`) - pub(crate) fn construct_array(array_obj: &Value, array_contents: &[Value]) -> Result { + pub(crate) fn construct_array( + array_obj: &Value, + array_contents: &[Value], + context: &mut Context, + ) -> Result { let array_obj_ptr = array_obj.clone(); // Wipe existing contents of the array object - let orig_length = array_obj.get_field("length").as_number().unwrap() as i32; + let orig_length = array_obj.get_field("length").to_length(context)?; for n in 0..orig_length { array_obj_ptr.remove_property(n); } @@ -248,17 +257,21 @@ impl Array { /// Utility function which takes an existing array object and puts additional /// values on the end, correctly rewriting the length - pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> Result { - let orig_length = array_ptr.get_field("length").as_number().unwrap() as i32; + pub(crate) fn add_to_array_object( + array_ptr: &Value, + add_values: &[Value], + context: &mut Context, + ) -> Result { + let orig_length = array_ptr.get_field("length").to_length(context)?; for (n, value) in add_values.iter().enumerate() { - let new_index = orig_length.wrapping_add(n as i32); + let new_index = orig_length.wrapping_add(n); array_ptr.set_field(new_index, value); } array_ptr.set_field( "length", - Value::from(orig_length.wrapping_add(add_values.len() as i32)), + Value::from(orig_length.wrapping_add(add_values.len())), ); Ok(array_ptr.clone()) @@ -294,7 +307,7 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat - pub(crate) fn concat(this: &Value, args: &[Value], _: &mut Context) -> Result { + pub(crate) fn concat(this: &Value, args: &[Value], context: &mut Context) -> Result { if args.is_empty() { // If concat is called with no arguments, it returns the original array return Ok(this.clone()); @@ -304,19 +317,19 @@ impl Array { // one) let mut new_values: Vec = Vec::new(); - let this_length = this.get_field("length").as_number().unwrap() as i32; + let this_length = this.get_field("length").to_length(context)?; for n in 0..this_length { new_values.push(this.get_field(n)); } for concat_array in args { - let concat_length = concat_array.get_field("length").as_number().unwrap() as i32; + let concat_length = concat_array.get_field("length").to_length(context)?; for n in 0..concat_length { new_values.push(concat_array.get_field(n)); } } - Self::construct_array(this, &new_values) + Self::construct_array(this, &new_values, context) } /// `Array.prototype.push( ...items )` @@ -331,8 +344,8 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push - pub(crate) fn push(this: &Value, args: &[Value], _: &mut Context) -> Result { - let new_array = Self::add_to_array_object(this, args)?; + pub(crate) fn push(this: &Value, args: &[Value], context: &mut Context) -> Result { + let new_array = Self::add_to_array_object(this, args, context)?; Ok(new_array.get_field("length")) } @@ -346,8 +359,8 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop - pub(crate) fn pop(this: &Value, _: &[Value], _: &mut Context) -> Result { - let curr_length = this.get_field("length").as_number().unwrap() as i32; + pub(crate) fn pop(this: &Value, _: &[Value], context: &mut Context) -> Result { + let curr_length = this.get_field("length").to_length(context)?; if curr_length < 1 { return Ok(Value::undefined()); @@ -377,7 +390,7 @@ impl Array { let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").as_number().unwrap() as i32; + let length = this.get_field("length").to_length(context)?; for i in 0..length { let element = this.get_field(i); @@ -412,7 +425,7 @@ impl Array { }; let mut elem_strs = Vec::new(); - let length = this.get_field("length").as_number().unwrap() as i32; + let length = this.get_field("length").to_length(context)?; for n in 0..length { let elem_str = this.get_field(n).to_string(context)?.to_string(); elem_strs.push(elem_str); @@ -473,10 +486,10 @@ impl Array { /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse #[allow(clippy::else_if_without_else)] - pub(crate) fn reverse(this: &Value, _: &[Value], _: &mut Context) -> Result { - let len = this.get_field("length").as_number().unwrap() as i32; + pub(crate) fn reverse(this: &Value, _: &[Value], context: &mut Context) -> Result { + let len = this.get_field("length").to_length(context)?; - let middle: i32 = len.wrapping_div(2); + let middle = len.wrapping_div(2); for lower in 0..middle { let upper = len.wrapping_sub(lower).wrapping_sub(1); @@ -512,8 +525,8 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift - pub(crate) fn shift(this: &Value, _: &[Value], _: &mut Context) -> Result { - let len = this.get_field("length").as_number().unwrap() as i32; + pub(crate) fn shift(this: &Value, _: &[Value], context: &mut Context) -> Result { + let len = this.get_field("length").to_length(context)?; if len == 0 { this.set_field("length", 0); @@ -553,10 +566,10 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift - pub(crate) fn unshift(this: &Value, args: &[Value], _: &mut Context) -> Result { - let len = this.get_field("length").as_number().unwrap() as i32; + pub(crate) fn unshift(this: &Value, args: &[Value], context: &mut Context) -> Result { + let len = this.get_field("length").to_length(context)?; - let arg_c: i32 = args.len() as i32; + let arg_c = args.len(); if arg_c > 0 { for k in (1..=len).rev() { @@ -571,12 +584,7 @@ impl Array { } } for j in 0..arg_c { - this.set_field( - j, - args.get(j as usize) - .expect("Could not get argument") - .clone(), - ); + this.set_field(j, args.get(j).expect("Could not get argument").clone()); } } @@ -611,7 +619,7 @@ impl Array { Value::undefined() }; let mut i = 0; - let max_len = this.get_field("length").as_number().unwrap() as i32; + let max_len = this.get_field("length").to_length(context)?; let mut len = max_len; while i < len { let element = this.get_field(i); @@ -620,10 +628,7 @@ impl Array { if !result.to_boolean() { return Ok(Value::from(false)); } - len = min( - max_len, - this.get_field("length").as_number().unwrap() as i32, - ); + len = min(max_len, this.get_field("length").to_length(context)?); i += 1; } Ok(Value::from(true)) @@ -669,7 +674,7 @@ impl Array { }) .collect(); - Self::construct_array(&new, &values) + Self::construct_array(&new, &values, context) } /// `Array.prototype.indexOf( searchElement[, fromIndex ] )` @@ -691,23 +696,27 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf - pub(crate) fn index_of(this: &Value, args: &[Value], _: &mut Context) -> Result { + pub(crate) fn index_of(this: &Value, args: &[Value], context: &mut Context) -> Result { // If no arguments, return -1. Not described in spec, but is what chrome does. if args.is_empty() { return Ok(Value::from(-1)); } let search_element = args[0].clone(); - let len = this.get_field("length").as_number().unwrap() as i32; + let len = this.get_field("length").to_length(context)?; let mut idx = match args.get(1) { Some(from_idx_ptr) => { - let from_idx = from_idx_ptr.as_number().unwrap() as i32; - - if from_idx < 0 { - len + from_idx + let from_idx = from_idx_ptr.to_number(context)?; + + if !from_idx.is_finite() { + return Ok(Value::from(-1)); + } else if from_idx < 0.0 { + let k = + isize::try_from(len).map_err(interror_to_value)? + f64_to_isize(from_idx)?; + usize::try_from(max(0, k)).map_err(interror_to_value)? } else { - from_idx + f64_to_usize(from_idx)? } } None => 0, @@ -744,26 +753,33 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf - pub(crate) fn last_index_of(this: &Value, args: &[Value], _: &mut Context) -> Result { + pub(crate) fn last_index_of( + this: &Value, + args: &[Value], + context: &mut Context, + ) -> Result { // If no arguments, return -1. Not described in spec, but is what chrome does. if args.is_empty() { return Ok(Value::from(-1)); } let search_element = args[0].clone(); - let len = this + let len: isize = this .get_field("length") - .as_number() - .expect("length was not a number") as i32; + .to_length(context)? + .try_into() + .map_err(interror_to_value)?; let mut idx = match args.get(1) { Some(from_idx_ptr) => { - let from_idx = from_idx_ptr.as_number().unwrap() as i32; + let from_idx = from_idx_ptr.to_integer(context)?; - if from_idx >= 0 { - min(from_idx, len - 1) + if !from_idx.is_finite() { + return Ok(Value::from(-1)); + } else if from_idx < 0.0 { + len + f64_to_isize(from_idx)? } else { - len + from_idx + min(f64_to_isize(from_idx)?, len - 1) } } None => len - 1, @@ -773,7 +789,7 @@ impl Array { let check_element = this.get_field(idx).clone(); if check_element.strict_equals(&search_element) { - return Ok(Value::from(idx)); + return Ok(Value::from(i32::try_from(idx).map_err(interror_to_value)?)); } idx -= 1; @@ -802,7 +818,7 @@ impl Array { } let callback = &args[0]; let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - let len = this.get_field("length").as_number().unwrap() as i32; + let len = this.get_field("length").to_length(context)?; for i in 0..len { let element = this.get_field(i); let arguments = [element.clone(), Value::from(i), this.clone()]; @@ -837,7 +853,7 @@ impl Array { let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").as_number().unwrap() as i32; + let length = this.get_field("length").to_length(context)?; for i in 0..length { let element = this.get_field(i); @@ -846,11 +862,12 @@ impl Array { let result = context.call(predicate_arg, &this_arg, &arguments)?; if result.to_boolean() { - return Ok(Value::rational(f64::from(i))); + let result = i32::try_from(i).map_err(interror_to_value)?; + return Ok(Value::integer(result)); } } - Ok(Value::rational(-1_f64)) + Ok(Value::integer(-1)) } /// `Array.prototype.fill( value[, start[, end]] )` @@ -865,27 +882,12 @@ 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: i32 = this.get_field("length").as_number().unwrap() as i32; + 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 = args.get(1).unwrap_or(&default_value).to_number(context)? as i32; - let relative_end_val = args.get(2).unwrap_or(&default_value); - let relative_end = if relative_end_val.is_undefined() { - len - } else { - relative_end_val.to_number(context)? as i32 - }; - let start = if relative_start < 0 { - max(len + relative_start, 0) - } else { - min(relative_start, len) - }; - let fin = if relative_end < 0 { - max(len + relative_end, 0) - } else { - min(relative_end, len) - }; + let start = Self::get_relative_start(context, args.get(1), len)?; + let fin = Self::get_relative_end(context, args.get(2), len)?; for i in start..fin { this.set_field(i, value.clone()); @@ -904,10 +906,14 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes - pub(crate) fn includes_value(this: &Value, args: &[Value], _: &mut Context) -> Result { + pub(crate) fn includes_value( + this: &Value, + args: &[Value], + context: &mut Context, + ) -> Result { let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").as_number().unwrap() as i32; + let length = this.get_field("length").to_length(context)?; for idx in 0..length { let check_element = this.get_field(idx).clone(); @@ -936,33 +942,16 @@ 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 = this.get_field("length").as_number().unwrap() as i32; - let start = match args.get(0) { - Some(v) => v.as_number().unwrap() as i32, - None => 0, - }; - let end = match args.get(1) { - Some(v) => v.as_number().unwrap() as i32, - None => len, - }; + let len = this.get_field("length").to_length(context)?; + let from = Self::get_relative_start(context, args.get(0), len)?; + let to = Self::get_relative_end(context, args.get(1), len)?; - let from = if start < 0 { - max(len.wrapping_add(start), 0) - } else { - min(start, len) - }; - let to = if end < 0 { - max(len.wrapping_add(end), 0) - } else { - min(end, len) - }; - - let span = max(to.wrapping_sub(from), 0); + 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) @@ -989,7 +978,7 @@ impl Array { let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = this.get_field("length").as_number().unwrap() as i32; + let length = this.get_field("length").to_length(context)?; let new = Self::new_array(context)?; @@ -1011,7 +1000,7 @@ impl Array { }) .collect::>(); - Self::construct_array(&new, &values) + Self::construct_array(&new, &values, context) } /// Array.prototype.some ( callbackfn [ , thisArg ] ) @@ -1042,7 +1031,7 @@ impl Array { Value::undefined() }; let mut i = 0; - let max_len = this.get_field("length").as_number().unwrap() as i32; + let max_len = this.get_field("length").to_length(context)?; let mut len = max_len; while i < len { let element = this.get_field(i); @@ -1052,10 +1041,7 @@ impl Array { return Ok(Value::from(true)); } // the length of the array must be updated because the callback can mutate it. - len = min( - max_len, - this.get_field("length").as_number().unwrap() as i32, - ); + len = min(max_len, this.get_field("length").to_length(context)?); i += 1; } Ok(Value::from(false)) @@ -1246,4 +1232,89 @@ impl Array { pub(crate) fn entries(this: &Value, _: &[Value], context: &mut Context) -> Result { ArrayIterator::create_array_iterator(context, this.clone(), ArrayIterationKind::KeyAndValue) } + + /// Represents the algorithm to calculate `relativeStart` (or `k`) in array functions. + pub(super) fn get_relative_start( + context: &mut Context, + arg: Option<&Value>, + len: usize, + ) -> Result { + let default_value = Value::undefined(); + // 1. Let relativeStart be ? ToIntegerOrInfinity(start). + let relative_start = arg + .unwrap_or(&default_value) + .to_integer_or_infinity(context)?; + match relative_start { + // 2. If relativeStart is -∞, let k be 0. + IntegerOrInfinity::NegativeInfinity => Ok(0), + // 3. Else if relativeStart < 0, let k be max(len + relativeStart, 0). + IntegerOrInfinity::Integer(i) if i < 0 => Self::offset(len as u64, i) + .try_into() + .map_err(interror_to_value), + // 4. Else, let k be min(relativeStart, len). + // Both `as` casts are safe as both variables are non-negative + IntegerOrInfinity::Integer(i) => (i as u64) + .min(len as u64) + .try_into() + .map_err(interror_to_value), + + // Special case - postive infinity. `len` is always smaller than +inf, thus from (4) + IntegerOrInfinity::PositiveInfinity => Ok(len), + } + } + + /// Represents the algorithm to calculate `relativeEnd` (or `final`) in array functions. + pub(super) fn get_relative_end( + context: &mut Context, + arg: Option<&Value>, + len: usize, + ) -> Result { + let default_value = Value::undefined(); + let value = arg.unwrap_or(&default_value); + // 1. If end is undefined, let relativeEnd be len [and return it] + if value.is_undefined() { + Ok(len) + } else { + // 1. cont, else let relativeEnd be ? ToIntegerOrInfinity(end). + let relative_end = value.to_integer_or_infinity(context)?; + match relative_end { + // 2. If relativeEnd is -∞, let final be 0. + IntegerOrInfinity::NegativeInfinity => Ok(0), + // 3. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). + IntegerOrInfinity::Integer(i) if i < 0 => Self::offset(len as u64, i) + .try_into() + .map_err(interror_to_value), + // 4. Else, let final be min(relativeEnd, len). + // Both `as` casts are safe as both variables are non-negative + IntegerOrInfinity::Integer(i) => (i as u64) + .min(len as u64) + .try_into() + .map_err(interror_to_value), + + // Special case - postive infinity. `len` is always smaller than +inf, thus from (4) + IntegerOrInfinity::PositiveInfinity => Ok(len), + } + } + } + + fn offset(len: u64, i: i64) -> u64 { + // `Number::MIN_SAFE_INTEGER > i64::MIN` so this should always hold + debug_assert!(i < 0 && i != i64::MIN); + // `i.staurating_neg()` will always be less than `u64::MAX` + len.saturating_sub(i.saturating_neg() as u64) + } +} + +fn f64_to_isize(v: f64) -> Result { + v.to_isize() + .ok_or_else(|| Value::string("cannot convert f64 to isize - out of range")) +} + +fn f64_to_usize(v: f64) -> Result { + v.to_usize() + .ok_or_else(|| Value::string("cannot convert f64 to usize - out of range")) +} + +fn interror_to_value(err: std::num::TryFromIntError) -> Value { + Value::string(format!("{}", err)) } diff --git a/boa/src/builtins/array/tests.rs b/boa/src/builtins/array/tests.rs index 869928ccea..e4b3a6c5cc 100644 --- a/boa/src/builtins/array/tests.rs +++ b/boa/src/builtins/array/tests.rs @@ -1,3 +1,5 @@ +use super::Array; +use crate::builtins::Number; use crate::{forward, Context, Value}; #[test] @@ -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) + ); +} diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index eb609dba2a..536a915834 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -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 diff --git a/boa/src/builtins/map/map_iterator.rs b/boa/src/builtins/map/map_iterator.rs index 71d0fb20d2..900484a258 100644 --- a/boa/src/builtins/map/map_iterator.rs +++ b/boa/src/builtins/map/map_iterator.rs @@ -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, diff --git a/boa/src/syntax/ast/node/array/mod.rs b/boa/src/syntax/ast/node/array/mod.rs index 4749777c65..a2e31f3ba0 100644 --- a/boa/src/syntax/ast/node/array/mod.rs +++ b/boa/src/syntax/ast/node/array/mod.rs @@ -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) } } diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 34a5e6c5ac..69ac236ee0 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -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] @@ -863,6 +871,37 @@ impl Value { Err(context.construct_type_error("Property description must be an object")) } } + + /// Converts argument to an integer, +∞, or -∞. + /// + /// See: + pub fn to_integer_or_infinity(&self, context: &mut Context) -> Result { + // 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 { diff --git a/boa/src/value/tests.rs b/boa/src/value/tests.rs index 0d8420cd6d..bf0afd9e46 100644 --- a/boa/src/value/tests.rs +++ b/boa/src/value/tests.rs @@ -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`