ffi: remove function signature property aliases

Signed-off-by: Renegade334 <contact.9a5d6388@renegade334.me.uk>
PR-URL: https://github.com/nodejs/node/pull/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>
This commit is contained in:
René authored and GitHub committed 2026-05-24 12:39:53 +00:00
1 parent 74ccf384a9
commit df09b2a48c
10 files changed
+254 -343

No files matched your search

+20 -19
View File
@@ -124,18 +124,18 @@ such as `0` and `1`; JavaScript `true` and `false` are not accepted.
Functions and callbacks are described with signature objects.
Supported fields:
Signature objects may contain the following properties, both of which are
optional:
* `result`, `return`, or `returns` for the return type.
* `parameters` or `arguments` for the parameter type list.
* `return` {string} A [type name][type names] specifying the return type of the
function or callback. **Default:** `'void'`.
* `arguments` {string\[]} An array of [type names][] specifying the argument
type list of the function or callback. **Default:** `[]`.
Only one return-type field and one parameter-list field may be present in a
single signature object.
```cjs
```js
const signature = {
result: 'i32',
parameters: ['i32', 'i32'],
return: 'i32',
arguments: ['i32', 'i32'],
};
```
@@ -193,7 +193,7 @@ import { dlopen } from 'node:ffi';
{
using handle = dlopen('./mylib.so', {
add_i32: { parameters: ['i32', 'i32'], result: 'i32' },
add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
});
console.log(handle.functions.add_i32(20, 22));
} // handle.lib.close() is invoked automatically here.
@@ -203,8 +203,8 @@ import { dlopen } from 'node:ffi';
import { dlopen } from 'node:ffi';
const { lib, functions } = dlopen('./mylib.so', {
add_i32: { parameters: ['i32', 'i32'], result: 'i32' },
string_length: { parameters: ['pointer'], result: 'u64' },
add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
string_length: { arguments: ['pointer'], return: 'u64' },
});
console.log(functions.add_i32(20, 22));
@@ -214,8 +214,8 @@ console.log(functions.add_i32(20, 22));
const { dlopen } = require('node:ffi');
const { lib, functions } = dlopen('./mylib.so', {
add_i32: { parameters: ['i32', 'i32'], result: 'i32' },
string_length: { parameters: ['pointer'], result: 'u64' },
add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
string_length: { arguments: ['pointer'], return: 'u64' },
});
console.log(functions.add_i32(20, 22));
@@ -356,8 +356,8 @@ const { DynamicLibrary } = require('node:ffi');
const lib = new DynamicLibrary('./mylib.so');
const add = lib.getFunction('add_i32', {
parameters: ['i32', 'i32'],
result: 'i32',
arguments: ['i32', 'i32'],
return: 'i32',
});
console.log(add(20, 22));
@@ -407,7 +407,7 @@ const { DynamicLibrary } = require('node:ffi');
const lib = new DynamicLibrary('./mylib.so');
const callback = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
(value) => value * 2,
);
```
@@ -417,7 +417,7 @@ Callbacks are subject to the following restrictions:
* They must be invoked on the same system thread where they were created.
* They must not throw exceptions.
* They must not return promises.
* They must return a value compatible with the declared result type.
* They must return a value compatible with the declared return type.
* They must not call `library.close()` on their owning library while running.
* They must not unregister themselves while running.
@@ -465,7 +465,7 @@ JavaScript `number` values that match the declared type.
For 64-bit integer types (`i64` and `u64`), pass JavaScript `bigint` values.
For pointer-like parameters:
For pointer-like arguments:
* `null` and `undefined` are passed as null pointers.
* `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.
[`--allow-ffi`]: cli.md#--allow-ffi
[`ffi.toBuffer(pointer, length, copy)`]: #ffitobufferpointer-length-copy
[`using`]: https://tc39.es/proposal-explicit-resource-management/#sec-using-declarations
[type names]: #type-names
+43 -77
View File
@@ -31,18 +31,14 @@ const {
TypeError,
} = primordials;
const {
codes: {
ERR_INTERNAL_ASSERTION,
},
} = require('internal/errors');
const assert = require('internal/assert');
const {
DynamicLibrary,
charIsSigned,
kSbArguments,
kSbInvokeSlow,
kSbParams,
kSbResult,
kSbReturn,
kSbSharedBuffer,
uintptrMax,
} = internalBinding('ffi');
@@ -159,11 +155,9 @@ function writeNumericArg(view, info, offset, arg, index) {
return;
}
/* c8 ignore start */
// Unreachable: caller filters out non-numeric kinds.
throw new ERR_INTERNAL_ASSERTION(
`FFI: writeNumericArg reached with unexpected kind="${kind}"`);
/* c8 ignore stop */
/* c8 ignore next */
assert.fail(`FFI: writeNumericArg reached with unexpected kind="${kind}"`);
}
// Returns true on fast-path success, false when the caller must fall back
@@ -208,51 +202,46 @@ function inheritMetadata(wrapper, rawFn, nargs) {
// arguments out of it into invocation-local storage before `ffi_call` and
// reads the return value back only after, so nested/reentrant calls into
// the same function are safe.
function wrapWithSharedBuffer(rawFn, parameters, resultType) {
if (rawFn === undefined || rawFn === null) return rawFn;
function wrapWithSharedBuffer(rawFn, signature) {
if (rawFn == null) return rawFn;
const buffer = rawFn[kSbSharedBuffer];
if (buffer === undefined) return rawFn;
// Callers without explicit signature info (the `functions` accessor
// patch below) rely on the `kSbParams` / `kSbResult` metadata attached
// patch below) rely on the `kSbArguments` / `kSbReturn` metadata attached
// by the native `CreateFunction`.
if (parameters === undefined) parameters = rawFn[kSbParams];
if (resultType === undefined) resultType = rawFn[kSbResult];
// `CreateFunction` always attaches these for SB-eligible functions.
// Missing here means the native side and this wrapper are out of sync.
/* c8 ignore start */
if (parameters === undefined || resultType === undefined) {
throw new ERR_INTERNAL_ASSERTION(
'FFI: shared-buffer raw function is missing kSbParams or kSbResult');
let argumentTypes, returnType;
if (signature === undefined) {
argumentTypes = rawFn[kSbArguments];
returnType = rawFn[kSbReturn];
// `CreateFunction` always attaches these for SB-eligible functions.
// Missing here means the native side and this wrapper are out of sync.
assert(argumentTypes !== undefined && returnType !== undefined,
'FFI: shared-buffer raw function is missing kSbArguments or kSbReturn');
} else {
argumentTypes = signature.arguments ?? [];
returnType = signature.return ?? 'void';
}
/* c8 ignore stop */
const slowInvoke = rawFn[kSbInvokeSlow];
const view = new DataView(buffer);
let retGetter = null;
if (resultType !== 'void') {
const retInfo = sbTypeInfo[resultType];
/* c8 ignore start */
if (retInfo === undefined) {
throw new ERR_INTERNAL_ASSERTION(
`FFI: shared-buffer type table missing entry for result type "${resultType}"`);
}
/* c8 ignore stop */
if (returnType !== 'void') {
const retInfo = sbTypeInfo[returnType];
assert(retInfo !== undefined,
`FFI: shared-buffer type table missing entry for return type "${returnType}"`);
retGetter = retInfo.get;
}
const nargs = parameters.length;
const nargs = argumentTypes.length;
const argInfos = [];
const argOffsets = [];
let anyPointer = false;
for (let i = 0; i < nargs; i++) {
const info = sbTypeInfo[parameters[i]];
/* c8 ignore start */
if (info === undefined) {
throw new ERR_INTERNAL_ASSERTION(
`FFI: shared-buffer type table missing entry for parameter type "${parameters[i]}"`);
}
/* c8 ignore stop */
const info = sbTypeInfo[argumentTypes[i]];
assert(info !== undefined,
`FFI: shared-buffer type table missing entry for argument type "${argumentTypes[i]}"`);
// Push the `sbTypeInfo` entry directly (entries with the same `kind`
// share a shape, keeping `writeNumericArg`'s call sites
// low-polymorphism) and store offsets in a parallel array to avoid
@@ -267,13 +256,8 @@ function wrapWithSharedBuffer(rawFn, parameters, resultType) {
// Pointer signatures need a per-arg runtime type check and fall back
// to the native slow-path invoker for non-BigInt pointer arguments,
// so arity specialization wouldn't buy much here.
/* c8 ignore start */
if (slowInvoke === undefined) {
throw new ERR_INTERNAL_ASSERTION(
'FFI: shared-buffer raw function with pointer arguments is ' +
'missing kSbInvokeSlow');
}
/* c8 ignore stop */
assert(slowInvoke !== undefined,
'FFI: shared-buffer raw function with pointer arguments is missing kSbInvokeSlow');
wrapper = function(...args) {
if (args.length !== nargs) {
throwFFIArgCountError(nargs, args.length);
@@ -542,19 +526,6 @@ function buildNumericWrapper(
};
}
// Accept-set mirrors the native `ParseFunctionSignature` in
// `src/ffi/types.cc`. `ParseFunctionSignature` additionally throws when
// multiple aliases are set at once. The wrapper runs before the native
// call, so those conflicts still surface from the native side regardless
// of which alias we happen to read here.
function sigParams(sig) {
return sig.parameters ?? sig.arguments ?? [];
}
function sigResult(sig) {
return sig.result ?? sig.return ?? sig.returns ?? 'void';
}
// The native invoker for SB-eligible symbols is `InvokeFunctionSB`, which
// reads arguments from the shared buffer populated by
// `wrapWithSharedBuffer`. These patches make sure every path that surfaces
@@ -563,11 +534,11 @@ function sigResult(sig) {
const rawGetFunction = DynamicLibrary.prototype.getFunction;
const rawGetFunctions = DynamicLibrary.prototype.getFunctions;
DynamicLibrary.prototype.getFunction = function getFunction(name, sig) {
// Native `DynamicLibrary::GetFunction` validates `sig`, so by the time
// we have `raw` we know `sig` is a valid object.
const raw = FunctionPrototypeCall(rawGetFunction, this, name, sig);
return wrapWithSharedBuffer(raw, sigParams(sig), sigResult(sig));
DynamicLibrary.prototype.getFunction = function getFunction(name, signature) {
// Native `DynamicLibrary::GetFunction` validates `signature`, so by the time
// we have `raw` we know `signature` is a valid object.
const raw = FunctionPrototypeCall(rawGetFunction, this, name, signature);
return wrapWithSharedBuffer(raw, signature);
};
DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
@@ -583,13 +554,12 @@ DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
for (let i = 0; i < keys.length; i++) {
const name = keys[i];
// No `definitions`: native side returned every cached function, so we
// wrap using each function's own `kSbParams` / `kSbResult` metadata
// wrap using each function's own `kSbArguments` / `kSbReturn` metadata
// (same fallback as the `functions` accessor).
if (definitions === undefined) {
out[name] = wrapWithSharedBuffer(raw[name]);
} else {
const sig = definitions[name];
out[name] = wrapWithSharedBuffer(raw[name], sigParams(sig), sigResult(sig));
out[name] = wrapWithSharedBuffer(raw[name], definitions[name]);
}
}
return out;
@@ -602,16 +572,12 @@ DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
// uninitialized buffer.
const functionsDescriptor =
ObjectGetOwnPropertyDescriptor(DynamicLibrary.prototype, 'functions');
/* c8 ignore start */
if (functionsDescriptor === undefined || !functionsDescriptor.get) {
// Missing getter means the native and JS sides are out of sync; silently
// skipping the patch would expose the fast-path-against-uninitialized-buffer
// footgun this whole block exists to prevent.
throw new ERR_INTERNAL_ASSERTION(
'FFI: DynamicLibrary.prototype.functions accessor not found or has no getter');
}
/* c8 ignore stop */
const origGetter = functionsDescriptor.get;
const origGetter = functionsDescriptor?.get;
// Missing getter means the native and JS sides are out of sync; silently
// skipping the patch would expose the fast-path-against-uninitialized-buffer
// footgun this whole block exists to prevent.
assert(origGetter !== undefined,
'FFI: DynamicLibrary.prototype.functions accessor not found or has no getter');
ObjectDefineProperty(DynamicLibrary.prototype, 'functions', {
__proto__: null,
configurable: true,
+3 -5
View File
@@ -48,8 +48,8 @@
V(async_id_symbol, "async_id_symbol") \
V(ffi_sb_shared_buffer_symbol, "ffi_sb_shared_buffer_symbol") \
V(ffi_sb_invoke_slow_symbol, "ffi_sb_invoke_slow_symbol") \
V(ffi_sb_params_symbol, "ffi_sb_params_symbol") \
V(ffi_sb_result_symbol, "ffi_sb_result_symbol") \
V(ffi_sb_arguments_symbol, "ffi_sb_arguments_symbol") \
V(ffi_sb_return_symbol, "ffi_sb_return_symbol") \
V(constructor_key_symbol, "constructor_key_symbol") \
V(handle_onclose_symbol, "handle_onclose") \
V(no_message_symbol, "no_message_symbol") \
@@ -293,7 +293,6 @@
V(password_string, "password") \
V(path_string, "path") \
V(pathname_string, "pathname") \
V(parameters_string, "parameters") \
V(pending_handle_string, "pendingHandle") \
V(permission_string, "permission") \
V(phase_string, "phase") \
@@ -329,9 +328,8 @@
V(require_string, "require") \
V(resource_string, "resource") \
V(result_string, "result") \
V(return_string, "return") \
V(returns_string, "returns") \
V(return_arrays_string, "returnArrays") \
V(return_string, "return") \
V(salt_length_string, "saltLength") \
V(search_string, "search") \
V(servername_string, "servername") \
+5 -43
View File
@@ -83,63 +83,26 @@ Maybe<FunctionSignature> ParseFunctionSignature(Environment* env,
std::string_view name,
Local<Object> signature) {
Local<Context> context = env->context();
Local<String> returns_key = env->returns_string();
Local<String> return_key = env->return_string();
Local<String> result_key = env->result_string();
Local<String> parameters_key = env->parameters_string();
Local<String> arguments_key = env->arguments_string();
bool has_returns;
bool has_return;
bool has_result;
bool has_parameters;
bool has_arguments;
if (!signature->Has(context, returns_key).To(&has_returns) ||
!signature->Has(context, return_key).To(&has_return) ||
!signature->Has(context, result_key).To(&has_result) ||
!signature->Has(context, parameters_key).To(&has_parameters) ||
if (!signature->Has(context, return_key).To(&has_return) ||
!signature->Has(context, arguments_key).To(&has_arguments)) {
return {};
}
if (has_returns + has_return + has_result > 1) {
THROW_ERR_INVALID_ARG_VALUE(
env,
"Function signature of %s"
" must have either 'returns', 'return' or 'result' "
"property",
name);
return {};
}
if (has_arguments && has_parameters) {
THROW_ERR_INVALID_ARG_VALUE(env,
"Function signature of %s"
" must have either 'parameters' or 'arguments' "
"property",
name);
return {};
}
ffi_type* return_type = &ffi_type_void;
std::vector<ffi_type*> args;
std::string return_type_name = "void";
std::vector<std::string> arg_type_names;
Isolate* isolate = env->isolate();
if (has_returns || has_return || has_result) {
Local<String> return_type_key;
if (has_returns) {
return_type_key = returns_key;
} else if (has_return) {
return_type_key = return_key;
} else {
return_type_key = result_key;
}
if (has_return) {
Local<Value> return_type_val;
if (!signature->Get(context, return_type_key).ToLocal(&return_type_val)) {
if (!signature->Get(context, return_key).ToLocal(&return_type_val)) {
return {};
}
@@ -162,10 +125,9 @@ Maybe<FunctionSignature> ParseFunctionSignature(Environment* env,
return_type_name = return_type_str.ToString();
}
if (has_arguments || has_parameters) {
if (has_arguments) {
Local<Value> arguments_val;
if (!signature->Get(context, has_arguments ? arguments_key : parameters_key)
.ToLocal(&arguments_val)) {
if (!signature->Get(context, arguments_key).ToLocal(&arguments_val)) {
return {};
}
+13 -13
View File
@@ -312,28 +312,28 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(
// Attach the original signature type names so the JS wrapper can
// rebuild the signature from a raw function when the caller did not
// pass parameters and result explicitly. The `lib.functions` accessor
// pass arguments and return explicitly. The `lib.functions` accessor
// path relies on this.
Local<Value> params_arr;
if (!ToV8Value(context, fn->arg_type_names, isolate).ToLocal(&params_arr)) {
Local<Value> args_arr;
if (!ToV8Value(context, fn->arg_type_names, isolate).ToLocal(&args_arr)) {
return MaybeLocal<Function>();
}
if (!ret->DefineOwnProperty(context,
env->ffi_sb_params_symbol(),
params_arr,
env->ffi_sb_arguments_symbol(),
args_arr,
internal_attrs)
.FromMaybe(false)) {
return MaybeLocal<Function>();
}
Local<Value> result_name;
Local<Value> return_name;
if (!ToV8Value(context, fn->return_type_name, isolate)
.ToLocal(&result_name)) {
.ToLocal(&return_name)) {
return MaybeLocal<Function>();
}
if (!ret->DefineOwnProperty(context,
env->ffi_sb_result_symbol(),
result_name,
env->ffi_sb_return_symbol(),
return_name,
internal_attrs)
.FromMaybe(false)) {
return MaybeLocal<Function>();
@@ -1197,13 +1197,13 @@ static void Initialize(Local<Object> target,
.Check();
target
->Set(context,
FIXED_ONE_BYTE_STRING(isolate, "kSbParams"),
env->ffi_sb_params_symbol())
FIXED_ONE_BYTE_STRING(isolate, "kSbArguments"),
env->ffi_sb_arguments_symbol())
.Check();
target
->Set(context,
FIXED_ONE_BYTE_STRING(isolate, "kSbResult"),
env->ffi_sb_result_symbol())
FIXED_ONE_BYTE_STRING(isolate, "kSbReturn"),
env->ffi_sb_return_symbol())
.Check();
}
+47 -47
View File
@@ -31,56 +31,56 @@ function ensureFixtureLibrary() {
ensureFixtureLibrary();
const fixtureSymbols = {
add_i8: { parameters: ['i8', 'i8'], result: 'i8' },
add_u8: { parameters: ['u8', 'u8'], result: 'u8' },
add_i16: { parameters: ['i16', 'i16'], result: 'i16' },
add_u16: { parameters: ['u16', 'u16'], result: 'u16' },
add_i32: { parameters: ['i32', 'i32'], result: 'i32' },
add_u32: { parameters: ['u32', 'u32'], result: 'u32' },
add_i64: { parameters: ['i64', 'i64'], result: 'i64' },
add_u64: { parameters: ['u64', 'u64'], result: 'u64' },
identity_char: { parameters: ['char'], result: 'char' },
char_is_signed: { parameters: [], result: 'i32' },
add_f32: { parameters: ['f32', 'f32'], result: 'f32' },
multiply_f64: { parameters: ['f64', 'f64'], result: 'f64' },
identity_pointer: { parameters: ['pointer'], result: 'pointer' },
pointer_to_usize: { parameters: ['pointer'], result: 'u64' },
usize_to_pointer: { parameters: ['u64'], result: 'pointer' },
string_length: { parameters: ['pointer'], result: 'u64' },
string_concat: { parameters: ['pointer', 'pointer'], result: 'pointer' },
string_duplicate: { parameters: ['pointer'], result: 'pointer' },
free_string: { parameters: ['pointer'], result: 'void' },
fill_buffer: { parameters: ['pointer', 'u64', 'u32'], result: 'void' },
sum_buffer: { parameters: ['pointer', 'u64'], result: 'u64' },
reverse_buffer: { parameters: ['pointer', 'u64'], result: 'void' },
logical_and: { parameters: ['i32', 'i32'], result: 'i32' },
logical_or: { parameters: ['i32', 'i32'], result: 'i32' },
logical_not: { parameters: ['i32'], result: 'i32' },
increment_counter: { parameters: [], result: 'void' },
get_counter: { parameters: [], result: 'i32' },
reset_counter: { parameters: [], result: 'void' },
call_int_callback: { parameters: ['pointer', 'i32'], result: 'i32' },
call_int8_callback: { parameters: ['pointer', 'i8'], result: 'i8' },
call_pointer_callback_is_null: { parameters: ['pointer'], result: 'i32' },
call_void_callback: { parameters: ['pointer'], result: 'void' },
call_string_callback: { parameters: ['function', 'pointer'], result: 'void' },
call_binary_int_callback: { parameters: ['function', 'i32', 'i32'], result: 'i32' },
call_callback_multiple_times: { parameters: ['pointer', 'i32'], result: 'void' },
divide_i32: { parameters: ['i32', 'i32'], result: 'i32' },
safe_strlen: { parameters: ['pointer'], result: 'i32' },
sum_five_i32: { parameters: ['i32', 'i32', 'i32', 'i32', 'i32'], result: 'i32' },
sum_five_f64: { parameters: ['f64', 'f64', 'f64', 'f64', 'f64'], result: 'f64' },
mixed_operation: { parameters: ['i32', 'f32', 'f64', 'u32'], result: 'f64' },
allocate_memory: { parameters: ['u64'], result: 'pointer' },
deallocate_memory: { parameters: ['pointer'], result: 'void' },
array_get_i32: { parameters: ['pointer', 'u64'], result: 'i32' },
array_set_i32: { parameters: ['pointer', 'u64', 'i32'], result: 'void' },
array_get_f64: { parameters: ['pointer', 'u64'], result: 'f64' },
array_set_f64: { parameters: ['pointer', 'u64', 'f64'], result: 'void' },
add_i8: { arguments: ['i8', 'i8'], return: 'i8' },
add_u8: { arguments: ['u8', 'u8'], return: 'u8' },
add_i16: { arguments: ['i16', 'i16'], return: 'i16' },
add_u16: { arguments: ['u16', 'u16'], return: 'u16' },
add_i32: { arguments: ['i32', 'i32'], return: 'i32' },
add_u32: { arguments: ['u32', 'u32'], return: 'u32' },
add_i64: { arguments: ['i64', 'i64'], return: 'i64' },
add_u64: { arguments: ['u64', 'u64'], return: 'u64' },
identity_char: { arguments: ['char'], return: 'char' },
char_is_signed: { arguments: [], return: 'i32' },
add_f32: { arguments: ['f32', 'f32'], return: 'f32' },
multiply_f64: { arguments: ['f64', 'f64'], return: 'f64' },
identity_pointer: { arguments: ['pointer'], return: 'pointer' },
pointer_to_usize: { arguments: ['pointer'], return: 'u64' },
usize_to_pointer: { arguments: ['u64'], return: 'pointer' },
string_length: { arguments: ['pointer'], return: 'u64' },
string_concat: { arguments: ['pointer', 'pointer'], return: 'pointer' },
string_duplicate: { arguments: ['pointer'], return: 'pointer' },
free_string: { arguments: ['pointer'], return: 'void' },
fill_buffer: { arguments: ['pointer', 'u64', 'u32'], return: 'void' },
sum_buffer: { arguments: ['pointer', 'u64'], return: 'u64' },
reverse_buffer: { arguments: ['pointer', 'u64'], return: 'void' },
logical_and: { arguments: ['i32', 'i32'], return: 'i32' },
logical_or: { arguments: ['i32', 'i32'], return: 'i32' },
logical_not: { arguments: ['i32'], return: 'i32' },
increment_counter: { arguments: [], return: 'void' },
get_counter: { arguments: [], return: 'i32' },
reset_counter: { arguments: [], return: 'void' },
call_int_callback: { arguments: ['pointer', 'i32'], return: 'i32' },
call_int8_callback: { arguments: ['pointer', 'i8'], return: 'i8' },
call_pointer_callback_is_null: { arguments: ['pointer'], return: 'i32' },
call_void_callback: { arguments: ['pointer'], return: 'void' },
call_string_callback: { arguments: ['function', 'pointer'], return: 'void' },
call_binary_int_callback: { arguments: ['function', 'i32', 'i32'], return: 'i32' },
call_callback_multiple_times: { arguments: ['pointer', 'i32'], return: 'void' },
divide_i32: { arguments: ['i32', 'i32'], return: 'i32' },
safe_strlen: { arguments: ['pointer'], return: 'i32' },
sum_five_i32: { arguments: ['i32', 'i32', 'i32', 'i32', 'i32'], return: 'i32' },
sum_five_f64: { arguments: ['f64', 'f64', 'f64', 'f64', 'f64'], return: 'f64' },
mixed_operation: { arguments: ['i32', 'f32', 'f64', 'u32'], return: 'f64' },
allocate_memory: { arguments: ['u64'], return: 'pointer' },
deallocate_memory: { arguments: ['pointer'], return: 'void' },
array_get_i32: { arguments: ['pointer', 'u64'], return: 'i32' },
array_set_i32: { arguments: ['pointer', 'u64', 'i32'], return: 'void' },
array_get_f64: { arguments: ['pointer', 'u64'], return: 'f64' },
array_set_f64: { arguments: ['pointer', 'u64', 'f64'], return: 'void' },
};
if (!common.isWindows) {
fixtureSymbols.readonly_memory = { parameters: [], result: 'pointer' };
fixtureSymbols.readonly_memory = { arguments: [], return: 'pointer' };
}
function cString(value) {
+14 -14
View File
@@ -59,8 +59,8 @@ test('ffi bool signatures use uint8 values', () => {
assert.strictEqual(symbols.logical_not(0), 1);
const boolAdder = lib.getFunction('add_u8', {
parameters: ['bool', 'bool'],
result: 'bool',
arguments: ['bool', 'bool'],
return: 'bool',
});
assert.strictEqual(boolAdder(1, 0), 1);
assert.throws(() => boolAdder(true, false), /Argument 0 must be a uint8/);
@@ -148,15 +148,15 @@ test('ffi callbacks can be registered and invoked', () => {
const { lib, functions: symbols } = getLibrary();
const seen = [];
const intCallback = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
(value) => value * 2,
);
const stringCallback = lib.registerCallback(
{ parameters: ['pointer'], result: 'void' },
{ arguments: ['pointer'], return: 'void' },
(ptr) => seen.push(ffi.toString(ptr)),
);
const binaryCallback = lib.registerCallback(
{ arguments: ['i32', 'i32'], returns: 'i32' },
{ arguments: ['i32', 'i32'], return: 'i32' },
(a, b) => a + b,
);
@@ -166,8 +166,8 @@ test('ffi callbacks can be registered and invoked', () => {
assert.deepStrictEqual(seen, ['hello callback']);
assert.strictEqual(symbols.call_binary_int_callback(binaryCallback, 19, 23), 42);
const nullPointerCallback = lib.registerCallback({ result: 'pointer' }, () => null);
const undefinedPointerCallback = lib.registerCallback({ result: 'pointer' }, () => undefined);
const nullPointerCallback = lib.registerCallback({ return: 'pointer' }, () => null);
const undefinedPointerCallback = lib.registerCallback({ return: 'pointer' }, () => undefined);
try {
assert.strictEqual(symbols.call_pointer_callback_is_null(nullPointerCallback), 1);
assert.strictEqual(symbols.call_pointer_callback_is_null(undefinedPointerCallback), 1);
@@ -191,7 +191,7 @@ test('ffi callback ref and unref APIs work', () => {
called = true;
});
const countingCallback = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
(value) => {
values.push(value);
return 0;
@@ -271,7 +271,7 @@ const ffi = require('node:ffi');
const { fixtureSymbols, libraryPath } = require(${JSON.stringify(require.resolve('./ffi-test-common'))});
const { lib, functions } = ffi.dlopen(libraryPath, fixtureSymbols);
const callback = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
() => (${returnExpression}),
);
functions.call_int_callback(callback, 21);`,
@@ -294,7 +294,7 @@ const ffi = require('node:ffi');
const { fixtureSymbols, libraryPath } = require(${JSON.stringify(require.resolve('./ffi-test-common'))});
const { lib, functions } = ffi.dlopen(libraryPath, fixtureSymbols);
const callback = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
() => { ${callbackBody} },
);
functions.call_int_callback(callback, 21);`,
@@ -325,7 +325,7 @@ const ffi = require('node:ffi');
const { fixtureSymbols, libraryPath } = require(${JSON.stringify(require.resolve('./ffi-test-common'))});
const { lib } = ffi.dlopen(libraryPath, fixtureSymbols);
const callback = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
(value) => value * 2,
);
new Worker(${JSON.stringify(workerSource)}, { eval: true, workerData: callback });`,
@@ -359,7 +359,7 @@ test('ffi unrefCallback releases callback function', async () => {
let callback = () => 1;
const ref = new WeakRef(callback);
const pointer = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
callback,
);
@@ -383,7 +383,7 @@ test('ffi unrefCallback zero-fills narrow callback return', async () => {
let callback = () => 1;
const ref = new WeakRef(callback);
const pointer = lib.registerCallback(
{ parameters: ['i8'], result: 'i8' },
{ arguments: ['i8'], return: 'i8' },
callback,
);
@@ -406,7 +406,7 @@ test('ffi refCallback retains callback function', async () => {
try {
let callback = () => 1;
const ref = new WeakRef(callback);
const pointer = lib.registerCallback({ result: 'i32' }, callback);
const pointer = lib.registerCallback({ return: 'i32' }, callback);
lib.unrefCallback(pointer);
lib.refCallback(pointer);
+17 -33
View File
@@ -27,7 +27,7 @@ test('dlopen resolves symbols from the current process with null path', {
skip: common.isWindows,
}, () => {
const { lib, functions } = ffi.dlopen(null, {
uv_os_getpid: { result: 'i32', parameters: [] },
uv_os_getpid: { return: 'i32', arguments: [] },
});
try {
@@ -41,8 +41,8 @@ test('dlopen resolves symbols from the current process with null path', {
test('dlopen resolves functions from definitions', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i32: fixtureSymbols.add_i32,
add_f32: { returns: 'f32', arguments: ['f32', 'f32'] },
add_u64: { return: 'u64', parameters: ['u64', 'u64'] },
add_f32: { return: 'f32', arguments: ['f32', 'f32'] },
add_u64: { return: 'u64', arguments: ['u64', 'u64'] },
});
try {
@@ -73,7 +73,7 @@ test('DynamicLibrary exposes functions and symbols', () => {
try {
const addI32 = lib.getFunction('add_i32', fixtureSymbols.add_i32);
const addU64 = lib.getFunction('add_u64', {
returns: 'u64',
return: 'u64',
arguments: ['u64', 'u64'],
});
const addI32Ptr = lib.getSymbol('add_i32');
@@ -84,7 +84,7 @@ test('DynamicLibrary exposes functions and symbols', () => {
assert.strictEqual(addI32.pointer, addI32Ptr);
const functions = lib.getFunctions({
add_f32: { result: 'f32', parameters: ['f32', 'f32'] },
add_f32: { return: 'f32', arguments: ['f32', 'f32'] },
add_i64: { return: 'i64', arguments: ['i64', 'i64'] },
});
@@ -115,7 +115,7 @@ test('getFunction caches signatures consistently', () => {
);
assert.throws(() => {
lib.getFunction('add_i32', { parameters: ['u32', 'u32'], result: 'u32' });
lib.getFunction('add_i32', { arguments: ['u32', 'u32'], return: 'u32' });
}, /already requested with a different signature/);
} finally {
lib.close();
@@ -226,7 +226,7 @@ test('dynamic library APIs validate failures and bad signatures', () => {
assert.throws(() => {
ffi.dlopen(libraryPath, {
add_i32: fixtureSymbols.add_i32,
missing_symbol: { result: 'void', parameters: [] },
missing_symbol: { return: 'void', arguments: [] },
});
}, /dlsym failed:/);
@@ -245,7 +245,7 @@ test('dynamic library APIs validate failures and bad signatures', () => {
try {
assert.throws(() => {
lib.getFunction('missing_symbol', { result: 'void', parameters: [] });
lib.getFunction('missing_symbol', { return: 'void', arguments: [] });
}, /dlsym failed:/);
assert.throws(() => {
@@ -259,21 +259,21 @@ test('dynamic library APIs validate failures and bad signatures', () => {
assert.throws(() => {
lib.getFunctions({
add_i32: fixtureSymbols.add_i32,
missing_symbol: { result: 'void', parameters: [] },
missing_symbol: { return: 'void', arguments: [] },
});
}, /dlsym failed:/);
assert.strictEqual(lib.getFunction('add_i32', {
result: 'pointer',
parameters: ['pointer'],
return: 'pointer',
arguments: ['pointer'],
}).pointer, lib.getSymbol('add_i32'));
assert.throws(() => {
lib.getFunction('add_i32', { result: 'i32\0bad', parameters: [] });
lib.getFunction('add_i32', { return: 'i32\0bad', arguments: [] });
}, /Return value type of function add_i32 must not contain null bytes/);
assert.throws(() => {
lib.getFunction('add_i32', { result: 'i32', parameters: ['i32\0bad'] });
lib.getFunction('add_i32', { return: 'i32', arguments: ['i32\0bad'] });
}, /Argument 0 of function add_i32 must not contain null bytes/);
assert.throws(() => {
@@ -305,30 +305,14 @@ test('dynamic library APIs validate failures and bad signatures', () => {
});
assert.throws(() => {
lib.getFunction('add_i32', {
result: 'i32',
return: 'i32',
parameters: ['i32', 'i32'],
});
}, /must have either 'returns', 'return' or 'result' property/);
assert.throws(() => {
lib.getFunction('add_i32', {
result: 'i32',
parameters: ['i32', 'i32'],
arguments: ['i32', 'i32'],
});
}, /must have either 'parameters' or 'arguments' property/);
assert.throws(() => {
lib.getFunction('add_i32', { result: 'bogus', parameters: [] });
lib.getFunction('add_i32', { return: 'bogus', arguments: [] });
}, /Unsupported FFI type: bogus/);
const hasTrapError = new Error('signature has trap');
assert.throws(() => {
lib.getFunction('add_i32', new Proxy({}, {
has(target, key) {
if (key === 'result') {
if (key === 'return') {
throw hasTrapError;
}
return Reflect.has(target, key);
@@ -339,8 +323,8 @@ test('dynamic library APIs validate failures and bad signatures', () => {
const getterError = new Error('signature getter');
assert.throws(() => {
lib.getFunction('add_i32', {
result: 'i32',
get parameters() {
return: 'i32',
get arguments() {
throw getterError;
},
});
+90 -90
View File
@@ -19,8 +19,8 @@ const { internalBinding } = require('internal/test/binding');
const ffiBinding = internalBinding('ffi');
const {
kSbInvokeSlow,
kSbParams,
kSbResult,
kSbArguments,
kSbReturn,
kSbSharedBuffer,
} = ffiBinding;
const rawGetFunctionUnpatched = ffiBinding.DynamicLibrary.prototype.getFunction;
@@ -30,7 +30,7 @@ const { libraryPath } = require('./ffi-test-common');
test('numeric-only i32 function uses SB path', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
});
try {
assert.strictEqual(functions.add_i32(20, 22), 42);
@@ -44,10 +44,10 @@ test('numeric-only i32 function uses SB path', () => {
test('i8/u8/i16/u16 round-trip', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i8: { result: 'i8', parameters: ['i8', 'i8'] },
add_u8: { result: 'u8', parameters: ['u8', 'u8'] },
add_i16: { result: 'i16', parameters: ['i16', 'i16'] },
add_u16: { result: 'u16', parameters: ['u16', 'u16'] },
add_i8: { return: 'i8', arguments: ['i8', 'i8'] },
add_u8: { return: 'u8', arguments: ['u8', 'u8'] },
add_i16: { return: 'i16', arguments: ['i16', 'i16'] },
add_u16: { return: 'u16', arguments: ['u16', 'u16'] },
});
try {
assert.strictEqual(functions.add_i8(10, 20), 30);
@@ -61,8 +61,8 @@ test('i8/u8/i16/u16 round-trip', () => {
test('f32/f64 round-trip', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_f32: { result: 'f32', parameters: ['f32', 'f32'] },
add_f64: { result: 'f64', parameters: ['f64', 'f64'] },
add_f32: { return: 'f32', arguments: ['f32', 'f32'] },
add_f64: { return: 'f64', arguments: ['f64', 'f64'] },
});
try {
// 1.25 and 2.75 are exactly representable in float32, so the sum is exact.
@@ -75,8 +75,8 @@ test('f32/f64 round-trip', () => {
test('i64/u64 BigInt round-trip', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i64: { result: 'i64', parameters: ['i64', 'i64'] },
add_u64: { result: 'u64', parameters: ['u64', 'u64'] },
add_i64: { return: 'i64', arguments: ['i64', 'i64'] },
add_u64: { return: 'u64', arguments: ['u64', 'u64'] },
});
try {
assert.strictEqual(functions.add_i64(10n, 20n), 30n);
@@ -88,7 +88,7 @@ test('i64/u64 BigInt round-trip', () => {
test('zero-arg function', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
char_is_signed: { result: 'i32', parameters: [] },
char_is_signed: { return: 'i32', arguments: [] },
});
try {
const result = functions.char_is_signed();
@@ -101,7 +101,7 @@ test('zero-arg function', () => {
test('6-arg numeric function', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
sum_6_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32'] },
sum_6_i32: { return: 'i32', arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32'] },
});
try {
assert.strictEqual(functions.sum_6_i32(1, 2, 3, 4, 5, 6), 21);
@@ -112,8 +112,8 @@ test('6-arg numeric function', () => {
test('pointer args: fast path (BigInt/null) and slow-path fallback (Buffer/ArrayBuffer)', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
identity_pointer: { result: 'pointer', parameters: ['pointer'] },
pointer_to_usize: { result: 'u64', parameters: ['pointer'] },
identity_pointer: { return: 'pointer', arguments: ['pointer'] },
pointer_to_usize: { return: 'u64', arguments: ['pointer'] },
});
try {
assert.strictEqual(functions.identity_pointer(0n), 0n);
@@ -137,7 +137,7 @@ test('pointer args: fast path (BigInt/null) and slow-path fallback (Buffer/Array
test('string pointer uses slow-path fallback', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
string_length: { result: 'u64', parameters: ['pointer'] },
string_length: { return: 'u64', arguments: ['pointer'] },
});
try {
assert.strictEqual(functions.string_length('hello'), 5n);
@@ -150,8 +150,8 @@ test('string pointer uses slow-path fallback', () => {
test('non-SB-eligible signature falls back to raw function', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
string_duplicate: { result: 'pointer', parameters: ['pointer'] },
free_string: { result: 'void', parameters: ['pointer'] },
string_duplicate: { return: 'pointer', arguments: ['pointer'] },
free_string: { return: 'void', arguments: ['pointer'] },
});
try {
const dup = functions.string_duplicate('round-trip');
@@ -167,14 +167,14 @@ test('reentrancy across two FFI symbols', () => {
// A JS callback invoked by one FFI function reenters a different FFI
// function. Each has its own ArrayBuffer; neither may clobber the other.
const { lib, functions } = ffi.dlopen(libraryPath, {
call_int_callback: { result: 'i32', parameters: ['pointer', 'i32'] },
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
call_int_callback: { return: 'i32', arguments: ['pointer', 'i32'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
});
let callDepth = 0;
let innerResult = -1;
const callback = lib.registerCallback(
{ result: 'i32', parameters: ['i32'] },
{ return: 'i32', arguments: ['i32'] },
(x) => {
callDepth++;
if (callDepth === 1) innerResult = functions.add_i32(x, 100);
@@ -194,7 +194,7 @@ test('reentrancy across two FFI symbols', () => {
test('arity mismatch throws ERR_INVALID_ARG_VALUE', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
});
try {
assert.throws(() => functions.add_i32(1), {
@@ -213,8 +213,8 @@ test('arity mismatch throws ERR_INVALID_ARG_VALUE', () => {
test('arity 7+ uses the generic rest-params branch', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
sum_7_i32: {
result: 'i32',
parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
return: 'i32',
arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
},
});
try {
@@ -230,8 +230,8 @@ test('arity 7+ uses the generic rest-params branch', () => {
test('wrappers preserve name/length/pointer and the functions accessor returns wrappers', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
identity_pointer: { result: 'pointer', parameters: ['pointer'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
identity_pointer: { return: 'pointer', arguments: ['pointer'] },
});
try {
assert.strictEqual(functions.add_i32.name, 'add_i32');
@@ -253,12 +253,12 @@ test('wrappers preserve name/length/pointer and the functions accessor returns w
test('integer boundaries for i8/u8/i16/u16/i32/u32', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i8: { result: 'i8', parameters: ['i8', 'i8'] },
add_u8: { result: 'u8', parameters: ['u8', 'u8'] },
add_i16: { result: 'i16', parameters: ['i16', 'i16'] },
add_u16: { result: 'u16', parameters: ['u16', 'u16'] },
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
add_u32: { result: 'u32', parameters: ['u32', 'u32'] },
add_i8: { return: 'i8', arguments: ['i8', 'i8'] },
add_u8: { return: 'u8', arguments: ['u8', 'u8'] },
add_i16: { return: 'i16', arguments: ['i16', 'i16'] },
add_u16: { return: 'u16', arguments: ['u16', 'u16'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
add_u32: { return: 'u32', arguments: ['u32', 'u32'] },
});
try {
@@ -299,8 +299,8 @@ test('integer boundaries for i8/u8/i16/u16/i32/u32', () => {
test('i64/u64 BigInt boundaries and Number/BigInt type mismatches', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i64: { result: 'i64', parameters: ['i64', 'i64'] },
add_u64: { result: 'u64', parameters: ['u64', 'u64'] },
add_i64: { return: 'i64', arguments: ['i64', 'i64'] },
add_u64: { return: 'u64', arguments: ['u64', 'u64'] },
});
try {
@@ -328,8 +328,8 @@ test('i64/u64 BigInt boundaries and Number/BigInt type mismatches', () => {
test('char type picks signed/unsigned range based on host ABI', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
char_is_signed: { result: 'i32', parameters: [] },
identity_char: { result: 'char', parameters: ['char'] },
char_is_signed: { return: 'i32', arguments: [] },
identity_char: { return: 'char', arguments: ['char'] },
});
try {
@@ -358,13 +358,13 @@ test('SB metadata is Symbol-keyed, attribute-hardened, and not leaked onto the w
const rawLib = new ffiBinding.DynamicLibrary(libraryPath);
try {
const rawFn = rawGetFunctionUnpatched.call(
rawLib, 'add_i32', { result: 'i32', parameters: ['i32', 'i32'] });
rawLib, 'add_i32', { return: 'i32', arguments: ['i32', 'i32'] });
for (const [name, sym] of [
['kSbSharedBuffer', kSbSharedBuffer],
['kSbInvokeSlow', kSbInvokeSlow],
['kSbParams', kSbParams],
['kSbResult', kSbResult],
['kSbArguments', kSbArguments],
['kSbReturn', kSbReturn],
]) {
assert.strictEqual(typeof sym, 'symbol', `${name} must be a Symbol`);
}
@@ -372,8 +372,8 @@ test('SB metadata is Symbol-keyed, attribute-hardened, and not leaked onto the w
// Numeric-only signature: kSbInvokeSlow absent; the rest present and hardened.
for (const [name, sym] of [
['kSbSharedBuffer', kSbSharedBuffer],
['kSbParams', kSbParams],
['kSbResult', kSbResult],
['kSbArguments', kSbArguments],
['kSbReturn', kSbReturn],
]) {
const desc = Object.getOwnPropertyDescriptor(rawFn, sym);
assert.ok(desc !== undefined, `${name} missing on pure-numeric SB function`);
@@ -386,7 +386,7 @@ test('SB metadata is Symbol-keyed, attribute-hardened, and not leaked onto the w
// Pointer signature: kSbInvokeSlow must exist (and be hardened).
const rawPtrFn = rawGetFunctionUnpatched.call(
rawLib, 'identity_pointer', { result: 'pointer', parameters: ['pointer'] });
rawLib, 'identity_pointer', { return: 'pointer', arguments: ['pointer'] });
const slowDesc = Object.getOwnPropertyDescriptor(rawPtrFn, kSbInvokeSlow);
assert.ok(slowDesc !== undefined);
assert.strictEqual(slowDesc.enumerable, false);
@@ -396,18 +396,18 @@ test('SB metadata is Symbol-keyed, attribute-hardened, and not leaked onto the w
assert.deepStrictEqual(Object.keys(rawFn), ['pointer']);
const ownSyms = Object.getOwnPropertySymbols(rawFn);
assert.ok(ownSyms.includes(kSbSharedBuffer));
assert.ok(ownSyms.includes(kSbParams));
assert.ok(ownSyms.includes(kSbResult));
assert.ok(ownSyms.includes(kSbArguments));
assert.ok(ownSyms.includes(kSbReturn));
// Internals must not be forwarded by `inheritMetadata`.
const { lib, functions } = ffi.dlopen(libraryPath, {
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
});
try {
assert.strictEqual(functions.add_i32[kSbSharedBuffer], undefined);
assert.strictEqual(functions.add_i32[kSbInvokeSlow], undefined);
assert.strictEqual(functions.add_i32[kSbParams], undefined);
assert.strictEqual(functions.add_i32[kSbResult], undefined);
assert.strictEqual(functions.add_i32[kSbArguments], undefined);
assert.strictEqual(functions.add_i32[kSbReturn], undefined);
} finally {
lib.close();
}
@@ -418,7 +418,7 @@ test('SB metadata is Symbol-keyed, attribute-hardened, and not leaked onto the w
test('pointer fast-path range check: [0, 2^64 - 1]', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
identity_pointer: { result: 'pointer', parameters: ['pointer'] },
identity_pointer: { return: 'pointer', arguments: ['pointer'] },
});
try {
assert.strictEqual(functions.identity_pointer(0n), 0n);
@@ -438,15 +438,15 @@ test('self-recursive reentrancy: a single function\'s ArrayBuffer survives a nes
// call can reuse the same buffer without clobbering the outer frame.
const { lib, functions } = ffi.dlopen(libraryPath, {
call_binary_int_callback: {
result: 'i32',
parameters: ['function', 'i32', 'i32'],
return: 'i32',
arguments: ['function', 'i32', 'i32'],
},
});
try {
let depth = 0;
const callback = lib.registerCallback(
{ result: 'i32', parameters: ['i32', 'i32'] },
{ return: 'i32', arguments: ['i32', 'i32'] },
common.mustCall((a, b) => {
depth++;
if (depth === 1) {
@@ -469,9 +469,9 @@ test('self-recursive reentrancy: a single function\'s ArrayBuffer survives a nes
test('void-return 0-arg wrapper branch', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
reset_counter: { result: 'void', parameters: [] },
increment_counter: { result: 'void', parameters: [] },
get_counter: { result: 'i32', parameters: [] },
reset_counter: { return: 'void', arguments: [] },
increment_counter: { return: 'void', arguments: [] },
get_counter: { return: 'i32', arguments: [] },
});
try {
assert.strictEqual(functions.reset_counter(), undefined);
@@ -496,26 +496,26 @@ test('void-return wrapper at every specialized arity observes side effects', ()
// at every arity the ladder specializes (1..6) plus the 7+ rest-params
// fallback.
const { lib, functions } = ffi.dlopen(libraryPath, {
store_i32: { result: 'void', parameters: ['i32'] },
store_sum_2_i32: { result: 'void', parameters: ['i32', 'i32'] },
store_sum_3_i32: { result: 'void', parameters: ['i32', 'i32', 'i32'] },
store_i32: { return: 'void', arguments: ['i32'] },
store_sum_2_i32: { return: 'void', arguments: ['i32', 'i32'] },
store_sum_3_i32: { return: 'void', arguments: ['i32', 'i32', 'i32'] },
store_sum_4_i32: {
result: 'void',
parameters: ['i32', 'i32', 'i32', 'i32'],
return: 'void',
arguments: ['i32', 'i32', 'i32', 'i32'],
},
store_sum_5_i32: {
result: 'void',
parameters: ['i32', 'i32', 'i32', 'i32', 'i32'],
return: 'void',
arguments: ['i32', 'i32', 'i32', 'i32', 'i32'],
},
store_sum_6_i32: {
result: 'void',
parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
return: 'void',
arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
},
store_sum_8_i32: {
result: 'void',
parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
return: 'void',
arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
},
get_scratch: { result: 'i32', parameters: [] },
get_scratch: { return: 'i32', arguments: [] },
});
try {
// Powers-of-two summands detect a dropped or duplicated slot at each
@@ -598,17 +598,17 @@ test('value-return wrapper arity mismatch hits every specialized branch', () =>
// value-return closures for arities 1..6 so each specialization's
// argument-count guard runs at least once.
const { lib, functions } = ffi.dlopen(libraryPath, {
logical_not: { result: 'i32', parameters: ['i32'] },
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
sum_3_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32'] },
sum_4_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32', 'i32'] },
logical_not: { return: 'i32', arguments: ['i32'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
sum_3_i32: { return: 'i32', arguments: ['i32', 'i32', 'i32'] },
sum_4_i32: { return: 'i32', arguments: ['i32', 'i32', 'i32', 'i32'] },
sum_five_i32: {
result: 'i32',
parameters: ['i32', 'i32', 'i32', 'i32', 'i32'],
return: 'i32',
arguments: ['i32', 'i32', 'i32', 'i32', 'i32'],
},
sum_6_i32: {
result: 'i32',
parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
return: 'i32',
arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32'],
},
});
try {
@@ -647,7 +647,7 @@ test('pointer-dispatch wrapper rejects wrong-arity calls', () => {
// per-arity ladder, but it still has its own `throwFFIArgCountError`
// branch that needs to be exercised.
const { lib, functions } = ffi.dlopen(libraryPath, {
identity_pointer: { result: 'pointer', parameters: ['pointer'] },
identity_pointer: { return: 'pointer', arguments: ['pointer'] },
});
try {
assert.throws(
@@ -669,10 +669,10 @@ test('pointer-dispatch wrapper rejects wrong-arity calls', () => {
test('mid-arity wrappers (1, 3, 4, 5)', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
logical_not: { result: 'i32', parameters: ['i32'] },
sum_3_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32'] },
sum_4_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32', 'i32'] },
sum_five_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32', 'i32', 'i32'] },
logical_not: { return: 'i32', arguments: ['i32'] },
sum_3_i32: { return: 'i32', arguments: ['i32', 'i32', 'i32'] },
sum_4_i32: { return: 'i32', arguments: ['i32', 'i32', 'i32', 'i32'] },
sum_five_i32: { return: 'i32', arguments: ['i32', 'i32', 'i32', 'i32', 'i32'] },
});
try {
assert.strictEqual(functions.logical_not(0), 1);
@@ -689,8 +689,8 @@ test('mid-arity wrappers (1, 3, 4, 5)', () => {
test('float specials: NaN, ±Infinity, -0 round-trip bit-exact', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
add_f64: { result: 'f64', parameters: ['f64', 'f64'] },
multiply_f64: { result: 'f64', parameters: ['f64', 'f64'] },
add_f64: { return: 'f64', arguments: ['f64', 'f64'] },
multiply_f64: { return: 'f64', arguments: ['f64', 'f64'] },
});
try {
assert.ok(Number.isNaN(functions.add_f64(NaN, 1.0)));
@@ -704,7 +704,7 @@ test('float specials: NaN, ±Infinity, -0 round-trip bit-exact', () => {
test('arity-7+ branch still runs per-arg validation', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
sum_7_i32: { result: 'i32', parameters: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'] },
sum_7_i32: { return: 'i32', arguments: ['i32', 'i32', 'i32', 'i32', 'i32', 'i32', 'i32'] },
});
try {
assert.throws(
@@ -720,7 +720,7 @@ test('mixed-kind signature (i32, f32, f64, u32) dispatches the right writer per
// Four distinct `sbTypeInfo.kind` values (int, float, float, int) — a
// wiring bug that reused one writer across slots would surface here.
const { lib, functions } = ffi.dlopen(libraryPath, {
mixed_operation: { parameters: ['i32', 'f32', 'f64', 'u32'], result: 'f64' },
mixed_operation: { arguments: ['i32', 'f32', 'f64', 'u32'], return: 'f64' },
});
try {
@@ -748,11 +748,11 @@ test('lib.getFunctions() with no arguments wraps every cached function', () => {
// the early-return path in `wrapWithSharedBuffer` alongside the wrapped
// branch.
const { lib } = ffi.dlopen(libraryPath, {
add_i32: { result: 'i32', parameters: ['i32', 'i32'] },
add_f64: { result: 'f64', parameters: ['f64', 'f64'] },
mixed_operation: { parameters: ['i32', 'f32', 'f64', 'u32'], result: 'f64' },
identity_pointer: { result: 'pointer', parameters: ['pointer'] },
string_length: { result: 'u64', parameters: ['string'] },
add_i32: { return: 'i32', arguments: ['i32', 'i32'] },
add_f64: { return: 'f64', arguments: ['f64', 'f64'] },
mixed_operation: { arguments: ['i32', 'f32', 'f64', 'u32'], return: 'f64' },
identity_pointer: { return: 'pointer', arguments: ['pointer'] },
string_length: { return: 'u64', arguments: ['string'] },
});
try {
@@ -789,12 +789,12 @@ test('lib.getFunctions() with no arguments wraps every cached function', () => {
test('mixed pointer + numeric signature uses the pointer-dispatch wrapper', () => {
const { lib, functions } = ffi.dlopen(libraryPath, {
call_int_callback: { result: 'i32', parameters: ['pointer', 'i32'] },
call_int_callback: { return: 'i32', arguments: ['pointer', 'i32'] },
});
try {
const cb = lib.registerCallback(
{ result: 'i32', parameters: ['i32'] },
{ return: 'i32', arguments: ['i32'] },
(x) => x * 2,
);
try {
+2 -2
View File
@@ -15,7 +15,7 @@ test('ffi unrefCallback releases callback function', async (t) => {
let callback = () => 1;
const ref = new WeakRef(callback);
const pointer = lib.registerCallback(
{ parameters: ['i32'], result: 'i32' },
{ arguments: ['i32'], return: 'i32' },
callback,
);
@@ -37,7 +37,7 @@ test('ffi refCallback retains callback function', async (t) => {
let callback = () => 1;
const ref = new WeakRef(callback);
const pointer = lib.registerCallback({ result: 'i32' }, callback);
const pointer = lib.registerCallback({ return: 'i32' }, callback);
lib.unrefCallback(pointer);
lib.refCallback(pointer);