Repository navigation
napi: reject SharedArrayBuffer in napi_detach_arraybuffer - #34143
Conversation
napi_detach_arraybuffer on a SharedArrayBuffer returned napi_ok without detaching anything. JSC backs SharedArrayBuffer with the same JSArrayBuffer cell type as a plain ArrayBuffer, so the dynamicDowncast succeeded and the isDetachable() guard silently skipped the detach before falling through to NAPI_RETURN_SUCCESS. Node returns napi_arraybuffer_expected here because V8's IsArrayBuffer() is false for a SharedArrayBuffer. The false napi_ok is a memory-lifetime signal: an addon that trusts it may free or recycle a backing store that JS (and other threads holding the same SharedArrayBuffer) still read and write. Also align napi_is_detached_arraybuffer with Node: it now returns napi_ok with result=false for any non-ArrayBuffer value instead of napi_arraybuffer_expected. Also deduplicate get_all_property_names in the napi test addon; #34126 and #34130 each added a definition and the file no longer compiled.
|
Updated 7:36 AM PT - Jul 14th, 2026
❌ @robobun, your commit 5cb2d80 has 2 failures in
🧪 To try this PR locally: bunx bun-pr 34143That installs a local version of the PR into your bun-34143 --bun |
WalkthroughChangesThe PR aligns N-API ArrayBuffer detachment behavior with Node/V8 semantics, adds regression coverage for shared and detached buffers, and updates property-name test helpers to use structured results. ArrayBuffer detachment behavior
Property-name test helper contract
Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
The |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
There was a problem hiding this comment.
No bugs found. This is a focused N-API compat fix with a checkSameOutput test that diffs Bun against Node byte-for-byte, but it changes error-return semantics for two production N-API functions (including relaxing napi_is_detached_arraybuffer from error → napi_ok/false on non-ArrayBuffers, and tightening napi_detach_arraybuffer from napi_ok → error on non-detachable buffers). Worth a human look given N-API's ecosystem-wide reach.
What was reviewed:
napi_detach_arraybuffer/napi_is_detached_arraybufferlogic in napi.cpp — theisShared()gate matches the established pattern from #32624, andisDetached() || isDetachable()correctly keeps double-detach asnapi_ok.- Confirmed
napi_detachable_arraybuffer_expectedis a defined status andJSArrayBuffer::isShared()is used elsewhere in bindings. - Checked the existing
test_napi_empty_buffer_infopath (callsnapi_is_detached_arraybufferon a real ArrayBuffer) — unaffected by the new short-circuit. - The
get_all_property_namesdedup +.keyscaller update in module.js is consistent with the surviving{status, keys}helper.
Extended reasoning...
Overview
Two small edits to src/jsc/bindings/napi.cpp aligning napi_detach_arraybuffer and napi_is_detached_arraybuffer with Node.js semantics, plus a new test_detach_arraybuffer C++ helper in the napi-app addon and a checkSameOutput test in napi.test.ts that asserts Bun's status codes match Node's for [SharedArrayBuffer, ArrayBuffer, already-detached ArrayBuffer, Uint8Array]. Also removes a duplicate get_all_property_names definition in js_test_helpers.cpp (two prior PRs each landed one, breaking the addon build) and updates the one caller in module.js to use the surviving {status, keys} return shape.
Security risks
None. No untrusted-input parsing, allocation sizing, or auth/crypto paths are touched. The change only adjusts which napi_status enum value is returned for edge-case inputs.
Level of scrutiny
Medium-high. The diff itself is ~10 lines of production code and follows the exact pattern established in #32624 (!jsArrayBuffer->isShared() to model V8's IsArrayBuffer()). But N-API is load-bearing for every native addon, and this PR changes return semantics in three ways: (1) SAB detach now errors instead of silently succeeding, (2) non-detachable non-shared buffers (e.g. Wasm memory) now error instead of silently succeeding, (3) napi_is_detached_arraybuffer on non-ArrayBuffers now returns napi_ok/false instead of napi_arraybuffer_expected. All three match Node — verified by the checkSameOutput diff test — but they are user-observable behavior changes that a maintainer should acknowledge.
Other factors
- The PR description notes tests could not be run locally ("Platform-specific test(s) that do not run on this machine. Deferring to CI"), so correctness rests on the CI run.
- The test addon dedup is a necessary drive-by build fix, but it's a second concern in the PR that a reviewer should be aware of.
- The
isDetached() || isDetachable()disjunct is defensively written to keep double-detach atnapi_okregardless of whether JSC'sisDetachable()flips after detach — this looks correct but is exactly the kind of engine-semantics subtlety a maintainer familiar with JSC's ArrayBuffer internals should confirm.
There was a problem hiding this comment.
I didn't find any bugs. The change is small and well-reasoned, but it alters error-return semantics for two N-API functions in production C++ bindings, and overlaps with #34131/#34141 — worth a human confirming the direction and merge order.
What was reviewed:
napi_detach_arraybuffer/napi_is_detached_arraybufferlogic vs Node'sIsArrayBuffer()/IsDetachable()gates — matches; already-detached path still returnsnapi_ok.- Short-circuit ordering in
jsArrayBuffer && !isShared() && impl()->isDetached()— no null-deref regression vs old code. - New
checkSameOutputtest covers SAB, AB, re-detach, and typed array; existingtest_napi_empty_buffer_infostill passes the newnapi_is_detached_arraybuffercontract. get_all_property_namesdedup: remaining{status, keys}overload is still registered; the one bare-array caller inmodule.jswas updated.
Extended reasoning...
Overview
Two N-API functions in src/jsc/bindings/napi.cpp are aligned with Node.js semantics:
napi_detach_arraybuffer: rejectsSharedArrayBufferwithnapi_arraybuffer_expected(was silently returningnapi_ok), and rejects non-detachable buffers withnapi_detachable_arraybuffer_expected(was silently succeeding). Already-detached buffers still succeed.napi_is_detached_arraybuffer: now always returnsnapi_okand writesfalsefor any non-ArrayBuffer/SAB input, instead of returningnapi_arraybuffer_expectedwithout writing*result.
Test coverage is added via a new test_detach_arraybuffer addon function driven by checkSameOutput (byte-for-byte diff against Node). A bundled compile fix removes a duplicate get_all_property_names definition in the test addon and updates its one caller.
Security risks
None. This tightens validation (previously lied about detach success on a shared buffer, which the PR correctly frames as a memory-lifetime hazard for addons). No new untrusted-input parsing, no allocation changes.
Level of scrutiny
Medium. The diff is ~15 lines of production C++, follows the exact pattern of the neighboring napi_is_arraybuffer SAB fix (same !isShared() guard, same macro usage), and is verified against Node via checkSameOutput. But it changes user-visible error codes for two N-API entry points — an addon that previously got napi_ok on a non-detachable buffer will now get napi_detachable_arraybuffer_expected. That's the Node-correct behavior, but it's a semantic change a maintainer should sign off on.
Other factors
- The PR notes tests couldn't be run locally (platform-specific; deferred to CI).
- Overlaps with open PR #34131 (also touches
napi_detach_arraybuffervalidation) and #34141 (same test-helper dedup) — merge order needs human coordination. - I checked that the new
napi_is_detached_arraybuffercontract doesn't break the existingtest_napi_empty_buffer_infotest (it passes a real non-shared detached ArrayBuffer, sois_detachedremains true). NAPI_RETURN_EARLY_IF_FALSEcorrectly routes the new error code throughnapi_set_last_error, sonapi_get_last_error_infostays consistent.
|
CI status across builds #72841 and #72871:
None of these exercise N-API ArrayBuffer detach. Ready for review. |
What
napi_detach_arraybufferon aSharedArrayBufferreturnednapi_okwithout detaching anything. Node returnsnapi_arraybuffer_expected(19).Repro
Cause
JSC backs
SharedArrayBufferwith the sameJSC::JSArrayBuffercell type as a plainArrayBuffer, sodynamicDowncast<JSArrayBuffer>succeeds. TheisDetachable()check (which is false for shared buffers) then silently skipped the detach and fell through toNAPI_RETURN_SUCCESS. V8 treatsSharedArrayBufferas a distinct type, so Node'svalue->IsArrayBuffer()check rejects it up front.The
napi_okhere is a memory-lifetime lie: an addon that trusts it may free or recycle a backing store that JS (and other threads holding the same SAB) still read and write.Fix
In
napi_detach_arraybuffer: reject shared buffers withnapi_arraybuffer_expected(matching Node'sIsArrayBuffer()gate), then requireisDetachable()withnapi_detachable_arraybuffer_expectedinstead of succeeding as a no-op. Detaching an already-detached buffer remainsnapi_ok.Also aligns
napi_is_detached_arraybufferwith Node: it now returnsnapi_okwithresult=falsefor any non-ArrayBuffer value (typed arrays, SAB, anything else) instead ofnapi_arraybuffer_expected.Verification
New
test_detach_arraybufferin the napi-app addon prints the status for[SharedArrayBuffer, ArrayBuffer, <same ArrayBuffer again>, Uint8Array]andcheckSameOutputasserts Bun matches Node byte-for-byte.Fails on system Bun (SAB row:
napi_detach_arraybuffer=0vs Node's19; Uint8Array row:napi_is_detached_arraybuffer=19vs Node's0), passes with this change.Also in this PR
Deduplicates
get_all_property_namesintest/napi/napi-app/js_test_helpers.cpp. #34126 and #34130 each landed a definition with a different return shape and the addon no longer compiled on main; kept the{status, keys}form and updated the one caller that expected a bare array.no test proof · iteration 1 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/napi/napi.test.ts