Skip to content

bun:ffi: materialize a view's ArrayBuffer before ptr() returns its address - #32055

Open
robobun wants to merge 3 commits into
mainfrom
farm/cdacbf14/ffi-ptr-stable-vector
Open

robobun wants to merge 3 commits into
mainfrom
farm/cdacbf14/ffi-ptr-stable-vector

Conversation

@robobun

@robobun robobun commented Jun 10, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #32054

Problem

  • ptr(typedArray) on a small view returns an address that goes stale once the caller is DFG-compiled. Native writes land in an abandoned block and JS reads return frozen values: FAIL: 82347/100000 stale typed-array reads.
  • A view of at most 1000 elements is a JSC FastTypedArray whose vector lives in the GC heap. DFG tier-up registers a watchpoint on the view, which calls possiblySharedBuffer(). That copies the vector into a fresh ArrayBuffer and repoints the view. ptr_ (src/runtime/ffi/FFIObject.rs) handed out the old address.

Fix

  • After its input checks, ptr_ calls the existing JSValue::materialize_array_buffer_view_buffer (napi uses it for the same reason) and reads the address again. The view now owns an ArrayBuffer that JSC never moves.
  • Verified: test/js/bun/ffi/ffi.test.js, test ptr(typedArray) stays valid after DFG tier-up. Main fails it with MOVED: the view's storage was relocated after ptr() was taken. The issue's C repro passes 100000/100000.

Background

  • JSC keeps a typed array's bytes in the GC heap (fast), in a malloc'd vector (oversize) or in an ArrayBuffer object (wasteful). Only fast storage moves, once, when JSC materializes the ArrayBuffer.
  • bun:ffi: use the engine-native FFI when available #35246 makes the DFG treat an FFI call as a memory clobber. It does not stop the relocation: the repro still fails on 1.4.0-canary (see the issue thread).
  • Considered the per-call marshaling in FFI.h. It reads the vector at call time, so only a ptr() address outlives the call.

Downsides

  • A view passed to ptr() now owns an ArrayBuffer, once per view. For a fast view that is one malloc of up to 1000 elements plus one copy. For an oversize view JSC adopts the vector in place: one ArrayBuffer object, no copy, no double GC accounting. JSC does the same on .buffer access.
  • Each ptr() call pays one extra as_array_buffer read (a type switch, no allocation).
  • A read through a ptr() address after the view is collected sees freed memory sooner. That is a use-after-free on main too (a churn probe fails 4 of 50 runs there). The primitives test did this and is fixed here.
Notes
  • First shape of this PR: a new JSC__JSValue__ensureStableTypedArrayVector binding that checked mode() == FastTypedArray before possiblySharedBuffer(). Review pointed at the existing Bun__JSValue__materializeArrayBufferViewBuffer (napi, src/jsc/bindings/bindings.cpp). The only difference is that the existing helper also materializes an oversize view, which adopts the vector in place with no copy.
  • CI on the first shape failed run ffi > primitives on aarch64: new CString(ptr(Buffer.from([...])), 4, 2) read a freed buffer. The Buffer was a temporary that nothing held. On main its bytes stay in the GC heap after collection, so the read happened to work. With the fix the bytes live in a malloc'd ArrayBuffer that frees at collection. Probe on main (Linux x64, release): ptr(Buffer.from(...)), Bun.gc(true), allocate 2000 small arrays, then read: wrong 4 of 50 runs. The test now keeps the buffer referenced.
  • Validation order: type, length and byteOffset checks run before materialization, so a rejected call has no side effect on the view.
  • ptr(view, 9) on an 8 byte view returns NaN instead of throwing, on main and here. Not touched.
  • Suites run with the debug ASAN build: test/js/bun/ffi/ffi.test.js (152 pass, 2 timeouts at 5 s: ptr argument: ArrayBuffer cells through an FTL-compiled call site and JSCallback tolerates worker.terminate() arriving inside the callback, both also slow on main on this machine and green in CI).
  • Self-reviewed: 7 review concerns raised, 5 addressed (existing helper, validate first, comment length at three sites, stderr assertion). Rejected: pinning in the per-call marshaling path (see Background), and a mode check that skips oversize views (the existing napi helper is the one shared invariant, and the oversize transition copies nothing).
