From a88fe4cd0a76ff4ebcb441a095558674f65e7ef5 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Wed, 5 Aug 2026 07:59:59 -0700 Subject: [PATCH] ffi: refresh cached string buffers on every call Native code can mutate temporary string storage during an FFI call. Rewrite cached buffers on every conversion so a later call with the same JavaScript string receives a fresh copy of its UTF-8 bytes. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol --- lib/internal/ffi/fast-api.js | 5 ----- test/ffi/fixture_library/ffi_test_library.c | 4 ++++ test/ffi/test-ffi-fast-buffer.js | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/internal/ffi/fast-api.js b/lib/internal/ffi/fast-api.js index 486a119a2e07..1328c8c4238a 100644 --- a/lib/internal/ffi/fast-api.js +++ b/lib/internal/ffi/fast-api.js @@ -134,9 +134,6 @@ function getStringConversionPointer(state, value, index) { const size = value.length * 3 + 1; const buffers = state.buffers[state.depth - 1]; let entry = buffers[index]; - if (entry !== undefined && entry.string === value) { - return entry.pointer; - } if (StringPrototypeIncludes(value, '\0')) { throwFFIArgError(`Argument ${index} must not contain null bytes`); } @@ -146,7 +143,6 @@ function getStringConversionPointer(state, value, index) { __proto__: null, buffer, pointer: getRawPointer(buffer), - string: undefined, }; buffers[index] = entry; } @@ -154,7 +150,6 @@ function getStringConversionPointer(state, value, index) { const buffer = entry.buffer; const written = buffer.write(value, 0, size - 1, 'utf8'); buffer[written] = 0; - entry.string = value; return entry.pointer; } diff --git a/test/ffi/fixture_library/ffi_test_library.c b/test/ffi/fixture_library/ffi_test_library.c index 10d2ed9f66c9..85fca8eb54fd 100644 --- a/test/ffi/fixture_library/ffi_test_library.c +++ b/test/ffi/fixture_library/ffi_test_library.c @@ -108,6 +108,10 @@ FFI_EXPORT uint8_t string_equals_hello(const char* str) { return str && strcmp(str, "hello") == 0; } +FFI_EXPORT char* overwrite_string(char* str, int32_t value, uint64_t length) { + return memset(str, value, (size_t)length); +} + FFI_EXPORT char* string_concat(const char* a, const char* b) { if (!a || !b) { // NOLINTNEXTLINE (readability/null_usage) diff --git a/test/ffi/test-ffi-fast-buffer.js b/test/ffi/test-ffi-fast-buffer.js index e4399ee8ee47..e939b47691ce 100644 --- a/test/ffi/test-ffi-fast-buffer.js +++ b/test/ffi/test-ffi-fast-buffer.js @@ -96,6 +96,24 @@ test('fast FFI string buffers survive reentrant callbacks', { } }); +test('fast FFI refreshes cached temporary string buffers', () => { + const lib = new ffi.DynamicLibrary(libraryPath); + const overwriteString = lib.getFunction('overwrite_string', { + arguments: ['string', 'i32', 'u64'], + return: 'pointer', + }); + + try { + const mutated = overwriteString('hello', 0x79, 1n); + assert.strictEqual(ffi.toString(mutated), 'yello'); + + const refreshed = overwriteString('hello', 0x79, 0n); + assert.strictEqual(ffi.toString(refreshed), 'hello'); + } finally { + lib.close(); + } +}); + test('optimized buffer signatures preserve pointer-like conversions', () => { const lib = new ffi.DynamicLibrary(libraryPath); const asBuffer = lib.getFunction('pointer_to_usize', {