process: install UnhandledPromiseRejectionWarning .stack as non-enumerable - #34292
process: install UnhandledPromiseRejectionWarning .stack as non-enumerable#34292robobun wants to merge 2 commits into
Conversation
…rable Bun__promises__emitUnhandledRejectionWarning copied the reason's stack onto the warning ErrorInstance via the 3-arg putDirect overload, which defaults to attributes 0 (enumerable). Node installs it non-enumerable, so Object.keys / JSON.stringify / for..in / spread on the warning object diverged. This was the one engine-installed .stack write on an ErrorInstance that #34259 did not cover.
|
Warning Review limit reached
Next review available in: 12 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
Comment |
|
Reproduced with CI on builds 73577 and 73587: the new test passes on every lane. Remaining reds are unrelated to this diff ( |
|
Updated 9:52 PM PT - Jul 15th, 2026
❌ @robobun, your commit dbafa9c has 1 failures in
🧪 To try this PR locally: bunx bun-pr 34292That installs a local version of the PR into your bun-34292 --bun |
There was a problem hiding this comment.
LGTM — one-line attribute flag addition matching the pattern from #34259, with a targeted regression test.
What was reviewed:
- Verified the
DontEnum | 0attribute matches every other.stackwrite on ErrorInstance in the bindings (FormatStackTraceForJS.cpp, NodeVM.cpp, BunProcess.cpp:2020) and the adjacent.namewrite on line 1320. - Confirmed the two intentionally-untouched
putDirect(.stack, ..., 0)sites (BunProcess.cpp:2372, BunProcessReportObjectWindows.cpp:277) are on theprocess.reportplain object, not an ErrorInstance, so leaving them enumerable is correct. - Test follows harness conventions (spawn with
-e,await using, concurrent pipe drain) and is placed alongside the existing unhandledRejection tests.
Extended reasoning...
Overview
Single-character-class change: adds JSC::PropertyAttribute::DontEnum | 0 as the fourth argument to one putDirect call in Bun__promises__emitUnhandledRejectionWarning (src/jsc/bindings/BunProcess.cpp:1328). This makes the .stack property installed on the UnhandledPromiseRejectionWarning Error object non-enumerable, matching Node.js behavior. A 45-line regression test is added to test/js/node/process/process.test.js.
Security risks
None. This changes only a property attribute flag on a warning object; no input parsing, no auth, no allocation, no control-flow change.
Level of scrutiny
Low. This is a mechanical follow-up to #34259 (already merged as 8215078), which applied the identical fix to the FormatStackTraceForJS.cpp sites. A grep of putDirect.*propertyNames->stack across src/jsc/bindings/ confirms every ErrorInstance .stack write now uses DontEnum, and the two remaining attributes-0 writes are on the process.report.getReport().javascriptStack plain object where enumerability is intentional (as the PR description states and the code confirms — javascriptStack is a plain constructEmptyObject, not an ErrorInstance). The adjacent .name putDirect on line 1320 already uses the exact same DontEnum | 0 idiom, so this is purely bringing one line into consistency.
Other factors
The test is well-constructed: it spawns a subprocess with --unhandled-rejections=warn, filters to the specific warning whose .stack matches the reason's stack, and asserts enumerable: false / configurable: true plus that Object.keys, for..in, and JSON.stringify all omit stack. It correctly avoids asserting data-vs-accessor descriptor shape (a known JSC/V8 divergence). Pipes are drained concurrently, bunEnv/bunExe() are used, and it sits inside the existing describe.concurrent block next to related unhandledRejection tests. The PR description documents USE_SYSTEM_BUN=1 failure and bun bd pass. No outstanding reviewer comments; no prior claude[bot] reviews on this PR.
If a future regression causes no warning to match the filter, the fixture now
emits "null" instead of "undefined" so the assertion shows a readable
result: null vs {...} diff rather than a bare JSON.parse SyntaxError.
There was a problem hiding this comment.
LGTM — one-token attribute addition matching the established pattern from #34259.
What was reviewed:
- The
putDirectchange matches every other.stackwrite onErrorInstancein the bindings (FormatStackTraceForJS.cpp,NodeVM.cpp, and the.namewrite two lines above). - Confirmed the two intentionally-excluded
.stackwrites (report objects) are on plain objects, notErrorInstance— correct to leave alone. - Test's TDZ on
reasonis fine (handler runs async after the const initializes); thew.stack !== reason.stackfilter correctly selects the second of the two emitted warnings.
Extended reasoning...
Overview
Single-line change in src/jsc/bindings/BunProcess.cpp: adds JSC::PropertyAttribute::DontEnum | 0 to the putDirect call that copies the rejection reason's .stack onto the UnhandledPromiseRejectionWarning ErrorInstance. Accompanied by a subprocess-spawning test in test/js/node/process/process.test.js that asserts the descriptor is non-enumerable and that Object.keys/for..in/JSON.stringify all omit stack.
Security risks
None. This changes only the enumerability attribute of a property on a warning object. No auth, crypto, permissions, untrusted input parsing, or memory management is touched.
Level of scrutiny
Low. This is a follow-up to #34259 (visible in recent commits) which fixed the same class of bug at other sites. The | 0 idiom and DontEnum attribute are used identically on line 1320 of the same function for .name, and at 7+ other .stack writes across FormatStackTraceForJS.cpp and NodeVM.cpp. There is no ambiguity in what the change does or why.
Other factors
- The PR description explicitly enumerates the two other attributes-0
.stackwrites (process.report.getReport().javascriptStack.stack) and correctly explains why they're excluded — those populate plain-object fields that Node also exposes as enumerable. - The test follows the file's conventions:
await usingon the spawn, concurrent drain of stdout/stderr/exited, combined-object assertion. It correctly handles the fact that two warnings fire (one withreasonStackas the message, one with thewarningobject) by filtering onw.stack === reason.stack. - PR author verified against Node v26.3.0 and confirmed
USE_SYSTEM_BUN=1fails /bun bdpasses. - No prior reviewer comments to address; no CODEOWNERS concerns for a mechanical Node-compat attribute fix.
Repro
Cause
Bun__promises__emitUnhandledRejectionWarninginsrc/jsc/bindings/BunProcess.cppcopies the rejection reason's.stackonto the warningErrorInstanceviawarning->putDirect(vm, vm.propertyNames->stack, reasonStack). The 3-argputDirectoverload defaults to attributes 0, which is enumerable. Node assignswarning.stack = reason.stack, and V8's Error stack accessor keeps the property non-enumerable.This was the one engine-installed
.stackwrite on anErrorInstancenot covered by #34259, which fixed the same pattern at theFormatStackTraceForJS.cppsites.Fix
Pass
JSC::PropertyAttribute::DontEnumon thatputDirect, matching every other.stackwrite on anErrorInstancein the bindings andErrorInstance::materializeErrorInfoIfNeededitself. The two otherputDirect.stackwrites with attributes 0 (BunProcess.cpp:2372,BunProcessReportObjectWindows.cpp:277) are left alone: they populateprocess.report.getReport().javascriptStack.stack, an enumerable array field on a plain object in Node as well.Verification
New test in
test/js/node/process/process.test.jsspawnsbun --unhandled-rejections=warn, picks the warning whose.stackis the reason's stack, and assertsenumerable: false,configurable: true, and thatObject.keys/for..in/JSON.stringifyall omitstack. Every asserted field was first checked against Node v26.3.0. The test does not assert data-vs-accessor shape (V8 keeps an accessor, JSC writes a data property); the observable enumerability behaviour is identical.Also green:
test/js/node/promise/reject-tostring.test.ts,test/js/node/v8/capture-stack-trace.test.js, the existingunhandledRejectiontests inprocess.test.js, andBUN_JSC_validateExceptionChecks=1over the warn path.no test proof · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/node/process/process.test.js