objc-js 0.0.12 → 0.0.14
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.
- package/README.md +56 -1
- package/binding.gyp +7 -2
- package/build/Release/nobjc_native.node +0 -0
- package/dist/index.d.ts +116 -1
- package/dist/index.js +117 -2
- package/dist/native.d.ts +2 -2
- package/dist/native.js +2 -2
- package/package.json +1 -1
- package/src/native/debug.h +19 -0
- package/src/native/ffi-utils.h +342 -0
- package/src/native/method-forwarding.h +5 -0
- package/src/native/method-forwarding.mm +110 -58
- package/src/native/nobjc.mm +3 -0
- package/src/native/protocol-impl.mm +26 -7
- package/src/native/protocol-storage.h +61 -0
- package/src/native/subclass-impl.h +20 -0
- package/src/native/subclass-impl.mm +1001 -0
- package/src/native/type-conversion.h +6 -8
|
@@ -0,0 +1,1001 @@
|
|
|
1
|
+
#include "subclass-impl.h"
|
|
2
|
+
#include "bridge.h"
|
|
3
|
+
#include "debug.h"
|
|
4
|
+
#include "ffi-utils.h"
|
|
5
|
+
#include "method-forwarding.h"
|
|
6
|
+
#include "ObjcObject.h"
|
|
7
|
+
#include "protocol-storage.h"
|
|
8
|
+
#include "type-conversion.h"
|
|
9
|
+
#include <Foundation/Foundation.h>
|
|
10
|
+
#include <atomic>
|
|
11
|
+
#include <chrono>
|
|
12
|
+
#include <napi.h>
|
|
13
|
+
#include <objc/message.h>
|
|
14
|
+
#include <objc/runtime.h>
|
|
15
|
+
#include <sstream>
|
|
16
|
+
|
|
17
|
+
// MARK: - objc_msgSendSuper declarations
|
|
18
|
+
|
|
19
|
+
// Declare objc_msgSendSuper2 for super calls
|
|
20
|
+
// Note: On ARM64, there is no separate stret variant - it's handled automatically
|
|
21
|
+
// objc_msgSendSuper2 isn't in the headers but is exported
|
|
22
|
+
extern "C" id objc_msgSendSuper2(struct objc_super *super, SEL op, ...);
|
|
23
|
+
|
|
24
|
+
// MARK: - Global Storage Definition
|
|
25
|
+
|
|
26
|
+
std::unordered_map<void *, SubclassImplementation> g_subclasses;
|
|
27
|
+
std::mutex g_subclasses_mutex;
|
|
28
|
+
|
|
29
|
+
// MARK: - Forward Declarations for Method Forwarding
|
|
30
|
+
|
|
31
|
+
static BOOL SubclassRespondsToSelector(id self, SEL _cmd, SEL selector);
|
|
32
|
+
static NSMethodSignature *SubclassMethodSignatureForSelector(id self, SEL _cmd,
|
|
33
|
+
SEL selector);
|
|
34
|
+
static void SubclassForwardInvocation(id self, SEL _cmd,
|
|
35
|
+
NSInvocation *invocation);
|
|
36
|
+
static void SubclassDeallocImplementation(id self, SEL _cmd);
|
|
37
|
+
|
|
38
|
+
// MARK: - Subclass Method Forwarding Implementation
|
|
39
|
+
|
|
40
|
+
static BOOL SubclassRespondsToSelector(id self, SEL _cmd, SEL selector) {
|
|
41
|
+
Class cls = object_getClass(self);
|
|
42
|
+
void *clsPtr = (__bridge void *)cls;
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
std::lock_guard<std::mutex> lock(g_subclasses_mutex);
|
|
46
|
+
auto it = g_subclasses.find(clsPtr);
|
|
47
|
+
if (it != g_subclasses.end()) {
|
|
48
|
+
NSString *selectorString = NSStringFromSelector(selector);
|
|
49
|
+
if (selectorString != nil) {
|
|
50
|
+
std::string selName = [selectorString UTF8String];
|
|
51
|
+
auto methodIt = it->second.methods.find(selName);
|
|
52
|
+
if (methodIt != it->second.methods.end()) {
|
|
53
|
+
return YES;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check superclass
|
|
60
|
+
Class superClass = class_getSuperclass(cls);
|
|
61
|
+
if (superClass != nil) {
|
|
62
|
+
return [superClass instancesRespondToSelector:selector];
|
|
63
|
+
}
|
|
64
|
+
return NO;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static NSMethodSignature *SubclassMethodSignatureForSelector(id self, SEL _cmd,
|
|
68
|
+
SEL selector) {
|
|
69
|
+
Class cls = object_getClass(self);
|
|
70
|
+
void *clsPtr = (__bridge void *)cls;
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
std::lock_guard<std::mutex> lock(g_subclasses_mutex);
|
|
74
|
+
auto it = g_subclasses.find(clsPtr);
|
|
75
|
+
if (it != g_subclasses.end()) {
|
|
76
|
+
NSString *selectorString = NSStringFromSelector(selector);
|
|
77
|
+
std::string selName = [selectorString UTF8String];
|
|
78
|
+
auto methodIt = it->second.methods.find(selName);
|
|
79
|
+
if (methodIt != it->second.methods.end()) {
|
|
80
|
+
return [NSMethodSignature
|
|
81
|
+
signatureWithObjCTypes:methodIt->second.typeEncoding.c_str()];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Fall back to superclass
|
|
87
|
+
Class superClass = class_getSuperclass(cls);
|
|
88
|
+
if (superClass != nil) {
|
|
89
|
+
return [superClass instanceMethodSignatureForSelector:selector];
|
|
90
|
+
}
|
|
91
|
+
return nil;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static void SubclassForwardInvocation(id self, SEL _cmd,
|
|
95
|
+
NSInvocation *invocation) {
|
|
96
|
+
if (!invocation) {
|
|
97
|
+
NOBJC_ERROR("SubclassForwardInvocation called with nil invocation");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[invocation retainArguments];
|
|
102
|
+
[invocation retain];
|
|
103
|
+
|
|
104
|
+
SEL selector = [invocation selector];
|
|
105
|
+
NSString *selectorString = NSStringFromSelector(selector);
|
|
106
|
+
if (!selectorString) {
|
|
107
|
+
NOBJC_ERROR("Failed to convert selector to string");
|
|
108
|
+
[invocation release];
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
std::string selectorName = [selectorString UTF8String];
|
|
113
|
+
|
|
114
|
+
NOBJC_LOG("SubclassForwardInvocation: Called for selector %s", selectorName.c_str());
|
|
115
|
+
|
|
116
|
+
Class cls = object_getClass(self);
|
|
117
|
+
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
|
+
}
|
|
134
|
+
|
|
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
|
+
}
|
|
150
|
+
|
|
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;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
auto methodIt = it->second.methods.find(selectorName);
|
|
192
|
+
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;
|
|
198
|
+
}
|
|
199
|
+
|
|
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
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
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
|
+
}
|
|
249
|
+
}
|
|
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
|
+
|
|
269
|
+
status = tsfn.NonBlockingCall(data, CallJSCallback); // Use unified CallJSCallback
|
|
270
|
+
tsfn.Release();
|
|
271
|
+
|
|
272
|
+
NOBJC_LOG("SubclassForwardInvocation: NonBlockingCall returned status=%d", status);
|
|
273
|
+
|
|
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
|
+
}
|
|
281
|
+
|
|
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
|
+
}
|
|
291
|
+
}
|
|
292
|
+
iterations++;
|
|
293
|
+
if (iterations % 1000 == 0) {
|
|
294
|
+
NOBJC_LOG("SubclassForwardInvocation: Still waiting... (%d iterations)", iterations);
|
|
295
|
+
}
|
|
296
|
+
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, true);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
static void SubclassDeallocImplementation(id self, SEL _cmd) {
|
|
302
|
+
// Nothing special to clean up per-instance for subclasses
|
|
303
|
+
// The class-level storage remains until explicitly disposed
|
|
304
|
+
|
|
305
|
+
// Call super dealloc (handled by ARC, but we need to be careful)
|
|
306
|
+
Class cls = object_getClass(self);
|
|
307
|
+
Class superClass = class_getSuperclass(cls);
|
|
308
|
+
if (superClass) {
|
|
309
|
+
// Under ARC, dealloc is handled automatically
|
|
310
|
+
// We don't need to call [super dealloc]
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// MARK: - Main DefineClass Implementation
|
|
315
|
+
|
|
316
|
+
Napi::Value DefineClass(const Napi::CallbackInfo &info) {
|
|
317
|
+
Napi::Env env = info.Env();
|
|
318
|
+
|
|
319
|
+
// Validate arguments
|
|
320
|
+
if (info.Length() < 1 || !info[0].IsObject()) {
|
|
321
|
+
throw Napi::TypeError::New(
|
|
322
|
+
env, "Expected an object argument with class definition");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
Napi::Object definition = info[0].As<Napi::Object>();
|
|
326
|
+
|
|
327
|
+
// Extract class name
|
|
328
|
+
if (!definition.Has("name") || !definition.Get("name").IsString()) {
|
|
329
|
+
throw Napi::TypeError::New(env, "Class definition must have 'name' string");
|
|
330
|
+
}
|
|
331
|
+
std::string className = definition.Get("name").As<Napi::String>().Utf8Value();
|
|
332
|
+
|
|
333
|
+
// Check if class already exists
|
|
334
|
+
if (NSClassFromString([NSString stringWithUTF8String:className.c_str()]) !=
|
|
335
|
+
nil) {
|
|
336
|
+
throw Napi::Error::New(
|
|
337
|
+
env, "Class '" + className + "' already exists in the Objective-C runtime");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Extract superclass
|
|
341
|
+
Class superClass = nil;
|
|
342
|
+
if (!definition.Has("superclass")) {
|
|
343
|
+
throw Napi::TypeError::New(env,
|
|
344
|
+
"Class definition must have 'superclass'");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
Napi::Value superValue = definition.Get("superclass");
|
|
348
|
+
if (superValue.IsString()) {
|
|
349
|
+
std::string superName = superValue.As<Napi::String>().Utf8Value();
|
|
350
|
+
superClass =
|
|
351
|
+
NSClassFromString([NSString stringWithUTF8String:superName.c_str()]);
|
|
352
|
+
if (superClass == nil) {
|
|
353
|
+
throw Napi::Error::New(env, "Superclass '" + superName + "' not found");
|
|
354
|
+
}
|
|
355
|
+
} else if (superValue.IsObject()) {
|
|
356
|
+
Napi::Object superObj = superValue.As<Napi::Object>();
|
|
357
|
+
if (superObj.InstanceOf(ObjcObject::constructor.Value())) {
|
|
358
|
+
ObjcObject *objcObj = Napi::ObjectWrap<ObjcObject>::Unwrap(superObj);
|
|
359
|
+
superClass = (Class)objcObj->objcObject;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (superClass == nil) {
|
|
364
|
+
throw Napi::TypeError::New(
|
|
365
|
+
env, "'superclass' must be a string or ObjcObject representing a Class");
|
|
366
|
+
}
|
|
367
|
+
|
|
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
|
+
}
|
|
383
|
+
|
|
384
|
+
// Allocate the new class
|
|
385
|
+
Class newClass = objc_allocateClassPair(superClass, className.c_str(), 0);
|
|
386
|
+
if (newClass == nil) {
|
|
387
|
+
throw Napi::Error::New(env,
|
|
388
|
+
"Failed to allocate class pair for '" + className + "'");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Create the subclass implementation storage
|
|
392
|
+
SubclassImplementation impl{
|
|
393
|
+
.className = className,
|
|
394
|
+
.objcClass = (__bridge void *)newClass,
|
|
395
|
+
.superClass = (__bridge void *)superClass,
|
|
396
|
+
.methods = {},
|
|
397
|
+
.env = env,
|
|
398
|
+
.js_thread = pthread_self(),
|
|
399
|
+
.isElectron = isElectron, // Only Electron needs TSFN always; Bun works with direct calls
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// Add protocol conformance
|
|
403
|
+
if (definition.Has("protocols") && definition.Get("protocols").IsArray()) {
|
|
404
|
+
Napi::Array protocols = definition.Get("protocols").As<Napi::Array>();
|
|
405
|
+
for (uint32_t i = 0; i < protocols.Length(); i++) {
|
|
406
|
+
if (protocols.Get(i).IsString()) {
|
|
407
|
+
std::string protoName =
|
|
408
|
+
protocols.Get(i).As<Napi::String>().Utf8Value();
|
|
409
|
+
Protocol *proto = objc_getProtocol(protoName.c_str());
|
|
410
|
+
if (proto != nullptr) {
|
|
411
|
+
class_addProtocol(newClass, proto);
|
|
412
|
+
} else {
|
|
413
|
+
NOBJC_WARN("Protocol %s not found", protoName.c_str());
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Process method definitions
|
|
420
|
+
if (definition.Has("methods") && definition.Get("methods").IsObject()) {
|
|
421
|
+
Napi::Object methods = definition.Get("methods").As<Napi::Object>();
|
|
422
|
+
Napi::Array methodNames = methods.GetPropertyNames();
|
|
423
|
+
|
|
424
|
+
for (uint32_t i = 0; i < methodNames.Length(); i++) {
|
|
425
|
+
Napi::Value key = methodNames.Get(i);
|
|
426
|
+
if (!key.IsString())
|
|
427
|
+
continue;
|
|
428
|
+
|
|
429
|
+
std::string selectorName = key.As<Napi::String>().Utf8Value();
|
|
430
|
+
Napi::Value methodDef = methods.Get(key);
|
|
431
|
+
|
|
432
|
+
if (!methodDef.IsObject()) {
|
|
433
|
+
NOBJC_WARN("Method definition for %s is not an object",
|
|
434
|
+
selectorName.c_str());
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
Napi::Object methodObj = methodDef.As<Napi::Object>();
|
|
439
|
+
|
|
440
|
+
// Get type encoding
|
|
441
|
+
if (!methodObj.Has("types") || !methodObj.Get("types").IsString()) {
|
|
442
|
+
NOBJC_WARN("Method %s missing 'types' string", selectorName.c_str());
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
std::string typeEncoding =
|
|
446
|
+
methodObj.Get("types").As<Napi::String>().Utf8Value();
|
|
447
|
+
|
|
448
|
+
// Get implementation function
|
|
449
|
+
if (!methodObj.Has("implementation") ||
|
|
450
|
+
!methodObj.Get("implementation").IsFunction()) {
|
|
451
|
+
NOBJC_WARN("Method %s missing 'implementation' function",
|
|
452
|
+
selectorName.c_str());
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
Napi::Function jsImpl =
|
|
456
|
+
methodObj.Get("implementation").As<Napi::Function>();
|
|
457
|
+
|
|
458
|
+
SEL selector = sel_registerName(selectorName.c_str());
|
|
459
|
+
|
|
460
|
+
// Create ThreadSafeFunction
|
|
461
|
+
Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(
|
|
462
|
+
env, jsImpl, "SubclassMethod_" + selectorName, 0, 1,
|
|
463
|
+
[](Napi::Env) {});
|
|
464
|
+
|
|
465
|
+
// Store method info
|
|
466
|
+
SubclassMethodInfo methodInfo{
|
|
467
|
+
.callback = tsfn,
|
|
468
|
+
.jsCallback = Napi::Persistent(jsImpl),
|
|
469
|
+
.typeEncoding = typeEncoding,
|
|
470
|
+
.selectorName = selectorName,
|
|
471
|
+
.isClassMethod = false,
|
|
472
|
+
};
|
|
473
|
+
impl.methods[selectorName] = std::move(methodInfo);
|
|
474
|
+
|
|
475
|
+
// Add the method with _objc_msgForward as IMP (triggers forwarding)
|
|
476
|
+
// This ensures our forwardInvocation: gets called
|
|
477
|
+
class_addMethod(newClass, selector, (IMP)_objc_msgForward,
|
|
478
|
+
typeEncoding.c_str());
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Add message forwarding methods
|
|
483
|
+
class_addMethod(newClass, @selector(respondsToSelector:),
|
|
484
|
+
(IMP)SubclassRespondsToSelector, "B@::");
|
|
485
|
+
class_addMethod(newClass, @selector(methodSignatureForSelector:),
|
|
486
|
+
(IMP)SubclassMethodSignatureForSelector, "@@::");
|
|
487
|
+
class_addMethod(newClass, @selector(forwardInvocation:),
|
|
488
|
+
(IMP)SubclassForwardInvocation, "v@:@");
|
|
489
|
+
class_addMethod(newClass, sel_registerName("dealloc"),
|
|
490
|
+
(IMP)SubclassDeallocImplementation, "v@:");
|
|
491
|
+
|
|
492
|
+
// Register the class
|
|
493
|
+
objc_registerClassPair(newClass);
|
|
494
|
+
|
|
495
|
+
// Store in global map
|
|
496
|
+
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
|
+
}
|
|
501
|
+
|
|
502
|
+
// Return the Class object
|
|
503
|
+
return ObjcObject::NewInstance(env, newClass);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// MARK: - CallSuper Implementation
|
|
507
|
+
|
|
508
|
+
// New FFI-based implementation to avoid infinite recursion
|
|
509
|
+
static Napi::Value CallSuperWithFFI(
|
|
510
|
+
Napi::Env env,
|
|
511
|
+
id self,
|
|
512
|
+
Class superClass,
|
|
513
|
+
SEL selector,
|
|
514
|
+
NSMethodSignature* methodSig,
|
|
515
|
+
const Napi::CallbackInfo& info,
|
|
516
|
+
size_t argStartIndex) {
|
|
517
|
+
|
|
518
|
+
std::string selectorName = NSStringFromSelector(selector).UTF8String;
|
|
519
|
+
NOBJC_LOG("CallSuperWithFFI: selector=%s, self=%p, superClass=%s",
|
|
520
|
+
selectorName.c_str(), self, class_getName(superClass));
|
|
521
|
+
|
|
522
|
+
// Vector to track allocated FFI types for cleanup
|
|
523
|
+
std::vector<ffi_type*> allocatedTypes;
|
|
524
|
+
|
|
525
|
+
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
|
|
529
|
+
struct objc_super superStruct;
|
|
530
|
+
superStruct.receiver = self;
|
|
531
|
+
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));
|
|
535
|
+
|
|
536
|
+
// 2. Determine which objc_msgSend variant to use
|
|
537
|
+
// Use objc_msgSendSuper with the superclass (matches the no-args case)
|
|
538
|
+
const char* returnEncoding = [methodSig methodReturnType];
|
|
539
|
+
SimplifiedTypeEncoding simpleReturnEncoding(returnEncoding);
|
|
540
|
+
|
|
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
|
|
563
|
+
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);
|
|
568
|
+
|
|
569
|
+
// 5. Prepare FFI CIF
|
|
570
|
+
ffi_cif cif;
|
|
571
|
+
ffi_status status = ffi_prep_cif(
|
|
572
|
+
&cif,
|
|
573
|
+
FFI_DEFAULT_ABI,
|
|
574
|
+
argFFITypes.size(),
|
|
575
|
+
returnFFIType,
|
|
576
|
+
argFFITypes.data()
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
if (status != FFI_OK) {
|
|
580
|
+
CleanupAllocatedFFITypes(allocatedTypes);
|
|
581
|
+
NOBJC_ERROR("CallSuperWithFFI: ffi_prep_cif failed with status %d", status);
|
|
582
|
+
throw Napi::Error::New(env, "FFI preparation failed");
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
NOBJC_LOG("CallSuperWithFFI: FFI CIF prepared successfully");
|
|
586
|
+
|
|
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);
|
|
604
|
+
|
|
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));
|
|
613
|
+
|
|
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
|
+
}
|
|
769
|
+
|
|
770
|
+
// 10. Cleanup
|
|
771
|
+
CleanupAllocatedFFITypes(allocatedTypes);
|
|
772
|
+
|
|
773
|
+
NOBJC_LOG("CallSuperWithFFI: Returning result");
|
|
774
|
+
return result;
|
|
775
|
+
|
|
776
|
+
} catch (const std::exception& e) {
|
|
777
|
+
CleanupAllocatedFFITypes(allocatedTypes);
|
|
778
|
+
NOBJC_ERROR("CallSuperWithFFI: Exception: %s", e.what());
|
|
779
|
+
throw;
|
|
780
|
+
} catch (...) {
|
|
781
|
+
CleanupAllocatedFFITypes(allocatedTypes);
|
|
782
|
+
NOBJC_ERROR("CallSuperWithFFI: Unknown exception");
|
|
783
|
+
throw;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
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
|
+
}
|
|
795
|
+
|
|
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
|
|
818
|
+
Class instanceClass = object_getClass(self);
|
|
819
|
+
Class superClass = nil;
|
|
820
|
+
|
|
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;
|
|
835
|
+
}
|
|
836
|
+
cls = class_getSuperclass(cls);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
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
|
+
|
|
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
|
+
}
|
|
892
|
+
|
|
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.
|
|
896
|
+
|
|
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
|
+
struct objc_super superStruct;
|
|
913
|
+
superStruct.receiver = self;
|
|
914
|
+
superStruct.super_class = superClass;
|
|
915
|
+
|
|
916
|
+
NOBJC_LOG("CallSuper: Using direct objc_msgSendSuper for method without arguments");
|
|
917
|
+
|
|
918
|
+
switch (returnType[0]) {
|
|
919
|
+
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");
|
|
925
|
+
return env.Undefined();
|
|
926
|
+
}
|
|
927
|
+
case '@':
|
|
928
|
+
case '#': { // id or Class
|
|
929
|
+
NOBJC_LOG("CallSuper: Calling method returning id/Class");
|
|
930
|
+
id result = ((id(*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
931
|
+
&superStruct, selector);
|
|
932
|
+
NOBJC_LOG("CallSuper: Method returned %p", result);
|
|
933
|
+
if (result == nil) {
|
|
934
|
+
return env.Null();
|
|
935
|
+
}
|
|
936
|
+
return ObjcObject::NewInstance(env, result);
|
|
937
|
+
}
|
|
938
|
+
case 'B': { // BOOL
|
|
939
|
+
BOOL result = ((BOOL(*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
940
|
+
&superStruct, selector);
|
|
941
|
+
return Napi::Boolean::New(env, result);
|
|
942
|
+
}
|
|
943
|
+
case 'c':
|
|
944
|
+
case 'i':
|
|
945
|
+
case 's':
|
|
946
|
+
case 'l': { // signed integers
|
|
947
|
+
long result = ((long (*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
948
|
+
&superStruct, selector);
|
|
949
|
+
return Napi::Number::New(env, result);
|
|
950
|
+
}
|
|
951
|
+
case 'C':
|
|
952
|
+
case 'I':
|
|
953
|
+
case 'S':
|
|
954
|
+
case 'L': { // unsigned integers
|
|
955
|
+
unsigned long result =
|
|
956
|
+
((unsigned long (*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
957
|
+
&superStruct, selector);
|
|
958
|
+
return Napi::Number::New(env, result);
|
|
959
|
+
}
|
|
960
|
+
case 'q': { // long long / NSInteger
|
|
961
|
+
long long result =
|
|
962
|
+
((long long (*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
963
|
+
&superStruct, selector);
|
|
964
|
+
return Napi::Number::New(env, result);
|
|
965
|
+
}
|
|
966
|
+
case 'Q': { // unsigned long long / NSUInteger
|
|
967
|
+
unsigned long long result =
|
|
968
|
+
((unsigned long long (*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
969
|
+
&superStruct, selector);
|
|
970
|
+
return Napi::Number::New(env, result);
|
|
971
|
+
}
|
|
972
|
+
case 'f': { // float
|
|
973
|
+
float result = ((float (*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
974
|
+
&superStruct, selector);
|
|
975
|
+
return Napi::Number::New(env, result);
|
|
976
|
+
}
|
|
977
|
+
case 'd': { // double
|
|
978
|
+
double result = ((double (*)(struct objc_super *, SEL))objc_msgSendSuper)(
|
|
979
|
+
&superStruct, selector);
|
|
980
|
+
return Napi::Number::New(env, result);
|
|
981
|
+
}
|
|
982
|
+
default: {
|
|
983
|
+
// For complex return types or methods with arguments, fall back to
|
|
984
|
+
// NSInvocation but invoke the IMP directly
|
|
985
|
+
|
|
986
|
+
// This is a workaround: invoke on the superclass's implementation
|
|
987
|
+
// by creating a temporary object or using method_invoke
|
|
988
|
+
|
|
989
|
+
// For methods WITH arguments, we need a different approach
|
|
990
|
+
// Let's use method_invoke which calls the IMP with an invocation
|
|
991
|
+
|
|
992
|
+
// Actually, the safest thing is to just use the invocation
|
|
993
|
+
// but make sure we're calling on the right implementation
|
|
994
|
+
|
|
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.");
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|