From 4c61ba87e09ac104b37c0287a38b610c78c4ffc2 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 1 Mar 2022 21:55:06 +0100 Subject: [PATCH 1/3] Make `StringToNumber` spec compliant --- boa_engine/src/string.rs | 63 +++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/boa_engine/src/string.rs b/boa_engine/src/string.rs index 53659fbc9e1..df4a0ff9e0b 100644 --- a/boa_engine/src/string.rs +++ b/boa_engine/src/string.rs @@ -472,26 +472,55 @@ impl JsString { pub(crate) fn string_to_number(&self) -> f64 { let string = self.trim_matches(is_trimmable_whitespace); - // TODO: write our own lexer to match syntax StrDecimalLiteral match string { - "" => 0.0, - "Infinity" | "+Infinity" => f64::INFINITY, - "-Infinity" => f64::NEG_INFINITY, - _ if matches!( - string - .chars() - .take(4) - .collect::() - .to_ascii_lowercase() - .as_str(), - "inf" | "+inf" | "-inf" | "nan" | "+nan" | "-nan" - ) => - { - // Prevent fast_float from parsing "inf", "+inf" as Infinity and "-inf" as -Infinity - f64::NAN + "" => return 0.0, + "inf" | "+inf" | "-inf" | "nan" | "+nan" | "-nan" => return f64::NAN, + "Infinity" | "+Infinity" => return f64::INFINITY, + "-Infinity" => return f64::NEG_INFINITY, + _ => {} + } + + let mut s = string.bytes().peekable(); + + let sign = match s.peek() { + Some(b'+') => Some(1.0), + Some(b'-') => Some(-1.0), + Some(_) => None, + None => return f64::NAN, + }; + + // If it has a sign (+ or -) then it must be a float. + if sign.is_some() { + return fast_float::parse(string).unwrap_or(f64::NAN); + } + + let base = match (s.next(), s.next().as_ref().map(u8::to_ascii_lowercase)) { + (Some(b'0'), Some(b'b')) => Some(2), + (Some(b'0'), Some(b'o')) => Some(8), + (Some(b'0'), Some(b'x')) => Some(16), + _ => None, + }; + + // Parse number that begin with `0b`, `0o` and `0x`. + if let Some(base) = base { + // Fast path + if let Ok(value) = u32::from_str_radix(&string[2..], base) { + return f64::from(value); } - _ => fast_float::parse(string).unwrap_or(f64::NAN), + + // Slow path + let mut value = 0.0; + for c in s { + if let Some(digit) = char::from(c).to_digit(base) { + value = value * f64::from(base) + f64::from(digit); + } else { + return f64::NAN; + } + } + return value; } + + fast_float::parse(string).unwrap_or(f64::NAN) } } From e76e10d8aac3b7e3c28bbad96eff18ee5597b1c6 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 1 Mar 2022 22:13:26 +0100 Subject: [PATCH 2/3] Fix invalid `0b`, `0o` and `0x` parse --- boa_engine/src/string.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/boa_engine/src/string.rs b/boa_engine/src/string.rs index df4a0ff9e0b..ae8ba1b36e2 100644 --- a/boa_engine/src/string.rs +++ b/boa_engine/src/string.rs @@ -501,10 +501,15 @@ impl JsString { _ => None, }; - // Parse number that begin with `0b`, `0o` and `0x`. + // Parse numbers that begin with `0b`, `0o` and `0x`. if let Some(base) = base { + let string = &string[2..]; + if string.is_empty() { + return f64::NAN; + } + // Fast path - if let Ok(value) = u32::from_str_radix(&string[2..], base) { + if let Ok(value) = u32::from_str_radix(string, base) { return f64::from(value); } From dbef2d0e32d224755e58902037557541cbf23aa5 Mon Sep 17 00:00:00 2001 From: HalidOdat Date: Tue, 1 Mar 2022 23:37:15 +0100 Subject: [PATCH 3/3] Optimize parsing --- boa_engine/src/string.rs | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/boa_engine/src/string.rs b/boa_engine/src/string.rs index ae8ba1b36e2..0a624184b49 100644 --- a/boa_engine/src/string.rs +++ b/boa_engine/src/string.rs @@ -474,30 +474,16 @@ impl JsString { match string { "" => return 0.0, - "inf" | "+inf" | "-inf" | "nan" | "+nan" | "-nan" => return f64::NAN, - "Infinity" | "+Infinity" => return f64::INFINITY, "-Infinity" => return f64::NEG_INFINITY, + "Infinity" | "+Infinity" => return f64::INFINITY, _ => {} } - let mut s = string.bytes().peekable(); - - let sign = match s.peek() { - Some(b'+') => Some(1.0), - Some(b'-') => Some(-1.0), - Some(_) => None, - None => return f64::NAN, - }; - - // If it has a sign (+ or -) then it must be a float. - if sign.is_some() { - return fast_float::parse(string).unwrap_or(f64::NAN); - } - - let base = match (s.next(), s.next().as_ref().map(u8::to_ascii_lowercase)) { - (Some(b'0'), Some(b'b')) => Some(2), - (Some(b'0'), Some(b'o')) => Some(8), - (Some(b'0'), Some(b'x')) => Some(16), + let mut s = string.bytes(); + let base = match (s.next(), s.next()) { + (Some(b'0'), Some(b'b' | b'B')) => Some(2), + (Some(b'0'), Some(b'o' | b'O')) => Some(8), + (Some(b'0'), Some(b'x' | b'X')) => Some(16), _ => None, }; @@ -525,7 +511,11 @@ impl JsString { return value; } - fast_float::parse(string).unwrap_or(f64::NAN) + match string { + // Handle special cases so `fast_float` does not return infinity. + "inf" | "+inf" | "-inf" => f64::NAN, + string => fast_float::parse(string).unwrap_or(f64::NAN), + } } }