@@ -106,6 +106,14 @@ void DynamicLibrary::MemoryInfo(MemoryTracker* tracker) const {
106106 sizeof (decltype (function_wrappers_)::value_type),
107107 " std::unordered_map<std::string, v8::Global<v8::Function>>" );
108108
109+ tracker->TrackFieldWithSize (
110+ " pointer_functions" ,
111+ pointer_functions_ == nullptr
112+ ? 0
113+ : sizeof (std::unordered_set<FFIFunction*>) +
114+ pointer_functions_->size () * sizeof (FFIFunction*),
115+ " std::unordered_set<FFIFunction*>" );
116+
109117 // FFIFunctionInfo instances and their sb_backing ArrayBuffers are
110118 // owned by V8 function wrappers and reachable only via weak references,
111119 // so they are deliberately not counted here.
@@ -117,6 +125,14 @@ void DynamicLibrary::Close() {
117125 fn->ptr = nullptr ;
118126 }
119127
128+ if (pointer_functions_ != nullptr ) {
129+ for (FFIFunction* fn : *pointer_functions_) {
130+ fn->closed = true ;
131+ fn->ptr = nullptr ;
132+ }
133+ pointer_functions_->clear ();
134+ }
135+
120136 // Closing the library invalidates all registered callbacks. Node.js does not
121137 // track or revoke callback pointers that have already been handed to native
122138 // code. If native code calls a callback pointer after `close()` or
@@ -158,7 +174,10 @@ Maybe<void*> DynamicLibrary::ResolveSymbol(Environment* env,
158174}
159175
160176Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction (
161- Environment* env, const std::string& name, Local<Object> signature) {
177+ Environment* env,
178+ const std::string& name,
179+ Local<Object> signature,
180+ void * ptr) {
162181 std::shared_ptr<FFIFunction> fn;
163182 FunctionSignature parsed;
164183
@@ -167,22 +186,22 @@ Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction(
167186 }
168187 // Look up the cache only after parsing: the signature's getters run user
169188 // code that may close the library, which clears `functions_`.
170- auto existing = functions_.find (name);
189+ const bool from_pointer = ptr != nullptr ;
190+ auto existing = from_pointer ? functions_.end () : functions_.find (name);
171191 auto [return_type, args, return_type_name, arg_type_names] =
172192 std::move (parsed);
173193
174194 bool should_cache_symbol = false ;
175195 bool should_cache_function = false ;
176196
177197 if (existing == functions_.end ()) {
178- void * ptr;
179-
180- if (!ResolveSymbol (env, name).To (&ptr)) {
181- return {};
198+ if (!from_pointer) {
199+ if (!ResolveSymbol (env, name).To (&ptr)) {
200+ return {};
201+ }
202+ should_cache_symbol = symbols_.find (name) == symbols_.end ();
182203 }
183204
184- should_cache_symbol = symbols_.find (name) == symbols_.end ();
185-
186205 fn = std::make_shared<FFIFunction>();
187206 fn->ptr = ptr;
188207 fn->args = std::move (args);
@@ -221,7 +240,7 @@ Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction(
221240 }
222241#endif
223242
224- should_cache_function = true ;
243+ should_cache_function = !from_pointer ;
225244 } else {
226245 fn = existing->second ;
227246
@@ -309,7 +328,6 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(
309328 const std::string& name,
310329 const std::shared_ptr<FFIFunction>& fn) {
311330 Isolate* isolate = env->isolate ();
312- Local<Context> context = env->context ();
313331
314332 // Creating a callable emits a trampoline, allocates an FFIFunctionInfo, and
315333 // on the SharedBuffer path allocates an ArrayBuffer, so reuse the one already
@@ -324,20 +342,41 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(
324342 function_wrappers_.erase (cached);
325343 }
326344
345+ Local<Function> ret;
346+ if (!BuildFunction (env, name, fn, true ).ToLocal (&ret)) {
347+ return {};
348+ }
349+ function_wrappers_.emplace (name, Global<Function>(isolate, ret))
350+ .first ->second .SetWeak ();
351+ return ret;
352+ }
353+
354+ MaybeLocal<Function> DynamicLibrary::BuildFunction (
355+ Environment* env,
356+ const std::string& name,
357+ const std::shared_ptr<FFIFunction>& fn,
358+ bool optimize) {
359+ Isolate* isolate = env->isolate ();
360+ Local<Context> context = env->context ();
327361 auto info = FFIFunctionInfo::Create (env, fn, this );
362+ if (!info) {
363+ return {};
364+ }
328365
329366 DCHECK_EQ (fn->args .size (), fn->arg_type_names .size ());
330367
331368 // Try the generated Fast API path first. If metadata creation rejects the
332369 // signature, fall back to SharedBuffer for supported scalar shapes, then to
333370 // the generic libffi invoker.
334- std::shared_ptr<FFIFunction> fast_fn = CloneWithRawPointerArgNames (fn);
335- if (!CreateFastFFIMetadata (*fast_fn, &fn->closed , isolate)
336- .MoveTo (&info->fast_metadata )) {
337- return {};
371+ if (optimize) {
372+ std::shared_ptr<FFIFunction> fast_fn = CloneWithRawPointerArgNames (fn);
373+ if (!CreateFastFFIMetadata (*fast_fn, &fn->closed , isolate)
374+ .MoveTo (&info->fast_metadata )) {
375+ return {};
376+ }
338377 }
339378 bool use_fast_api = info->fast_metadata != nullptr ;
340- bool use_sb = !use_fast_api && IsSBEligibleSignature (*fn);
379+ bool use_sb = optimize && !use_fast_api && IsSBEligibleSignature (*fn);
341380 bool has_ptr_args = use_sb && SignatureHasPointerArgs (*fn);
342381 // Signatures that need JS-side conversion or validation use a wrapper, as
343382 // do all fast signatures on platforms without a native library guard.
@@ -532,14 +571,6 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(
532571 }
533572 }
534573
535- // A strong handle would root the callable, which holds the library object
536- // through FFIFunctionInfo, so neither could ever be collected. Weaken the
537- // stored handle instead, so the cache lasts exactly as long as user code
538- // keeps a reference. SetWeak() runs after the move into the map because
539- // moving a handle relocates the underlying slot.
540- function_wrappers_.emplace (name, Global<Function>(isolate, ret))
541- .first ->second .SetWeak ();
542-
543574 return ret;
544575}
545576
@@ -921,6 +952,59 @@ void DynamicLibrary::GetFunction(const FunctionCallbackInfo<Value>& args) {
921952 args.GetReturnValue ().Set (ret);
922953}
923954
955+ void DynamicLibrary::ToFunction (const FunctionCallbackInfo<Value>& args) {
956+ Environment* env = Environment::GetCurrent (args);
957+ THROW_IF_INSUFFICIENT_PERMISSIONS (env, permission::PermissionScope::kFFI , " " );
958+
959+ if (args.Length () < 1 || !args[0 ]->IsBigInt ()) {
960+ THROW_ERR_INVALID_ARG_TYPE (env, " Function pointer must be a bigint" );
961+ return ;
962+ }
963+ bool lossless;
964+ uint64_t address = args[0 ].As <BigInt>()->Uint64Value (&lossless);
965+ if (!lossless || address == 0 ||
966+ address > static_cast <uint64_t >(std::numeric_limits<uintptr_t >::max ())) {
967+ THROW_ERR_INVALID_ARG_VALUE (env, " Invalid function pointer" );
968+ return ;
969+ }
970+ if (args.Length () < 2 || !args[1 ]->IsObject () || args[1 ]->IsArray ()) {
971+ THROW_ERR_INVALID_ARG_TYPE (env, " Function signature must be an object" );
972+ return ;
973+ }
974+
975+ DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This ());
976+ if (lib->is_closed ()) {
977+ THROW_ERR_FFI_LIBRARY_CLOSED (env);
978+ return ;
979+ }
980+ Local<Object> signature = args[1 ].As <Object>();
981+ PreparedFunction prepared;
982+ void * ptr = reinterpret_cast <void *>(static_cast <uintptr_t >(address));
983+ if (!lib->PrepareFunction (env, " <pointer>" , signature, ptr).To (&prepared)) {
984+ return ;
985+ }
986+ THROW_IF_INSUFFICIENT_PERMISSIONS (env, permission::PermissionScope::kFFI , " " );
987+ if (lib->is_closed ()) {
988+ THROW_ERR_FFI_LIBRARY_CLOSED (env);
989+ return ;
990+ }
991+
992+ auto fn = std::move (prepared.fn );
993+ if (lib->pointer_functions_ == nullptr ) {
994+ lib->pointer_functions_ =
995+ std::make_shared<std::unordered_set<FFIFunction*>>();
996+ }
997+ fn->pointer_registry = lib->pointer_functions_ ;
998+ lib->pointer_functions_ ->insert (fn.get ());
999+ Local<Function> ret;
1000+ if (!lib->BuildFunction (env, " <pointer>" , fn, false ).ToLocal (&ret)) {
1001+ lib->pointer_functions_ ->erase (fn.get ());
1002+ fn->ptr = nullptr ;
1003+ return ;
1004+ }
1005+ args.GetReturnValue ().Set (ret);
1006+ }
1007+
9241008void DynamicLibrary::GetFunctions (const FunctionCallbackInfo<Value>& args) {
9251009 Environment* env = Environment::GetCurrent (args);
9261010 Isolate* isolate = env->isolate ();
@@ -1397,6 +1481,7 @@ Local<FunctionTemplate> DynamicLibrary::GetConstructorTemplate(
13971481 SetProtoMethod (isolate, tmpl, " close" , DynamicLibrary::Close);
13981482 SetProtoDispose (isolate, tmpl, DynamicLibrary::Close);
13991483 SetProtoMethod (isolate, tmpl, " getFunction" , DynamicLibrary::GetFunction);
1484+ SetProtoMethod (isolate, tmpl, " toFunction" , DynamicLibrary::ToFunction);
14001485 SetProtoMethod (isolate, tmpl, " getFunctions" , DynamicLibrary::GetFunctions);
14011486 SetProtoMethod (isolate, tmpl, " getSymbol" , DynamicLibrary::GetSymbol);
14021487 SetProtoMethod (isolate, tmpl, " getSymbols" , DynamicLibrary::GetSymbols);
0 commit comments