Version
main (ad7a5b8)
Platform
Darwin 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:28:34 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T6041 arm64
Subsystem
ffi
What steps will reproduce the bug?
// node --experimental-ffi repro.js
const { getRawPointer, setFloat64, getFloat64 } = require('node:ffi');
const ptr = getRawPointer(Buffer.alloc(8));
for (const value of ['1.5', true, {}, null]) {
setFloat64(ptr, 0, value);
console.log(`setFloat64(${String(value)}) ->`, getFloat64(ptr, 0));
}
try {
setFloat64(ptr, 0, { valueOf() { throw new RangeError('boom'); } });
} catch (err) {
console.log('valueOf threw ->', err.name, err.message);
}
How often does it reproduce? Is there a required condition?
Always. setFloat32() behaves the same way.
What is the expected behavior? Why is that the expected behavior?
setFloat32() and setFloat64() should reject a value that is not a number, like every other setter in the module, and they should not replace an exception the value itself threw.
The same double is type-checked on the argument path, where ToFFIArgument() requires IsNumber() and throws Argument %s must be a double, and the documentation says the setters "validate the supplied JavaScript value against the target native type before writing it into memory".
What do you see instead?
setFloat64(1.5) -> 1.5
setFloat64(true) -> 1
setFloat64([object Object]) -> NaN
setFloat64(null) -> 0
valueOf threw -> TypeError Value must be a number
The integer branches of SetValue<T>() require IsNumber() before any range check, but the floating-point branch calls ToNumber() and casts the result. When ToNumber() itself fails, the ERR_INVALID_ARG_VALUE overwrites the exception it left pending, so the RangeError never reaches the caller. DataView.prototype.setFloat64() and Buffer.prototype.writeDoubleLE() both propagate it.
|
} else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) { |
|
MaybeLocal<Number> number = value->ToNumber(context); |
|
Local<Number> number_local; |
|
|
|
if (!number.ToLocal(&number_local)) { |
|
THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a number"); |
|
return; |
|
} |
|
|
|
converted = static_cast<T>(number_local->Value()); |
Additional information
The setters write straight into native memory, so a '1,5' typo from a config file stores NaN in a struct field with no error at the call site. setInt32() throws on the same mistake.
DataView and Buffer coerce here too, but they coerce for integers as well (dv.setInt8(0, 300) writes 44), while node:ffi rejects those.
The integer validation, the float coercion and the documentation sentence all landed together in d0fa608, the floating-point branch has not been modified since, and I found no review comment about the difference in the pull requests that touched it.
Refs: #62072
Refs: #62762
Refs: #62858
Refs: #65342
Version
main (ad7a5b8)
Platform
Subsystem
ffi
What steps will reproduce the bug?
How often does it reproduce? Is there a required condition?
Always.
setFloat32()behaves the same way.What is the expected behavior? Why is that the expected behavior?
setFloat32()andsetFloat64()should reject a value that is not anumber, like every other setter in the module, and they should not replace an exception the value itself threw.The same
doubleis type-checked on the argument path, whereToFFIArgument()requiresIsNumber()and throwsArgument %s must be a double, and the documentation says the setters "validate the supplied JavaScript value against the target native type before writing it into memory".What do you see instead?
The integer branches of
SetValue<T>()requireIsNumber()before any range check, but the floating-point branch callsToNumber()and casts the result. WhenToNumber()itself fails, theERR_INVALID_ARG_VALUEoverwrites the exception it left pending, so theRangeErrornever reaches the caller.DataView.prototype.setFloat64()andBuffer.prototype.writeDoubleLE()both propagate it.node/src/ffi/data.cc
Lines 402 to 411 in ad7a5b8
Additional information
The setters write straight into native memory, so a
'1,5'typo from a config file storesNaNin a struct field with no error at the call site.setInt32()throws on the same mistake.DataViewandBuffercoerce here too, but they coerce for integers as well (dv.setInt8(0, 300)writes44), whilenode:ffirejects those.The integer validation, the float coercion and the documentation sentence all landed together in d0fa608, the floating-point branch has not been modified since, and I found no review comment about the difference in the pull requests that touched it.
Refs: #62072
Refs: #62762
Refs: #62858
Refs: #65342