about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbeetrees <b@beetr.ee>2022-09-29 18:50:10 +0100
committerbeetrees <b@beetr.ee>2022-09-29 23:06:22 +0100
commite409ce2159e97514178633771dfd9eb05fc4327f (patch)
tree0aa0786e1e85d6e956ada887ecae559e5f82bcf1
parent65445a571c11f07f4ca1ed0f63bad247ae295bd0 (diff)
downloadrust-e409ce2159e97514178633771dfd9eb05fc4327f.tar.gz
rust-e409ce2159e97514178633771dfd9eb05fc4327f.zip
Fix integer overflow in `format!("{:.0?}", Duration::MAX)`
-rw-r--r--library/core/src/time.rs39
-rw-r--r--library/core/tests/time.rs24
2 files changed, 52 insertions, 11 deletions
diff --git a/library/core/src/time.rs b/library/core/src/time.rs
index 2b85d6e2225..1d0c51c3c83 100644
--- a/library/core/src/time.rs
+++ b/library/core/src/time.rs
@@ -1043,7 +1043,7 @@ impl fmt::Debug for Duration {
         /// to the formatter's `width`, if specified.
         fn fmt_decimal(
             f: &mut fmt::Formatter<'_>,
-            mut integer_part: u64,
+            integer_part: u64,
             mut fractional_part: u32,
             mut divisor: u32,
             prefix: &str,
@@ -1075,7 +1075,7 @@ impl fmt::Debug for Duration {
             // normal floating point numbers. However, we only need to do work
             // when rounding up. This happens if the first digit of the
             // remaining ones is >= 5.
-            if fractional_part > 0 && fractional_part >= divisor * 5 {
+            let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 {
                 // Round up the number contained in the buffer. We go through
                 // the buffer backwards and keep track of the carry.
                 let mut rev_pos = pos;
@@ -1099,9 +1099,18 @@ impl fmt::Debug for Duration {
                 // the whole buffer to '0's and need to increment the integer
                 // part.
                 if carry {
-                    integer_part += 1;
+                    // If `integer_part == u64::MAX` and precision < 9, any
+                    // carry of the overflow during rounding of the
+                    // `fractional_part` into the `integer_part` will cause the
+                    // `integer_part` itself to overflow. Avoid this by using an
+                    // `Option<u64>`, with `None` representing `u64::MAX + 1`.
+                    integer_part.checked_add(1)
+                } else {
+                    Some(integer_part)
                 }
-            }
+            } else {
+                Some(integer_part)
+            };
 
             // Determine the end of the buffer: if precision is set, we just
             // use as many digits from the buffer (capped to 9). If it isn't
@@ -1111,7 +1120,12 @@ impl fmt::Debug for Duration {
             // This closure emits the formatted duration without emitting any
             // padding (padding is calculated below).
             let emit_without_padding = |f: &mut fmt::Formatter<'_>| {
-                write!(f, "{}{}", prefix, integer_part)?;
+                if let Some(integer_part) = integer_part {
+                    write!(f, "{}{}", prefix, integer_part)?;
+                } else {
+                    // u64::MAX + 1 == 18446744073709551616
+                    write!(f, "{}18446744073709551616", prefix)?;
+                }
 
                 // Write the decimal point and the fractional part (if any).
                 if end > 0 {
@@ -1141,12 +1155,17 @@ impl fmt::Debug for Duration {
                     // 2. The postfix: can be "µs" so we have to count UTF8 characters.
                     let mut actual_w = prefix.len() + postfix.chars().count();
                     // 3. The integer part:
-                    if let Some(log) = integer_part.checked_ilog10() {
-                        // integer_part is > 0, so has length log10(x)+1
-                        actual_w += 1 + log as usize;
+                    if let Some(integer_part) = integer_part {
+                        if let Some(log) = integer_part.checked_ilog10() {
+                            // integer_part is > 0, so has length log10(x)+1
+                            actual_w += 1 + log as usize;
+                        } else {
+                            // integer_part is 0, so has length 1.
+                            actual_w += 1;
+                        }
                     } else {
-                        // integer_part is 0, so has length 1.
-                        actual_w += 1;
+                        // integer_part is u64::MAX + 1, so has length 20
+                        actual_w += 20;
                     }
                     // 4. The fractional part (if any):
                     if end > 0 {
diff --git a/library/core/tests/time.rs b/library/core/tests/time.rs
index fe2d2f2412d..697bf33a8b0 100644
--- a/library/core/tests/time.rs
+++ b/library/core/tests/time.rs
@@ -197,9 +197,31 @@ fn correct_sum() {
 #[test]
 fn debug_formatting_extreme_values() {
     assert_eq!(
-        format!("{:?}", Duration::new(18_446_744_073_709_551_615, 123_456_789)),
+        format!("{:?}", Duration::new(u64::MAX, 123_456_789)),
         "18446744073709551615.123456789s"
     );
+    assert_eq!(format!("{:.0?}", Duration::MAX), "18446744073709551616s");
+    assert_eq!(format!("{:.0?}", Duration::new(u64::MAX, 500_000_000)), "18446744073709551616s");
+    assert_eq!(format!("{:.0?}", Duration::new(u64::MAX, 499_999_999)), "18446744073709551615s");
+    assert_eq!(
+        format!("{:.3?}", Duration::new(u64::MAX, 999_500_000)),
+        "18446744073709551616.000s"
+    );
+    assert_eq!(
+        format!("{:.3?}", Duration::new(u64::MAX, 999_499_999)),
+        "18446744073709551615.999s"
+    );
+    assert_eq!(
+        format!("{:.8?}", Duration::new(u64::MAX, 999_999_995)),
+        "18446744073709551616.00000000s"
+    );
+    assert_eq!(
+        format!("{:.8?}", Duration::new(u64::MAX, 999_999_994)),
+        "18446744073709551615.99999999s"
+    );
+    assert_eq!(format!("{:21.0?}", Duration::MAX), "18446744073709551616s");
+    assert_eq!(format!("{:22.0?}", Duration::MAX), "18446744073709551616s ");
+    assert_eq!(format!("{:24.0?}", Duration::MAX), "18446744073709551616s   ");
 }
 
 #[test]