diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 1af75ae94d4..a976f9c6b1d 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -511,7 +511,10 @@ fn resolve_asterisk_width( Some(CanAsterisk::Asterisk(loc)) => { let nb = args.next_i64(loc); if nb < 0 { - Some((usize::try_from(-(nb as isize)).ok().unwrap_or(0), true)) + // `nb.unsigned_abs()` computes the magnitude in unsigned + // arithmetic, so `i64::MIN` (whose magnitude 2^63 overflows + // `i64`) does not panic here. + Some((usize::try_from(nb.unsigned_abs()).ok().unwrap_or(0), true)) } else { Some((usize::try_from(nb).ok().unwrap_or(0), false)) } @@ -670,6 +673,27 @@ mod tests { ) ); } + + #[test] + fn asterisk_i64_min_width() { + // Regression test for https://github.com/uutils/coreutils/issues/13766 + // |i64::MIN| = 2^63 is not representable in i64, so computing the + // magnitude of a negative `*` width used to panic ("attempt to negate + // with overflow"). It must be computed in unsigned arithmetic. + let expected = usize::try_from(i64::MIN.unsigned_abs()).unwrap_or(0); + for arg in [ + FormatArgument::SignedInt(i64::MIN), + FormatArgument::Unparsed(i64::MIN.to_string().into()), + ] { + assert_eq!( + Some((expected, true)), + resolve_asterisk_width( + Some(CanAsterisk::Asterisk(ArgumentLocation::NextArgument)), + &mut FormatArguments::new(&[arg]), + ) + ); + } + } } mod resolve_asterisk_precision { diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index fda7f7d158e..e3d969ca307 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1538,6 +1538,26 @@ fn test_extreme_field_width_overflow() { .stderr_contains("printf: write error"); //could contains additional message like "formatting width too large" not in GNU, thats fine. } +#[test] +fn test_asterisk_width_i64_min_no_panic() { + // Regression test for https://github.com/uutils/coreutils/issues/13766 + // A negative `*` width argument of `i64::MIN` used to panic with "attempt to + // negate with overflow" while computing the magnitude. |i64::MIN| = 2^63 is + // not representable in `i64`, so the magnitude is computed in unsigned + // arithmetic. On 64-bit targets the resulting width exceeds the maximum + // format width and printf reports a write error (exit 1); on 32-bit targets + // the magnitude does not fit `usize` and is clamped to 0, so printf succeeds + // (exit 0). In all cases printf must exit cleanly rather than panic. + let result = new_ucmd!() + .args(&["|%*d|", &i64::MIN.to_string(), "1"]) + .run(); + assert!( + result.succeeded() || result.code() == 1, + "printf must not panic on an i64::MIN '*' width (got exit code {})", + result.code() + ); +} + #[test] fn test_q_string_control_chars_with_quotes() { // Test %q with control characters and single quotes combined.