Version
main
Platform
Subsystem
ffi
What steps will reproduce the bug?
repro.c
#include <stdint.h>
#include <string.h>
int32_t inner(const char *text) {
return (int32_t)strlen(text);
}
int32_t outer(const char *text, void (*callback)(void)) {
callback();
return strcmp(text, "OUTER") == 0;
}
Compile
$ clang -dynamiclib repro.c -o librepro.dylib
repro.js
import { dlopen } from 'node:ffi';
const { lib, functions } = dlopen('./librepro.dylib', {
inner: {
arguments: ['string'],
return: 'i32',
},
// `pointer` keeps this function eligible for the Fast API path.
outer: {
arguments: ['string', 'pointer'],
return: 'i32',
},
});
let nestedLength;
const callback = lib.registerCallback(() => {
nestedLength = functions.inner('INNER');
});
console.log(
'outer string unchanged:',
functions.outer('OUTER', callback),
);
console.log('nested length:', nestedLength);
lib.unregisterCallback(callback);
lib.close();
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
outer string unchanged: 1
nested length: 5
The outer string remains valid and unchanged for the entire native call.
What do you see instead?
outer string unchanged: 0
nested length: 5
The nested call overwrites the outer call’s argument-0 string buffer, so native code sees "INNER" instead of "OUTER"
Additional information
No response
Version
main
Platform
Subsystem
ffi
What steps will reproduce the bug?
repro.cCompile
repro.jsHow often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
The outer string remains valid and unchanged for the entire native call.
What do you see instead?
The nested call overwrites the outer call’s argument-0 string buffer, so native code sees
"INNER"instead of"OUTER"Additional information
No response