Repository navigation
Conversation
…y in Napi::defineProperty NapiClass::create -> finishCreation opens and closes its own ThrowScope. When Napi::defineProperty builds a method/getter/setter descriptor it calls NapiClass::create and then immediately defineOwnProperty without observing the exception state in between, which aborts under BUN_JSC_validateExceptionChecks=1 with: This scope can throw: finishCreation @ NapiClass.cpp:120 Unchecked as of: defineOwnNonIndexProperty @ JSObject.cpp:4062 Add RETURN_IF_EXCEPTION after each of the three NapiClass::create calls so the pending check is satisfied before defineOwnProperty enters its own scope. Covers both callers (napi_define_class and napi_define_properties).
|
Updated 7:21 PM PT - Jul 19th, 2026
❌ @robobun, your commit f140d3e has 1 failures in
🧪 To try this PR locally: bunx bun-pr 34750That installs a local version of the PR into your bun-34750 --bun |
|
Reproduced locally on a debug+ASAN build: CI (build #75915): the new test passed on every lane. Remaining red is unrelated to this diff: |
|
Warning Review limit reached
Next review available in: 19 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 |
The exports object passed to Init may carry loader-added own properties that differ between Bun and Node; the test only cares that all four descriptor kinds were installed without the validator aborting.
There was a problem hiding this comment.
LGTM — three mechanical RETURN_IF_EXCEPTION insertions matching the pattern already used elsewhere in the same function.
What was reviewed:
- Confirmed
NapiClass::finishCreationdeclares a ThrowScope (NapiClass.cpp:120), so the check is required before the subsequentdefineOwnProperty. - Checked the other
NapiClass::createcall sites:napi_define_classalready hasNAPI_RETURN_IF_EXCEPTION;napi_create_functionhas no ThrowScope-constructing call betweencreateand return, so it doesn't trip the validator. - Test drains pipes concurrently, skips when
ccis unavailable / on Windows, doesn't assert exact stderr, and asserts addon-defined properties without depending on loader-added ones. - The lone CI failure (
test-net-connect-memleak.js) is unrelated to N-API.
Extended reasoning...
Overview
This PR adds three RETURN_IF_EXCEPTION(scope, napi_pending_exception) calls in Napi::defineProperty (src/jsc/bindings/napi.cpp), one after each NapiClass::create call for getter, setter, and method descriptors. It also adds a new test file that compiles minimal C addons against the in-repo N-API headers and loads them under BUN_JSC_validateExceptionChecks=1 to prove the validator no longer aborts.
Security risks
None. This is purely JSC exception-scope discipline in the N-API property definition path — no user input parsing, no auth/crypto, no boundary validation changes.
Level of scrutiny
Low. The native change is three identical one-liners that mirror the exact pattern already used at napi.cpp:345 (after toPropertyKey) and napi.cpp:389 (after defineOwnProperty) in the same function. NapiClass::finishCreation at NapiClass.cpp:120 does declare a ThrowScope, so the checks are correctly placed. The return value napi_pending_exception matches every other exception-check site in this function.
Other factors
- I checked for sibling
NapiClass::createsites that might need the same treatment.napi_define_class(napi.cpp:2179) already hasNAPI_RETURN_IF_EXCEPTIONimmediately after.napi_create_function(napi.cpp:969) does not, but there is no ThrowScope-constructing call betweencreateandNAPI_RETURN_SUCCESS, so the validator cannot trip there — and the PR description explicitly scopes the broader boundary-scope work to #32911. - The test follows harness conventions well:
tempDir,bunEnvspread, concurrent pipe drain,test.concurrent,skipIf(isWindows || !cc), and it asserts{stdout, exitCode}viatoMatchObjectwhile including stderr in the received object for diagnostics without asserting it exactly (debug builds emit noise). The second test intentionally filters to addon-defined own properties rather than asserting the full set, avoiding coupling to loader implementation details. - The one CI failure so far is
test/js/node/test/parallel/test-net-connect-memleak.js, which touches nothing in the N-API path and is clearly unrelated flake. - The PR description demonstrates the test fails without the fix (exit 134) and passes with it, satisfying the "fails for the right reason" requirement.
Repro
Any N-API addon whose
Initcallsnapi_define_classornapi_define_propertieswith a method, getter, or setter descriptor aborts underBUN_JSC_validateExceptionChecks=1:Hit by every
test/napi/node-napi-tests/test/js-native-api/*suite that defines a class or a function property (33 entries in the# 3rd party napisection oftest/no-validate-exceptions.txt).Cause
Napi::defineProperty(shared bynapi_define_classandnapi_define_properties) builds method/getter/setter descriptors by callingNapiClass::create, which opens and closes its ownThrowScopeinfinishCreation. On return that scope's destructor simulates a throw. The very next call isdefineOwnProperty, which entersdefineOwnNonIndexPropertyand constructs a newThrowScope; its constructor finds the unsatisfied check andRELEASE_ASSERTs. This surfaced after #34134 routed every descriptor kind throughdefineOwnProperty.Fix
Add
RETURN_IF_EXCEPTION(scope, napi_pending_exception)after each of the threeNapiClass::createcalls inNapi::defineProperty(getter, setter, method), so the exception state is observed beforedefineOwnProperty. The same pattern is already used aftertoPropertyKeyand afterdefineOwnPropertyin the same function.Verification
test/napi/napi-define-property-exception-check.test.tscompiles two tiny C addons with the systemccagainst the in-repo headers (no node-gyp) andrequire()s each in a child process withBUN_JSC_validateExceptionChecks=1: one exercisingnapi_define_classwith method/getter/setter/accessor properties, one exercisingnapi_define_propertieswith the same shapes. Without the fix both abort (exit 134) with thefinishCreation/defineOwnNonIndexPropertyreport above; with it, both load cleanly. On release builds the validator is compiled out, so the option is a no-op and the tests pass trivially; the gate runs a debug build.Also ran against the fixed debug build:
test/napi/node-napi-tests/test/js-native-api/{6_object_wrap,test_constructor,test_properties}/do.test.ts(9 pass) andtest/napi/napi.test.ts -t napi_define(the[[DefineOwnProperty]]test from #34134 still passes).Relationship to #32911
#32911 (open) addresses the broader question of addon C code not being able to satisfy JSC's exception-check discipline between consecutive
napi_*calls, via aNapiBoundaryScopeat the preamble level, and as a secondary fix adds the getter/setter checks here (but not the method one). This PR is the narrowNapi::definePropertyhalf of that and stands on its own: a singlenapi_define_classcall is enough to abort, regardless of what the addon does before or after it. Theno-validate-exceptions.txtentries are intentionally left in place since most of them still need the preamble-side fix from #32911 (or thedlopenpath fix it mentions) before the validator can be enabled for them.