Fix crash in Bun.build with many conditions - #30466
Conversation
The expression 'if (allow_addons) 1 else 0 + conditions.len' parses as 'if (allow_addons) 1 else (0 + conditions.len)' due to Zig's if/else precedence, so when allow_addons is true (the default), conditions.len was dropped from the ensureTotalCapacity computation. Passing enough conditions to Bun.build() would then overflow the preallocated capacity and trip an assertion in putAssumeCapacity.
|
Updated 9:41 PM PT - May 10th, 2026
❌ @robobun, your commit cc22fb4 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 30466That installs a local version of the PR into your bun-30466 --bun |
There was a problem hiding this comment.
LGTM — straightforward operator-precedence fix with a regression test.
Extended reasoning...
Overview
Three identical lines in ESMConditions.init (src/bundler/options.zig) replace if (allow_addons) 1 else 0 with @intFromBool(allow_addons) so that + conditions.len is no longer swallowed into the else branch. A 10-line regression test in bun-build-api.test.ts exercises Bun.build with 8 custom conditions to cover the previously-crashing path.
Security risks
None. This is an arithmetic fix to a hashmap capacity hint; no auth, parsing, or external-input handling is touched beyond what already existed.
Level of scrutiny
Low. The diff is mechanical and self-evidently correct: @intFromBool(allow_addons) yields the intended 0/1 and the surrounding addition now matches the actual number of putAssumeCapacity calls below (verified against the insert loops for default/import/require maps). The style map line is untouched, which is correct since it never inserts node-addons.
Other factors
The one inline comment is a non-blocking nit about a sibling occurrence of the same pattern in loadersFromTransformOptions; that one only mis-sizes a capacity hint (subsequent inserts use getOrPutValue, which grows) and is pre-existing, so it doesn't affect approval. No prior reviewer comments to address, and the fix was fuzzer-discovered with a clear repro.
Same pattern; this one only mis-sized a capacity hint since subsequent inserts use getOrPutValue which grows, but fix it for consistency.
There was a problem hiding this comment.
LGTM — the precedence fix is correct and the sibling case in loadersFromTransformOptions is now addressed too.
Extended reasoning...
Overview
Two surgical arithmetic fixes in src/bundler/options.zig plus a regression test:
ESMConditions.init: replacesif (allow_addons) 1 else 0 + conditions.lenwith@intFromBool(allow_addons) + conditions.lenin threeensureTotalCapacitycalls. The old form parsed asif (allow_addons) 1 else (0 + conditions.len), droppingconditions.lenfrom the capacity request whenallow_addonsis true (the default), which tripped theputAssumeCapacityassertion with enough custom conditions.loadersFromTransformOptions: parenthesizes the twoif (...) N else 0terms sodefault_loader_ext.lenis always included in the capacity hint (follow-up to my earlier comment, fixed in be7e33a).- New
test/bundler/bun-build-api.test.tscase builds with 8 customconditionsto cover the original crash.
Security risks
None. This only adjusts hashmap capacity arithmetic for bundler option setup; no user-input parsing, auth, or boundary changes.
Level of scrutiny
Low. The diff is ~6 lines of integer-expression rewrites whose correctness is verifiable by inspection: @intFromBool(allow_addons) yields 0/1 with no precedence ambiguity, and the parenthesized if expressions now sum exactly the intended terms. The behavior change is strictly "reserve enough capacity" — semantics of the maps are unchanged.
Other factors
- My previous review comment (the sibling precedence bug in
loadersFromTransformOptions) was addressed. - A regression test was added that exercises the crashing path.
- CI on the first commit (ba9a2d0) showed build-zig failures across all platforms; the robobun status hasn't yet refreshed for be7e33a. CI remains the merge gate, so approving here doesn't bypass that — if the failure is real it'll still block, but the code change itself reads as correct Zig.
There was a problem hiding this comment.
LGTM — the sibling loadersFromTransformOptions case is now fixed too.
Extended reasoning...
Overview
Two-line arithmetic fix in src/bundler/options.zig plus a regression test:
ESMConditions.init(options.zig:1090-1092): replacesif (allow_addons) 1 else 0with@intFromBool(allow_addons)in threeensureTotalCapacitycalls. The old form parsed asif (allow_addons) 1 else (0 + conditions.len), so whenallow_addonswas true the user-suppliedconditions.lenterm was dropped from the capacity request and the subsequentputAssumeCapacityloop asserted.loadersFromTransformOptions(options.zig:1575-1577): parenthesizes the sameif ... else 0 + ...pattern. This one was only a capacity hint (followed by growinggetOrPutValueinserts), but it's the exact footgun I flagged in my earlier review and the author addressed it in be7e33a.- New
test.concurrentinbun-build-api.test.tsthat callsBun.buildwith 8 conditions and assertsbuild.success.
Security risks
None. Pure integer-arithmetic correction in a capacity calculation; no parsing, auth, or untrusted-input handling changes.
Level of scrutiny
Low. The diff is six lines of mechanical precedence disambiguation with no behavioral change beyond requesting the intended capacity. @intFromBool returns a u1 so the sum is now unambiguous, and the parenthesized form in loadersFromTransformOptions is trivially equivalent to intent. The regression test follows existing patterns in the file (tempDirWithFiles + Bun.build + expect(build.success)).
Other factors
My earlier inline comment on this PR pointed out the second occurrence in loadersFromTransformOptions; it was fixed in a follow-up commit and the thread is resolved. The bug-hunting pass found nothing on the latest revision. No CODEOWNERS apply to these paths.
|
CI failure is |
|
Dupe-checked from #30619 — this fixes the same capacity-precedence bug the issue reporter hit on the CLI side ( |
Fires on `else 0 + <addend>` / `else 1 + <addend>` — Zig's `if` expression has lower precedence than `+`, so the addend is absorbed into the else-branch and silently dropped when the condition is true. Backed by oven-sh/bun#30466 and 20+ duplicate PRs where `if (allow_addons) 1 else 0 + conditions.len` dropped `conditions.len` whenever `allow_addons` was true, crashing Bun.build with several conditions via putAssumeCapacity past the reserved slots.
ESMConditions.initcomputed hashmap capacity with:which Zig parses as
if (allow_addons) 1 else (0 + conditions.len). Whenallow_addonsis true (the default forBun.build),conditions.lenwas dropped from the capacity request, so passing several custom conditions would overflow the preallocated map and hit theputAssumeCapacityassertion on the bundle thread.Replaced the
ifexpression with@intFromBool(allow_addons)so the sum is unambiguous.Repro
Found by Fuzzilli (fingerprint
f7b40b0e431d91d6).