From bbed0321999a349dae27088c5cb4b7b48eb58076 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Wed, 22 Jul 2020 00:30:49 +0200 Subject: [PATCH] Make `String.prototype.repeat()` ECMAScript specification compliant (#582) --- boa/src/builtins/string/mod.rs | 34 +++++++++++---- boa/src/builtins/string/tests.rs | 75 ++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 3165ef1e91..c657691751 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -41,6 +41,13 @@ impl String { /// The amount of arguments this function object takes. pub(crate) const LENGTH: usize = 1; + /// JavaScript strings must be between `0` and less than positive `Infinity` and cannot be a negative number. + /// The range of allowed values can be described like this: `[0, +∞)`. + /// + /// The resulting string can also not be larger than the maximum string size, + /// which can differ in JavaScript engines. In Boa it is `2^32 - 1` + pub(crate) const MAX_STRING_LENGTH: f64 = u32::MAX as f64; + fn this_string_value(this: &Value, ctx: &mut Interpreter) -> Result { match this { Value::String(ref string) => return Ok(string.clone()), @@ -206,16 +213,27 @@ impl String { /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.repeat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat pub(crate) fn repeat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.to_string(this)?; + let object = ctx.require_object_coercible(this)?; + let string = ctx.to_string(object)?; - let repeat_times = usize::from( - args.get(0) - .expect("failed to get argument for String method"), - ); + if let Some(arg) = args.get(0) { + let n = ctx.to_integer(arg)?; + if n < 0.0 { + return ctx.throw_range_error("repeat count cannot be a negative number"); + } + + if n.is_infinite() { + return ctx.throw_range_error("repeat count cannot be infinity"); + } - Ok(Value::from(primitive_val.repeat(repeat_times))) + if n * (string.len() as f64) > Self::MAX_STRING_LENGTH { + return ctx + .throw_range_error("repeat count must not overflow maximum string length"); + } + Ok(string.repeat(n as usize).into()) + } else { + Ok("".into()) + } } /// `String.prototype.slice( beginIndex [, endIndex] )` diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index 18b9b8fe99..c1b6786858 100644 --- a/boa/src/builtins/string/tests.rs +++ b/boa/src/builtins/string/tests.rs @@ -128,6 +128,81 @@ fn repeat() { assert_eq!(forward(&mut engine, "zh.repeat(2)"), "中文中文"); } +#[test] +fn repeat_throws_when_count_is_negative() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!( + forward( + &mut engine, + r#" + try { + 'x'.repeat(-1) + } catch (e) { + e.toString() + } + "# + ), + "RangeError: repeat count cannot be a negative number" + ); +} + +#[test] +fn repeat_throws_when_count_is_infinity() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!( + forward( + &mut engine, + r#" + try { + 'x'.repeat(Infinity) + } catch (e) { + e.toString() + } + "# + ), + "RangeError: repeat count cannot be infinity" + ); +} + +#[test] +fn repeat_throws_when_count_overflows_max_length() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + + assert_eq!( + forward( + &mut engine, + r#" + try { + 'x'.repeat(2 ** 64) + } catch (e) { + e.toString() + } + "# + ), + "RangeError: repeat count must not overflow maximum string length" + ); +} + +#[test] +fn repeat_generic() { + let realm = Realm::create(); + let mut engine = Interpreter::new(realm); + let init = "Number.prototype.repeat = String.prototype.repeat;"; + + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "(0).repeat(0)"), ""); + assert_eq!(forward(&mut engine, "(1).repeat(1)"), "1"); + + assert_eq!(forward(&mut engine, "(1).repeat(5)"), "11111"); + assert_eq!(forward(&mut engine, "(12).repeat(3)"), "121212"); +} + #[test] fn replace() { let realm = Realm::create();