Skip to content

Commit 1fee1dd

Browse files
committed
lib: add new methods and error codes
Signed-off-by: Paolo Insogna <paolo@cowtech.it>
1 parent 6909ea6 commit 1fee1dd

File tree

13 files changed

+477
-86
lines changed

13 files changed

+477
-86
lines changed

benchmark/ffi/getpid.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const ffi = require('node:ffi');
5+
6+
const bench = common.createBenchmark(main, {
7+
n: [1e7],
8+
}, {
9+
flags: ['--experimental-ffi'],
10+
});
11+
12+
const { lib, functions } = ffi.dlopen(null, {
13+
uv_os_getpid: { result: 'i32', parameters: [] },
14+
});
15+
16+
const getpid = functions.uv_os_getpid;
17+
18+
function main({ n }) {
19+
bench.start();
20+
for (let i = 0; i < n; ++i)
21+
getpid();
22+
bench.end(n);
23+
24+
lib.close();
25+
}

doc/api/errors.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,6 +1332,24 @@ added: v14.0.0
13321332
Used when a feature that is not available
13331333
to the current platform which is running Node.js is used.
13341334

1335+
<a id="ERR_FFI_INVALID_POINTER"></a>
1336+
1337+
### `ERR_FFI_INVALID_POINTER`
1338+
1339+
An invalid pointer was passed to an FFI operation.
1340+
1341+
<a id="ERR_FFI_LIBRARY_CLOSED"></a>
1342+
1343+
### `ERR_FFI_LIBRARY_CLOSED`
1344+
1345+
An operation was attempted on an FFI dynamic library after it was closed.
1346+
1347+
<a id="ERR_FFI_SYSCALL_FAILED"></a>
1348+
1349+
### `ERR_FFI_SYSCALL_FAILED`
1350+
1351+
A low-level FFI call failed.
1352+
13351353
<a id="ERR_FS_CP_DIR_TO_NON_DIR"></a>
13361354

13371355
### `ERR_FS_CP_DIR_TO_NON_DIR`

doc/api/ffi.md

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,15 @@ const path = `libsqlite3.${suffix}`;
165165
added: REPLACEME
166166
-->
167167

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

172173
Loads a dynamic library and resolves the requested function definitions.
173174

175+
On Windows passing `null` is not supported.
176+
174177
When `definitions` is omitted, `functions` is returned as an empty object until
175178
symbols are resolved explicitly.
176179

@@ -237,10 +240,13 @@ Represents a loaded dynamic library.
237240

238241
### `new DynamicLibrary(path)`
239242

240-
* `path` {string} Path to a dynamic library.
243+
* `path` {string|null} Path to a dynamic library, or `null` to resolve symbols
244+
from the current process image.
241245

242246
Loads the dynamic library without resolving any functions eagerly.
243247

