Repository: paupino/rust-decimal (Rust)

Issue:
`from_str` panics on large number (in debug mode)
```rust
use rust_decimal::prelude::*;

fn main() {
    println!("{:?}", Decimal::from_str("79228162514264337593543950335"));
    println!("{:?}", Decimal::from_str("79228162514264337593543950336"));
    println!("{:?}", Decimal::from_str("79228162514264337593543950337"));
    println!("{:?}", Decimal::from_str("79228162514264337593543950338"));
    println!("{:?}", Decimal::from_str("79228162514264337593543950339"));
    println!("{:?}", Decimal::from_str("79228162514264337593543950340"));
}
```
Output:
```
Ok(79228162514264337593543950335)
Err(Error { message: "Invalid decimal: overflow from carry" })
Err(Error { message: "Invalid decimal: overflow from carry" })
Err(Error { message: "Invalid decimal: overflow from carry" })
Err(Error { message: "Invalid decimal: overflow from carry" })
thread 'main' panicked at 'attempt to subtract with overflow', /home/mbu/.cargo/registry/src/github.com-1e
cc6299db9ec823/rust_decimal-1.5.0/src/decimal.rs:2326:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
<details>
<summary>Backtrace</summary>

```
stack backtrace:
   0:     0x558277986d84 - backtrace::backtrace::libunwind::trace::h90669f559fb267f0
                               at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/bac
ktrace/libunwind.rs:88
   1:     0x558277986d84 - backtrace::backtrace::trace_unsynchronized::hffde4e353d8f2f9a
                               at /cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.40/src/bac
ktrace/mod.rs:66
   2:     0x558277986d84 - std::sys_common::backtrace::_print_fmt::heaf44068b7eaaa6a
                               at src/libstd/sys_common/backtrace.rs:77
   3:     0x558277986d84 - <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::f
mt::h88671019cf081de2
                               at src/libstd/sys_common/backtrace.rs:59
   4:     0x55827799d6cc - core::fmt::write::h4e6a29ee6319c9fd
                               at src/libcore/fmt/mod.rs:1052
   5:     0x558277985407 - std::io::Write::write_fmt::hf06b1c86d898d7d6
                               at src/libstd/io/mod.rs:1426
   6:     0x558277988de5 - std::sys_common::backtrace::_print::h404ff5f2b50cae09
                               at src/libstd/sys_common/backtrace.rs:62
   7:     0x558277988de5 - std::sys_common::backtrace::print::hcc4377f1f882322e
                               at src/libstd/sys_common/backtrace.rs:49
   8:     0x558277988de5 - std::panicking::default_hook::{{closure}}::hc172eff6f35b7f39
                               at src/libstd/panicking.rs:204
   9:     0x558277988ad1 - std::panicking::default_hook::h7a68887d113f8029
                               at src/libstd/panicking.rs:224
  10:     0x55827798939a - std::panicking::rust_panic_with_hook::hb7ad5693188bdb00
                               at src/libstd/panicking.rs:472
  11:     0x558277988f80 - rust_begin_unwind
                               at src/libstd/panicking.rs:380
  12:     0x55827799cb91 - core::panicking::panic_fmt::hb1f3e14b86a3520c
                               at src/libcore/panicking.rs:85
  13:     0x55827799cadd - core::panicking::panic::hcdc9f0ba8d71d265
                               at src/libcore/panicking.rs:52
  14:     0x5582779729db - <rust_decimal::decimal::Decimal as num_traits::Num>::from_str_radix::h0f380ee27
b280e4d
                               at /home/mbu/.cargo/registry/src/github.com-1ecc6299db9ec823/rust_decimal-1
.5.0/src/decimal.rs:2326
  15:     0x558277972a0a - <rust_decimal::decimal::Decimal as core::str::FromStr>::from_str::h6c7c8eef00c2
9092
                               at /home/mbu/.cargo/registry/src/github.com-1ecc6299db9ec823/rust_decimal-1
