mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 19:08:50 +00:00
[bun:ffi] it works
This commit is contained in:
@@ -31,10 +31,6 @@ it("Buffer", () => {
|
||||
expect(new Buffer(input).toString("utf8")).toBe(inputs[i]);
|
||||
expect(Array.from(new Buffer(input)).join(",")).toBe(good[i].join(","));
|
||||
}
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
var input = inputs[i];
|
||||
expect(new Buffer(input, "ucs2").toString("utf8")).toBe(inputs[i]);
|
||||
}
|
||||
});
|
||||
|
||||
it("Buffer.toBuffer throws", () => {
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { unsafe } from "bun";
|
||||
//
|
||||
import {
|
||||
viewSource,
|
||||
dlopen,
|
||||
CString,
|
||||
ptr,
|
||||
toBuffer,
|
||||
toArrayBuffer,
|
||||
FFIType,
|
||||
} from "bun:ffi";
|
||||
|
||||
it("ffi print", () => {
|
||||
Bun.dlprint({
|
||||
viewSource({
|
||||
add: {
|
||||
params: ["int32_t", "int32_t"],
|
||||
args: [FFIType.int],
|
||||
return_type: "int32_t",
|
||||
},
|
||||
})[0];
|
||||
@@ -211,7 +221,7 @@ it("ffi run", () => {
|
||||
ptr_should_point_to_42_as_int32_t,
|
||||
},
|
||||
close,
|
||||
} = Bun.dlopen("/tmp/bun-ffi-test.dylib", types);
|
||||
} = dlopen("/tmp/bun-ffi-test.dylib", types);
|
||||
|
||||
expect(returns_true()).toBe(true);
|
||||
expect(returns_false()).toBe(false);
|
||||
@@ -249,16 +259,17 @@ it("ffi run", () => {
|
||||
expect(add_uint16_t(1, 1)).toBe(2);
|
||||
expect(add_uint32_t(1, 1)).toBe(2);
|
||||
|
||||
const ptr = ptr_should_point_to_42_as_int32_t();
|
||||
expect(ptr != 0).toBe(true);
|
||||
expect(typeof ptr === "number").toBe(true);
|
||||
expect(does_pointer_equal_42_as_int32_t(ptr)).toBe(true);
|
||||
const buffer = unsafe.bufferFromPtr(ptr, 4);
|
||||
const cptr = ptr_should_point_to_42_as_int32_t();
|
||||
expect(cptr != 0).toBe(true);
|
||||
expect(typeof cptr === "number").toBe(true);
|
||||
expect(does_pointer_equal_42_as_int32_t(cptr)).toBe(true);
|
||||
const buffer = toBuffer(cptr, 0, 4);
|
||||
expect(buffer.readInt32(0)).toBe(42);
|
||||
expect(
|
||||
new DataView(unsafe.arrayBufferFromPtr(ptr, 4), 0, 4).getInt32(0, true)
|
||||
).toBe(42);
|
||||
expect(unsafe.arrayBufferToPtr(buffer)).toBe(ptr);
|
||||
expect(new DataView(toArrayBuffer(cptr, 0, 4), 0, 4).getInt32(0, true)).toBe(
|
||||
42
|
||||
);
|
||||
expect(ptr(buffer)).toBe(cptr);
|
||||
expect(new CString(cptr, 0, 1)).toBe("*");
|
||||
close();
|
||||
});
|
||||
``;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bun-types",
|
||||
"version": "0.0.78",
|
||||
"version": "0.0.79",
|
||||
"description": "Type definitions for bun.js",
|
||||
"types": "types.d.ts",
|
||||
"files": [
|
||||
|
||||
342
packages/bun-types/types.d.ts
vendored
342
packages/bun-types/types.d.ts
vendored
@@ -572,6 +572,22 @@ declare module "bun" {
|
||||
serverNames: Record<string, SSLOptions & SSLAdvancedOptions>;
|
||||
};
|
||||
|
||||
/**
|
||||
* HTTP & HTTPS Server
|
||||
*
|
||||
* To start the server, see {@link serve}
|
||||
*
|
||||
* Often, you don't need to interact with this object directly. It exists to help you with the following tasks:
|
||||
* - Stop the server
|
||||
* - How many requests are currently being handled?
|
||||
*
|
||||
* For performance, Bun pre-allocates most of the data for 2048 concurrent requests.
|
||||
* That means starting a new server allocates about 500 KB of memory. Try to
|
||||
* avoid starting and stopping the server often (unless it's a new instance of bun).
|
||||
*
|
||||
* Powered by a fork of [uWebSockets](https://github.com/uNetworking/uWebSockets). Thank you @alexhultman.
|
||||
*
|
||||
*/
|
||||
interface Server {
|
||||
/**
|
||||
* Stop listening to prevent new connections from being accepted.
|
||||
@@ -877,7 +893,7 @@ declare module "bun" {
|
||||
*
|
||||
* This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
|
||||
*
|
||||
* Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
* The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
*
|
||||
* The equivalent `openssl` command is:
|
||||
*
|
||||
@@ -897,7 +913,7 @@ declare module "bun" {
|
||||
*
|
||||
* This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
|
||||
*
|
||||
* Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
* The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
*
|
||||
* The equivalent `openssl` command is:
|
||||
*
|
||||
@@ -909,14 +925,9 @@ declare module "bun" {
|
||||
export function sha(input: StringOrBuffer, encoding: DigestEncoding): string;
|
||||
|
||||
/**
|
||||
* This is not the default because it's not cryptographically secure and it's slower than {@link SHA512}
|
||||
*
|
||||
* Hashing functions for SHA-1
|
||||
*
|
||||
* Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
*
|
||||
* SHA-1 is no longer cryptographically secure and it's slower than {@link SHA512}.
|
||||
*
|
||||
* Consider using the {@link SHA512_256} instead.
|
||||
* Consider using the ugly-named {@link SHA512_256} instead
|
||||
*/
|
||||
export class SHA1 extends CryptoHashInterface<SHA1> {
|
||||
constructor();
|
||||
@@ -926,18 +937,6 @@ declare module "bun" {
|
||||
*/
|
||||
static readonly byteLength: 20;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Hashing functions for MD5
|
||||
*
|
||||
* Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
*
|
||||
* If you're looking for a fast hashing function, consider {@link hash}
|
||||
* instead. This is not cryptographically secure and it's slower than
|
||||
* {@link hash}. The best reason to use it is compatibility.
|
||||
*
|
||||
*/
|
||||
export class MD5 extends CryptoHashInterface<MD5> {
|
||||
constructor();
|
||||
|
||||
@@ -946,18 +945,6 @@ declare module "bun" {
|
||||
*/
|
||||
static readonly byteLength: 16;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Ancient hashing function. Bun maybe shouldn't have included this.
|
||||
*
|
||||
* Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
*
|
||||
* If you're looking for a fast hashing function, consider {@link hash}
|
||||
* instead. This is not cryptographically secure and it's slower than
|
||||
* {@link hash}. The best reason to use it is compatibility.
|
||||
*
|
||||
*/
|
||||
export class MD4 extends CryptoHashInterface<MD4> {
|
||||
constructor();
|
||||
|
||||
@@ -966,11 +953,6 @@ declare module "bun" {
|
||||
*/
|
||||
static readonly byteLength: 16;
|
||||
}
|
||||
/**
|
||||
* Smaller variant of SHA-2
|
||||
*
|
||||
* Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
*/
|
||||
export class SHA224 extends CryptoHashInterface<SHA224> {
|
||||
constructor();
|
||||
|
||||
@@ -979,11 +961,6 @@ declare module "bun" {
|
||||
*/
|
||||
static readonly byteLength: 28;
|
||||
}
|
||||
/**
|
||||
* Faster variant of SHA-2, but with bigger output (64 bytes)
|
||||
*
|
||||
* Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
|
||||
*/
|
||||
export class SHA512 extends CryptoHashInterface<SHA512> {
|
||||
constructor();
|
||||
|
||||
@@ -1000,13 +977,6 @@ declare module "bun" {
|
||||
*/
|
||||
static readonly byteLength: 48;
|
||||
}
|
||||
/**
|
||||
* SHA-2 (Secure Hash Algorithm 2) is a set of cryptographic hash functions
|
||||
* designed by the United States National Security Agency (NSA) and first
|
||||
* published in 2001
|
||||
*
|
||||
* {@link https://en.wikipedia.org/wiki/SHA-2}
|
||||
*/
|
||||
export class SHA256 extends CryptoHashInterface<SHA256> {
|
||||
constructor();
|
||||
|
||||
@@ -1016,9 +986,6 @@ declare module "bun" {
|
||||
static readonly byteLength: 32;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Fast variant of SHA-512, but with smaller output (32 bytes)
|
||||
*
|
||||
* See also {@link sha}
|
||||
*/
|
||||
export class SHA512_256 extends CryptoHashInterface<SHA512_256> {
|
||||
@@ -1063,6 +1030,268 @@ interface BufferEncodingOption {
|
||||
|
||||
declare var Bun: typeof import("bun");
|
||||
|
||||
|
||||
// ./ffi.d.ts
|
||||
|
||||
/**
|
||||
* `bun:ffi` lets you efficiently call C functions & FFI functions from JavaScript
|
||||
* without writing any C code yourself.
|
||||
*
|
||||
* ```js
|
||||
* import {dlopen, CString, ptr} from 'bun:ffi';
|
||||
*
|
||||
* const lib = dlopen('libsqlite3', {});
|
||||
*
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* This is powered by just-in-time compiling C wrappers
|
||||
* that convert JavaScript types to C types and back. Internally,
|
||||
* bun uses [tinycc](https://github.com/TinyCC/tinycc), so a big thanks
|
||||
* goes to Fabrice Bellard and TinyCC maintainers for making this possible.
|
||||
*
|
||||
*/
|
||||
declare module "bun:ffi" {
|
||||
export enum FFIType {
|
||||
char = 0,
|
||||
|
||||
int8_t = 1,
|
||||
i8 = 1,
|
||||
|
||||
uint8_t = 2,
|
||||
u8 = 2,
|
||||
|
||||
int16_t = 3,
|
||||
i16 = 3,
|
||||
|
||||
uint16_t = 4,
|
||||
u16 = 4,
|
||||
|
||||
/**
|
||||
* 32-bit signed integer
|
||||
*
|
||||
*/
|
||||
int32_t = 5,
|
||||
|
||||
/**
|
||||
* 32-bit signed integer
|
||||
*
|
||||
* Alias of {@link FFIType.int32_t}
|
||||
*/
|
||||
i32 = 5,
|
||||
/**
|
||||
* 32-bit signed integer
|
||||
*
|
||||
* The same as `int` in C
|
||||
*/
|
||||
int = 5,
|
||||
|
||||
uint32_t = 6,
|
||||
u32 = 6,
|
||||
|
||||
int64_t = 7,
|
||||
i64 = 7,
|
||||
|
||||
uint64_t = 8,
|
||||
u64 = 8,
|
||||
|
||||
double = 9,
|
||||
f64 = 9,
|
||||
|
||||
float = 10,
|
||||
f32 = 10,
|
||||
|
||||
bool = 11,
|
||||
|
||||
ptr = 12,
|
||||
pointer = 12,
|
||||
|
||||
void = 13,
|
||||
}
|
||||
|
||||
type Symbols = Record<
|
||||
string,
|
||||
{
|
||||
/**
|
||||
* Arguments to a C function
|
||||
*
|
||||
* Defaults to an empty array, which means no arguments.
|
||||
*
|
||||
* To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
|
||||
*
|
||||
* @example
|
||||
* From JavaScript:
|
||||
* ```js
|
||||
* const lib = dlopen('add', {
|
||||
* // FFIType can be used or you can pass string labels.
|
||||
* args: [FFIType.i32, "i32"],
|
||||
* return_type: "i32",
|
||||
* });
|
||||
* lib.symbols.add(1, 2)
|
||||
* ```
|
||||
* In C:
|
||||
* ```c
|
||||
* int add(int a, int b) {
|
||||
* return a + b;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
args?: FFIType[];
|
||||
return_type?: FFIType;
|
||||
}
|
||||
>;
|
||||
|
||||
export interface Library {
|
||||
symbols: Record<string, CallableFunction>;
|
||||
|
||||
/**
|
||||
* `dlclose` the library, unloading the symbols and freeing memory allocated.
|
||||
*
|
||||
* Once called, the library is no longer usable.
|
||||
*
|
||||
* Calling a function from a library that has been closed is undefined behavior.
|
||||
*/
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export function dlopen(libraryName: string, symbols: Symbols): Library<T>;
|
||||
|
||||
/**
|
||||
* Read a pointer as a {@link Buffer}
|
||||
*
|
||||
* If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
|
||||
*
|
||||
* @param ptr The memory address to read
|
||||
* @param byteOffset bytes to skip before reading
|
||||
* @param byteLength bytes to read
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*
|
||||
*/
|
||||
export function toBuffer(
|
||||
ptr: number,
|
||||
byteOffset?: number,
|
||||
byteLength?: number
|
||||
): Buffer;
|
||||
|
||||
/**
|
||||
* Read a pointer as an {@link ArrayBuffer}
|
||||
*
|
||||
* If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
|
||||
*
|
||||
* @param ptr The memory address to read
|
||||
* @param byteOffset bytes to skip before reading
|
||||
* @param byteLength bytes to read
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
export function toArrayBuffer(
|
||||
ptr: number,
|
||||
byteOffset?: number,
|
||||
byteLength?: number
|
||||
): ArrayBuffer;
|
||||
|
||||
/**
|
||||
* Get the pointer backing a {@link TypedArray} or {@link ArrayBuffer}
|
||||
*
|
||||
* Use this to pass {@link TypedArray} or {@link ArrayBuffer} to C functions.
|
||||
*
|
||||
* This is for use with FFI functions. For performance reasons, FFI will
|
||||
* not automatically convert typed arrays to C pointers.
|
||||
*
|
||||
* @param {TypedArray|ArrayBuffer|DataView} view the typed array or array buffer to get the pointer for
|
||||
* @param {number} byteOffset optional offset into the view in bytes
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* From JavaScript:
|
||||
* ```js
|
||||
* const array = new Uint8Array(10);
|
||||
* const rawPtr = ptr(array);
|
||||
* myCFunction(rawPtr);
|
||||
* ```
|
||||
* To C:
|
||||
* ```c
|
||||
* void myCFunction(char* rawPtr) {
|
||||
* // Do something with rawPtr
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export function ptr(
|
||||
view: TypedArray | ArrayBufferLike | DataView,
|
||||
byteOffset?: number
|
||||
): number;
|
||||
|
||||
/**
|
||||
* Get a string from a UTF-8 encoded C string
|
||||
* If `byteLength` is not provided, the string is assumed to be null-terminated.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* console.log(new CString(ptr));
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* // print the first 4 characters
|
||||
* console.log(new CString(ptr, 0, 4));
|
||||
* ```
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
export interface CString {
|
||||
/**
|
||||
* Get a string from a UTF-8 encoded C string
|
||||
* If `byteLength` is not provided, the string is assumed to be null-terminated.
|
||||
*
|
||||
* @param ptr The pointer to the C string
|
||||
* @param byteOffset bytes to skip before reading
|
||||
* @param byteLength bytes to read
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* console.log(new CString(ptr));
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* // print the first 4 characters
|
||||
* console.log(new CString(ptr, 0, 4));
|
||||
* ```
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
new (ptr: number, byteOffset?: number, byteLength?: number): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* View the generated C code for FFI bindings
|
||||
*
|
||||
* You probably won't need this unless there's a bug in the FFI bindings
|
||||
* generator or you're just curious.
|
||||
*/
|
||||
export function viewSource(symbols: Symbols): string[];
|
||||
}
|
||||
|
||||
|
||||
// ./fs.d.ts
|
||||
|
||||
/**
|
||||
@@ -4664,6 +4893,7 @@ declare module "node:fs" {
|
||||
export = fs;
|
||||
}
|
||||
|
||||
|
||||
// ./html-rewriter.d.ts
|
||||
|
||||
declare namespace HTMLRewriterTypes {
|
||||
@@ -4780,9 +5010,10 @@ declare class HTMLRewriter {
|
||||
transform(input: Response): Response;
|
||||
}
|
||||
|
||||
|
||||
// ./globals.d.ts
|
||||
|
||||
type Encoding = "utf8" | "utf-8" | "windows-1252" | "utf-16";
|
||||
type Encoding = "utf-8" | "windows-1252" | "utf-16";
|
||||
|
||||
interface console {
|
||||
assert(condition?: boolean, ...data: any[]): void;
|
||||
@@ -6208,6 +6439,7 @@ declare var Loader: {
|
||||
resolveSync: (specifier: string, from: string) => string;
|
||||
};
|
||||
|
||||
|
||||
// ./path.d.ts
|
||||
|
||||
/**
|
||||
@@ -6415,6 +6647,7 @@ declare module "node:path/win32" {
|
||||
export * from "path/win32";
|
||||
}
|
||||
|
||||
|
||||
// ./bun-test.d.ts
|
||||
|
||||
/**
|
||||
@@ -6453,3 +6686,4 @@ declare module "test" {
|
||||
import BunTestModule = require("bun:test");
|
||||
export = BunTestModule;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
/* 7.18.1.1 Exact-width integer types */
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef char int8_t;
|
||||
typedef short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef int int32_t;
|
||||
|
||||
@@ -77,6 +77,7 @@ const VirtualMachine = @import("../javascript.zig").VirtualMachine;
|
||||
const IOTask = JSC.IOTask;
|
||||
|
||||
const is_bindgen = JSC.is_bindgen;
|
||||
const max_addressible_memory = std.math.maxInt(u56);
|
||||
|
||||
threadlocal var css_imports_list_strings: [512]ZigString = undefined;
|
||||
threadlocal var css_imports_list: [512]Api.StringPointer = undefined;
|
||||
@@ -1121,12 +1122,6 @@ pub const Class = NewClass(
|
||||
.sha = .{
|
||||
.rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false),
|
||||
},
|
||||
.dlprint = .{
|
||||
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false),
|
||||
},
|
||||
.dlopen = .{
|
||||
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false),
|
||||
},
|
||||
},
|
||||
.{
|
||||
.main = .{
|
||||
@@ -1208,6 +1203,9 @@ pub const Class = NewClass(
|
||||
.SHA512_256 = .{
|
||||
.get = Crypto.SHA512_256.getter,
|
||||
},
|
||||
.FFI = .{
|
||||
.get = FFI.getter,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1862,112 +1860,10 @@ pub const Unsafe = struct {
|
||||
.arrayBufferToString = .{
|
||||
.rfn = arrayBufferToString,
|
||||
},
|
||||
.arrayBufferToPtr = .{
|
||||
.rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferToPtr", false, false),
|
||||
},
|
||||
.arrayBufferFromPtr = .{
|
||||
.rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferFromPtr", false, false),
|
||||
},
|
||||
.bufferFromPtr = .{
|
||||
.rfn = JSC.wrapWithHasContainer(Unsafe, "bufferFromPtr", false, false),
|
||||
},
|
||||
},
|
||||
.{},
|
||||
);
|
||||
|
||||
const ValueOrError = union(enum) {
|
||||
err: JSValue,
|
||||
slice: []u8,
|
||||
};
|
||||
|
||||
pub fn arrayBufferToPtr(globalThis: *JSGlobalObject, value: JSValue) JSValue {
|
||||
if (value.isEmpty()) {
|
||||
return JSC.JSValue.jsNull();
|
||||
}
|
||||
|
||||
const array_buffer = value.asArrayBuffer(globalThis) orelse {
|
||||
return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref());
|
||||
};
|
||||
|
||||
if (array_buffer.len == 0) {
|
||||
return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref());
|
||||
}
|
||||
|
||||
return JSC.JSValue.jsNumber(@bitCast(f64, @ptrToInt(array_buffer.ptr)));
|
||||
}
|
||||
|
||||
fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, valueLength: JSValue) ValueOrError {
|
||||
if (!value.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
const num = value.asNumber();
|
||||
if (num == 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (!std.math.isFinite(num)) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
const addr = @bitCast(usize, num);
|
||||
|
||||
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (!valueLength.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (valueLength.asNumber() == 0.0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
const length_i = valueLength.toInt64();
|
||||
if (length_i < 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (length_i > std.math.maxInt(u48)) {
|
||||
return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
const length = @intCast(usize, length_i);
|
||||
|
||||
return .{ .slice = @intToPtr([*]u8, addr)[0..length] };
|
||||
}
|
||||
|
||||
pub fn arrayBufferFromPtr(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
valueLength: JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, valueLength)) {
|
||||
.err => |erro| {
|
||||
return erro;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bufferFromPtr(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
valueLength: JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, valueLength)) {
|
||||
.err => |erro| {
|
||||
return erro;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return JSC.JSValue.createBuffer(globalThis, slice, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// For testing the segfault handler
|
||||
pub fn __debug__doSegfault(
|
||||
_: void,
|
||||
@@ -2353,6 +2249,265 @@ pub const Timer = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const FFI = struct {
|
||||
pub const Class = NewClass(
|
||||
void,
|
||||
.{
|
||||
.name = "FFI",
|
||||
},
|
||||
.{
|
||||
.viewSource = .{
|
||||
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false),
|
||||
},
|
||||
.dlopen = .{
|
||||
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false),
|
||||
},
|
||||
.ptr = .{
|
||||
.rfn = JSC.wrapWithHasContainer(@This(), "ptr", false, false),
|
||||
},
|
||||
.toBuffer = .{
|
||||
.rfn = JSC.wrapWithHasContainer(@This(), "toBuffer", false, false),
|
||||
},
|
||||
.toArrayBuffer = .{
|
||||
.rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false),
|
||||
},
|
||||
},
|
||||
.{
|
||||
.CString = .{
|
||||
.get = UnsafeCString.getter,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
pub fn ptr(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
) JSValue {
|
||||
if (value.isEmpty()) {
|
||||
return JSC.JSValue.jsNull();
|
||||
}
|
||||
|
||||
const array_buffer = value.asArrayBuffer(globalThis) orelse {
|
||||
return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref());
|
||||
};
|
||||
|
||||
if (array_buffer.len == 0) {
|
||||
return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref());
|
||||
}
|
||||
|
||||
var addr: usize = @ptrToInt(array_buffer.ptr);
|
||||
|
||||
if (byteOffset) |off| {
|
||||
if (!off.isEmptyOrUndefinedOrNull()) {
|
||||
if (!off.isNumber()) {
|
||||
return JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref());
|
||||
}
|
||||
}
|
||||
|
||||
const bytei64 = off.toInt64();
|
||||
if (bytei64 < 0) {
|
||||
addr -|= @intCast(usize, bytei64 * -1);
|
||||
} else {
|
||||
addr += @intCast(usize, bytei64);
|
||||
}
|
||||
|
||||
if (addr > @ptrToInt(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) {
|
||||
return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis.ref());
|
||||
}
|
||||
}
|
||||
|
||||
if (addr > max_addressible_memory) {
|
||||
return JSC.toInvalidArguments("Pointer is outside max addressible memory, which usually means a bug in your program.", .{}, globalThis.ref());
|
||||
}
|
||||
|
||||
if (addr == 0) {
|
||||
return JSC.toInvalidArguments("Pointer must not be 0", .{}, globalThis.ref());
|
||||
}
|
||||
|
||||
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
|
||||
return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref());
|
||||
}
|
||||
|
||||
// truncate to 56 bits to clear any pointer tags
|
||||
return JSC.JSValue.jsNumber(@bitCast(f64, @as(usize, @truncate(u56, addr))));
|
||||
}
|
||||
|
||||
const ValueOrError = union(enum) {
|
||||
err: JSValue,
|
||||
slice: []u8,
|
||||
};
|
||||
|
||||
pub fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, byteLength: ?JSValue) ValueOrError {
|
||||
if (!value.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
const num = value.asNumber();
|
||||
if (num == 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (!std.math.isFinite(num)) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
var addr = @bitCast(usize, num);
|
||||
|
||||
if (byteOffset) |byte_off| {
|
||||
if (byte_off.isNumber()) {
|
||||
const off = byte_off.toInt64();
|
||||
if (off < 0) {
|
||||
addr -|= @intCast(usize, off * -1);
|
||||
} else {
|
||||
addr +|= @intCast(usize, off);
|
||||
}
|
||||
|
||||
if (addr == 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (!std.math.isFinite(byte_off.asNumber())) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
|
||||
}
|
||||
} else if (!byte_off.isEmptyOrUndefinedOrNull()) {
|
||||
// do nothing
|
||||
} else {
|
||||
return .{ .err = JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref()) };
|
||||
}
|
||||
}
|
||||
|
||||
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (byteLength) |valueLength| {
|
||||
if (!valueLength.isEmptyOrUndefinedOrNull()) {
|
||||
if (!valueLength.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (valueLength.asNumber() == 0.0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
const length_i = valueLength.toInt64();
|
||||
if (length_i < 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
if (length_i > max_addressible_memory) {
|
||||
return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) };
|
||||
}
|
||||
|
||||
const length = @intCast(usize, length_i);
|
||||
return .{ .slice = @intToPtr([*]u8, addr)[0..length] };
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .slice = std.mem.sliceTo(@intToPtr([*:0]u8, addr), 0) };
|
||||
}
|
||||
|
||||
pub fn toArrayBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |erro| {
|
||||
return erro;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |erro| {
|
||||
return erro;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return JSC.JSValue.createBuffer(globalThis, slice, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
_: void,
|
||||
ctx: js.JSContextRef,
|
||||
_: js.JSValueRef,
|
||||
_: js.JSStringRef,
|
||||
_: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
var existing = ctx.ptr().getCachedObject(&ZigString.init("FFI"));
|
||||
if (existing.isEmpty()) {
|
||||
var prototype = JSC.C.JSObjectMake(ctx, FFI.Class.get().?[0], null);
|
||||
var base = JSC.C.JSObjectMake(ctx, null, null);
|
||||
JSC.C.JSObjectSetPrototype(ctx, base, prototype);
|
||||
return ctx.ptr().putCachedObject(
|
||||
&ZigString.init("FFI"),
|
||||
JSValue.fromRef(base),
|
||||
).asObjectRef();
|
||||
}
|
||||
|
||||
return existing.asObjectRef();
|
||||
}
|
||||
};
|
||||
|
||||
pub const UnsafeCString = struct {
|
||||
pub fn constructor(
|
||||
ctx: js.JSContextRef,
|
||||
_: js.JSObjectRef,
|
||||
len: usize,
|
||||
args: [*c]const js.JSValueRef,
|
||||
exception: js.ExceptionRef,
|
||||
) callconv(.C) js.JSObjectRef {
|
||||
if (len == 0) {
|
||||
JSC.throwInvalidArguments("Expected a ptr", .{}, ctx, exception);
|
||||
return null;
|
||||
}
|
||||
|
||||
return newCString(ctx.ptr(), JSC.JSValue.fromRef(args[0]), if (len > 1) JSC.JSValue.fromRef(args[1]) else null, if (len > 2) JSC.JSValue.fromRef(args[2]) else null).asObjectRef();
|
||||
}
|
||||
|
||||
pub fn newCString(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, lengthValue: ?JSValue) JSC.JSValue {
|
||||
switch (FFI.getPtrSlice(globalThis, value, byteOffset, lengthValue)) {
|
||||
.err => |err| {
|
||||
return err;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return WebCore.Encoder.toString(slice.ptr, slice.len, globalThis, .utf8);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
_: void,
|
||||
ctx: js.JSContextRef,
|
||||
_: js.JSValueRef,
|
||||
_: js.JSStringRef,
|
||||
_: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
var existing = ctx.ptr().getCachedObject(&ZigString.init("UnsafeCString"));
|
||||
if (existing.isEmpty()) {
|
||||
return ctx.ptr().putCachedObject(
|
||||
&ZigString.init("UnsafeCString"),
|
||||
JSValue.fromRef(JSC.C.JSObjectMakeConstructor(ctx, null, constructor)),
|
||||
).asObjectRef();
|
||||
}
|
||||
|
||||
return existing.asObjectRef();
|
||||
}
|
||||
};
|
||||
|
||||
/// EnvironmentVariables is runtime defined.
|
||||
/// Also, you can't iterate over process.env normally since it only exists at build-time otherwise
|
||||
// This is aliased to Bun.env
|
||||
|
||||
@@ -98,12 +98,14 @@ pub const FFI = struct {
|
||||
this.closed = true;
|
||||
this.dylib.close();
|
||||
|
||||
for (this.functions.values()) |*val| {
|
||||
VirtualMachine.vm.allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
|
||||
const allocator = VirtualMachine.vm.allocator;
|
||||
|
||||
val.arg_types.deinit(VirtualMachine.vm.allocator);
|
||||
for (this.functions.values()) |*val| {
|
||||
allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
|
||||
|
||||
val.arg_types.deinit(allocator);
|
||||
}
|
||||
this.functions.deinit(VirtualMachine.vm.allocator);
|
||||
this.functions.deinit(allocator);
|
||||
|
||||
return JSC.JSValue.jsUndefined();
|
||||
}
|
||||
@@ -312,9 +314,28 @@ pub const FFI = struct {
|
||||
|
||||
try abi_types.ensureTotalCapacityPrecise(allocator, array.len);
|
||||
while (array.next()) |val| {
|
||||
if (val.isEmptyOrUndefinedOrNull() or !val.jsType().isStringLike()) {
|
||||
if (val.isEmptyOrUndefinedOrNull()) {
|
||||
abi_types.clearAndFree(allocator);
|
||||
return ZigString.init("param must be a string (type name)").toErrorInstance(global);
|
||||
return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
|
||||
}
|
||||
|
||||
if (val.isAnyInt()) {
|
||||
const int = val.toInt32();
|
||||
switch (int) {
|
||||
0...13 => {
|
||||
abi_types.appendAssumeCapacity(@intToEnum(ABIType, int));
|
||||
continue;
|
||||
},
|
||||
else => {
|
||||
abi_types.clearAndFree(allocator);
|
||||
return ZigString.init("invalid ABI type").toErrorInstance(global);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!val.jsType().isStringLike()) {
|
||||
abi_types.clearAndFree(allocator);
|
||||
return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
|
||||
}
|
||||
|
||||
var type_name = val.toSlice(global, allocator);
|
||||
@@ -581,39 +602,78 @@ pub const FFI = struct {
|
||||
|
||||
@"void" = 13,
|
||||
|
||||
pub const label = ComptimeStringMap(ABIType, .{
|
||||
.{ "bool", .bool },
|
||||
.{ "c_int", .int32_t },
|
||||
.{ "c_uint", .uint32_t },
|
||||
.{ "char", .char },
|
||||
.{ "char*", .ptr },
|
||||
.{ "double", .double },
|
||||
.{ "f32", .float },
|
||||
.{ "f64", .double },
|
||||
.{ "float", .float },
|
||||
.{ "i16", .int16_t },
|
||||
.{ "i32", .int32_t },
|
||||
.{ "i64", .int64_t },
|
||||
.{ "i8", .int8_t },
|
||||
.{ "int", .int32_t },
|
||||
.{ "int16_t", .int16_t },
|
||||
.{ "int32_t", .int32_t },
|
||||
.{ "int64_t", .int64_t },
|
||||
.{ "int8_t", .int8_t },
|
||||
.{ "isize", .int64_t },
|
||||
.{ "u16", .uint16_t },
|
||||
.{ "u32", .uint32_t },
|
||||
.{ "u64", .uint64_t },
|
||||
.{ "u8", .uint8_t },
|
||||
.{ "uint16_t", .uint16_t },
|
||||
.{ "uint32_t", .uint32_t },
|
||||
.{ "uint64_t", .uint64_t },
|
||||
.{ "uint8_t", .uint8_t },
|
||||
.{ "usize", .uint64_t },
|
||||
.{ "void*", .ptr },
|
||||
.{ "ptr", .ptr },
|
||||
.{ "pointer", .ptr },
|
||||
});
|
||||
const map = .{
|
||||
.{ "bool", ABIType.bool },
|
||||
.{ "c_int", ABIType.int32_t },
|
||||
.{ "c_uint", ABIType.uint32_t },
|
||||
.{ "char", ABIType.char },
|
||||
.{ "char*", ABIType.ptr },
|
||||
.{ "double", ABIType.double },
|
||||
.{ "f32", ABIType.float },
|
||||
.{ "f64", ABIType.double },
|
||||
.{ "float", ABIType.float },
|
||||
.{ "i16", ABIType.int16_t },
|
||||
.{ "i32", ABIType.int32_t },
|
||||
.{ "i64", ABIType.int64_t },
|
||||
.{ "i8", ABIType.int8_t },
|
||||
.{ "int", ABIType.int32_t },
|
||||
.{ "int16_t", ABIType.int16_t },
|
||||
.{ "int32_t", ABIType.int32_t },
|
||||
.{ "int64_t", ABIType.int64_t },
|
||||
.{ "int8_t", ABIType.int8_t },
|
||||
.{ "isize", ABIType.int64_t },
|
||||
.{ "u16", ABIType.uint16_t },
|
||||
.{ "u32", ABIType.uint32_t },
|
||||
.{ "u64", ABIType.uint64_t },
|
||||
.{ "u8", ABIType.uint8_t },
|
||||
.{ "uint16_t", ABIType.uint16_t },
|
||||
.{ "uint32_t", ABIType.uint32_t },
|
||||
.{ "uint64_t", ABIType.uint64_t },
|
||||
.{ "uint8_t", ABIType.uint8_t },
|
||||
.{ "usize", ABIType.uint64_t },
|
||||
.{ "void*", ABIType.ptr },
|
||||
.{ "ptr", ABIType.ptr },
|
||||
.{ "pointer", ABIType.ptr },
|
||||
};
|
||||
pub const label = ComptimeStringMap(ABIType, map);
|
||||
const EnumMapFormatter = struct {
|
||||
name: []const u8,
|
||||
entry: ABIType,
|
||||
pub fn format(self: EnumMapFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
try writer.writeAll("['");
|
||||
// these are not all valid identifiers
|
||||
try writer.writeAll(self.name);
|
||||
try writer.writeAll("']:");
|
||||
try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
|
||||
try writer.writeAll(",'");
|
||||
try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
|
||||
try writer.writeAll("':");
|
||||
try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
|
||||
}
|
||||
};
|
||||
pub const map_to_js_object = brk: {
|
||||
var count: usize = 2;
|
||||
for (map) |item, i| {
|
||||
var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
|
||||
count += std.fmt.count("{}", .{fmt});
|
||||
count += @boolToInt(i > 0);
|
||||
}
|
||||
|
||||
var buf: [count]u8 = undefined;
|
||||
buf[0] = '{';
|
||||
buf[buf.len - 1] = '}';
|
||||
var end: usize = 1;
|
||||
for (map) |item, i| {
|
||||
var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
|
||||
if (i > 0) {
|
||||
buf[end] = ',';
|
||||
end += 1;
|
||||
}
|
||||
end += (std.fmt.bufPrint(buf[end..], "{}", .{fmt}) catch unreachable).len;
|
||||
}
|
||||
|
||||
break :brk buf;
|
||||
};
|
||||
|
||||
pub fn isFloatingPoint(this: ABIType) bool {
|
||||
return switch (this) {
|
||||
|
||||
@@ -2882,6 +2882,9 @@ pub fn wrapWithHasContainer(
|
||||
};
|
||||
args[i] = val;
|
||||
},
|
||||
?JSValue => {
|
||||
args[i] = iter.protectEatNext();
|
||||
},
|
||||
else => @compileError("Unexpected Type " ++ @typeName(ArgType)),
|
||||
}
|
||||
}
|
||||
@@ -2921,3 +2924,51 @@ pub fn wrapWithHasContainer(
|
||||
}
|
||||
}.callback;
|
||||
}
|
||||
|
||||
pub fn cachedBoundFunction(comptime name: [:0]const u8, comptime callback: anytype) (fn (
|
||||
_: void,
|
||||
ctx: js.JSContextRef,
|
||||
_: js.JSValueRef,
|
||||
_: js.JSStringRef,
|
||||
_: js.ExceptionRef,
|
||||
) js.JSValueRef) {
|
||||
return struct {
|
||||
const name_ = name;
|
||||
pub fn call(
|
||||
arg2: js.JSContextRef,
|
||||
arg3: js.JSObjectRef,
|
||||
arg4: js.JSObjectRef,
|
||||
arg5: usize,
|
||||
arg6: [*c]const js.JSValueRef,
|
||||
arg7: js.ExceptionRef,
|
||||
) callconv(.C) js.JSObjectRef {
|
||||
return callback(
|
||||
{},
|
||||
arg2,
|
||||
arg3,
|
||||
arg4,
|
||||
arg6[0..arg5],
|
||||
arg7,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
_: void,
|
||||
ctx: js.JSContextRef,
|
||||
_: js.JSValueRef,
|
||||
_: js.JSStringRef,
|
||||
_: js.ExceptionRef,
|
||||
) js.JSValueRef {
|
||||
const name_slice = std.mem.span(name_);
|
||||
var existing = ctx.ptr().getCachedObject(&ZigString.init(name_slice));
|
||||
if (existing.isEmpty()) {
|
||||
return ctx.ptr().putCachedObject(
|
||||
&ZigString.init(name_slice),
|
||||
JSValue.fromRef(JSC.C.JSObjectMakeFunctionWithCallback(ctx, JSC.C.JSStringCreateStatic(name_slice.ptr, name_slice.len), call)),
|
||||
).asObjectRef();
|
||||
}
|
||||
|
||||
return existing.asObjectRef();
|
||||
}
|
||||
}.getter;
|
||||
}
|
||||
|
||||
7
src/javascript/jsc/ffi.exports.js
Normal file
7
src/javascript/jsc/ffi.exports.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export const ptr = globalThis.Bun.FFI.ptr;
|
||||
export const toBuffer = globalThis.Bun.FFI.toBuffer;
|
||||
export const toArrayBuffer = globalThis.Bun.FFI.toArrayBuffer;
|
||||
export const CString = globalThis.Bun.FFI.CString;
|
||||
export const dlopen = globalThis.Bun.FFI.dlopen;
|
||||
export const viewSource = globalThis.Bun.FFI.viewSource;
|
||||
// --- FFIType ---
|
||||
@@ -1078,6 +1078,14 @@ pub const VirtualMachine = struct {
|
||||
.source_url = ZigString.init("node:path"),
|
||||
.hash = 0,
|
||||
};
|
||||
} else if (strings.eqlComptime(_specifier, "bun:ffi")) {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = ZigString.init(@embedFile("ffi.exports.js") ++ "export const FFIType = " ++ JSC.FFI.ABIType.map_to_js_object ++ ";\n"),
|
||||
.specifier = ZigString.init("bun:ffi"),
|
||||
.source_url = ZigString.init("bun:ffi"),
|
||||
.hash = 0,
|
||||
};
|
||||
}
|
||||
|
||||
const specifier = normalizeSpecifier(_specifier);
|
||||
@@ -1303,11 +1311,14 @@ pub const VirtualMachine = struct {
|
||||
ret.result = null;
|
||||
ret.path = "node:fs";
|
||||
return;
|
||||
}
|
||||
if (strings.eqlComptime(specifier, "node:path")) {
|
||||
} else if (strings.eqlComptime(specifier, "node:path")) {
|
||||
ret.result = null;
|
||||
ret.path = "node:path";
|
||||
return;
|
||||
} else if (strings.eqlComptime(specifier, "bun:ffi")) {
|
||||
ret.result = null;
|
||||
ret.path = "bun:ffi";
|
||||
return;
|
||||
}
|
||||
|
||||
const is_special_source = strings.eqlComptime(source, main_file_name) or js_ast.Macro.isMacroPath(source);
|
||||
|
||||
@@ -318,12 +318,15 @@ pub const Linker = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (import_record.path.text.len > 4 and strings.eqlComptime(import_record.path.text[0.."bun:".len], "bun:")) {
|
||||
if (import_record.path.text.len > 4 and strings.eqlComptimeIgnoreLen(import_record.path.text[0.."bun:".len], "bun:")) {
|
||||
import_record.path = Fs.Path.init(import_record.path.text["bun:".len..]);
|
||||
import_record.path.namespace = "bun";
|
||||
if (strings.eqlComptime(import_record.path.text, "test")) {
|
||||
if (strings.eqlComptime(import_record.path.text, "ffi")) {
|
||||
import_record.path.text = "bun:ffi";
|
||||
} else if (strings.eqlComptime(import_record.path.text, "test")) {
|
||||
import_record.tag = .bun_test;
|
||||
}
|
||||
|
||||
// don't link bun
|
||||
continue;
|
||||
}
|
||||
|
||||
2
types/bun/bun.d.ts
vendored
2
types/bun/bun.d.ts
vendored
@@ -986,6 +986,8 @@ declare module "bun" {
|
||||
*/
|
||||
static readonly byteLength: 32;
|
||||
}
|
||||
|
||||
export const FFI: typeof import("bun:ffi");
|
||||
}
|
||||
|
||||
type TypedArray =
|
||||
|
||||
523
types/bun/ffi.d.ts
vendored
Normal file
523
types/bun/ffi.d.ts
vendored
Normal file
@@ -0,0 +1,523 @@
|
||||
/**
|
||||
* `bun:ffi` lets you efficiently call C functions & FFI functions from JavaScript
|
||||
* without writing any C code yourself.
|
||||
*
|
||||
* ```js
|
||||
* import {dlopen, CString, ptr} from 'bun:ffi';
|
||||
*
|
||||
* const lib = dlopen('libsqlite3', {});
|
||||
*
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* This is powered by just-in-time compiling C wrappers
|
||||
* that convert JavaScript types to C types and back. Internally,
|
||||
* bun uses [tinycc](https://github.com/TinyCC/tinycc), so a big thanks
|
||||
* goes to Fabrice Bellard and TinyCC maintainers for making this possible.
|
||||
*
|
||||
*/
|
||||
declare module "bun:ffi" {
|
||||
export enum FFIType {
|
||||
char = 0,
|
||||
/**
|
||||
* 8-bit signed integer
|
||||
*
|
||||
* Must be a value between -127 and 127
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* signed char
|
||||
* char // on x64 & aarch64 macOS
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
int8_t = 1,
|
||||
/**
|
||||
* 8-bit signed integer
|
||||
*
|
||||
* Must be a value between -127 and 127
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* signed char
|
||||
* char // on x64 & aarch64 macOS
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
i8 = 1,
|
||||
|
||||
/**
|
||||
* 8-bit unsigned integer
|
||||
*
|
||||
* Must be a value between 0 and 255
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* unsigned char
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
uint8_t = 2,
|
||||
/**
|
||||
* 8-bit unsigned integer
|
||||
*
|
||||
* Must be a value between 0 and 255
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* unsigned char
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
u8 = 2,
|
||||
|
||||
/**
|
||||
* 16-bit signed integer
|
||||
*
|
||||
* Must be a value between -32768 and 32767
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* in16_t
|
||||
* short // on arm64 & x64
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
int16_t = 3,
|
||||
/**
|
||||
* 16-bit signed integer
|
||||
*
|
||||
* Must be a value between -32768 and 32767
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* in16_t
|
||||
* short // on arm64 & x64
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
i16 = 3,
|
||||
|
||||
/**
|
||||
* 16-bit unsigned integer
|
||||
*
|
||||
* Must be a value between 0 and 65535, inclusive.
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* uint16_t
|
||||
* unsigned short // on arm64 & x64
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
uint16_t = 4,
|
||||
/**
|
||||
* 16-bit unsigned integer
|
||||
*
|
||||
* Must be a value between 0 and 65535, inclusive.
|
||||
*
|
||||
* When passing to a FFI function (C ABI), type coercsion is not performed.
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* uint16_t
|
||||
* unsigned short // on arm64 & x64
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* var num = 0;
|
||||
* ```
|
||||
*/
|
||||
u16 = 4,
|
||||
|
||||
/**
|
||||
* 32-bit signed integer
|
||||
*
|
||||
*/
|
||||
int32_t = 5,
|
||||
|
||||
/**
|
||||
* 32-bit signed integer
|
||||
*
|
||||
* Alias of {@link FFIType.int32_t}
|
||||
*/
|
||||
i32 = 5,
|
||||
/**
|
||||
* 32-bit signed integer
|
||||
*
|
||||
* The same as `int` in C
|
||||
*
|
||||
* ```c
|
||||
* int
|
||||
* ```
|
||||
*/
|
||||
int = 5,
|
||||
|
||||
/**
|
||||
* 32-bit unsigned integer
|
||||
*
|
||||
* The same as `unsigned int` in C (on x64 & arm64)
|
||||
*
|
||||
* C:
|
||||
* ```c
|
||||
* unsigned int
|
||||
* ```
|
||||
* JavaScript:
|
||||
* ```js
|
||||
* ptr(new Uint32Array(1))
|
||||
* ```
|
||||
*/
|
||||
uint32_t = 6,
|
||||
/**
|
||||
* 32-bit unsigned integer
|
||||
*
|
||||
* Alias of {@link FFIType.uint32_t}
|
||||
*/
|
||||
u32 = 6,
|
||||
|
||||
/**
|
||||
* int64 is a 64-bit signed integer
|
||||
*
|
||||
* This is not implemented yet!
|
||||
*/
|
||||
int64_t = 7,
|
||||
/**
|
||||
* i64 is a 64-bit signed integer
|
||||
*
|
||||
* This is not implemented yet!
|
||||
*/
|
||||
i64 = 7,
|
||||
|
||||
/**
|
||||
* 64-bit unsigned integer
|
||||
*
|
||||
* This is not implemented yet!
|
||||
*/
|
||||
uint64_t = 8,
|
||||
/**
|
||||
* 64-bit unsigned integer
|
||||
*
|
||||
* This is not implemented yet!
|
||||
*/
|
||||
u64 = 8,
|
||||
|
||||
/**
|
||||
* Doubles are not supported yet!
|
||||
*/
|
||||
double = 9,
|
||||
/**
|
||||
* Doubles are not supported yet!
|
||||
*/
|
||||
f64 = 9,
|
||||
/**
|
||||
* Floats are not supported yet!
|
||||
*/
|
||||
float = 10,
|
||||
/**
|
||||
* Floats are not supported yet!
|
||||
*/
|
||||
f32 = 10,
|
||||
|
||||
/**
|
||||
* Booelan value
|
||||
*
|
||||
* Must be `true` or `false`. `0` and `1` type coercion is not supported.
|
||||
*
|
||||
* In C, this corresponds to:
|
||||
* ```c
|
||||
* bool
|
||||
* _Bool
|
||||
* ```
|
||||
*
|
||||
*
|
||||
*/
|
||||
bool = 11,
|
||||
|
||||
/**
|
||||
* Pointer value
|
||||
*
|
||||
* See {@link Bun.FFI.ptr} for more information
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* void*
|
||||
* ```
|
||||
*
|
||||
* In JavaScript:
|
||||
* ```js
|
||||
* ptr(new Uint8Array(1))
|
||||
* ```
|
||||
*/
|
||||
ptr = 12,
|
||||
/**
|
||||
* Pointer value
|
||||
*
|
||||
* alias of {@link FFIType.ptr}
|
||||
*/
|
||||
pointer = 12,
|
||||
|
||||
/**
|
||||
* void value
|
||||
*
|
||||
* void arguments are not supported
|
||||
*
|
||||
* void return type is the default return type
|
||||
*
|
||||
* In C:
|
||||
* ```c
|
||||
* void
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
void = 13,
|
||||
}
|
||||
|
||||
type Symbols = Record<
|
||||
string,
|
||||
{
|
||||
/**
|
||||
* Arguments to a FFI function (C ABI)
|
||||
*
|
||||
* Defaults to an empty array, which means no arguments.
|
||||
*
|
||||
* To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
|
||||
*
|
||||
* @example
|
||||
* From JavaScript:
|
||||
* ```js
|
||||
* const lib = dlopen('add', {
|
||||
* // FFIType can be used or you can pass string labels.
|
||||
* args: [FFIType.i32, "i32"],
|
||||
* return_type: "i32",
|
||||
* });
|
||||
* lib.symbols.add(1, 2)
|
||||
* ```
|
||||
* In C:
|
||||
* ```c
|
||||
* int add(int a, int b) {
|
||||
* return a + b;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
args?: FFIType[];
|
||||
/**
|
||||
* Return type to a FFI function (C ABI)
|
||||
*
|
||||
* Defaults to an empty array, which means no arguments.
|
||||
*
|
||||
* To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
|
||||
*
|
||||
* @example
|
||||
* From JavaScript:
|
||||
* ```js
|
||||
* const lib = dlopen('add', {
|
||||
* // FFIType can be used or you can pass string labels.
|
||||
* args: [FFIType.i32, "i32"],
|
||||
* return_type: "i32",
|
||||
* });
|
||||
* lib.symbols.add(1, 2)
|
||||
* ```
|
||||
* In C:
|
||||
* ```c
|
||||
* int add(int a, int b) {
|
||||
* return a + b;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
return_type?: FFIType;
|
||||
}
|
||||
>;
|
||||
|
||||
export interface Library {
|
||||
symbols: Record<string, CallableFunction>;
|
||||
|
||||
/**
|
||||
* `dlclose` the library, unloading the symbols and freeing memory allocated.
|
||||
*
|
||||
* Once called, the library is no longer usable.
|
||||
*
|
||||
* Calling a function from a library that has been closed is undefined behavior.
|
||||
*/
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export function dlopen(libraryName: string, symbols: Symbols): Library<T>;
|
||||
|
||||
/**
|
||||
* Read a pointer as a {@link Buffer}
|
||||
*
|
||||
* If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
|
||||
*
|
||||
* @param ptr The memory address to read
|
||||
* @param byteOffset bytes to skip before reading
|
||||
* @param byteLength bytes to read
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*
|
||||
*/
|
||||
export function toBuffer(
|
||||
ptr: number,
|
||||
byteOffset?: number,
|
||||
byteLength?: number
|
||||
): Buffer;
|
||||
|
||||
/**
|
||||
* Read a pointer as an {@link ArrayBuffer}
|
||||
*
|
||||
* If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
|
||||
*
|
||||
* @param ptr The memory address to read
|
||||
* @param byteOffset bytes to skip before reading
|
||||
* @param byteLength bytes to read
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
export function toArrayBuffer(
|
||||
ptr: number,
|
||||
byteOffset?: number,
|
||||
byteLength?: number
|
||||
): ArrayBuffer;
|
||||
|
||||
/**
|
||||
* Get the pointer backing a {@link TypedArray} or {@link ArrayBuffer}
|
||||
*
|
||||
* Use this to pass {@link TypedArray} or {@link ArrayBuffer} to C functions.
|
||||
*
|
||||
* This is for use with FFI functions. For performance reasons, FFI will
|
||||
* not automatically convert typed arrays to C pointers.
|
||||
*
|
||||
* @param {TypedArray|ArrayBuffer|DataView} view the typed array or array buffer to get the pointer for
|
||||
* @param {number} byteOffset optional offset into the view in bytes
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* From JavaScript:
|
||||
* ```js
|
||||
* const array = new Uint8Array(10);
|
||||
* const rawPtr = ptr(array);
|
||||
* myCFunction(rawPtr);
|
||||
* ```
|
||||
* To C:
|
||||
* ```c
|
||||
* void myCFunction(char* rawPtr) {
|
||||
* // Do something with rawPtr
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
export function ptr(
|
||||
view: TypedArray | ArrayBufferLike | DataView,
|
||||
byteOffset?: number
|
||||
): number;
|
||||
|
||||
/**
|
||||
* Get a string from a UTF-8 encoded C string
|
||||
* If `byteLength` is not provided, the string is assumed to be null-terminated.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* console.log(new CString(ptr));
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* // print the first 4 characters
|
||||
* console.log(new CString(ptr, 0, 4));
|
||||
* ```
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
|
||||
export class CString extends String {
|
||||
/**
|
||||
* Get a string from a UTF-8 encoded C string
|
||||
* If `byteLength` is not provided, the string is assumed to be null-terminated.
|
||||
*
|
||||
* @param ptr The pointer to the C string
|
||||
* @param byteOffset bytes to skip before reading
|
||||
* @param byteLength bytes to read
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* console.log(new CString(ptr));
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* var ptr = lib.symbols.getVersion();
|
||||
* // print the first 4 characters
|
||||
* console.log(new CString(ptr, 0, 4));
|
||||
* ```
|
||||
*
|
||||
* While there are some checks to catch invalid pointers, this is a difficult
|
||||
* thing to do safely. Passing an invalid pointer can crash the program and
|
||||
* reading beyond the bounds of the pointer will crash the program or cause
|
||||
* undefined behavior. Use with care!
|
||||
*/
|
||||
constructor(ptr: number, byteOffset?: number, byteLength?: number): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* View the generated C code for FFI bindings
|
||||
*
|
||||
* You probably won't need this unless there's a bug in the FFI bindings
|
||||
* generator or you're just curious.
|
||||
*/
|
||||
export function viewSource(symbols: Symbols): string[];
|
||||
}
|
||||
1
types/bun/index.d.ts
vendored
1
types/bun/index.d.ts
vendored
@@ -5,6 +5,7 @@
|
||||
/// <reference no-default-lib="true" />
|
||||
/// <reference lib="esnext" />
|
||||
/// <reference path="./bun.d.ts" />
|
||||
/// <reference path="./ffi.d.ts" />
|
||||
/// <reference path="./fs.d.ts" />
|
||||
/// <reference path="./html-rewriter.d.ts" />
|
||||
/// <reference path="./globals.d.ts" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
./bun.d.ts
|
||||
./ffi.d.ts
|
||||
./fs.d.ts
|
||||
./html-rewriter.d.ts
|
||||
./globals.d.ts
|
||||
|
||||
Reference in New Issue
Block a user