Skip to content

Commit df09b2a

Browse files
authored
ffi: remove function signature property aliases
Signed-off-by: Renegade334 <contact.9a5d6388@renegade334.me.uk> PR-URL: nodejs#63482 Refs: https://github.com/nodejs/node/pull/62072/changes#r3067834658 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent 74ccf38 commit df09b2a

10 files changed

Lines changed: 254 additions & 343 deletions

doc/api/ffi.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,18 @@ such as `0` and `1`; JavaScript `true` and `false` are not accepted.
124124

125125
Functions and callbacks are described with signature objects.
126126

127-
Supported fields:
127+
Signature objects may contain the following properties, both of which are
128+
optional:
128129

129-
* `result`, `return`, or `returns` for the return type.
130-
* `parameters` or `arguments` for the parameter type list.
130+
* `return` {string} A [type name][type names] specifying the return type of the
131+
function or callback. **Default:** `'void'`.
132+
* `arguments` {string\[]} An array of [type names][] specifying the argument
133+
type list of the function or callback. **Default:** `[]`.
131134

132-
Only one return-type field and one parameter-list field may be present in a
133-
single signature object.
134-
135-
```cjs
135+
```js
136136
const signature = {
137-
result: 'i32',
138-
parameters: ['i32', 'i32'],
137+
return: 'i32',
138+
arguments: ['i32', 'i32'],
139139
};
140140
```
141141

@@ -193,7 +193,7 @@ import { dlopen } from 'node:ffi';
193193

194194
{
195195
using handle = dlopen('./mylib.so', {
196-
add_i32: { parameters: ['i32', 'i32'], result: 'i32' },
196+
add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
197197
});
198198
console.log(handle.functions.add_i32(20, 22));
199199
} // handle.lib.close() is invoked automatically here.
@@ -203,8 +203,8 @@ import { dlopen } from 'node:ffi';
203203
import { dlopen } from 'node:ffi';
204204

205205
const { lib, functions } = dlopen('./mylib.so', {
206-
add_i32: { parameters: ['i32', 'i32'], result: 'i32' },
207-
string_length: { parameters: ['pointer'], result: 'u64' },
206+
add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
207+
string_length: { arguments: ['pointer'], return: 'u64' },
208208
});
209209

210210
console.log(functions.add_i32(20, 22));
@@ -214,8 +214,8 @@ console.log(functions.add_i32(20, 22));
214214
const { dlopen } = require('node:ffi');
215215

216216
const { lib, functions } = dlopen('./mylib.so', {
217-
add_i32: { parameters: ['i32', 'i32'], result: 'i32' },
218-
string_length: { parameters: ['pointer'], result: 'u64' },
217+
add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
218+
string_length: { arguments: ['pointer'], return: 'u64' },
219219
});
220220

221221
console.log(functions.add_i32(20, 22));
@@ -356,8 +356,8 @@ const { DynamicLibrary } = require('node:ffi');
356356

357357
const lib = new DynamicLibrary('./mylib.so');
358358
const add = lib.getFunction('add_i32', {
359-
parameters: ['i32', 'i32'],
360-
result: 'i32',
359+
arguments: ['i32', 'i32'],
360+
return: 'i32',
361361
});
362362

363363
console.log(add(20, 22));
@@ -407,7 +407,7 @@ const { DynamicLibrary } = require('node:ffi');
407407
const lib = new DynamicLibrary('./mylib.so');
408408

409409
const callback = lib.registerCallback(
410-
{ parameters: ['i32'], result: 'i32' },
410+
{ arguments: ['i32'], return: 'i32' },
411411
(value) => value * 2,
412412
);
413413
```
@@ -417,7 +417,7 @@ Callbacks are subject to the following restrictions:
417417
* They must be invoked on the same system thread where they were created.
418418
* They must not throw exceptions.
419419
* They must not return promises.
420-
* They must return a value compatible with the declared result type.
420+
* They must return a value compatible with the declared return type.
421421
* They must not call `library.close()` on their owning library while running.
422422
* They must not unregister themselves while running.
423423

@@ -465,7 +465,7 @@ JavaScript `number` values that match the declared type.
465465

