Visitar URL original
ffi: evaluate function signatures once by trivikr · Pull Request #64559 · nodejs/node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
ffi: evaluate function signatures once
Function signatures were read by the native implementation and then
read again while creating JavaScript wrappers. Accessors, proxies,
or mutations could therefore produce different native and JavaScript
signatures.

Build wrappers from signature metadata captured by native code instead.

Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: openai:gpt-5.6-sol
  • Loading branch information
trivikr committed Jul 17, 2026
commit 5fc8a83a7aec0fc2ee5bdb7a8db1177b6ea2eb3c
18 changes: 7 additions & 11 deletions lib/ffi.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ function makeSignature(argumentTypes, returnType) {
};
}

function wrapFFIFunction(rawFn, argumentTypes, returnType, owner) {
if (argumentTypes === undefined && rawFn !== undefined && rawFn !== null) {
function wrapFFIFunction(rawFn, owner) {
let argumentTypes;
let returnType;
if (rawFn !== undefined && rawFn !== null) {
const sbArguments = rawFn[kSbArguments];
argumentTypes = sbArguments ?? rawFn[kFastArguments];
if (sbArguments !== undefined) {
Expand All @@ -103,7 +105,7 @@ const rawGetFunctions = DynamicLibrary.prototype.getFunctions;

DynamicLibrary.prototype.getFunction = function getFunction(name, signature) {
const raw = FunctionPrototypeCall(rawGetFunction, this, name, signature);
return wrapFFIFunction(raw, signature.arguments, signature.return, this);
return wrapFFIFunction(raw, this);
};

DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
Expand All @@ -115,13 +117,7 @@ DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
const out = { __proto__: null };
for (let i = 0; i < keys.length; i++) {
const name = keys[i];
if (definitions === undefined) {
out[name] = wrapFFIFunction(raw[name], undefined, undefined, this);
} else {
const signature = definitions[name];
out[name] = wrapFFIFunction(
raw[name], signature.arguments, signature.return, this);
}
out[name] = wrapFFIFunction(raw[name], this);
}
return out;
};
Expand All @@ -145,7 +141,7 @@ DynamicLibrary.prototype.getFunctions = function getFunctions(definitions) {
const keys = ObjectKeys(raw);
for (let i = 0; i < keys.length; i++) {
const name = keys[i];
wrapped[name] = wrapFFIFunction(raw[name], undefined, undefined, this);
wrapped[name] = wrapFFIFunction(raw[name], this);
}
return wrapped;
},
Expand Down
54 changes: 54 additions & 0 deletions test/ffi/test-ffi-dynamic-library.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,60 @@ test('DynamicLibrary exposes functions and symbols', () => {
}
});

test('DynamicLibrary evaluates function signatures once', () => {
function makeChangingSignature() {
const reads = { arguments: 0, return: 0 };
return {
reads,
signature: {
get arguments() {
reads.arguments++;
return reads.arguments === 1 ?
Array(8).fill('i32') : ['i32'];
},
get return() {
reads.return++;
return 'i32';
},
},
};
}

{
const lib = new ffi.DynamicLibrary(libraryPath);
const { reads, signature } = makeChangingSignature();

try {
const fn = lib.getFunction('sum_8_i32', signature);
assert.strictEqual(fn(1, 2, 3, 4, 5, 6, 7, 8), 36);
assert.deepStrictEqual(reads, { arguments: 1, return: 1 });
} finally {
lib.close();
}
}

{
const lib = new ffi.DynamicLibrary(libraryPath);
const { reads, signature } = makeChangingSignature();
let definitionReads = 0;
const definitions = {
get sum_8_i32() {
definitionReads++;
return signature;
},
};

try {
const { sum_8_i32: fn } = lib.getFunctions(definitions);
assert.strictEqual(fn(1, 2, 3, 4, 5, 6, 7, 8), 36);
assert.strictEqual(definitionReads, 1);
assert.deepStrictEqual(reads, { arguments: 1, return: 1 });
} finally {
lib.close();
}
}
});

test('getFunction caches signatures consistently', () => {
const lib = new ffi.DynamicLibrary(libraryPath);

Expand Down
Loading