napi: guard string creators and buffer accessors against pending VM exception - #36095
napi: guard string creators and buffer accessors against pending VM exception#36095robobun wants to merge 3 commits into
Conversation
…xception
Under debug/assert builds, calling napi_create_string_{utf8,latin1,utf16} or
napi_{is_arraybuffer,get_arraybuffer_info,get_typedarray_info,get_dataview_info,
get_buffer_info} while a JSC VM exception is pending aborts in
ExceptionScope::releaseAssertNoException. The Rust entry points used the bare
get_env! prologue and then called into C++ helpers (BunString__transferToJS /
BunString__createUTF8ForJS / JSC__JSValue__asArrayBuffer) whose validation
scope asserts no VM exception.
Add preamble_no_pending_check! (Rust mirror of NAPI_PREAMBLE_NO_PENDING_CHECK)
that returns napi_pending_exception when vm.m_exception is set but leaves an
env-stashed napi_throw* exception alone, and use it at the eight sites. A
stashed exception does not reach the asserting helpers, and Node.js permits
these calls with a stashed exception (test_deferred_exceptions relies on
that), so only the VM slot is gated.
|
Warning Review limit reached
Next review available in: 28 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 (4)
Comment |
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
On the overlap with #36091 and #36093: those two together cover the same eight entry points using |
These two already return napi_pending_exception without aborting: constructEmptyArray declares its own ThrowScope and hits RETURN_IF_EXCEPTION(scope, nullptr) before allocating, so call_zero_is_throw sees a zero return and takes the Err branch. Pin that behavior alongside the eight entry points this PR guards.
|
Updated 6:53 AM PT - Jul 27th, 2026
❌ @robobun, your commit c43ba8b has 1 failures in
🧪 To try this PR locally: bunx bun-pr 36095That installs a local version of the PR into your bun-36095 --bun |
There was a problem hiding this comment.
No issues found with the implementation itself, but this and #36091/#36093 are mutually-exclusive fixes for the same abort — prologue-gate (returns napi_pending_exception, diverging from Node) vs. SuspendExceptionScope (Node-parity napi_ok). A maintainer should pick which approach lands.
What was reviewed:
NapiEnv__hasVMException/preamble_no_pending_check!correctly mirror the existing C++NAPI_PREAMBLE_NO_PENDING_CHECK(DECLARE_TOP_EXCEPTION_SCOPE+NAPI_RETURN_IF_VM_EXCEPTION).- The test only prints "survived" (not status codes), so
checkSameOutputholds despite the status divergence; the bigint-overflow route is a no-op on Node so that line matches too. - My earlier
napi_create_arrayconcern was refuted empirically and is now pinned in the test.
Extended reasoning...
Overview
Adds a Rust-side mirror of the C++ NAPI_PREAMBLE_NO_PENDING_CHECK macro: a new NapiEnv__hasVMException FFI (napi.cpp), has_vm_exception() wrapper, and preamble_no_pending_check! macro in napi_body.rs. Eight Rust NAPI entry points (napi_create_string_{utf8,latin1,utf16}, napi_is_arraybuffer, napi_get_{arraybuffer,typedarray,dataview,buffer}_info) swap get_env! for the new macro so they return napi_pending_exception instead of aborting on the JSC exception-validation assert when vm.m_exception is already set. A new test_vm_pending_exception_no_abort in the napi test addon exercises all eight plus napi_create_array{,_with_length} via checkSameOutput.
Security risks
None. This is a defensive early-return in NAPI entry points; no new attack surface, no untrusted input parsing, no memory ownership changes.
Level of scrutiny
Medium-high. The change is small and mechanically follows an established in-tree pattern (the C++ NAPI_PREAMBLE_NO_PENDING_CHECK at napi.cpp:113), but it touches exception-handling semantics in the NAPI layer and deliberately diverges from Node's return status for these eight functions. The correctness of the change itself is straightforward; the design question — whether Bun should return napi_pending_exception here (this PR) or suspend the exception and return napi_ok like Node (#36091/#36093) — is a maintainer-level decision.
Other factors
- Two open competing PRs (#36091, #36093) take the alternative
SuspendExceptionScopeapproach; the author has already flagged them as mutually exclusive in the thread. - My prior inline finding on
napi_create_arraywas refuted with a traced explanation (constructEmptyArrayhitsRETURN_IF_EXCEPTIONand returns null, socall_zero_is_throwtakes theErrbranch) and the author added those two calls to the test to pin the behavior. - The test is carefully constructed to produce identical output on Node and Bun despite differing status codes (it prints "survived" rather than the status), and cleans up the pending exception at the end.
- The comment-cop bot flags on the doc comments were resolved; the remaining doc comments are the same length/style as neighboring
has_pending_exception/preamble!docs. - PR description reports fail-before/pass-after but notes "no test proof" locally (deferred to CI), so CI results should be checked before merge.
|
CI #83339: the only hard failure is |
Repro
Release builds return
napi_okand keep running; the assertion is compiled out there. The same abort is reachable without an addon throw vianapi_create_bigint_wordspast the engine cap (throws aRangeErrorinto the VM, returns 10), then any of the eight calls below.Cause
The Rust
napi_create_string_{utf8,latin1,utf16}andnapi_{is_arraybuffer,get_arraybuffer_info,get_typedarray_info,get_dataview_info,get_buffer_info}entry points used the bareget_env!prologue and then called into C++ helpers (BunString__transferToJS/BunString__createUTF8ForJS/JSC__JSValue__asArrayBuffer) whose exception-validation scope asserts the VM has no pending exception when they return a value. Withvm.m_exceptionalready set, that assertion fails.Fix
Add
preamble_no_pending_check!innapi_body.rs(the Rust mirror ofNAPI_PREAMBLE_NO_PENDING_CHECK): it returnsnapi_pending_exceptionwhenvm.m_exceptionis set but leaves an env-stashednapi_throw*exception alone. Use it at all eight sites.Only the VM slot is gated because (a) a stashed exception never reaches the asserting helpers, and (b) Node.js allows these calls with a stashed exception pending: the existing
test_deferred_exceptionsusesnapi_create_string_utf8immediately after aThrowAsJavaScriptException()and expectsnapi_ok, viacheckSameOutput. The fullerpreamble!(which also gates on the stash) would regress that. With a VM exception pending Node.js still returnsnapi_okfrom these eight; Bun now returnsnapi_pending_exception, which joins the existing status divergence shared withnapi_create_objectet al rather than aborting.Test
test_vm_pending_exception_no_abortintest/napi/napi-app/standalone_tests.cpparms a VM exception vianapi_throw+napi_call_function, calls all eight entry points, and asserts the originalError: E1is still pending and catchable afterwards. It then re-arms via thenapi_create_bigint_wordsoverflow route and callsnapi_create_string_utf8again. Output matches Node byte-for-byte viacheckSameOutput.Fail-before (
src/stashed,bun bd test test/napi/napi.test.ts -t 'does not abort when a VM exception'): aborts withreleaseAssertNoException, exit 134.After: passes;
test_deferred_exceptionsandtest_pending_exception_gateunchanged.Related: #36091 takes the alternative node-parity approach (suspend the exception and return
napi_ok) for the three string creators only.no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/napi/napi.test.ts