From 5fc8a83a7aec0fc2ee5bdb7a8db1177b6ea2eb3c Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Fri, 17 Jul 2026 09:13:21 -0700 Subject: [PATCH] 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 --- lib/ffi.js | 18 ++++------ test/ffi/test-ffi-dynamic-library.js | 54 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/lib/ffi.js b/lib/ffi.js index d91b6d4521e679..f35ebae3e312ab 100644 --- a/lib/ffi.js +++ b/lib/ffi.js @@ -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) { @@ -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) { @@ -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; }; @@ -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; }, diff --git a/test/ffi/test-ffi-dynamic-library.js b/test/ffi/test-ffi-dynamic-library.js index ba0f8a383ffd8b..88897be6eaec74 100644 --- a/test/ffi/test-ffi-dynamic-library.js +++ b/test/ffi/test-ffi-dynamic-library.js @@ -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);