Skip to content

ffi: reject non-void return types for threadsafe JSCallback - #31779

Closed
EffortlessSteven wants to merge 1 commit into
oven-sh:mainfrom
EffortlessSteven:claude/ffi-threadsafe-callback-void
Closed

ffi: reject non-void return types for threadsafe JSCallback#31779
EffortlessSteven wants to merge 1 commit into
oven-sh:mainfrom
EffortlessSteven:claude/ffi-threadsafe-callback-void

Conversation

@EffortlessSteven

Copy link
Copy Markdown
Contributor

What this does

Safe JS can construct a thread-safe JSCallback with a non-void return type. A thread-safe callback is delivered asynchronously and cannot return a value to native code, so its caller reads garbage. Bun accepts it instead of rejecting it.

Fix: reject the non-void case (check the parsed threadsafe flag), and throw the error like dlopen() instead of returning a value the constructor silently dropped.

Verification

Red on main, green after.

Check Result
non-void thread-safe JSCallback throws Threadsafe functions must return void
invalid JSCallback options throw, not silent ptr === undefined
void thread-safe callbacks unchanged
fmt, clippy clean

Review map

  • ffi_body.rs (guard): check the parsed local threadsafe, not the stale function.threadsafe
  • ffi_body.rs (callback): throw the construction error, not return it
  • ffi.test.js: non-void threadsafe rejected; invalid options throw

A threadsafe JSCallback posts work to the JS event loop and cannot
synchronously return a value to native code, so its return type must be
void. The validation guard checked `function.threadsafe` before the
parsed `threadsafe` flag had been assigned to it, so it never fired: a
non-void threadsafe callback was accepted and wired to the void
trampoline, returning garbage to the native caller.

Check the parsed local flag so the existing "Threadsafe functions must
return void" validation runs. Also surface the error the way dlopen()
does: the JSCallback constructor destructures `{ ctx, ptr }` from the
native return, so an error returned as a value was silently dropped and
left a callback with `ptr === undefined`; throw it instead.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bb2c0488-6256-417f-ba86-802a45b32fd8

📥 Commits

Reviewing files that changed from the base of the PR and between d2a6506 and 29b4523.

📒 Files selected for processing (2)
  • src/runtime/ffi/ffi_body.rs
  • test/js/bun/ffi/ffi.test.js

Walkthrough

This PR improves FFI callback construction error handling. The implementation now throws validation errors as JavaScript exceptions rather than returning them, and fixes the threadsafe callback validation to check the correct flag state. Tests verify both the threadsafe return-type constraint and unknown return-type error handling are properly enforced at construction time.

Changes

FFI Callback Error Handling

Layer / File(s) Summary
Callback validation and error handling fixes
src/runtime/ffi/ffi_body.rs
FFI::callback throws validation errors into JS via throw_value() instead of returning them as normal results. generate_symbol_for_function validates the threadsafe/void-return constraint using the computed local threadsafe flag rather than accessing the function attribute directly.
Callback construction error tests
test/js/bun/ffi/ffi.test.js
Tests verify JSCallback construction throws when threadsafe callbacks have non-void return types, and when unknown return types are provided, rather than silently producing undefined pointers.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ffi: reject non-void return types for threadsafe JSCallback' is clear, specific, and directly describes the main change in the changeset.
Description check ✅ Passed The description includes both required sections (What this does/What does this PR do and Verification/How did you verify), with detailed explanations, verification results, and a review map.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@robobun

robobun commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

I opened #32782 without having seen this PR; my duplicate search was scoped too narrowly and missed it. You found and fixed the dead function.threadsafe read first, so credit for the find belongs here, and I have noted that in #32782's description.

The two are not identical: #32782 puts the guard in compile_callback instead of generate_symbol_for_function, so dlopen / cc / CFunction specs that carry the (JSCallback-only, no-op) threadsafe flag keep working; it surfaces the Step::Failed error path as well as the validation one; and it also fixes a Zig::getErrorInstance use-after-free in helpers.h that the new guard exposed under ASAN. The full comparison is at #32782 (comment).

I am not closing either PR; that is a maintainer's call. If they prefer this one, I will close #32782 and send a separate PR for the getErrorInstance UAF and the docs. If they prefer #32782, I have asked that the guard fix carry Co-authored-by credit for you. Sorry for the duplicated work.

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator

Closing this since #35246 (bun:ffi: use the engine-native FFI when available) merged and covers the same ground. Thank you @EffortlessSteven for the PR — if there's a piece of this that #35246 didn't pick up, please say so and we'll take another look.

(This comment was written by Claude, on behalf of the Bun team.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants