| #if HAVE_FFI |
| |
| #include "node_ffi.h" |
| #include <climits> |
| #include <cstring> |
| #include <limits> |
| #include <memory> |
| #include "base_object-inl.h" |
| #include "env-inl.h" |
| #include "ffi/data.h" |
| #include "ffi/fast.h" |
| #include "ffi/types.h" |
| #include "node_errors.h" |
| |
| namespace node { |
| |
| using v8::Array; |
| using v8::ArrayBuffer; |
| using v8::BigInt; |
| using v8::Boolean; |
| using v8::Context; |
| using v8::DontDelete; |
| using v8::DontEnum; |
| using v8::Function; |
| using v8::FunctionCallbackInfo; |
| 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::Null; |
| using v8::Object; |
| using v8::PropertyAttribute; |
| using v8::ReadOnly; |
| using v8::String; |
| using v8::TryCatch; |
| using v8::Value; |
| |
| namespace ffi { |
| |
| void FFIFunctionInfo::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackField("sb_backing", sb_backing); |
| } |
| |
| DynamicLibrary::DynamicLibrary(Environment* env, Local<Object> object) |
| : BaseObject(env, object) { |
| MakeWeak(); |
| } |
| |
| DynamicLibrary::~DynamicLibrary() { |
| this->Close(); |
| } |
| |
| bool DynamicLibrary::is_closed() const { |
| return static_cast<void*>(lib_.handle) == nullptr; |
| } |
| |
| void DynamicLibrary::MemoryInfo(MemoryTracker* tracker) const { |
| tracker->TrackFieldWithSize("path", path_.capacity() + 1, "std::string"); |
| |
| size_t symbols_size = 0; |
| for (const auto& [name, ptr] : symbols_) { |
| symbols_size += name.capacity() + 1; |
| symbols_size += sizeof(ptr); |
| symbols_size += sizeof(decltype(symbols_)::value_type); |
| } |
| |
| tracker->TrackFieldWithSize( |
| "symbols", symbols_size, "std::unordered_map<std::string, void*>"); |
| |
| // FFIFunctionInfo instances and their sb_backing ArrayBuffers are |
| // owned by V8 function wrappers and reachable only via weak references, |
| // so they are deliberately not counted here. |
| } |
| |
| void DynamicLibrary::Close() { |
| for (auto& [name, fn] : functions_) { |
| fn->closed = true; |
| fn->ptr = nullptr; |
| } |
| |
| // Closing the library invalidates all registered callbacks. Node.js does not |
| // track or revoke callback pointers that have already been handed to native |
| // code. If native code calls a callback pointer after `close()` or |
| // `unregisterCallback()`, the behavior is undefined, not allowed, and |
| // dangerous: it can crash the process, produce incorrect output, or corrupt |
| // memory. |
| |
| if (!is_closed()) { |
| uv_dlclose(&lib_); |
| lib_ = {}; |
| } |
| |
| symbols_.clear(); |
| functions_.clear(); |
| callbacks_.clear(); |
| } |
| |
| Maybe<void*> DynamicLibrary::ResolveSymbol(Environment* env, |
| const std::string& name) { |
| if (is_closed()) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return {}; |
| } |
| |
| auto existing = symbols_.find(name); |
| void* ptr; |
| |
| if (existing != symbols_.end()) { |
| ptr = existing->second; |
| } else { |
| if (uv_dlsym(&lib_, name.c_str(), &ptr) != 0) { |
| THROW_ERR_FFI_CALL_FAILED(env, "dlsym failed: %s", uv_dlerror(&lib_)); |
| return {}; |
| } |
| } |
| |
| return Just(ptr); |
| } |
| |
| Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction( |
| Environment* env, const std::string& name, Local<Object> signature) { |
| std::shared_ptr<FFIFunction> fn; |
| auto existing = functions_.find(name); |
| FunctionSignature parsed; |
| |
| if (!ParseFunctionSignature(env, name, signature).To(&parsed)) { |
| return {}; |
| } |
| auto [return_type, args, return_type_name, arg_type_names] = |
| std::move(parsed); |
| |
| bool should_cache_symbol = false; |
| bool should_cache_function = false; |
| |
| if (existing == functions_.end()) { |
| void* ptr; |
| |
| if (!ResolveSymbol(env, name).To(&ptr)) { |
| return {}; |
| } |
| |
| should_cache_symbol = symbols_.find(name) == symbols_.end(); |
| |
| fn = std::make_shared<FFIFunction>( |
| FFIFunction{.closed = false, |
| .ptr = ptr, |
| .cif = {}, |
| .args = args, |
| .return_type = return_type, |
| .arg_type_names = std::move(arg_type_names), |
| .return_type_name = std::move(return_type_name)}); |
| |
| ffi_status status = ffi_prep_cif(&fn->cif, |
| FFI_DEFAULT_ABI, |
| fn->args.size(), |
| fn->return_type, |
| fn->args.data()); |
| if (status != FFI_OK) { |
| const char* msg = "ffi_prep_cif failed"; |
| switch (status) { |
| case FFI_BAD_TYPEDEF: |
| msg = "ffi_prep_cif failed: bad typedef"; |
| break; |
| case FFI_BAD_ABI: |
| msg = "ffi_prep_cif failed: bad ABI"; |
| break; |
| default: |
| msg = "ffi_prep_cif failed: unknown error"; |
| break; |
| } |
| |
| THROW_ERR_FFI_CALL_FAILED(env, msg); |
| return {}; |
| } |
| |
| should_cache_function = true; |
| } else { |
| fn = existing->second; |
| |
| if (!SignaturesMatch(*fn, return_type, args)) { |
| THROW_ERR_INVALID_ARG_VALUE( |
| env, |
| "Function %s" |
| " was already requested with a different signature", |
| name); |
| return {}; |
| } |
| } |
| |
| return Just(PreparedFunction{fn, should_cache_symbol, should_cache_function}); |
| } |
| |
| FFIFunctionInfo::FFIFunctionInfo(Environment* env, |
| Local<Object> object, |
| std::shared_ptr<FFIFunction> fn, |
| DynamicLibrary* library) |
| : BaseObject(env, object), fn(std::move(fn)) { |
| // Keep the DynamicLibrary instance alive as long as any of its functions are |
| // alive |
| object->SetInternalField(FFIFunctionInfo::kLibrary, library->object()); |
| } |
| |
| Local<FunctionTemplate> FFIFunctionInfo::GetConstructorTemplate( |
| IsolateData* isolate_data) { |
| Local<FunctionTemplate> tmpl = |
| isolate_data->ffi_function_constructor_template(); |
| if (tmpl.IsEmpty()) { |
| Isolate* isolate = isolate_data->isolate(); |
| tmpl = MakeLazilyInitializedJSTemplate(isolate_data, kInternalFieldCount); |
| Local<String> classname = FIXED_ONE_BYTE_STRING(isolate, "FFIFunctionInfo"); |
| tmpl->SetClassName(classname); |
| auto instance = tmpl->InstanceTemplate(); |
| instance->SetInternalFieldCount(FFIFunctionInfo::kInternalFieldCount); |
| isolate_data->set_ffi_function_constructor_template(tmpl); |
| } |
| return tmpl; |
| } |
| |
| BaseObjectPtr<FFIFunctionInfo> FFIFunctionInfo::Create( |
| Environment* env, |
| std::shared_ptr<FFIFunction> fn, |
| DynamicLibrary* library) { |
| Local<Object> obj; |
| if (!GetConstructorTemplate(env->isolate_data()) |
| ->InstanceTemplate() |
| ->NewInstance(env->context()) |
| .ToLocal(&obj)) { |
| return nullptr; |
| } |
| return MakeWeakBaseObject<FFIFunctionInfo>(env, obj, std::move(fn), library); |
| } |
| |
| MaybeLocal<Function> DynamicLibrary::CreateFunction( |
| Environment* env, |
| const std::string& name, |
| const std::shared_ptr<FFIFunction>& fn) { |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| |
| auto info = FFIFunctionInfo::Create(env, fn, this); |
| |
| DCHECK_EQ(fn->args.size(), fn->arg_type_names.size()); |
| |
| // Try the generated Fast API path first. If metadata creation rejects the |
| // signature, fall back to SharedBuffer for supported scalar shapes, then to |
| // the generic libffi invoker. |
| info->fast_metadata = CreateFastFFIMetadata(*fn); |
| bool use_fast_api = info->fast_metadata != nullptr; |
| bool use_sb = !use_fast_api && IsSBEligibleSignature(*fn); |
| bool has_ptr_args = use_sb && SignatureHasPointerArgs(*fn); |
| // Fast API signatures that still accept JS pointer-like values need a JS |
| // wrapper with the native type names attached as hidden metadata. |
| bool needs_raw_pointer_conversions = |
| use_fast_api && SignatureNeedsRawPointerConversions(*fn); |
| // A single pointer-like parameter can get a separate Buffer-aware Fast API |
| // entrypoint so Buffer calls avoid JS pointer extraction. |
| bool needs_fast_buffer_invoke = |
| use_fast_api && SignatureNeedsFastBufferInvoke(*fn); |
| |
| MaybeLocal<Function> maybe_ret; |
| if (use_fast_api) { |
| // V8 calls this FunctionTemplate through `fast_metadata->c_function` when |
| // the optimized Fast API call path is available. The normal callback stays |
| // attached as a fallback for V8 deopts and unsupported call sites. |
| Local<FunctionTemplate> tmpl = |
| FunctionTemplate::New(isolate, |
| DynamicLibrary::InvokeFunction, |
| info->object(), |
| Local<v8::Signature>(), |
| fn->args.size(), |
| v8::ConstructorBehavior::kThrow, |
| v8::SideEffectType::kHasSideEffect, |
| &info->fast_metadata->c_function); |
| maybe_ret = tmpl->GetFunction(context); |
| } else { |
| // Non-Fast signatures either use the SharedBuffer invoker, where JS writes |
| // argument slots before calling with no arguments, or the generic invoker |
| // that converts each JS argument in C++. |
| maybe_ret = Function::New(context, |
| use_sb ? DynamicLibrary::InvokeFunctionSB |
| : DynamicLibrary::InvokeFunction, |
| info->object()); |
| } |
| |
| Local<Function> ret; |
| if (!maybe_ret.ToLocal(&ret)) { |
| return MaybeLocal<Function>(); |
| } |
| |
| Local<Value> name_str; |
| if (!ToV8Value(env->context(), name, isolate).ToLocal(&name_str)) { |
| return MaybeLocal<Function>(); |
| } |
| ret->SetName(name_str.As<String>()); |
| |
| if (!ret->Set( |
| context, |
| env->pointer_string(), |
| BigInt::NewFromUnsigned( |
| isolate, |
| static_cast<uint64_t>(reinterpret_cast<uintptr_t>(fn->ptr)))) |
| .FromMaybe(false)) { |
| return MaybeLocal<Function>(); |
| } |
| |
| // Internal properties are keyed by per-isolate Symbols (see |
| // `env_properties.h`) to keep them out of string-key reflection, and the |
| // `ReadOnly | DontEnum | DontDelete` attribute set blocks user code from |
| // reading, modifying, or deleting them. |
| PropertyAttribute internal_attrs = |
| static_cast<PropertyAttribute>(ReadOnly | DontEnum | DontDelete); |
| |
| if (use_sb) { |
| // SharedBuffer layout is intentionally fixed-width: slot 0 stores the |
| // return value and slots 1..N store argument payloads. The JS wrapper and |
| // InvokeFunctionSB share this exact layout. |
| size_t sb_size = 8 * (fn->args.size() + 1); |
| Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, sb_size); |
| // The shared_ptr to the backing store keeps the memory alive while |
| // FFIFunctionInfo still references it. |
| info->sb_backing = ab->GetBackingStore(); |
| |
| if (!ret->DefineOwnProperty( |
| context, env->ffi_sb_shared_buffer_symbol(), ab, internal_attrs) |
| .FromMaybe(false)) { |
| return MaybeLocal<Function>(); |
| } |
| |
| // Signatures with pointer args also expose a slow-path invoker bound |
| // to the same FFIFunctionInfo. The JS wrapper routes through it when a |
| // pointer argument is anything other than a BigInt, null, or undefined |
| // (strings, Buffers, ArrayBuffers, and ArrayBufferViews). |
| if (has_ptr_args) { |
| Local<Function> slow_fn; |
| if (!Function::New( |
| context, DynamicLibrary::InvokeFunction, info->object()) |
| .ToLocal(&slow_fn)) { |
| return MaybeLocal<Function>(); |
| } |
| if (!ret->DefineOwnProperty(context, |
| env->ffi_sb_invoke_slow_symbol(), |
| slow_fn, |
| internal_attrs) |
| .FromMaybe(false)) { |
| return MaybeLocal<Function>(); |
| } |
| } |
| |
| // Attach the original signature type names so the JS wrapper can |
| // rebuild the signature from a raw function when the caller did not |
| // pass arguments and return explicitly. The `lib.functions` accessor |
| // path relies on this. |
| Local<Value> args_arr; |
| if (!ToV8Value(context, fn->arg_type_names, isolate).ToLocal(&args_arr)) { |
| return MaybeLocal<Function>(); |
| } |
| if (!ret->DefineOwnProperty(context, |
| env->ffi_sb_arguments_symbol(), |
| args_arr, |
| internal_attrs) |
| .FromMaybe(false)) { |
| return MaybeLocal<Function>(); |
| } |
| |
| Local<Value> return_name; |
| if (!ToV8Value(context, fn->return_type_name, isolate) |
| .ToLocal(&return_name)) { |
| return MaybeLocal<Function>(); |
| } |
| if (!ret->DefineOwnProperty(context, |
| env->ffi_sb_return_symbol(), |
| return_name, |
| internal_attrs) |
| .FromMaybe(false)) { |
| return MaybeLocal<Function>(); |
| } |
| } |
| |
| if (needs_raw_pointer_conversions || needs_fast_buffer_invoke) { |
| // Fast API wrappers need only the parameter type names. Result conversion |
| // is still handled by V8's CFunction metadata, unlike the SharedBuffer path |
| // which must also know how to read slot 0. |
| Local<Value> arguments_arr; |
| if (!ToV8Value(context, fn->arg_type_names, isolate) |
| .ToLocal(&arguments_arr)) { |
| return MaybeLocal<Function>(); |
| } |
| if (!ret->DefineOwnProperty(context, |
| env->ffi_fast_arguments_symbol(), |
| arguments_arr, |
| internal_attrs) |
| .FromMaybe(false)) { |
| return MaybeLocal<Function>(); |
| } |
| } |
| |
| if (needs_fast_buffer_invoke) { |
| // Build an alternate CFunction that describes the pointer-like argument as |
| // a V8 buffer value. The JS wrapper dispatches here only when the runtime |
| // argument is Buffer/ArrayBuffer-backed memory. |
| std::shared_ptr<FFIFunction> fast_buffer_fn = |
| CloneWithFastBufferArgNames(fn); |
| info->fast_buffer_metadata = CreateFastFFIMetadata(*fast_buffer_fn); |
| if (info->fast_buffer_metadata != nullptr) { |
| // Store the secondary invoker on the primary raw function under a hidden |
| // Symbol. Keeping it separate avoids overloading SharedBuffer slow-path |
| // metadata for Fast API routing. |
| Local<FunctionTemplate> tmpl = |
| FunctionTemplate::New(isolate, |
| DynamicLibrary::InvokeFunction, |
| info->object(), |
| Local<v8::Signature>(), |
| fn->args.size(), |
| v8::ConstructorBehavior::kThrow, |
| v8::SideEffectType::kHasSideEffect, |
| &info->fast_buffer_metadata->c_function); |
| Local<Function> fast_buffer_invoke; |
| if (!tmpl->GetFunction(context).ToLocal(&fast_buffer_invoke)) { |
| return MaybeLocal<Function>(); |
| } |
| if (!ret->DefineOwnProperty(context, |
| env->ffi_fast_buffer_invoke_symbol(), |
| fast_buffer_invoke, |
| internal_attrs) |
| .FromMaybe(false)) { |
| return MaybeLocal<Function>(); |
| } |
| } |
| } |
| |
| return ret; |
| } |
| |
| void DynamicLibrary::New(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| |
| if (!args.IsConstructCall()) { |
| return THROW_ERR_CONSTRUCT_CALL_REQUIRED( |
| env, |
| "Class constructor DynamicLibrary cannot be invoked without 'new'"); |
| } |
| |
| 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()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "Library path must be a string"); |
| return; |
| } |
| #endif |
| |
| const char* library_path = nullptr; |
| DynamicLibrary* lib = new DynamicLibrary(env, args.This()); |
| |
| if (args[0]->IsString()) { |
| Utf8Value filename(env->isolate(), args[0]); |
| if (ThrowIfContainsNullBytes(env, filename, "Library path")) { |
| return; |
| } |
| lib->path_ = filename.ToString(); |
| library_path = lib->path_.c_str(); |
| } |
| |
| CHECK(lib->is_closed()); |
| // Open the library |
| if (uv_dlopen(library_path, &lib->lib_) != 0) { |
| THROW_ERR_FFI_CALL_FAILED(env, "dlopen failed: %s", uv_dlerror(&lib->lib_)); |
| return; |
| } |
| } |
| |
| void DynamicLibrary::Close(const FunctionCallbackInfo<Value>& args) { |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| // Closing a library from one of its active callbacks is unsupported and |
| // dangerous. Callbacks must return before the owning library is closed. |
| lib->Close(); |
| } |
| |
| void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| FFIFunctionInfo* info = Unwrap<FFIFunctionInfo>(args.Data()); |
| CHECK_NOT_NULL(info); |
| FFIFunction* fn = info->fn.get(); |
| |
| if (fn == nullptr || fn->closed || fn->ptr == nullptr) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| // Convert arguments |
| unsigned int expected_args = fn->args.size(); |
| unsigned int provided_args = args.Length(); |
| |
| if (provided_args != expected_args) { |
| THROW_ERR_INVALID_ARG_VALUE(env, |
| "Invalid argument count: expected %s, got %s", |
| expected_args, |
| provided_args); |
| return; |
| } |
| |
| std::vector<uint64_t> values(expected_args, 0); |
| std::vector<void*> ffi_args(expected_args, nullptr); |
| std::vector<std::string> strings; |
| strings.reserve(expected_args); |
| |
| for (unsigned int i = 0; i < expected_args; i++) { |
| FFIArgumentCategory 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 == FFIArgumentCategory::String) { |
| Utf8Value str(env->isolate(), args[i]); |
| |
| if (*str == nullptr) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "Argument %s must be a string", i); |
| return; |
| } |
| |
| if (ThrowIfContainsNullBytes(env, str, "Argument " + std::to_string(i))) { |
| return; |
| } |
| |
| strings.push_back(*str); |
| values[i] = reinterpret_cast<uint64_t>(strings.back().c_str()); |
| ffi_args[i] = &values[i]; |
| } else { |
| ffi_args[i] = &values[i]; |
| } |
| } |
| |
| void* result = nullptr; |
| |
| if (fn->return_type->type != FFI_TYPE_VOID) { |
| result = Malloc(GetFFIReturnValueStorageSize(fn->return_type)); |
| } |
| |
| ffi_call(&fn->cif, FFI_FN(fn->ptr), result, ffi_args.data()); |
| |
| // Return result back to Javascript |
| ToJSReturnValue(env, args, fn->return_type, result); |
| free(result); |
| } |
| |
| void DynamicLibrary::InvokeFunctionSB(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| FFIFunctionInfo* info = Unwrap<FFIFunctionInfo>(args.Data()); |
| CHECK_NOT_NULL(info); |
| FFIFunction* fn = info->fn.get(); |
| |
| if (fn == nullptr || fn->closed || fn->ptr == nullptr) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| // Arguments reach the native invoker through the shared buffer, not |
| // through V8. The JS wrapper always calls the raw function as `rawFn()` |
| // so any non-zero argument count indicates that user code reached the |
| // raw SB function directly and is about to read stale buffer contents. |
| if (args.Length() != 0) { |
| THROW_ERR_INVALID_ARG_VALUE( |
| env, |
| "SB-invoked FFI functions receive arguments through the shared " |
| "buffer, not as JavaScript arguments"); |
| return; |
| } |
| |
| // A failure of either CHECK means the SB invoker ran against a function |
| // that `CreateFunction` did not set up for the fast path, which is a |
| // contract violation. They stay enabled in Release because each FFI call |
| // is already dominated by `ffi_call` itself. |
| CHECK(info->sb_backing); |
| CHECK_EQ(info->sb_backing->ByteLength(), 8u * (info->fn->args.size() + 1)); |
| |
| uint8_t* buffer = static_cast<uint8_t*>(info->sb_backing->Data()); |
| unsigned int nargs = fn->args.size(); |
| |
| // Layout is 8 bytes per slot. The return value lives at offset 0 and |
| // argument i lives at offset 8*(i+1). |
| std::vector<uint64_t> values(nargs, 0); |
| std::vector<void*> ffi_args(nargs, nullptr); |
| |
| for (unsigned int i = 0; i < nargs; i++) { |
| ReadFFIArgFromBuffer(fn->args[i], buffer, 8 * (i + 1), &values[i]); |
| ffi_args[i] = &values[i]; |
| } |
| |
| // The storage must cover both the ffi_arg width that libffi uses for |
| // promoted small integer returns and the 8 bytes needed for non-promoted |
| // SB-eligible returns like f64, i64, and u64. `sizeof(ffi_arg)` is only |
| // 4 on 32-bit ARM, so take the max. |
| constexpr size_t kSBResultStorageSize = |
| sizeof(ffi_arg) > 8 ? sizeof(ffi_arg) : 8; |
| alignas(8) uint8_t result_storage[kSBResultStorageSize] = {0}; |
| void* result = (fn->return_type != &ffi_type_void) ? result_storage : nullptr; |
| |
| ffi_call(&fn->cif, FFI_FN(fn->ptr), result, ffi_args.data()); |
| |
| if (result != nullptr) { |
| WriteFFIReturnToBuffer(fn->return_type, result, buffer, 0); |
| } |
| } |
| |
| // 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, |
| void* user_data) { |
| FFICallback* cb = static_cast<FFICallback*>(user_data); |
| |
| // It is unsupported and dangerous for a callback to unregister itself or |
| // close its owning library while executing. The current invocation must |
| // return before teardown APIs are used. |
| if (cb->owner->is_closed() || cb->ptr == nullptr) { |
| if (ret != nullptr && cb->return_type->size > 0) { |
| std::memset(ret, 0, GetFFIReturnValueStorageSize(cb->return_type)); |
| } |
| return; |
| } |
| |
| if (std::this_thread::get_id() != cb->thread_id) { |
| FPrintF(stderr, |
| "Callbacks can only be invoked on the system thread they were " |
| "created on\n"); |
| ABORT(); |
| } |
| |
| Environment* env = cb->env; |
| Isolate* isolate = env->isolate(); |
| |
| HandleScope handle_scope(isolate); |
| Local<Context> context = env->context(); |
| |
| if (cb->fn.IsEmpty()) { |
| if (ret != nullptr && cb->return_type->size > 0) { |
| std::memset(ret, 0, GetFFIReturnValueStorageSize(cb->return_type)); |
| } |
| return; |
| } |
| |
| size_t expected_args = cb->args.size(); |
| LocalVector<Value> callback_args(isolate, expected_args); |
| |
| for (size_t i = 0; i < expected_args; i++) { |
| if (args[i] == nullptr) { |
| callback_args[i] = Null(isolate); |
| continue; |
| } else { |
| callback_args[i] = ToJSArgument(isolate, cb->args[i], args[i]); |
| } |
| } |
| |
| TryCatch try_catch(isolate); |
| Local<Function> callback = Local<Function>::New(isolate, cb->fn); |
| MaybeLocal<Value> result = callback->Call( |
| context, Undefined(isolate), expected_args, callback_args.data()); |
| |
| // Handle exceptions by crashing (can't propagate across FFI boundary) |
| if (try_catch.HasCaught()) { |
| FPrintF(stderr, "Callbacks cannot throw an exception\n"); |
| ABORT(); |
| } |
| |
| Local<Value> result_val; |
| if (!result.ToLocal(&result_val)) { |
| if (try_catch.HasCaught()) { |
| FPrintF(stderr, "Callbacks cannot return an exception\n"); |
| ABORT(); |
| } |
| return; |
| } |
| |
| if (result_val->IsPromise()) { |
| FPrintF(stderr, "Callbacks cannot return promises\n"); |
| ABORT(); |
| } |
| |
| if (!ToFFIReturnValue(result_val, cb->return_type, ret)) { |
| FPrintF(stderr, "Callback returned invalid value for declared FFI type\n"); |
| ABORT(); |
| } |
| } |
| |
| void DynamicLibrary::GetPath(const FunctionCallbackInfo<Value>& args) { |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| |
| Local<Value> path; |
| if (!ToV8Value(lib->env()->context(), lib->path_, args.GetIsolate()) |
| .ToLocal(&path)) { |
| return; |
| } |
| |
| args.GetReturnValue().Set(path); |
| } |
| |
| void DynamicLibrary::GetFunction(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| if (args.Length() < 1 || !args[0]->IsString()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "Function name must be a string"); |
| return; |
| } |
| |
| if (args.Length() < 2 || !args[1]->IsObject() || args[1]->IsArray()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "Function signature must be an object"); |
| return; |
| } |
| |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| Utf8Value name(isolate, args[0]); |
| if (ThrowIfContainsNullBytes(env, name, "Function name")) { |
| return; |
| } |
| PreparedFunction prepared; |
| |
| Local<Object> signature = args[1].As<Object>(); |
| 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); |
| } |
| if (should_cache_function) { |
| lib->functions_.emplace(*name, fn); |
| } |
| |
| MaybeLocal<Function> maybe_ret = lib->CreateFunction(env, *name, fn); |
| Local<Function> ret; |
| if (!maybe_ret.ToLocal(&ret)) { |
| return; |
| } |
| args.GetReturnValue().Set(ret); |
| } |
| |
| void DynamicLibrary::GetFunctions(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| |
| if (lib->is_closed()) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| Local<Object> functions = Object::New(isolate); |
| if (!functions->SetPrototype(context, Null(isolate)).FromMaybe(false)) { |
| return; |
| } |
| |
| if (args.Length() > 0) { |
| if (!args[0]->IsObject() || args[0]->IsArray()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "Functions signatures must be an object"); |
| return; |
| } |
| |
| Local<Object> signatures = args[0].As<Object>(); |
| Local<Array> keys; |
| if (!signatures->GetOwnPropertyNames(context).ToLocal(&keys)) { |
| return; |
| } |
| |
| std::vector<ResolvedFunction> pending; |
| pending.reserve(keys->Length()); |
| |
| for (uint32_t i = 0; i < keys->Length(); i++) { |
| Local<Value> key; |
| Local<Value> signature; |
| |
| if (!keys->Get(context, i).ToLocal(&key)) { |
| return; |
| } |
| |
| Utf8Value name(isolate, key); |
| if (ThrowIfContainsNullBytes(env, name, "Function name")) { |
| return; |
| } |
| |
| if (!signatures->Get(env->context(), key).ToLocal(&signature)) { |
| return; |
| } |
| |
| if (!signature->IsObject() || signature->IsArray()) { |
| THROW_ERR_INVALID_ARG_TYPE( |
| env, "Signature of function %s must be an object", name); |
| return; |
| } |
| |
| PreparedFunction prepared; |
| |
| Local<Object> signature_object = signature.As<Object>(); |
| 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, |
| .fn = fn, |
| .should_cache_symbol = should_cache_symbol, |
| .should_cache_function = should_cache_function, |
| }); |
| } |
| |
| for (const auto& item : pending) { |
| if (item.should_cache_symbol) { |
| lib->symbols_.emplace(item.name, item.fn->ptr); |
| } |
| if (item.should_cache_function) { |
| lib->functions_.emplace(item.name, item.fn); |
| } |
| } |
| |
| for (const auto& item : pending) { |
| MaybeLocal<Function> maybe_ret = |
| lib->CreateFunction(env, item.name, item.fn); |
| Local<Function> ret; |
| if (!maybe_ret.ToLocal(&ret)) { |
| return; |
| } |
| |
| Local<Value> name_string; |
| if (!ToV8Value(env->context(), item.name, env->isolate()) |
| .ToLocal(&name_string)) { |
| return; |
| } |
| |
| if (!functions->Set(context, name_string.As<String>(), ret) |
| .FromMaybe(false)) { |
| return; |
| } |
| } |
| } else { |
| for (const auto& entry : lib->functions_) { |
| MaybeLocal<Function> maybe_fn = |
| lib->CreateFunction(env, entry.first, entry.second); |
| Local<Function> fn; |
| if (!maybe_fn.ToLocal(&fn)) { |
| return; |
| } |
| |
| Local<Value> name_string; |
| if (!ToV8Value(env->context(), entry.first, env->isolate()) |
| .ToLocal(&name_string)) { |
| return; |
| } |
| |
| if (!functions->Set(context, name_string.As<String>(), fn) |
| .FromMaybe(false)) { |
| return; |
| } |
| } |
| } |
| |
| args.GetReturnValue().Set(functions); |
| } |
| |
| void DynamicLibrary::GetSymbol(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| if (args.Length() < 1 || !args[0]->IsString()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "Symbol name must be a string"); |
| return; |
| } |
| |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| Utf8Value name(isolate, args[0]); |
| if (ThrowIfContainsNullBytes(env, name, "Symbol name")) { |
| return; |
| } |
| void* ptr; |
| |
| if (!lib->ResolveSymbol(env, *name).To(&ptr)) { |
| return; |
| } |
| |
| lib->symbols_.emplace(*name, ptr); |
| |
| args.GetReturnValue().Set(BigInt::NewFromUnsigned( |
| isolate, static_cast<uint64_t>(reinterpret_cast<uintptr_t>(ptr)))); |
| } |
| |
| void DynamicLibrary::GetSymbols(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| |
| if (lib->is_closed()) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| Local<Object> symbols = Object::New(isolate); |
| if (!symbols->SetPrototype(context, Null(isolate)).FromMaybe(false)) { |
| return; |
| } |
| for (const auto& entry : lib->symbols_) { |
| Local<Value> symbol_key; |
| if (!ToV8Value(env->context(), entry.first, env->isolate()) |
| .ToLocal(&symbol_key)) { |
| return; |
| } |
| |
| if (!symbols |
| ->Set(context, |
| symbol_key.As<String>(), |
| BigInt::NewFromUnsigned( |
| isolate, |
| static_cast<uint64_t>( |
| reinterpret_cast<uintptr_t>(entry.second)))) |
| .FromMaybe(false)) { |
| return; |
| } |
| } |
| |
| args.GetReturnValue().Set(symbols); |
| } |
| |
| void DynamicLibrary::RegisterCallback(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| Isolate* isolate = env->isolate(); |
| |
| ffi_type* return_type = &ffi_type_void; |
| std::vector<ffi_type*> callback_args; |
| Local<Function> fn; |
| |
| if (args.Length() < 1) { |
| THROW_ERR_INVALID_ARG_TYPE( |
| env, "First argument must be a function or a signature object"); |
| return; |
| } |
| |
| if (args[0]->IsFunction()) { |
| fn = args[0].As<Function>(); |
| } else { |
| if (!args[0]->IsObject() || args[0]->IsArray()) { |
| THROW_ERR_INVALID_ARG_TYPE( |
| env, "First argument must be a function or a signature object"); |
| return; |
| } |
| |
| if (args.Length() < 2 || !args[1]->IsFunction()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "Second argument must be a function"); |
| return; |
| } |
| |
| FunctionSignature parsed; |
| if (!ParseFunctionSignature(env, "<callback>", args[0].As<Object>()) |
| .To(&parsed)) { |
| return; |
| } |
| |
| return_type = parsed.return_type; |
| callback_args = std::move(parsed.args); |
| |
| fn = args[1].As<Function>(); |
| } |
| |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| if (lib->is_closed()) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| auto callback = std::unique_ptr<FFICallback>( |
| new FFICallback{.owner = lib, |
| .env = env, |
| .thread_id = std::this_thread::get_id(), |
| .fn = Global<Function>(isolate, fn), |
| .closure = nullptr, |
| .ptr = nullptr, |
| .cif = {}, |
| .args = std::move(callback_args), |
| .return_type = return_type}); |
| |
| callback->closure = static_cast<ffi_closure*>( |
| ffi_closure_alloc(sizeof(ffi_closure), &callback->ptr)); |
| |
| if (callback->closure == nullptr) { |
| THROW_ERR_FFI_CALL_FAILED(env, "ffi_closure_alloc failed"); |
| return; |
| } |
| |
| ffi_status status; |
| status = ffi_prep_cif(&callback->cif, |
| FFI_DEFAULT_ABI, |
| callback->args.size(), |
| callback->return_type, |
| callback->args.data()); |
| if (status != FFI_OK) { |
| const char* msg = "ffi_prep_cif failed"; |
| switch (status) { |
| case FFI_BAD_TYPEDEF: |
| msg = "ffi_prep_cif failed: bad typedef"; |
| break; |
| case FFI_BAD_ABI: |
| msg = "ffi_prep_cif failed: bad ABI"; |
| break; |
| default: |
| msg = "ffi_prep_cif failed: unknown error"; |
| break; |
| } |
| |
| THROW_ERR_FFI_CALL_FAILED(env, msg); |
| return; |
| } |
| |
| status = ffi_prep_closure_loc(callback->closure, |
| &callback->cif, |
| DynamicLibrary::InvokeCallback, |
| callback.get(), |
| callback->ptr); |
| if (status != FFI_OK) { |
| const char* msg = "ffi_prep_closure_loc failed"; |
| switch (status) { |
| case FFI_BAD_TYPEDEF: |
| msg = "ffi_prep_closure_loc failed: bad typedef"; |
| break; |
| case FFI_BAD_ABI: |
| msg = "ffi_prep_closure_loc failed: bad ABI"; |
| break; |
| default: |
| msg = "ffi_prep_closure_loc failed: unknown error"; |
| break; |
| } |
| |
| THROW_ERR_FFI_CALL_FAILED(env, msg); |
| return; |
| } |
| |
| auto ret = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(callback->ptr)); |
| lib->callbacks_.emplace(callback->ptr, std::move(callback)); |
| args.GetReturnValue().Set(BigInt::NewFromUnsigned(isolate, ret)); |
| } |
| |
| void DynamicLibrary::UnregisterCallback( |
| const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| |
| if (lib->is_closed()) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| if (args.Length() < 1 || !args[0]->IsBigInt()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint"); |
| return; |
| } |
| |
| uintptr_t raw_ptr; |
| if (!GetValidatedPointerAddress(env, args[0], "first argument") |
| .To(&raw_ptr)) { |
| return; |
| } |
| |
| void* ptr = reinterpret_cast<void*>(raw_ptr); |
| auto existing = lib->callbacks_.find(ptr); |
| |
| if (existing == lib->callbacks_.end()) { |
| THROW_ERR_INVALID_ARG_VALUE(env, "Callback not found"); |
| return; |
| } |
| |
| // This releases the callback trampoline immediately. If foreign code still |
| // retains and invokes the pointer afterwards, the behavior is undefined, not |
| // allowed, and dangerous: it can crash the process, produce incorrect |
| // output, or corrupt memory. Unregistering a callback while it is currently |
| // executing is also unsupported and dangerous. |
| lib->callbacks_.erase(existing); |
| } |
| |
| void DynamicLibrary::RefCallback(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| |
| if (lib->is_closed()) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| if (args.Length() < 1 || !args[0]->IsBigInt()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint"); |
| return; |
| } |
| |
| uintptr_t raw_ptr; |
| if (!GetValidatedPointerAddress(env, args[0], "first argument") |
| .To(&raw_ptr)) { |
| return; |
| } |
| |
| void* ptr = reinterpret_cast<void*>(raw_ptr); |
| auto existing = lib->callbacks_.find(ptr); |
| |
| if (existing == lib->callbacks_.end()) { |
| THROW_ERR_INVALID_ARG_VALUE(env, "Callback not found"); |
| return; |
| } |
| |
| existing->second->fn.ClearWeak(); |
| } |
| |
| void DynamicLibrary::UnrefCallback(const FunctionCallbackInfo<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This()); |
| |
| if (lib->is_closed()) { |
| THROW_ERR_FFI_LIBRARY_CLOSED(env); |
| return; |
| } |
| |
| if (args.Length() < 1 || !args[0]->IsBigInt()) { |
| THROW_ERR_INVALID_ARG_TYPE(env, "The first argument must be a bigint"); |
| return; |
| } |
| |
| uintptr_t raw_ptr; |
| if (!GetValidatedPointerAddress(env, args[0], "first argument") |
| .To(&raw_ptr)) { |
| return; |
| } |
| |
| void* ptr = reinterpret_cast<void*>(raw_ptr); |
| auto existing = lib->callbacks_.find(ptr); |
| |
| if (existing == lib->callbacks_.end()) { |
| THROW_ERR_INVALID_ARG_VALUE(env, "Callback not found"); |
| return; |
| } |
| |
| existing->second->fn.SetWeak(); |
| } |
| |
| Local<FunctionTemplate> DynamicLibrary::GetConstructorTemplate( |
| Environment* env) { |
| Local<FunctionTemplate> tmpl = |
| env->ffi_dynamic_library_constructor_template(); |
| |
| if (tmpl.IsEmpty()) { |
| Isolate* isolate = env->isolate(); |
| enum PropertyAttribute attributes = |
| static_cast<PropertyAttribute>(ReadOnly | DontDelete); |
| |
| tmpl = NewFunctionTemplate(isolate, DynamicLibrary::New); |
| tmpl->InstanceTemplate()->SetInternalFieldCount( |
| DynamicLibrary::kInternalFieldCount); |
| |
| tmpl->InstanceTemplate()->SetAccessorProperty( |
| FIXED_ONE_BYTE_STRING(isolate, "path"), |
| FunctionTemplate::New(env->isolate(), DynamicLibrary::GetPath), |
| Local<FunctionTemplate>(), |
| attributes); |
| |
| tmpl->InstanceTemplate()->SetAccessorProperty( |
| FIXED_ONE_BYTE_STRING(isolate, "symbols"), |
| FunctionTemplate::New(env->isolate(), DynamicLibrary::GetSymbols), |
| Local<FunctionTemplate>(), |
| attributes); |
| |
| // `functions` lives on the prototype template rather than the instance |
| // template so `lib/ffi.js` can replace it via `Object.defineProperty` |
| // on the prototype. The attribute set omits `DontDelete` for the same |
| // reason. |
| tmpl->PrototypeTemplate()->SetAccessorProperty( |
| FIXED_ONE_BYTE_STRING(isolate, "functions"), |
| FunctionTemplate::New(env->isolate(), DynamicLibrary::GetFunctions), |
| Local<FunctionTemplate>(), |
| static_cast<PropertyAttribute>(ReadOnly)); |
| |
| SetProtoMethod(isolate, tmpl, "close", DynamicLibrary::Close); |
| SetProtoDispose(isolate, tmpl, DynamicLibrary::Close); |
| SetProtoMethod(isolate, tmpl, "getFunction", DynamicLibrary::GetFunction); |
| SetProtoMethod(isolate, tmpl, "getFunctions", DynamicLibrary::GetFunctions); |
| SetProtoMethod(isolate, tmpl, "getSymbol", DynamicLibrary::GetSymbol); |
| SetProtoMethod(isolate, tmpl, "getSymbols", DynamicLibrary::GetSymbols); |
| SetProtoMethod( |
| isolate, tmpl, "registerCallback", DynamicLibrary::RegisterCallback); |
| SetProtoMethod(isolate, |
| tmpl, |
| "unregisterCallback", |
| DynamicLibrary::UnregisterCallback); |
| SetProtoMethod(isolate, tmpl, "refCallback", DynamicLibrary::RefCallback); |
| SetProtoMethod( |
| isolate, tmpl, "unrefCallback", DynamicLibrary::UnrefCallback); |
| |
| env->set_ffi_dynamic_library_constructor_template(tmpl); |
| } |
| |
| return tmpl; |
| } |
| |
| // Module initialization. |
| static void Initialize(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Environment* env = Environment::GetCurrent(context); |
| |
| // Create the DynamicLibrary template |
| Local<FunctionTemplate> dl_tmpl = DynamicLibrary::GetConstructorTemplate(env); |
| SetConstructorFunction(context, target, "DynamicLibrary", dl_tmpl); |
| SetMethod(context, target, "toString", ToString); |
| SetMethod(context, target, "toBuffer", ToBuffer); |
| SetMethod(context, target, "toArrayBuffer", ToArrayBuffer); |
| SetMethod(context, target, "exportBytes", ExportBytes); |
| SetMethod(context, target, "getRawPointer", GetRawPointer); |
| |
| SetMethod(context, target, "getInt8", GetInt8); |
| SetMethod(context, target, "getUint8", GetUint8); |
| SetMethod(context, target, "getInt16", GetInt16); |
| SetMethod(context, target, "getUint16", GetUint16); |
| SetMethod(context, target, "getInt32", GetInt32); |
| SetMethod(context, target, "getUint32", GetUint32); |
| SetMethod(context, target, "getInt64", GetInt64); |
| SetMethod(context, target, "getUint64", GetUint64); |
| SetMethod(context, target, "getFloat32", GetFloat32); |
| SetMethod(context, target, "getFloat64", GetFloat64); |
| |
| SetMethod(context, target, "setInt8", SetInt8); |
| SetMethod(context, target, "setUint8", SetUint8); |
| SetMethod(context, target, "setInt16", SetInt16); |
| SetMethod(context, target, "setUint16", SetUint16); |
| SetMethod(context, target, "setInt32", SetInt32); |
| SetMethod(context, target, "setUint32", SetUint32); |
| SetMethod(context, target, "setInt64", SetInt64); |
| SetMethod(context, target, "setUint64", SetUint64); |
| SetMethod(context, target, "setFloat32", SetFloat32); |
| SetMethod(context, target, "setFloat64", SetFloat64); |
| |
| // ToFFIType maps `char` to sint8 or uint8 based on `CHAR_MIN < 0` at C++ |
| // build time. Exposing the same decision to JS lets the shared-buffer |
| // wrapper's range check match `ToFFIArgument` on every platform. |
| Isolate* isolate = env->isolate(); |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "charIsSigned"), |
| Boolean::New(isolate, CHAR_MIN < 0)) |
| .Check(); |
| |
| // The shared-buffer fast path uses `uintptrMax` to reject pointer BigInts |
| // that would otherwise be silently truncated by `ReadFFIArgFromBuffer`'s |
| // `memcpy(..., type->size, ...)` on 32-bit platforms. The slow path |
| // rejects the same values through `ToFFIArgument`. |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "uintptrMax"), |
| v8::BigInt::NewFromUnsigned( |
| isolate, |
| static_cast<uint64_t>(std::numeric_limits<uintptr_t>::max()))) |
| .Check(); |
| |
| // Per-isolate Symbols used by `lib/internal/ffi-shared-buffer.js` to key |
| // shared-buffer internal state on raw FFI functions. |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "kSbSharedBuffer"), |
| env->ffi_sb_shared_buffer_symbol()) |
| .Check(); |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "kSbInvokeSlow"), |
| env->ffi_sb_invoke_slow_symbol()) |
| .Check(); |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "kSbArguments"), |
| env->ffi_sb_arguments_symbol()) |
| .Check(); |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "kSbReturn"), |
| env->ffi_sb_return_symbol()) |
| .Check(); |
| // Fast API wrappers use separate metadata Symbols so pointer-conversion |
| // routing does not depend on SharedBuffer internals. |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "kFastArguments"), |
| env->ffi_fast_arguments_symbol()) |
| .Check(); |
| target |
| ->Set(context, |
| FIXED_ONE_BYTE_STRING(isolate, "kFastBufferInvoke"), |
| env->ffi_fast_buffer_invoke_symbol()) |
| .Check(); |
| } |
| |
| } // namespace ffi |
| } // namespace node |
| |
| NODE_BINDING_CONTEXT_AWARE_INTERNAL(ffi, node::ffi::Initialize) |
| |
| #endif // HAVE_FFI |