Rebase notes

Rebased onto main (faac63e). The change is the same.

  • src/jsc/JSValue.rs: JSC__JSValue__pinArrayBuffer returns u8 on main. The new extern line sits after it.
  • test/js/bun/ffi/ffi.test.js: the new test sits before the describe.skipIf(!FFI_FIXTURE_PATH)("run ffi") block of main.
  • Without the fix, on a debug build of main 4b02e10, the new test fails with MOVED: the view's storage was relocated after ptr() was taken.
  • With this head (debug build, ASAN): the new test passes. ffi.test.js has 146 pass and 3 timeouts at the 5 s limit on this machine. One of them (ptr argument: ArrayBuffer cells through an FTL-compiled call site) also times out on main. The other two (the JSCallback worker teardown tests) take 4.2 s and 4.5 s on main.

no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/ffi/ffi.test.js

@robobun

robobun commented Jun 10, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 10:32 AM PT - Oct 2nd, 2026

✅ @robobun, your commit b0569563b0ef8f2b0802fce1acf39ad7bae09794 passed in Build #122923! 🎉


🧪   To try this PR locally:

bunx bun-pr 32055

That installs a local version of the PR into your bun-32055 executable, so you can run:

bun-32055 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Segfault in JSFFIFunction::trampoline on Windows standalone executable after sustained FFI polling #31941 - Segfault after sustained FFI polling with ptr(Uint32Array(1)) in a setInterval — the small typed array's backing store gets relocated by DFG tier-up, causing the pointer from ptr() to dangle, which is exactly the bug this PR fixes by forcing possiblySharedBuffer() before reading the address.

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #31941

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: oven-sh/bun/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 38905cd2-6743-4179-a588-76fd0cdd07ac

📥 Commits

Reviewing files that changed from the base of the PR and between b08cb51 and b056956.

📒 Files selected for processing (1)
  • src/runtime/ffi/FFIObject.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.


Walkthrough

ptr_ now materializes an ArrayBufferView’s backing buffer before returning its address. The FFI tests retain backing buffers in CString cases and check pointer stability across JIT tier-up.

Changes

FFI typed-array pointer stability

Layer / File(s) Summary
Stabilize backing storage during pointer capture
src/runtime/ffi/FFIObject.rs
ptr_ materializes the view’s backing buffer, returns an out-of-memory JS value if materialization fails, and applies the original offset to the materialized buffer’s pointer.
Test pointer stability and backing-buffer retention
test/js/bun/ffi/ffi.test.js
CString tests retain the backing Buffer. A subprocess regression test checks pointer stability after a hot loop and verifies that a later write is visible through the captured address.

Suggested reviewers: jarred-sumner

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to b0569

The change stabilizes typed-array pointers for the view’s lifetime, and the regression test checks pointer stability after a hot loop. No concrete merge-blocking risk is supported by the reviewed evidence; the change is ready for normal checks.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #32054 is closed and supplies historical context only. No active directly linked issue defines coding requirements. The PR still implements the reported pointer-stability fix by materializing th…
Out of Scope Changes check ✅ Passed The changes stay within the pointer-stability scope. FFIObject.rs materializes the backing buffer and recalculates the address. The test changes retain required Buffer references and verify pointe…
Title check ✅ Passed The title clearly identifies the main change: materializing a view’s ArrayBuffer before ptr() returns its address. It is specific and concise.
Description check ✅ Passed The description explains the problem, implementation, trade-offs, and verification results. It does not use the template headings exactly, but it provides the required information in equivalent sectio…

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

@robobun

robobun commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Checked whether #31941 is the same bug before linking it: it isn't provably so, and the evidence leans against.

The actual OpenCode guard (packages/tui/src/terminal-win32.ts, win32InstallCtrlCGuard) calls ptr(buf) fresh inside every poll tick and the address is consumed synchronously by the same GetConsoleMode call: there is no JS execution point between reading the vector address and the native write where a tier-up-driven relocation can run (plan finalization happens at JS function prologues and loop back-edges, and both callees are host functions). The bug this PR fixes requires a pointer captured once that outlives a later relocation; per-call ptr() re-reads the current vector and is unaffected either way.