.5.0/src/decimal.rs:2353
  16:     0x55827796f929 - rust_decimal_panic::main::h10a1d805b0dcde63
                               at src/main.rs:9
  17:     0x55827796fc00 - std::rt::lang_start::{{closure}}::hf632d91a5f17bd40
                               at /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447/src/libstd/rt.rs:67
  18:     0x558277988eb3 - std::rt::lang_start_internal::{{closure}}::hb26e39676675046f
                               at src/libstd/rt.rs:52
  19:     0x558277988eb3 - std::panicking::try::do_call::he4701ab6e48d80c0
                               at src/libstd/panicking.rs:305
  20:     0x55827798a617 - __rust_maybe_catch_panic
                               at src/libpanic_unwind/lib.rs:86
  21:     0x558277989840 - std::panicking::try::hd3de25f3cb7024b8
                               at src/libstd/panicking.rs:281
  22:     0x558277989840 - std::panic::catch_unwind::h86c02743a24e3d92
                               at src/libstd/panic.rs:394
  23:     0x558277989840 - std::rt::lang_start_internal::h9cf8802361ad86c2
                               at src/libstd/rt.rs:51
  24:     0x55827796fbd9 - std::rt::lang_start::h52e3c4b5cfec01e2
                               at /rustc/b8cedc00407a4c56a3bda1ed605c6fc166655447/src/libstd/rt.rs:67
  25:     0x55827796fa8a - main
  26:     0x7f028c571e0b - __libc_start_main
  27:     0x55827796f17a - _start
  28:                0x0 - <unknown>
```

</details>
rust_decimal 1.5.0 and Rust 1.42.0.


Discussion:
Thanks for raising this; I found the issue and will shortly merge a PR that fixes this. I'm waiting until I get one PG issue merged before bumping a new official release, but for the meantime you should be able to test on `master`



Patch:
diff --git a/src/decimal.rs b/src/decimal.rs
index 1faeb87d..58152066 100644
--- a/src/decimal.rs
+++ b/src/decimal.rs
@@ -645,10 +645,11 @@ impl Decimal {
 
     /// Returns a new `Decimal` number with the specified number of decimal points for fractional
     /// portion.
-    /// Rounding is performed using the provided `RoundingStrategy`
+    /// Rounding is performed using the provided [`RoundingStrategy`]
     ///
     /// # Arguments
     /// * `dp`: the number of decimal points to round to.
+    /// * `strategy`: the [`RoundingStrategy`] to use.
     ///
     /// # Example
     ///
@@ -2322,7 +2323,11 @@ impl Num for Decimal {
                     }
                 }
                 // We're also one less digit so reduce the scale
-                scale -= (len - i) as u32;
+                let diff = (len - i) as u32;
+                if diff > scale {
+                    return Err(Error::new("Invalid decimal: overflow from too many digits"));
+                }
+                scale -= diff;
                 break;
             } else {
                 data[0] = tmp[0];


Test patch:
diff --git a/tests/decimal_tests.rs b/tests/decimal_tests.rs
index 3f3eaaae..98ce13b2 100644
--- a/tests/decimal_tests.rs
+++ b/tests/decimal_tests.rs
@@ -1315,6 +1315,25 @@ fn it_can_reject_invalid_formats() {
     }
 }
 
+#[test]
+fn it_can_reject_large_numbers_with_panic() {
+    let tests = &[
+        // The maximum number supported is 79,228,162,514,264,337,593,543,950,335
+        "79228162514264337593543950336",
+        "79228162514264337593543950337",
+        "79228162514264337593543950338",
+        "79228162514264337593543950339",
+        "79228162514264337593543950340",
+    ];
+    for &value in tests {
+        assert!(
+            Decimal::from_str(value).is_err(),
+            "This succeeded unexpectedly: {}",
+            value
+        );
+    }
+}
+
 #[test]
 fn it_can_parse_individual_parts() {
     let pi = Decimal::from_parts(1102470952, 185874565, 1703060790, false, 28);
@@ -1547,5 +1566,5 @@ fn it_computes_equal_hashes_for_positive_and_negative_zero() {
 #[test]
 #[should_panic]
 fn it_handles_i128_min() {
-    Decimal::from_i128_with_scale(i128::MIN, 0);
+    Decimal::from_i128_with_scale(std::i128::MIN, 0);
 }