@@ -90,6 +90,14 @@ void DynamicLibrary::MemoryInfo(MemoryTracker* tracker) const {
9090 sizeof (decltype (function_wrappers_)::value_type),
9191 " std::unordered_map<std::string, v8::Global<v8::Function>>" );
9292
93+ tracker->TrackFieldWithSize (
94+ " pointer_functions" ,
95+ pointer_functions_ == nullptr
96+ ? 0
97+ : sizeof (std::unordered_set<FFIFunction*>) +
98+ pointer_functions_->size () * sizeof (FFIFunction*),
99+ " std::unordered_set<FFIFunction*>" );
100+
93101 // FFIFunctionInfo instances and their sb_backing ArrayBuffers are
94102 // owned by V8 function wrappers and reachable only via weak references,
95103 // so they are deliberately not counted here.
@@ -101,6 +109,14 @@ void DynamicLibrary::Close() {
101109 fn->ptr = nullptr ;
102110 }
103111
112+ if (pointer_functions_ != nullptr ) {
113+ for (FFIFunction* fn : *pointer_functions_) {
114+ fn->closed = true ;
115+ fn->ptr = nullptr ;
116+ }
117+ pointer_functions_->clear ();
118+ }
119+
104120 // Closing the library invalidates all registered callbacks. Node.js does not
105121 // track or revoke callback pointers that have already been handed to native
106122 // code. If native code calls a callback pointer after `close()` or
@@ -142,7 +158,10 @@ Maybe<void*> DynamicLibrary::ResolveSymbol(Environment* env,
142158}
143159
144160Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction (
145- Environment* env, const std::string& name, Local<Object> signature) {
161+ Environment* env,
162+ const std::string& name,
163+ Local<Object> signature,
164+ void * ptr) {
146165 std::shared_ptr<FFIFunction> fn;
147166 FunctionSignature parsed;
148167
@@ -151,22 +170,22 @@ Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction(
151170 }
152171 // Look up the cache only after parsing: the signature's getters run user
153172 // code that may close the library, which clears `functions_`.
154- auto existing = functions_.find (name);
173+ const bool from_pointer = ptr != nullptr ;
174+ auto existing = from_pointer ? functions_.end () : functions_.find (name);
155175 auto [return_type, args, return_type_name, arg_type_names] =
156176 std::move (parsed);
157177
158178 bool should_cache_symbol = false ;
159179 bool should_cache_function = false ;
160180
161181 if (existing == functions_.end ()) {
162- void * ptr;
163-
164- if (!ResolveSymbol (env, name).To (&ptr)) {
165- return {};
182+ if (!from_pointer) {
183+ if (!ResolveSymbol (env, name).To (&ptr)) {
184+ return {};
185+ }
186+ should_cache_symbol = symbols_.find (name) == symbols_.end ();
166187 }
167188
168- should_cache_symbol = symbols_.find (name) == symbols_.end ();
169-
170189 fn = std::make_shared<FFIFunction>();
171190 fn->ptr = ptr;
172191 fn->args = std::move (args);
@@ -205,7 +224,7 @@ Maybe<DynamicLibrary::PreparedFunction> DynamicLibrary::PrepareFunction(
205224 }
206225#endif
207226
208- should_cache_function = true ;
227+ should_cache_function = !from_pointer ;
209228 } else {
210229 fn = existing->second ;
211230
@@ -267,7 +286,6 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(
267286 const std::string& name,
268287 const std::shared_ptr<FFIFunction>& fn) {
269288 Isolate* isolate = env->isolate ();
270- Local<Context> context = env->context ();
271289
272290 // Creating a callable emits a trampoline, allocates an FFIFunctionInfo, and
273291 // on the SharedBuffer path allocates an ArrayBuffer, so reuse the one already
@@ -282,17 +300,38 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(
282300 function_wrappers_.erase (cached);
283301 }
284302
303+ Local<Function> ret;
304+ if (!BuildFunction (env, name, fn, true ).ToLocal (&ret)) {
305+ return {};
306+ }
307+ function_wrappers_.emplace (name, Global<Function>(isolate, ret))
308+ .first ->second .SetWeak ();
309+ return ret;
310+ }
311+
312+ MaybeLocal<Function> DynamicLibrary::BuildFunction (
313+ Environment* env,
314+ const std::string& name,
315+ const std::shared_ptr<FFIFunction>& fn,
316+ bool optimize) {
317+ Isolate* isolate = env->isolate ();
318+ Local<Context> context = env->context ();
285319 auto info = FFIFunctionInfo::Create (env, fn, this );
320+ if (!info) {
321+ return {};
322+ }
286323
287324 DCHECK_EQ (fn->args .size (), fn->arg_type_names .size ());
288325
289326 // Try the generated Fast API path first. If metadata creation rejects the
290327 // signature, fall back to SharedBuffer for supported scalar shapes, then to
291328 // the generic libffi invoker.
292- std::shared_ptr<FFIFunction> fast_fn = CloneWithRawPointerArgNames (fn);
293- info->fast_metadata = CreateFastFFIMetadata (*fast_fn, &fn->closed , isolate);
329+ if (optimize) {
330+ std::shared_ptr<FFIFunction> fast_fn = CloneWithRawPointerArgNames (fn);
331+ info->fast_metadata = CreateFastFFIMetadata (*fast_fn, &fn->closed , isolate);
332+ }
294333 bool use_fast_api = info->fast_metadata != nullptr ;
295- bool use_sb = !use_fast_api && IsSBEligibleSignature (*fn);
334+ bool use_sb = optimize && !use_fast_api && IsSBEligibleSignature (*fn);
296335 bool has_ptr_args = use_sb && SignatureHasPointerArgs (*fn);
297336 // Signatures that need JS-side conversion or validation use a wrapper, as
298337 // do all fast signatures on platforms without a native library guard.
@@ -485,14 +524,6 @@ MaybeLocal<Function> DynamicLibrary::CreateFunction(
485524 }
486525 }
487526
488- // A strong handle would root the callable, which holds the library object
489- // through FFIFunctionInfo, so neither could ever be collected. Weaken the
490- // stored handle instead, so the cache lasts exactly as long as user code
491- // keeps a reference. SetWeak() runs after the move into the map because
492- // moving a handle relocates the underlying slot.
493- function_wrappers_.emplace (name, Global<Function>(isolate, ret))
494- .first ->second .SetWeak ();
495-
496527 return ret;
497528}
498529
@@ -607,6 +638,7 @@ void DynamicLibrary::InvokeFunction(const FunctionCallbackInfo<Value>& args) {
607638 std::vector<uint64_t > values (expected_args, 0 );
608639 std::vector<void *> ffi_args (expected_args, nullptr );
609640 std::vector<std::string> strings;
641+ strings.reserve (expected_args);
610642
611643 for (unsigned int i = 0 ; i < expected_args; i++) {
612644 FFIArgumentCategory res;
@@ -861,6 +893,68 @@ void DynamicLibrary::GetFunction(const FunctionCallbackInfo<Value>& args) {
861893 args.GetReturnValue ().Set (ret);
862894}
863895
896+ void DynamicLibrary::GetFunctionFromPointer (
897+ const FunctionCallbackInfo<Value>& args) {
898+ Environment* env = Environment::GetCurrent (args);
899+ THROW_IF_INSUFFICIENT_PERMISSIONS (env, permission::PermissionScope::kFFI , " " );
900+
901+ if (args.Length () < 1 || !args[0 ]->IsBigInt ()) {
902+ THROW_ERR_INVALID_ARG_TYPE (env, " Function pointer must be a bigint" );
903+ return ;
904+ }
905+ bool lossless;
906+ uint64_t address = args[0 ].As <BigInt>()->Uint64Value (&lossless);
907+ if (!lossless || address == 0 ||
908+ address > static_cast <uint64_t >(
909+ std::numeric_limits<uintptr_t >::max ())) {
910+ THROW_ERR_INVALID_ARG_VALUE (env, " Invalid function pointer" );
911+ return ;
912+ }
913+ if (args.Length () < 2 || !args[1 ]->IsObject () || args[1 ]->IsArray ()) {
914+ THROW_ERR_INVALID_ARG_TYPE (env, " Function signature must be an object" );
915+ return ;
916+ }
917+
918+ DynamicLibrary* lib = Unwrap<DynamicLibrary>(args.This ());
919+ if (lib->is_closed ()) {
920+ THROW_ERR_FFI_LIBRARY_CLOSED (env);
921+ return ;
922+ }
923+ Local<Object> signature = args[1 ].As <Object>();
924+ PreparedFunction prepared;
925+ void * ptr = reinterpret_cast <void *>(static_cast <uintptr_t >(address));
926+ if (!lib->PrepareFunction (env, " <pointer>" , signature, ptr).To (&prepared)) {
927+ return ;
928+ }
929+ THROW_IF_INSUFFICIENT_PERMISSIONS (env, permission::PermissionScope::kFFI , " " );
930+ if (lib->is_closed ()) {
931+ THROW_ERR_FFI_LIBRARY_CLOSED (env);
932+ return ;
933+ }
934+
935+ auto fn = std::move (prepared.fn );
936+ if (lib->pointer_functions_ == nullptr ) {
937+ lib->pointer_functions_ =
938+ std::make_shared<std::unordered_set<FFIFunction*>>();
939+ }
940+ fn->pointer_registry = lib->pointer_functions_ ;
941+ fn->closed = true ;
942+ lib->pointer_functions_ ->insert (fn.get ());
943+ Local<Function> ret;
944+ if (!lib->BuildFunction (env, " <pointer>" , fn, false ).ToLocal (&ret)) {
945+ lib->pointer_functions_ ->erase (fn.get ());
946+ fn->ptr = nullptr ;
947+ return ;
948+ }
949+ THROW_IF_INSUFFICIENT_PERMISSIONS (env, permission::PermissionScope::kFFI , " " );
950+ if (lib->is_closed ()) {
951+ THROW_ERR_FFI_LIBRARY_CLOSED (env);
952+ return ;
953+ }
954+ fn->closed = false ;
955+ args.GetReturnValue ().Set (ret);
956+ }
957+
864958void DynamicLibrary::GetFunctions (const FunctionCallbackInfo<Value>& args) {
865959 Environment* env = Environment::GetCurrent (args);
866960 Isolate* isolate = env->isolate ();
@@ -1324,6 +1418,10 @@ Local<FunctionTemplate> DynamicLibrary::GetConstructorTemplate(
13241418 SetProtoMethod (isolate, tmpl, " close" , DynamicLibrary::Close);
13251419 SetProtoDispose (isolate, tmpl, DynamicLibrary::Close);
13261420 SetProtoMethod (isolate, tmpl, " getFunction" , DynamicLibrary::GetFunction);
1421+ SetProtoMethod (isolate,
1422+ tmpl,
1423+ " getFunctionFromPointer" ,
1424+ DynamicLibrary::GetFunctionFromPointer);
13271425 SetProtoMethod (isolate, tmpl, " getFunctions" , DynamicLibrary::GetFunctions);
13281426 SetProtoMethod (isolate, tmpl, " getSymbol" , DynamicLibrary::GetSymbol);
13291427 SetProtoMethod (isolate, tmpl, " getSymbols" , DynamicLibrary::GetSymbols);
0 commit comments