From 46e33fa8ca010f709ef03697b92369ca0851e5a9 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 13 Mar 2023 19:33:33 -0600 Subject: [PATCH 1/4] Handle surrogates in `String.fromCodePoint` --- boa_engine/src/builtins/number/mod.rs | 2 +- boa_engine/src/builtins/string/mod.rs | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index d9043af931a..744bb4001e1 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -850,7 +850,7 @@ impl Number { /// Checks if the float argument is an integer. #[allow(clippy::float_cmp)] pub(crate) fn is_float_integer(number: f64) -> bool { - number.is_finite() && number.abs().floor() == number.abs() + number.is_finite() && number.trunc() == number } /// The abstract operation `Number::equal` takes arguments diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index bcfe708bd81..5fa46a6f2d7 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -255,22 +255,30 @@ impl String { // b. If ! IsIntegralNumber(nextCP) is false, throw a RangeError exception. if !Number::is_float_integer(nextcp) { return Err(JsNativeError::range() - .with_message(format!("invalid code point: {nextcp}")) + .with_message(format!("codepoint `{nextcp}` is not an integer")) .into()); } // c. If ℝ(nextCP) < 0 or ℝ(nextCP) > 0x10FFFF, throw a RangeError exception. if nextcp < 0.0 || nextcp > f64::from(0x0010_FFFF) { return Err(JsNativeError::range() - .with_message(format!("invalid code point: {nextcp}")) + .with_message(format!("codepoint `{nextcp}` outside of Unicode range")) .into()); } - let nextcp = - char::from_u32(nextcp as u32).expect("Checked above the range of `nextcp`"); + // SAFETY: + // - `nextcp` is not NaN (by the call to `is_float_integer`). + // - `nextcp` is not infinite (by the call to `is_float_integer`). + // - `nextcp` is in the u32 range (by the check above). + let nextcp = unsafe { nextcp.to_int_unchecked::() }; // d. Set result to the string-concatenation of result and ! UTF16EncodeCodePoint(ℝ(nextCP)). - result.extend_from_slice(nextcp.encode_utf16(&mut buf)); + result.extend_from_slice(match u16::try_from(nextcp) { + Ok(ref cp) => std::slice::from_ref(cp), + Err(_) => char::from_u32(nextcp) + .expect("u32 is in range and cannot be a surrogate by the conversion above") + .encode_utf16(&mut buf), + }); } // 3. Assert: If codePoints is empty, then result is the empty String. From 50dde008c02b799832fa171088ab9af7c989052b Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 13 Mar 2023 20:42:35 -0600 Subject: [PATCH 2/4] Add test for `fromCodePoint` --- boa_engine/src/builtins/string/tests.rs | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/boa_engine/src/builtins/string/tests.rs b/boa_engine/src/builtins/string/tests.rs index d2a51294e35..895f61b046b 100644 --- a/boa_engine/src/builtins/string/tests.rs +++ b/boa_engine/src/builtins/string/tests.rs @@ -856,3 +856,33 @@ fn search() { TestAction::assert_eq("'ba'.search(/a/)", 1), ]); } + +#[test] +fn from_code_point() { + // Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint + run_test_actions([ + TestAction::assert_eq("String.fromCodePoint(42)", "*"), + TestAction::assert_eq("String.fromCodePoint(65, 90)", "AZ"), + TestAction::assert_eq("String.fromCodePoint(0x404)", "Є"), + TestAction::assert_eq( + "String.fromCodePoint(0x2f804)", + js_string!(&[0xD87E, 0xDC04]), + ), + TestAction::assert_eq( + "String.fromCodePoint(0x1D306, 0x1D307)", + js_string!(&[0xD834, 0xDF06, 0xD834, 0xDF07]), + ), + // Should encode to unpaired surrogates + TestAction::assert_eq( + "String.fromCharCode(0xD800, 0xD8FF)", + js_string!(&[0xD800, 0xD8FF]), + ), + TestAction::assert_eq("String.fromCodePoint(9731, 9733, 9842, 0x2F804)", "☃★♲你"), + TestAction::assert_native_error("String.fromCodePoint('_')", ErrorKind::Range, "codepoint `NaN` is not an integer"), + TestAction::assert_native_error("String.fromCodePoint(Infinity)", ErrorKind::Range, "codepoint `inf` is not an integer"), + TestAction::assert_native_error("String.fromCodePoint(-1)", ErrorKind::Range, "codepoint `-1` outside of Unicode range"), + TestAction::assert_native_error("String.fromCodePoint(3.14)", ErrorKind::Range, "codepoint `3.14` is not an integer"), + TestAction::assert_native_error("String.fromCodePoint(3e-2)", ErrorKind::Range, "codepoint `0.03` is not an integer"), + TestAction::assert_native_error("String.fromCodePoint(NaN)", ErrorKind::Range, "codepoint `NaN` is not an integer"), + ]) +} From c6dfb9b531f35ebef650df151ddf06227b771715 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 13 Mar 2023 20:44:46 -0600 Subject: [PATCH 3/4] cargo fmt --- boa_engine/src/builtins/string/tests.rs | 36 ++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/boa_engine/src/builtins/string/tests.rs b/boa_engine/src/builtins/string/tests.rs index 895f61b046b..a8ecd757a5e 100644 --- a/boa_engine/src/builtins/string/tests.rs +++ b/boa_engine/src/builtins/string/tests.rs @@ -878,11 +878,35 @@ fn from_code_point() { js_string!(&[0xD800, 0xD8FF]), ), TestAction::assert_eq("String.fromCodePoint(9731, 9733, 9842, 0x2F804)", "☃★♲你"), - TestAction::assert_native_error("String.fromCodePoint('_')", ErrorKind::Range, "codepoint `NaN` is not an integer"), - TestAction::assert_native_error("String.fromCodePoint(Infinity)", ErrorKind::Range, "codepoint `inf` is not an integer"), - TestAction::assert_native_error("String.fromCodePoint(-1)", ErrorKind::Range, "codepoint `-1` outside of Unicode range"), - TestAction::assert_native_error("String.fromCodePoint(3.14)", ErrorKind::Range, "codepoint `3.14` is not an integer"), - TestAction::assert_native_error("String.fromCodePoint(3e-2)", ErrorKind::Range, "codepoint `0.03` is not an integer"), - TestAction::assert_native_error("String.fromCodePoint(NaN)", ErrorKind::Range, "codepoint `NaN` is not an integer"), + TestAction::assert_native_error( + "String.fromCodePoint('_')", + ErrorKind::Range, + "codepoint `NaN` is not an integer", + ), + TestAction::assert_native_error( + "String.fromCodePoint(Infinity)", + ErrorKind::Range, + "codepoint `inf` is not an integer", + ), + TestAction::assert_native_error( + "String.fromCodePoint(-1)", + ErrorKind::Range, + "codepoint `-1` outside of Unicode range", + ), + TestAction::assert_native_error( + "String.fromCodePoint(3.14)", + ErrorKind::Range, + "codepoint `3.14` is not an integer", + ), + TestAction::assert_native_error( + "String.fromCodePoint(3e-2)", + ErrorKind::Range, + "codepoint `0.03` is not an integer", + ), + TestAction::assert_native_error( + "String.fromCodePoint(NaN)", + ErrorKind::Range, + "codepoint `NaN` is not an integer", + ), ]) } From b940ea9e35fa7acd4cb2b0fe40af54730f16784e Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 13 Mar 2023 20:57:42 -0600 Subject: [PATCH 4/4] cargo clippy --- boa_engine/src/builtins/string/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boa_engine/src/builtins/string/tests.rs b/boa_engine/src/builtins/string/tests.rs index a8ecd757a5e..45bca332c49 100644 --- a/boa_engine/src/builtins/string/tests.rs +++ b/boa_engine/src/builtins/string/tests.rs @@ -877,7 +877,7 @@ fn from_code_point() { "String.fromCharCode(0xD800, 0xD8FF)", js_string!(&[0xD800, 0xD8FF]), ), - TestAction::assert_eq("String.fromCodePoint(9731, 9733, 9842, 0x2F804)", "☃★♲你"), + TestAction::assert_eq("String.fromCodePoint(9731, 9733, 9842, 0x4F60)", "☃★♲你"), TestAction::assert_native_error( "String.fromCodePoint('_')", ErrorKind::Range, @@ -908,5 +908,5 @@ fn from_code_point() { ErrorKind::Range, "codepoint `NaN` is not an integer", ), - ]) + ]); }