Browse Source

Make `String.prototype.repeat()` ECMAScript specification compliant (#582)

pull/590/head
HalidOdat 4 years ago committed by GitHub
parent
commit
bbed032199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 34
      boa/src/builtins/string/mod.rs
  2. 75
      boa/src/builtins/string/tests.rs

34
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<RcString, Value> {
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] )`

75
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();

Loading…
Cancel
Save