fix(utils): stop generateCodeFrame hanging on a CRLF source - #23345
Open
NgoQuocViet2001 wants to merge 1 commit into
Open
fix(utils): stop generateCodeFrame hanging on a CRLF source#23345NgoQuocViet2001 wants to merge 1 commit into
NgoQuocViet2001 wants to merge 1 commit into
Conversation
The inner loop had no upper bound on j:
for (let j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length) continue
Once j runs past the last line the continue skips the count update at
the bottom of the body, so count freezes while end stays larger, and
the loop spins forever at 100% CPU.
It only shows on CRLF input. count advances by one per line break, but
end is clamped to source.length, which counts two characters per CRLF
-- so on a CRLF source end can exceed anything count reaches. On LF the
two always meet and the loop exits.
Bound j by lines.length in the second disjunct.
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Description
generateCodeFrame()never returns — a 100% CPU spin, no allocation, no throw — when the source uses CRLF line endings and the highlighted range reaches the end of the file.The same calls on the LF equivalent return normally, which is why it has gone unnoticed: every existing test in the
generateCodeFramesblock that passes anenduses the LFsource.Why it loops
The inner loop has no upper bound on
j:Once
jpasses the last line, thecontinueskips thecountupdate at the bottom of the body, socountfreezes atsum(line lengths) + (lines.length - 1)whileendstays larger.end > countis then permanently true andjincrements forever.CRLF is what makes
endunreachable:countadvances by one per line break, butendis clamped tosource.length, which counts two characters per CRLF. So on a CRLF sourceendcan exceed anythingcountwill ever reach. On LF the two always meet and the loop exits.Instrumented, capped at 200k iterations:
The fix bounds
jbylines.lengthin the second disjunct, so the loop cannot outrun the input.This matters because the function runs on the error path —
formatError/buildErrorMessage— so a CRLF file (ordinary on Windows, and whatgit config core.autocrlf=truechecks out) turns a build or dev-server error into a hung process rather than a message.Validation
The regression test genuinely guards it: on
mainit hangs, so I ran it under an external timeout.Before the fix
After the fix
npx vitest run packages/vite/src/node/__tests__/utils.spec.ts— 123 passed (123).npx eslint packages/vite/src/node/utils.ts packages/vite/src/node/__tests__/utils.spec.ts— clean.npx oxfmt --checkon both — "All matched files use the correct format."pnpm --filter vite build— succeeds (includingbuild-types-check).Scope
This is the minimal fix: it makes the loop terminate. It does not change the frames that already worked, hence no snapshot churn.
There is a deeper issue in the same function that I deliberately left alone:
counthardcodes+ 1per separator, so on CRLF the underline lengths are computed against a position space two characters narrower thanend. Makingcountandendagree on the real separator width would fix that too, but it moves existing output and belongs in its own PR. Happy to follow up if you want it.