Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions benchmark/ffi/getpid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const common = require('../common.js');
const ffi = require('node:ffi');

const bench = common.createBenchmark(main, {
n: [1e7],
}, {
flags: ['--experimental-ffi'],
});

const { lib, functions } = ffi.dlopen(null, {
uv_os_getpid: { result: 'i32', parameters: [] },
});

const getpid = functions.uv_os_getpid;

function main({ n }) {
bench.start();
for (let i = 0; i < n; ++i)
getpid();
bench.end(n);

lib.close();
}
18 changes: 18 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,24 @@ added: v14.0.0
Used when a feature that is not available
to the current platform which is running Node.js is used.

<a id="ERR_FFI_INVALID_POINTER"></a>

### `ERR_FFI_INVALID_POINTER`

An invalid pointer was passed to an FFI operation.

<a id="ERR_FFI_LIBRARY_CLOSED"></a>

### `ERR_FFI_LIBRARY_CLOSED`

An operation was attempted on an FFI dynamic library after it was closed.

<a id="ERR_FFI_SYSCALL_FAILED"></a>

### `ERR_FFI_SYSCALL_FAILED`

A low-level FFI call failed.

<a id="ERR_FS_CP_DIR_TO_NON_DIR"></a>

### `ERR_FS_CP_DIR_TO_NON_DIR`
Expand Down
59 changes: 57 additions & 2 deletions doc/api/ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,15 @@ const path = `libsqlite3.${suffix}`;
added: REPLACEME
-->

* `path` {string} Path to a dynamic library.
* `path` {string|null} Path to a dynamic library, or `null` to resolve symbols
from the current process image.
* `definitions` {Object} Symbol definitions to resolve immediately.
* Returns: {Object}

Loads a dynamic library and resolves the requested function definitions.

On Windows passing `null` is not supported.

When `definitions` is omitted, `functions` is returned as an empty object until
symbols are resolved explicitly.

Expand Down Expand Up @@ -237,10 +240,13 @@ Represents a loaded dynamic library.

### `new DynamicLibrary(path)`

* `path` {string} Path to a dynamic library.
* `path` {string|null} Path to a dynamic library, or `null` to resolve symbols
from the current process image.

Loads the dynamic library without resolving any functions eagerly.

On Windows passing `null` is not supported.

```cjs
const { DynamicLibrary } = require('node:ffi');

Expand Down Expand Up @@ -603,6 +609,55 @@ available storage. This function does not allocate memory on its own.

`buffer` must be a Node.js `Buffer`.

## `ffi.exportArrayBuffer(arrayBuffer, pointer, length)`

<!-- YAML
added: REPLACEME
-->

* `arrayBuffer` {ArrayBuffer}
* `pointer` {bigint}
* `length` {number}

Copies bytes from an `ArrayBuffer` into native memory.

`length` must be at least `arrayBuffer.byteLength`.

`pointer` must refer to writable native memory with at least `length` bytes of
available storage. This function does not allocate memory on its own.

## `ffi.exportArrayBufferView(arrayBufferView, pointer, length)`

<!-- YAML
added: REPLACEME
-->

* `arrayBufferView` {ArrayBufferView}
* `pointer` {bigint}
* `length` {number}

Copies bytes from an `ArrayBufferView` into native memory.

`length` must be at least `arrayBufferView.byteLength`.

`pointer` must refer to writable native memory with at least `length` bytes of
available storage. This function does not allocate memory on its own.

## `ffi.getRawPointer(source)`

<!-- YAML
added: REPLACEME
-->

* `source` {Buffer|ArrayBuffer|ArrayBufferView}
* Returns: {bigint}

Returns the raw memory address of JavaScript-managed byte storage.

This is unsafe and dangerous. The returned pointer can become invalid if the
underlying memory is detached, resized, transferred, or otherwise invalidated.
Using stale pointers can cause memory corruption or process crashes.

## Safety notes

The `node:ffi` module does not track pointer validity, memory ownership, or
Expand Down
54 changes: 47 additions & 7 deletions lib/ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

const {
ObjectFreeze,
ObjectPrototypeToString,
} = primordials;
const { Buffer } = require('buffer');
const { emitExperimentalWarning } = require('internal/util');
const {
isArrayBufferView,
} = require('internal/util/types');
const {
codes: {
ERR_ACCESS_DENIED,
Expand Down Expand Up @@ -32,6 +36,8 @@ const {
getUint64,
getFloat32,
getFloat64,
exportBytes,
getRawPointer,
setInt8,
setUint8,
setInt16,
Expand Down Expand Up @@ -114,21 +120,52 @@ function exportString(str, data, len, encoding = 'utf8') {
targetBuffer.fill(0, dataLength, dataLength + terminatorSize);
}

function exportBuffer(buffer, data, len) {
function exportBuffer(source, data, len) {
checkFFIPermission();

if (!Buffer.isBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', 'Buffer', buffer);
if (!Buffer.isBuffer(source)) {
throw new ERR_INVALID_ARG_TYPE('buffer', 'Buffer', source);
}

validateInteger(len, 'len', 0);

if (len < buffer.length) {
throw new ERR_OUT_OF_RANGE('len', `>= ${buffer.length}`, len);
if (len < source.length) {
throw new ERR_OUT_OF_RANGE('len', `>= ${source.length}`, len);
}

const targetBuffer = toBuffer(data, len, false);
buffer.copy(targetBuffer, 0, 0, buffer.length);
exportBytes(source, data, len);
}

function exportArrayBuffer(source, data, len) {
checkFFIPermission();

if (ObjectPrototypeToString(source) !== '[object ArrayBuffer]') {
throw new ERR_INVALID_ARG_TYPE('arrayBuffer', 'ArrayBuffer', source);
}

validateInteger(len, 'len', 0);

if (len < source.byteLength) {
throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
}

exportBytes(source, data, len);
}

function exportArrayBufferView(source, data, len) {
checkFFIPermission();

if (!isArrayBufferView(source)) {
throw new ERR_INVALID_ARG_TYPE('arrayBufferView', 'ArrayBufferView', source);
}

validateInteger(len, 'len', 0);

if (len < source.byteLength) {
throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
}

exportBytes(source, data, len);
}

const suffix = process.platform === 'win32' ? 'dll' : process.platform === 'darwin' ? 'dylib' : 'so';
Expand Down Expand Up @@ -163,6 +200,8 @@ module.exports = {
dlopen,
dlclose,
dlsym,
exportArrayBuffer,
exportArrayBufferView,
exportString,
exportBuffer,
getInt8,
Expand All @@ -175,6 +214,7 @@ module.exports = {
getUint64,
getFloat32,
getFloat64,
getRawPointer,
setInt8,
setUint8,
setInt16,
Expand Down
Loading
Loading