Skip to content

Commit 570ba85

Browse files
committed
lib,src,test,doc: various improvements
Signed-off-by: Paolo Insogna <paolo@cowtech.it>
1 parent da7cdbf commit 570ba85

40 files changed

Lines changed: 1880 additions & 974 deletions

Makefile

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ testclean: ## Remove test artifacts.
222222
# Next one is legacy remove this at some point
223223
$(RM) -r test/tmp*
224224
$(RM) -r test/.tmp*
225+
$(RM) test/ffi/.buildstamp
226+
$(RM) -r test/ffi/fixture_library/build
225227

226228
.PHONY: distclean
227229
.NOTPARALLEL: distclean
@@ -542,24 +544,6 @@ else
542544
build-sqlite-tests:
543545
endif
544546

545-
FFI_BINDING_GYPS := $(wildcard test/ffi/*/binding.gyp)
546-
547-
FFI_BINDING_SOURCES := \
548-
$(wildcard test/ffi/*/*.c) \
549-
$(wildcard test/ffi/*/*.def)
550-
551-
# Implicitly depends on $(NODE_EXE), see the build-ffi-tests rule for rationale.
552-
test/ffi/.buildstamp: $(ADDONS_PREREQS) \
553-
$(FFI_BINDING_GYPS) $(FFI_BINDING_SOURCES)
554-
@$(call run_build_addons,"$$PWD/test/ffi",$@)
555-
556-
.PHONY: build-ffi-tests
557-
# .buildstamp needs $(NODE_EXE) but cannot depend on it
558-
# directly because it calls make recursively. The parent make cannot know
559-
# if the subprocess touched anything so it pessimistically assumes that
560-
# .buildstamp is out of date and need a rebuild.
561-
build-ffi-tests: | $(NODE_EXE) test/ffi/.buildstamp ## Build FFI tests.
562-
563547
.PHONY: clear-stalled
564548
clear-stalled: ## Clear any stalled processes.
565549
$(info Clean up any leftover processes but don't error if found.)
@@ -570,7 +554,7 @@ clear-stalled: ## Clear any stalled processes.
570554
fi
571555

572556
.PHONY: test-build
573-
test-build: | all build-addons build-js-native-api-tests build-node-api-tests build-sqlite-tests build-ffi-tests ## Build all tests.
557+
test-build: | all build-addons build-js-native-api-tests build-node-api-tests build-sqlite-tests ## Build all tests.
574558

575559
.PHONY: test-build-js-native-api
576560
test-build-js-native-api: all build-js-native-api-tests ## Build JS Native-API tests.
@@ -595,7 +579,7 @@ test-all-suites: | clear-stalled test-build bench-addons-build doc-only ## Run a
595579
$(PYTHON) tools/test.py $(PARALLEL_ARGS) --mode=$(BUILDTYPE_LOWER) test/*
596580

597581
JS_SUITES ?= default
598-
NATIVE_SUITES ?= addons js-native-api node-api embedding
582+
NATIVE_SUITES ?= addons js-native-api node-api ffi embedding
599583
# CI_* variables should be kept synchronized with the ones in vcbuild.bat
600584
CI_NATIVE_SUITES ?= $(NATIVE_SUITES) benchmark
601585
CI_JS_SUITES ?= $(JS_SUITES) pummel
@@ -632,7 +616,7 @@ test-ci-js: | clear-stalled ## Build and test JavaScript with building anything
632616
.PHONY: test-ci
633617
# Related CI jobs: most CI tests, excluding node-test-commit-arm-fanned
634618
test-ci: LOGLEVEL := info ## Build and test everything (CI).
635-
test-ci: | clear-stalled bench-addons-build build-addons build-js-native-api-tests build-node-api-tests build-sqlite-tests build-ffi-tests doc-only
619+
test-ci: | clear-stalled bench-addons-build build-addons build-js-native-api-tests build-node-api-tests build-sqlite-tests doc-only
636620
out/Release/cctest --gtest_output=xml:out/junit/cctest.xml
637621
$(PYTHON) tools/test.py $(PARALLEL_ARGS) -p tap --logfile test.tap \
638622
--mode=$(BUILDTYPE_LOWER) --flaky-tests=$(FLAKY_TESTS) \

configure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2215,7 +2215,7 @@ def bundled_ffi_supported(os_name, target_arch):
22152215
'freebsd': {'arm', 'arm64', 'x64'},
22162216
'linux': {'arm', 'arm64', 'x64'},
22172217
'mac': {'arm64', 'x64'},
2218-
'win': {'arm', 'arm64', 'x64'},
2218+
'win': {'arm64', 'x64'},
22192219
}
22202220

22212221
if target_arch == 'x86':

deps/libffi/generate-headers.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#!/usr/bin/env python3
22

33
import argparse
4+
import os
5+
import platform
46
import sys
57
from pathlib import Path
68

@@ -175,15 +177,62 @@ def generate_headers(output_dir, target_arch, os_name):
175177
encoding='utf-8')
176178

177179

180+
def detect_os_name():
181+
if sys.platform.startswith('win'):
182+
return 'win'
183+
if sys.platform == 'darwin':
184+
return 'mac'
185+
if sys.platform.startswith('linux'):
186+
return 'linux'
187+
if sys.platform.startswith('freebsd'):
188+
return 'freebsd'
189+
raise ValueError(f'Unsupported host platform {sys.platform!r}')
190+
191+
192+
def detect_target_arch():
193+
candidates = [
194+
os.environ.get('TARGET_ARCH'),
195+
os.environ.get('npm_config_arch'),
196+
os.environ.get('VSCMD_ARG_TGT_ARCH'),
197+
os.environ.get('Platform'),
198+
os.environ.get('PROCESSOR_ARCHITECTURE'),
199+
platform.machine(),
200+
]
201+
202+
aliases = {
203+
'amd64': 'x64',
204+
'x86_64': 'x64',
205+
'x64': 'x64',
206+
'win32': 'x64',
207+
'i386': 'x86',
208+
'i686': 'x86',
209+
'x86': 'x86',
210+
'arm64': 'arm64',
211+
'aarch64': 'arm64',
212+
'arm': 'arm',
213+
}
214+
215+
for candidate in candidates:
216+
if not candidate:
217+
continue
218+
normalized = aliases.get(candidate.lower())
219+
if normalized is not None:
220+
return normalized
221+
222+
raise ValueError('Unable to determine target architecture for libffi headers')
223+
224+
178225
def main(argv=None):
179226
parser = argparse.ArgumentParser(description='Generate libffi headers')
180227
parser.add_argument('--output-dir', required=True)
181-
parser.add_argument('--target-arch', required=True)
182-
parser.add_argument('--os', required=True)
228+
parser.add_argument('--target-arch')
229+
parser.add_argument('--os')
183230
args = parser.parse_args(argv)
184231

185232
try:
186-
generate_headers(args.output_dir, args.target_arch, args.os)
233+
generate_headers(args.output_dir,
234+
args.target_arch or detect_target_arch(),
235+
args.os or detect_os_name())
187236
except Exception as exc: # pylint: disable=broad-except
188237
print(exc, file=sys.stderr)
189238
return 1

deps/libffi/libffi.gyp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'src/java_raw_api.c',
77
'src/prep_cif.c',
88
'src/raw_api.c',
9+
'src/tramp.c',
910
'src/types.c',
1011
],
1112
'libffi_defines%': [],
@@ -137,8 +138,10 @@
137138
'<(python)',
138139
'generate-headers.py',
139140
'--output-dir=<(INTERMEDIATE_DIR)',
140-
'--target-arch=<(target_arch)',
141-
'--os=<(OS)',
141+
'--target-arch',
142+
'<(target_arch)',
143+
'--os',
144+
'<(OS)',
142145
],
143146
},
144147
],

doc/api/cli.md

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ native addons by default.
111111
Attempts to do so will throw an `ERR_DLOPEN_DISABLED` unless the
112112
user explicitly passes the `--allow-addons` flag when starting Node.js.
113113

114-
Example:
114+
Example without `--allow-child-process`:
115115

116116
```cjs
117117
// Attempt to require an native addon
@@ -160,7 +160,7 @@ child process by default.
160160
Attempts to do so will throw an `ERR_ACCESS_DENIED` unless the
161161
user explicitly passes the `--allow-child-process` flag when starting Node.js.
162162

163-
Example:
163+
Example without `--allow-ffi`:
164164

165165
```js
166166
const childProcess = require('node:child_process');
@@ -213,7 +213,7 @@ const lib = new DynamicLibrary('mylib.so');
213213
```
214214

215215
```console
216-
$ node --permission --experimental-ffi --allow-fs-read=* index.js
216+
$ node --permission --experimental-ffi index.js
217217
Error: Access to this API has been restricted. Use --allow-ffi to manage permissions.
218218
at node:internal/main/run_main_module:17:47 {
219219
code: 'ERR_ACCESS_DENIED',
@@ -1982,16 +1982,6 @@ changes:
19821982
19831983
Legacy alias for [`--no-require-module`][].
19841984

1985-
### `--no-experimental-ffi`
1986-
1987-
<!-- YAML
1988-
added: REPLACEME
1989-
-->
1990-
1991-
Disable the experimental [`node:ffi`][] module.
1992-
1993-
This flag is only available in builds with FFI support.
1994-
19951985
### `--no-experimental-sqlite`
19961986

19971987
<!-- YAML
@@ -2215,7 +2205,7 @@ following permissions are restricted:
22152205
* Worker Threads - manageable through [`--allow-worker`][] flag
22162206
* WASI - manageable through [`--allow-wasi`][] flag
22172207
* Addons - manageable through [`--allow-addons`][] flag
2218-
* FFI - manageable through \[`--allow-ffi`]\[] flag
2208+
* FFI - manageable through [`--allow-ffi`](#--allow-ffi) flag
22192209

22202210
### `--permission-audit`
22212211

@@ -3696,7 +3686,6 @@ one is included in the list below.
36963686
* `--no-addons`
36973687
* `--no-async-context-frame`
36983688
* `--no-deprecation`
3699-
* `--no-experimental-ffi`
37003689
* `--no-experimental-global-navigator`
37013690
* `--no-experimental-repl-await`
37023691
* `--no-experimental-sqlite`

doc/api/ffi.md

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,18 @@ const ffi = require('node:ffi');
3030
This module is only available under the `node:` scheme in builds with FFI
3131
support and is gated by the `--experimental-ffi` flag.
3232

33-
When using the [Permission Model][], FFI APIs are restricted unless the
34-
[`--allow-ffi`][] flag is provided.
33+
Bundled libffi support currently targets:
34+
35+
* macOS on `arm64` and `x64`
36+
* Windows on `arm64` and `x64`
37+
* FreeBSD on `arm`, `arm64`, and `x64`
38+
* Linux on `arm`, `arm64`, and `x64`
39+
40+
Other targets require building Node.js against a shared libffi with
41+
`--shared-ffi`. The unofficial GN build does not support `node:ffi`.
42+
43+
When using the [Permission Model](permissions.md#permission-model), FFI APIs are
44+
restricted unless the [`--allow-ffi`](cli.md#--allow-ffi) flag is provided.
3545

3646
## Overview
3747

@@ -69,6 +79,16 @@ Supported type names:
6979
Pointer-like types (`pointer`, `string`, `buffer`, `arraybuffer`, and
7080
`function`) are all passed through the native layer as pointers.
7181

82+
When `Buffer`, `ArrayBuffer`, or typed array values are passed as pointer-like
83+
arguments, Node.js borrows a raw pointer to their backing memory for the
84+
duration of the native call. The caller must ensure that backing store remains
85+
valid and stable for the entire call.
86+
87+
It is unsupported and dangerous to resize, transfer, detach, or otherwise
88+
invalidate that backing store while the native call is active, including
89+
through reentrant JavaScript such as FFI callbacks. Doing so may crash the
90+
process, produce incorrect output, or corrupt memory.
91+
7292
The `char` type follows the platform C ABI. On platforms where plain C `char`
7393
is signed it behaves like `i8`; otherwise it behaves like `u8`.
7494

@@ -220,6 +240,10 @@ behavior, is not allowed, and is dangerous: it can crash the process, produce
220240
incorrect output, or corrupt memory. Native code must stop using callback
221241
addresses before the library is closed or before the callback is unregistered.
222242

243+
Calling `library.close()` from one of the library's active callbacks is
244+
unsupported and dangerous. The callback must return before the library is
245+
closed.
246+
223247
### `library.getFunction(name, signature)`
224248

225249
* `name` {string}
@@ -301,13 +325,23 @@ Callbacks are subject to the following restrictions:
301325
* They must not throw exceptions.
302326
* They must not return promises.
303327
* They must return a value compatible with the declared result type.
328+
* They must not call `library.close()` on their owning library while running.
329+
* They must not unregister themselves while running.
330+
331+
Closing the owning library or unregistering the currently executing callback
332+
from inside the callback is unsupported and dangerous. Doing so may crash the
333+
process, produce incorrect output, or corrupt memory.
304334

305335
### `library.unregisterCallback(pointer)`
306336

307337
* `pointer` {bigint}
308338

309339
Releases a callback previously created with `library.registerCallback()`.
310340

341+
Calling `library.unregisterCallback(pointer)` for a callback that is currently
342+
executing is unsupported and dangerous. The callback must return before it is
343+
unregistered.
344+
311345
After `library.unregisterCallback(pointer)` returns, invoking that callback
312346
pointer from native code has undefined behavior, is not allowed, and is
313347
dangerous: it can crash the process, produce incorrect output, or corrupt
@@ -476,7 +510,8 @@ When `copy` is `false`, the returned `ArrayBuffer` references the original
476510
native memory directly.
477511

478512
The same lifetime and bounds requirements described for
479-
[`ffi.toBuffer()`][] apply here. With `copy: false`, the
513+
[`ffi.toBuffer(pointer, length[, copy])`](#ffitobufferpointer-length-copy) apply
514+
here. With `copy: false`, the
480515
returned `ArrayBuffer` is a zero-copy view of foreign memory and is only safe
481516
while that memory remains allocated, unchanged in layout, and valid for the
482517
entire exposed range.
@@ -492,10 +527,12 @@ added: REPLACEME
492527
* `length` {number}
493528
* `encoding` {string} **Default:** `'utf8'`.
494529

495-
Copies a JavaScript string into native memory and appends a trailing NUL byte
496-
when space is available.
530+
Copies a JavaScript string into native memory and appends a trailing NUL
531+
terminator.
497532

498-
If `length` is too small, the string is truncated to fit.
533+
`length` must be large enough to hold the full encoded string plus the trailing
534+
NUL terminator. For UTF-16 and UCS-2 encodings, the trailing terminator uses
535+
two zero bytes.
499536

500537
`pointer` must refer to writable native memory with at least `length` bytes of
501538
available storage. This function does not allocate memory on its own.
@@ -514,8 +551,7 @@ added: REPLACEME
514551

515552
Copies bytes from a `Buffer` into native memory.
516553

517-
If `length` is smaller than `buffer.length`, only the first `length` bytes are
518-
copied.
554+
`length` must be at least `buffer.length`.
519555

520556
`pointer` must refer to writable native memory with at least `length` bytes of
521557
available storage. This function does not allocate memory on its own.
@@ -542,7 +578,3 @@ In particular:
542578

543579
As a general rule, prefer copied values unless zero-copy access is required,
544580
and keep callback and pointer lifetimes explicit on the native side.
545-
546-
[Permission Model]: permissions.md#permission-model
547-
[`--allow-ffi`]: cli.md#--allow-ffi
548-
[`ffi.toBuffer()`]: #ffitobufferpointer-length-copy

doc/api/permissions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ using the [`--allow-child-process`][] and [`--allow-worker`][] respectively.
7171
To allow network access, use [`--allow-net`][] and for allowing native addons
7272
when using permission model, use the [`--allow-addons`][]
7373
flag. For WASI, use the [`--allow-wasi`][] flag. For FFI, use the
74-
[`--allow-ffi`][] flag. The \[`node:ffi`]\[] module also requires the
74+
[`--allow-ffi`][] flag. The [`node:ffi`](ffi.md) module also requires the
7575
`--experimental-ffi` flag and is only available in builds with FFI support.
7676

7777
#### Runtime API

doc/node.1

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ When using the Permission Model, the process will not be able to use
7777
native addons by default.
7878
Attempts to do so will throw an \fBERR_DLOPEN_DISABLED\fR unless the
7979
user explicitly passes the \fB--allow-addons\fR flag when starting Node.js.
80-
Example:
80+
Example without \fB--allow-ffi\fR:
8181
.Bd -literal
8282
// Attempt to require an native addon
8383
require('nodejs-addon-example');
@@ -140,13 +140,15 @@ When using the Permission Model, the process will not be able to use
140140
\fBnode:ffi\fR by default.
141141
Attempts to use FFI APIs will throw an \fBERR_ACCESS_DENIED\fR exception unless
142142
the user explicitly passes the \fB--allow-ffi\fR flag when starting Node.js.
143+
The \fBnode:ffi\fR module also requires the \fB--experimental-ffi\fR flag and
144+
is only available in builds with FFI support.
143145
Example:
144146
.Bd -literal
145147
const { DynamicLibrary } = require('node:ffi');
146148
const lib = new DynamicLibrary('mylib.so');
147149
.Ed
148150
.Bd -literal
149-
$ node --permission --experimental-ffi --allow-fs-read=* index.js
151+
$ node --permission --experimental-ffi index.js
150152
Error: Access to this API has been restricted. Use --allow-ffi to manage permissions.
151153
at node:internal/main/run_main_module:17:47 {
152154
code: 'ERR_ACCESS_DENIED',
@@ -717,6 +719,7 @@ Enable exposition of EventSource Web API on the global scope.
717719
.
718720
.It Fl -experimental-ffi
719721
Enable the experimental \fBnode:ffi\fR module.
722+
This flag is only available in builds with FFI support.
720723
.
721724
.It Fl -experimental-import-meta-resolve
722725
Enable experimental \fBimport.meta.resolve()\fR parent URL support, which allows

0 commit comments

Comments
 (0)