Fix capacity overflow panic wrapping a wide char at narrow terminal width - #3886
Fix capacity overflow panic wrapping a wide char at narrow terminal width#3886vjymisal0 wants to merge 3 commits into
Conversation
…idth
InteractivePrinter::print_line's wrapping branch advances `cursor` by a
chunk's full display width even when that chunk (a double-width CJK
character or emoji) is wider than `cursor_max` (the terminal width).
When a background is painted on the line (e.g. via --highlight-line),
the end-of-line fill computed `" ".repeat(cursor_max - cursor)`, which
underflows when cursor > cursor_max, aborting with "capacity overflow".
Reproduced on current master:
printf '\U0001F4E6\U0001F4E6\n' | bat --highlight-line 1 \
--terminal-width 1 --wrap character --color always \
--paging never --theme OneHalfDark
# thread 'main' panicked at src/printer.rs:961:49:
# attempt to subtract with overflow
Fix clamps the fill width with saturating_sub, matching the pattern
already used a few lines above (line 789-793) for the no-wrap branch
and the earlier width-1 snip fix (sharkdp#3804). When cursor exceeds
cursor_max, the background fill is now empty instead of underflowing.
Added a regression test (wide_char_wrap_at_terminal_width_one_with_highlight_does_not_panic)
covering the exact repro. Full integration suite (230 tests) and
clippy pass locally.
Closes sharkdp#3844
xhon-pelushi
left a comment
There was a problem hiding this comment.
Checked out this branch and built it to verify the fix (cargo build --release, then reproduced both the original panic — confirmed fixed — and the case below).
The description says this is "the last remaining unclamped subtraction of the same shape," but there's a second one in the same function that this doesn't cover: max_width = cursor_max - cursor at printer.rs:829 (a few lines above the one you fixed, in the same char-wrapping branch). That line only runs once per Text chunk from the region/style iterator — for a line with a single style span (like your repro, a bare emoji line with no syntax highlighting boundaries) it only ever evaluates at cursor == 0, so it's safe. But cursor isn't reset between chunks, only inside the wrap-triggered branch further down. So a line with multiple style spans, where an earlier span already leaves cursor overshooting cursor_max (exactly the state your fix's own scenario produces), hits this second raw subtraction on the next span and panics the same way.
Repro against your branch (built from this PR, not master):
$ printf 'x = "📦" + y\n' > multispan.py
$ bat --highlight-line 1 --terminal-width 1 --wrap character --color always --paging never --theme OneHalfDark multispan.py
thread 'main' panicked at src/printer.rs:829:49:
attempt to subtract with overflow
Backtrace confirms the exact site:
17: <bat::printer::InteractivePrinter as bat::printer::Printer>::print_line
at src/printer.rs:829:49
This is pre-existing on master too (I checked — that line is untouched by your diff), so it's not a regression you introduced, just a second instance of the bug class your PR is otherwise a correct, well-tested fix for. Given the PR's stated goal is closing out this whole class of panic (referencing #3804 as the prior instance), seems worth clamping this one too — cursor_max.saturating_sub(cursor) in the same style as your fix — while you're in this function, rather than leaving a known second repro for a follow-up issue.
Everything else about the change (the fix at line 968, the CHANGELOG entry, the regression test) looks correct and I confirmed it against the emoji-only single-span case as described.
|
One correction to my own review, found during independent re-verification: I implied the second unguarded site (`printer.rs:829`) needs `--highlight-line` like the one this PR fixes. It doesn't — line 829 has no `background_color` guard, so it panics on a multi-style-span line (e.g. ordinary syntax-highlighted code) with no highlight flag at all. That makes it more broadly reachable than the site this PR patches, which strengthens the case for fixing both together rather than just the one this PR targets. |
xhon-pelushi
left a comment
There was a problem hiding this comment.
c162d403 covers the second site — thanks. Rebuilt the PR head and re-ran the cases:
| case | result |
|---|---|
the original #3844 repro (--highlight-line=1 --terminal-width=1 --wrap=character, double-width emoji) |
exits 0 |
a multi-style-span line (var x = "📦" + y;) at --terminal-width=1, no --highlight-line |
exits 0 (panicked before this commit) |
same line with --highlight-line=1 |
exits 0 |
ZWJ sequence (👨👩👦) in a string literal at width 1 and width 2 |
exits 0 |
Both cursor_max - cursor subtractions are now saturating_sub. The third cursor_max-relative subtraction in that function, cursor_max - cursor_total + 1 at line 797, is already inside an explicit if cursor_total <= cursor_max, so I do not think anything is left unguarded here.
Also checked the guard does not quietly break wrapping: var x = "📦" + y; at --terminal-width=8 --wrap=character still emits three wrapped lines with the content intact, so the max_width == 0 path recovers on the first wrap (cursor = 0; max_width = cursor_max;) rather than looping or dropping text.
Looks good to me.
Bug
bat --terminal-width 1 --wrap characteraborts withcapacity overflow(exit 101) when a line contains a character wider than the terminal (a double-width CJK char or emoji) and a background is painted on the line (--highlight-line, or a theme/style that fills the row background). Reported in #3844.Root cause
In
InteractivePrinter::print_line(src/printer.rs), the character-wrapping branch accumulatescursorby each chunk's display width. When a single character is wider than the remainingmax_width, it still gets pushed ontoline_bufand its full width added tocursorat flush time — socursorcan end up greater thancursor_max(the terminal width). The end-of-line background fill then computes:cursor_max - cursorunderflows (usize), producing a repeat count nearusize::MAX, which aborts insidestr::repeat.This is the same defect class as the
--style=snipwidth-1 panic fixed in #3804 — that fix clamps a different overflow site in the same function; this one is the last remaining unclamped subtraction of the same shape.Fix
Clamp with
saturating_sub, matching the pattern already used a few lines above (line ~789) for the no-wrap branch:When the cursor has overshot the terminal width, the background fill is now empty instead of underflowing — matching the "clamp to empty" behavior bat already uses elsewhere for this exact class of bug.
Testing
Reproduced the panic on current
masterbefore the fix:After the fix, the same command exits 0 with no panic.
Added a regression test,
wide_char_wrap_at_terminal_width_one_with_highlight_does_not_panic, covering this exact case. Ran the full integration suite (cargo test --test integration_tests): 230 passed, 0 failed. Also rancargo clippy --libwith no new warnings.Added a changelog entry under Bugfixes per CONTRIBUTING.md.
Closes #3844