diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 49cb8f49e04..f17f208a394 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -329,7 +329,7 @@ impl Json { // 7. Else if Type(space) is String, then } else if let Some(s) = space.as_string() { // a. If the length of space is 10 or less, let gap be space; otherwise let gap be the substring of space from 0 to 10. - js_string!(s.get(..=10).unwrap_or(s)) + js_string!(s.get(..10).unwrap_or(s)) // 8. Else, } else { // a. Let gap be the empty String. diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 0cd407f5e94..6249bfa6244 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -617,7 +617,7 @@ impl String { IntegerOrInfinity::Integer(n) if n == 0 => Ok(js_string!().into()), // 4. If n < 0 or n is +∞, throw a RangeError exception. _ => context.throw_range_error( - "repeat count must be a positive finite number\ + "repeat count must be a positive finite number \ that doesn't overflow the maximum string length", ), } @@ -940,7 +940,7 @@ impl String { }; // 10. Let preserved be the substring of string from 0 to position. - let preserved = &this_str[0..position]; + let preserved = &this_str[..position]; // 11. If functionalReplace is true, then // 12. Else, @@ -971,7 +971,12 @@ impl String { }; // 13. Return the string-concatenation of preserved, replacement, and the substring of string from position + searchLength. - Ok(js_string!(preserved, &replacement, &this_str[position..search_length]).into()) + Ok(js_string!( + preserved, + &replacement, + &this_str[position + search_length..] + ) + .into()) } /// `22.1.3.18 String.prototype.replaceAll ( searchValue, replaceValue )` @@ -1212,7 +1217,9 @@ impl String { let pos = if num_pos.is_nan() { IntegerOrInfinity::PositiveInfinity } else { - JsValue::new(num_pos).to_integer_or_infinity(context)? + JsValue::new(num_pos) + .to_integer_or_infinity(context) + .expect("Already called `to_number so this must not fail.") }; // 7. Let len be the length of S. @@ -1230,7 +1237,7 @@ impl String { if let Some(end) = len.checked_sub(search_len) { // 11. For each non-negative integer i starting with start such that i ≤ len - searchLen, in descending order, do - for i in (start..=end).rev() { + for i in (0..=min(start, end)).rev() { // a. Let candidate be the substring of S from i to i + searchLen. let candidate = &string[i..i + search_len]; @@ -1750,8 +1757,7 @@ impl String { .get(..lim) .unwrap_or(&this_str[..]) .iter() - .copied() - .map(|code| (u32::from(code)).into()); + .map(|code| js_string!(std::slice::from_ref(code)).into()); // c. Return ! CreateArrayFromList(codeUnits). return Ok(Array::create_array_from_list(head, context).into()); } diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index c3118869f29..8abfb0a5580 100644 --- a/boa/src/builtins/string/tests.rs +++ b/boa/src/builtins/string/tests.rs @@ -147,7 +147,8 @@ fn repeat_throws_when_count_is_negative() { } "# ), - "\"RangeError: repeat count cannot be a negative number\"" + "\"RangeError: repeat count must be a positive finite number \ + that doesn't overflow the maximum string length\"" ); } @@ -166,7 +167,8 @@ fn repeat_throws_when_count_is_infinity() { } "# ), - "\"RangeError: repeat count cannot be infinity\"" + "\"RangeError: repeat count must be a positive finite number \ + that doesn't overflow the maximum string length\"" ); } @@ -185,7 +187,8 @@ fn repeat_throws_when_count_overflows_max_length() { } "# ), - "\"RangeError: repeat count must not overflow maximum string length\"" + "\"RangeError: repeat count must be a positive finite number \ + that doesn't overflow the maximum string length\"" ); } @@ -678,13 +681,14 @@ fn split() { forward(&mut context, "['']") ); - // TODO: Support keeping invalid code point in string assert_eq!( forward( &mut context, "\'\u{1d7d8}\u{1d7d9}\u{1d7da}\u{1d7db}\'.split(\'\')" ), - forward(&mut context, "['�','�','�','�','�','�','�','�']") + // TODO: modify interner to store UTF-16 surrogates from string literals + // forward(&mut context, "['�','�','�','�','�','�','�','�']") + "[ \"\\uD835\", \"\\uDFD8\", \"\\uD835\", \"\\uDFD9\", \"\\uD835\", \"\\uDFDA\", \"\\uD835\", \"\\uDFDB\" ]" ); } @@ -1135,7 +1139,7 @@ fn string_get_property() { assert_eq!(forward(&mut context, "'abc'[2]"), "\"c\""); assert_eq!(forward(&mut context, "'abc'[3]"), "undefined"); assert_eq!(forward(&mut context, "'abc'['foo']"), "undefined"); - assert_eq!(forward(&mut context, "'😀'[0]"), "\"�\""); + assert_eq!(forward(&mut context, "'😀'[0]"), "\"\\uD83D\""); } #[test] diff --git a/boa/src/string.rs b/boa/src/string.rs index dda876f62fc..c156fe5f953 100644 --- a/boa/src/string.rs +++ b/boa/src/string.rs @@ -358,9 +358,17 @@ impl JsString { string } - /// Decode a `JsString` into a [`String`], replacing invalid data with the replacement character (`U+FFFD`). + /// Decode a `JsString` into a [`String`], replacing invalid data with + /// its escaped representation in 4 digit hexadecimal. pub fn as_std_string_lossy(&self) -> String { - String::from_utf16_lossy(self) + let mut result = String::new(); + for code_point in self.to_code_points() { + match code_point { + CodePoint::Unicode(c) => result.push(c), + CodePoint::UnpairedSurrogate(surr) => result.push_str(&format!("\\u{surr:04X}")), + } + } + result } /// Decode a `JsString` into a [`String`], returning [`Err`] if it contains any invalid data.