Skip to content

ffi: memory helpers return undefined instead of throwing when a required argument is omitted #65499

Description

@soulee-dev

Version

main (a48e33f)

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 ffi = require('node:ffi');

console.log(ffi.getUint8());    // pointer omitted
console.log(ffi.setUint8());    // pointer omitted
console.log(ffi.toBuffer(1n));  // length omitted

// The same arguments, passed explicitly.
try { ffi.getUint8(undefined); } catch (err) { console.log(err.message); }
try { ffi.toBuffer(1n, undefined); } catch (err) { console.log(err.message); }

How often does it reproduce? Is there a required condition?

Always. All 22 helpers behave the same way: the ten getters, the ten setters, toBuffer() and toArrayBuffer().

What is the expected behavior? Why is that the expected behavior?

Omitting a required argument should throw, like passing it explicitly as undefined already does.

The documented signatures are ffi.getInt8(pointer[, offset]), ffi.setInt8(pointer, offset, value) and ffi.toBuffer(pointer, length[, copy]), and the getters are documented to return a number or a bigint. The helpers are inconsistent among themselves too: setUint8(ptr) throws Expected an offset argument, so only a missing first argument goes unreported.

What do you see instead?

undefined
undefined
undefined
The pointer must be a bigint
The length must be a number

GetValidatedPointerAddress() and GetValidatedSize() already reject undefined. The args.Length() test in front of them short-circuits the call and returns Nothing without scheduling an exception:

node/src/ffi/data.cc

Lines 163 to 169 in a48e33f

Maybe<std::pair<uint8_t*, size_t>> GetValidatedPointerAndOffset(
Environment* env, const FunctionCallbackInfo<Value>& args) {
uintptr_t raw_ptr;
if (args.Length() < 1 ||
!GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) {
return {};
}

The same shape guards the setters (L207), the length of ToBuffer() (L559) and of ToArrayBuffer() (L621), and both arguments of ExportBytes() (L697, L703).

Additional information

These are raw memory helpers, so the silent undefined is easy to miss: if (ffi.getUint8(ptr)) written with the pointer left out is falsy exactly like a zero byte, and ffi.setUint8() reports success for a write that never happened.

The tests do not look deliberate. FunctionCallbackInfo::operator[] returns Undefined for an out-of-range index, which is exactly the value these validators reject, and these six are the only tests in src/ where args.Length() can skip a call that throws (the only other Length() tests that guard a call at all guard Buffer::HasInstance(), which cannot throw). They arrived with the module in d0fa608 and survived fe41105 (#62858), which reworked the surrounding error handling; I found no mention of argument count in the reviews of #62072, #62762 or #62858.

Refs: #62072
Refs: #62858
Refs: #65342

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions