Conversation
…indent counter When compiling CSS nesting for targets that don't support it, every nested rule's selector prelude inlines the full parent-selector chain. The existing MAX_SELECTOR_EXPANSION cap bounds how many rules that expansion produces, but not how long each rule's prelude is (nesting depth is capped at 512 by the parser). A stylesheet that stacks many single-selector levels above a handful of multi-selector levels stays under the 65,536-rule cap while each of those rules prints a prelude hundreds of levels long: a 10 KB input reached ~200 MB of output and ~1.6 GB RSS (fuzzer signature oom:css:...spec_from_iter_nested...bun_css5rules). Track the selector-prelude bytes written under a compiled-nesting StyleContext and cap them at 64 MB (same as MAX_PREFIX_EXPANSION_BYTES), reporting the existing maximum_nesting_expansion printer error. Also widen Printer.indent_amt from u8 to u16: the parser accepts up to 512 nesting levels and each level indents by 2, so preserving nesting past depth 128 overflowed the counter (a panic in debug builds, wrong indentation in release).
|
Warning Review limit reached
More reviews will be available in 9 minutes and 56 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThe change widens ChangesCSS Nesting Expansion Budget
Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
Updated 11:31 PM PT - Jun 16th, 2026
❌ @autofix-ci[bot], your commit 35cc6d0 has 2 failures in
🧪 To try this PR locally: bunx bun-pr 32454That installs a local version of the PR into your bun-32454 --bun |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
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/css/nested-selector-list-expansion.test.ts`:
- Around line 300-304: The strict assertion checking stderr equality in the
nested-selector-list-expansion test can flake in debug/ASAN builds due to benign
startup noise. Replace the exact stderr equality check with conditional stderr
normalization that filters out non-actionable debug/ASAN noise while still
detecting actual panic signatures. Keep the existing assertions for exitCode,
signalCode, and stdout validation as these provide sufficient crash detection
without being prone to environmental flakes.
🪄 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: cdb4fa99-2827-468a-8ee2-e42dfe1c5607
📒 Files selected for processing (3)
src/css/printer.rssrc/css/rules/style.rstest/js/bun/css/nested-selector-list-expansion.test.ts
…assertion @scope preludes serialize with a parent context the same way style-rule preludes do (scope-end with scope-start as a temporary parent, or an outer StyleContext), so charge them against MAX_NESTING_EXPANSION_BYTES too. Replace the exact stderr equality in the subprocess test with a combined-object assertion (stderr checked only for panic signatures), per the repo convention for subprocess output.
|
Re the duplicate flag: all three PRs (#31913, #31916, #32451) address overlapping pieces of the same CSS nesting-expansion bug family, for different fuzzer signatures. I verified against this PR's exact fuzzer input (signature
This PR is the only standalone change that handles all four passes of this specific reproduction. The PR body's "Relationship to open PRs" section has the same summary; 6881223 adds the |
…ring When @scope has a <scope-end> but no <scope-start>, ScopeRule::to_css returned immediately after serializing <scope-end>, skipping the closing paren, the rule body, the closing brace, and the nesting-expansion byte charge added in 6881223. @scope to (& .x) nested under the padded-then-forked chain therefore still inlined the full ancestor chain per cloned @scope without the byte budget ever firing (~121 MB output from an ~8.5 KB input), and the emitted CSS was truncated to '@scope to (<scope-end>' regardless of nesting. Drop the early return so the scope-end-only branch falls through to the existing close-paren, byte-metering, and body serialization. Add regression tests for both the byte-budget bypass and the truncated output, and update the nesting_expansion_bytes doc comment to list ScopeRule::to_css alongside StyleRule::to_css_base.
|
CI on 35cc6d0: 284/286 jobs passed. The two failures are unrelated to this diff:
|
|
Stale PR review: closing. No human has commented on or reviewed this PR since it opened on 2026-06-17, and it conflicts with main. Each source change in it is merged or in another open PR. The indent counter is already The bug is still on main and stays tracked by #31916. With bun 1.4.3, Reopen if this evidence is wrong. |
What does this PR do?
Fixes a CSS minifier OOM found by fuzzing (signature
oom:css:_RNvC7___rustc12___rust_alloc|_RNvXs_NtNtC5alloc3vec21spec_from_iter_nestedINtB6_3VecINtNtC7bun_css5rules7C10css, on 1.4.0-canary.1+55f6c899f): a ~10 KB input drove the CSS printer to ~200 MB of output and ~1.6 GB peak RSS when compiling nesting for Safari 13, without tripping any existing limit.Root cause
When the browser targets don't support CSS nesting, every nested rule's selector prelude inlines the full parent-selector chain (
serialize_nestingwalks theStyleContextup to the root). The existingMAX_SELECTOR_EXPANSIONcap (65,536) bounds how many rules that expansion produces, but not how long each rule's prelude is; the per-preludeMAX_NESTING_EXPANSIONScap resets at every rule. Nesting depth is bounded only by the parser's 512-levelMAX_NESTING_DEPTH.By placing many single-selector levels above a handful of two-selector levels whose selectors are incompatible with the targets (so they are partitioned into one rule per selector instead of collapsed into
:is()), the selector-expansion total stays well under 65,536 while each of the tens of thousands of resulting rules prints a prelude hundreds of ancestors long. On current main (all existing caps intact, release build):The minimized 10 KB fuzzer input (283 nesting levels, ~14 of which are two-selector with custom/vendor-prefixed pseudo-classes) reaches the same path:
selector_expansion_totaltops out at 50,320 (under 65,536), output is ~200 MB, RSS peaks at ~1.6 GB including the returned JS string.Reachable via
bun build --minifywith default browser targets.Fix
Track the selector-prelude bytes written while a
StyleContextparent chain is being inlined (i.e. the rule is being de-nested) and cap them atMAX_NESTING_EXPANSION_BYTES = 64 MBacross the whole stylesheet, mirroring the existingMAX_PREFIX_EXPANSION_BYTES. Past the cap the printer reports the existingmaximum_nesting_expansionerror. Top-level rules (no parent context) are not charged, so large flat stylesheets are unaffected.Widen
Printer.indent_amtfromu8tou16. The parser accepts up to 512 nesting levels and each level indents by 2, so preserving nesting past depth 128 overflowed the counter: a panic (attempt to add with overflow) in debug builds and wrong indentation in release builds.Relationship to open PRs
hang:css:...write_fmt...) adds the samePrinter::nesting_expansion_bytesmechanism measured per-prelude into_css_base, and also meters@scopepreludes. That PR does not widenindent_amt, so this fuzzer input'sminifyTestpass (no targets, nesting preserved at depth 283) still panics there in debug builds. This PR now includes the same@scopemetering.serialize_nestingplus a minify-side selector-weight estimate; its minify-side estimate also catches this input. That PR does not widenindent_amteither.indent_amt(tou32) and bounds the deep-clone payload weight, but does not catch this input: the clone-weight walk totals ~48 MB here, under its 64 MB cap, and the ~200 MB output is emitted.This PR is the minimal standalone fix that handles every pass of this specific fuzzer reproduction; textual conflicts in
printer.rs/style.rs/scope.rsare expected with whichever of the above lands first.Verification
minifyTest,_test,prefixTest, round-tripminifyTest) now either completes or throwsMaximum nesting expansion exceededinstead of allocating >1 GB.test/js/bun/css/nested-selector-list-expansion.test.ts. On an unfixed build: the padded-then-forked test returns the ~230 MB output instead of throwing, and the depth-500 indentation test panics with the overflow in debug builds. On this branch all 29 tests in the file pass.test/js/bun/css/css.test.ts(1093 pass),nested-selector-expansion.test.ts,nested-vendor-prefix-duplication.test.ts,test/bundler/css/(167 pass). Thecss-fuzz.test.tsdebug-build timeouts are pre-existing on main.