From 8926b4d5b1cd8f22b8f3e3e9ecf780f7ecf9bf9d 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 308bde60cae..5478ea5a281 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; } @@ -694,13 +692,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 72d37efacd5..951ef71986b 100644 --- a/test/ffi/test-ffi-memory.js +++ b/test/ffi/test-ffi-memory.js @@ -319,3 +319,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](), /The pointer must be a bigint/); + assert.throws(() => ffi[name](undefined), /The pointer must be a bigint/); + } + } + + assert.throws(() => ffi.toBuffer(1n), /The length must be a number/); + assert.throws(() => ffi.toBuffer(1n, undefined), /The length must be a number/); + assert.throws(() => ffi.toArrayBuffer(1n), /The length must be a number/); + assert.throws(() => ffi.toArrayBuffer(1n, undefined), /The length must be a number/); +});