From 48c8a24cb8b77ac957008fa4d642a272054310ca Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 20 Apr 2026 17:29:04 +0200 Subject: [PATCH 1/2] src: align FFI error handling with Node.js source Align the FFI error handling logic with the rest of the Node.js source. Refs: https://github.com/nodejs/node/blob/main/src/README.md#exception-handling Signed-off-by: Anna Henningsen --- src/ffi/data.cc | 285 ++++++++++++++++++++++++----------------------- src/ffi/data.h | 31 +++--- src/ffi/types.cc | 230 +++++++++++++++++--------------------- src/ffi/types.h | 35 +++--- src/node_ffi.cc | 170 +++++++++++++--------------- src/node_ffi.h | 16 +-- 6 files changed, 366 insertions(+), 401 deletions(-) diff --git a/src/ffi/data.cc b/src/ffi/data.cc index 0d92981bf54ceb..1f50ed094798b6 100644 --- a/src/ffi/data.cc +++ b/src/ffi/data.cc @@ -20,11 +20,16 @@ using v8::Context; using v8::FunctionCallbackInfo; using v8::Integer; using v8::Isolate; +using v8::Just; +using v8::JustVoid; using v8::Local; +using v8::Maybe; using v8::MaybeLocal; using v8::NewStringType; +using v8::Nothing; using v8::Number; using v8::Object; +using v8::SharedArrayBuffer; using v8::String; using v8::Value; @@ -32,38 +37,35 @@ namespace node { namespace ffi { -bool GetValidatedSize(Environment* env, - Local value, - const char* label, - size_t* out) { +Maybe GetValidatedSize(Environment* env, + Local value, + const char* label) { if (!value->IsNumber()) { THROW_ERR_INVALID_ARG_VALUE(env, "The %s must be a number", label); - return false; + return Nothing(); } double length = value.As()->Value(); if (!std::isfinite(length) || length < 0 || std::floor(length) != length) { THROW_ERR_INVALID_ARG_VALUE( env, "The %s must be a non-negative integer", label); - return false; + return Nothing(); } if (length > static_cast(std::numeric_limits::max())) { THROW_ERR_OUT_OF_RANGE(env, "The %s is too large", label); - return false; + return Nothing(); } - *out = static_cast(length); - return true; + return Just(static_cast(length)); } -bool GetValidatedPointerAddress(Environment* env, - Local value, - const char* label, - uintptr_t* out) { +Maybe GetValidatedPointerAddress(Environment* env, + Local value, + const char* label) { if (!value->IsBigInt()) { THROW_ERR_INVALID_ARG_VALUE(env, "The %s must be a bigint", label); - return false; + return Nothing(); } bool lossless; @@ -71,183 +73,177 @@ bool GetValidatedPointerAddress(Environment* env, if (!lossless) { THROW_ERR_INVALID_ARG_VALUE( env, "The %s must be a non-negative bigint", label); - return false; + return Nothing(); } if (address > static_cast(std::numeric_limits::max())) { THROW_ERR_INVALID_ARG_VALUE( env, "The %s exceeds the platform pointer range", label); - return false; + return Nothing(); } - *out = static_cast(address); - - return true; + return Just(static_cast(address)); } -bool GetValidatedSignedInt(Environment* env, - Local value, - int64_t min, - int64_t max, - const char* type_name, - int64_t* out) { +Maybe GetValidatedSignedInt(Environment* env, + Local value, + int64_t min, + int64_t max, + const char* type_name) { if (!value->IsNumber()) { - THROW_ERR_INVALID_ARG_VALUE( - env, (std::string("Value must be an ") + type_name).c_str()); - return false; + THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an %s", type_name); + return Nothing(); } double number = value.As()->Value(); if (!std::isfinite(number) || std::floor(number) != number || number < min || number > max) { - THROW_ERR_INVALID_ARG_VALUE( - env, (std::string("Value must be an ") + type_name).c_str()); - return false; + THROW_ERR_INVALID_ARG_VALUE(env, "Value must be an %s", type_name); + return Nothing(); } - *out = static_cast(number); - return true; + return Just(static_cast(number)); } -bool GetValidatedUnsignedInt(Environment* env, - Local value, - uint64_t max, - const char* type_name, - uint64_t* out) { +Maybe GetValidatedUnsignedInt(Environment* env, + Local value, + uint64_t max, + const char* type_name) { if (!value->IsNumber()) { - THROW_ERR_INVALID_ARG_VALUE( - env, (std::string("Value must be a ") + type_name).c_str()); - return false; + THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a %s", type_name); + return Nothing(); } double number = value.As()->Value(); if (!std::isfinite(number) || std::floor(number) != number || number < 0 || number > static_cast(max)) { - THROW_ERR_INVALID_ARG_VALUE( - env, (std::string("Value must be a ") + type_name).c_str()); - return false; + THROW_ERR_INVALID_ARG_VALUE(env, "Value must be a %s", type_name); + return Nothing(); } - *out = static_cast(number); - return true; + return Just(static_cast(number)); } -bool ValidatePointerSpan(Environment* env, - uintptr_t raw_ptr, - size_t offset, - size_t length, - const char* error_message) { +Maybe ValidatePointerSpan(Environment* env, + uintptr_t raw_ptr, + size_t offset, + size_t length, + const char* error_message) { if (offset > std::numeric_limits::max() - raw_ptr) { THROW_ERR_INVALID_ARG_VALUE(env, error_message); - return false; + return Nothing(); } uintptr_t start = raw_ptr + offset; if (length > 0 && length - 1 > std::numeric_limits::max() - start) { THROW_ERR_INVALID_ARG_VALUE(env, error_message); - return false; + return Nothing(); } - return true; + return JustVoid(); } -bool ValidateBufferLength(Environment* env, size_t len) { +Maybe ValidateBufferLength(Environment* env, size_t len) { if (len > Buffer::kMaxLength) { THROW_ERR_BUFFER_TOO_LARGE(env, "Buffer is too large"); - return false; + return Nothing(); } - return true; + return JustVoid(); } -bool ValidateStringLength(Environment* env, size_t len) { +Maybe ValidateStringLength(Environment* env, size_t len) { if (len > static_cast(String::kMaxLength)) { THROW_ERR_STRING_TOO_LONG(env, "String is too long"); - return false; + return Nothing(); } - return true; + return JustVoid(); } -bool GetValidatedPointerAndOffset(Environment* env, - const FunctionCallbackInfo& args, - uint8_t** ptr, - size_t* offset) { +Maybe> GetValidatedPointerAndOffset( + Environment* env, const FunctionCallbackInfo& args) { uintptr_t raw_ptr; if (args.Length() < 1 || - !GetValidatedPointerAddress(env, args[0], "pointer", &raw_ptr)) { - return false; + !GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { + return {}; } if (raw_ptr == 0) { THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer"); - return false; + return {}; } - *offset = 0; + size_t offset = 0; if (args.Length() > 1 && !args[1]->IsUndefined()) { - if (!GetValidatedSize(env, args[1], "offset", offset)) { - return false; + if (!GetValidatedSize(env, args[1], "offset").To(&offset)) { + return {}; } } - if (!ValidatePointerSpan( + if (ValidatePointerSpan( env, raw_ptr, - *offset, + offset, 1, - "The pointer and offset exceed the platform address range")) { - return false; + "The pointer and offset exceed the platform address range") + .IsNothing()) { + return {}; } - *ptr = reinterpret_cast(static_cast(raw_ptr)); - return true; + return Just(std::make_pair(reinterpret_cast(raw_ptr), offset)); } -bool GetValidatedPointerValueAndOffset(Environment* env, - const FunctionCallbackInfo& args, - uint8_t** ptr, - Local* value, - size_t* offset) { +struct PointerValueAndOffset { + uint8_t* ptr; + size_t offset; + Local value; +}; + +Maybe GetValidatedPointerValueAndOffset( + Environment* env, const FunctionCallbackInfo& args) { + size_t offset; + Local value; uintptr_t raw_ptr; if (args.Length() < 1 || - !GetValidatedPointerAddress(env, args[0], "pointer", &raw_ptr)) { - return false; + !GetValidatedPointerAddress(env, args[0], "pointer").To(&raw_ptr)) { + return {}; } if (raw_ptr == 0) { THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer"); - return false; + return {}; } if (args.Length() < 2 || args[1]->IsUndefined()) { THROW_ERR_INVALID_ARG_VALUE(env, "Expected an offset argument"); - return false; + return {}; } - if (!GetValidatedSize(env, args[1], "offset", offset)) { - return false; + if (!GetValidatedSize(env, args[1], "offset").To(&offset)) { + return {}; } - if (!ValidatePointerSpan( + if (ValidatePointerSpan( env, raw_ptr, - *offset, + offset, 1, - "The pointer and offset exceed the platform address range")) { - return false; + "The pointer and offset exceed the platform address range") + .IsNothing()) { + return {}; } if (args.Length() < 3 || args[2]->IsUndefined()) { THROW_ERR_INVALID_ARG_VALUE(env, "Expected a value argument"); - return false; + return {}; } - *value = args[2]; + value = args[2]; - *ptr = reinterpret_cast(static_cast(raw_ptr)); - return true; + uint8_t* ptr = reinterpret_cast(static_cast(raw_ptr)); + return Just(PointerValueAndOffset{ptr, offset, value}); } template @@ -255,20 +251,21 @@ void GetValue(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); Isolate* isolate = env->isolate(); - uint8_t* ptr; - size_t offset; + std::pair ptr_and_offset; - if (!GetValidatedPointerAndOffset(env, args, &ptr, &offset)) { + if (!GetValidatedPointerAndOffset(env, args).To(&ptr_and_offset)) { return; } + auto [ptr, offset] = ptr_and_offset; uintptr_t raw_ptr = reinterpret_cast(ptr); - if (!ValidatePointerSpan( + if (ValidatePointerSpan( env, raw_ptr, offset, sizeof(T), - "The accessed range exceeds the platform address range")) { + "The accessed range exceeds the platform address range") + .IsNothing()) { return; } @@ -287,6 +284,8 @@ void GetValue(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(BigInt::NewFromUnsigned(isolate, value)); } else if constexpr (std::is_same_v || std::is_same_v) { args.GetReturnValue().Set(Number::New(isolate, value)); + } else { + UNREACHABLE(); } } @@ -294,21 +293,21 @@ template void SetValue(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, ""); - uint8_t* ptr; - Local value; - size_t offset; + PointerValueAndOffset data; - if (!GetValidatedPointerValueAndOffset(env, args, &ptr, &value, &offset)) { + if (!GetValidatedPointerValueAndOffset(env, args).To(&data)) { return; } + auto [ptr, offset, value] = data; uintptr_t raw_ptr = reinterpret_cast(ptr); - if (!ValidatePointerSpan( + if (ValidatePointerSpan( env, raw_ptr, offset, sizeof(T), - "The accessed range exceeds the platform address range")) { + "The accessed range exceeds the platform address range") + .IsNothing()) { return; } @@ -317,42 +316,43 @@ void SetValue(const FunctionCallbackInfo& args) { if constexpr (std::is_same_v) { int64_t validated; - if (!GetValidatedSignedInt( - env, value, INT8_MIN, INT8_MAX, "int8", &validated)) { + if (!GetValidatedSignedInt(env, value, INT8_MIN, INT8_MAX, "int8") + .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { uint64_t validated; - if (!GetValidatedUnsignedInt(env, value, UINT8_MAX, "uint8", &validated)) { + if (!GetValidatedUnsignedInt(env, value, UINT8_MAX, "uint8") + .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { int64_t validated; - if (!GetValidatedSignedInt( - env, value, INT16_MIN, INT16_MAX, "int16", &validated)) { + if (!GetValidatedSignedInt(env, value, INT16_MIN, INT16_MAX, "int16") + .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { uint64_t validated; - if (!GetValidatedUnsignedInt( - env, value, UINT16_MAX, "uint16", &validated)) { + if (!GetValidatedUnsignedInt(env, value, UINT16_MAX, "uint16") + .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { int64_t validated; - if (!GetValidatedSignedInt( - env, value, INT32_MIN, INT32_MAX, "int32", &validated)) { + if (!GetValidatedSignedInt(env, value, INT32_MIN, INT32_MAX, "int32") + .To(&validated)) { return; } converted = static_cast(validated); } else if constexpr (std::is_same_v) { uint64_t validated; - if (!GetValidatedUnsignedInt( - env, value, UINT32_MAX, "uint32", &validated)) { + if (!GetValidatedUnsignedInt(env, value, UINT32_MAX, "uint32") + .To(&validated)) { return; } converted = static_cast(validated); @@ -370,8 +370,8 @@ void SetValue(const FunctionCallbackInfo& args) { value, -static_cast(kMaxSafeJsInteger), static_cast(kMaxSafeJsInteger), - "int64", - &validated)) { + "int64") + .To(&validated)) { return; } converted = static_cast(validated); @@ -389,11 +389,9 @@ void SetValue(const FunctionCallbackInfo& args) { } } else if (value->IsNumber()) { uint64_t validated; - if (!GetValidatedUnsignedInt(env, - value, - static_cast(kMaxSafeJsInteger), - "uint64", - &validated)) { + if (!GetValidatedUnsignedInt( + env, value, static_cast(kMaxSafeJsInteger), "uint64") + .To(&validated)) { return; } converted = static_cast(validated); @@ -411,6 +409,8 @@ void SetValue(const FunctionCallbackInfo& args) { } converted = static_cast(number_local->Value()); + } else { + UNREACHABLE(); } std::memcpy(ptr + offset, &converted, sizeof(converted)); @@ -513,7 +513,7 @@ void ToString(const FunctionCallbackInfo& args) { } uintptr_t ptr; - if (!GetValidatedPointerAddress(env, args[0], "first argument", &ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) { return; } @@ -524,12 +524,12 @@ void ToString(const FunctionCallbackInfo& args) { const char* str = reinterpret_cast(ptr); size_t len = std::strlen(str); - if (!ValidateStringLength(env, len)) { + if (ValidateStringLength(env, len).IsNothing()) { return; } Local out; - if (!String::NewFromUtf8(isolate, str, NewStringType::kNormal) + if (!String::NewFromUtf8(isolate, str, NewStringType::kNormal, len) .ToLocal(&out)) { return; } @@ -553,12 +553,12 @@ void ToBuffer(const FunctionCallbackInfo& args) { } uintptr_t ptr; - if (!GetValidatedPointerAddress(env, args[0], "first argument", &ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) { return; } size_t len; - if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length", &len)) { + if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length").To(&len)) { return; } @@ -568,16 +568,17 @@ void ToBuffer(const FunctionCallbackInfo& args) { return; } - if (!ValidatePointerSpan( + if (ValidatePointerSpan( env, ptr, 0, len, - "The pointer and length exceed the platform address range")) { + "The pointer and length exceed the platform address range") + .IsNothing()) { return; } - if (!ValidateBufferLength(env, len)) { + if (ValidateBufferLength(env, len).IsNothing()) { return; } @@ -614,12 +615,12 @@ void ToArrayBuffer(const FunctionCallbackInfo& args) { } uintptr_t ptr; - if (!GetValidatedPointerAddress(env, args[0], "first argument", &ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "first argument").To(&ptr)) { return; } size_t len; - if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length", &len)) { + if (args.Length() < 2 || !GetValidatedSize(env, args[1], "length").To(&len)) { return; } @@ -629,16 +630,17 @@ void ToArrayBuffer(const FunctionCallbackInfo& args) { return; } - if (!ValidatePointerSpan( + if (ValidatePointerSpan( env, ptr, 0, len, - "The pointer and length exceed the platform address range")) { + "The pointer and length exceed the platform address range") + .IsNothing()) { return; } - if (!ValidateBufferLength(env, len)) { + if (ValidateBufferLength(env, len).IsNothing()) { return; } @@ -704,12 +706,12 @@ void ExportBytes(const FunctionCallbackInfo& args) { uintptr_t ptr; if (args.Length() < 2 || - !GetValidatedPointerAddress(env, args[1], "pointer", &ptr)) { + !GetValidatedPointerAddress(env, args[1], "pointer").To(&ptr)) { return; } size_t len; - if (args.Length() < 3 || !GetValidatedSize(env, args[2], "length", &len)) { + if (args.Length() < 3 || !GetValidatedSize(env, args[2], "length").To(&len)) { return; } @@ -724,12 +726,13 @@ void ExportBytes(const FunctionCallbackInfo& args) { return; } - if (!ValidatePointerSpan( + if (ValidatePointerSpan( env, ptr, 0, len, - "The pointer and length exceed the platform address range")) { + "The pointer and length exceed the platform address range") + .IsNothing()) { return; } diff --git a/src/ffi/data.h b/src/ffi/data.h index dbc6ba85238805..556b4a4a1b6ac4 100644 --- a/src/ffi/data.h +++ b/src/ffi/data.h @@ -9,23 +9,20 @@ namespace node::ffi { -bool GetValidatedSignedInt(Environment* env, - v8::Local value, - int64_t min, - int64_t max, - const char* type_name, - int64_t* out); - -bool GetValidatedUnsignedInt(Environment* env, - v8::Local value, - uint64_t max, - const char* type_name, - uint64_t* out); - -bool GetValidatedPointerAddress(Environment* env, - v8::Local value, - const char* label, - uintptr_t* out); +v8::Maybe GetValidatedSignedInt(Environment* env, + v8::Local value, + int64_t min, + int64_t max, + const char* type_name); + +v8::Maybe GetValidatedUnsignedInt(Environment* env, + v8::Local value, + uint64_t max, + const char* type_name); + +v8::Maybe GetValidatedPointerAddress(Environment* env, + v8::Local value, + const char* label); size_t GetFFIReturnValueStorageSize(ffi_type* type); } // namespace node::ffi diff --git a/src/ffi/types.cc b/src/ffi/types.cc index b5d9d762164c89..11b653cdf4e0d6 100644 --- a/src/ffi/types.cc +++ b/src/ffi/types.cc @@ -22,6 +22,7 @@ using v8::Context; using v8::FunctionCallbackInfo; using v8::Integer; using v8::Isolate; +using v8::Just; using v8::Local; using v8::Maybe; using v8::Number; @@ -35,30 +36,16 @@ namespace ffi { bool ThrowIfContainsNullBytes(Environment* env, const Utf8Value& value, - const std::string& label) { + std::string_view label) { if (value.length() != 0 && std::memchr(*value, '\0', value.length()) != nullptr) { - THROW_ERR_INVALID_ARG_VALUE( - env, "%s must not contain null bytes", label.c_str()); + THROW_ERR_INVALID_ARG_VALUE(env, "%s must not contain null bytes", label); return true; } return false; } -bool HasProperty(Local context, - Local object, - Local key, - bool* out) { - Maybe has = object->Has(context, key); - if (has.IsNothing()) { - return false; - } - - *out = has.FromJust(); - return true; -} - bool GetStrictSignedInteger(Local value, int64_t min, int64_t max, @@ -92,11 +79,9 @@ bool GetStrictUnsignedInteger(Local value, uint64_t max, uint64_t* out) { return true; } -bool ParseFunctionSignature(Environment* env, - const std::string& name, - Local signature, - ffi_type** return_type, - std::vector* args) { +Maybe ParseFunctionSignature(Environment* env, + std::string_view name, + Local signature) { Local context = env->context(); Local returns_key = env->returns_string(); Local return_key = env->return_string(); @@ -110,32 +95,37 @@ bool ParseFunctionSignature(Environment* env, bool has_parameters; bool has_arguments; - if (!HasProperty(context, signature, returns_key, &has_returns) || - !HasProperty(context, signature, return_key, &has_return) || - !HasProperty(context, signature, result_key, &has_result) || - !HasProperty(context, signature, parameters_key, &has_parameters) || - !HasProperty(context, signature, arguments_key, &has_arguments)) { - return false; + ffi_type* return_type; + std::vector args; + + if (!signature->Has(context, returns_key).To(&has_returns) || + !signature->Has(context, return_key).To(&has_return) || + !signature->Has(context, result_key).To(&has_result) || + !signature->Has(context, parameters_key).To(&has_parameters) || + !signature->Has(context, arguments_key).To(&has_arguments)) { + return {}; } if (has_returns + has_return + has_result > 1) { - std::string msg = "Function signature of " + name + - " must have either 'returns', 'return' or 'result' " - "property"; - THROW_ERR_INVALID_ARG_VALUE(env, msg); - return false; + THROW_ERR_INVALID_ARG_VALUE( + env, + "Function signature of %s" + " must have either 'returns', 'return' or 'result' " + "property", + name); + return {}; } if (has_arguments && has_parameters) { - std::string msg = "Function signature of " + name + - " must have either 'parameters' or 'arguments' " - "property"; - THROW_ERR_INVALID_ARG_VALUE(env, msg); - return false; + THROW_ERR_INVALID_ARG_VALUE(env, + "Function signature of %s" + " must have either 'parameters' or 'arguments' " + "property", + name); + return {}; } - *return_type = &ffi_type_void; - args->clear(); + return_type = &ffi_type_void; Isolate* isolate = env->isolate(); if (has_returns || has_return || has_result) { @@ -150,23 +140,24 @@ bool ParseFunctionSignature(Environment* env, Local return_type_val; if (!signature->Get(context, return_type_key).ToLocal(&return_type_val)) { - return false; + return {}; } if (!return_type_val->IsString()) { - std::string msg = - "Return value type of function " + name + " must be a string"; - THROW_ERR_INVALID_ARG_VALUE(env, msg); - return false; + THROW_ERR_INVALID_ARG_VALUE( + env, "Return value type of function %s must be a string", name); + return {}; } Utf8Value return_type_str(isolate, return_type_val); if (ThrowIfContainsNullBytes( - env, return_type_str, "Return value type of function " + name)) { - return false; + env, + return_type_str, + "Return value type of function " + std::string(name))) { + return {}; } - if (!ToFFIType(env, *return_type_str, return_type)) { - return false; + if (!ToFFIType(env, return_type_str.ToStringView()).To(&return_type)) { + return {}; } } @@ -174,50 +165,48 @@ bool ParseFunctionSignature(Environment* env, Local arguments_val; if (!signature->Get(context, has_arguments ? arguments_key : parameters_key) .ToLocal(&arguments_val)) { - return false; + return {}; } if (!arguments_val->IsArray()) { - std::string msg = - "Arguments list of function " + name + " must be an array"; - THROW_ERR_INVALID_ARG_VALUE(env, msg); - return false; + THROW_ERR_INVALID_ARG_VALUE( + env, "Arguments list of function %s must be an array", name); + return {}; } Local arguments_array = arguments_val.As(); unsigned int argn = arguments_array->Length(); - args->reserve(argn); + args.reserve(argn); for (unsigned int i = 0; i < argn; i++) { Local arg; if (!arguments_array->Get(context, i).ToLocal(&arg)) { - return false; + return {}; } if (!arg->IsString()) { - std::string msg = "Argument " + std::to_string(i) + " of function " + - name + " must be a string"; - THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str()); - return false; + THROW_ERR_INVALID_ARG_VALUE( + env, "Argument %u of function %s must be a string", i, name); + return {}; } Utf8Value arg_str(isolate, arg); - if (ThrowIfContainsNullBytes( - env, - arg_str, - "Argument " + std::to_string(i) + " of function " + name)) { - return false; + if (ThrowIfContainsNullBytes(env, + arg_str, + "Argument " + std::to_string(i) + + " of function " + std::string(name))) { + return {}; } ffi_type* arg_type; - if (!ToFFIType(env, *arg_str, &arg_type)) { - return false; + if (!ToFFIType(env, arg_str.ToStringView()).To(&arg_type)) { + return {}; } - args->push_back(arg_type); + args.push_back(arg_type); } } - return true; + return Just(FunctionSignature{return_type, std::move(args)}); } bool SignaturesMatch(const FFIFunction& fn, @@ -236,138 +225,127 @@ bool SignaturesMatch(const FFIFunction& fn, return true; } -bool ToFFIType(Environment* env, const std::string& type_str, ffi_type** ret) { - if (ret == nullptr) { - THROW_ERR_INVALID_ARG_VALUE(env, "ret must not be null"); - return false; - } - +v8::Maybe ToFFIType(Environment* env, std::string_view type_str) { if (type_str == "void") { - *ret = &ffi_type_void; + return Just(&ffi_type_void); } else if (type_str == "i8" || type_str == "int8") { - *ret = &ffi_type_sint8; + return Just(&ffi_type_sint8); } else if (type_str == "u8" || type_str == "uint8" || type_str == "bool") { - *ret = &ffi_type_uint8; + return Just(&ffi_type_uint8); } else if (type_str == "char") { - *ret = CHAR_MIN < 0 ? &ffi_type_sint8 : &ffi_type_uint8; + return Just(CHAR_MIN < 0 ? &ffi_type_sint8 : &ffi_type_uint8); } else if (type_str == "i16" || type_str == "int16") { - *ret = &ffi_type_sint16; + return Just(&ffi_type_sint16); } else if (type_str == "u16" || type_str == "uint16") { - *ret = &ffi_type_uint16; + return Just(&ffi_type_uint16); } else if (type_str == "i32" || type_str == "int32") { - *ret = &ffi_type_sint32; + return Just(&ffi_type_sint32); } else if (type_str == "u32" || type_str == "uint32") { - *ret = &ffi_type_uint32; + return Just(&ffi_type_uint32); } else if (type_str == "i64" || type_str == "int64") { - *ret = &ffi_type_sint64; + return Just(&ffi_type_sint64); } else if (type_str == "u64" || type_str == "uint64") { - *ret = &ffi_type_uint64; + return Just(&ffi_type_uint64); } else if (type_str == "f32" || type_str == "float") { - *ret = &ffi_type_float; + return Just(&ffi_type_float); } else if (type_str == "f64" || type_str == "double") { - *ret = &ffi_type_double; + return Just(&ffi_type_double); } else if (type_str == "buffer" || type_str == "arraybuffer" || type_str == "string" || type_str == "str" || type_str == "pointer" || type_str == "ptr" || type_str == "function") { - *ret = &ffi_type_pointer; + return Just(&ffi_type_pointer); } else { - std::string msg = std::string("Unsupported FFI type: ") + type_str; - THROW_ERR_INVALID_ARG_VALUE(env, msg); - return false; + THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported FFI type: %s", type_str); + return {}; } - - return true; } -uint8_t ToFFIArgument(Environment* env, - unsigned int index, - ffi_type* type, - Local arg, - void* ret) { +Maybe ToFFIArgument(Environment* env, + unsigned int index, + ffi_type* type, + Local arg, + void* ret) { Local context = env->context(); if (type == &ffi_type_void) { - return 1; + return Just(FFIArgumentCategory::Regular); } else if (type == &ffi_type_sint8) { int64_t value; - if (!GetValidatedSignedInt(env, arg, INT8_MIN, INT8_MAX, "int8", &value)) { - if (env->isolate()->IsExecutionTerminating()) return 0; + if (!GetValidatedSignedInt(env, arg, INT8_MIN, INT8_MAX, "int8") + .To(&value)) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int8", index); - return 0; + return {}; } *static_cast(ret) = static_cast(value); } else if (type == &ffi_type_uint8) { uint64_t value; - if (!GetValidatedUnsignedInt(env, arg, UINT8_MAX, "uint8", &value)) { - if (env->isolate()->IsExecutionTerminating()) return 0; + if (!GetValidatedUnsignedInt(env, arg, UINT8_MAX, "uint8").To(&value)) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint8", index); - return 0; + return {}; } *static_cast(ret) = static_cast(value); } else if (type == &ffi_type_sint16) { int64_t value; - if (!GetValidatedSignedInt( - env, arg, INT16_MIN, INT16_MAX, "int16", &value)) { - if (env->isolate()->IsExecutionTerminating()) return 0; + if (!GetValidatedSignedInt(env, arg, INT16_MIN, INT16_MAX, "int16") + .To(&value)) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int16", index); - return 0; + return {}; } *static_cast(ret) = static_cast(value); } else if (type == &ffi_type_uint16) { uint64_t value; - if (!GetValidatedUnsignedInt(env, arg, UINT16_MAX, "uint16", &value)) { - if (env->isolate()->IsExecutionTerminating()) return 0; + if (!GetValidatedUnsignedInt(env, arg, UINT16_MAX, "uint16").To(&value)) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint16", index); - return 0; + return {}; } *static_cast(ret) = static_cast(value); } else if (type == &ffi_type_sint32) { if (!arg->IsInt32()) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int32", index); - return 0; + return {}; } *static_cast(ret) = arg->Int32Value(context).FromJust(); } else if (type == &ffi_type_uint32) { if (!arg->IsUint32()) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint32", index); - return 0; + return {}; } *static_cast(ret) = arg->Uint32Value(context).FromJust(); } else if (type == &ffi_type_sint64) { if (!arg->IsBigInt()) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int64", index); - return 0; + return {}; } bool lossless; *static_cast(ret) = arg.As()->Int64Value(&lossless); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int64", index); - return 0; + return {}; } } else if (type == &ffi_type_uint64) { if (!arg->IsBigInt()) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint64", index); - return 0; + return {}; } bool lossless; *static_cast(ret) = arg.As()->Uint64Value(&lossless); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint64", index); - return 0; + return {}; } } else if (type == &ffi_type_float) { if (!arg->IsNumber()) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a float", index); - return 0; + return {}; } *static_cast(ret) = @@ -375,7 +353,7 @@ uint8_t ToFFIArgument(Environment* env, } else if (type == &ffi_type_double) { if (!arg->IsNumber()) { THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a double", index); - return 0; + return {}; } *static_cast(ret) = arg->NumberValue(context).FromJust(); @@ -385,7 +363,7 @@ uint8_t ToFFIArgument(Environment* env, } else if (arg->IsString()) { // This will handled in Invoke so that we can free the copied string after // the call - return 2; + return Just(FFIArgumentCategory::String); } else if (arg->IsArrayBufferView()) { // Pointer-like ArrayBufferView arguments borrow backing-store memory // without pinning. Resizing, transferring, detaching, or otherwise @@ -399,7 +377,7 @@ uint8_t ToFFIArgument(Environment* env, env, "Invalid ArrayBufferView backing store for argument %u", index); - return 0; + return {}; } void* data = store->Data(); @@ -418,7 +396,7 @@ uint8_t ToFFIArgument(Environment* env, if (!store) { THROW_ERR_INVALID_ARG_VALUE( env, "Invalid ArrayBuffer backing store for argument %u", index); - return 0; + return {}; } *static_cast(ret) = reinterpret_cast(store->Data()); @@ -429,7 +407,7 @@ uint8_t ToFFIArgument(Environment* env, std::numeric_limits::max())) { THROW_ERR_INVALID_ARG_VALUE( env, "Argument %u must be a non-negative pointer bigint", index); - return 0; + return {}; } *static_cast(ret) = pointer; @@ -438,15 +416,15 @@ uint8_t ToFFIArgument(Environment* env, env, "Argument %u must be a buffer, an ArrayBuffer, a string, or a bigint", index); - return 0; + return {}; } } else { THROW_ERR_INVALID_ARG_VALUE( env, "Unsupported FFI type for argument %u", index); - return 0; + return {}; } - return 1; + return Just(FFIArgumentCategory::Regular); } Local ToJSArgument(Isolate* isolate, ffi_type* type, void* data) { diff --git a/src/ffi/types.h b/src/ffi/types.h index 47010e9908a6d2..1b7802e5b52480 100644 --- a/src/ffi/types.h +++ b/src/ffi/types.h @@ -15,21 +15,26 @@ struct FFIFunction; bool ThrowIfContainsNullBytes(Environment* env, const Utf8Value& value, - const std::string& label); - -bool ParseFunctionSignature(Environment* env, - const std::string& name, - v8::Local signature, - ffi_type** return_type, - std::vector* args); - -bool ToFFIType(Environment* env, const std::string& type_str, ffi_type** ret); - -uint8_t ToFFIArgument(Environment* env, - unsigned int index, - ffi_type* type, - v8::Local arg, - void* ret); + std::string_view label); + +struct FunctionSignature { + ffi_type* return_type; + std::vector args; +}; +v8::Maybe ParseFunctionSignature( + Environment* env, std::string_view name, v8::Local signature); + +v8::Maybe ToFFIType(Environment* env, std::string_view type_str); + +enum class FFIArgumentCategory { + Regular, + String, +}; +v8::Maybe ToFFIArgument(Environment* env, + unsigned int index, + ffi_type* type, + v8::Local arg, + void* ret); v8::Local ToJSArgument(v8::Isolate* isolate, ffi_type* type, diff --git a/src/node_ffi.cc b/src/node_ffi.cc index be5df0d736b5fc..bf94480e4051d6 100644 --- a/src/node_ffi.cc +++ b/src/node_ffi.cc @@ -21,10 +21,11 @@ using v8::FunctionTemplate; using v8::Global; using v8::HandleScope; using v8::Isolate; +using v8::Just; using v8::Local; using v8::LocalVector; +using v8::Maybe; using v8::MaybeLocal; -using v8::NewStringType; using v8::Null; using v8::Object; using v8::PropertyAttribute; @@ -83,12 +84,11 @@ void DynamicLibrary::Close() { callbacks_.clear(); } -bool DynamicLibrary::ResolveSymbol(Environment* env, - const std::string& name, - void** ret) { +Maybe DynamicLibrary::ResolveSymbol(Environment* env, + const std::string& name) { if (handle_ == nullptr) { THROW_ERR_FFI_LIBRARY_CLOSED(env); - return false; + return {}; } auto existing = symbols_.find(name); @@ -98,42 +98,36 @@ bool DynamicLibrary::ResolveSymbol(Environment* env, ptr = existing->second; } else { if (uv_dlsym(&lib_, name.c_str(), &ptr) != 0) { - std::string msg = std::string("dlsym failed: ") + uv_dlerror(&lib_); - THROW_ERR_FFI_CALL_FAILED(env, msg.c_str()); - return false; + THROW_ERR_FFI_CALL_FAILED(env, "dlsym failed: %s", uv_dlerror(&lib_)); + return {}; } } - *ret = ptr; - return true; + return Just(ptr); } -bool DynamicLibrary::PrepareFunction(Environment* env, - const std::string& name, - Local signature, - std::shared_ptr* ret, - bool* should_cache_symbol, - bool* should_cache_function) { +Maybe DynamicLibrary::PrepareFunction( + Environment* env, const std::string& name, Local signature) { std::shared_ptr fn; auto existing = functions_.find(name); - ffi_type* return_type = &ffi_type_void; - std::vector args; - - *should_cache_symbol = false; - *should_cache_function = false; + FunctionSignature parsed; - if (!ParseFunctionSignature(env, name, signature, &return_type, &args)) { - return false; + if (!ParseFunctionSignature(env, name, signature).To(&parsed)) { + return {}; } + auto [return_type, args] = parsed; + + bool should_cache_symbol = false; + bool should_cache_function = false; if (existing == functions_.end()) { void* ptr; - if (!ResolveSymbol(env, name, &ptr)) { - return false; + if (!ResolveSymbol(env, name).To(&ptr)) { + return {}; } - *should_cache_symbol = symbols_.find(name) == symbols_.end(); + should_cache_symbol = symbols_.find(name) == symbols_.end(); fn = std::make_shared(FFIFunction{.closed = false, .ptr = ptr, @@ -161,23 +155,24 @@ bool DynamicLibrary::PrepareFunction(Environment* env, } THROW_ERR_FFI_CALL_FAILED(env, msg); - return false; + return {}; } - *should_cache_function = true; + should_cache_function = true; } else { fn = existing->second; if (!SignaturesMatch(*fn, return_type, args)) { - std::string msg = "Function " + name + - " was already requested with a different signature"; - THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str()); - return false; + THROW_ERR_INVALID_ARG_VALUE( + env, + "Function %s" + " was already requested with a different signature", + name); + return {}; } } - *ret = fn; - return true; + return Just(PreparedFunction{fn, should_cache_symbol, should_cache_function}); } void DynamicLibrary::CleanupFunctionInfo( @@ -204,16 +199,15 @@ MaybeLocal DynamicLibrary::CreateFunction( return MaybeLocal(); } - Local name_str; - if (!String::NewFromUtf8(isolate, name.c_str(), NewStringType::kNormal) - .ToLocal(&name_str)) { + Local name_str; + if (!ToV8Value(env->context(), name, isolate).ToLocal(&name_str)) { return MaybeLocal(); } info->self.Reset(isolate, ret); info->self.SetWeak( info, DynamicLibrary::CleanupFunctionInfo, WeakCallbackType::kParameter); - ret->SetName(name_str); + ret->SetName(name_str.As()); if (!ret->Set( env->context(), env->pointer_string(), @@ -264,8 +258,7 @@ void DynamicLibrary::New(const FunctionCallbackInfo& args) { // Open the library if (uv_dlopen(library_path, &lib->lib_) != 0) { - std::string msg = std::string("dlopen failed: ") + uv_dlerror(&lib->lib_); - THROW_ERR_FFI_CALL_FAILED(env, msg.c_str()); + THROW_ERR_FFI_CALL_FAILED(env, "dlopen failed: %s", uv_dlerror(&lib->lib_)); return; } @@ -295,10 +288,10 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo& args) { unsigned int provided_args = args.Length(); if (provided_args != expected_args) { - std::string msg = "Invalid argument count: expected " + - std::to_string(expected_args) + ", got " + - std::to_string(provided_args); - THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str()); + THROW_ERR_INVALID_ARG_VALUE(env, + "Invalid argument count: expected %s, got %s", + expected_args, + provided_args); return; } @@ -308,20 +301,18 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo& args) { strings.reserve(expected_args); for (unsigned int i = 0; i < expected_args; i++) { - uint8_t res = ToFFIArgument(env, i, fn->args[i], args[i], &values[i]); + FFIArgumentCategory res; - if (!res) { + if (!ToFFIArgument(env, i, fn->args[i], args[i], &values[i]).To(&res)) { return; } // The argument is a string, we need to copy - if (res == 2) { + if (res == FFIArgumentCategory::String) { Utf8Value str(env->isolate(), args[i]); if (*str == nullptr) { - THROW_ERR_INVALID_ARG_TYPE( - env, - ("Argument " + std::to_string(i) + " must be a string").c_str()); + THROW_ERR_INVALID_ARG_TYPE(env, "Argument %s must be a string", i); return; } @@ -440,9 +431,8 @@ void DynamicLibrary::InvokeCallback(ffi_cif* cif, void DynamicLibrary::GetPath(const FunctionCallbackInfo& args) { DynamicLibrary* lib = Unwrap(args.This()); - Local path; - if (!String::NewFromUtf8( - args.GetIsolate(), lib->path_.c_str(), NewStringType::kNormal) + Local path; + if (!ToV8Value(lib->env()->context(), lib->path_, args.GetIsolate()) .ToLocal(&path)) { return; } @@ -469,19 +459,13 @@ void DynamicLibrary::GetFunction(const FunctionCallbackInfo& args) { if (ThrowIfContainsNullBytes(env, name, "Function name")) { return; } - std::shared_ptr fn; - bool should_cache_symbol; - bool should_cache_function; + PreparedFunction prepared; Local signature = args[1].As(); - if (!lib->PrepareFunction(env, - *name, - signature, - &fn, - &should_cache_symbol, - &should_cache_function)) { + if (!lib->PrepareFunction(env, *name, signature).To(&prepared)) { return; } + auto [fn, should_cache_symbol, should_cache_function] = prepared; if (should_cache_symbol) { lib->symbols_.emplace(*name, fn->ptr); @@ -547,25 +531,18 @@ void DynamicLibrary::GetFunctions(const FunctionCallbackInfo& args) { } if (!signature->IsObject() || signature->IsArray()) { - std::string msg = std::string("Signature of function ") + name.out() + - " must be an object"; - THROW_ERR_INVALID_ARG_TYPE(env, msg.c_str()); + THROW_ERR_INVALID_ARG_TYPE( + env, "Signature of function %s must be an object", name); return; } - std::shared_ptr fn; - bool should_cache_symbol; - bool should_cache_function; + PreparedFunction prepared; Local signature_object = signature.As(); - if (!lib->PrepareFunction(env, - *name, - signature_object, - &fn, - &should_cache_symbol, - &should_cache_function)) { + if (!lib->PrepareFunction(env, *name, signature_object).To(&prepared)) { return; } + auto [fn, should_cache_symbol, should_cache_function] = prepared; pending.push_back(ResolvedFunction{ .name = *name, @@ -592,14 +569,14 @@ void DynamicLibrary::GetFunctions(const FunctionCallbackInfo& args) { return; } - Local name_string; - if (!String::NewFromUtf8( - isolate, item.name.c_str(), NewStringType::kNormal) + Local name_string; + if (!ToV8Value(env->context(), item.name, env->isolate()) .ToLocal(&name_string)) { return; } - if (!functions->Set(context, name_string, ret).FromMaybe(false)) { + if (!functions->Set(context, name_string.As(), ret) + .FromMaybe(false)) { return; } } @@ -612,14 +589,14 @@ void DynamicLibrary::GetFunctions(const FunctionCallbackInfo& args) { return; } - Local name_string; - if (!String::NewFromUtf8( - isolate, entry.first.c_str(), NewStringType::kNormal) + Local name_string; + if (!ToV8Value(env->context(), entry.first, env->isolate()) .ToLocal(&name_string)) { return; } - if (!functions->Set(context, name_string, fn).FromMaybe(false)) { + if (!functions->Set(context, name_string.As(), fn) + .FromMaybe(false)) { return; } } @@ -644,7 +621,7 @@ void DynamicLibrary::GetSymbol(const FunctionCallbackInfo& args) { } void* ptr; - if (!lib->ResolveSymbol(env, *name, &ptr)) { + if (!lib->ResolveSymbol(env, *name).To(&ptr)) { return; } @@ -670,16 +647,15 @@ void DynamicLibrary::GetSymbols(const FunctionCallbackInfo& args) { return; } for (const auto& entry : lib->symbols_) { - Local symbol_key; - if (!String::NewFromUtf8( - isolate, entry.first.c_str(), NewStringType::kNormal) + Local symbol_key; + if (!ToV8Value(env->context(), entry.first, env->isolate()) .ToLocal(&symbol_key)) { return; } if (!symbols ->Set(context, - symbol_key, + symbol_key.As(), BigInt::NewFromUnsigned( isolate, static_cast( @@ -720,14 +696,15 @@ void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo& args) { return; } - if (!ParseFunctionSignature(env, - "", - args[0].As(), - &return_type, - &callback_args)) { + FunctionSignature parsed; + if (!ParseFunctionSignature(env, "", args[0].As()) + .To(&parsed)) { return; } + return_type = parsed.return_type; + callback_args = std::move(parsed.args); + fn = args[1].As(); } @@ -827,7 +804,8 @@ void DynamicLibrary::UnregisterCallback( } uintptr_t raw_ptr; - if (!GetValidatedPointerAddress(env, args[0], "first argument", &raw_ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "first argument") + .To(&raw_ptr)) { return; } @@ -862,7 +840,8 @@ void DynamicLibrary::RefCallback(const FunctionCallbackInfo& args) { } uintptr_t raw_ptr; - if (!GetValidatedPointerAddress(env, args[0], "first argument", &raw_ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "first argument") + .To(&raw_ptr)) { return; } @@ -892,7 +871,8 @@ void DynamicLibrary::UnrefCallback(const FunctionCallbackInfo& args) { } uintptr_t raw_ptr; - if (!GetValidatedPointerAddress(env, args[0], "first argument", &raw_ptr)) { + if (!GetValidatedPointerAddress(env, args[0], "first argument") + .To(&raw_ptr)) { return; } diff --git a/src/node_ffi.h b/src/node_ffi.h index a4c518ee8171f0..6482a31ae386a6 100644 --- a/src/node_ffi.h +++ b/src/node_ffi.h @@ -97,13 +97,15 @@ class DynamicLibrary : public BaseObject { private: void Close(); - bool ResolveSymbol(Environment* env, const std::string& name, void** ptr); - bool PrepareFunction(Environment* env, - const std::string& name, - v8::Local signature, - std::shared_ptr* ret, - bool* should_cache_symbol, - bool* should_cache_function); + v8::Maybe ResolveSymbol(Environment* env, const std::string& name); + struct PreparedFunction { + std::shared_ptr fn; + bool should_cache_symbol; + bool should_cache_function; + }; + v8::Maybe PrepareFunction(Environment* env, + const std::string& name, + v8::Local signature); v8::MaybeLocal CreateFunction( Environment* env, const std::string& name, From 16ead6852b7aada32010f0e8d0504be5a200635e Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Mon, 20 Apr 2026 18:53:49 +0200 Subject: [PATCH 2/2] fixup! src: align FFI error handling with Node.js source --- src/ffi/data.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ffi/data.cc b/src/ffi/data.cc index 1f50ed094798b6..5d1fbcfe082a29 100644 --- a/src/ffi/data.cc +++ b/src/ffi/data.cc @@ -29,7 +29,6 @@ using v8::NewStringType; using v8::Nothing; using v8::Number; using v8::Object; -using v8::SharedArrayBuffer; using v8::String; using v8::Value;