-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
date: width prefix in %N format specifier fix #12098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
6df88d3
93ad040
88f389b
864853b
86c66d7
a6b25ac
b241b1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -75,9 +75,10 @@ struct ParsedSpec<'a> { | |||||||||||||||||||||||||||||||||||||||||||||||
| /// Flag characters from `[_0^#+-]`. | ||||||||||||||||||||||||||||||||||||||||||||||||
| flags: &'a str, | ||||||||||||||||||||||||||||||||||||||||||||||||
| /// Explicit width, if present. `None` means no width was specified. | ||||||||||||||||||||||||||||||||||||||||||||||||
| /// A value that overflows `usize` is represented as `Some(usize::MAX)` so | ||||||||||||||||||||||||||||||||||||||||||||||||
| /// Negative widths are used when `-` flag precedes the width. | ||||||||||||||||||||||||||||||||||||||||||||||||
| /// A value that overflows `isize` is represented as `Some(isize::MAX)` so | ||||||||||||||||||||||||||||||||||||||||||||||||
| /// the downstream allocation check surfaces it as `FieldWidthTooLarge`. | ||||||||||||||||||||||||||||||||||||||||||||||||
| width: Option<usize>, | ||||||||||||||||||||||||||||||||||||||||||||||||
| width: Option<isize>, | ||||||||||||||||||||||||||||||||||||||||||||||||
| /// The specifier itself, including any leading colons (e.g. `Y`, `:z`, `::z`). | ||||||||||||||||||||||||||||||||||||||||||||||||
| spec: &'a str, | ||||||||||||||||||||||||||||||||||||||||||||||||
| /// Total byte length of the parsed sequence including the leading `%`. | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -104,13 +105,18 @@ fn parse_format_spec(s: &str) -> Option<ParsedSpec<'_>> { | |||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| let flags = &s[flags_start..pos]; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if '-' flag is present | ||||||||||||||||||||||||||||||||||||||||||||||||
| let has_minus_flag = flags.contains('-'); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Width: zero or more ASCII digits. | ||||||||||||||||||||||||||||||||||||||||||||||||
| let width_start = pos; | ||||||||||||||||||||||||||||||||||||||||||||||||
| while pos < bytes.len() && bytes[pos].is_ascii_digit() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| pos += 1; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| let width = if pos > width_start { | ||||||||||||||||||||||||||||||||||||||||||||||||
| Some(s[width_start..pos].parse::<usize>().unwrap_or(usize::MAX)) | ||||||||||||||||||||||||||||||||||||||||||||||||
| let parsed: isize = s[width_start..pos].parse::<isize>().unwrap_or(isize::MAX); | ||||||||||||||||||||||||||||||||||||||||||||||||
| // Make width negative if '-' flag is present | ||||||||||||||||||||||||||||||||||||||||||||||||
| Some(if has_minus_flag { -parsed } else { parsed }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| None | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -343,6 +349,11 @@ fn apply_modifiers(value: &str, parsed: &ParsedSpec<'_>) -> Result<String, Forma | |||||||||||||||||||||||||||||||||||||||||||||||
| let specifier = parsed.spec; | ||||||||||||||||||||||||||||||||||||||||||||||||
| let mut result = value.to_string(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Ensure nanoseconds are always zero-padded to 9 digits from the start | ||||||||||||||||||||||||||||||||||||||||||||||||
| // if result.len() < 9 { | ||||||||||||||||||||||||||||||||||||||||||||||||
| // result = format!("{:0>9}", result); | ||||||||||||||||||||||||||||||||||||||||||||||||
| // } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Ensure nanoseconds are always zero-padded to 9 digits from the start | |
| // if result.len() < 9 { | |
| // result = format!("{:0>9}", result); | |
| // } |
Copilot
AI
Apr 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new no_pad handling truncates result whenever a width is present, and returns early without strip_default_padding. This breaks existing documented/tested behavior that %-5d ignores width and still strips default padding (see tests in this module). Additionally, &result[..end] truncates from the left and can panic on non-ASCII output (not guaranteed to be on a UTF-8 char boundary). This truncation logic should be limited to the %N specifier (if required), should preserve existing no-pad behavior for other specifiers, and should implement the intended direction (right-truncation per PR description) safely.
| // If no_pad flag is active, suppress all padding and return | |
| if no_pad { | |
| if let Some(w) = width { | |
| let abs_w = w.abs() as usize; | |
| let end = abs_w.min(result.len()); | |
| let truncated = &result[..end]; | |
| // Return truncated nanoseconds without trimming trailing zeros (GNU behavior) | |
| return Ok(truncated.to_string()); | |
| } | |
| // If no_pad flag is active, suppress all padding and return. | |
| // Width-based truncation only applies to %N; other specifiers keep the | |
| // documented behavior of ignoring width and stripping default padding. | |
| if no_pad { | |
| if specifier == "N" { | |
| if let Some(w) = width { | |
| let abs_w = w.abs() as usize; | |
| // Truncate from the right by keeping the leftmost characters, | |
| // using char iteration to avoid slicing at a non-UTF-8 boundary. | |
| let truncated: String = result.chars().take(abs_w).collect(); | |
| // Return truncated nanoseconds without trimming trailing zeros (GNU behavior) | |
| return Ok(truncated); | |
| } | |
| } |
Copilot
AI
Apr 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These assertions compare FormatError::FieldWidthTooLarge { width, .. } (where width is declared as a usize) against isize::MAX. As written this won’t compile. Either keep FormatError::FieldWidthTooLarge.width as usize and compare against an appropriate usize value (e.g., isize::MAX as usize if that’s now the sentinel), or change the error type consistently to isize and update all call sites/formatting accordingly.
Copilot
AI
Apr 30, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue here: FormatError::FieldWidthTooLarge.width is a usize, but the match guard compares it to isize::MAX, which will not type-check. Please make the width sentinel/type consistent between parsing, error reporting, and tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parse_format_speccurrently negates the parsed width whenever the-flag is present (Some(if has_minus_flag { -parsed } else { parsed })). This changes the meaning of width for all specifiers (e.g.%-5dnow yields a negative width) and causes downstream logic/tests that treat width as a magnitude to misbehave. Suggestion: keepwidthas a non-negative magnitude (or asusize) and rely on the existingno_pad/flag handling inapply_modifiers; if%Nneeds special behavior when-is present, encode that via flags/specifier-specific handling instead of sign-encoding width.