Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions doc/api/ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ 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.
* `result` {string} A [type name][type names] specifying the return type of the
function or callback. **Default:** `'void'`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I can cast a vote for keeping return instead of result, I'd like to do so – that's just generally more in line with what the standardized terminology around function signatures is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if I have to pick one I'd go for return and arguments.
@Renegade334 Are you fine with doing so?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine by me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if @addaleax concurs I think you can continue with the changes.

* `parameters` {string\[]} An array of [type names][] specifying the parameter
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'],
Expand Down Expand Up @@ -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
19 changes: 4 additions & 15 deletions lib/internal/ffi-shared-buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -542,19 +542,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
Expand All @@ -567,7 +554,7 @@ 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));
return wrapWithSharedBuffer(raw, sig.parameters ?? [], sig.result ?? 'void');
};

DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
Expand All @@ -589,7 +576,9 @@ DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
out[name] = wrapWithSharedBuffer(raw[name]);
} else {
const sig = definitions[name];
out[name] = wrapWithSharedBuffer(raw[name], sigParams(sig), sigResult(sig));
out[name] = wrapWithSharedBuffer(raw[name],
sig.parameters ?? [],
sig.result ?? 'void');
}
}
return out;
Expand Down
3 changes: 0 additions & 3 deletions src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@
V(allow_unknown_named_params_string, "allowUnknownNamedParameters") \
V(alpn_callback_string, "ALPNCallback") \
V(args_string, "args") \
V(arguments_string, "arguments") \
V(async_ids_stack_string, "async_ids_stack") \
V(attributes_string, "attributes") \
V(backup_string, "backup") \
Expand Down Expand Up @@ -329,8 +328,6 @@
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(salt_length_string, "saltLength") \
V(search_string, "search") \
Expand Down
50 changes: 6 additions & 44 deletions src/ffi/types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -83,42 +83,14 @@ 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) ||
!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);
if (!signature->Has(context, result_key).To(&has_result) ||
!signature->Has(context, parameters_key).To(&has_parameters)) {
return {};
}

Expand All @@ -128,18 +100,9 @@ Maybe<FunctionSignature> ParseFunctionSignature(Environment* env,
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_result) {
Local<Value> return_type_val;
if (!signature->Get(context, return_type_key).ToLocal(&return_type_val)) {
if (!signature->Get(context, result_key).ToLocal(&return_type_val)) {
return {};
}

Expand All @@ -162,10 +125,9 @@ Maybe<FunctionSignature> ParseFunctionSignature(Environment* env,
return_type_name = return_type_str.ToString();
}

if (has_arguments || has_parameters) {
if (has_parameters) {
Local<Value> arguments_val;
if (!signature->Get(context, has_arguments ? arguments_key : parameters_key)
.ToLocal(&arguments_val)) {
if (!signature->Get(context, parameters_key).ToLocal(&arguments_val)) {
return {};
}

Expand Down
2 changes: 1 addition & 1 deletion test/ffi/test-ffi-calls.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ test('ffi callbacks can be registered and invoked', () => {
(ptr) => seen.push(ffi.toString(ptr)),
);
const binaryCallback = lib.registerCallback(
{ arguments: ['i32', 'i32'], returns: 'i32' },
{ parameters: ['i32', 'i32'], result: 'i32' },
(a, b) => a + b,
);

Expand Down
26 changes: 5 additions & 21 deletions test/ffi/test-ffi-dynamic-library.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: { result: 'f32', parameters: ['f32', 'f32'] },
add_u64: { result: 'u64', parameters: ['u64', 'u64'] },
});

try {
Expand Down Expand Up @@ -73,8 +73,8 @@ test('DynamicLibrary exposes functions and symbols', () => {
try {
const addI32 = lib.getFunction('add_i32', fixtureSymbols.add_i32);
const addU64 = lib.getFunction('add_u64', {
returns: 'u64',
arguments: ['u64', 'u64'],
result: 'u64',
parameters: ['u64', 'u64'],
});
const addI32Ptr = lib.getSymbol('add_i32');

Expand All @@ -85,7 +85,7 @@ test('DynamicLibrary exposes functions and symbols', () => {

const functions = lib.getFunctions({
add_f32: { result: 'f32', parameters: ['f32', 'f32'] },
add_i64: { return: 'i64', arguments: ['i64', 'i64'] },
add_i64: { result: 'i64', parameters: ['i64', 'i64'] },
});

assert.strictEqual(functions.add_f32(10, 32), 42);
Expand Down Expand Up @@ -304,22 +304,6 @@ test('dynamic library APIs validate failures and bad signatures', () => {
message: 'Signature of function add_i32 must be an object',
});

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: [] });
}, /Unsupported FFI type: bogus/);
Expand Down
Loading