diff --git a/benchmark/ffi/getpid.js b/benchmark/ffi/getpid.js
new file mode 100644
index 00000000000000..c2e7d4ed89c34e
--- /dev/null
+++ b/benchmark/ffi/getpid.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const common = require('../common.js');
+const ffi = require('node:ffi');
+
+const bench = common.createBenchmark(main, {
+ n: [1e7],
+}, {
+ flags: ['--experimental-ffi'],
+});
+
+const { lib, functions } = ffi.dlopen(null, {
+ uv_os_getpid: { result: 'i32', parameters: [] },
+});
+
+const getpid = functions.uv_os_getpid;
+
+function main({ n }) {
+ bench.start();
+ for (let i = 0; i < n; ++i)
+ getpid();
+ bench.end(n);
+
+ lib.close();
+}
diff --git a/common.gypi b/common.gypi
index 183d8707682e8e..8bdc0a938e3256 100644
--- a/common.gypi
+++ b/common.gypi
@@ -15,6 +15,7 @@
'python%': 'python',
'node_shared%': 'false',
+ 'node_enable_experimentals%': 0,
'force_dynamic_crt%': 0,
'node_use_v8_platform%': 'true',
'node_use_bundled_v8%': 'true',
@@ -437,6 +438,9 @@
}],
# The defines bellow must include all things from the external_v8_defines
# list in v8/BUILD.gn.
+ ['node_enable_experimentals==1', {
+ 'defines': ['NODE_ENABLE_EXPERIMENTALS'],
+ }],
['v8_enable_v8_checks == 1', {
'defines': ['V8_ENABLE_CHECKS'],
}],
diff --git a/configure.py b/configure.py
index 995d800bf69461..c0bee68d2c7c89 100755
--- a/configure.py
+++ b/configure.py
@@ -797,6 +797,12 @@
default=None,
help='Enable the --trace-maps flag in V8 (use at your own risk)')
+parser.add_argument('--experimental',
+ action='store_true',
+ dest='experimental',
+ default=None,
+ help='Enable all experimental features by default')
+
parser.add_argument('--experimental-enable-pointer-compression',
action='store_true',
dest='enable_pointer_compression',
@@ -1803,6 +1809,7 @@ def configure_node_cctest_sources(o):
def configure_node(o):
if options.dest_os == 'android':
o['variables']['OS'] = 'android'
+ o['variables']['node_enable_experimentals'] = B(options.experimental)
o['variables']['node_prefix'] = options.prefix
o['variables']['node_install_npm'] = b(not options.without_npm)
o['variables']['node_install_corepack'] = b(options.with_corepack)
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 98073d49d62098..c7ef683b082402 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1332,6 +1332,24 @@ added: v14.0.0
Used when a feature that is not available
to the current platform which is running Node.js is used.
+
+
+### `ERR_FFI_INVALID_POINTER`
+
+An invalid pointer was passed to an FFI operation.
+
+
+
+### `ERR_FFI_LIBRARY_CLOSED`
+
+An operation was attempted on an FFI dynamic library after it was closed.
+
+
+
+### `ERR_FFI_SYSCALL_FAILED`
+
+A low-level FFI call failed.
+
### `ERR_FS_CP_DIR_TO_NON_DIR`
diff --git a/doc/api/ffi.md b/doc/api/ffi.md
index 33eef15106124f..6f458e8a7b2f38 100644
--- a/doc/api/ffi.md
+++ b/doc/api/ffi.md
@@ -165,12 +165,15 @@ const path = `libsqlite3.${suffix}`;
added: REPLACEME
-->
-* `path` {string} Path to a dynamic library.
+* `path` {string|null} Path to a dynamic library, or `null` to resolve symbols
+ from the current process image.
* `definitions` {Object} Symbol definitions to resolve immediately.
* Returns: {Object}
Loads a dynamic library and resolves the requested function definitions.
+On Windows passing `null` is not supported.
+
When `definitions` is omitted, `functions` is returned as an empty object until
symbols are resolved explicitly.
@@ -237,10 +240,13 @@ Represents a loaded dynamic library.
### `new DynamicLibrary(path)`
-* `path` {string} Path to a dynamic library.
+* `path` {string|null} Path to a dynamic library, or `null` to resolve symbols
+ from the current process image.
Loads the dynamic library without resolving any functions eagerly.
+On Windows passing `null` is not supported.
+
```cjs
const { DynamicLibrary } = require('node:ffi');
@@ -603,6 +609,55 @@ available storage. This function does not allocate memory on its own.
`buffer` must be a Node.js `Buffer`.
+## `ffi.exportArrayBuffer(arrayBuffer, pointer, length)`
+
+
+
+* `arrayBuffer` {ArrayBuffer}
+* `pointer` {bigint}
+* `length` {number}
+
+Copies bytes from an `ArrayBuffer` into native memory.
+
+`length` must be at least `arrayBuffer.byteLength`.
+
+`pointer` must refer to writable native memory with at least `length` bytes of
+available storage. This function does not allocate memory on its own.
+
+## `ffi.exportArrayBufferView(arrayBufferView, pointer, length)`
+
+
+
+* `arrayBufferView` {ArrayBufferView}
+* `pointer` {bigint}
+* `length` {number}
+
+Copies bytes from an `ArrayBufferView` into native memory.
+
+`length` must be at least `arrayBufferView.byteLength`.
+
+`pointer` must refer to writable native memory with at least `length` bytes of
+available storage. This function does not allocate memory on its own.
+
+## `ffi.getRawPointer(source)`
+
+
+
+* `source` {Buffer|ArrayBuffer|ArrayBufferView}
+* Returns: {bigint}
+
+Returns the raw memory address of JavaScript-managed byte storage.
+
+This is unsafe and dangerous. The returned pointer can become invalid if the
+underlying memory is detached, resized, transferred, or otherwise invalidated.
+Using stale pointers can cause memory corruption or process crashes.
+
## Safety notes
The `node:ffi` module does not track pointer validity, memory ownership, or
diff --git a/lib/ffi.js b/lib/ffi.js
index 944a01330d6ba0..b276f4b29dfcdc 100644
--- a/lib/ffi.js
+++ b/lib/ffi.js
@@ -2,9 +2,13 @@
const {
ObjectFreeze,
+ ObjectPrototypeToString,
} = primordials;
const { Buffer } = require('buffer');
const { emitExperimentalWarning } = require('internal/util');
+const {
+ isArrayBufferView,
+} = require('internal/util/types');
const {
codes: {
ERR_ACCESS_DENIED,
@@ -32,6 +36,8 @@ const {
getUint64,
getFloat32,
getFloat64,
+ exportBytes,
+ getRawPointer,
setInt8,
setUint8,
setInt16,
@@ -114,21 +120,52 @@ function exportString(str, data, len, encoding = 'utf8') {
targetBuffer.fill(0, dataLength, dataLength + terminatorSize);
}
-function exportBuffer(buffer, data, len) {
+function exportBuffer(source, data, len) {
checkFFIPermission();
- if (!Buffer.isBuffer(buffer)) {
- throw new ERR_INVALID_ARG_TYPE('buffer', 'Buffer', buffer);
+ if (!Buffer.isBuffer(source)) {
+ throw new ERR_INVALID_ARG_TYPE('buffer', 'Buffer', source);
}
validateInteger(len, 'len', 0);
- if (len < buffer.length) {
- throw new ERR_OUT_OF_RANGE('len', `>= ${buffer.length}`, len);
+ if (len < source.length) {
+ throw new ERR_OUT_OF_RANGE('len', `>= ${source.length}`, len);
}
- const targetBuffer = toBuffer(data, len, false);
- buffer.copy(targetBuffer, 0, 0, buffer.length);
+ exportBytes(source, data, len);
+}
+
+function exportArrayBuffer(source, data, len) {
+ checkFFIPermission();
+
+ if (ObjectPrototypeToString(source) !== '[object ArrayBuffer]') {
+ throw new ERR_INVALID_ARG_TYPE('arrayBuffer', 'ArrayBuffer', source);
+ }
+
+ validateInteger(len, 'len', 0);
+
+ if (len < source.byteLength) {
+ throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
+ }
+
+ exportBytes(source, data, len);
+}
+
+function exportArrayBufferView(source, data, len) {
+ checkFFIPermission();
+
+ if (!isArrayBufferView(source)) {
+ throw new ERR_INVALID_ARG_TYPE('arrayBufferView', 'ArrayBufferView', source);
+ }
+
+ validateInteger(len, 'len', 0);
+
+ if (len < source.byteLength) {
+ throw new ERR_OUT_OF_RANGE('len', `>= ${source.byteLength}`, len);
+ }
+
+ exportBytes(source, data, len);
}
const suffix = process.platform === 'win32' ? 'dll' : process.platform === 'darwin' ? 'dylib' : 'so';
@@ -163,6 +200,8 @@ module.exports = {
dlopen,
dlclose,
dlsym,
+ exportArrayBuffer,
+ exportArrayBufferView,
exportString,
exportBuffer,
getInt8,
@@ -175,6 +214,7 @@ module.exports = {
getUint64,
getFloat32,
getFloat64,
+ getRawPointer,
setInt8,
setUint8,
setInt16,
diff --git a/src/ffi/data.cc b/src/ffi/data.cc
index 5ffedf0ffb5650..2135bc23786d64 100644
--- a/src/ffi/data.cc
+++ b/src/ffi/data.cc
@@ -13,6 +13,7 @@
#include
using v8::ArrayBuffer;
+using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BigInt;
using v8::Context;
@@ -51,8 +52,8 @@ bool GetValidatedSize(Environment* env,
}
if (length > static_cast(std::numeric_limits::max())) {
- env->ThrowRangeError(
- (std::string("The ") + label + " is too large").c_str());
+ THROW_ERR_OUT_OF_RANGE(
+ env, (std::string("The ") + label + " is too large").c_str());
return false;
}
@@ -81,7 +82,8 @@ bool GetValidatedPointerAddress(Environment* env,
}
if (address > static_cast(std::numeric_limits::max())) {
- env->ThrowRangeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
(std::string("The ") + label + " exceeds the platform pointer range")
.c_str());
return false;
@@ -145,14 +147,14 @@ bool ValidatePointerSpan(Environment* env,
size_t length,
const char* error_message) {
if (offset > std::numeric_limits::max() - raw_ptr) {
- env->ThrowRangeError(error_message);
+ THROW_ERR_INVALID_ARG_VALUE(env, error_message);
return false;
}
uintptr_t start = raw_ptr + offset;
if (length > 0 &&
length - 1 > std::numeric_limits::max() - start) {
- env->ThrowRangeError(error_message);
+ THROW_ERR_INVALID_ARG_VALUE(env, error_message);
return false;
}
@@ -188,7 +190,7 @@ bool GetValidatedPointerAndOffset(Environment* env,
}
if (raw_ptr == 0) {
- env->ThrowError("Cannot dereference a null pointer");
+ THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer");
return false;
}
@@ -224,7 +226,7 @@ bool GetValidatedPointerValueAndOffset(Environment* env,
}
if (raw_ptr == 0) {
- env->ThrowError("Cannot dereference a null pointer");
+ THROW_ERR_FFI_INVALID_POINTER(env, "Cannot dereference a null pointer");
return false;
}
@@ -570,7 +572,8 @@ void ToBuffer(const FunctionCallbackInfo& args) {
}
if (ptr == 0 && len > 0) {
- env->ThrowError("Cannot create a buffer from a null pointer");
+ THROW_ERR_FFI_INVALID_POINTER(env,
+ "Cannot create a buffer from a null pointer");
return;
}
@@ -630,7 +633,8 @@ void ToArrayBuffer(const FunctionCallbackInfo& args) {
}
if (ptr == 0 && len > 0) {
- env->ThrowError("Cannot create an ArrayBuffer from a null pointer");
+ THROW_ERR_FFI_INVALID_POINTER(
+ env, "Cannot create an ArrayBuffer from a null pointer");
return;
}
@@ -668,6 +672,130 @@ void ToArrayBuffer(const FunctionCallbackInfo& args) {
args.GetReturnValue().Set(ab);
}
+void ExportBytes(const FunctionCallbackInfo& args) {
+ Environment* env = Environment::GetCurrent(args);
+
+ THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");
+
+ if (args.Length() < 1) {
+ THROW_ERR_INVALID_ARG_TYPE(
+ env,
+ "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView");
+ return;
+ }
+
+ uint8_t* source_data = nullptr;
+ size_t source_len = 0;
+
+ if (Buffer::HasInstance(args[0])) {
+ source_data = reinterpret_cast(Buffer::Data(args[0]));
+ source_len = Buffer::Length(args[0]);
+ } else if (args[0]->IsArrayBuffer()) {
+ Local array_buffer = args[0].As();
+ std::shared_ptr store = array_buffer->GetBackingStore();
+ if (!store) {
+ THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBuffer backing store");
+ return;
+ }
+ source_data = static_cast(store->Data());
+ source_len = array_buffer->ByteLength();
+ } else if (args[0]->IsArrayBufferView()) {
+ Local view = args[0].As();
+ std::shared_ptr store = view->Buffer()->GetBackingStore();
+ if (!store) {
+ THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
+ return;
+ }
+ source_data = static_cast(store->Data()) + view->ByteOffset();
+ source_len = view->ByteLength();
+ } else {
+ THROW_ERR_INVALID_ARG_TYPE(
+ env,
+ "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView");
+ return;
+ }
+
+ uintptr_t ptr;
+ if (args.Length() < 2 ||
+ !GetValidatedPointerAddress(env, args[1], "pointer", &ptr)) {
+ return;
+ }
+
+ size_t len;
+ if (args.Length() < 3 || !GetValidatedSize(env, args[2], "length", &len)) {
+ return;
+ }
+
+ if (len < source_len) {
+ THROW_ERR_OUT_OF_RANGE(env, "The length must be >= source byte length");
+ return;
+ }
+
+ if (ptr == 0 && source_len > 0) {
+ THROW_ERR_FFI_INVALID_POINTER(env,
+ "Cannot create a buffer from a null pointer");
+ return;
+ }
+
+ if (!ValidatePointerSpan(
+ env,
+ ptr,
+ 0,
+ len,
+ "The pointer and length exceed the platform address range")) {
+ return;
+ }
+
+ if (source_len > 0) {
+ std::memcpy(reinterpret_cast(ptr), source_data, source_len);
+ }
+}
+
+void GetRawPointer(const FunctionCallbackInfo& args) {
+ Environment* env = Environment::GetCurrent(args);
+ Isolate* isolate = env->isolate();
+
+ THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");
+
+ if (args.Length() < 1) {
+ THROW_ERR_INVALID_ARG_TYPE(
+ env,
+ "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView");
+ return;
+ }
+
+ uintptr_t ptr = 0;
+
+ if (Buffer::HasInstance(args[0])) {
+ ptr = reinterpret_cast(Buffer::Data(args[0]));
+ } else if (args[0]->IsArrayBuffer()) {
+ Local array_buffer = args[0].As();
+ std::shared_ptr store = array_buffer->GetBackingStore();
+ if (!store) {
+ THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBuffer backing store");
+ return;
+ }
+ ptr = reinterpret_cast(store->Data());
+ } else if (args[0]->IsArrayBufferView()) {
+ Local view = args[0].As();
+ std::shared_ptr store = view->Buffer()->GetBackingStore();
+ if (!store) {
+ THROW_ERR_INVALID_ARG_VALUE(env, "Invalid ArrayBufferView backing store");
+ return;
+ }
+ ptr = reinterpret_cast(static_cast(store->Data()) +
+ view->ByteOffset());
+ } else {
+ THROW_ERR_INVALID_ARG_TYPE(
+ env,
+ "The first argument must be a Buffer, ArrayBuffer, or ArrayBufferView");
+ return;
+ }
+
+ args.GetReturnValue().Set(
+ BigInt::NewFromUnsigned(isolate, static_cast(ptr)));
+}
+
} // namespace ffi
} // namespace node
diff --git a/src/ffi/types.cc b/src/ffi/types.cc
index f70fa8d09a05dd..54f4c83f127b17 100644
--- a/src/ffi/types.cc
+++ b/src/ffi/types.cc
@@ -4,6 +4,7 @@
#include "base_object-inl.h"
#include "data.h"
#include "ffi.h"
+#include "node_errors.h"
#include "node_ffi.h"
#include "v8.h"
@@ -37,7 +38,8 @@ bool ThrowIfContainsNullBytes(Environment* env,
const std::string& label) {
if (value.length() != 0 &&
std::memchr(*value, '\0', value.length()) != nullptr) {
- env->ThrowTypeError((label + " must not contain null bytes").c_str());
+ THROW_ERR_INVALID_ARG_VALUE(
+ env, (label + " must not contain null bytes").c_str());
return true;
}
@@ -120,7 +122,7 @@ bool ParseFunctionSignature(Environment* env,
std::string msg = "Function signature of " + name +
" must have either 'returns', 'return' or 'result' "
"property";
- env->ThrowTypeError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return false;
}
@@ -128,7 +130,7 @@ bool ParseFunctionSignature(Environment* env,
std::string msg = "Function signature of " + name +
" must have either 'parameters' or 'arguments' "
"property";
- env->ThrowTypeError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return false;
}
@@ -154,7 +156,7 @@ bool ParseFunctionSignature(Environment* env,
if (!return_type_val->IsString()) {
std::string msg =
"Return value type of function " + name + " must be a string";
- env->ThrowTypeError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return false;
}
@@ -178,7 +180,7 @@ bool ParseFunctionSignature(Environment* env,
if (!arguments_val->IsArray()) {
std::string msg =
"Arguments list of function " + name + " must be an array";
- env->ThrowTypeError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return false;
}
@@ -195,7 +197,7 @@ bool ParseFunctionSignature(Environment* env,
if (!arg->IsString()) {
std::string msg = "Argument " + std::to_string(i) + " of function " +
name + " must be a string";
- env->ThrowTypeError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return false;
}
@@ -236,7 +238,7 @@ bool SignaturesMatch(const FFIFunction& fn,
bool ToFFIType(Environment* env, const std::string& type_str, ffi_type** ret) {
if (ret == nullptr) {
- env->ThrowTypeError("ret must not be null");
+ THROW_ERR_INVALID_ARG_VALUE(env, "ret must not be null");
return false;
}
@@ -271,7 +273,7 @@ bool ToFFIType(Environment* env, const std::string& type_str, ffi_type** ret) {
*ret = &ffi_type_pointer;
} else {
std::string msg = std::string("Unsupported FFI type: ") + type_str;
- env->ThrowTypeError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return false;
}
@@ -291,7 +293,8 @@ uint8_t ToFFIArgument(Environment* env,
int64_t value;
if (!GetValidatedSignedInt(env, arg, INT8_MIN, INT8_MAX, "int8", &value)) {
if (env->isolate()->IsExecutionTerminating()) return 0;
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be an int8").c_str());
return 0;
}
@@ -301,7 +304,8 @@ uint8_t ToFFIArgument(Environment* env,
uint64_t value;
if (!GetValidatedUnsignedInt(env, arg, UINT8_MAX, "uint8", &value)) {
if (env->isolate()->IsExecutionTerminating()) return 0;
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be a uint8").c_str());
return 0;
}
@@ -312,7 +316,8 @@ uint8_t ToFFIArgument(Environment* env,
if (!GetValidatedSignedInt(
env, arg, INT16_MIN, INT16_MAX, "int16", &value)) {
if (env->isolate()->IsExecutionTerminating()) return 0;
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be an int16").c_str());
return 0;
}
@@ -322,7 +327,8 @@ uint8_t ToFFIArgument(Environment* env,
uint64_t value;
if (!GetValidatedUnsignedInt(env, arg, UINT16_MAX, "uint16", &value)) {
if (env->isolate()->IsExecutionTerminating()) return 0;
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be a uint16").c_str());
return 0;
}
@@ -330,7 +336,8 @@ uint8_t ToFFIArgument(Environment* env,
*static_cast(ret) = static_cast(value);
} else if (type == &ffi_type_sint32) {
if (!arg->IsInt32()) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be an int32").c_str());
return 0;
}
@@ -338,7 +345,8 @@ uint8_t ToFFIArgument(Environment* env,
*static_cast(ret) = arg->Int32Value(context).FromJust();
} else if (type == &ffi_type_uint32) {
if (!arg->IsUint32()) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be a uint32").c_str());
return 0;
}
@@ -346,7 +354,8 @@ uint8_t ToFFIArgument(Environment* env,
*static_cast(ret) = arg->Uint32Value(context).FromJust();
} else if (type == &ffi_type_sint64) {
if (!arg->IsBigInt()) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be an int64").c_str());
return 0;
}
@@ -354,13 +363,15 @@ uint8_t ToFFIArgument(Environment* env,
bool lossless;
*static_cast(ret) = arg.As()->Int64Value(&lossless);
if (!lossless) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be an int64").c_str());
return 0;
}
} else if (type == &ffi_type_uint64) {
if (!arg->IsBigInt()) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be a uint64").c_str());
return 0;
}
@@ -368,13 +379,15 @@ uint8_t ToFFIArgument(Environment* env,
bool lossless;
*static_cast(ret) = arg.As()->Uint64Value(&lossless);
if (!lossless) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be a uint64").c_str());
return 0;
}
} else if (type == &ffi_type_float) {
if (!arg->IsNumber()) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be a float").c_str());
return 0;
}
@@ -383,7 +396,8 @@ uint8_t ToFFIArgument(Environment* env,
static_cast(arg->NumberValue(context).FromJust());
} else if (type == &ffi_type_double) {
if (!arg->IsNumber()) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) + " must be a double").c_str());
return 0;
}
@@ -405,7 +419,8 @@ uint8_t ToFFIArgument(Environment* env,
std::shared_ptr store = view->Buffer()->GetBackingStore();
if (!store) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Invalid ArrayBufferView backing store for argument " +
std::to_string(index))
.c_str());
@@ -426,9 +441,11 @@ uint8_t ToFFIArgument(Environment* env,
std::shared_ptr store = buffer->GetBackingStore();
if (!store) {
- env->ThrowTypeError(("Invalid ArrayBuffer backing store for argument " +
- std::to_string(index))
- .c_str());
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
+ ("Invalid ArrayBuffer backing store for argument " +
+ std::to_string(index))
+ .c_str());
return 0;
}
@@ -438,22 +455,25 @@ uint8_t ToFFIArgument(Environment* env,
uint64_t pointer = arg.As()->Uint64Value(&lossless);
if (!lossless || pointer > static_cast(
std::numeric_limits::max())) {
- env->ThrowTypeError(("Argument " + std::to_string(index) +
- " must be a non-negative pointer bigint")
- .c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env,
+ ("Argument " + std::to_string(index) +
+ " must be a non-negative pointer bigint")
+ .c_str());
return 0;
}
*static_cast(ret) = pointer;
} else {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Argument " + std::to_string(index) +
" must be a buffer, an ArrayBuffer, a string, or a bigint")
.c_str());
return 0;
}
} else {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_VALUE(
+ env,
("Unsupported FFI type for argument " + std::to_string(index)).c_str());
return 0;
}
diff --git a/src/node_errors.h b/src/node_errors.h
index 8f14b75b10493c..5cbd8f37f44bb9 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -79,6 +79,9 @@ void OOMErrorHandler(const char* location, const v8::OOMDetails& details);
V(ERR_DLOPEN_FAILED, Error) \
V(ERR_ENCODING_INVALID_ENCODED_DATA, TypeError) \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, Error) \
+ V(ERR_FFI_INVALID_POINTER, Error) \
+ V(ERR_FFI_LIBRARY_CLOSED, Error) \
+ V(ERR_FFI_SYSCALL_FAILED, Error) \
V(ERR_FS_CP_EINVAL, Error) \
V(ERR_FS_CP_DIR_TO_NON_DIR, Error) \
V(ERR_FS_CP_NON_DIR_TO_DIR, Error) \
@@ -218,6 +221,7 @@ ERRORS_WITH_CODE(V)
V(ERR_CRYPTO_UNSUPPORTED_OPERATION, "Unsupported crypto operation") \
V(ERR_CRYPTO_JOB_INIT_FAILED, "Failed to initialize crypto job config") \
V(ERR_DLOPEN_FAILED, "DLOpen failed") \
+ V(ERR_FFI_LIBRARY_CLOSED, "Library is closed") \
V(ERR_EXECUTION_ENVIRONMENT_NOT_AVAILABLE, \
"Context not associated with Node.js environment") \
V(ERR_ILLEGAL_CONSTRUCTOR, "Illegal constructor") \
diff --git a/src/node_ffi.cc b/src/node_ffi.cc
index d0c578cfedd46a..dc5143d811b1e4 100644
--- a/src/node_ffi.cc
+++ b/src/node_ffi.cc
@@ -87,7 +87,7 @@ bool DynamicLibrary::ResolveSymbol(Environment* env,
const std::string& name,
void** ret) {
if (handle_ == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return false;
}
@@ -99,7 +99,7 @@ bool DynamicLibrary::ResolveSymbol(Environment* env,
} else {
if (uv_dlsym(&lib_, name.c_str(), &ptr) != 0) {
std::string msg = std::string("dlsym failed: ") + uv_dlerror(&lib_);
- env->ThrowError(msg.c_str());
+ THROW_ERR_FFI_SYSCALL_FAILED(env, msg.c_str());
return false;
}
}
@@ -160,7 +160,7 @@ bool DynamicLibrary::PrepareFunction(Environment* env,
break;
}
- env->ThrowError(msg);
+ THROW_ERR_FFI_SYSCALL_FAILED(env, msg);
return false;
}
@@ -171,7 +171,7 @@ bool DynamicLibrary::PrepareFunction(Environment* env,
if (!SignaturesMatch(*fn, return_type, args)) {
std::string msg = "Function " + name +
" was already requested with a different signature";
- env->ThrowError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return false;
}
}
@@ -238,22 +238,34 @@ void DynamicLibrary::New(const FunctionCallbackInfo& args) {
THROW_IF_INSUFFICIENT_PERMISSIONS(env, permission::PermissionScope::kFFI, "");
+#ifndef _WIN32
+ if (args.Length() < 1 || (!args[0]->IsString() && !args[0]->IsNull())) {
+ THROW_ERR_INVALID_ARG_TYPE(env, "Library path must be a string or null");
+ return;
+ }
+#else
if (args.Length() < 1 || !args[0]->IsString()) {
- env->ThrowTypeError("Library path must be a string");
+ THROW_ERR_INVALID_ARG_TYPE(env, "Library path must be a string");
return;
}
+#endif
+ char* library_path = nullptr;
DynamicLibrary* lib = new DynamicLibrary(env, args.This());
- Utf8Value filename(env->isolate(), args[0]);
- if (ThrowIfContainsNullBytes(env, filename, "Library path")) {
- return;
+
+ if (args[0]->IsString()) {
+ Utf8Value filename(env->isolate(), args[0]);
+ if (ThrowIfContainsNullBytes(env, filename, "Library path")) {
+ return;
+ }
+ library_path = *filename;
+ lib->path_ = std::string(*filename);
}
- lib->path_ = std::string(*filename);
// Open the library
- if (uv_dlopen(*filename, &lib->lib_) != 0) {
+ if (uv_dlopen(library_path, &lib->lib_) != 0) {
std::string msg = std::string("dlopen failed: ") + uv_dlerror(&lib->lib_);
- env->ThrowError(msg.c_str());
+ THROW_ERR_FFI_SYSCALL_FAILED(env, msg.c_str());
return;
}
@@ -274,7 +286,7 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo& args) {
FFIFunction* fn = info->fn.get();
if (fn == nullptr || fn->closed || fn->ptr == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return;
}
@@ -286,7 +298,7 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo& args) {
std::string msg = "Invalid argument count: expected " +
std::to_string(expected_args) + ", got " +
std::to_string(provided_args);
- env->ThrowError(msg.c_str());
+ THROW_ERR_INVALID_ARG_VALUE(env, msg.c_str());
return;
}
@@ -307,7 +319,8 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo& args) {
Utf8Value str(env->isolate(), args[i]);
if (*str == nullptr) {
- env->ThrowTypeError(
+ THROW_ERR_INVALID_ARG_TYPE(
+ env,
("Argument " + std::to_string(i) + " must be a string").c_str());
return;
}
@@ -337,6 +350,14 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo& args) {
free(result);
}
+// This is the function that will be called by libffi when a callback
+// is invoked from a dlopen library. It converts the arguments to JavaScript
+// values and calls the original JavaScript callback function.
+// It also handles the return value and exceptions properly.
+// Note that since this function is called from native code, it must not throw
+// exceptions or return promises, as there is no defined way to propagate them
+// back to the caller.
+// If such cases occur, the process will be aborted to avoid undefined behavior.
void DynamicLibrary::InvokeCallback(ffi_cif* cif,
void* ret,
void** args,
@@ -434,12 +455,12 @@ void DynamicLibrary::GetFunction(const FunctionCallbackInfo& args) {
Isolate* isolate = env->isolate();
if (args.Length() < 1 || !args[0]->IsString()) {
- env->ThrowTypeError("Function name must be a string");
+ THROW_ERR_INVALID_ARG_TYPE(env, "Function name must be a string");
return;
}
if (args.Length() < 2 || !args[1]->IsObject() || args[1]->IsArray()) {
- env->ThrowTypeError("Function signature must be an object");
+ THROW_ERR_INVALID_ARG_TYPE(env, "Function signature must be an object");
return;
}
@@ -484,7 +505,7 @@ void DynamicLibrary::GetFunctions(const FunctionCallbackInfo& args) {
DynamicLibrary* lib = Unwrap(args.This());
if (lib->handle_ == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return;
}
@@ -495,7 +516,7 @@ void DynamicLibrary::GetFunctions(const FunctionCallbackInfo& args) {
if (args.Length() > 0) {
if (!args[0]->IsObject() || args[0]->IsArray()) {
- env->ThrowTypeError("Functions signatures must be an object");
+ THROW_ERR_INVALID_ARG_TYPE(env, "Functions signatures must be an object");
return;
}
@@ -528,7 +549,7 @@ 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";
- env->ThrowTypeError(msg.c_str());
+ THROW_ERR_INVALID_ARG_TYPE(env, msg.c_str());
return;
}
@@ -612,7 +633,7 @@ void DynamicLibrary::GetSymbol(const FunctionCallbackInfo& args) {
Isolate* isolate = env->isolate();
if (args.Length() < 1 || !args[0]->IsString()) {
- env->ThrowTypeError("Symbol name must be a string");
+ THROW_ERR_INVALID_ARG_TYPE(env, "Symbol name must be a string");
return;
}
@@ -640,7 +661,7 @@ void DynamicLibrary::GetSymbols(const FunctionCallbackInfo& args) {
DynamicLibrary* lib = Unwrap(args.This());
if (lib->handle_ == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return;
}
@@ -680,8 +701,8 @@ void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo& args) {
Local fn;
if (args.Length() < 1) {
- env->ThrowTypeError(
- "First argument must be a function or a signature object");
+ THROW_ERR_INVALID_ARG_TYPE(
+ env, "First argument must be a function or a signature object");
return;
}
@@ -689,13 +710,13 @@ void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo& args) {
fn = args[0].As();
} else {
if (!args[0]->IsObject() || args[0]->IsArray()) {
- env->ThrowTypeError(
- "First argument must be a function or a signature object");
+ THROW_ERR_INVALID_ARG_TYPE(
+ env, "First argument must be a function or a signature object");
return;
}
if (args.Length() < 2 || !args[1]->IsFunction()) {
- env->ThrowTypeError("Second argument must be a function");
+ THROW_ERR_INVALID_ARG_TYPE(env, "Second argument must be a function");
return;
}
@@ -712,7 +733,7 @@ void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo& args) {
DynamicLibrary* lib = Unwrap(args.This());
if (lib->handle_ == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return;
}
@@ -730,7 +751,7 @@ void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo& args) {
ffi_closure_alloc(sizeof(ffi_closure), &callback->ptr));
if (callback->closure == nullptr) {
- env->ThrowError("ffi_closure_alloc failed");
+ THROW_ERR_FFI_SYSCALL_FAILED(env, "ffi_closure_alloc failed");
delete callback;
return;
}
@@ -755,7 +776,7 @@ void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo& args) {
break;
}
- env->ThrowError(msg);
+ THROW_ERR_FFI_SYSCALL_FAILED(env, msg);
delete callback;
return;
}
@@ -779,7 +800,7 @@ void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo& args) {
break;
}
- env->ThrowError(msg);
+ THROW_ERR_FFI_SYSCALL_FAILED(env, msg);
delete callback;
return;
}
@@ -796,12 +817,12 @@ void DynamicLibrary::UnregisterCallback(
DynamicLibrary* lib = Unwrap(args.This());
if (lib->handle_ == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return;
}
if (args.Length() < 1 || !args[0]->IsBigInt()) {
- env->ThrowTypeError("The first argument must be a bigint");
+ THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint");
return;
}
@@ -814,7 +835,7 @@ void DynamicLibrary::UnregisterCallback(
auto existing = lib->callbacks_.find(ptr);
if (existing == lib->callbacks_.end()) {
- env->ThrowError("Callback not found");
+ THROW_ERR_INVALID_ARG_VALUE(env, "Callback not found");
return;
}
@@ -831,12 +852,12 @@ void DynamicLibrary::RefCallback(const FunctionCallbackInfo& args) {
DynamicLibrary* lib = Unwrap(args.This());
if (lib->handle_ == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return;
}
if (args.Length() < 1 || !args[0]->IsBigInt()) {
- env->ThrowTypeError("The first argument must be a bigint");
+ THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint");
return;
}
@@ -849,7 +870,7 @@ void DynamicLibrary::RefCallback(const FunctionCallbackInfo& args) {
auto existing = lib->callbacks_.find(ptr);
if (existing == lib->callbacks_.end()) {
- env->ThrowError("Callback not found");
+ THROW_ERR_INVALID_ARG_VALUE(env, "Callback not found");
return;
}
@@ -861,12 +882,12 @@ void DynamicLibrary::UnrefCallback(const FunctionCallbackInfo& args) {
DynamicLibrary* lib = Unwrap(args.This());
if (lib->handle_ == nullptr) {
- env->ThrowError("Library is closed");
+ THROW_ERR_FFI_LIBRARY_CLOSED(env);
return;
}
if (args.Length() < 1 || !args[0]->IsBigInt()) {
- env->ThrowTypeError("The first argument must be a bigint");
+ THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint");
return;
}
@@ -879,7 +900,7 @@ void DynamicLibrary::UnrefCallback(const FunctionCallbackInfo& args) {
auto existing = lib->callbacks_.find(ptr);
if (existing == lib->callbacks_.end()) {
- env->ThrowError("Callback not found");
+ THROW_ERR_INVALID_ARG_VALUE(env, "Callback not found");
return;
}
@@ -952,6 +973,8 @@ static void Initialize(Local