mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 12:51:54 +00:00
Fix FFI segfault when passing ArrayBuffer as pointer argument (#22225)
The FFI `JSVALUE_TO_PTR` function was missing support for ArrayBuffer objects, causing segfaults when ArrayBuffers were passed as pointer arguments to FFI functions. TypedArrays and DataViews worked correctly, but ArrayBuffers fell through to number-as-pointer conversion logic, resulting in invalid pointers. Changes: - Add `JSC__JSValue__toArrayBufferPtr` C++ helper function to extract ArrayBuffer data pointer - Add `JSCELL_IS_ARRAY_BUFFER` helper function for type detection - Update `JSVALUE_TO_PTR` in FFI.h to handle ArrayBuffer case - Add JSTypeArrayBuffer constant (value 38) for type detection - Integrate ArrayBuffer support into FFI symbol table - Add regression test to prevent future issues - Add test/js/bun/ffi/*.so to .gitignore to prevent committing compiled FFI test binaries Fixes: ArrayBuffer, TypedArray, and DataView now all work identically when passed as pointer arguments to FFI functions. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -186,4 +186,4 @@ scratch*.{js,ts,tsx,cjs,mjs}
|
||||
|
||||
*.bun-build
|
||||
|
||||
scripts/lldb-inline
|
||||
scripts/lldb-inline*.so
|
||||
|
||||
@@ -180,8 +180,10 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__
|
||||
static uint8_t GET_JSTYPE(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) __attribute__((__always_inline__));
|
||||
static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static bool JSCELL_IS_ARRAY_BUFFER(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
void* JSVALUE_TO_ARRAYBUFFER_PTR(EncodedJSValue val);
|
||||
|
||||
static bool JSVALUE_IS_CELL(EncodedJSValue val) {
|
||||
return !(val.asInt64 & NotCellMask);
|
||||
@@ -207,6 +209,10 @@ static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) {
|
||||
return JSVALUE_IS_CELL(val) && JSTYPE_IS_TYPED_ARRAY(GET_JSTYPE(val));
|
||||
}
|
||||
|
||||
static bool JSCELL_IS_ARRAY_BUFFER(EncodedJSValue val) {
|
||||
return JSVALUE_IS_CELL(val) && GET_JSTYPE(val) == JSTypeArrayBuffer;
|
||||
}
|
||||
|
||||
static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) {
|
||||
return *(void**)((char*)val.asPtr + JSArrayBufferView__offsetOfVector);
|
||||
}
|
||||
@@ -228,6 +234,10 @@ static void* JSVALUE_TO_PTR(EncodedJSValue val) {
|
||||
return JSVALUE_TO_TYPED_ARRAY_VECTOR(val);
|
||||
}
|
||||
|
||||
if (JSCELL_IS_ARRAY_BUFFER(val)) {
|
||||
return JSVALUE_TO_ARRAYBUFFER_PTR(val);
|
||||
}
|
||||
|
||||
val.asInt64 -= DoubleEncodeOffset;
|
||||
size_t ptr = (size_t)val.asDouble;
|
||||
return (void*)ptr;
|
||||
|
||||
@@ -2323,6 +2323,7 @@ const CompilerRT = struct {
|
||||
JSVALUE_TO_UINT64: *const fn (JSValue0: jsc.JSValue) callconv(.C) u64,
|
||||
INT64_TO_JSVALUE: *const fn (arg0: *jsc.JSGlobalObject, arg1: i64) callconv(.C) jsc.JSValue,
|
||||
UINT64_TO_JSVALUE: *const fn (arg0: *jsc.JSGlobalObject, arg1: u64) callconv(.C) jsc.JSValue,
|
||||
JSVALUE_TO_ARRAYBUFFER_PTR: *const fn (JSValue0: jsc.JSValue) callconv(.C) ?*anyopaque,
|
||||
bun_call: *const @TypeOf(jsc.C.JSObjectCallAsFunction),
|
||||
};
|
||||
const headers = JSValue.exposed_to_ffi;
|
||||
@@ -2331,6 +2332,7 @@ const CompilerRT = struct {
|
||||
.JSVALUE_TO_UINT64 = headers.JSVALUE_TO_UINT64,
|
||||
.INT64_TO_JSVALUE = headers.INT64_TO_JSVALUE,
|
||||
.UINT64_TO_JSVALUE = headers.UINT64_TO_JSVALUE,
|
||||
.JSVALUE_TO_ARRAYBUFFER_PTR = headers.JSVALUE_TO_ARRAYBUFFER_PTR,
|
||||
.bun_call = &jsc.C.JSObjectCallAsFunction,
|
||||
};
|
||||
|
||||
@@ -2369,6 +2371,7 @@ const CompilerRT = struct {
|
||||
.JSCell__offsetOfType = offsets.JSCell__offsetOfType,
|
||||
.JSTypeArrayBufferViewMin = @intFromEnum(jsc.JSValue.JSType.min_typed_array),
|
||||
.JSTypeArrayBufferViewMax = @intFromEnum(jsc.JSValue.JSType.max_typed_array),
|
||||
.JSTypeArrayBuffer = @intFromEnum(jsc.JSValue.JSType.ArrayBuffer),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2382,6 +2385,7 @@ const CompilerRT = struct {
|
||||
state.addSymbol("JSVALUE_TO_UINT64_SLOW", workaround.JSVALUE_TO_UINT64) catch unreachable;
|
||||
state.addSymbol("INT64_TO_JSVALUE_SLOW", workaround.INT64_TO_JSVALUE) catch unreachable;
|
||||
state.addSymbol("UINT64_TO_JSVALUE_SLOW", workaround.UINT64_TO_JSVALUE) catch unreachable;
|
||||
state.addSymbol("JSVALUE_TO_ARRAYBUFFER_PTR", workaround.JSVALUE_TO_ARRAYBUFFER_PTR) catch unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1124,6 +1124,8 @@ pub const JSValue = enum(i64) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
extern fn JSC__JSValue__toArrayBufferPtr(value: JSValue) callconv(jsc.conv) ?*anyopaque;
|
||||
extern fn JSC__JSValue__fromInt64NoTruncate(globalObject: *JSGlobalObject, i: i64) JSValue;
|
||||
/// This always returns a JS BigInt
|
||||
pub fn fromInt64NoTruncate(globalObject: *JSGlobalObject, i: i64) JSValue {
|
||||
@@ -2359,6 +2361,7 @@ pub const JSValue = enum(i64) {
|
||||
pub const JSVALUE_TO_UINT64 = JSValue.JSC__JSValue__toUInt64NoTruncate;
|
||||
pub const INT64_TO_JSVALUE = JSValue.JSC__JSValue__fromInt64NoTruncate;
|
||||
pub const UINT64_TO_JSVALUE = JSValue.JSC__JSValue__fromUInt64NoTruncate;
|
||||
pub const JSVALUE_TO_ARRAYBUFFER_PTR = JSC__JSValue__toArrayBufferPtr;
|
||||
};
|
||||
|
||||
pub const backing_int = @typeInfo(JSValue).@"enum".tag_type;
|
||||
|
||||
@@ -4297,6 +4297,15 @@ JSC::JSString* JSC__JSValue__toStringOrNull(JSC::EncodedJSValue JSValue0, JSC::J
|
||||
return value.toStringOrNull(arg1);
|
||||
}
|
||||
|
||||
void* JSC__JSValue__toArrayBufferPtr(JSC::EncodedJSValue JSValue0)
|
||||
{
|
||||
JSC::JSValue value = JSC::JSValue::decode(JSValue0);
|
||||
if (auto* arrayBuffer = JSC::jsDynamicCast<JSC::JSArrayBuffer*>(value)) {
|
||||
return arrayBuffer->impl()->data();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool JSC__JSValue__toMatch(JSC::EncodedJSValue regexValue, JSC::JSGlobalObject* global, JSC::EncodedJSValue value)
|
||||
{
|
||||
ASSERT_NO_PENDING_EXCEPTION(global);
|
||||
|
||||
1
src/bun.js/bindings/headers.h
generated
1
src/bun.js/bindings/headers.h
generated
@@ -289,6 +289,7 @@ CPP_DECL bool JSC__JSValue__toMatch(JSC::EncodedJSValue JSValue0, JSC::JSGlobalO
|
||||
CPP_DECL JSC::JSObject* JSC__JSValue__toObject(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1);
|
||||
CPP_DECL JSC::JSString* JSC__JSValue__toString(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1);
|
||||
CPP_DECL JSC::JSString* JSC__JSValue__toStringOrNull(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1);
|
||||
CPP_DECL void* JSC__JSValue__toArrayBufferPtr(JSC::EncodedJSValue JSValue0);
|
||||
CPP_DECL uint64_t JSC__JSValue__toUInt64NoTruncate(JSC::EncodedJSValue JSValue0);
|
||||
CPP_DECL void JSC__JSValue__toZigException(JSC::EncodedJSValue JSValue0, JSC::JSGlobalObject* arg1, ZigException* arg2);
|
||||
CPP_DECL void JSC__JSValue__toZigString(JSC::EncodedJSValue JSValue0, ZigString* arg1, JSC::JSGlobalObject* arg2);
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
|
||||
#define ZIG_REPR_TYPE int64_t
|
||||
|
||||
#ifdef _WIN32
|
||||
#define BUN_FFI_IMPORT __declspec(dllimport)
|
||||
#else
|
||||
#define BUN_FFI_IMPORT
|
||||
#endif
|
||||
|
||||
// /* 7.18.1.1 Exact-width integer types */
|
||||
typedef unsigned char uint8_t;
|
||||
@@ -62,9 +67,9 @@ typedef enum {
|
||||
napi_detachable_arraybuffer_expected,
|
||||
napi_would_deadlock // unused
|
||||
} napi_status;
|
||||
void* NapiHandleScope__open(void* napi_env, bool detached);
|
||||
void NapiHandleScope__close(void* napi_env, void* handleScope);
|
||||
extern struct napi_env__ Bun__thisFFIModuleNapiEnv;
|
||||
BUN_FFI_IMPORT void* NapiHandleScope__open(void* napi_env, bool detached);
|
||||
BUN_FFI_IMPORT void NapiHandleScope__close(void* napi_env, void* handleScope);
|
||||
BUN_FFI_IMPORT extern struct napi_env__ Bun__thisFFIModuleNapiEnv;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -138,7 +143,7 @@ typedef void* JSContext;
|
||||
|
||||
#ifdef IS_CALLBACK
|
||||
void* callback_ctx;
|
||||
ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
|
||||
BUN_FFI_IMPORT ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
|
||||
// We wrap
|
||||
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__));
|
||||
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) {
|
||||
@@ -177,8 +182,10 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__
|
||||
static uint8_t GET_JSTYPE(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) __attribute__((__always_inline__));
|
||||
static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static bool JSCELL_IS_ARRAY_BUFFER(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
void* JSVALUE_TO_ARRAYBUFFER_PTR(EncodedJSValue val);
|
||||
|
||||
static bool JSVALUE_IS_CELL(EncodedJSValue val) {
|
||||
return !(val.asInt64 & NotCellMask);
|
||||
@@ -204,6 +211,10 @@ static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) {
|
||||
return JSVALUE_IS_CELL(val) && JSTYPE_IS_TYPED_ARRAY(GET_JSTYPE(val));
|
||||
}
|
||||
|
||||
static bool JSCELL_IS_ARRAY_BUFFER(EncodedJSValue val) {
|
||||
return JSVALUE_IS_CELL(val) && GET_JSTYPE(val) == JSTypeArrayBuffer;
|
||||
}
|
||||
|
||||
static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) {
|
||||
return *(void**)((char*)val.asPtr + JSArrayBufferView__offsetOfVector);
|
||||
}
|
||||
@@ -225,6 +236,10 @@ static void* JSVALUE_TO_PTR(EncodedJSValue val) {
|
||||
return JSVALUE_TO_TYPED_ARRAY_VECTOR(val);
|
||||
}
|
||||
|
||||
if (JSCELL_IS_ARRAY_BUFFER(val)) {
|
||||
return JSVALUE_TO_ARRAYBUFFER_PTR(val);
|
||||
}
|
||||
|
||||
val.asInt64 -= DoubleEncodeOffset;
|
||||
size_t ptr = (size_t)val.asDouble;
|
||||
return (void*)ptr;
|
||||
@@ -350,7 +365,7 @@ static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) {
|
||||
}
|
||||
|
||||
#ifndef IS_CALLBACK
|
||||
ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
|
||||
BUN_FFI_IMPORT ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
|
||||
#define ZIG_REPR_TYPE int64_t
|
||||
|
||||
#ifdef _WIN32
|
||||
#define BUN_FFI_IMPORT __declspec(dllimport)
|
||||
#else
|
||||
#define BUN_FFI_IMPORT
|
||||
#endif
|
||||
|
||||
// /* 7.18.1.1 Exact-width integer types */
|
||||
typedef unsigned char uint8_t;
|
||||
@@ -62,9 +67,9 @@ typedef enum {
|
||||
napi_detachable_arraybuffer_expected,
|
||||
napi_would_deadlock // unused
|
||||
} napi_status;
|
||||
void* NapiHandleScope__open(void* napi_env, bool detached);
|
||||
void NapiHandleScope__close(void* napi_env, void* handleScope);
|
||||
extern struct napi_env__ Bun__thisFFIModuleNapiEnv;
|
||||
BUN_FFI_IMPORT void* NapiHandleScope__open(void* napi_env, bool detached);
|
||||
BUN_FFI_IMPORT void NapiHandleScope__close(void* napi_env, void* handleScope);
|
||||
BUN_FFI_IMPORT extern struct napi_env__ Bun__thisFFIModuleNapiEnv;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -138,7 +143,7 @@ typedef void* JSContext;
|
||||
|
||||
#ifdef IS_CALLBACK
|
||||
void* callback_ctx;
|
||||
ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
|
||||
BUN_FFI_IMPORT ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
|
||||
// We wrap
|
||||
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__));
|
||||
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) {
|
||||
@@ -177,8 +182,10 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__
|
||||
static uint8_t GET_JSTYPE(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) __attribute__((__always_inline__));
|
||||
static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static bool JSCELL_IS_ARRAY_BUFFER(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) __attribute__((__always_inline__));
|
||||
void* JSVALUE_TO_ARRAYBUFFER_PTR(EncodedJSValue val);
|
||||
|
||||
static bool JSVALUE_IS_CELL(EncodedJSValue val) {
|
||||
return !(val.asInt64 & NotCellMask);
|
||||
@@ -204,6 +211,10 @@ static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) {
|
||||
return JSVALUE_IS_CELL(val) && JSTYPE_IS_TYPED_ARRAY(GET_JSTYPE(val));
|
||||
}
|
||||
|
||||
static bool JSCELL_IS_ARRAY_BUFFER(EncodedJSValue val) {
|
||||
return JSVALUE_IS_CELL(val) && GET_JSTYPE(val) == JSTypeArrayBuffer;
|
||||
}
|
||||
|
||||
static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) {
|
||||
return *(void**)((char*)val.asPtr + JSArrayBufferView__offsetOfVector);
|
||||
}
|
||||
@@ -225,6 +236,10 @@ static void* JSVALUE_TO_PTR(EncodedJSValue val) {
|
||||
return JSVALUE_TO_TYPED_ARRAY_VECTOR(val);
|
||||
}
|
||||
|
||||
if (JSCELL_IS_ARRAY_BUFFER(val)) {
|
||||
return JSVALUE_TO_ARRAYBUFFER_PTR(val);
|
||||
}
|
||||
|
||||
val.asInt64 -= DoubleEncodeOffset;
|
||||
size_t ptr = (size_t)val.asDouble;
|
||||
return (void*)ptr;
|
||||
@@ -350,7 +365,7 @@ static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) {
|
||||
}
|
||||
|
||||
#ifndef IS_CALLBACK
|
||||
ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
|
||||
BUN_FFI_IMPORT ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
42
test/regression/issue/22225.test.ts
Normal file
42
test/regression/issue/22225.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { tempDirWithFiles } from "harness";
|
||||
import { endianness } from "os";
|
||||
import { FFIType, cc } from "bun:ffi";
|
||||
|
||||
test("FFI ArrayBuffer should work as pointer without segfault (issue #22225)", async () => {
|
||||
const LE = endianness() === "LE";
|
||||
|
||||
// Create temp directory with test C code
|
||||
const dir = tempDirWithFiles("test-ffi-arraybuffer", {
|
||||
"test.c": `
|
||||
#include <stdint.h>
|
||||
uint32_t get(uint32_t* value) {
|
||||
return *value;
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
// Compile C code and get FFI function
|
||||
const { symbols: { get } } = cc({
|
||||
source: `${dir}/test.c`,
|
||||
symbols: {
|
||||
get: {
|
||||
args: [FFIType.ptr],
|
||||
returns: FFIType.u32,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create test buffers
|
||||
const buff = new ArrayBuffer(4);
|
||||
const tarr = new Uint32Array(buff);
|
||||
const view = new DataView(buff);
|
||||
|
||||
// Set test value
|
||||
view.setUint32(0, 420, LE);
|
||||
|
||||
// Test that all three work correctly
|
||||
expect(get(view)).toBe(420);
|
||||
expect(get(tarr)).toBe(420);
|
||||
expect(get(buff)).toBe(420); // This should not segfault anymore
|
||||
});
|
||||
Reference in New Issue
Block a user