The crash signature in #31941 (frame inside an unsymbolized module above JSFFIFunction::trampoline, Windows standalone build) looks like the #28135 / #28113 trampoline family instead, which the issue itself notes.

One honest caveat: with this PR, the first ptr(buf) call pins the storage permanently, so if #31941 were caused by any vector movement at all, it would be incidentally fixed. But that is not established, so I am not adding an auto-close line for a crash report that may have a different root cause. Worth retesting that workload on a build with this fix once it lands.

@robobun
robobun force-pushed the farm/cdacbf14/ffi-ptr-stable-vector branch from 6bbdade to 1571a72 Compare June 28, 2026 10:58

@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.

I didn't find any issues — the fix is small and mirrors the existing borrowBytesForOffThread/pinArrayBuffer pattern — but since this is a memory-safety change at the FFI/JSC-JIT boundary, it's worth a human sanity check on the typed-array storage-mode assumptions.

Extended reasoning...

Overview

This PR fixes #32054: bun:ffi's ptr(typedArray) could return an address that goes stale once DFG tier-up relocates a FastTypedArray's backing vector. The fix adds a new C++ binding JSC__JSValue__ensureStableTypedArrayVector (bindings.cpp) plus a Rust wrapper (JSValue.rs), and calls it from ptr_() in FFIObject.rs before reading the vector address. A subprocess regression test in ffi.test.js forces early tier-up and asserts the captured address remains valid. Net new logic is ~9 lines; the rest is comments and the test.

Security risks

This is a memory-safety fix (eliminates a dangling pointer / write-to-abandoned-GC-allocation hazard). It does not introduce new attack surface — bun:ffi is already an inherently unsafe, trusted API. No injection/auth/data-exposure concerns. The only new failure mode is an OOM throw if possiblySharedBuffer() fails to allocate, which is the correct behavior.

Level of scrutiny

Medium-high. The diff is tiny and follows an established in-tree pattern (the adjacent pinArrayBuffer / borrowBytesForOffThread helpers in bindings.cpp do the same possiblySharedBuffer() materialization for the same reason, and the PR description explicitly cross-references them). I verified that the only live code path for ptr() is FFI__ptr__put → FFI__ptr__slowpathWrapper → ptr_() (the DOMJIT fastpath in ZigGeneratedCode.cpp is commented out), so the fix covers all current callers. That said, correctness here hinges on JSC-internals assumptions about typed-array storage-mode transitions and JIT watchpoint installation — knowledge I can verify against the comments but not against JSC source itself.

Other factors

No bugs were flagged by the bug-hunting system, no outstanding reviewer comments, no relevant CODEOWNERS entries. The root-cause analysis in the description is unusually thorough and the regression test is deterministic. I'm deferring rather than approving solely because FFI + JIT-internals + raw-pointer lifetime is exactly the kind of change where a human with JSC expertise should confirm the storage-mode reasoning (and note whether the dormant DOMJIT fastpath would need the same treatment if re-enabled).

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the careful read. Deferring to a human on the JSC storage-mode reasoning is fair, so here are the specifics a reviewer would want to confirm, plus a direct answer on the dormant DOMJIT fastpath.

Dormant DOMJIT fastpath: good catch, and the answer is yes. ptr is currently registered with NoIntrinsic and no DOMJIT signature (the fastpath JSFunction::create with DOMJIT_ptr_signature is commented out in ZigGeneratedCode.cpp), so the only live path is FFI__ptr__put -> FFI__ptr__slowpathWrapper -> FFI__ptr__slowpath -> ptr_(), which is exactly where the guard sits. All current callers are covered. If that fastpath is ever revived, it would reintroduce this bug unless it forces the same fast-to-wasteful transition before reading view->vector(): a JIT-inlined read of the vector is precisely the "capture the address, then the engine relocates it" shape. Note the commented-out signature already used Effect::forReadWrite(top, top), so it was not claiming purity; the hazard is the raw vector read, not the effect annotation. I did not add a warning at the commented-out block because it is generated, dead code, but flagging it here for whoever reenables it.

