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.
- package/README.md +30 -288
- package/binding.gyp +2 -1
- package/dist/native.js +2 -1
- package/package.json +12 -6
- package/prebuilds/darwin-arm64/objc-js.node +0 -0
- package/prebuilds/darwin-x64/objc-js.node +0 -0
- package/src/native/ObjcObject.mm +2 -14
- package/src/native/constants.h +42 -0
- package/src/native/ffi-utils.h +103 -1
- package/src/native/forwarding-common.h +87 -0
- package/src/native/forwarding-common.mm +155 -0
- package/src/native/memory-utils.h +197 -0
- package/src/native/method-forwarding.mm +137 -208
- package/src/native/nobjc.mm +7 -31
- package/src/native/pointer-utils.h +63 -0
- package/src/native/protocol-impl.mm +7 -27
- package/src/native/protocol-manager.h +145 -0
- package/src/native/protocol-storage.h +12 -33
- package/src/native/runtime-detection.h +54 -0
- package/src/native/subclass-impl.mm +232 -566
- package/src/native/subclass-manager.h +170 -0
- package/src/native/super-call-helpers.h +361 -0
- package/src/native/type-conversion.h +200 -246
- package/src/native/type-dispatch.h +241 -0
- package/build/Release/nobjc_native.node +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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(
|
|
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 (
|
|
194
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
270
|
-
|
|
188
|
+
auto methodIt = it->second.methods.find(selName);
|
|
189
|
+
if (methodIt == it->second.methods.end()) {
|
|
190
|
+
return Napi::Function();
|
|
191
|
+
}
|
|
271
192
|
|
|
272
|
-
|
|
193
|
+
return methodIt->second.jsCallback.Value();
|
|
194
|
+
});
|
|
195
|
+
};
|
|
273
196
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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
|
|
369
|
-
bool isElectron =
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
533
|
-
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
565
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
588
|
-
|
|
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
|
-
//
|
|
606
|
-
|
|
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
|
-
//
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
//
|
|
771
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
797
|
-
|
|
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("
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
auto it =
|
|
830
|
-
if (it !=
|
|
831
|
-
|
|
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
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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("
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
(
|
|
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("
|
|
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("
|
|
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
|
-
|
|
984
|
-
|
|
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
|
-
|
|
987
|
-
|
|
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
|
-
|
|
990
|
-
|
|
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
|
-
|
|
993
|
-
|
|
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
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
}
|