Skip to content

Commit 75d3027

Browse files
committed
Auto merge of rust-lang#102484 - beetrees:duration-debug-bug-fix, r=scottmcm
Fix integer overflow in `format!("{:.0?}", Duration::MAX)` Currently `format!("{:.0?}", Duration::MAX)` causes an integer overflow in the `Duration` `Debug` impl ([playground link](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=67675c6895bdb2e37ee727f0ed7622b2)). This is because the carry from the rounding of the fractional_part into the integer_part will cause the integer_part to overflow as it is already `u64::MAX`. This PR uses a larger integer type to avoid that issue, and adds a test for the correct behaviour.
2 parents f914b82 + e409ce2 commit 75d3027

File tree

2 files changed

+52
-11
lines changed

2 files changed

+52
-11
lines changed

Diff for: library/core/src/time.rs

+29-10
Original file line numberDiff line numberDiff line change
@@ -1043,7 +1043,7 @@ impl fmt::Debug for Duration {
10431043
/// to the formatter's `width`, if specified.
10441044
fn fmt_decimal(
10451045
f: &mut fmt::Formatter<'_>,
1046-
mut integer_part: u64,
1046+
integer_part: u64,
10471047
mut fractional_part: u32,
10481048
mut divisor: u32,
10491049
prefix: &str,
@@ -1075,7 +1075,7 @@ impl fmt::Debug for Duration {
10751075
// normal floating point numbers. However, we only need to do work
10761076
// when rounding up. This happens if the first digit of the
10771077
// remaining ones is >= 5.
1078-
if fractional_part > 0 && fractional_part >= divisor * 5 {
1078+
let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 {
10791079
// Round up the number contained in the buffer. We go through
10801080
// the buffer backwards and keep track of the carry.
10811081
let mut rev_pos = pos;
@@ -1099,9 +1099,18 @@ impl fmt::Debug for Duration {
10991099
// the whole buffer to '0's and need to increment the integer
11001100
// part.
11011101
if carry {
1102-
integer_part += 1;
1102+
// If `integer_part == u64::MAX` and precision < 9, any
1103+
// carry of the overflow during rounding of the
1104+
// `fractional_part` into the `integer_part` will cause the
1105+
// `integer_part` itself to overflow. Avoid this by using an
1106+
// `Option<u64>`, with `None` representing `u64::MAX + 1`.
1107+
integer_part.checked_add(1)
1108+
} else {
1109+
Some(integer_part)
11031110
}
1104-
}
1111+
} else {
1112+
Some(integer_part)
1113+
};
11051114

11061115
// Determine the end of the buffer: if precision is set, we just
11071116
// use as many digits from the buffer (capped to 9). If it isn't
@@ -1111,7 +1120,12 @@ impl fmt::Debug for Duration {
11111120
// This closure emits the formatted duration without emitting any
11121121
// padding (padding is calculated below).
11131122
let emit_without_padding = |f: &mut fmt::Formatter<'_>| {
1114-
write!(f, "{}{}", prefix, integer_part)?;
1123+
if let Some(integer_part) = integer_part {
1124+
write!(f, "{}{}", prefix, integer_part)?;
1125+
} else {
1126+
// u64::MAX + 1 == 18446744073709551616
1127+
write!(f, "{}18446744073709551616", prefix)?;
1128+
}
11151129

11161130
// Write the decimal point and the fractional part (if any).
11171131
if end > 0 {
@@ -1141,12 +1155,17 @@ impl fmt::Debug for Duration {
11411155
// 2. The postfix: can be "µs" so we have to count UTF8 characters.
11421156
let mut actual_w = prefix.len() + postfix.chars().count();
11431157
// 3. The integer part:
1144-
if let Some(log) = integer_part.checked_ilog10() {
1145-
// integer_part is > 0, so has length log10(x)+1
1146-
actual_w += 1 + log as usize;
1158+
if let Some(integer_part) = integer_part {
1159+
if let Some(log) = integer_part.checked_ilog10() {
1160+
// integer_part is > 0, so has length log10(x)+1
1161+
actual_w += 1 + log as usize;
1162+
} else {
1163+
// integer_part is 0, so has length 1.
1164+
actual_w += 1;
1165+
}
11471166
} else {
1148-
// integer_part is 0, so has length 1.
1149-
actual_w += 1;
1167+
// integer_part is u64::MAX + 1, so has length 20
1168+
actual_w += 20;
11501169
}
11511170
// 4. The fractional part (if any):
11521171
if end > 0 {

Diff for: library/core/tests/time.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,31 @@ fn correct_sum() {
197197
#[test]
198198
fn debug_formatting_extreme_values() {
199199
assert_eq!(
200-
format!("{:?}", Duration::new(18_446_744_073_709_551_615, 123_456_789)),
200+
format!("{:?}", Duration::new(u64::MAX, 123_456_789)),
201201
"18446744073709551615.123456789s"
202202
);
203+
assert_eq!(format!("{:.0?}", Duration::MAX), "18446744073709551616s");
204+
assert_eq!(format!("{:.0?}", Duration::new(u64::MAX, 500_000_000)), "18446744073709551616s");
205+
assert_eq!(format!("{:.0?}", Duration::new(u64::MAX, 499_999_999)), "18446744073709551615s");
206+
assert_eq!(
207+
format!("{:.3?}", Duration::new(u64::MAX, 999_500_000)),
208+
"18446744073709551616.000s"
209+
);
210+
assert_eq!(
211+
format!("{:.3?}", Duration::new(u64::MAX, 999_499_999)),
212+
"18446744073709551615.999s"
213+
);
214+
assert_eq!(
215+
format!("{:.8?}", Duration::new(u64::MAX, 999_999_995)),
216+
"18446744073709551616.00000000s"
217+
);
218+
assert_eq!(
219+
format!("{:.8?}", Duration::new(u64::MAX, 999_999_994)),
220+
"18446744073709551615.99999999s"
221+
);
222+
assert_eq!(format!("{:21.0?}", Duration::MAX), "18446744073709551616s");
223+
assert_eq!(format!("{:22.0?}", Duration::MAX), "18446744073709551616s ");
224+
assert_eq!(format!("{:24.0?}", Duration::MAX), "18446744073709551616s ");
203225
}
204226

205227
#[test]

0 commit comments

Comments
 (0)