From 7ce4938aab4c3205a7df4a4891b2bd45ea4457e1 Mon Sep 17 00:00:00 2001 From: Soul Lee Date: Sat, 22 Aug 2026 16:26:12 +0900 Subject: [PATCH] ffi: throw on missing memory helper arguments ffi.getInt8() through ffi.getFloat64(), ffi.setInt8() through ffi.setFloat64(), ffi.toBuffer() and ffi.toArrayBuffer() return undefined instead of throwing when a required argument is omitted, so a call that read or wrote nothing cannot be told apart from one that read a zero byte. All 22 helpers behave this way. GetValidatedPointerAddress() and GetValidatedSize() already reject the same argument when it is passed explicitly as undefined. The args.Length() test in front of them short-circuits the call and returns Nothing without scheduling an exception. 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. The remaining tests in this file guard an inline predicate and throw in the branch, which is why setUint8(ptr) reports "Expected an offset argument" while setUint8() reports nothing at all. Drop those tests. FunctionCallbackInfo::operator[] returns Undefined for an out-of-range index, which is exactly the value these validators reject, so each missing argument now produces the error its explicit undefined counterpart produces. The documentation already describes this behavior: the 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. ExportBytes() carried the same two tests. They are unreachable through the public API because exportBytes is not exported and its three callers all validate len in JavaScript first, but they are the same shape. Signed-off-by: Soul Lee --- src/ffi/data.cc | 15 ++++++--------- test/ffi/test-ffi-memory.js | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/ffi/data.cc b/src/ffi/data.cc index 8bd2c0d9f352..bc287ddd13f9 100644 --- a/src/ffi/data.cc +++ b/src/ffi/data.cc @@ -163,8 +163,7 @@ Maybe ValidateStringLength(Environment* env, size_t len) { Maybe> GetValidatedPointerAndOffset( Environment* env, const FunctionCallbackInfo& args) { uintptr_t raw_ptr; - if (args.Length() < 1 || - !GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { return {}; } @@ -204,8 +203,7 @@ Maybe GetValidatedPointerOffsetAndValue( size_t offset; Local value; uintptr_t raw_ptr; - if (args.Length() < 1 || - !GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { return {}; } @@ -556,7 +554,7 @@ void ToBuffer(const FunctionCallbackInfo& args) { } size_t len; - if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length").To(&len)) { + if (!GetValidatedSize(env, args[1], "length").To(&len)) { return; } @@ -618,7 +616,7 @@ void ToArrayBuffer(const FunctionCallbackInfo& args) { } size_t len; - if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length").To(&len)) { + if (!GetValidatedSize(env, args[1], "length").To(&len)) { return; } @@ -696,13 +694,12 @@ void ExportBytes(const FunctionCallbackInfo& args) { } uintptr_t ptr; - if (args.Length() < 2 || - !GetValidatedPointerAddress(env, args[1], "pointer").To(&ptr)) { + if (!GetValidatedPointerAddress(env, args[1], "pointer").To(&ptr)) { return; } size_t len; - if (args.Length() < 3 || !GetValidatedSize(env, args[2], "length").To(&len)) { + if (!GetValidatedSize(env, args[2], "length").To(&len)) { return; } diff --git a/test/ffi/test-ffi-memory.js b/test/ffi/test-ffi-memory.js index 5e667af2eb13..6d47200db187 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -318,3 +318,23 @@ test('ffi validates memory access arguments', () => { } })); }); + +test('ffi memory helpers reject missing required arguments', () => { + const widths = ['Int8', 'Uint8', 'Int16', 'Uint16', 'Int32', 'Uint32', + 'Int64', 'Uint64', 'Float32', 'Float64']; + + // Calling a helper with no arguments must report the missing pointer the + // same way an explicitly passed `undefined` does, instead of returning + // `undefined` as if the read or the write had succeeded. + for (const width of widths) { + for (const name of [`get${width}`, `set${width}`]) { + assert.throws(() => ffi[name](), { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => ffi[name](undefined), { code: 'ERR_INVALID_ARG_VALUE' }); + } + } + + assert.throws(() => ffi.toBuffer(1n), { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => ffi.toBuffer(1n, undefined), { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => ffi.toArrayBuffer(1n), { code: 'ERR_INVALID_ARG_VALUE' }); + assert.throws(() => ffi.toArrayBuffer(1n, undefined), { code: 'ERR_INVALID_ARG_VALUE' }); +});