#if HAVE_FFI
#include "types.h"
#include "base_object-inl.h"
#include "data.h"
#include "ffi.h"
#include "node_errors.h"
#include "node_ffi.h"
#include "v8.h"
#include <climits>
#include <cmath>
#include <cstring>
#include <limits>
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BigInt;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Integer;
using v8::Isolate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;
namespace node {
namespace ffi {
bool ThrowIfContainsNullBytes(Environment* env,
const Utf8Value& value,
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);
return true;
}
return false;
}
bool GetStrictSignedInteger(Local<Value> value,
int64_t min,
int64_t max,
int64_t* out) {
if (!value->IsNumber()) {
return false;
}
double number = value.As<Number>()->Value();
if (!std::isfinite(number) || std::floor(number) != number || number < min ||
number > max) {
return false;
}
*out = static_cast<int64_t>(number);
return true;
}
bool GetStrictUnsignedInteger(Local<Value> value, uint64_t max, uint64_t* out) {
if (!value->IsNumber()) {
return false;
}
double number = value.As<Number>()->Value();
if (!std::isfinite(number) || std::floor(number) != number || number < 0 ||
number > static_cast<double>(max)) {
return false;
}
*out = static_cast<uint64_t>(number);
return true;
}
Maybe<FunctionSignature> ParseFunctionSignature(Environment* env,
std::string_view name,
Local<Object> signature) {
Local<Context> context = env->context();
Local<String> return_key = env->return_string();
Local<String> arguments_key = env->arguments_string();
bool has_return;
bool has_arguments;
if (!signature->Has(context, return_key).To(&has_return) ||
!signature->Has(context, arguments_key).To(&has_arguments)) {
return {};
}
ffi_type* return_type = &ffi_type_void;
std::vector<ffi_type*> args;
std::string return_type_name = "void";
std::vector<std::string> arg_type_names;
Isolate* isolate = env->isolate();
if (has_return) {
Local<Value> return_type_val;
if (!signature->Get(context, return_key).ToLocal(&return_type_val)) {
return {};
}
if (!return_type_val->IsString()) {
THROW_ERR_INVALID_ARG_TYPE(
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 " + std::string(name))) {
return {};
}
if (!ToFFIType(env, return_type_str.ToStringView()).To(&return_type)) {
return {};
}
return_type_name = return_type_str.ToString();
}
if (has_arguments) {
Local<Value> arguments_val;
if (!signature->Get(context, arguments_key).ToLocal(&arguments_val)) {
return {};
}
if (!arguments_val->IsArray()) {
THROW_ERR_INVALID_ARG_TYPE(
env, "Arguments list of function %s must be an array", name);
return {};
}
Local<Array> arguments_array = arguments_val.As<Array>();
unsigned int argn = arguments_array->Length();
args.reserve(argn);
for (unsigned int i = 0; i < argn; i++) {
Local<Value> arg;
if (!arguments_array->Get(context, i).ToLocal(&arg)) {
return {};
}
if (!arg->IsString()) {
THROW_ERR_INVALID_ARG_TYPE(
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 " + std::string(name))) {
return {};
}
ffi_type* arg_type;
if (!ToFFIType(env, arg_str.ToStringView()).To(&arg_type)) {
return {};
}
if (arg_type == &ffi_type_void) {
THROW_ERR_INVALID_ARG_VALUE(
env,
"Argument %u of function %s must not be 'void'; "
"use an empty array for no-argument functions",
i,
name);
return {};
}
args.push_back(arg_type);
arg_type_names.emplace_back(arg_str.ToString());
}
}
return Just(FunctionSignature{return_type,
std::move(args),
std::move(return_type_name),
std::move(arg_type_names)});
}
bool SignaturesMatch(const FFIFunction& fn,
ffi_type* return_type,
const std::vector<ffi_type*>& args) {
if (fn.return_type != return_type || fn.args.size() != args.size()) {
return false;
}
for (size_t i = 0; i < args.size(); i++) {
if (fn.args[i] != args[i]) {
return false;
}
}
return true;
}
namespace {
bool IsFastCallEligibleFFIType(ffi_type* type) {
// Accept all numeric types, pointer, and void (void OK as return only).
// Rejects struct types (not yet supported in fast-call path).
return type == &ffi_type_void || type == &ffi_type_sint8 ||
type == &ffi_type_uint8 || type == &ffi_type_sint16 ||
type == &ffi_type_uint16 || type == &ffi_type_sint32 ||
type == &ffi_type_uint32 || type == &ffi_type_sint64 ||
type == &ffi_type_uint64 || type == &ffi_type_float ||
type == &ffi_type_double || type == &ffi_type_pointer;
}
bool IsFunctionTypeName(const std::string& name) {
return name == "function";
}
// Check if an FFI type occupies a floating-point register.
bool IsFFITypeFloat(ffi_type* type) {
return type == &ffi_type_float || type == &ffi_type_double;
}
// Check if an FFI type name maps to a kBuffer (kV8Value) argument in the
// fast-call path, which consumes an extra GP register for the helper call.
bool IsBufferTypeName(const std::string& name) {
return name == "buffer" || name == "arraybuffer";
}
} // namespace
bool IsFastCallEligible(const FFIFunction& fn, const char** out_reason) {
static const char* dummy = "";
if (out_reason == nullptr) out_reason = &dummy;
// Check that a platform stub emitter exists for the current ABI.
// Stub emitters cover AArch64, x86_64 SysV, and Win64 x64. Other platforms
// fall back to libffi.
#if !defined(__aarch64__) && !defined(_M_ARM64) && !defined(__x86_64__) && \
!defined(_M_X64) && !defined(__powerpc64__) && !defined(__ppc64__) && \
!defined(__PPC64__) && !defined(__loongarch64) && \
!(defined(__riscv) && __riscv_xlen == 64) && !defined(__s390x__)
*out_reason = "no platform stub emitter";
return false;
#endif
// Check return type eligibility.
if (!IsFastCallEligibleFFIType(fn.return_type)) {
*out_reason = "unsupported return type";
return false;
}
if (IsFunctionTypeName(fn.return_type_name)) {
*out_reason = "return type is function";
return false;
}
// V8's fast-call lowering caps the C-side arg count. With HasReceiver=kNo
// there's no implicit receiver in the count, so this is the user-arg cap.
// V8's hard limit is 8 args; signatures over that fall back to libffi.
if (fn.args.size() > 8) {
*out_reason = "argument count exceeds V8 fast-call cap";
return false;
}
// Per-ABI register caps for arguments that must be passed in registers.
// If an arg can't fit in a register, it goes on the stack — which the
// current trampoline generators don't support.
// `args` and `arg_type_names` are read in lockstep below. A malformed
// FFIFunction with mismatched lengths would otherwise index out of bounds,
// so reject it here rather than relying on callers to pre-validate.
if (fn.args.size() != fn.arg_type_names.size()) {
*out_reason = "argument type name count mismatch";
return false;
}
size_t gp_count = 0;
size_t fp_count = 0;
bool has_buffer_arg = false;
for (size_t i = 0; i < fn.args.size(); ++i) {
ffi_type* t = fn.args[i];
const std::string& name = fn.arg_type_names[i];
if (!IsFastCallEligibleFFIType(t)) {
*out_reason = "unsupported arg type";
return false;
}
// `void` is fine as a return type but has no register slot, so it cannot
// appear in `args`.
if (t == &ffi_type_void) {
*out_reason = "void cannot be an argument type";
return false;
}
if (IsFunctionTypeName(name)) {
*out_reason = "arg is function";
return false;
}
// `buffer`/`arraybuffer` args arrive as kV8Value in the V8 fast-call
// signature, consuming an extra GP register for the helper call.
if (IsBufferTypeName(name)) {
has_buffer_arg = true;
}
// Count register classes used by each argument.
if (IsFFITypeFloat(t)) {
fp_count++;
} else {
gp_count++;
}
}
// Platform-specific register pressure limits.
#if defined(__aarch64__) || defined(_M_ARM64)
// AArch64: 8 FP registers (v0-v7) + up to 7 GP registers per trampoline
// constraint (the 8th GP slot is consumed by the helper call for buffer
// args). Buffer args and float args can't coexist — the helper call would
// clobber FP state.
const size_t effective_gp = gp_count + (has_buffer_arg ? 1 : 0);
if (has_buffer_arg && fp_count != 0) {
*out_reason = "buffer and float args cannot coexist on AArch64";
return false;
}
if (effective_gp > 7 || fp_count > 8) {
*out_reason = "argument count exceeds AArch64 register limit";
return false;
}
#elif defined(_M_X64)
// Win64 x64 uses positional integer/FP registers. The current emitter handles
// only the register-only scalar subset: receiver plus up to three public
// arguments. Buffer-shaped arguments require FastApiCallbackOptions and a C++
// helper call, which is left to fallback until the Win64 emitter grows stack
// and helper support.
if (has_buffer_arg) {
*out_reason = "buffer args are not yet supported on Win64 x64";
return false;
}
if (fn.args.size() > 3) {
*out_reason = "argument count exceeds Win64 x64 register-only limit";
return false;
}
if (fp_count > 3 || gp_count > 3) {
*out_reason = "argument count exceeds Win64 x64 register-only limit";
return false;
}
#elif defined(__powerpc64__) || defined(__ppc64__) || defined(__PPC64__)
#if defined(_AIX) || \
!(defined(__LITTLE_ENDIAN__) || \
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
*out_reason = "no PPC64BE fast-call trampoline emitter";
return false;
#else
// PPC64LE ELFv2: r3 is occupied by V8's receiver, leaving r4..r10 for
// incoming user GP arguments. FP arguments use FPRs and are not shifted by
// the receiver slot. The first PPC64LE emitter is scalar-only and
// tail-branches to the target, so narrow return normalization and buffer
// helper calls fall back.
if (has_buffer_arg) {
*out_reason = "buffer args are not yet supported on PPC64LE";
return false;
}
if (fn.return_type == &ffi_type_sint8 || fn.return_type == &ffi_type_uint8 ||
fn.return_type == &ffi_type_sint16 ||
fn.return_type == &ffi_type_uint16) {
*out_reason = "narrow returns are not yet supported on PPC64LE";
return false;
}
if (gp_count > 7 || fp_count > 8) {
*out_reason = "argument count exceeds PPC64LE register limit";
return false;
}
#endif
#elif defined(__loongarch64)
// LoongArch64: a0 is occupied by V8's receiver, leaving a1..a7 for incoming
// user GP arguments. FP arguments are already in fa0..fa7. The current
// emitter is scalar-only and tail-branches to the target, so narrow returns
// and buffer helper calls fall back.
if (has_buffer_arg) {
*out_reason = "buffer args are not yet supported on LoongArch64";
return false;
}
if (fn.return_type == &ffi_type_sint8 || fn.return_type == &ffi_type_uint8 ||
fn.return_type == &ffi_type_sint16 ||
fn.return_type == &ffi_type_uint16) {
*out_reason = "narrow returns are not yet supported on LoongArch64";
return false;
}
if (gp_count > 7 || fp_count > 8) {
*out_reason = "argument count exceeds LoongArch64 register limit";
return false;
}
#elif defined(__riscv) && __riscv_xlen == 64
// RISC-V LP64D: a0 is occupied by V8's receiver, leaving a1..a7 for incoming
// user GP arguments. FP arguments are already in fa0..fa7. The current
// emitter is scalar-only and tail-branches to the target, so narrow returns
// and buffer helper calls fall back.
if (has_buffer_arg) {
*out_reason = "buffer args are not yet supported on RISC-V 64";
return false;
}
if (fn.return_type == &ffi_type_sint8 || fn.return_type == &ffi_type_uint8 ||
fn.return_type == &ffi_type_sint16 ||
fn.return_type == &ffi_type_uint16) {
*out_reason = "narrow returns are not yet supported on RISC-V 64";
return false;
}
if (gp_count > 7 || fp_count > 8) {
*out_reason = "argument count exceeds RISC-V 64 register limit";
return false;
}
#elif defined(__s390x__)
// Linux s390x: r2 is occupied by V8's receiver, leaving r3..r6 for incoming
// user GP arguments. FP arguments are already in f0, f2, f4, and f6. The
// current emitter is scalar-only and tail-branches to the target, so narrow
// returns and buffer helper calls fall back.
if (has_buffer_arg) {
*out_reason = "buffer args are not yet supported on s390x";
return false;
}
if (fn.return_type == &ffi_type_sint8 || fn.return_type == &ffi_type_uint8 ||
fn.return_type == &ffi_type_sint16 ||
fn.return_type == &ffi_type_uint16) {
*out_reason = "narrow returns are not yet supported on s390x";
return false;
}
if (gp_count > 4 || fp_count > 4) {
*out_reason = "argument count exceeds s390x register limit";
return false;
}
#elif defined(__x86_64__)
// x86_64 SysV: the V8 receiver occupies rdi, leaving rsi, rdx, rcx, r8, r9
// (5 incoming GP slots); scalar signatures can load one more user GP arg
// from the caller stack, for an effective cap of 6 GP. FP args use
// xmm0-xmm7. Buffer args spill the whole incoming GP window through a C++
// helper, so they cannot coexist with FP args and stay register-only
// (incoming GP = gp + 1 must fit in the 5 incoming registers, i.e. <= 4 GP
// when a buffer is present). These rules mirror the constraints enforced by
// node_ffi_create_fast_trampoline in src/ffi/platforms/x64.cc.
if (has_buffer_arg && fp_count != 0) {
*out_reason = "buffer and float args cannot coexist on x86_64 SysV";
return false;
}
const size_t incoming_gp = gp_count + (has_buffer_arg ? 1 : 0);
const size_t max_incoming_gp = has_buffer_arg ? 5 : 6;
if (incoming_gp > max_incoming_gp || fp_count > 8) {
*out_reason = "argument count exceeds x86_64 SysV register limit";
return false;
}
#endif // __x86_64__
*out_reason = "";
return true;
}
bool IsSBEligibleFFIType(ffi_type* type) {
return type == &ffi_type_void || type == &ffi_type_sint8 ||
type == &ffi_type_uint8 || type == &ffi_type_sint16 ||
type == &ffi_type_uint16 || type == &ffi_type_sint32 ||
type == &ffi_type_uint32 || type == &ffi_type_sint64 ||
type == &ffi_type_uint64 || type == &ffi_type_float ||
type == &ffi_type_double || type == &ffi_type_pointer;
}
bool IsSBEligibleSignature(const FFIFunction& fn) {
// The JS wrapper writes and reads the shared buffer little-endian while
// the C++ side uses memcpy in host order. On big-endian hosts these
// disagree, so the fast path is disabled there.
if constexpr (IsBigEndian()) {
return false;
}
// Zero-argument functions gain nothing from the shared-buffer path
// (no argument packing to skip) and measurably lose on tight native
// calls like `uv_os_getpid` due to the wrapper's fixed overhead.
if (fn.args.empty()) return false;
if (!IsSBEligibleFFIType(fn.return_type)) return false;
for (ffi_type* arg : fn.args) {
if (!IsSBEligibleFFIType(arg)) return false;
}
return true;
}
bool SignatureHasPointerArgs(const FFIFunction& fn) {
for (ffi_type* arg : fn.args) {
if (arg == &ffi_type_pointer) return true;
}
return false;
}
void ReadFFIArgFromBuffer(ffi_type* type,
const uint8_t* buffer,
size_t offset,
void* out) {
CHECK(IsSBEligibleFFIType(type));
CHECK_LE(type->size, sizeof(uint64_t));
// memcpy avoids the strict-aliasing violation that a direct typed load
// from the raw uint8_t buffer would incur.
const uint8_t* src = buffer + offset;
std::memcpy(out, src, type->size);
}
void WriteFFIReturnToBuffer(ffi_type* type,
const void* result,
uint8_t* buffer,
size_t offset) {
CHECK(IsSBEligibleFFIType(type));
uint8_t* dst = buffer + offset;
std::memset(dst, 0, 8);
if (type == &ffi_type_void) {
return;
}
// libffi promotes small integer return values to ffi_arg size, so these
// branches read as ffi_arg or ffi_sarg and then truncate back down.
if (type == &ffi_type_sint8) {
int8_t tmp = static_cast<int8_t>(*static_cast<const ffi_sarg*>(result));
std::memcpy(dst, &tmp, sizeof(tmp));
return;
}
if (type == &ffi_type_uint8) {
uint8_t tmp = static_cast<uint8_t>(*static_cast<const ffi_arg*>(result));
std::memcpy(dst, &tmp, sizeof(tmp));
return;
}
if (type == &ffi_type_sint16) {
int16_t tmp = static_cast<int16_t>(*static_cast<const ffi_sarg*>(result));
std::memcpy(dst, &tmp, sizeof(tmp));
return;
}
if (type == &ffi_type_uint16) {
uint16_t tmp = static_cast<uint16_t>(*static_cast<const ffi_arg*>(result));
std::memcpy(dst, &tmp, sizeof(tmp));
return;
}
if (type == &ffi_type_sint32) {
int32_t tmp = static_cast<int32_t>(*static_cast<const ffi_sarg*>(result));
std::memcpy(dst, &tmp, sizeof(tmp));
return;
}
if (type == &ffi_type_uint32) {
uint32_t tmp = static_cast<uint32_t>(*static_cast<const ffi_arg*>(result));
std::memcpy(dst, &tmp, sizeof(tmp));
return;
}
// Remaining SB-eligible types (sint64, uint64, float, double, pointer)
// are not promoted by libffi and can be copied as-is.
std::memcpy(dst, result, type->size);
}
v8::Maybe<ffi_type*> ToFFIType(Environment* env, std::string_view type_str) {
if (type_str == "void") {
return Just(&ffi_type_void);
} else if (type_str == "char") {
return Just(CHAR_MIN < 0 ? &ffi_type_sint8 : &ffi_type_uint8);
} else if (type_str == "int8" || type_str == "i8") {
return Just(&ffi_type_sint8);
} else if (type_str == "uint8" || type_str == "u8" || type_str == "bool") {
return Just(&ffi_type_uint8);
} else if (type_str == "int16" || type_str == "i16") {
return Just(&ffi_type_sint16);
} else if (type_str == "uint16" || type_str == "u16") {
return Just(&ffi_type_uint16);
} else if (type_str == "int32" || type_str == "i32") {
return Just(&ffi_type_sint32);
} else if (type_str == "uint32" || type_str == "u32") {
return Just(&ffi_type_uint32);
} else if (type_str == "int64" || type_str == "i64") {
return Just(&ffi_type_sint64);
} else if (type_str == "uint64" || type_str == "u64") {
return Just(&ffi_type_uint64);
} else if (type_str == "float32" || type_str == "f32" ||
type_str == "float") {
return Just(&ffi_type_float);
} else if (type_str == "float64" || type_str == "f64" ||
type_str == "double") {
return Just(&ffi_type_double);
} else if (type_str == "pointer" || type_str == "string" ||
type_str == "buffer" || type_str == "arraybuffer" ||
type_str == "function" || type_str == "ptr" || type_str == "str") {
return Just(&ffi_type_pointer);
} else {
THROW_ERR_INVALID_ARG_VALUE(env, "Unsupported FFI type: %s", type_str);
return {};
}
}
// The JS fast path in lib/internal/ffi-shared-buffer.js mirrors the
// validation below. `writeNumericArg` matches the numeric branches and
// `writePointerArg` matches the pointer-BigInt branch. Error codes and
// messages must stay identical across all three sites.
Maybe<FFIArgumentCategory> ToFFIArgument(Environment* env,
unsigned int index,
ffi_type* type,
Local<Value> arg,
void* ret) {
Local<Context> context = env->context();
if (type == &ffi_type_void) {
return Just(FFIArgumentCategory::Regular);
} else if (type == &ffi_type_sint8) {
int64_t value;
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 {};
}
*static_cast<int8_t*>(ret) = static_cast<int8_t>(value);
} else if (type == &ffi_type_uint8) {
uint64_t value;
if (!GetValidatedUnsignedInt(env, arg, UINT8_MAX, "uint8").To(&value)) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint8", index);
return {};
}
*static_cast<uint8_t*>(ret) = static_cast<uint8_t>(value);
} else if (type == &ffi_type_sint16) {
int64_t value;
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 {};
}
*static_cast<int16_t*>(ret) = static_cast<int16_t>(value);
} else if (type == &ffi_type_uint16) {
uint64_t value;
if (!GetValidatedUnsignedInt(env, arg, UINT16_MAX, "uint16").To(&value)) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint16", index);
return {};
}
*static_cast<uint16_t*>(ret) = static_cast<uint16_t>(value);
} else if (type == &ffi_type_sint32) {
int64_t value;
if (!GetValidatedSignedInt(env, arg, INT32_MIN, INT32_MAX, "int32")
.To(&value)) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int32", index);
return {};
}
*static_cast<int32_t*>(ret) = static_cast<int32_t>(value);
} else if (type == &ffi_type_uint32) {
uint64_t value;
if (!GetValidatedUnsignedInt(env, arg, UINT32_MAX, "uint32").To(&value)) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint32", index);
return {};
}
*static_cast<uint32_t*>(ret) = static_cast<uint32_t>(value);
} else if (type == &ffi_type_sint64) {
if (arg->IsBigInt()) {
bool lossless;
int64_t value = arg.As<BigInt>()->Int64Value(&lossless);
if (!lossless) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int64", index);
return {};
}
*static_cast<int64_t*>(ret) = value;
} else {
int64_t value;
if (!GetStrictSignedInteger(
arg, -kMaxSafeJsInteger, kMaxSafeJsInteger, &value)) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be an int64", index);
return {};
}
*static_cast<int64_t*>(ret) = value;
}
} else if (type == &ffi_type_uint64) {
if (arg->IsBigInt()) {
bool lossless;
uint64_t value = arg.As<BigInt>()->Uint64Value(&lossless);
if (!lossless) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint64", index);
return {};
}
*static_cast<uint64_t*>(ret) = value;
} else {
uint64_t value;
if (!GetStrictUnsignedInteger(arg, kMaxSafeJsInteger, &value)) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a uint64", index);
return {};
}
*static_cast<uint64_t*>(ret) = value;
}
} else if (type == &ffi_type_float) {
if (!arg->IsNumber()) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a float", index);
return {};
}
*static_cast<float*>(ret) =
static_cast<float>(arg->NumberValue(context).FromJust());
} else if (type == &ffi_type_double) {
if (!arg->IsNumber()) {
THROW_ERR_INVALID_ARG_VALUE(env, "Argument %u must be a double", index);
return {};
}
*static_cast<double*>(ret) = arg->NumberValue(context).FromJust();
} else if (type == &ffi_type_pointer) {
if (arg->IsNullOrUndefined()) {
*static_cast<uint64_t*>(ret) = reinterpret_cast<uint64_t>(nullptr);
} else if (arg->IsString()) {
// String arguments are handled in Invoke so the UTF-8 copy can be
// freed after the call.
return Just(FFIArgumentCategory::String);
} else if (arg->IsArrayBufferView()) {
// Pointer-like ArrayBufferView arguments borrow backing-store memory
// without pinning. Resizing, transferring, detaching, or otherwise
// invalidating that backing store during the active FFI call is
// unsupported and dangerous.
Local<ArrayBufferView> view = arg.As<ArrayBufferView>();
if (view->Buffer()->WasDetached()) {
THROW_ERR_INVALID_ARG_VALUE(
env,
"Argument %u is an ArrayBufferView backed by a detached "
"ArrayBuffer",
index);
return {};
}
std::shared_ptr<BackingStore> store = view->Buffer()->GetBackingStore();
if (!store) {
THROW_ERR_INVALID_ARG_VALUE(
env,
"Invalid ArrayBufferView backing store for argument %u",
index);
return {};
}
void* data = store->Data();
size_t offset = view->ByteOffset();
*static_cast<uint64_t*>(ret) =
reinterpret_cast<uint64_t>(static_cast<char*>(data) + offset);
} else if (arg->IsArrayBuffer()) {
// Pointer-like ArrayBuffer arguments borrow backing-store memory without
// pinning. Resizing, transferring, detaching, or otherwise invalidating
// that backing store during the active FFI call is unsupported and
// dangerous.
Local<ArrayBuffer> buffer = arg.As<ArrayBuffer>();
if (buffer->WasDetached()) {
THROW_ERR_INVALID_ARG_VALUE(
env, "Argument %u is a detached ArrayBuffer", index);
return {};
}
std::shared_ptr<BackingStore> store = buffer->GetBackingStore();
if (!store) {
THROW_ERR_INVALID_ARG_VALUE(
env, "Invalid ArrayBuffer backing store for argument %u", index);
return {};
}
*static_cast<uint64_t*>(ret) = reinterpret_cast<uint64_t>(store->Data());
} else if (arg->IsBigInt()) {
bool lossless;
uint64_t pointer = arg.As<BigInt>()->Uint64Value(&lossless);
if (!lossless || pointer > static_cast<uint64_t>(
std::numeric_limits<uintptr_t>::max())) {
THROW_ERR_INVALID_ARG_VALUE(
env, "Argument %u must be a non-negative pointer bigint", index);
return {};
}
*static_cast<uint64_t*>(ret) = pointer;
} else {
THROW_ERR_INVALID_ARG_VALUE(
env,
"Argument %u must be a buffer, an ArrayBuffer, a string, or a bigint",
index);
return {};
}
} else {
THROW_ERR_INVALID_ARG_VALUE(
env, "Unsupported FFI type for argument %u", index);
return {};
}
return Just(FFIArgumentCategory::Regular);
}
Local<Value> ToJSArgument(Isolate* isolate, ffi_type* type, void* data) {
Local<Value> ret;
if (type == &ffi_type_sint8) {
ret = Integer::New(isolate, *static_cast<int8_t*>(data));
} else if (type == &ffi_type_uint8) {
ret = Integer::NewFromUnsigned(isolate, *static_cast<uint8_t*>(data));
} else if (type == &ffi_type_sint16) {
ret = Integer::New(isolate, *static_cast<int16_t*>(data));
} else if (type == &ffi_type_uint16) {
ret = Integer::NewFromUnsigned(isolate, *static_cast<uint16_t*>(data));
} else if (type == &ffi_type_sint32) {
ret = Integer::New(isolate, *static_cast<int32_t*>(data));
} else if (type == &ffi_type_uint32) {
ret = Integer::NewFromUnsigned(isolate, *static_cast<uint32_t*>(data));
} else if (type == &ffi_type_sint64) {
ret = BigInt::New(isolate, *static_cast<int64_t*>(data));
} else if (type == &ffi_type_uint64) {
ret = BigInt::NewFromUnsigned(isolate, *static_cast<uint64_t*>(data));
} else if (type == &ffi_type_float) {
ret = Number::New(isolate, *static_cast<float*>(data));
} else if (type == &ffi_type_double) {
ret = Number::New(isolate, *static_cast<double*>(data));
} else if (type == &ffi_type_pointer) {
// Pointers surface as BigInt. Callers decode them further with the
// ffi helpers.
ret = BigInt::NewFromUnsigned(
isolate, reinterpret_cast<uint64_t>(*static_cast<void**>(data)));
} else {
ret = Undefined(isolate);
}
return ret;
}
size_t GetFFIReturnValueStorageSize(ffi_type* type) {
if (type == &ffi_type_sint8 || type == &ffi_type_uint8 ||
type == &ffi_type_sint16 || type == &ffi_type_uint16 ||
type == &ffi_type_sint32 || type == &ffi_type_uint32) {
return sizeof(ffi_arg);
}
return type->size;
}
bool ToJSReturnValue(Environment* env,
const FunctionCallbackInfo<Value>& args,
ffi_type* type,
void* result) {
if (type == &ffi_type_void) {
args.GetReturnValue().SetUndefined();
} else if (type == &ffi_type_sint8) {
args.GetReturnValue().Set(static_cast<int32_t>(
static_cast<int8_t>(*static_cast<const ffi_sarg*>(result))));
} else if (type == &ffi_type_uint8) {
args.GetReturnValue().Set(static_cast<uint32_t>(
static_cast<uint8_t>(*static_cast<const ffi_arg*>(result))));
} else if (type == &ffi_type_sint16) {
args.GetReturnValue().Set(static_cast<int32_t>(
static_cast<int16_t>(*static_cast<const ffi_sarg*>(result))));
} else if (type == &ffi_type_uint16) {
args.GetReturnValue().Set(static_cast<uint32_t>(
static_cast<uint16_t>(*static_cast<const ffi_arg*>(result))));
} else if (type == &ffi_type_sint32) {
args.GetReturnValue().Set(
static_cast<int32_t>(*static_cast<const ffi_sarg*>(result)));
} else if (type == &ffi_type_uint32) {
args.GetReturnValue().Set(
static_cast<uint32_t>(*static_cast<const ffi_arg*>(result)));
} else if (type == &ffi_type_sint64) {
args.GetReturnValue().Set(
BigInt::New(env->isolate(), *static_cast<const int64_t*>(result)));
} else if (type == &ffi_type_uint64) {
args.GetReturnValue().Set(BigInt::NewFromUnsigned(
env->isolate(), *static_cast<const uint64_t*>(result)));
} else if (type == &ffi_type_float) {
args.GetReturnValue().Set(*static_cast<const float*>(result));
} else if (type == &ffi_type_double) {
args.GetReturnValue().Set(*static_cast<const double*>(result));
} else if (type == &ffi_type_pointer) {
auto ptr = *static_cast<void* const*>(result);
args.GetReturnValue().Set(BigInt::NewFromUnsigned(
env->isolate(),
static_cast<uint64_t>(reinterpret_cast<uintptr_t>(ptr))));
}
return true;
}
bool ToFFIReturnValue(Local<Value> result, ffi_type* type, void* ret) {
if (type != &ffi_type_void && ret != nullptr) {
std::memset(ret, 0, GetFFIReturnValueStorageSize(type));
}
if (type == &ffi_type_sint8) {
int64_t value;
if (!GetStrictSignedInteger(result, INT8_MIN, INT8_MAX, &value)) {
return false;
}
*static_cast<ffi_sarg*>(ret) = static_cast<ffi_sarg>(value);
} else if (type == &ffi_type_uint8) {
uint64_t value;
if (!GetStrictUnsignedInteger(result, UINT8_MAX, &value)) {
return false;
}
*static_cast<ffi_arg*>(ret) = static_cast<ffi_arg>(value);
} else if (type == &ffi_type_sint16) {
int64_t value;
if (!GetStrictSignedInteger(result, INT16_MIN, INT16_MAX, &value)) {
return false;
}
*static_cast<ffi_sarg*>(ret) = static_cast<ffi_sarg>(value);
} else if (type == &ffi_type_uint16) {
uint64_t value;
if (!GetStrictUnsignedInteger(result, UINT16_MAX, &value)) {
return false;
}
*static_cast<ffi_arg*>(ret) = static_cast<ffi_arg>(value);
} else if (type == &ffi_type_sint32) {
int64_t value;
if (!GetStrictSignedInteger(result, INT32_MIN, INT32_MAX, &value)) {
return false;
}
*static_cast<ffi_sarg*>(ret) = static_cast<ffi_sarg>(value);
} else if (type == &ffi_type_uint32) {
uint64_t value;
if (!GetStrictUnsignedInteger(result, UINT32_MAX, &value)) {
return false;
}
*static_cast<ffi_arg*>(ret) = static_cast<ffi_arg>(value);
} else if (type == &ffi_type_sint64) {
bool lossless;
if (!result->IsBigInt()) {
return false;
}
*static_cast<int64_t*>(ret) = result.As<BigInt>()->Int64Value(&lossless);
if (!lossless) {
return false;
}
} else if (type == &ffi_type_uint64) {
bool lossless;
if (!result->IsBigInt()) {
return false;
}
*static_cast<uint64_t*>(ret) = result.As<BigInt>()->Uint64Value(&lossless);
if (!lossless) {
return false;
}
} else if (type == &ffi_type_float) {
if (!result->IsNumber()) {
return false;
}
*static_cast<float*>(ret) =
static_cast<float>(result.As<Number>()->Value());
} else if (type == &ffi_type_double) {
if (!result->IsNumber()) {
return false;
}
*static_cast<double*>(ret) = result.As<Number>()->Value();
} else if (type == &ffi_type_pointer) {
bool lossless;
if (result->IsNullOrUndefined()) {
*static_cast<uint64_t*>(ret) = reinterpret_cast<uint64_t>(nullptr);
} else if (result->IsBigInt()) {
uint64_t pointer = result.As<BigInt>()->Uint64Value(&lossless);
if (!lossless || pointer > static_cast<uint64_t>(
std::numeric_limits<uintptr_t>::max())) {
return false;
}
*static_cast<uint64_t*>(ret) = pointer;
} else {
// Strings, Buffers, and ArrayBuffers are not accepted as pointer
// return values from a JS callback. The slot is zeroed before the
// false return so the caller sees a defined null pointer.
*static_cast<uint64_t*>(ret) = reinterpret_cast<uint64_t>(nullptr);
return false;
}
} else if (type != &ffi_type_void) {
return false;
}
return true;
}
} // namespace ffi
} // namespace node
#endif // HAVE_FFI