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
37 changes: 26 additions & 11 deletions src/uucore/src/lib/features/format/num_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,18 @@ fn strip_fractional_zeroes_and_dot(s: &mut String) {
}
}

fn write_padding(writer: &mut impl Write, byte: u8, mut len: usize) -> std::io::Result<()> {
// One KiB keeps stack use small while amortizing writes for large field widths.
const BUFFER_SIZE: usize = 1024;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please document why 1024

let buffer = [byte; BUFFER_SIZE];

while len >= BUFFER_SIZE {
writer.write_all(&buffer)?;
len -= BUFFER_SIZE;
}
writer.write_all(&buffer[..len])
}

fn write_output(
mut writer: impl Write,
sign_indicator: String,
Expand All @@ -769,17 +781,18 @@ fn write_output(
// Check if the width is too large for formatting
super::check_width(remaining_width)?;

let padding = width.saturating_sub(sign_indicator.len().saturating_add(s.len()));

match alignment {
NumberAlignment::Left => write!(writer, "{sign_indicator}{s:<remaining_width$}"),
NumberAlignment::Left => {
writer.write_all(sign_indicator.as_bytes())?;
writer.write_all(s.as_bytes())?;
write_padding(&mut writer, b' ', padding)
}
NumberAlignment::RightSpace => {
let is_sign = sign_indicator.starts_with('-') || sign_indicator.starts_with('+'); // When sign_indicator is in ['-', '+']
if is_sign && remaining_width > 0 {
// Make sure sign_indicator is just next to number, e.g. "% +5.1f" 1 ==> $ +1.0
let s = sign_indicator + s.as_str();
write!(writer, "{s:>width$}", width = remaining_width + 1) // Since we now add sign_indicator and s together, plus 1
} else {
write!(writer, "{sign_indicator}{s:>remaining_width$}")
}
write_padding(&mut writer, b' ', padding)?;
writer.write_all(sign_indicator.as_bytes())?;
writer.write_all(s.as_bytes())
}
NumberAlignment::RightZero => {
// Add the padding after "0x" for hexadecimals
Expand All @@ -788,8 +801,10 @@ fn write_output(
} else {
("", s.as_str())
};
let remaining_width = remaining_width.saturating_sub(prefix.len());
write!(writer, "{sign_indicator}{prefix}{rest:0>remaining_width$}")
writer.write_all(sign_indicator.as_bytes())?;
writer.write_all(prefix.as_bytes())?;
write_padding(&mut writer, b'0', padding)?;
writer.write_all(rest.as_bytes())
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions tests/by-util/test_printf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1528,6 +1528,18 @@ fn test_large_width_format() {
}
}

#[test]
fn test_numeric_field_width_above_u16_max() {
const WIDTH: usize = 65_536;

let result = new_ucmd!().args(&["%65536d", "5"]).succeeds();
let stdout = result.stdout();

assert_eq!(stdout.len(), WIDTH);
assert!(stdout[..WIDTH - 1].iter().all(|&byte| byte == b' '));
assert_eq!(stdout[WIDTH - 1], b'5');
}

#[test]
fn test_extreme_field_width_overflow() {
// Test the specific case that was causing panic due to integer overflow
Expand Down
Loading