From 8f57be25ca23b06e71e330f839d93121e48c8277 Mon Sep 17 00:00:00 2001 From: Tpt Date: Tue, 18 Apr 2023 11:28:07 +0200 Subject: [PATCH] xsd:decimal parsing: properly detect underflow checked_div is only failing on division by 0... --- lib/oxsdatatypes/src/decimal.rs | 84 +++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/lib/oxsdatatypes/src/decimal.rs b/lib/oxsdatatypes/src/decimal.rs index 43a9a8de..82d27567 100644 --- a/lib/oxsdatatypes/src/decimal.rs +++ b/lib/oxsdatatypes/src/decimal.rs @@ -370,49 +370,57 @@ impl FromStr for Decimal { return Err(PARSE_UNEXPECTED_END); } - let (sign, mut cursor) = match input.first() { - Some(b'+') => (1, 1), - Some(b'-') => (-1, 1), - _ => (1, 0), + let (sign, mut input) = match input.first() { + Some(b'+') => (1, &input[1..]), + Some(b'-') => (-1, &input[1..]), + _ => (1, input), }; let mut value = 0_i128; - let mut with_before_dot = false; - while cursor < input.len() && b'0' <= input[cursor] && input[cursor] <= b'9' { - value = value - .checked_mul(10) - .ok_or(PARSE_OVERFLOW)? - .checked_add((input[cursor] - b'0').into()) - .ok_or(PARSE_OVERFLOW)?; - cursor += 1; - with_before_dot = true; - } - - let mut exp = DECIMAL_PART_POW; - if input.len() > cursor { - if input[cursor] != b'.' { - return Err(PARSE_UNEXPECTED_CHAR); - } - cursor += 1; - - let mut with_after_dot = false; - while cursor < input.len() && b'0' <= input[cursor] && input[cursor] <= b'9' { - exp = exp.checked_div(10).ok_or(PARSE_UNDERFLOW)?; + let with_before_dot = input.first().map_or(false, |c| c.is_ascii_digit()); + while let Some(c) = input.first() { + if c.is_ascii_digit() { value = value .checked_mul(10) .ok_or(PARSE_OVERFLOW)? - .checked_add((input[cursor] - b'0').into()) + .checked_add((*c - b'0').into()) .ok_or(PARSE_OVERFLOW)?; - cursor += 1; - with_after_dot = true; + input = &input[1..]; + } else { + break; } + } - if !with_before_dot && !with_after_dot { + let mut exp = DECIMAL_PART_POW; + if let Some(c) = input.first() { + if *c != b'.' { + return Err(PARSE_UNEXPECTED_CHAR); + } + input = &input[1..]; + if input.is_empty() && !with_before_dot { //We only have a dot return Err(PARSE_UNEXPECTED_END); } - if input.len() > cursor { - return Err(PARSE_UNEXPECTED_CHAR); + while input.last() == Some(&b'0') { + // Hack to avoid underflows + input = &input[..input.len() - 1]; + } + while let Some(c) = input.first() { + if c.is_ascii_digit() { + exp /= 10; + value = value + .checked_mul(10) + .ok_or(PARSE_OVERFLOW)? + .checked_add((*c - b'0').into()) + .ok_or(PARSE_OVERFLOW)?; + input = &input[1..]; + } else { + return Err(PARSE_UNEXPECTED_CHAR); + } + } + if exp == 0 { + //Underflow + return Err(PARSE_UNDERFLOW); } } else if !with_before_dot { //It's empty @@ -591,6 +599,14 @@ mod tests { #[test] fn from_str() -> Result<(), ParseDecimalError> { + assert!(Decimal::from_str("").is_err()); + assert!(Decimal::from_str("+").is_err()); + assert!(Decimal::from_str("-").is_err()); + assert!(Decimal::from_str(".").is_err()); + assert!(Decimal::from_str("+.").is_err()); + assert!(Decimal::from_str("-.").is_err()); + assert!(Decimal::from_str("a").is_err()); + assert!(Decimal::from_str(".a").is_err()); assert_eq!(Decimal::from_str("210")?.to_string(), "210"); assert_eq!(Decimal::from_str("1000")?.to_string(), "1000"); assert_eq!(Decimal::from_str("-1.23")?.to_string(), "-1.23"); @@ -616,6 +632,12 @@ mod tests { )?, Decimal::MIN.checked_add(Decimal::step()).unwrap() ); + assert!(Decimal::from_str("0.0000000000000000001").is_err()); + assert!(Decimal::from_str("1000000000000000000000").is_err()); + assert_eq!( + Decimal::from_str("0.100000000000000000000000000").unwrap(), + Decimal::from_str("0.1").unwrap() + ); Ok(()) }