466466
For 64-bit integer types (`i64` and `u64`), pass JavaScript `bigint` values.
467467

468-
For pointer-like parameters:
468+
For pointer-like arguments:
469469

470470
* `null` and `undefined` are passed as null pointers.
471471
* `string` values are copied to temporary NUL-terminated UTF-8 strings for the
@@ -727,3 +727,4 @@ and keep callback and pointer lifetimes explicit on the native side.
727727
[`--allow-ffi`]: cli.md#--allow-ffi
728728
[`ffi.toBuffer(pointer, length, copy)`]: #ffitobufferpointer-length-copy
729729
[`using`]: https://tc39.es/proposal-explicit-resource-management/#sec-using-declarations
730+
[type names]: #type-names

lib/internal/ffi-shared-buffer.js

Lines changed: 43 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,14 @@ const {
3131
TypeError,
3232
} = primordials;
3333

34-
const {
35-
codes: {
36-
ERR_INTERNAL_ASSERTION,
37-
},
38-
} = require('internal/errors');
34+
const assert = require('internal/assert');
3935

4036
const {
4137
DynamicLibrary,
4238
charIsSigned,
39+
kSbArguments,
4340
kSbInvokeSlow,
44-
kSbParams,
45-
kSbResult,
41+
kSbReturn,
4642
kSbSharedBuffer,
4743
uintptrMax,
4844
} = internalBinding('ffi');
@@ -159,11 +155,9 @@ function writeNumericArg(view, info, offset, arg, index) {
159155
return;
160156
}
161157

162-
/* c8 ignore start */
163158
// Unreachable: caller filters out non-numeric kinds.
164-
throw new ERR_INTERNAL_ASSERTION(
165-
`FFI: writeNumericArg reached with unexpected kind="${kind}"`);
166-
/* c8 ignore stop */
159+
/* c8 ignore next */
160+
assert.fail(`FFI: writeNumericArg reached with unexpected kind="${kind}"`);
167161
}
168162