Storage-mode assumptions to confirm:

  • Only FastTypedArray has GC-movable storage (vector in the GC heap). Every other mode has a stable data pointer: OversizeTypedArray is fastMalloc'd and adopted in place by slowDownAndWasteMemory() (no copy, no move), WastefulTypedArray/DataView already own an ArrayBuffer, and a plain JSArrayBuffer's data never moves. The guard early-returns for all of these, so it is a no-op except for the one mode that needs it.
  • The transition is one-time and monotonic: after the first ptr() the view is permanently wasteful, so later ptr() calls and indexed JS reads all see the same stable vector. This matches the repro where re-reading ptr(out) after tier-up returned a different address on the unfixed build and a stable one with the fix.
  • This is the same FastTypedArray classification and possiblySharedBuffer() materialization the adjacent borrowBytesForOffThread/pinArrayBuffer helpers already rely on in bindings.cpp, so the reasoning is not new to this PR.

@robobun

robobun commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator Author

Status: the diff is green and ready for review/merge. The red CI is entirely unrelated flakes and infra on lanes this change does not touch.

This PR only adds a guard inside FFI.ptr (reachable solely via bun:ffi), a Rust wrapper, and a C++ helper that is called from nowhere else. The new regression test (test/js/bun/ffi/ffi.test.js) passes on every shard it ran, and no FFI test appears in any failure annotation across the recent builds. Verified locally: the test fails on the unfixed build and passes with the fix, and the full ffi.test.js suite is green.

Failures seen on the CI runs for this branch, none of which execute the changed code path:

  • Build 66405: test/js/bun/util/v8-heap-snapshot.test.ts killed by SIGKILL (OOM on a memory-heavy heap-snapshot test; no core file, no bun:ffi usage in the test). The rest were retried-to-green flakes, tagged context: flaky by the runner: bun-install, transpiler-cache, spawn (timeout), napi, s3, hot, watch-many-dirs.
  • Build 66369: two darwin aarch64 jobs failed with buildkite-agent artifact download timed out after 120s (artifact-store infra, no tests ran); the rest were context: flaky install/napi tests that passed on retry.

I have used my one CI re-roll on this PR. A maintainer re-run should clear the flaky/infra lanes. Happy to rebase again if needed.

@robobun

robobun commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Leaving this open while closing the other pre-#35246 bun:ffi PRs: #32054 still reproduces after #35246.

On 1.4.0-canary.1+da3851e57 (Linux x64, engine-native FFI), the issue's original repro (write_and_check through a captured ptr(out) in a hot loop) fails at default JIT thresholds in 3 of 3 runs, for example:

iter=47572: C wrote 572.5; memory actually contains 572.5; JS reads out[0] = 571.5; ptr(out) now = 5333429653840 (captured 5333451374896)
FAIL: 52376/100000 stale typed-array reads (first at iter 47572)

The ptr(out) address changing mid-run is the relocation this PR describes: ptr() on main still returns view->vector() of a FastTypedArray without forcing it out of fast mode (ptr_ in src/runtime/ffi/FFIObject.rs reading as_array_buffer().ptr), and DFG tier-up still relocates that storage. #35246 changed how the call itself is compiled (CallFFI clobbers the heap, so this is not load elimination), which does not affect a pointer captured earlier. Passing the typed array itself as the argument is unaffected because the engine reads the vector fresh on every call; a pointer captured once via ptr() is the case that breaks.

The test in this PR reproduces it without a native library (ptr(out) differs before and after the loop) on the same build. The branch needs a rebase onto the post-#35246 tree before it can go in.

simonklee pushed a commit to anomalyco/opentui that referenced this pull request Aug 20, 2026
## Summary

