bun:ffi: accept FFIType.buffer and FFIType.buffer_length as numeric type tags - #38430
bun:ffi: accept FFIType.buffer and FFIType.buffer_length as numeric type tags#38430robobun wants to merge 1 commit into
Conversation
…ype tags Symbol signatures spelled with the numbers 20 (buffer) or 21 (buffer_length) were rejected with "invalid ABI type" while the string spellings worked. generate_symbol_for_function filtered numeric tags through ABIType::MAX, which was still napi_value (19): it was not bumped when Buffer and BufferLength were added to the enum. ABIType::from_int already returns None for unknown discriminants, so drop MAX and the filter and let from_int be the only range check.
|
Warning Review limit reached
Next review available in: 10 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 (3)
Comment |
|
Status: ready for review. Reproduced on the released binary (1.4.0) with |
There was a problem hiding this comment.
LGTM — deletes the stale ABIType::MAX bound and relies on from_int's exhaustive match, so FFIType.buffer (20) and FFIType.buffer_length (21) now parse the same as their string spellings.
What was reviewed:
- Confirmed
ABIType::MAXhas no other users in the tree, andfrom_intalready returnsNonefor anything outside 0–21, so the range check is preserved. - Checked the out-of-range test values against
to_int32()'s saturating semantics —2**31and2**32 + 20saturate toi32::MAX, whichfrom_intrejects. - Tests cover every entry point (dlopen/linkSymbols/CFunction/JSCallback/cc/viewSource) in both
argsandreturnspositions, plus the downstream type-specific rejections and out-of-range numbers.
Extended reasoning...
Overview
The PR fixes numeric FFIType tags 20 (buffer) and 21 (buffer_length/buffer_bytelength) being rejected with "invalid ABI type" while the equivalent string spellings work. The two numeric branches in generate_symbol_for_function (src/runtime/ffi/ffi_body.rs) filtered with int <= ABIType::MAX, and MAX was never bumped past NapiValue (19) when Buffer and BufferLength were added. The fix deletes ABIType::MAX entirely and lets ABIType::from_int — an exhaustive match over discriminants 0–21 that returns None for anything else — be the single range check. Net native change: 2 deleted lines in abi_type.rs, ~8 lines reshaped to let-else in ffi_body.rs. The rest is ~110 lines of new tests in test/js/bun/ffi/ffi.test.js.
Security risks
None. The string path already accepted "buffer" and "buffer_length" and produced the exact same ABIType variants; this change only makes the numeric spelling reach the same enum values. All downstream validation (argument-only rejection for both types as return values, reject_cc_unsupported_types_error for buffer_length in cc/viewSource/JSCallback) is unchanged and now applies uniformly. Out-of-range numbers still hit from_int's _ => None arm; I traced 2**31 and 2**32 + FFIType.buffer through is_any_int() (JSC's int52 check, so both qualify) → to_int32() (saturates to i32::MAX per JSValue.rs:759-763) → from_int(i32::MAX) → None, so the test assertions are sound.
Level of scrutiny
Low-to-medium. The native change is a mechanical removal of a redundant guard that had gone stale twice — exactly the "one source of truth" pattern REVIEW.md calls out. from_int is visibly exhaustive over the enum in abi_type.rs, and a repo-wide grep confirms ABIType::MAX had no other consumers. The refactor to let Some(t) = ... else { return ... } is behavior-preserving and idiomatic.
Other factors
Test coverage is thorough and follows the review guidelines: every FFI entry point is exercised with the numeric spelling in both args and returns; positive assertions check actual data-pointer/byteLength values (including subarray/DataView offsets and Float64Array byte-width) rather than just "doesn't throw"; viewSource output is compared byte-for-byte against the string spelling; error-path assertions check the specific error message rather than bare toThrow(); out-of-range numbers are still asserted to fail. Resources are released via try/finally before assertions could leak them. The PR description states the new cases fail under USE_SYSTEM_BUN=1 and pass under bun bd. No outstanding reviewer comments (only a coderabbit rate-limit notice).
|
Updated 5:05 AM PT - Aug 14th, 2026
❌ @robobun, your commit 98305a1 has some failures in 🧪 To try this PR locally: bunx bun-pr 38430That installs a local version of the PR into your bun-38430 --bun |
Problem
FFIType.buffer(20) orFFIType.buffer_length/FFIType.buffer_bytelength(21) throwsinvalid ABI type; the same signature spelled"buffer"/"buffer_length"works.dlopen,linkSymbols,CFunction,JSCallback,cc,viewSource), in bothargsandreturns. The docs examplewrite_all: { args: ["i32", "buffer", "buffer_length"], returns: "u64" }throws as soon as it is written withFFITypemembers, whichbun-typesaccepts.generate_symbol_for_function(src/runtime/ffi/ffi_body.rs, theargsandreturnsnumeric branches) filtered numeric tags withint <= ABIType::MAX, andABIType::MAX(src/runtime/ffi/abi_type.rs) was stillNapiValue(19). It was not bumped whenBuffer = 20was added (Experiment: Addbuffertype and inline pointer #14036) nor whenBufferLength = 21was added (bun:ffi: use the engine-native FFI when available #35246), sofrom_int, which already maps 20 and 21, never got to return them.buffer_lengthwithout touchingMAX, so the numeric spelling of both types is still rejected on main.Fix
ABIType::MAXand the<= MAXfilter; the numeric branches now useABIType::from_intalone.from_intis an exhaustive match over the enum's discriminants and returnsNonefor anything else, so it is already the range check;MAXwas a second copy of the same fact, and the second copy is the one that went stale twice.ABIType, so the existing rejections (Cannot return a buffer ...,buffer_length is an argument-only type ..., andreject_cc_unsupported_types_errorforcc/viewSource/JSCallback) now apply to the numbers exactly as they do to the strings. Unknown numbers (22, -1, values that saturate into_int32) still throwinvalid ABI type.test/js/bun/ffi/ffi.test.js):linkSymbolsoverJSCallbacks bound with[FFIType.buffer, FFIType.buffer_length]receives the view's data pointer (including subarray / DataView offsets) and byteLength;dlopenof the C fixture with the numeric spelling returns the same values as the existing string test;viewSourceemits identical source for both spellings; numericbuffer/buffer_lengthreturn types and numericbuffer_lengtharguments incc/viewSource/JSCallbackget the type-specific errors rather thaninvalid ABI type; out-of-range numbers still getinvalid ABI type.USE_SYSTEM_BUN=1, all withinvalid ABI type) and pass withbun bd test;bun bd test test/js/bun/ffi/is otherwise unchanged (theinteger identitiescases time out locally under ASAN on a loaded machine, with and without this change; they are green on main CI).Background
ABITypeis Bun's tag enum for FFI parameter and return types. A signature'sargs/returnsentries may be strings (looked up inABI_TYPE_LABEL) or numbers (the values exported asFFITypefrombun:ffi, which are the enum discriminants); both paths produce anABIType, and everything after parsing works on the enum.bufferpasses a TypedArray / DataView's data pointer;buffer_lengthpasses that same view's byteLength, read at call time. Both are argument-only, which is why the return-type rejections exist, andbuffer_lengthis only implemented by the engine-native binding, which is whycc/viewSource/JSCallbackreject it.