169163
// Returns true on fast-path success, false when the caller must fall back
@@ -208,51 +202,46 @@ function inheritMetadata(wrapper, rawFn, nargs) {
208202
// arguments out of it into invocation-local storage before `ffi_call` and
209203
// reads the return value back only after, so nested/reentrant calls into
210204
// the same function are safe.
211-
function wrapWithSharedBuffer(rawFn, parameters, resultType) {
212-
if (rawFn === undefined || rawFn === null) return rawFn;
205+
function wrapWithSharedBuffer(rawFn, signature) {
206+
if (rawFn == null) return rawFn;
213207
const buffer = rawFn[kSbSharedBuffer];
214208
if (buffer === undefined) return rawFn;
215209

216210
// Callers without explicit signature info (the `functions` accessor
217-
// patch below) rely on the `kSbParams` / `kSbResult` metadata attached
211+
// patch below) rely on the `kSbArguments` / `kSbReturn` metadata attached
218212
// by the native `CreateFunction`.
219-
if (parameters === undefined) parameters = rawFn[kSbParams];
220-
if (resultType === undefined) resultType = rawFn[kSbResult];
221-
// `CreateFunction` always attaches these for SB-eligible functions.
222-
// Missing here means the native side and this wrapper are out of sync.
223-
/* c8 ignore start */
224-
if (parameters === undefined || resultType === undefined) {
225-
throw new ERR_INTERNAL_ASSERTION(
226-
'FFI: shared-buffer raw function is missing kSbParams or kSbResult');
213+
let argumentTypes, returnType;
214+
if (signature === undefined) {
215+
argumentTypes = rawFn[kSbArguments];
216+
returnType = rawFn[kSbReturn];
217+
218+
// `CreateFunction` always attaches these for SB-eligible functions.
219+
// Missing here means the native side and this wrapper are out of sync.
220+
assert(argumentTypes !== undefined && returnType !== undefined,
221+
'FFI: shared-buffer raw function is missing kSbArguments or kSbReturn');
222+
} else {
223+
argumentTypes = signature.arguments ?? [];
224+
returnType = signature.return ?? 'void';
227225
}
228-
/* c8 ignore stop */
229226

230227
const slowInvoke = rawFn[kSbInvokeSlow];
231228
const view = new DataView(buffer);
232229
let retGetter = null;
233-
if (resultType !== 'void') {
234-
const retInfo = sbTypeInfo[resultType];
235-
/* c8 ignore start */
236-
if (retInfo === undefined) {
237-
throw new ERR_INTERNAL_ASSERTION(
238-
`FFI: shared-buffer type table missing entry for result type "${resultType}"`);
239-
}
240-
/* c8 ignore stop */
230+
if (returnType !== 'void') {
231+
const retInfo = sbTypeInfo[returnType];
232+
assert(retInfo !== undefined,
233+
`FFI: shared-buffer type table missing entry for return type "${returnType}"`);
241234
retGetter = retInfo.get;
242235
}
243236

244-
const nargs = parameters.length;
237+
const nargs = argumentTypes.length;
245238
const argInfos = [];
246239
const argOffsets = [];
247240
let anyPointer = false;
248241
for (let i = 0; i < nargs; i++) {
249-
const info = sbTypeInfo[parameters[i]];
250-
/* c8 ignore start */
251-
if (info === undefined) {
252-
throw new ERR_INTERNAL_ASSERTION(
253-
`FFI: shared-buffer type table missing entry for parameter type "${parameters[i]}"`);
254-
}
255-
/* c8 ignore stop */
242+
const info = sbTypeInfo[argumentTypes[i]];
243+
assert(info !== undefined,
244+
`FFI: shared-buffer type table missing entry for argument type "${argumentTypes[i]}"`);
256245
// Push the `sbTypeInfo` entry directly (entries with the same `kind`
257246
// share a shape, keeping `writeNumericArg`'s call sites
258247
// low-polymorphism) and store offsets in a parallel array to avoid
@@ -267,13 +256,8 @@ function wrapWithSharedBuffer(rawFn, parameters, resultType) {
267256
// Pointer signatures need a per-arg runtime type check and fall back
268257
// to the native slow-path invoker for non-BigInt pointer arguments,
269258
// so arity specialization wouldn't buy much here.
270-
/* c8 ignore start */
271-
if (slowInvoke === undefined) {
272-
throw new ERR_INTERNAL_ASSERTION(
273-
'FFI: shared-buffer raw function with pointer arguments is ' +
274-
'missing kSbInvokeSlow');
275-
}
276-
/* c8 ignore stop */
259+
assert(slowInvoke !== undefined,
260+
'FFI: shared-buffer raw function with pointer arguments is missing kSbInvokeSlow');
277261
wrapper = function(...args) {
278262
if (args.length !== nargs) {
279263
throwFFIArgCountError(nargs, args.length);
@@ -542,19 +526,6 @@ function buildNumericWrapper(
542526
};
543527
}
544528

545-
// Accept-set mirrors the native `ParseFunctionSignature` in
546-
// `src/ffi/types.cc`. `ParseFunctionSignature` additionally throws when
547-
// multiple aliases are set at once. The wrapper runs before the native
548-
// call, so those conflicts still surface from the native side regardless
549-
// of which alias we happen to read here.
550-
function sigParams(sig) {
551-
return sig.parameters ?? sig.arguments ?? [];
552-
}
553-
554-
function sigResult(sig) {
555-
return sig.result ?? sig.return ?? sig.returns ?? 'void';
556-
}
557-
558529
// The native invoker for SB-eligible symbols is `InvokeFunctionSB`, which
559530
// reads arguments from the shared buffer populated by
560531
// `wrapWithSharedBuffer`. These patches make sure every path that surfaces
@@ -563,11 +534,11 @@ function sigResult(sig) {
563534
const rawGetFunction = DynamicLibrary.prototype.getFunction;
564535
const rawGetFunctions = DynamicLibrary.prototype.getFunctions;
565536

566-
DynamicLibrary.prototype.getFunction = function getFunction(name, sig) {
567-
// Native `DynamicLibrary::GetFunction` validates `sig`, so by the time
568-
// we have `raw` we know `sig` is a valid object.
569-
const raw = FunctionPrototypeCall(rawGetFunction, this, name, sig);
570-
return wrapWithSharedBuffer(raw, sigParams(sig), sigResult(sig));
537+
DynamicLibrary.prototype.getFunction = function getFunction(name, signature) {
538+
// Native `DynamicLibrary::GetFunction` validates `signature`, so by the time
539+
// we have `raw` we know `signature` is a valid object.
540+
const raw = FunctionPrototypeCall(rawGetFunction, this, name, signature);
541+
return wrapWithSharedBuffer(raw, signature);
571542
};
572543

573544
DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
@@ -583,13 +554,12 @@ DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
583554
for (let i = 0; i < keys.length; i++) {
584555
const name = keys[i];
585556
// No `definitions`: native side returned every cached function, so we
586-
// wrap using each function's own `kSbParams` / `kSbResult` metadata
557+
// wrap using each function's own `kSbArguments` / `kSbReturn` metadata
587558
// (same fallback as the `functions` accessor).
588559
if (definitions === undefined) {
589560
out[name] = wrapWithSharedBuffer(raw[name]);
590561
} else {
591-
const sig = definitions[name];
592-
out[name] = wrapWithSharedBuffer(raw[name], sigParams(sig), sigResult(sig));
562+
out[name] = wrapWithSharedBuffer(raw[name], definitions[name]);
593563
}
594564
}
595565
return out;
@@ -602,16 +572,12 @@ DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
602572
// uninitialized buffer.
603573
const functionsDescriptor =
604574
ObjectGetOwnPropertyDescriptor(DynamicLibrary.prototype, 'functions');
605-
/* c8 ignore start */
606-
if (functionsDescriptor === undefined || !functionsDescriptor.get) {
607-
// Missing getter means the native and JS sides are out of sync; silently
608-
// skipping the patch would expose the fast-path-against-uninitialized-buffer
609-
// footgun this whole block exists to prevent.
610-
throw new ERR_INTERNAL_ASSERTION(
611-
'FFI: DynamicLibrary.prototype.functions accessor not found or has no getter');
612-
}
613-
/* c8 ignore stop */
614-
const origGetter = functionsDescriptor.get;
575+
const origGetter = functionsDescriptor?.get;
576+
// Missing getter means the native and JS sides are out of sync; silently
577+
// skipping the patch would expose the fast-path-against-uninitialized-buffer
578+
// footgun this whole block exists to prevent.
579+
assert(origGetter !== undefined,
580+
'FFI: DynamicLibrary.prototype.functions accessor not found or has no getter');
615581
ObjectDefineProperty(DynamicLibrary.prototype, 'functions', {
616582
__proto__: null,
617583
configurable: true,

src/env_properties.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
V(async_id_symbol, "async_id_symbol") \
4949
V(ffi_sb_shared_buffer_symbol, "ffi_sb_shared_buffer_symbol") \
5050
V(ffi_sb_invoke_slow_symbol, "ffi_sb_invoke_slow_symbol") \
51-
V(ffi_sb_params_symbol, "ffi_sb_params_symbol") \
52-
V(ffi_sb_result_symbol, "ffi_sb_result_symbol") \
51+
V(ffi_sb_arguments_symbol, "ffi_sb_arguments_symbol") \
52+
V(ffi_sb_return_symbol, "ffi_sb_return_symbol") \
5353
V(constructor_key_symbol, "constructor_key_symbol") \
5454
V(handle_onclose_symbol, "handle_onclose") \
5555
V(no_message_symbol, "no_message_symbol") \
@@ -293,7 +293,6 @@
293293
V(password_string, "password") \
294294
V(path_string, "path") \
295295
V(pathname_string, "pathname") \
296-
V(parameters_string, "parameters") \
297296
V(pending_handle_string, "pendingHandle") \
298297
V(permission_string, "permission") \
299298
V(phase_string, "phase") \
@@ -329,9 +328,8 @@
329328
V(require_string, "require") \
330329
V(resource_string, "resource") \
331330
V(result_string, "result") \
332-
V(return_string, "return") \
333-
V(returns_string, "returns") \
334331
V(return_arrays_string, "returnArrays") \
332+
V(return_string, "return") \
335333
V(salt_length_string, "saltLength") \
336334
V(search_string, "search") \
337335
V(servername_string, "servername") \

0 commit comments

Comments
 (0)