- use `buffer` ABI parameters and pass typed-array owners directly for
transient synchronous Bun FFI calls
- lower portable `buffer` parameters to Node's stable `pointer` path
while still passing owner objects directly
- keep `ptr` only for nullable, mixed native-pointer, raw `ArrayBuffer`,
callback, and retained-memory cases
- stabilize retained text-memory views through `.buffer` before
resolving their native address
- preserve raw-pointer compatibility while allowing direct buffers in
supersample, packed-buffer, matrix, and grayscale APIs
- document the Bun 1.3.14 and Bun 1.4+ ownership rules in `AGENTS.md`

## Why

A Bun `FastTypedArray` may store its data inline. Calling `ptr(view)`
captures that address, but a later first access to `view.buffer` can
move the data into separate `ArrayBuffer` storage and leave the native
address stale. Passing the owner directly lets the FFI backend borrow
the current storage for synchronous calls and allows Bun's `buffer` fast
path to keep the owner visible to the JIT.

This keeps nullable and true native-pointer parameters on `ptr`, where
`buffer` cannot represent the ABI, while avoiding pre-resolved addresses
for transient memory. Retained pointers explicitly materialize stable
backing storage before `ptr(view)` and keep the view alive for the
native lifetime.

Node 26.4's Linux optimized `buffer` trampoline delivered a null pointer
to a multi-argument audio call in CI. OpenTUI therefore maps its
portable `buffer` descriptor to Node `pointer`, whose documented
owner-borrowing path passes the same views correctly. Bun continues to
receive the `buffer` descriptor.

Related Bun investigation: oven-sh/bun#32054 and oven-sh/bun#32055.

## Testing

- `bunx bun@1.3.14 test
src/tests/ffi-borrowed-pointer-callsites.test.ts` (23 passed)
- `bun test src/tests/ffi-borrowed-pointer-callsites.test.ts` on Bun 1.4
canary (23 passed)
- `bun run test:js` (5,397 passed, 23 skipped)
- `bun run test:js:node` with Node 26.4.0 (4,665 passed, 6 skipped)
- `bun run test:dist`
- `bun run build:lib`
- `bun run fmt:check`
- `bun run lint`
carsteneu pushed a commit to carsteneu/opentui that referenced this pull request Aug 20, 2026
## Summary

- use `buffer` ABI parameters and pass typed-array owners directly for
transient synchronous Bun FFI calls
- lower portable `buffer` parameters to Node's stable `pointer` path
while still passing owner objects directly
- keep `ptr` only for nullable, mixed native-pointer, raw `ArrayBuffer`,
callback, and retained-memory cases
- stabilize retained text-memory views through `.buffer` before
resolving their native address
- preserve raw-pointer compatibility while allowing direct buffers in
supersample, packed-buffer, matrix, and grayscale APIs
- document the Bun 1.3.14 and Bun 1.4+ ownership rules in `AGENTS.md`

## Why

A Bun `FastTypedArray` may store its data inline. Calling `ptr(view)`
captures that address, but a later first access to `view.buffer` can
move the data into separate `ArrayBuffer` storage and leave the native
address stale. Passing the owner directly lets the FFI backend borrow
the current storage for synchronous calls and allows Bun's `buffer` fast
path to keep the owner visible to the JIT.

This keeps nullable and true native-pointer parameters on `ptr`, where
`buffer` cannot represent the ABI, while avoiding pre-resolved addresses
for transient memory. Retained pointers explicitly materialize stable
backing storage before `ptr(view)` and keep the view alive for the
native lifetime.

Node 26.4's Linux optimized `buffer` trampoline delivered a null pointer
to a multi-argument audio call in CI. OpenTUI therefore maps its
portable `buffer` descriptor to Node `pointer`, whose documented
owner-borrowing path passes the same views correctly. Bun continues to
receive the `buffer` descriptor.

Related Bun investigation: oven-sh/bun#32054 and oven-sh/bun#32055.

## Testing

- `bunx bun@1.3.14 test
src/tests/ffi-borrowed-pointer-callsites.test.ts` (23 passed)
- `bun test src/tests/ffi-borrowed-pointer-callsites.test.ts` on Bun 1.4
canary (23 passed)
- `bun run test:js` (5,397 passed, 23 skipped)
- `bun run test:js:node` with Node 26.4.0 (4,665 passed, 6 skipped)
- `bun run test:dist`
- `bun run build:lib`
- `bun run fmt:check`
- `bun run lint`
@robobun

