From 3c0f695e284673dfa3728b824a0cdf21983db7d3 Mon Sep 17 00:00:00 2001 From: Vedant Madane <6527493+VedantMadane@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:28:45 +0530 Subject: [PATCH 1/2] fix: fix(printf): do not panic when field width exceeds u16::MAX - num_format.rs: manual padding path when width > u16::MAX - test_printf.rs: regression for %65536d Fixes #13850 Signed-off-by: Vedant Madane <6527493+VedantMadane@users.noreply.github.com> --- .../src/lib/features/format/num_format.rs | 53 +++++++++++++++++++ tests/by-util/test_printf.rs | 14 +++++ 2 files changed, 67 insertions(+) diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index b52859de908..b1dc5cc2bb2 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -374,6 +374,39 @@ impl Formatter<&ExtendedBigDecimal> for Float { /// standard formatting machinery, which panics with "Formatting argument out /// of range" once the dynamic width exceeds `u16::MAX`. A large precision such /// as `%.100000d` is valid input for `printf`/`seq`, so it must not panic. + +/// Write `sign_indicator` and `s` into `writer` with space padding to `width` +/// without using Rust dynamic format widths (which panic above `u16::MAX`). +fn align_pad_write( + mut writer: impl Write, + sign_indicator: &str, + s: &str, + width: usize, + alignment: NumberAlignment, +) -> std::io::Result<()> { + let content_len = sign_indicator.len() + s.len(); + let pad = width.saturating_sub(content_len); + match alignment { + NumberAlignment::Left => { + writer.write_all(sign_indicator.as_bytes())?; + writer.write_all(s.as_bytes())?; + writer.write_all(&vec![b' '; pad])?; + } + NumberAlignment::RightSpace => { + // Keep the sign adjacent to the number (GNU printf style). + writer.write_all(&vec![b' '; pad])?; + writer.write_all(sign_indicator.as_bytes())?; + writer.write_all(s.as_bytes())?; + } + NumberAlignment::RightZero => { + writer.write_all(sign_indicator.as_bytes())?; + writer.write_all(&vec![b'0'; pad])?; + writer.write_all(s.as_bytes())?; + } + } + Ok(()) +} + fn zero_pad_to(s: &str, width: usize) -> String { if s.len() >= width { s.to_string() @@ -769,6 +802,26 @@ fn write_output( // Check if the width is too large for formatting super::check_width(remaining_width)?; + // Rust's format width is a u16. Above that, pad manually so we match GNU + // printf instead of panicking with "Formatting argument out of range" (#13850). + const RUST_FMT_WIDTH_MAX: usize = u16::MAX as usize; + if remaining_width > RUST_FMT_WIDTH_MAX { + return match alignment { + NumberAlignment::RightZero => { + let (prefix, rest) = if s.len() >= 2 && s[..2].eq_ignore_ascii_case("0x") { + (&s[..2], &s[2..]) + } else { + ("", s.as_str()) + }; + let body = zero_pad_to(rest, remaining_width.saturating_sub(prefix.len())); + writer.write_all(sign_indicator.as_bytes())?; + writer.write_all(prefix.as_bytes())?; + writer.write_all(body.as_bytes()) + } + other => align_pad_write(writer, &sign_indicator, &s, width, other), + }; + } + match alignment { NumberAlignment::Left => write!(writer, "{sign_indicator}{s: { diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 5cae85c2c25..2869b064fbe 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1770,3 +1770,17 @@ printf: %z: invalid conversion specification .stderr_only("printf: %5.2c: invalid conversion specification\n"); } } + +#[test] +fn test_width_above_u16_max_does_not_panic() { + // Field widths above u16::MAX used to panic inside Rust's formatter (#13850). + // GNU prints the padded field; we must not abort. + let out = new_ucmd!() + .args(&["%65536d", "5"]) + .succeeds() + .stdout_str() + .to_string(); + assert_eq!(out.len(), 65536, "expected width-padded output, got len {}", out.len()); + assert!(out.ends_with('5')); +} + From a2efbc7498c8b2d018693194966b7b8a8a176b0c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:58:58 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/by-util/test_printf.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index 2869b064fbe..abfa9ab4239 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -1783,4 +1783,3 @@ fn test_width_above_u16_max_does_not_panic() { assert_eq!(out.len(), 65536, "expected width-padded output, got len {}", out.len()); assert!(out.ends_with('5')); } -