shell: widen brace-rollback counter to u32 to avoid debug overflow panic#31030
shell: widen brace-rollback counter to u32 to avoid debug overflow panic#31030ldkhang1201 wants to merge 3 commits into
Conversation
`$.braces` with an unclosed outer `{` wrapping >=256 levels of balanced
`{...}` panicked the debug build (`attempt to add with overflow` in
`rollback_braces`, exit 134) where released Bun wraps silently. Debug-only:
Cargo's dev profile enables `overflow-checks`; the release profile doesn't,
so release wrapped (256 -> 0) exactly like Zig's ReleaseFast.
`NewLexer::rollback_braces` used `let mut braces: u8 = 0;` with
`braces += 1;` for each `Token::Open` encountered while walking a
rollback span. There is no upstream depth cap on the input
(`MAX_NESTED_BRACES = 10` only sets `SmallVec` inline capacity — it spills
to heap on overflow rather than rejecting), so a deeply nested input
reaches the loop and the 256th increment overflows. `braces` is only
compared `> 0` / `== 0` and never stored or cast, so a wider type is
strictly more correct — it also avoids Zig's silent `u8` wrap, which was
a latent bug that produced wrong rollback output but didn't crash.
Widen `braces` to `u32`. Regression test (`$.braces` with 300 nested
levels around an `x`, run in a subprocess since the failure was an
uncatchable abort) passes on the debug build and matches released Bun.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThe PR widens the internal brace nesting counter in ChangesBrace nesting overflow fix
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/bun/shell/brace.test.ts`:
- Line 97: Remove the explicit per-test timeout argument (30_000) from the test
in brace.test.ts: locate the test(...) or it(...) invocation that currently ends
with "}, 30_000);" and change it to call the test function without the timeout
parameter so Bun's default timeout is used; ensure only the numeric timeout
argument is removed and the test callback (the function referenced by the
closing "}") remains unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 828a34ef-0a07-410c-8d82-0c4490a026ef
📒 Files selected for processing (2)
src/shell_parser/braces.rstest/js/bun/shell/brace.test.ts
Per `test/CLAUDE.md`, tests under `test/js/bun/**` should not set explicit timeouts — Bun's default is sufficient. The single-subprocess test runs in ~650ms under debug, well below the 5s default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/js/bun/shell/brace.test.ts`:
- Around line 88-96: The test currently awaits proc.stderr.text() and
proc.exited together and asserts stderr and exitCode; change it to capture and
assert stdout (proc.stdout.text()) and stderr first, then await proc.exited as
the final assertion to improve subprocess failure diagnostics—locate the
Bun.spawn call and the Promise.all usage with proc, replace or reorder to call
proc.stdout.text() and proc.stderr.text(), assert stdout/stderr, then await
proc.exited and assert exit code.
- Around line 86-87: Replace the use of String.prototype.repeat when building
the large test string: instead of using "{" + "{".repeat(depth) + "x" +
"}".repeat(depth) to construct input, allocate a Buffer with the repeat count
and fill character and call toString() for the repeated segments, preserving the
same concatenation and the variables depth and input so the test behavior is
unchanged (i.e., use Buffer.alloc(depth, "{").toString() for the left braces and
Buffer.alloc(depth, "}").toString() for the right braces while keeping the "x"
in the middle).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: bfb1cf19-4384-435c-a426-2b72f94dab3b
📒 Files selected for processing (1)
test/js/bun/shell/brace.test.ts
Two CodeRabbit follow-ups on the regression test:
- Build the 300-char `{`/`}` segments with `Buffer.alloc(n, c).toString()`
instead of `String.prototype.repeat`, which is very slow in debug JSC.
- Capture and assert `stdout` (expected empty) before `exitCode` so a
failure mode that prints to stdout produces a useful diagnostic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What does this PR do?
Fix a
u8overflow inNewLexer::rollback_braces(src/shell_parser/braces.rs) that aborts the debug build ofBun.$.braces(...)when the input has an unclosed outer{wrapping ≥256 levels of balanced{...}.Same shape as #31010 (semver): direct port of
var braces: u8 = 0from the Zig original. Cargo's dev profile enablesoverflow-checks(release does not), so debug panics on the 256th+= 1while release wraps silently (256 → 0), matching Zig's ReleaseFast.Reproducer:
Why nothing upstream stops it: there is no depth cap on the input.
MAX_NESTED_BRACES = 10only setsSmallVecinline capacity (heap-spills on overflow rather than rejecting), andBunObject::bracescapsexpansion_countafter tokenization, which is too late.The fix: widen
bracestou32to match the surrounding index typei: u32.bracesis only compared> 0/== 0, never stored or cast, so a wider type is strictly more correct.How did you verify your code works?
Added a regression test in
test/js/bun/shell/brace.test.tsthat runs$.braceswith 300 nested levels inside an unclosed outer brace in a subprocess (the failure is an uncatchable abort, same pattern as the #31010 test).With the
u32change reverted, the new test fails on debug with the expected stack:The pre-existing 12 brace tests continue to pass.
Note on
--asan=off: the default ASan-enabled debug build crashes at startup on Darwin 25 (AddressSanitizer: CHECK failed: sanitizer_procmaps_mac.cpp:214) — an environmental ASan/macOS issue unrelated to this change.🤖 Generated with Claude Code