Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/uucore/src/lib/features/format/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down Expand Up @@ -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 {
Expand Down
20 changes: 20 additions & 0 deletions tests/by-util/test_printf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading