numfmt: fix char-boundary panic on a multibyte locale decimal separator - #13938
numfmt: fix char-boundary panic on a multibyte locale decimal separator#13938leeewee wants to merge 2 commits into
Conversation
`find_valid_number_with_suffix` located the suffix with `s.chars().skip(numeric_part.len())`, using `numeric_part.len()` (a byte length) as a char-skip count. When the numeric part holds a multibyte char — e.g. the Arabic decimal separator `٫` (U+066B) under `LC_ALL=ar_SA.UTF-8`, so `1٫€K` parses `1٫` as the numeric part — the byte count skips past the following multibyte `€` onto a later valid suffix, taking a slicing arm; then `&s[..=numeric_part.len()]` cuts `€` mid-char and aborts instead of reporting the invalid suffix like GNU. Index the suffix from the byte offset `numeric_part.len()` (already a char boundary) instead of skipping by chars, so the real next char is examined and an invalid multibyte suffix takes the graceful rejection path. Fixes uutils#13937
There was a problem hiding this comment.
Pull request overview
Fixes a crash in numfmt when parsing inputs under locales with a multibyte decimal separator by avoiding mixing byte offsets and char iteration, and adds a regression test for the reported panic case.
Changes:
- Fix
find_valid_number_with_suffixto derive the suffix iterator from a byte-slice (s[numeric_part.len()..].chars()) instead ofchars().skip(byte_len). - Add a regression test covering
--from=siand--from=autowithLC_ALL=ar_SA.UTF-8and an invalid multibyte suffix sequence (€…).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/uu/numfmt/src/format.rs | Prevents char-boundary panics by aligning suffix scanning with the numeric prefix byte boundary. |
| tests/by-util/test_numfmt.rs | Adds regression coverage for malformed inputs under a multibyte-decimal locale. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| #[test] | ||
| fn test_from_multibyte_decimal_separator_invalid_suffix() { | ||
| new_ucmd!() | ||
| .env("LC_ALL", "ar_SA.UTF-8") | ||
| .args(&["--from=si", "1٫€K"]) | ||
| .fails_with_code(2) |
Merging this PR will improve performance by 4.02%
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ⚡ | Simulation | numfmt_large_numbers_si[10000] |
98.1 ms | 93.2 ms | +5.22% |
| ⚡ | Simulation | complex_relative_date |
330.6 µs | 318.7 µs | +3.72% |
| ⚡ | Simulation | ls_recursive_balanced_tree[(6, 4, 15)] |
120.7 ms | 117.1 ms | +3.12% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing leeewee:numfmt-fix-locale-char-boundary (96e37c8) with main (ebaf6e9)
Footnotes
-
50 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
|
Binary size comparison: |
|
GNU testsuite comparison: |
We have the locale bullt Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
tests/by-util/test_numfmt.rs:463
- The new regression test sets
LC_ALLtofr_FR. UTF-8(note the stray space) while using the Arabic decimal separator٫in the input. Underfr_FR.UTF-8the decimal separator is,, so this test won't exercise the reported multibyte-decimal-separator path and is likely to fail (or even fail to set the locale). Also, since this test relies on locale env vars, it should be ignored underwasi_runnerlike the existing locale tests in this file, and it’s safer to early-return whenar_SA.UTF-8isn’t available on Unix CI.
#[test]
fn test_from_multibyte_decimal_separator_invalid_suffix() {
new_ucmd!()
.env("LC_ALL", "fr_FR. UTF-8")
.args(&["--from=si", "1٫€K"])
|
I broke it, sorry |
Fixes #13937
numfmtaborts with astrchar-boundary panic on an input like1٫€Kunder a locale whose decimal separator is multibyte (e.g.LC_ALL=ar_SA.UTF-8, where the separator is the Arabic٫U+066B), while GNU rejects it gracefully:Cause
find_valid_number_with_suffixlocated the suffix withs.chars().skip(numeric_part.len()), usingnumeric_part.len()— a byte length — as a char skip count. When the numeric part holds a multibyte char (here the locale decimal separator٫, so1٫is the numeric part withlen() == 3bytes but only 2 chars), the byte count over-skips past the following multibyte€onto a later valid suffix (K), taking a slicing arm;&s[..=numeric_part.len()]then slices to a byte offset inside€and panics.Fix
Index the suffix from the byte offset
numeric_part.len()(s[numeric_part.len()..].chars()) rather than skipping by chars.numeric_partis always a prefix ofs, sonumeric_part.len()is a char boundary and the slice is safe; the real next char (€) is now examined, is not a valid suffix, and the input takes the same graceful "invalid suffix" rejection GNU gives. The existing byte-slices are unaffected — a valid suffix is ASCII, so they remain char-aligned.A regression test covers the
--from=siand--from=autovariants underLC_ALL=ar_SA.UTF-8.