From 9159f9072560e23cca2eea4b2899486efbbc0196 Mon Sep 17 00:00:00 2001 From: Haled Odat Date: Sun, 23 Apr 2023 09:43:51 +0000 Subject: [PATCH] Fix `PropertyKey` index parse (#2843) Fixes incorrect parsing of index property keys, such as: - `"+0"` is converted to an integer index `0`, should be a string - `"00"` is converted to an integer index `0`, should be a string - `"01"` is converted to an integer index `1`, should be a string --- boa_engine/src/property/mod.rs | 88 ++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/boa_engine/src/property/mod.rs b/boa_engine/src/property/mod.rs index fd639b6857..58601d4728 100644 --- a/boa_engine/src/property/mod.rs +++ b/boa_engine/src/property/mod.rs @@ -19,7 +19,7 @@ mod attribute; use crate::{js_string, JsString, JsSymbol, JsValue}; use boa_gc::{Finalize, Trace}; -use std::fmt; +use std::{fmt, iter::FusedIterator}; pub use attribute::Attribute; @@ -569,13 +569,72 @@ pub enum PropertyKey { Index(u32), } +/// Utility function for parsing [`PropertyKey`]. +fn parse_u32_index(mut input: I) -> Option +where + I: Iterator + ExactSizeIterator + FusedIterator, + T: Into, +{ + // min: 0 --> 1 char + // max: 4_294_967_296 --> 10 chars + // + // Max char range: [1, 10] inclusive. + const MAX_CHAR_COUNT: usize = 10; + + const CHAR_ZERO: u16 = b'0' as u16; + const CHAR_NINE: u16 = b'9' as u16; + + // Eliminate any string if it's greater than the max char count. + let len = input.len(); + if len > MAX_CHAR_COUNT { + return None; + } + + // Helper function, for converting character to digit [0, 9]. + let to_digit = |c: u16| -> Option { + if matches!(c, CHAR_ZERO..=CHAR_NINE) { + Some(u32::from(c - CHAR_ZERO)) + } else { + None + } + }; + + let byte = input.next()?.into(); + if byte == CHAR_ZERO { + if len == 1 { + return Some(0); + } + + // String "012345" is not a valid index. + return None; + } + + let mut result = to_digit(byte)?; + + // If the len is equal to max chars, then we need to do checked opterations, + // in case of overflows. If less use unchecked versions. + if len == MAX_CHAR_COUNT { + for c in input { + result = result.checked_mul(10)?.checked_add(to_digit(c.into())?)?; + } + } else { + for c in input { + result = result * 10 + to_digit(c.into())?; + } + } + + Some(result) +} + impl From<&[u16]> for PropertyKey { #[inline] fn from(string: &[u16]) -> Self { - debug_assert!(String::from_utf16(string) - .expect("should be ascii string") - .parse::() - .is_err()); + debug_assert!(parse_u32_index( + String::from_utf16(string) + .expect("should be ascii string") + .bytes() + ) + .is_none()); Self::String(string.into()) } } @@ -583,38 +642,29 @@ impl From<&[u16]> for PropertyKey { impl From for PropertyKey { #[inline] fn from(string: JsString) -> Self { - string - .to_std_string() - .ok() - .and_then(|s| s.parse().ok()) - .map_or(Self::String(string), Self::Index) + parse_u32_index(string.as_slice().iter().copied()).map_or(Self::String(string), Self::Index) } } impl From<&str> for PropertyKey { #[inline] fn from(string: &str) -> Self { - string - .parse() - .map_or_else(|_| Self::String(string.into()), Self::Index) + parse_u32_index(string.bytes()).map_or_else(|| Self::String(string.into()), Self::Index) } } impl From for PropertyKey { #[inline] fn from(string: String) -> Self { - string - .parse() - .map_or_else(|_| Self::String(string.into()), Self::Index) + parse_u32_index(string.bytes()).map_or_else(|| Self::String(string.into()), Self::Index) } } impl From> for PropertyKey { #[inline] fn from(string: Box) -> Self { - string - .parse() - .map_or_else(|_| Self::String(string.as_ref().into()), Self::Index) + parse_u32_index(string.bytes()) + .map_or_else(|| Self::String(string.as_ref().into()), Self::Index) } }