Fix conversions from JSValue to FFI pointer (#25045)

Fixes this issue, where two identical JS numbers could become two
different FFI pointers:

```c
// gcc -fpic -shared -o main.so
#include <stdio.h>

void* getPtr(void) {
    return (void*)123;
}

void printPtr(void* ptr) {
    printf("%zu\n", (size_t)ptr);
}
```

```js
import { dlopen, FFIType } from "bun:ffi";

const lib = dlopen("./main.so", {
  getPtr: { args: [], returns: FFIType.ptr },
  printPtr: { args: [FFIType.ptr], returns: FFIType.void },
});

const ptr = lib.symbols.getPtr();
console.log(`${typeof ptr} ${ptr}`);

const ptr2 = Number(String(ptr));
console.log(`${typeof ptr2} ${ptr2}`);

console.log(`pointers equal? ${ptr === ptr2}`);
lib.symbols.printPtr(ptr);
lib.symbols.printPtr(ptr2);
```

```console
$ bun main.js
number 123
number 123
pointers equal? true
123
18446744073709551615
```

Fixes #20072

(For internal tracking: fixes ENG-22327)
This commit is contained in:
taylor.fish
2025-11-24 17:34:39 -08:00
committed by GitHub
parent 9c8575f975
commit 7335cb747b
2 changed files with 11 additions and 18 deletions

View File

@@ -225,23 +225,26 @@ static void* JSVALUE_TO_PTR(EncodedJSValue val) {
return 0;
if (JSCELL_IS_TYPED_ARRAY(val)) {
return JSVALUE_TO_TYPED_ARRAY_VECTOR(val);
return JSVALUE_TO_TYPED_ARRAY_VECTOR(val);
}
if (JSVALUE_IS_INT32(val)) {
return (void*)(uintptr_t)JSVALUE_TO_INT32(val);
}
// Assume the JSValue is a double
val.asInt64 -= DoubleEncodeOffset;
size_t ptr = (size_t)val.asDouble;
return (void*)ptr;
return (void*)(uintptr_t)val.asDouble;
}
static EncodedJSValue PTR_TO_JSVALUE(void* ptr) {
EncodedJSValue val;
if (ptr == 0)
{
val.asInt64 = TagValueNull;
return val;
if (ptr == 0) {
val.asInt64 = TagValueNull;
return val;
}
val.asDouble = (double)(size_t)ptr;
val.asDouble = (double)(uintptr_t)ptr;
val.asInt64 += DoubleEncodeOffset;
return val;
}

View File

@@ -100,16 +100,6 @@ pub inline fn BOOLEAN_TO_JSVALUE(arg_val: @"bool") EncodedJSValue {
res.asInt64 = @as(i64, @bitCast(@as(c_longlong, if (@as(c_int, @intFromBool(val)) != 0) (@as(c_int, 2) | @as(c_int, 4)) | @as(c_int, 1) else (@as(c_int, 2) | @as(c_int, 4)) | @as(c_int, 0))));
return res;
}
pub inline fn PTR_TO_JSVALUE(arg_ptr: ?*anyopaque) EncodedJSValue {
const ptr = arg_ptr;
var val: EncodedJSValue = undefined;
val.asInt64 = @as(i64, @intCast(@intFromPtr(ptr))) + (@as(c_longlong, 1) << @as(@import("std").math.Log2Int(c_longlong), @intCast(49)));
return val;
}
pub inline fn JSVALUE_TO_PTR(arg_val: EncodedJSValue) ?*anyopaque {
const val = arg_val;
return @as(?*anyopaque, @ptrFromInt(val.asInt64 - (@as(c_longlong, 1) << @as(@import("std").math.Log2Int(c_longlong), @intCast(49)))));
}
pub inline fn JSVALUE_TO_INT32(arg_val: EncodedJSValue) i32 {
const val = arg_val;
return @as(i32, @bitCast(@as(c_int, @truncate(val.asInt64))));