mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
date: extract try_alloc_padded helper and add large-width test
Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
@@ -33,13 +33,12 @@
|
||||
//! - `%^B`: Month name in uppercase (JUNE)
|
||||
//! - `%+4C`: Century with sign, padded to 4 characters (+019)
|
||||
|
||||
use fluent::FluentArgs;
|
||||
use jiff::Zoned;
|
||||
use jiff::fmt::strtime::{BrokenDownTime, Config, PosixCustom};
|
||||
use regex::Regex;
|
||||
use std::fmt;
|
||||
use std::sync::OnceLock;
|
||||
use uucore::locale::get_message_with_args;
|
||||
use uucore::translate;
|
||||
|
||||
/// Error type for format modifier operations
|
||||
#[derive(Debug)]
|
||||
@@ -47,23 +46,22 @@ pub enum FormatError {
|
||||
/// Error from the underlying jiff library
|
||||
JiffError(jiff::Error),
|
||||
/// Field width calculation overflowed or required allocation failed
|
||||
FieldWidthTooLarge { width: String, specifier: String },
|
||||
FieldWidthTooLarge { width: usize, specifier: String },
|
||||
}
|
||||
|
||||
impl fmt::Display for FormatError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::JiffError(e) => write!(f, "{e}"),
|
||||
Self::FieldWidthTooLarge { width, specifier } => {
|
||||
let mut args = FluentArgs::new();
|
||||
args.set("width", width.clone());
|
||||
args.set("specifier", specifier.clone());
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
get_message_with_args("date-error-format-modifier-width-too-large", args)
|
||||
Self::FieldWidthTooLarge { width, specifier } => write!(
|
||||
f,
|
||||
"{}",
|
||||
translate!(
|
||||
"date-error-format-modifier-width-too-large",
|
||||
"width" => width,
|
||||
"specifier" => specifier
|
||||
)
|
||||
}
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,16 +153,7 @@ fn format_with_modifiers(
|
||||
// Check if this specifier has modifiers
|
||||
if !flags.is_empty() || !width_str.is_empty() {
|
||||
// Apply modifiers to the formatted value
|
||||
let width = if width_str.is_empty() {
|
||||
0
|
||||
} else {
|
||||
width_str
|
||||
.parse()
|
||||
.map_err(|_| FormatError::FieldWidthTooLarge {
|
||||
width: width_str.to_string(),
|
||||
specifier: spec.to_string(),
|
||||
})?
|
||||
};
|
||||
let width: usize = width_str.parse().unwrap_or(0);
|
||||
let explicit_width = !width_str.is_empty();
|
||||
let modified = apply_modifiers(&formatted, flags, width, spec, explicit_width)?;
|
||||
result.push_str(&modified);
|
||||
@@ -406,38 +395,14 @@ fn apply_modifiers(
|
||||
// Zero padding: sign first, then zeros (e.g., "-0022")
|
||||
let sign = result.chars().next().unwrap();
|
||||
let rest = &result[1..];
|
||||
let target_len = result.len().checked_add(padding).ok_or_else(|| {
|
||||
FormatError::FieldWidthTooLarge {
|
||||
width: width.to_string(),
|
||||
specifier: specifier.to_string(),
|
||||
}
|
||||
})?;
|
||||
let mut padded = String::new();
|
||||
padded
|
||||
.try_reserve(target_len)
|
||||
.map_err(|_| FormatError::FieldWidthTooLarge {
|
||||
width: width.to_string(),
|
||||
specifier: specifier.to_string(),
|
||||
})?;
|
||||
let mut padded = try_alloc_padded(result.len(), padding, effective_width, specifier)?;
|
||||
padded.push(sign);
|
||||
padded.extend(std::iter::repeat_n('0', padding));
|
||||
padded.push_str(rest);
|
||||
result = padded;
|
||||
} else {
|
||||
// Default: pad on the left (e.g., " -22" or " 1999")
|
||||
let target_len = result.len().checked_add(padding).ok_or_else(|| {
|
||||
FormatError::FieldWidthTooLarge {
|
||||
width: width.to_string(),
|
||||
specifier: specifier.to_string(),
|
||||
}
|
||||
})?;
|
||||
let mut padded = String::new();
|
||||
padded
|
||||
.try_reserve(target_len)
|
||||
.map_err(|_| FormatError::FieldWidthTooLarge {
|
||||
width: width.to_string(),
|
||||
specifier: specifier.to_string(),
|
||||
})?;
|
||||
let mut padded = try_alloc_padded(result.len(), padding, effective_width, specifier)?;
|
||||
padded.extend(std::iter::repeat_n(pad_char, padding));
|
||||
padded.push_str(&result);
|
||||
result = padded;
|
||||
@@ -447,6 +412,30 @@ fn apply_modifiers(
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Allocate a `String` with enough capacity for `current_len + padding`,
|
||||
/// returning `FieldWidthTooLarge` on arithmetic overflow or allocation failure.
|
||||
fn try_alloc_padded(
|
||||
current_len: usize,
|
||||
padding: usize,
|
||||
width: usize,
|
||||
specifier: &str,
|
||||
) -> Result<String, FormatError> {
|
||||
let target_len =
|
||||
current_len
|
||||
.checked_add(padding)
|
||||
.ok_or_else(|| FormatError::FieldWidthTooLarge {
|
||||
width,
|
||||
specifier: specifier.to_string(),
|
||||
})?;
|
||||
let mut s = String::new();
|
||||
s.try_reserve(target_len)
|
||||
.map_err(|_| FormatError::FieldWidthTooLarge {
|
||||
width,
|
||||
specifier: specifier.to_string(),
|
||||
})?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -734,6 +723,16 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifiers_width_too_large() {
|
||||
let err = apply_modifiers("x", "", usize::MAX, "c", true).unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
FormatError::FieldWidthTooLarge { width, specifier }
|
||||
if width == usize::MAX && specifier == "c"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_underscore_flag_without_width() {
|
||||
// %_m should pad month to default width 2 with spaces
|
||||
@@ -743,7 +742,8 @@ mod tests {
|
||||
// %_H should pad hour to default width 2 with spaces
|
||||
assert_eq!(apply_modifiers("5", "_", 0, "H", false).unwrap(), " 5");
|
||||
// %_Y should pad year to default width 4 with spaces
|
||||
assert_eq!(apply_modifiers("1999", "_", 0, "Y", false).unwrap(), "1999"); // already at default width
|
||||
assert_eq!(apply_modifiers("1999", "_", 0, "Y", false).unwrap(), "1999");
|
||||
// already at default width
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -793,14 +793,4 @@ mod tests {
|
||||
"GNU: %_C should produce '19', not ' 19' (default width is 2, not 4)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_modifiers_width_too_large() {
|
||||
let err = apply_modifiers("x", "", usize::MAX, "c", true).unwrap_err();
|
||||
assert!(matches!(
|
||||
err,
|
||||
FormatError::FieldWidthTooLarge { width, specifier }
|
||||
if width == usize::MAX.to_string() && specifier == "c"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+41
-13
@@ -2387,6 +2387,47 @@ fn test_date_format_modifier_percent_escape() {
|
||||
.stdout_is("%Y=0000001999\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_format_modifier_huge_width_fails_without_abort() {
|
||||
// GNU date also exits with failure for extremely large width.
|
||||
// Assert exit code only to avoid coupling to implementation-specific error text.
|
||||
let format = format!("+%{}c", usize::MAX);
|
||||
new_ucmd!().arg(&format).fails().code_is(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_format_large_width_no_oom() {
|
||||
// Regression: very large width like %8888888888r caused OOM.
|
||||
// GNU caps width to i32::MAX; verify we don't crash.
|
||||
// Use a moderate width with a fixed date to check the code path works.
|
||||
new_ucmd!()
|
||||
.arg("-d")
|
||||
.arg("2024-01-01")
|
||||
.arg("+%300S")
|
||||
.succeeds()
|
||||
.stdout_is(format!("{}\n", format_args!("{:0>300}", "00")));
|
||||
|
||||
// Test with a larger width to exercise the code path without producing
|
||||
// gigabytes of output (the original %8888888888r would produce ~2GB).
|
||||
new_ucmd!()
|
||||
.arg("-d")
|
||||
.arg("2024-01-01")
|
||||
.arg("+%10000S")
|
||||
.succeeds()
|
||||
.stdout_is(format!("{}\n", format_args!("{:0>10000}", "00")));
|
||||
|
||||
// Mixed literal text with multiple width-modified specifiers.
|
||||
// 2024-01-01 is Monday (day-of-week 1).
|
||||
// %2u → "01", literal "ueuu", %6666u → "1" zero-padded to 6666, literal "-r".
|
||||
let expected = format!("01ueuu{}-r\n", format_args!("{:0>6666}", "1"));
|
||||
new_ucmd!()
|
||||
.arg("-d")
|
||||
.arg("2024-01-01")
|
||||
.arg("+%2uueuu%6666u-r")
|
||||
.succeeds()
|
||||
.stdout_is(expected);
|
||||
}
|
||||
|
||||
// Tests for format modifier edge cases (flags without explicit width)
|
||||
#[test]
|
||||
fn test_date_format_modifier_edge_cases() {
|
||||
@@ -2507,19 +2548,6 @@ fn test_date_format_modifier_edge_cases() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_format_modifier_huge_width_fails_without_abort() {
|
||||
// GNU date also exits with failure for extremely large width.
|
||||
// Assert exit code only to avoid coupling to implementation-specific error text.
|
||||
let formats = [
|
||||
format!("+%{}c", usize::MAX),
|
||||
"+%184467440737095516160c".into(),
|
||||
];
|
||||
for format in formats {
|
||||
new_ucmd!().arg(&format).fails().code_is(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Tests for --debug flag
|
||||
#[test]
|
||||
fn test_date_debug_basic() {
|
||||
|
||||
Reference in New Issue
Block a user