robobun commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

#44404 carries this guard. It is the same change on top of the helper that #37154 added later (JSValue::materialize_array_buffer_view_buffer), with the tier-up test of this PR, because #44404 makes Buffer.from(string) return a Buffer with its bytes in GC storage.

Branch: https://github.com/oven-sh/bun/tree/robobun/79736f0f/buffer-from-string-in-place (commit 6add1a1 has the guard alone).

One thing #44404 found in CI that applies here too: test/js/bun/ffi/ffi.test.js:504 and :505 read through ptr(Buffer.from([...])) of a Buffer that nothing references. With the guard that address is in the Buffer's ArrayBuffer, which the GC frees with the Buffer, and line 505 fails under BUN_GARBAGE_COLLECTOR_LEVEL=1. #44404 keeps the Buffer in a constant.

If #44404 merges, this PR is covered.

…ress

ptr(typedArray) returned the raw vector address of the view without
forcing the view out of FastTypedArray mode. JSC relocates that vector
when the view transitions to wasteful mode, and the engine triggers the
transition on its own: DFG tier-up folds the view into compiled code and
registers an ArrayBufferView watchpoint, whose installation calls
possiblySharedBuffer() (slowDownAndWasteMemory copies the storage into a
fresh ArrayBuffer and repoints m_vector). From that moment the captured
pointer dangles: native writes land in the abandoned allocation while JS
reads the new one.

ptr() now forces the one-time fast-to-wasteful transition before reading
the address, so the pointer it hands out can never be invalidated by the
engine. Other storage modes already have immovable data and are left
untouched.

Fixes #32054
@robobun
robobun force-pushed the farm/cdacbf14/ffi-ptr-stable-vector branch from 87174b5 to 51098c6 Compare October 2, 2026 09:47
Comment thread src/jsc/JSValue.rs Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/runtime/ffi/FFIObject.rs Outdated

@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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline findings, I checked whether the new JSC__JSValue__ensureStableTypedArrayVector binding is needed at all: JSValue::materialize_array_buffer_view_buffer (src/jsc/JSValue.rs:2658, backed by Bun__JSValue__materializeArrayBufferViewBuffer at src/jsc/bindings/bindings.cpp:6567, already used by napi for the same pointer-stability purpose) calls possiblySharedBuffer() on any view and returns false on allocation failure, so ptr_ could call that existing helper and the new C++ function, extern decl, and Rust wrapper could be dropped. The only difference is the new helper's FastTypedArray mode check, which skips a no-copy in-place adoption for Oversize views and does not affect pointer stability.

Extended reasoning...

The change adds a one-time fast-to-wasteful transition in bun:ffi's ptr() before the data pointer is captured, plus a new C++ binding, Rust wrapper, and a spawned regression test; it touches no auth, crypto, or input-parsing surface. Inline findings are already posted, and the additional fact that an equivalent helper already exists in-tree is what this body adds.

Comment thread src/runtime/ffi/FFIObject.rs Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread test/js/bun/ffi/ffi.test.js Outdated
Comment thread src/runtime/ffi/FFIObject.rs Outdated
…tr() validates its input

Use JSValue::materialize_array_buffer_view_buffer, which napi already uses
for the same pointer-stability reason, instead of a new binding. Run it
after the type, length and byteOffset checks, then read the address again.

The primitives test held no reference to the Buffer it took a pointer to,
so the pointer dangled once the GC collected it. Keep the buffer alive.
@robobun robobun changed the title bun:ffi: keep ptr(typedArray) valid across DFG tier-up bun:ffi: materialize a view's ArrayBuffer before ptr() returns its address Oct 2, 2026
Comment thread src/runtime/ffi/FFIObject.rs Outdated

@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.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Still open from earlier reviews (1):

  • Unresolved: 1 minor or pre-existing.

Comment thread src/runtime/ffi/FFIObject.rs

@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.

Code review found no issues

No high-confidence issues detected in this change.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typed-array reads after a bun:ffi call return stale values once the calling function is JIT-compiled (DFG load elimination across native calls)

1 participant