From 07838568f92693d9cb7e3988129e8159d7521268 Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Tue, 21 Aug 2018 16:13:58 -0400 Subject: [PATCH 1/7] Implement `FromStr` for `Decimal`/`UDecimal`. Signed-off-by: Jean Pierre Dudey --- src/util/decimal.rs | 207 ++++++++++++++++++++++++++++++++------------ 1 file changed, 151 insertions(+), 56 deletions(-) diff --git a/src/util/decimal.rs b/src/util/decimal.rs index 9b839c6..a6b74d3 100644 --- a/src/util/decimal.rs +++ b/src/util/decimal.rs @@ -22,9 +22,11 @@ //! use std::{fmt, ops}; +#[cfg(feature = "serde-decimal")] use std::error; +#[cfg(feature = "serde-decimal")] use std::str::FromStr; #[cfg(feature = "serde-decimal")] use serde; -#[cfg(feature = "serde-decimal")] use strason::Json; +#[cfg(feature = "serde-decimal")] use strason::{self, Json}; /// A fixed-point decimal type #[derive(Copy, Clone, Debug, Eq, Ord)] @@ -126,6 +128,47 @@ impl Decimal { /// Returns whether or not the number is nonnegative #[inline] pub fn nonnegative(&self) -> bool { self.mantissa >= 0 } + + // Converts a JSON number to a Decimal previously parsed by strason + #[cfg(feature = "serde-decimal")] + fn parse_decimal(s: &str) -> Decimal { + // We know this will be a well-formed Json number, so we can + // be pretty lax about parsing + let mut negative = false; + let mut past_dec = false; + let mut exponent = 0; + let mut mantissa = 0i64; + + for b in s.as_bytes() { + match *b { + b'-' => { negative = true; } + b'0'...b'9' => { + mantissa = 10 * mantissa + (b - b'0') as i64; + if past_dec { exponent += 1; } + } + b'.' => { past_dec = true; } + _ => { /* whitespace or something, just ignore it */ } + } + } + if negative { mantissa *= -1; } + Decimal { + mantissa: mantissa, + exponent: exponent, + } + } +} + +#[cfg(feature = "serde-decimal")] +impl FromStr for Decimal { + type Err = ParseDecimalError; + + /// Parses a `Decimal` from the given amount string. + fn from_str(s: &str) -> Result { + Json::from_str(s)? + .num() + .map(Decimal::parse_decimal) + .ok_or(ParseDecimalError::NotANumber) + } } #[cfg(feature = "serde-decimal")] @@ -140,35 +183,10 @@ impl<'de> serde::Deserialize<'de> for Decimal { where D: serde::Deserializer<'de>, { - let json = Json::deserialize(deserializer)?; - match json.num() { - Some(s) => { - // We know this will be a well-formed Json number, so we can - // be pretty lax about parsing - let mut negative = false; - let mut past_dec = false; - let mut exponent = 0; - let mut mantissa = 0i64; - - for b in s.as_bytes() { - match *b { - b'-' => { negative = true; } - b'0'...b'9' => { - mantissa = 10 * mantissa + (b - b'0') as i64; - if past_dec { exponent += 1; } - } - b'.' => { past_dec = true; } - _ => { /* whitespace or something, just ignore it */ } - } - } - if negative { mantissa *= -1; } - Ok(Decimal { - mantissa: mantissa, - exponent: exponent, - }) - } - None => Err(serde::de::Error::custom("expected decimal, got non-numeric")) - } + Json::deserialize(deserializer)? + .num() + .map(Decimal::parse_decimal) + .ok_or(serde::de::Error::custom("expected decimal, got non-numeric")) } } @@ -260,6 +278,44 @@ impl UDecimal { self.mantissa * 10u64.pow((exponent - self.exponent) as u32) } } + + // Converts a JSON number to a Decimal previously parsed by strason + #[cfg(feature = "serde-decimal")] + fn parse_udecimal(s: &str) -> UDecimal { + // We know this will be a well-formed Json number, so we can + // be pretty lax about parsing + let mut past_dec = false; + let mut exponent = 0; + let mut mantissa = 0u64; + + for b in s.as_bytes() { + match *b { + b'0'...b'9' => { + mantissa = 10 * mantissa + (b - b'0') as u64; + if past_dec { exponent += 1; } + } + b'.' => { past_dec = true; } + _ => { /* whitespace or something, just ignore it */ } + } + } + UDecimal { + mantissa: mantissa, + exponent: exponent, + } + } +} + +#[cfg(feature = "serde-decimal")] +impl FromStr for UDecimal { + type Err = ParseDecimalError; + + /// Parses a `UDecimal` from the given amount string. + fn from_str(s: &str) -> Result { + Json::from_str(s)? + .num() + .map(UDecimal::parse_udecimal) + .ok_or(ParseDecimalError::NotANumber) + } } #[cfg(feature = "serde-decimal")] @@ -274,32 +330,10 @@ impl<'de> serde::Deserialize<'de> for UDecimal { where D: serde::Deserializer<'de>, { - let json = Json::deserialize(deserializer)?; - match json.num() { - Some(s) => { - // We know this will be a well-formed Json number, so we can - // be pretty lax about parsing - let mut past_dec = false; - let mut exponent = 0; - let mut mantissa = 0u64; - - for b in s.as_bytes() { - match *b { - b'0'...b'9' => { - mantissa = 10 * mantissa + (b - b'0') as u64; - if past_dec { exponent += 1; } - } - b'.' => { past_dec = true; } - _ => { /* whitespace or something, just ignore it */ } - } - } - Ok(UDecimal { - mantissa: mantissa, - exponent: exponent, - }) - } - None => Err(serde::de::Error::custom("expected decimal, got non-numeric")) - } + Json::deserialize(deserializer)? + .num() + .map(UDecimal::parse_udecimal) + .ok_or(serde::de::Error::custom("expected decimal, got non-numeric")) } } @@ -321,6 +355,51 @@ impl serde::Serialize for UDecimal { } } +/// Errors that occur during `Decimal`/`UDecimal` parsing. +#[cfg(feature = "serde-decimal")] +#[derive(Debug)] +pub enum ParseDecimalError { + /// An error ocurred while parsing the JSON number. + Json(strason::Error), + /// Not a number. + NotANumber, +} + +#[cfg(feature = "serde-decimal")] +#[doc(hidden)] +impl From for ParseDecimalError { + fn from(e: strason::Error) -> ParseDecimalError { + ParseDecimalError::Json(e) + } +} + +#[cfg(feature = "serde-decimal")] +impl fmt::Display for ParseDecimalError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match *self { + ParseDecimalError::Json(ref e) => fmt::Display::fmt(e, fmt), + ParseDecimalError::NotANumber => fmt.write_str("not a valid JSON number") + } + } +} + +#[cfg(feature = "serde-decimal")] +impl error::Error for ParseDecimalError { + fn description(&self) -> &str { + match *self { + ParseDecimalError::Json(ref e) => e.description(), + ParseDecimalError::NotANumber => "not a valid JSON number", + } + } + + fn cause(&self) -> Option<&error::Error> { + match *self { + ParseDecimalError::Json(ref e) => Some(e), + _ => None, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -468,4 +547,20 @@ mod tests { let dec: UDecimal = json.into_deserialize().unwrap(); assert_eq!(dec, UDecimal::new(98000, 7)); } + + #[test] + #[cfg(feature = "serde-decimal")] + fn parse_decimal_udecimal() { + let dec = "0.00980000".parse::().unwrap(); + assert_eq!(dec, Decimal::new(980000, 8)); + + let dec = "0.00980000".parse::().unwrap(); + assert_eq!(dec, UDecimal::new(980000, 8)); + + let dec = "0.00980".parse::().unwrap(); + assert_eq!(dec, Decimal::new(98000, 7)); + + let dec = "0.00980".parse::().unwrap(); + assert_eq!(dec, UDecimal::new(98000, 7)); + } } From be0d54738b8da8329683394a9fbe844653459c5d Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Tue, 21 Aug 2018 16:21:29 -0400 Subject: [PATCH 2/7] Add fuzz tests for `Decimal`/`UDecimal` parsing. Signed-off-by: Jean Pierre Dudey --- fuzz/Cargo.toml | 10 +++- fuzz/fuzz_targets/deserialize_decimal.rs | 61 +++++++++++++++++++++++ fuzz/fuzz_targets/deserialize_udecimal.rs | 61 +++++++++++++++++++++++ 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 fuzz/fuzz_targets/deserialize_decimal.rs create mode 100644 fuzz/fuzz_targets/deserialize_udecimal.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 51437b2..990bf0a 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -14,7 +14,7 @@ honggfuzz_fuzz = ["honggfuzz"] [dependencies] honggfuzz = { version = "0.5", optional = true } afl = { version = "0.3", optional = true } -bitcoin = { path = "..", features = ["fuzztarget"] } +bitcoin = { path = "..", features = ["fuzztarget", "serde-decimal"] } # Prevent this from interfering with workspaces [workspace] @@ -35,3 +35,11 @@ path = "fuzz_targets/deserialize_transaction.rs" [[bin]] name = "deserialize_address" path = "fuzz_targets/deserialize_address.rs" + +[[bin]] +name = "deserialize_decimal" +path = "fuzz_targets/deserialize_decimal.rs" + +[[bin]] +name = "deserialize_udecimal" +path = "fuzz_targets/deserialize_udecimal.rs" diff --git a/fuzz/fuzz_targets/deserialize_decimal.rs b/fuzz/fuzz_targets/deserialize_decimal.rs new file mode 100644 index 0000000..002bb76 --- /dev/null +++ b/fuzz/fuzz_targets/deserialize_decimal.rs @@ -0,0 +1,61 @@ +extern crate bitcoin; +use std::str::FromStr; +fn do_test(data: &[u8]) { + let data_str = String::from_utf8_lossy(data); + let dec = match bitcoin::util::decimal::Decimal::from_str(&data_str) { + Ok(dec) => dec, + Err(_) => return, + }; + let dec_roundtrip = match bitcoin::util::decimal::Decimal::from_str(&dec.to_string()) { + Ok(dec) => dec, + Err(_) => return, + }; + assert_eq!(dec, dec_roundtrip); +} + +#[cfg(feature = "afl")] +extern crate afl; +#[cfg(feature = "afl")] +fn main() { + afl::read_stdio_bytes(|data| { + do_test(&data); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + do_test(data); + }); + } +} + +#[cfg(test)] +mod tests { + fn extend_vec_from_hex(hex: &str, out: &mut Vec) { + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'...b'F' => b |= c - b'A' + 10, + b'a'...b'f' => b |= c - b'a' + 10, + b'0'...b'9' => b |= c - b'0', + _ => panic!("Bad hex"), + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + } + + #[test] + fn duplicate_crash() { + let mut a = Vec::new(); + extend_vec_from_hex("00000000", &mut a); + super::do_test(&a); + } +} diff --git a/fuzz/fuzz_targets/deserialize_udecimal.rs b/fuzz/fuzz_targets/deserialize_udecimal.rs new file mode 100644 index 0000000..558b1e3 --- /dev/null +++ b/fuzz/fuzz_targets/deserialize_udecimal.rs @@ -0,0 +1,61 @@ +extern crate bitcoin; +use std::str::FromStr; +fn do_test(data: &[u8]) { + let data_str = String::from_utf8_lossy(data); + let dec = match bitcoin::util::decimal::UDecimal::from_str(&data_str) { + Ok(dec) => dec, + Err(_) => return, + }; + let dec_roundtrip = match bitcoin::util::decimal::UDecimal::from_str(&dec.to_string()) { + Ok(dec) => dec, + Err(_) => return, + }; + assert_eq!(dec, dec_roundtrip); +} + +#[cfg(feature = "afl")] +extern crate afl; +#[cfg(feature = "afl")] +fn main() { + afl::read_stdio_bytes(|data| { + do_test(&data); + }); +} + +#[cfg(feature = "honggfuzz")] +#[macro_use] extern crate honggfuzz; +#[cfg(feature = "honggfuzz")] +fn main() { + loop { + fuzz!(|data| { + do_test(data); + }); + } +} + +#[cfg(test)] +mod tests { + fn extend_vec_from_hex(hex: &str, out: &mut Vec) { + let mut b = 0; + for (idx, c) in hex.as_bytes().iter().enumerate() { + b <<= 4; + match *c { + b'A'...b'F' => b |= c - b'A' + 10, + b'a'...b'f' => b |= c - b'a' + 10, + b'0'...b'9' => b |= c - b'0', + _ => panic!("Bad hex"), + } + if (idx & 1) == 1 { + out.push(b); + b = 0; + } + } + } + + #[test] + fn duplicate_crash() { + let mut a = Vec::new(); + extend_vec_from_hex("00000000", &mut a); + super::do_test(&a); + } +} From 455bc66d3c925784abaf1a64b893a3254f5a324e Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Tue, 21 Aug 2018 21:29:07 -0400 Subject: [PATCH 3/7] Fix parsing for numbers that are too big to fit in a `Decimal`/`UDecimal`. Signed-off-by: Jean Pierre Dudey --- src/util/decimal.rs | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/util/decimal.rs b/src/util/decimal.rs index a6b74d3..6cf46c1 100644 --- a/src/util/decimal.rs +++ b/src/util/decimal.rs @@ -131,7 +131,7 @@ impl Decimal { // Converts a JSON number to a Decimal previously parsed by strason #[cfg(feature = "serde-decimal")] - fn parse_decimal(s: &str) -> Decimal { + fn parse_decimal(s: &str) -> Result { // We know this will be a well-formed Json number, so we can // be pretty lax about parsing let mut negative = false; @@ -143,7 +143,10 @@ impl Decimal { match *b { b'-' => { negative = true; } b'0'...b'9' => { - mantissa = 10 * mantissa + (b - b'0') as i64; + match 10i64.overflowing_mul(mantissa + (b - b'0') as i64) { + (_, true) => return Err(ParseDecimalError::TooBig), + (n, false) => mantissa = n, + } if past_dec { exponent += 1; } } b'.' => { past_dec = true; } @@ -151,10 +154,10 @@ impl Decimal { } } if negative { mantissa *= -1; } - Decimal { + Ok(Decimal { mantissa: mantissa, exponent: exponent, - } + }) } } @@ -166,8 +169,8 @@ impl FromStr for Decimal { fn from_str(s: &str) -> Result { Json::from_str(s)? .num() - .map(Decimal::parse_decimal) .ok_or(ParseDecimalError::NotANumber) + .and_then(Decimal::parse_decimal) } } @@ -183,10 +186,12 @@ impl<'de> serde::Deserialize<'de> for Decimal { where D: serde::Deserializer<'de>, { + use serde::de; + Json::deserialize(deserializer)? .num() - .map(Decimal::parse_decimal) - .ok_or(serde::de::Error::custom("expected decimal, got non-numeric")) + .ok_or(de::Error::custom("expected decimal, got non-numeric")) + .and_then(|s| Decimal::parse_decimal(s).map_err(de::Error::custom)) } } @@ -281,7 +286,7 @@ impl UDecimal { // Converts a JSON number to a Decimal previously parsed by strason #[cfg(feature = "serde-decimal")] - fn parse_udecimal(s: &str) -> UDecimal { + fn parse_udecimal(s: &str) -> Result { // We know this will be a well-formed Json number, so we can // be pretty lax about parsing let mut past_dec = false; @@ -291,17 +296,20 @@ impl UDecimal { for b in s.as_bytes() { match *b { b'0'...b'9' => { - mantissa = 10 * mantissa + (b - b'0') as u64; + match 10u64.overflowing_mul(mantissa + (b - b'0') as u64) { + (_, true) => return Err(ParseDecimalError::TooBig), + (n, false) => mantissa = n, + } if past_dec { exponent += 1; } } b'.' => { past_dec = true; } _ => { /* whitespace or something, just ignore it */ } } } - UDecimal { + Ok(UDecimal { mantissa: mantissa, exponent: exponent, - } + }) } } @@ -313,8 +321,8 @@ impl FromStr for UDecimal { fn from_str(s: &str) -> Result { Json::from_str(s)? .num() - .map(UDecimal::parse_udecimal) .ok_or(ParseDecimalError::NotANumber) + .and_then(UDecimal::parse_udecimal) } } @@ -330,10 +338,12 @@ impl<'de> serde::Deserialize<'de> for UDecimal { where D: serde::Deserializer<'de>, { + use serde::de; + Json::deserialize(deserializer)? .num() - .map(UDecimal::parse_udecimal) - .ok_or(serde::de::Error::custom("expected decimal, got non-numeric")) + .ok_or(de::Error::custom("expected decimal, got non-numeric")) + .and_then(|s| UDecimal::parse_udecimal(s).map_err(de::Error::custom)) } } @@ -363,6 +373,8 @@ pub enum ParseDecimalError { Json(strason::Error), /// Not a number. NotANumber, + /// The number is too big to fit in a `Decimal` or `UDecimal`. + TooBig, } #[cfg(feature = "serde-decimal")] @@ -378,7 +390,8 @@ impl fmt::Display for ParseDecimalError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { ParseDecimalError::Json(ref e) => fmt::Display::fmt(e, fmt), - ParseDecimalError::NotANumber => fmt.write_str("not a valid JSON number") + ParseDecimalError::NotANumber => fmt.write_str("not a valid JSON number"), + ParseDecimalError::TooBig => fmt.write_str("number is too big"), } } } @@ -389,6 +402,7 @@ impl error::Error for ParseDecimalError { match *self { ParseDecimalError::Json(ref e) => e.description(), ParseDecimalError::NotANumber => "not a valid JSON number", + ParseDecimalError::TooBig => "number is too big", } } From e48e559740a1f91a7d9fb442ed46913244d435c0 Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Tue, 21 Aug 2018 21:31:02 -0400 Subject: [PATCH 4/7] Fix `UDecimal::parse_udecimal` identation. Signed-off-by: Jean Pierre Dudey --- src/util/decimal.rs | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/util/decimal.rs b/src/util/decimal.rs index 6cf46c1..f2c5ef1 100644 --- a/src/util/decimal.rs +++ b/src/util/decimal.rs @@ -287,29 +287,29 @@ impl UDecimal { // Converts a JSON number to a Decimal previously parsed by strason #[cfg(feature = "serde-decimal")] fn parse_udecimal(s: &str) -> Result { - // We know this will be a well-formed Json number, so we can - // be pretty lax about parsing - let mut past_dec = false; - let mut exponent = 0; - let mut mantissa = 0u64; + // We know this will be a well-formed Json number, so we can + // be pretty lax about parsing + let mut past_dec = false; + let mut exponent = 0; + let mut mantissa = 0u64; - for b in s.as_bytes() { - match *b { - b'0'...b'9' => { - match 10u64.overflowing_mul(mantissa + (b - b'0') as u64) { - (_, true) => return Err(ParseDecimalError::TooBig), - (n, false) => mantissa = n, - } - if past_dec { exponent += 1; } - } - b'.' => { past_dec = true; } - _ => { /* whitespace or something, just ignore it */ } - } - } - Ok(UDecimal { - mantissa: mantissa, - exponent: exponent, - }) + for b in s.as_bytes() { + match *b { + b'0'...b'9' => { + match 10u64.overflowing_mul(mantissa + (b - b'0') as u64) { + (_, true) => return Err(ParseDecimalError::TooBig), + (n, false) => mantissa = n, + } + if past_dec { exponent += 1; } + } + b'.' => { past_dec = true; } + _ => { /* whitespace or something, just ignore it */ } + } + } + Ok(UDecimal { + mantissa: mantissa, + exponent: exponent, + }) } } From a915bc194dd3524e1bab2966e27e5f67b7f93c91 Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Wed, 22 Aug 2018 07:22:47 -0400 Subject: [PATCH 5/7] Fix multiplication logic in decimal parsing functions. Signed-off-by: Jean Pierre Dudey --- src/util/decimal.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/util/decimal.rs b/src/util/decimal.rs index f2c5ef1..bdad3a0 100644 --- a/src/util/decimal.rs +++ b/src/util/decimal.rs @@ -143,9 +143,14 @@ impl Decimal { match *b { b'-' => { negative = true; } b'0'...b'9' => { - match 10i64.overflowing_mul(mantissa + (b - b'0') as i64) { - (_, true) => return Err(ParseDecimalError::TooBig), - (n, false) => mantissa = n, + match 10i64.checked_mul(mantissa) { + None => return Err(ParseDecimalError::TooBig), + Some(n) => { + match n.checked_add((b - b'0') as i64) { + None => return Err(ParseDecimalError::TooBig), + Some(n) => mantissa = n, + } + } } if past_dec { exponent += 1; } } @@ -296,9 +301,14 @@ impl UDecimal { for b in s.as_bytes() { match *b { b'0'...b'9' => { - match 10u64.overflowing_mul(mantissa + (b - b'0') as u64) { - (_, true) => return Err(ParseDecimalError::TooBig), - (n, false) => mantissa = n, + match 10u64.checked_mul(mantissa) { + None => return Err(ParseDecimalError::TooBig), + Some(n) => { + match n.checked_add((b - b'0') as u64) { + None => return Err(ParseDecimalError::TooBig), + Some(n) => mantissa = n, + } + } } if past_dec { exponent += 1; } } From 6902bf826c0d1ce84ea819f408209fce0ba65cbc Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Wed, 22 Aug 2018 11:26:05 -0400 Subject: [PATCH 6/7] Fix negative symbol bug in `FromStr` display implementation. The negative symbol wasn't there when `int_part` was equal to zero. Signed-off-by: Jean Pierre Dudey --- src/util/decimal.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/util/decimal.rs b/src/util/decimal.rs index bdad3a0..6762517 100644 --- a/src/util/decimal.rs +++ b/src/util/decimal.rs @@ -63,7 +63,11 @@ impl fmt::Display for Decimal { let ten = 10i64.pow(self.exponent as u32); let int_part = self.mantissa / ten; let dec_part = (self.mantissa % ten).abs(); - write!(f, "{}.{:02$}", int_part, dec_part, self.exponent) + if int_part == 0 && self.mantissa < 0 { + write!(f, "-{}.{:02$}", int_part, dec_part, self.exponent) + } else { + write!(f, "{}.{:02$}", int_part, dec_part, self.exponent) + } } } From 9cdc75a930c626f5976f001849353823e79d48e1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Dudey Date: Wed, 22 Aug 2018 14:00:30 -0400 Subject: [PATCH 7/7] Forbid exponents larger than 18. Signed-off-by: Jean Pierre Dudey --- src/util/decimal.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/util/decimal.rs b/src/util/decimal.rs index 6762517..ab308eb 100644 --- a/src/util/decimal.rs +++ b/src/util/decimal.rs @@ -156,7 +156,12 @@ impl Decimal { } } } - if past_dec { exponent += 1; } + if past_dec { + exponent += 1; + if exponent > 18 { + return Err(ParseDecimalError::TooBig); + } + } } b'.' => { past_dec = true; } _ => { /* whitespace or something, just ignore it */ } @@ -314,7 +319,12 @@ impl UDecimal { } } } - if past_dec { exponent += 1; } + if past_dec { + exponent += 1; + if exponent > 18 { + return Err(ParseDecimalError::TooBig); + } + } } b'.' => { past_dec = true; } _ => { /* whitespace or something, just ignore it */ }