Skip to content

printf: reject malformed hex and unicode escapes in %b arguments - #14406

Open
zfaustk wants to merge 6 commits into
uutils:mainfrom
zfaustk:fix-printf-b-escapes
Open

zfaustk wants to merge 6 commits into
uutils:mainfrom
zfaustk:fix-printf-b-escapes

Conversation

@zfaustk

@zfaustk zfaustk commented Sep 6, 2026

Copy link
Copy Markdown

In %b arguments, malformed \x, \u, and \U escape sequences were previously swallowed by parse_escape_only, printing a literal \x and exiting 0 while dropping consumed characters in \u and \U.

Make parse_escape_only return Result<EscapedChar, FormatError> and propagate errors from %b argument parsing so printf reports missing hex numbers and invalid universal character names with exit code 1 to match GNU coreutils. Also ensure parse_unicode only consumes input when hex digits are present.

Fixes #14404

In %b arguments, malformed \x, \u, and \U escape sequences were previously
swallowed by parse_escape_only, printing a literal \x and exiting 0 while
dropping consumed characters in \u and \U.

Make parse_escape_only return Result<EscapedChar, FormatError> and propagate
errors from %b argument parsing so printf reports missing hex numbers and
invalid universal character names with exit code 1 to match GNU coreutils.
Also ensure parse_unicode only consumes input when hex digits are present.

Fixes uutils#14404
@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

GNU testsuite comparison:

Congrats! The gnu test tests/du/max-depth is no longer failing!
Congrats! The gnu test tests/fold/fold is no longer failing!

@sylvestre

Copy link
Copy Markdown
Contributor

some jobs are failing

@SichenLiang

Copy link
Copy Markdown
Contributor

Hi — thanks for turnin so fast!

Moving *input = rest to after the hex parse is the right fix for the character
loss, and I can confirm that part works.

Two things came up testing your branch against GNU 9.7:

Output produced before the bad escape is dropped. GNU writes everything it
converted up to the offending escape and then diagnoses:

$ printf '%s|%b' first 'bad\x'
gnu       first|bad      exit 1
main      first|bad\x    exit 0
this PR   first|         exit 1

It scales with the argument — with 100,000 As followed by \x, GNU writes
100,000 bytes and exits 1, this branch writes 0 and exits 1. Same when stdout is
a file, so it isn't buffering.

Out-of-range \U now errors where main matched GNU. GNU passes it through
and exits 0:

$ printf '%b' '[\U00110000]'
gnu       [\U00110000]   exit 0
main      [\x]           exit 0
this PR   (nothing)      exit 1, invalid universal character name \U00110000

Same for \UFFFFFFFF. main's stdout was wrong there too, but its exit status
agreed with GNU and now it doesn't.

The %b diagnostics themselves are exactly right — it's only what happens to the
bytes before them.

Separately, and not from this change: uutils' echo -e interprets \u/\U where GNU's doesn't at all -- echo -e '[\u0041]'
gives [A] here and [\u0041] on GNU. That's pre-existing, so I'll open a
separate issue for it rather than widen this PR.

Measured on GNU coreutils 9.7 (Ubuntu 26.04) against 71a9079, LC_ALL=C.

…ut-of-range \U

In %b arguments, valid converted bytes preceding a malformed escape
sequence were discarded when parsing failed. Write converted bytes
directly to the output writer so prefixes are preserved on error.

Also pass through \U escape sequences whose numeric value exceeds
0x10FFFF verbatim with exit status 0, matching GNU coreutils.
Format error is kept only for invalid universal character names
(surrogate code points in 0xD800..=0xDFFF).
@SichenLiang

Copy link
Copy Markdown
Contributor

I tested eae9778 against GNU coreutils 9.12. The four escapes in the #14404 table, the dropped-text examples and the well-formed escapes now match GNU in stdout and exit status, in the format string and in %b. Both points from my earlier comment are fixed: printf '%s|%b' first 'bad\x' prints first|bad and exits 1, also with 100,000 bytes to a pipe or a file, and [\U00110000] passes through with exit 0. I found no case where main matched GNU and this branch does not, echo -e included. One leftover, already present for the format string: GNU prints \ud800 in lower case in the surrogate message, this branch prints \uD800. The branch conflicts in printf.rs, where 61a3e74 changed map_err(&raise) to map_err(raise).

@codspeed

codspeed Bot commented Oct 13, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 3.65%

⚠️ 120 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

ℹ️ 1 benchmark has no flame graph

The flamegraph could not be generated because too much time was spent in runtime internals, making the visualization not useful for performance analysis.

⚡ 2 improved benchmarks
❌ 1 regressed benchmark
✅ 398 untouched benchmarks
⏩ 70 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ Simulation factor_three_39_bit_primes 942.2 ms 1,250.4 ms -24.64%
⚡ Simulation factor_five_38_bit_primes 1.8 s 1.6 s +11.11%
⚡ Simulation factor_thirteen_39_bit_primes 9.4 s 8.8 s +6.82%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing zfaustk:fix-printf-b-escapes (44d6422) with main (774cca5)2

Open in CodSpeed

Footnotes

  1. 70 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. ↩

  2. No successful run was found on main (fb68e6c) during the generation of this report, so 774cca5 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

printf: a malformed \u or \U in a %b argument drops characters and exits 0

3 participants