objc-js 0.0.15 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,16 @@
1
1
  #include "subclass-impl.h"
2
2
  #include "bridge.h"
3
+ #include "constants.h"
3
4
  #include "debug.h"
4
5
  #include "ffi-utils.h"
6
+ #include "forwarding-common.h"
7
+ #include "memory-utils.h"
5
8
  #include "method-forwarding.h"
6
9
  #include "ObjcObject.h"
7
10
  #include "protocol-storage.h"
11
+ #include "runtime-detection.h"
12
+ #include "subclass-manager.h"
13
+ #include "super-call-helpers.h"
8
14
  #include "type-conversion.h"
9
15
  #include <Foundation/Foundation.h>
10
16
  #include <atomic>
@@ -21,10 +27,7 @@
21
27
  // objc_msgSendSuper2 isn't in the headers but is exported
22
28
  extern "C" id objc_msgSendSuper2(struct objc_super *super, SEL op, ...);
23
29
 
24
- // MARK: - Global Storage Definition
25
-
26
- std::unordered_map<void *, SubclassImplementation> g_subclasses;
27
- std::mutex g_subclasses_mutex;
30
+ using nobjc::SubclassManager;
28
31
 
29
32
  // MARK: - Forward Declarations for Method Forwarding
30
33
 
@@ -41,19 +44,23 @@ static BOOL SubclassRespondsToSelector(id self, SEL _cmd, SEL selector) {
41
44
  Class cls = object_getClass(self);
42
45
  void *clsPtr = (__bridge void *)cls;
43
46
 
44
- {
45
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
46
- auto it = g_subclasses.find(clsPtr);
47
- if (it != g_subclasses.end()) {
47
+ bool found = SubclassManager::Instance().WithLock([clsPtr, selector](auto& map) {
48
+ auto it = map.find(clsPtr);
49
+ if (it != map.end()) {
48
50
  NSString *selectorString = NSStringFromSelector(selector);
49
51
  if (selectorString != nil) {
50
52
  std::string selName = [selectorString UTF8String];
51
53
  auto methodIt = it->second.methods.find(selName);
52
54
  if (methodIt != it->second.methods.end()) {
53
- return YES;
55
+ return true;
54
56
  }
55
57
  }
56
58
  }
59
+ return false;
60
+ });
61
+
62
+ if (found) {
63
+ return YES;
57
64
  }
58
65
 
59
66
  // Check superclass
@@ -69,10 +76,9 @@ static NSMethodSignature *SubclassMethodSignatureForSelector(id self, SEL _cmd,
69
76
  Class cls = object_getClass(self);
70
77
  void *clsPtr = (__bridge void *)cls;
71
78
 
72
- {
73
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
74
- auto it = g_subclasses.find(clsPtr);
75
- if (it != g_subclasses.end()) {
79
+ NSMethodSignature *sig = SubclassManager::Instance().WithLock([clsPtr, selector](auto& map) -> NSMethodSignature* {
80
+ auto it = map.find(clsPtr);
81
+ if (it != map.end()) {
76
82
  NSString *selectorString = NSStringFromSelector(selector);
77
83
  std::string selName = [selectorString UTF8String];
78
84
  auto methodIt = it->second.methods.find(selName);
@@ -81,6 +87,11 @@ static NSMethodSignature *SubclassMethodSignatureForSelector(id self, SEL _cmd,
81
87
  signatureWithObjCTypes:methodIt->second.typeEncoding.c_str()];
82
88
  }
83
89
  }
90
+ return nil;
91
+ });
92
+
93
+ if (sig != nil) {
94
+ return sig;
84
95
  }
85
96
 
86
97
  // Fall back to superclass
@@ -110,192 +121,104 @@ static void SubclassForwardInvocation(id self, SEL _cmd,
110
121
  }
111
122
 
112
123
  std::string selectorName = [selectorString UTF8String];
113
-
114
124
  NOBJC_LOG("SubclassForwardInvocation: Called for selector %s", selectorName.c_str());
115
-
125
+
116
126
  Class cls = object_getClass(self);
117
127
  void *clsPtr = (__bridge void *)cls;
118
-
119
- NOBJC_LOG("SubclassForwardInvocation: Class=%s, clsPtr=%p", class_getName(cls), clsPtr);
120
-
121
- Napi::ThreadSafeFunction tsfn;
122
- std::string typeEncoding;
123
- pthread_t js_thread;
124
- void *superClassPtr = nullptr;
125
-
126
- {
127
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
128
- auto it = g_subclasses.find(clsPtr);
129
- if (it == g_subclasses.end()) {
130
- NOBJC_WARN("Subclass implementation not found for class %p", cls);
131
- [invocation release];
132
- return;
133
- }
128
+ void *selfPtr = (__bridge void *)self;
134
129
 
135
- auto methodIt = it->second.methods.find(selectorName);
136
- if (methodIt == it->second.methods.end()) {
137
- NOBJC_WARN("Method not found for selector %s", selectorName.c_str());
138
- [invocation release];
139
- return;
140
- }
141
-
142
- tsfn = methodIt->second.callback;
143
- napi_status acq_status = tsfn.Acquire();
144
- if (acq_status != napi_ok) {
145
- NOBJC_WARN("Failed to acquire ThreadSafeFunction for selector %s",
146
- selectorName.c_str());
147
- [invocation release];
148
- return;
149
- }
130
+ NOBJC_LOG("SubclassForwardInvocation: Class=%s, clsPtr=%p", class_getName(cls), clsPtr);
150
131
 
151
- typeEncoding = methodIt->second.typeEncoding;
152
- js_thread = it->second.js_thread;
153
- superClassPtr = it->second.superClass;
154
- }
155
-
156
- bool is_js_thread = pthread_equal(pthread_self(), js_thread);
157
-
158
- auto data = new InvocationData();
159
- data->invocation = invocation;
160
- data->selectorName = selectorName;
161
- data->typeEncoding = typeEncoding;
162
- data->instancePtr = (__bridge void *)self;
163
- data->superClassPtr = superClassPtr;
164
- data->callbackType = CallbackType::Subclass; // Set callback type
165
-
166
- napi_status status;
167
-
168
- // IMPORTANT: Always try direct call when on JS thread (including Electron)
169
- // The direct call will catch exceptions and fall back to TSFN if needed
170
- // Only use TSFN+runloop when on a DIFFERENT thread
171
- if (is_js_thread) {
172
- // Direct call on JS thread (Node/Bun/Electron)
173
- NOBJC_LOG("SubclassForwardInvocation: Using direct call path (JS thread)");
174
- data->completionMutex = nullptr;
175
- data->completionCv = nullptr;
176
- data->isComplete = nullptr;
177
-
178
- tsfn.Release();
179
-
180
- napi_env stored_env;
181
- {
182
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
183
- auto it = g_subclasses.find(clsPtr);
184
- if (it == g_subclasses.end()) {
185
- NOBJC_WARN("Subclass implementation not found for class %p (JS thread path)", cls);
186
- [invocation release];
187
- delete data;
188
- return;
132
+ // Set up callbacks for subclass-specific storage access
133
+ ForwardingCallbacks callbacks;
134
+ callbacks.callbackType = CallbackType::Subclass;
135
+
136
+ // Capture selfPtr for use in lookupContext
137
+ void *capturedSelfPtr = selfPtr;
138
+
139
+ // Lookup context and acquire TSFN
140
+ callbacks.lookupContext = [capturedSelfPtr](void *lookupKey,
141
+ const std::string &selName) -> std::optional<ForwardingContext> {
142
+ return SubclassManager::Instance().WithLock([lookupKey, &selName, capturedSelfPtr](auto& map) -> std::optional<ForwardingContext> {
143
+ auto it = map.find(lookupKey);
144
+ if (it == map.end()) {
145
+ NOBJC_WARN("Subclass implementation not found for class %p", lookupKey);
146
+ return std::nullopt;
189
147
  }
190
148
 
191
- auto methodIt = it->second.methods.find(selectorName);
149
+ auto methodIt = it->second.methods.find(selName);
192
150
  if (methodIt == it->second.methods.end()) {
193
- NOBJC_WARN("Method not found for selector %s (JS thread path)",
194
- selectorName.c_str());
195
- [invocation release];
196
- delete data;
197
- return;
151
+ NOBJC_WARN("Method not found for selector %s", selName.c_str());
152
+ return std::nullopt;
198
153
  }
199
154
 
200
- stored_env = it->second.env;
201
- // Don't get the function value here - do it inside the HandleScope
202
- }
203
-
204
- try {
205
- NOBJC_LOG("SubclassForwardInvocation: About to call JS callback directly");
206
- Napi::Env callEnv(stored_env);
207
- Napi::HandleScope scope(callEnv);
208
-
209
- // Get the JS function within the HandleScope
210
- Napi::Function jsFn;
211
- {
212
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
213
- auto it = g_subclasses.find(clsPtr);
214
- if (it != g_subclasses.end()) {
215
- auto methodIt = it->second.methods.find(selectorName);
216
- if (methodIt != it->second.methods.end()) {
217
- jsFn = methodIt->second.jsCallback.Value();
218
- }
219
- }
155
+ // Acquire the TSFN
156
+ Napi::ThreadSafeFunction tsfn = methodIt->second.callback;
157
+ napi_status acq_status = tsfn.Acquire();
158
+ if (acq_status != napi_ok) {
159
+ NOBJC_WARN("Failed to acquire ThreadSafeFunction for selector %s", selName.c_str());
160
+ return std::nullopt;
220
161
  }
162
+
163
+ ForwardingContext ctx;
164
+ ctx.tsfn = tsfn;
165
+ ctx.typeEncoding = methodIt->second.typeEncoding;
166
+ ctx.js_thread = it->second.js_thread;
167
+ ctx.env = it->second.env;
168
+ ctx.skipDirectCallForElectron = false; // Subclass always tries direct call
169
+ ctx.instancePtr = capturedSelfPtr;
170
+ ctx.superClassPtr = it->second.superClass;
221
171
 
222
- CallJSCallback(callEnv, jsFn, data); // Use unified CallJSCallback
223
- // CallJSCallback releases invocation and deletes data.
224
- NOBJC_LOG("SubclassForwardInvocation: Direct call succeeded");
225
- } catch (const std::exception &e) {
226
- NOBJC_ERROR("Error calling JS callback directly (likely invalid env in Electron): %s",
227
- e.what());
228
- NOBJC_LOG("Falling back to ThreadSafeFunction for selector %s",
229
- selectorName.c_str());
230
-
231
- // Fallback to TSFN if direct call fails
232
- {
233
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
234
- auto it = g_subclasses.find(clsPtr);
235
- if (it != g_subclasses.end()) {
236
- auto methodIt = it->second.methods.find(selectorName);
237
- if (methodIt != it->second.methods.end()) {
238
- tsfn = methodIt->second.callback;
239
- napi_status acq_status = tsfn.Acquire();
240
- if (acq_status == napi_ok) {
241
- // Use helper function for fallback
242
- if (FallbackToTSFN(tsfn, data, selectorName)) {
243
- return; // Data cleaned up in callback
244
- }
245
- NOBJC_ERROR("SubclassForwardInvocation: Fallback failed");
246
- }
247
- }
248
- }
172
+ // Cache the JS callback reference to avoid mutex re-acquisition
173
+ ctx.cachedJsCallback = &methodIt->second.jsCallback;
174
+
175
+ return ctx;
176
+ });
177
+ };
178
+
179
+ // Get JS function for direct call path
180
+ callbacks.getJSFunction = [](void *lookupKey, const std::string &selName,
181
+ Napi::Env /*env*/) -> Napi::Function {
182
+ return SubclassManager::Instance().WithLock([lookupKey, &selName](auto& map) -> Napi::Function {
183
+ auto it = map.find(lookupKey);
184
+ if (it == map.end()) {
185
+ return Napi::Function();
249
186
  }
250
-
251
- // If fallback also failed, clean up manually
252
- [invocation release];
253
- delete data;
254
- }
255
- } else {
256
- // Cross-thread call via TSFN (NOT on JS thread)
257
- NOBJC_LOG("SubclassForwardInvocation: Using TSFN+runloop path (different thread)");
258
- std::mutex completionMutex;
259
- std::condition_variable completionCv;
260
- bool isComplete = false;
261
-
262
- data->completionMutex = &completionMutex;
263
- data->completionCv = &completionCv;
264
- data->isComplete = &isComplete;
265
-
266
- NOBJC_LOG("SubclassForwardInvocation: About to call NonBlockingCall for selector %s",
267
- selectorName.c_str());
268
187
 
269
- status = tsfn.NonBlockingCall(data, CallJSCallback); // Use unified CallJSCallback
270
- tsfn.Release();
188
+ auto methodIt = it->second.methods.find(selName);
189
+ if (methodIt == it->second.methods.end()) {
190
+ return Napi::Function();
191
+ }
271
192
 
272
- NOBJC_LOG("SubclassForwardInvocation: NonBlockingCall returned status=%d", status);
193
+ return methodIt->second.jsCallback.Value();
194
+ });
195
+ };
273
196
 
274
- if (status != napi_ok) {
275
- NOBJC_ERROR("Failed to call ThreadSafeFunction for selector %s (status: %d)",
276
- selectorName.c_str(), status);
277
- [invocation release];
278
- delete data;
279
- return;
280
- }
197
+ // Re-acquire TSFN for fallback path
198
+ callbacks.reacquireTSFN = [](void *lookupKey,
199
+ const std::string &selName) -> std::optional<Napi::ThreadSafeFunction> {
200
+ return SubclassManager::Instance().WithLock([lookupKey, &selName](auto& map) -> std::optional<Napi::ThreadSafeFunction> {
201
+ auto it = map.find(lookupKey);
202
+ if (it == map.end()) {
203
+ return std::nullopt;
204
+ }
281
205
 
282
- // Wait for callback via runloop pumping
283
- CFTimeInterval timeout = 0.001;
284
- int iterations = 0;
285
- while (true) {
286
- {
287
- std::unique_lock<std::mutex> lock(completionMutex);
288
- if (isComplete) {
289
- break;
290
- }
206
+ auto methodIt = it->second.methods.find(selName);
207
+ if (methodIt == it->second.methods.end()) {
208
+ return std::nullopt;
291
209
  }
292
- iterations++;
293
- if (iterations % 1000 == 0) {
294
- NOBJC_LOG("SubclassForwardInvocation: Still waiting... (%d iterations)", iterations);
210
+
211
+ Napi::ThreadSafeFunction tsfn = methodIt->second.callback;
212
+ napi_status acq_status = tsfn.Acquire();
213
+ if (acq_status != napi_ok) {
214
+ return std::nullopt;
295
215
  }
296
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
297
- }
298
- }
216
+
217
+ return tsfn;
218
+ });
219
+ };
220
+
221
+ ForwardInvocationCommon(invocation, selectorName, clsPtr, callbacks);
299
222
  }
300
223
 
301
224
  static void SubclassDeallocImplementation(id self, SEL _cmd) {
@@ -365,21 +288,8 @@ Napi::Value DefineClass(const Napi::CallbackInfo &info) {
365
288
  env, "'superclass' must be a string or ObjcObject representing a Class");
366
289
  }
367
290
 
368
- // Detect Electron (bun is commented out as it's not needed)
369
- bool isElectron = false;
370
- // bool isBun = false;
371
- try {
372
- Napi::Object global = env.Global();
373
- if (global.Has("process")) {
374
- Napi::Object process = global.Get("process").As<Napi::Object>();
375
- if (process.Has("versions")) {
376
- Napi::Object versions = process.Get("versions").As<Napi::Object>();
377
- isElectron = versions.Has("electron");
378
- // isBun = versions.Has("bun");
379
- }
380
- }
381
- } catch (...) {
382
- }
291
+ // Detect Electron runtime
292
+ bool isElectron = IsElectronRuntime(env);
383
293
 
384
294
  // Allocate the new class
385
295
  Class newClass = objc_allocateClassPair(superClass, className.c_str(), 0);
@@ -396,7 +306,7 @@ Napi::Value DefineClass(const Napi::CallbackInfo &info) {
396
306
  .methods = {},
397
307
  .env = env,
398
308
  .js_thread = pthread_self(),
399
- .isElectron = isElectron, // Only Electron needs TSFN always; Bun works with direct calls
309
+ .isElectron = isElectron,
400
310
  };
401
311
 
402
312
  // Add protocol conformance
@@ -492,12 +402,9 @@ Napi::Value DefineClass(const Napi::CallbackInfo &info) {
492
402
  // Register the class
493
403
  objc_registerClassPair(newClass);
494
404
 
495
- // Store in global map
405
+ // Store in manager
496
406
  void *classPtr = (__bridge void *)newClass;
497
- {
498
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
499
- g_subclasses.emplace(classPtr, std::move(impl));
500
- }
407
+ SubclassManager::Instance().Register(classPtr, std::move(impl));
501
408
 
502
409
  // Return the Class object
503
410
  return ObjcObject::NewInstance(env, newClass);
@@ -505,7 +412,7 @@ Napi::Value DefineClass(const Napi::CallbackInfo &info) {
505
412
 
506
413
  // MARK: - CallSuper Implementation
507
414
 
508
- // New FFI-based implementation to avoid infinite recursion
415
+ // FFI-based implementation to avoid infinite recursion
509
416
  static Napi::Value CallSuperWithFFI(
510
417
  Napi::Env env,
511
418
  id self,
@@ -519,417 +426,135 @@ static Napi::Value CallSuperWithFFI(
519
426
  NOBJC_LOG("CallSuperWithFFI: selector=%s, self=%p, superClass=%s",
520
427
  selectorName.c_str(), self, class_getName(superClass));
521
428
 
522
- // Vector to track allocated FFI types for cleanup
523
- std::vector<ffi_type*> allocatedTypes;
429
+ FFIArgumentContext ctx;
524
430
 
525
431
  try {
526
- //1. Prepare objc_super struct
527
- // Use objc_msgSendSuper (NOT Super2) with the superclass
528
- // This is the variant that works reliably on all platforms
432
+ // 1. Prepare objc_super struct
529
433
  struct objc_super superStruct;
530
434
  superStruct.receiver = self;
531
435
  superStruct.super_class = superClass;
532
- NOBJC_LOG("CallSuperWithFFI: superStruct is at address %p", &superStruct);
533
- NOBJC_LOG("CallSuperWithFFI: receiver=%p, calling superclass=%s",
534
- self, class_getName(superClass));
436
+ NOBJC_LOG("CallSuperWithFFI: superStruct at %p, receiver=%p, superclass=%s",
437
+ &superStruct, self, class_getName(superClass));
535
438
 
536
- // 2. Determine which objc_msgSend variant to use
537
- // Use objc_msgSendSuper with the superclass (matches the no-args case)
439
+ // 2. Get return type info
538
440
  const char* returnEncoding = [methodSig methodReturnType];
539
441
  SimplifiedTypeEncoding simpleReturnEncoding(returnEncoding);
540
442
 
541
- void* msgSendFn = (void*)objc_msgSendSuper;
542
-
543
- NOBJC_LOG("CallSuperWithFFI: Using objc_msgSendSuper with superclass");
544
-
545
- // 3. Build FFI type arrays for arguments
546
- size_t totalArgs = [methodSig numberOfArguments];
547
- std::vector<ffi_type*> argFFITypes;
548
-
549
- // First arg: objc_super pointer
550
- argFFITypes.push_back(&ffi_type_pointer);
551
- // Second arg: SEL
552
- argFFITypes.push_back(&ffi_type_pointer);
553
-
554
- // Remaining args: method arguments (starting from index 2)
555
- for (size_t i = 2; i < totalArgs; i++) {
556
- const char* argEncoding = [methodSig getArgumentTypeAtIndex:i];
557
- ffi_type* argType = GetFFITypeForEncoding(argEncoding, nullptr, allocatedTypes);
558
- argFFITypes.push_back(argType);
559
- NOBJC_LOG("CallSuperWithFFI: Arg %zu type encoding: %s", i - 2, argEncoding);
560
- }
561
-
562
- // 4. Build return FFI type
443
+ // 3. Prepare FFI argument types
563
444
  size_t returnSize = 0;
564
- ffi_type* returnFFIType = GetFFITypeForEncoding(simpleReturnEncoding.c_str(),
565
- &returnSize, allocatedTypes);
566
- NOBJC_LOG("CallSuperWithFFI: Return type encoding: %s, size: %zu",
567
- simpleReturnEncoding.c_str(), returnSize);
445
+ ffi_type* returnFFIType = PrepareFFIArgumentTypes(
446
+ methodSig, simpleReturnEncoding.c_str(), &returnSize, ctx);
568
447
 
569
- // 5. Prepare FFI CIF
448
+ // 4. Prepare FFI CIF
570
449
  ffi_cif cif;
571
450
  ffi_status status = ffi_prep_cif(
572
451
  &cif,
573
452
  FFI_DEFAULT_ABI,
574
- argFFITypes.size(),
453
+ ctx.argFFITypes.size(),
575
454
  returnFFIType,
576
- argFFITypes.data()
455
+ ctx.argFFITypes.data()
577
456
  );
578
457
 
579
458
  if (status != FFI_OK) {
580
- CleanupAllocatedFFITypes(allocatedTypes);
459
+ CleanupAllocatedFFITypes(ctx.allocatedTypes);
581
460
  NOBJC_ERROR("CallSuperWithFFI: ffi_prep_cif failed with status %d", status);
582
461
  throw Napi::Error::New(env, "FFI preparation failed");
583
462
  }
584
-
585
463
  NOBJC_LOG("CallSuperWithFFI: FFI CIF prepared successfully");
586
464
 
587
- // 6. Prepare argument value buffers
588
- std::vector<void*> argValues;
589
- std::vector<std::unique_ptr<uint8_t[]>> argBuffers;
590
-
591
- NOBJC_LOG("CallSuperWithFFI: Preparing argument buffers...");
592
-
593
- // Add objc_super pointer
594
- // libffi expects argValues[i] to point to the actual argument value
595
- // Store the pointer in a buffer to ensure it stays valid
596
- auto superPtrBuffer = std::make_unique<uint8_t[]>(sizeof(objc_super*));
597
- objc_super* superPtr = &superStruct;
598
- memcpy(superPtrBuffer.get(), &superPtr, sizeof(objc_super*));
599
- void* superPtrBufferRawPtr = superPtrBuffer.get(); // Get raw pointer before move
600
- argBuffers.push_back(std::move(superPtrBuffer)); // Move to keep alive
601
- argValues.push_back(superPtrBufferRawPtr); // Add raw pointer to argValues
602
- NOBJC_LOG("CallSuperWithFFI: Added objc_super* buffer at %p (points to %p)",
603
- superPtrBufferRawPtr, superPtr);
465
+ // 5. Add fixed arguments (objc_super* and SEL)
466
+ AddFixedFFIArguments(&superStruct, selector, ctx);
604
467
 
605
- // Add selector - also store in buffer
606
- auto selectorBuffer = std::make_unique<uint8_t[]>(sizeof(SEL));
607
- memcpy(selectorBuffer.get(), &selector, sizeof(SEL));
608
- void* selectorBufferRawPtr = selectorBuffer.get(); // Get raw pointer before move
609
- argBuffers.push_back(std::move(selectorBuffer)); // Move to keep alive
610
- argValues.push_back(selectorBufferRawPtr); // Add raw pointer to argValues
611
- NOBJC_LOG("CallSuperWithFFI: Added SEL buffer at %p (value=%p, name=%s)",
612
- selectorBufferRawPtr, selector, sel_getName(selector));
468
+ // 6. Extract method arguments from JS
469
+ ExtractMethodArguments(env, info, argStartIndex, methodSig, superClass, selectorName, ctx);
613
470
 
614
- // Add method arguments
615
- NOBJC_LOG("CallSuperWithFFI: Processing %zu method arguments...", info.Length() - argStartIndex);
616
- for (size_t i = argStartIndex; i < info.Length(); i++) {
617
- size_t argIndex = i - argStartIndex + 2; // +2 for self and _cmd
618
- const char* argEncoding = [methodSig getArgumentTypeAtIndex:argIndex];
619
- SimplifiedTypeEncoding simpleArgEncoding(argEncoding);
620
-
621
- NOBJC_LOG("CallSuperWithFFI: Processing JS arg %zu (method arg %zu), encoding=%s",
622
- i - argStartIndex, argIndex, argEncoding);
623
-
624
- // Handle special case for ^@ (out-params like NSError**)
625
- if (simpleArgEncoding[0] == '^' && simpleArgEncoding[1] == '@') {
626
- NOBJC_LOG("CallSuperWithFFI: Arg %zu is out-param (^@)", i - argStartIndex);
627
-
628
- // CRITICAL: For pointer-to-pointer types, we need TWO buffers:
629
- // 1. The actual storage location for the id (initialized to nil)
630
- // 2. A pointer to that storage (what we pass to the function)
631
-
632
- // Buffer 1: Storage for the id* (initialized to nil)
633
- auto errorStorage = std::make_unique<uint8_t[]>(sizeof(id));
634
- id nullObj = nil;
635
- memcpy(errorStorage.get(), &nullObj, sizeof(id));
636
- void* errorStoragePtr = errorStorage.get();
637
-
638
- NOBJC_LOG("CallSuperWithFFI: Allocated error storage at %p", errorStoragePtr);
639
- NOBJC_LOG("CallSuperWithFFI: Error storage contains: %p", *(id*)errorStoragePtr);
640
-
641
- // Buffer 2: Storage for the pointer to errorStorage (this is what argValues needs)
642
- auto pointerBuffer = std::make_unique<uint8_t[]>(sizeof(void*));
643
- memcpy(pointerBuffer.get(), &errorStoragePtr, sizeof(void*));
644
- void* pointerBufferPtr = pointerBuffer.get();
645
-
646
- NOBJC_LOG("CallSuperWithFFI: Allocated pointer buffer at %p", pointerBufferPtr);
647
- NOBJC_LOG("CallSuperWithFFI: Pointer buffer contains: %p (address of error storage)",
648
- *(void**)pointerBufferPtr);
649
- NOBJC_LOG("CallSuperWithFFI: This address will be passed to the method");
650
-
651
- // CRITICAL: argValues must point to pointerBuffer, not errorStorage
652
- // libffi will dereference this to get the address to pass
653
- argValues.push_back(pointerBufferPtr);
654
- argBuffers.push_back(std::move(errorStorage));
655
- argBuffers.push_back(std::move(pointerBuffer));
656
- continue;
657
- }
658
-
659
- // Calculate size for this argument
660
- size_t argSize = GetSizeForTypeEncoding(simpleArgEncoding[0]);
661
- if (argSize == 0) {
662
- // For complex types, use NSGetSizeAndAlignment
663
- NSUInteger size, alignment;
664
- NSGetSizeAndAlignment(argEncoding, &size, &alignment);
665
- argSize = size;
666
- NOBJC_LOG("CallSuperWithFFI: Complex type, size from NSGetSizeAndAlignment: %zu", argSize);
667
- }
668
-
669
- // Allocate buffer
670
- NOBJC_LOG("CallSuperWithFFI: Allocating buffer of %zu bytes for arg %zu", argSize, i - argStartIndex);
671
- auto buffer = std::make_unique<uint8_t[]>(argSize);
672
- memset(buffer.get(), 0, argSize);
673
- void* bufferPtr = buffer.get();
674
- NOBJC_LOG("CallSuperWithFFI: Buffer allocated at %p", bufferPtr);
675
-
676
- // Extract JS argument to buffer
677
- ObjcArgumentContext context = {
678
- .className = std::string(class_getName(superClass)),
679
- .selectorName = selectorName,
680
- .argumentIndex = (int)(i - argStartIndex),
681
- };
682
-
683
- try {
684
- NOBJC_LOG("CallSuperWithFFI: Calling ExtractJSArgumentToBuffer...");
685
- ExtractJSArgumentToBuffer(env, info[i], argEncoding, bufferPtr, context);
686
- NOBJC_LOG("CallSuperWithFFI: ExtractJSArgumentToBuffer succeeded");
687
- } catch (const std::exception& e) {
688
- CleanupAllocatedFFITypes(allocatedTypes);
689
- NOBJC_ERROR("CallSuperWithFFI: Failed to extract argument %zu: %s", i - argStartIndex, e.what());
690
- throw;
691
- }
692
-
693
- NOBJC_LOG("CallSuperWithFFI: Extracted argument %zu (size: %zu)", i - argStartIndex, argSize);
694
-
695
- // For object types, log the actual pointer value
696
- if (simpleArgEncoding[0] == '@') {
697
- [[maybe_unused]] id* objPtr = (id*)bufferPtr;
698
- NOBJC_LOG("CallSuperWithFFI: Argument %zu is object: buffer=%p, contains id=%p",
699
- i - argStartIndex, bufferPtr, *objPtr);
700
- }
701
-
702
- NOBJC_LOG("CallSuperWithFFI: Adding buffer %p to argValues (index %zu)", bufferPtr, argValues.size());
703
- argValues.push_back(bufferPtr);
704
- argBuffers.push_back(std::move(buffer));
705
- NOBJC_LOG("CallSuperWithFFI: Buffer moved to argBuffers (now size %zu)", argBuffers.size());
706
- }
707
-
708
- NOBJC_LOG("CallSuperWithFFI: Finished preparing %zu argument buffers", argBuffers.size());
709
-
710
- // 7. Prepare return buffer
711
- std::unique_ptr<uint8_t[]> returnBuffer;
712
- if (simpleReturnEncoding[0] != 'v') {
713
- size_t bufferSize = returnSize > 0 ? returnSize : 16; // Minimum 16 bytes
714
- returnBuffer = std::make_unique<uint8_t[]>(bufferSize);
715
- memset(returnBuffer.get(), 0, bufferSize);
716
- NOBJC_LOG("CallSuperWithFFI: Allocated return buffer of %zu bytes at %p",
717
- bufferSize, returnBuffer.get());
718
- } else {
719
- NOBJC_LOG("CallSuperWithFFI: No return buffer needed (void return)");
720
- }
721
-
722
- // 8. Make the FFI call
723
- NOBJC_LOG("CallSuperWithFFI: ========== FFI CALL SETUP ==========");
724
- NOBJC_LOG("CallSuperWithFFI: Function to call: objc_msgSendSuper at %p", msgSendFn);
725
- NOBJC_LOG("CallSuperWithFFI: Number of arguments: %zu", argValues.size());
726
- NOBJC_LOG("CallSuperWithFFI: Arg 0 (objc_super*): argValues[0]=%p",
727
- argValues[0]);
728
- // Log what's actually stored in the buffer
729
- [[maybe_unused]] objc_super** superPtrPtr = (objc_super**)argValues[0];
730
- NOBJC_LOG("CallSuperWithFFI: Buffer contains pointer: %p", *superPtrPtr);
731
- NOBJC_LOG("CallSuperWithFFI: objc_super.receiver=%p", superStruct.receiver);
732
- NOBJC_LOG("CallSuperWithFFI: objc_super.super_class=%p (%s)",
733
- superStruct.super_class, class_getName(superClass));
734
- NOBJC_LOG("CallSuperWithFFI: Arg 1 (SEL*): argValues[1]=%p",
735
- argValues[1]);
736
- [[maybe_unused]] SEL* selPtr = (SEL*)argValues[1];
737
- NOBJC_LOG("CallSuperWithFFI: Buffer contains SEL: %p (%s)",
738
- *selPtr, sel_getName(*selPtr));
739
-
740
- for (size_t i = 2; i < argValues.size(); i++) {
741
- const char* argEncoding = [methodSig getArgumentTypeAtIndex:i];
742
- SimplifiedTypeEncoding simpleArgEncoding(argEncoding);
743
- NOBJC_LOG("CallSuperWithFFI: Arg %zu: argValues[%zu]=%p, encoding=%s",
744
- i, i, argValues[i], simpleArgEncoding.c_str());
745
- if (simpleArgEncoding[0] == '@') {
746
- [[maybe_unused]] id* objPtrLocation = (id*)argValues[i];
747
- NOBJC_LOG("CallSuperWithFFI: Object pointer at %p points to id=%p",
748
- objPtrLocation, *objPtrLocation);
749
- } else if (simpleArgEncoding[0] == '^') {
750
- [[maybe_unused]] void** ptrLocation = (void**)argValues[i];
751
- NOBJC_LOG("CallSuperWithFFI: Pointer at %p contains: %p",
752
- ptrLocation, *ptrLocation);
753
- }
754
- }
755
-
756
- NOBJC_LOG("CallSuperWithFFI: About to call ffi_call...");
757
- ffi_call(&cif, FFI_FN(msgSendFn),
758
- returnBuffer ? returnBuffer.get() : nullptr,
759
- argValues.data());
760
- NOBJC_LOG("CallSuperWithFFI: ffi_call completed successfully!");
761
-
762
- // 9. Convert return value
763
- Napi::Value result;
764
- if (simpleReturnEncoding[0] == 'v') {
765
- result = env.Undefined();
766
- } else {
767
- result = ConvertFFIReturnToJS(env, returnBuffer.get(), simpleReturnEncoding.c_str());
768
- }
471
+ // 7. Log setup for debugging
472
+ void* msgSendFn = (void*)objc_msgSendSuper;
473
+ LogFFICallSetup(msgSendFn, ctx.argValues, superStruct, superClass, methodSig);
769
474
 
770
- // 10. Cleanup
771
- CleanupAllocatedFFITypes(allocatedTypes);
475
+ // 8. Execute FFI call and convert result
476
+ Napi::Value result = ExecuteFFICallAndConvert(
477
+ env, &cif, msgSendFn, ctx, simpleReturnEncoding.c_str(), returnSize);
772
478
 
773
- NOBJC_LOG("CallSuperWithFFI: Returning result");
479
+ // 9. Cleanup
480
+ CleanupAllocatedFFITypes(ctx.allocatedTypes);
774
481
  return result;
775
482
 
776
483
  } catch (const std::exception& e) {
777
- CleanupAllocatedFFITypes(allocatedTypes);
484
+ CleanupAllocatedFFITypes(ctx.allocatedTypes);
778
485
  NOBJC_ERROR("CallSuperWithFFI: Exception: %s", e.what());
779
486
  throw;
780
487
  } catch (...) {
781
- CleanupAllocatedFFITypes(allocatedTypes);
488
+ CleanupAllocatedFFITypes(ctx.allocatedTypes);
782
489
  NOBJC_ERROR("CallSuperWithFFI: Unknown exception");
783
490
  throw;
784
491
  }
785
492
  }
786
493
 
787
- Napi::Value CallSuper(const Napi::CallbackInfo &info) {
788
- Napi::Env env = info.Env();
789
-
790
- // Args: self, selector, ...args
791
- if (info.Length() < 2) {
792
- throw Napi::TypeError::New(
793
- env, "CallSuper requires at least 2 arguments: self and selector");
794
- }
494
+ // MARK: - CallSuper Helper Functions
795
495
 
796
- // Get self
797
- if (!info[0].IsObject()) {
798
- throw Napi::TypeError::New(env, "First argument must be an ObjcObject (self)");
799
- }
800
- Napi::Object selfObj = info[0].As<Napi::Object>();
801
- if (!selfObj.InstanceOf(ObjcObject::constructor.Value())) {
802
- throw Napi::TypeError::New(env, "First argument must be an ObjcObject (self)");
803
- }
804
- ObjcObject *selfWrapper = Napi::ObjectWrap<ObjcObject>::Unwrap(selfObj);
805
- id self = selfWrapper->objcObject;
806
-
807
- // Get selector
808
- if (!info[1].IsString()) {
809
- throw Napi::TypeError::New(env, "Second argument must be a selector string");
810
- }
811
- std::string selectorName = info[1].As<Napi::String>().Utf8Value();
812
- SEL selector = sel_registerName(selectorName.c_str());
813
-
814
- NOBJC_LOG("CallSuper: selector=%s, self=%p, argCount=%zu",
815
- selectorName.c_str(), self, info.Length() - 2);
816
-
817
- // Find the superclass
496
+ /// Find superclass from the subclass registry or fall back to direct superclass.
497
+ static Class FindSuperClass(id self) {
818
498
  Class instanceClass = object_getClass(self);
819
- Class superClass = nil;
820
499
 
821
- NOBJC_LOG("CallSuper: instanceClass=%s", class_getName(instanceClass));
822
-
823
- {
824
- std::lock_guard<std::mutex> lock(g_subclasses_mutex);
825
- // Walk up the class hierarchy to find our subclass implementation
826
- Class cls = instanceClass;
827
- while (cls != nil) {
828
- void *clsPtr = (__bridge void *)cls;
829
- auto it = g_subclasses.find(clsPtr);
830
- if (it != g_subclasses.end()) {
831
- superClass = (__bridge Class)it->second.superClass;
832
- NOBJC_LOG("CallSuper: Found superclass from registry: %s",
833
- class_getName(superClass));
834
- break;
500
+ NOBJC_LOG("FindSuperClass: instanceClass=%s", class_getName(instanceClass));
501
+
502
+ // Walk up the class hierarchy to find our subclass implementation
503
+ Class cls = instanceClass;
504
+ while (cls != nil) {
505
+ void *clsPtr = (__bridge void *)cls;
506
+
507
+ Class superClass = SubclassManager::Instance().WithLock([clsPtr](auto& map) -> Class {
508
+ auto it = map.find(clsPtr);
509
+ if (it != map.end()) {
510
+ return (__bridge Class)it->second.superClass;
835
511
  }
836
- cls = class_getSuperclass(cls);
512
+ return nil;
513
+ });
514
+
515
+ if (superClass != nil) {
516
+ NOBJC_LOG("FindSuperClass: Found superclass from registry: %s",
517
+ class_getName(superClass));
518
+ return superClass;
837
519
  }
520
+ cls = class_getSuperclass(cls);
838
521
  }
839
-
840
- if (superClass == nil) {
841
- // Fall back to direct superclass
842
- superClass = class_getSuperclass(instanceClass);
843
- NOBJC_LOG("CallSuper: Using direct superclass: %s",
844
- superClass ? class_getName(superClass) : "nil");
845
- }
846
-
847
- if (superClass == nil) {
848
- NOBJC_ERROR("CallSuper: Could not determine superclass for super call");
849
- throw Napi::Error::New(env, "Could not determine superclass for super call");
850
- }
851
-
852
- // Get method signature from superclass
853
- NSMethodSignature *methodSig =
854
- [superClass instanceMethodSignatureForSelector:selector];
855
- if (methodSig == nil) {
856
- NOBJC_ERROR("CallSuper: Selector '%s' not found on superclass %s",
857
- selectorName.c_str(), class_getName(superClass));
858
- throw Napi::Error::New(
859
- env, "Selector '" + selectorName + "' not found on superclass");
860
- }
861
-
862
- NOBJC_LOG("CallSuper: Method signature: %s", [methodSig description].UTF8String);
863
-
864
- // Get the super method's IMP directly
865
- Method superMethod = class_getInstanceMethod(superClass, selector);
866
- if (superMethod == nil) {
867
- NOBJC_ERROR("CallSuper: Could not get method implementation for selector '%s'",
868
- selectorName.c_str());
869
- throw Napi::Error::New(
870
- env, "Could not get method implementation for selector '" + selectorName +
871
- "' from superclass");
872
- }
873
-
874
- NOBJC_LOG("CallSuper: Found method implementation at %p",
875
- method_getImplementation(superMethod));
876
-
877
- // Validate argument count
878
- const size_t expectedArgCount = [methodSig numberOfArguments] - 2;
879
- const size_t providedArgCount = info.Length() - 2;
880
522
 
881
- NOBJC_LOG("CallSuper: Expected %zu args, provided %zu args",
882
- expectedArgCount, providedArgCount);
883
-
884
- if (providedArgCount != expectedArgCount) {
885
- NOBJC_ERROR("CallSuper: Argument count mismatch for selector '%s'",
886
- selectorName.c_str());
887
- throw Napi::Error::New(
888
- env, "Selector " + selectorName + " expected " +
889
- std::to_string(expectedArgCount) + " argument(s), but got " +
890
- std::to_string(providedArgCount));
891
- }
523
+ // Fall back to direct superclass
524
+ Class superClass = class_getSuperclass(instanceClass);
525
+ NOBJC_LOG("FindSuperClass: Using direct superclass: %s",
526
+ superClass ? class_getName(superClass) : "nil");
527
+ return superClass;
528
+ }
892
529
 
893
- // CRITICAL: Use FFI approach to avoid infinite recursion
894
- // The NSInvocation approach below causes infinite recursion because
895
- // [invocation invoke] dispatches to self's implementation, not super's.
530
+ /// Direct objc_msgSendSuper call for methods without arguments.
531
+ static Napi::Value CallSuperNoArgs(
532
+ Napi::Env env,
533
+ id self,
534
+ Class superClass,
535
+ SEL selector,
536
+ const char* returnType,
537
+ const std::string& selectorName) {
896
538
 
897
- // If we have arguments, use FFI approach
898
- if (providedArgCount > 0) {
899
- NOBJC_LOG("CallSuper: Using FFI approach for method with arguments");
900
-
901
- try {
902
- return CallSuperWithFFI(env, self, superClass, selector, methodSig, info, 2);
903
- } catch (const std::exception& e) {
904
- NOBJC_ERROR("CallSuper: FFI approach failed: %s", e.what());
905
- // Re-throw - don't fall back to broken NSInvocation
906
- throw;
907
- }
908
- }
909
-
910
- // For methods WITHOUT arguments, use direct objc_msgSendSuper
911
- const char *returnType = SimplifyTypeEncoding([methodSig methodReturnType]);
912
539
  struct objc_super superStruct;
913
540
  superStruct.receiver = self;
914
541
  superStruct.super_class = superClass;
915
-
916
- NOBJC_LOG("CallSuper: Using direct objc_msgSendSuper for method without arguments");
917
-
542
+
543
+ NOBJC_LOG("CallSuperNoArgs: Using direct objc_msgSendSuper for method without arguments");
544
+
918
545
  switch (returnType[0]) {
919
546
  case 'v': { // void
920
- // Call and return undefined
921
- NOBJC_LOG("CallSuper: Calling void method");
922
- ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct,
923
- selector);
924
- NOBJC_LOG("CallSuper: Void method completed");
547
+ NOBJC_LOG("CallSuperNoArgs: Calling void method");
548
+ ((void (*)(struct objc_super *, SEL))objc_msgSendSuper)(&superStruct, selector);
549
+ NOBJC_LOG("CallSuperNoArgs: Void method completed");
925
550
  return env.Undefined();
926
551
  }
927
552
  case '@':
928
553
  case '#': { // id or Class
929
- NOBJC_LOG("CallSuper: Calling method returning id/Class");
554
+ NOBJC_LOG("CallSuperNoArgs: Calling method returning id/Class");
930
555
  id result = ((id(*)(struct objc_super *, SEL))objc_msgSendSuper)(
931
556
  &superStruct, selector);
932
- NOBJC_LOG("CallSuper: Method returned %p", result);
557
+ NOBJC_LOG("CallSuperNoArgs: Method returned %p", result);
933
558
  if (result == nil) {
934
559
  return env.Null();
935
560
  }
@@ -980,22 +605,63 @@ Napi::Value CallSuper(const Napi::CallbackInfo &info) {
980
605
  return Napi::Number::New(env, result);
981
606
  }
982
607
  default: {
983
- // For complex return types or methods with arguments, fall back to
984
- // NSInvocation but invoke the IMP directly
608
+ throw Napi::Error::New(
609
+ env, "Unsupported return type '" + std::string(1, returnType[0]) +
610
+ "' for super call to " + selectorName + ". Use simpler return types.");
611
+ }
612
+ }
613
+ }
985
614
 
986
- // This is a workaround: invoke on the superclass's implementation
987
- // by creating a temporary object or using method_invoke
615
+ Napi::Value CallSuper(const Napi::CallbackInfo &info) {
616
+ Napi::Env env = info.Env();
617
+
618
+ // 1. Validate basic arguments
619
+ if (info.Length() < 2) {
620
+ throw Napi::TypeError::New(
621
+ env, "CallSuper requires at least 2 arguments: self and selector");
622
+ }
988
623
 
989
- // For methods WITH arguments, we need a different approach
990
- // Let's use method_invoke which calls the IMP with an invocation
624
+ // 2. Extract self
625
+ if (!info[0].IsObject()) {
626
+ throw Napi::TypeError::New(env, "First argument must be an ObjcObject (self)");
627
+ }
628
+ Napi::Object selfObj = info[0].As<Napi::Object>();
629
+ if (!selfObj.InstanceOf(ObjcObject::constructor.Value())) {
630
+ throw Napi::TypeError::New(env, "First argument must be an ObjcObject (self)");
631
+ }
632
+ ObjcObject *selfWrapper = Napi::ObjectWrap<ObjcObject>::Unwrap(selfObj);
633
+ id self = selfWrapper->objcObject;
991
634
 
992
- // Actually, the safest thing is to just use the invocation
993
- // but make sure we're calling on the right implementation
635
+ // 3. Extract selector
636
+ if (!info[1].IsString()) {
637
+ throw Napi::TypeError::New(env, "Second argument must be a selector string");
638
+ }
639
+ std::string selectorName = info[1].As<Napi::String>().Utf8Value();
640
+ SEL selector = sel_registerName(selectorName.c_str());
641
+
642
+ NOBJC_LOG("CallSuper: selector=%s, self=%p, argCount=%zu",
643
+ selectorName.c_str(), self, info.Length() - 2);
994
644
 
995
- // For now, throw for unsupported return types
996
- throw Napi::Error::New(
997
- env, "Unsupported return type '" + std::string(1, returnType[0]) +
998
- "' for super call. Use simpler return types.");
645
+ // 4. Find superclass
646
+ Class superClass = FindSuperClass(self);
647
+ if (superClass == nil) {
648
+ NOBJC_ERROR("CallSuper: Could not determine superclass for super call");
649
+ throw Napi::Error::New(env, "Could not determine superclass for super call");
999
650
  }
651
+
652
+ // 5. Validate method and arguments
653
+ const size_t providedArgCount = info.Length() - 2;
654
+ NSMethodSignature *methodSig = ValidateSuperMethod(
655
+ env, superClass, selector, selectorName, providedArgCount);
656
+
657
+ // 6. Dispatch to appropriate call path
658
+ if (providedArgCount > 0) {
659
+ // Methods with arguments: use FFI approach
660
+ NOBJC_LOG("CallSuper: Using FFI approach for method with arguments");
661
+ return CallSuperWithFFI(env, self, superClass, selector, methodSig, info, 2);
1000
662
  }
663
+
664
+ // Methods without arguments: use direct objc_msgSendSuper
665
+ const char *returnType = SimplifyTypeEncoding([methodSig methodReturnType]);
666
+ return CallSuperNoArgs(env, self, superClass, selector, returnType, selectorName);
1001
667
  }