248+
On Windows passing `null` is not supported.
249+
244250
```cjs
245251
const { DynamicLibrary } = require('node:ffi');
246252

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

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

612+
## `ffi.exportArrayBuffer(arrayBuffer, pointer, length)`
613+
614+
<!-- YAML
615+
added: REPLACEME
616+
-->
617+
618+
* `arrayBuffer` {ArrayBuffer}
619+
* `pointer` {bigint}
620+
* `length` {number}
621+
622+
Copies bytes from an `ArrayBuffer` into native memory.
623+
624+
`length` must be at least `arrayBuffer.byteLength`.
625+
626+
`pointer` must refer to writable native memory with at least `length` bytes of
627+
available storage. This function does not allocate memory on its own.
628+
629+
## `ffi.exportArrayBufferView(arrayBufferView, pointer, length)`
630+
631+
<!-- YAML
632+
added: REPLACEME
633+
-->
634+
635+
* `arrayBufferView` {ArrayBufferView}
636+
* `pointer` {bigint}
637+
* `length` {number}
638+
639+
Copies bytes from an `ArrayBufferView` into native memory.
640+
641+
`length` must be at least `arrayBufferView.byteLength`.
642+
643+
`pointer` must refer to writable native memory with at least `length` bytes of
644+
available storage. This function does not allocate memory on its own.
645+
646+
## `ffi.getRawPointer(source)`
647+
648+
<!-- YAML
649+
added: REPLACEME
650+
-->
651+
652+
* `source` {Buffer|ArrayBuffer|ArrayBufferView}
653+
* Returns: {bigint}
654+
655+
Returns the raw memory address of JavaScript-managed byte storage.
656+
657+
This is unsafe and dangerous. The returned pointer can become invalid if the
658+
underlying memory is detached, resized, transferred, or otherwise invalidated.
659+
Using stale pointers can cause memory corruption or process crashes.
660+
606661
## Safety notes
607662

608663
The `node:ffi` module does not track pointer validity, memory ownership, or

lib/ffi.js

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

33
const {
44
ObjectFreeze,
5+
ObjectPrototypeToString,
56
} = primordials;
67
const { Buffer } = require('buffer');
78
const { emitExperimentalWarning } = require('internal/util');
9+
const {
10+
isArrayBufferView,
11+
} = require('internal/util/types');
812
const {
913
codes: {
1014
ERR_ACCESS_DENIED,
@@ -32,6 +36,8 @@ const {
3236
getUint64,
3337
getFloat32,
3438
getFloat64,
39+
exportBytes,
40+
getRawPointer,
3541
setInt8,
3642
setUint8,
3743
setInt16,
@@ -114,21 +120,52 @@ function exportString(str, data, len, encoding = 'utf8') {
114120
targetBuffer.fill(0, dataLength, dataLength + terminatorSize);
115121
}
116122

117-
function exportBuffer(buffer, data, len) {
123+
function exportBuffer(source, data, len) {
118124
checkFFIPermission();
119125

120-
if (!Buffer.isBuffer(buffer)) {
121-
throw new ERR_INVALID_ARG_TYPE('buffer', 'Buffer', buffer);
126+
if (!Buffer.isBuffer(source)) {
127+
throw new ERR_INVALID_ARG_TYPE('buffer', 'Buffer', source);
122128
}
123129

124130
validateInteger(len, 'len', 0);
125131

126-
if (len < buffer.length) {
127-
throw new ERR_OUT_OF_RANGE('len', `>= ${buffer.length}`, len);
132+
if (len < source.length) {
133+
throw new ERR_OUT_OF_RANGE('len', `>= ${source.length}`, len);
128134
}
129135

130-
const targetBuffer = toBuffer(data, len, false);
131-
buffer.copy(targetBuffer, 0, 0, buffer.length);
136+
exportBytes(source, data, len);
137+
}
138+
139+
function exportArrayBuffer(source, data, len) {
140+
checkFFIPermission();
141+
142+
if (ObjectPrototypeToString(source) !== '[object ArrayBuffer]') {
143+
throw new ERR_INVALID_ARG_TYPE('arrayBuffer', 'ArrayBuffer', source);
144+
}
145+
146+
validateInteger(len, 'len', 0);
147+
148+
if (len < source.byteLength) {
149+
throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
150+
}
151+
152+
exportBytes(source, data, len);
153+
}
154+
155+
function exportArrayBufferView(source, data, len) {
156+
checkFFIPermission();
157+
158+
if (!isArrayBufferView(source)) {
159+
throw new ERR_INVALID_ARG_TYPE('arrayBufferView', 'ArrayBufferView', source);
160+
}
161+
162+
validateInteger(len, 'len', 0);
163+
164+
if (len < source.byteLength) {
165+
throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
166+
}
167+
168+
exportBytes(source, data, len);
132169
}
133170

134171
const suffix = process.platform === 'win32' ? 'dll' : process.platform === 'darwin' ? 'dylib' : 'so';
@@ -163,6 +200,8 @@ module.exports = {
163200
dlopen,
164201
dlclose,
165202
dlsym,
203+
exportArrayBuffer,
204+
exportArrayBufferView,
166205
exportString,
167206
exportBuffer,
168207
getInt8,
@@ -175,6 +214,7 @@ module.exports = {
175214
getUint64,
176215
getFloat32,
177216
getFloat64,
217+
getRawPointer,
178218
setInt8,
179219
setUint8,
180220
setInt16,

0 commit comments

Comments
 (0)