Conversation
The hot loop keeps polling the entry-point evaluation promise after module evaluation settles. A raw pointer can outlive its last GC edge and later alias an unrelated handled rejection after the cell is reused. Store the promise in a VM-owned strong slot and route every read and write through that rooted owner. Add a GC-stress hot-reload regression that verifies handled rejections stay handled.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: oven-sh/bun/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. WalkthroughThe VM replaces its public pending-promise pointer and protection flag with a private rooted slot. Entry loading, preload handling, hot reload, and test-runner paths use slot accessors. A hot-reload regression test checks for unhandled rejections across repeated boots and garbage collection. ChangesPending promise tracking
Suggested reviewers: Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to This change makes the hot-reload entry promise safer by keeping it rooted through garbage collection. No actionable merge-blocking issue was identified, and a regression test covers repeated reloads. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Rooting the entry promise addresses a hot-reload lifetime hazard. The remaining design question is whether worker shutdown fully releases the new VM-owned root. Retained concerns
Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Downstream validation update (2026-09-29):
On the retained worker-teardown question from the automated architecture review: |
|
Thank you for this PR, and for the handled-rejection case. It reproduces here as well: on a Linux x64 release build of main without a root, your test reports a caught rejection as unhandled in 7 of 7 runs, within 12 boots. CI for this PR is still blocked, so I carried your commit unchanged on #41146 (449404e, under your name) to run it there. These are the results. They apply to this PR because the source change is the same. The root fixes the bug. On release ASAN builds of main at ad60a9b with and without your commit, your test, the test "should keep reloading after a GC runs between reloads" and the Two existing tests fail with it on every lane that runs them (build 121863):
The slot holds the entry promise for the life of the process, so one Three facts about the test in this PR:
The details and the April history of a |
What does this PR do?
Fix a use-after-free in the entry-point promise polled by the
--hotloop.VirtualMachine.pending_internal_promisewas a rawJSInternalPromisepointer. Once entry-point evaluation settled, the module loader could release the last GC edge while the hot loop continued reading that pointer. After the cell was collected and reused, the hot-only reporter could interpret an unrelated, already-handled rejected promise as the entry-point promise and emit it throughunhandledRejection.Store the value in a VM-owned
strong::Optionaland route every read, replacement, reload clear, and test-isolation clear through that rooted slot. The local preload guard remains in place because HMR can replace the VM slot while that function still uses its original promise.The regression test completes entry-module evaluation before the application work starts, forces GC and promise-cell reuse across 100 reloads, and verifies that caught rejections are never reported as unhandled. Genuine unhandled application rejections and genuine rejected entry modules are still reported; this does not add an
isHandled()filter to the hot reporter.This implements the VM-owned
Strongalternative discussed in #41146 on currentmain. #41146 first identified the dangling entry-promise pointer; the reproduction here additionally observes the stale pointer aliasing the exact caught Promise and Error. Keeping the root onVirtualMachinealso followsREVIEW.md's guidance for per-VM state and avoids adding a field toZigGlobalObject.How did you verify your code works?
caught=true,owned=true, andsameReason=truetest/cli/hot/hot.test.ts: 13/13 passed, 467 assertionsmain:cargo check -p bun_runtimecargo fmt -p bun_jsc -p bun_runtime -- --checktest/cli/hot/hot.test.ts