mirror of
https://github.com/nodejs/node.git
synced 2026-10-29 02:35:20 +08:00
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:
10 files changed
+254
-343
No files matched your search
+20
-19
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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(¶ms_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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user