-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Avoid unnecessary allocation in stripANSI for false-positive escape bytes #28772
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
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 behavioral changes to
Bun__ANSI__next(preserving standalone0x9Cbytes viabreakwithout advancing, plus theescPos == startre-scan to maintain liveness) have no test coverage — the new test only exercisesBun.stripANSI, not the C iterator. Given that an earlier iteration of this exact fix introduced an infinite loop caught only by manual review, it would be worth adding a test that drivesANSIIteratorover input containing standalone0x9Cand asserts (a) termination and (b) that the yielded slices concatenate back to the original input.Extended reasoning...
What the gap is
This PR makes two non-trivial behavioral changes to
Bun__ANSI__nextinsrc/jsc/bindings/stripANSI.cpp:start++; break;becomesbreak;so that broad-mask false-positive bytes (standalone0x9C) are no longer silently dropped.escPos == startguard is added that re-scans fromstart + 1so the iterator does not infinite-loop on the byte it just declined to advance past.Neither change is exercised by any test.
grepconfirms thatANSIIterator,Bun__ANSI__next, andcopyToClipboardOSC52are referenced only insrc/string/immutable.zig,src/jsc/bindings/stripANSI.cpp, andsrc/cli/repl.zig— there are zero hits intest/. The only new test in this PR (stripANSI.test.ts:17–28) callsBun.stripANSI, which goes through the templatedstripANSI<Char>function — a completely separate code path from the C iterator.Why this matters here specifically
The PR timeline shows that this exact code went through a broken intermediate state during this review cycle: the first attempt at change (1) — removing
start++without adding the re-scan — introduced an infinite loop whereBun__ANSI__nextwould forever returntruewith a zero-length slice at an unchanging cursor, hangingcopyToClipboardOSC52. That regression was caught only by a manual review comment, not by a failing test. The fix (the re-scan at lines 143–148) is itself subtle enough that a separate nit was filed about it fragmenting slices for consecutive0x9Cbytes. Code that has already regressed once within a single PR cycle is a strong signal that it needs a regression test.Why existing coverage does not help
The
Bun.stripANSItest suite is comprehensive for the templated path, butBun__ANSI__nextshares onlyfindEscapeCharacter/consumeANSIwith it — the loop structure, cursor bookkeeping, and slice-boundary logic are entirely independent. A bug in the iterator's liveness or byte-preservation would not be detected by any existing test; the infinite-loop regression in this PR is direct proof of that.Step-by-step: what a regression test would have caught
For input
"\x9C"against the intermediate (broken) commit:cursor=0: skip-loop findsescPos == start,consumeANSIreturnsstart,breakwithout advancing. Post-loopfindEscapeCharacter(start, end)returnsstartagain →slice_len = 0,cursor = 0, returnstrue.cursor=0: identical state → identical result. The Zigwhile (it.next()) |slice|loop incopyToClipboardOSC52never terminates.A test that simply iterates
strings.ANSIIteratorover"a\x9Cb"and asserts the concatenated slices equal"a\x9Cb"would have (a) hung on the broken commit and (b) failed on the pre-PR code (which yielded"a" ++ "b", dropping the byte).Suggested fix
Add a Zig test (e.g. in
src/string/immutable.zigalongsideANSIIterator, or a small case in the bindings tests) that iterates over inputs like"\x9C","a\x9Cb", and"\x9C\x9C", concatenates the yielded slices, and asserts the result equals the input. This pins down both the termination guarantee and the byte-preservation guarantee that this PR establishes.This is nit severity — the current implementation traces correctly by hand and all verifiers agree it is sound; this is purely a coverage gap, not a correctness bug, and need not block the PR.