objc-js 0.0.14 → 1.0.0
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/binding.gyp +2 -1
- package/build/Release/nobjc_native.node +0 -0
- package/package.json +2 -1
- package/src/native/ObjcObject.mm +2 -14
- package/src/native/bridge.h +20 -0
- 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 -252
- package/src/native/type-dispatch.h +241 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#ifndef SUBCLASS_MANAGER_H
|
|
2
|
+
#define SUBCLASS_MANAGER_H
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @file subclass-manager.h
|
|
6
|
+
* @brief Singleton manager for subclass implementations.
|
|
7
|
+
*
|
|
8
|
+
* SubclassManager provides thread-safe access to subclass implementation storage.
|
|
9
|
+
* It replaces the global g_subclasses map and g_subclasses_mutex with
|
|
10
|
+
* an encapsulated singleton pattern.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* // Register a subclass implementation
|
|
14
|
+
* SubclassManager::Instance().Register(classPtr, std::move(impl));
|
|
15
|
+
*
|
|
16
|
+
* // Find an implementation
|
|
17
|
+
* auto* impl = SubclassManager::Instance().Find(classPtr);
|
|
18
|
+
*
|
|
19
|
+
* // Execute a callback with lock held
|
|
20
|
+
* SubclassManager::Instance().WithLock([](auto& map) {
|
|
21
|
+
* // ... work with map ...
|
|
22
|
+
* });
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
#include "protocol-storage.h"
|
|
26
|
+
#include <functional>
|
|
27
|
+
#include <mutex>
|
|
28
|
+
#include <optional>
|
|
29
|
+
#include <unordered_map>
|
|
30
|
+
|
|
31
|
+
namespace nobjc {
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @brief Thread-safe singleton manager for subclass implementations.
|
|
35
|
+
*
|
|
36
|
+
* Encapsulates storage and synchronization for JS-defined subclasses,
|
|
37
|
+
* providing a clean API for registration, lookup, and iteration.
|
|
38
|
+
*/
|
|
39
|
+
class SubclassManager {
|
|
40
|
+
public:
|
|
41
|
+
/**
|
|
42
|
+
* @brief Get the singleton instance.
|
|
43
|
+
* @return Reference to the singleton SubclassManager.
|
|
44
|
+
*/
|
|
45
|
+
static SubclassManager& Instance() {
|
|
46
|
+
static SubclassManager instance;
|
|
47
|
+
return instance;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Delete copy/move operations for singleton
|
|
51
|
+
SubclassManager(const SubclassManager&) = delete;
|
|
52
|
+
SubclassManager& operator=(const SubclassManager&) = delete;
|
|
53
|
+
SubclassManager(SubclassManager&&) = delete;
|
|
54
|
+
SubclassManager& operator=(SubclassManager&&) = delete;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @brief Find an implementation by class pointer.
|
|
58
|
+
* @param classPtr The Objective-C Class pointer (bridged from Class).
|
|
59
|
+
* @return Pointer to implementation if found, nullptr otherwise.
|
|
60
|
+
* @note Caller must NOT hold the lock. This method acquires the lock.
|
|
61
|
+
*/
|
|
62
|
+
SubclassImplementation* Find(void* classPtr) {
|
|
63
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
64
|
+
auto it = subclasses_.find(classPtr);
|
|
65
|
+
if (it != subclasses_.end()) {
|
|
66
|
+
return &it->second;
|
|
67
|
+
}
|
|
68
|
+
return nullptr;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @brief Register a new subclass implementation.
|
|
73
|
+
* @param classPtr The Objective-C Class pointer.
|
|
74
|
+
* @param impl The implementation to register (moved).
|
|
75
|
+
*/
|
|
76
|
+
void Register(void* classPtr, SubclassImplementation&& impl) {
|
|
77
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
78
|
+
subclasses_.emplace(classPtr, std::move(impl));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @brief Unregister a subclass implementation.
|
|
83
|
+
* @param classPtr The Objective-C Class pointer.
|
|
84
|
+
* @return true if the implementation was found and removed, false otherwise.
|
|
85
|
+
*/
|
|
86
|
+
bool Unregister(void* classPtr) {
|
|
87
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
88
|
+
return subclasses_.erase(classPtr) > 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @brief Execute a callback with the lock held.
|
|
93
|
+
* @param callback Function to call with the map reference.
|
|
94
|
+
*
|
|
95
|
+
* Use this for complex operations that need to access multiple map entries
|
|
96
|
+
* or perform conditional logic while holding the lock.
|
|
97
|
+
*/
|
|
98
|
+
template <typename Callback>
|
|
99
|
+
auto WithLock(Callback&& callback)
|
|
100
|
+
-> decltype(callback(std::declval<std::unordered_map<void*, SubclassImplementation>&>())) {
|
|
101
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
102
|
+
return callback(subclasses_);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @brief Execute a read-only callback with the lock held.
|
|
107
|
+
* @param callback Function to call with const map reference.
|
|
108
|
+
*/
|
|
109
|
+
template <typename Callback>
|
|
110
|
+
auto WithLockConst(Callback&& callback) const
|
|
111
|
+
-> decltype(callback(std::declval<const std::unordered_map<void*, SubclassImplementation>&>())) {
|
|
112
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
113
|
+
return callback(subclasses_);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @brief Find superclass by walking the class hierarchy.
|
|
118
|
+
* @param instanceClassPtr Starting class pointer.
|
|
119
|
+
* @return Superclass pointer if found in registry, nullptr otherwise.
|
|
120
|
+
*
|
|
121
|
+
* Walks up the class hierarchy to find a registered subclass implementation
|
|
122
|
+
* and returns its superclass pointer.
|
|
123
|
+
*/
|
|
124
|
+
void* FindSuperClassInHierarchy(void* instanceClassPtr) {
|
|
125
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
126
|
+
|
|
127
|
+
// Walk up the class hierarchy to find our subclass implementation
|
|
128
|
+
void* clsPtr = instanceClassPtr;
|
|
129
|
+
while (clsPtr != nullptr) {
|
|
130
|
+
auto it = subclasses_.find(clsPtr);
|
|
131
|
+
if (it != subclasses_.end()) {
|
|
132
|
+
return it->second.superClass;
|
|
133
|
+
}
|
|
134
|
+
// Get superclass - this requires Objective-C runtime, so we return nullptr
|
|
135
|
+
// and let the caller handle walking the hierarchy with runtime calls
|
|
136
|
+
return nullptr;
|
|
137
|
+
}
|
|
138
|
+
return nullptr;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @brief Get the number of registered subclasses.
|
|
143
|
+
* @return Count of subclasses.
|
|
144
|
+
*/
|
|
145
|
+
size_t Size() const {
|
|
146
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
147
|
+
return subclasses_.size();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @brief Check if a subclass exists for the given pointer.
|
|
152
|
+
* @param classPtr The Objective-C Class pointer.
|
|
153
|
+
* @return true if registered, false otherwise.
|
|
154
|
+
*/
|
|
155
|
+
bool Contains(void* classPtr) const {
|
|
156
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
157
|
+
return subclasses_.find(classPtr) != subclasses_.end();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private:
|
|
161
|
+
SubclassManager() = default;
|
|
162
|
+
~SubclassManager() = default;
|
|
163
|
+
|
|
164
|
+
mutable std::mutex mutex_;
|
|
165
|
+
std::unordered_map<void*, SubclassImplementation> subclasses_;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
} // namespace nobjc
|
|
169
|
+
|
|
170
|
+
#endif // SUBCLASS_MANAGER_H
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// super-call-helpers.h - Helper Functions for CallSuper Implementation
|
|
5
|
+
// ============================================================================
|
|
6
|
+
//
|
|
7
|
+
// These functions break up the large CallSuperWithFFI function into
|
|
8
|
+
// smaller, focused units for better maintainability and readability.
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
#include "constants.h"
|
|
12
|
+
#include "debug.h"
|
|
13
|
+
#include "ffi-utils.h"
|
|
14
|
+
#include "type-conversion.h"
|
|
15
|
+
#include <Foundation/Foundation.h>
|
|
16
|
+
#include <memory>
|
|
17
|
+
#include <napi.h>
|
|
18
|
+
#include <objc/message.h>
|
|
19
|
+
#include <objc/runtime.h>
|
|
20
|
+
#include <string>
|
|
21
|
+
#include <vector>
|
|
22
|
+
|
|
23
|
+
// MARK: - FFI Argument Context
|
|
24
|
+
|
|
25
|
+
/// Context structure for building FFI arguments
|
|
26
|
+
struct FFIArgumentContext {
|
|
27
|
+
std::vector<ffi_type*> argFFITypes;
|
|
28
|
+
std::vector<void*> argValues;
|
|
29
|
+
std::vector<std::unique_ptr<uint8_t[]>> argBuffers;
|
|
30
|
+
std::vector<ffi_type*> allocatedTypes;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// MARK: - PrepareFFIArgumentTypes
|
|
34
|
+
|
|
35
|
+
/// Build FFI type arrays for the method arguments.
|
|
36
|
+
/// Returns the FFI return type and populates ctx.argFFITypes.
|
|
37
|
+
inline ffi_type* PrepareFFIArgumentTypes(
|
|
38
|
+
NSMethodSignature* methodSig,
|
|
39
|
+
const char* returnEncoding,
|
|
40
|
+
size_t* outReturnSize,
|
|
41
|
+
FFIArgumentContext& ctx) {
|
|
42
|
+
|
|
43
|
+
size_t totalArgs = [methodSig numberOfArguments];
|
|
44
|
+
|
|
45
|
+
// First arg: objc_super pointer
|
|
46
|
+
ctx.argFFITypes.push_back(&ffi_type_pointer);
|
|
47
|
+
// Second arg: SEL
|
|
48
|
+
ctx.argFFITypes.push_back(&ffi_type_pointer);
|
|
49
|
+
|
|
50
|
+
// Remaining args: method arguments (starting from index 2)
|
|
51
|
+
for (size_t i = 2; i < totalArgs; i++) {
|
|
52
|
+
const char* argEncoding = [methodSig getArgumentTypeAtIndex:i];
|
|
53
|
+
ffi_type* argType = GetFFITypeForEncoding(argEncoding, nullptr, ctx.allocatedTypes);
|
|
54
|
+
ctx.argFFITypes.push_back(argType);
|
|
55
|
+
NOBJC_LOG("PrepareFFIArgumentTypes: Arg %zu type encoding: %s", i - 2, argEncoding);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Build return FFI type
|
|
59
|
+
ffi_type* returnFFIType = GetFFITypeForEncoding(returnEncoding, outReturnSize, ctx.allocatedTypes);
|
|
60
|
+
NOBJC_LOG("PrepareFFIArgumentTypes: Return type encoding: %s, size: %zu",
|
|
61
|
+
returnEncoding, *outReturnSize);
|
|
62
|
+
|
|
63
|
+
return returnFFIType;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// MARK: - AddFixedFFIArguments
|
|
67
|
+
|
|
68
|
+
/// Add the fixed arguments (objc_super* and SEL) to the argument buffers.
|
|
69
|
+
inline void AddFixedFFIArguments(
|
|
70
|
+
struct objc_super* superPtr,
|
|
71
|
+
SEL selector,
|
|
72
|
+
FFIArgumentContext& ctx) {
|
|
73
|
+
|
|
74
|
+
// Add objc_super pointer
|
|
75
|
+
// libffi expects argValues[i] to point to the actual argument value
|
|
76
|
+
auto superPtrBuffer = std::make_unique<uint8_t[]>(sizeof(objc_super*));
|
|
77
|
+
memcpy(superPtrBuffer.get(), &superPtr, sizeof(objc_super*));
|
|
78
|
+
void* superPtrBufferRawPtr = superPtrBuffer.get();
|
|
79
|
+
ctx.argBuffers.push_back(std::move(superPtrBuffer));
|
|
80
|
+
ctx.argValues.push_back(superPtrBufferRawPtr);
|
|
81
|
+
NOBJC_LOG("AddFixedFFIArguments: Added objc_super* buffer at %p (points to %p)",
|
|
82
|
+
superPtrBufferRawPtr, superPtr);
|
|
83
|
+
|
|
84
|
+
// Add selector
|
|
85
|
+
auto selectorBuffer = std::make_unique<uint8_t[]>(sizeof(SEL));
|
|
86
|
+
memcpy(selectorBuffer.get(), &selector, sizeof(SEL));
|
|
87
|
+
void* selectorBufferRawPtr = selectorBuffer.get();
|
|
88
|
+
ctx.argBuffers.push_back(std::move(selectorBuffer));
|
|
89
|
+
ctx.argValues.push_back(selectorBufferRawPtr);
|
|
90
|
+
NOBJC_LOG("AddFixedFFIArguments: Added SEL buffer at %p (value=%p, name=%s)",
|
|
91
|
+
selectorBufferRawPtr, selector, sel_getName(selector));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// MARK: - ExtractOutParamArgument
|
|
95
|
+
|
|
96
|
+
/// Handle out-param arguments (e.g., NSError**).
|
|
97
|
+
/// Returns true if this was an out-param that was handled.
|
|
98
|
+
inline bool ExtractOutParamArgument(
|
|
99
|
+
const SimplifiedTypeEncoding& simpleArgEncoding,
|
|
100
|
+
size_t argIndex,
|
|
101
|
+
FFIArgumentContext& ctx) {
|
|
102
|
+
|
|
103
|
+
if (simpleArgEncoding[0] != '^' || simpleArgEncoding[1] != '@') {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
NOBJC_LOG("ExtractOutParamArgument: Arg %zu is out-param (^@)", argIndex);
|
|
108
|
+
|
|
109
|
+
// Buffer 1: Storage for the id* (initialized to nil)
|
|
110
|
+
auto errorStorage = std::make_unique<uint8_t[]>(sizeof(id));
|
|
111
|
+
id nullObj = nil;
|
|
112
|
+
memcpy(errorStorage.get(), &nullObj, sizeof(id));
|
|
113
|
+
void* errorStoragePtr = errorStorage.get();
|
|
114
|
+
|
|
115
|
+
NOBJC_LOG("ExtractOutParamArgument: Allocated error storage at %p", errorStoragePtr);
|
|
116
|
+
|
|
117
|
+
// Buffer 2: Storage for the pointer to errorStorage
|
|
118
|
+
auto pointerBuffer = std::make_unique<uint8_t[]>(sizeof(void*));
|
|
119
|
+
memcpy(pointerBuffer.get(), &errorStoragePtr, sizeof(void*));
|
|
120
|
+
void* pointerBufferPtr = pointerBuffer.get();
|
|
121
|
+
|
|
122
|
+
NOBJC_LOG("ExtractOutParamArgument: Allocated pointer buffer at %p", pointerBufferPtr);
|
|
123
|
+
|
|
124
|
+
ctx.argValues.push_back(pointerBufferPtr);
|
|
125
|
+
ctx.argBuffers.push_back(std::move(errorStorage));
|
|
126
|
+
ctx.argBuffers.push_back(std::move(pointerBuffer));
|
|
127
|
+
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// MARK: - ExtractRegularArgument
|
|
132
|
+
|
|
133
|
+
/// Extract a regular (non-out-param) argument from JS to buffer.
|
|
134
|
+
inline void ExtractRegularArgument(
|
|
135
|
+
Napi::Env env,
|
|
136
|
+
Napi::Value jsValue,
|
|
137
|
+
const char* argEncoding,
|
|
138
|
+
const SimplifiedTypeEncoding& simpleArgEncoding,
|
|
139
|
+
const std::string& className,
|
|
140
|
+
const std::string& selectorName,
|
|
141
|
+
size_t argIndex,
|
|
142
|
+
FFIArgumentContext& ctx) {
|
|
143
|
+
|
|
144
|
+
// Calculate size for this argument
|
|
145
|
+
size_t argSize = GetSizeForTypeEncoding(simpleArgEncoding[0]);
|
|
146
|
+
if (argSize == 0) {
|
|
147
|
+
// For complex types, use NSGetSizeAndAlignment
|
|
148
|
+
NSUInteger size, alignment;
|
|
149
|
+
NSGetSizeAndAlignment(argEncoding, &size, &alignment);
|
|
150
|
+
argSize = size;
|
|
151
|
+
NOBJC_LOG("ExtractRegularArgument: Complex type, size: %zu", argSize);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Allocate buffer
|
|
155
|
+
NOBJC_LOG("ExtractRegularArgument: Allocating buffer of %zu bytes for arg %zu",
|
|
156
|
+
argSize, argIndex);
|
|
157
|
+
auto buffer = std::make_unique<uint8_t[]>(argSize);
|
|
158
|
+
memset(buffer.get(), 0, argSize);
|
|
159
|
+
void* bufferPtr = buffer.get();
|
|
160
|
+
|
|
161
|
+
// Extract JS argument to buffer
|
|
162
|
+
ObjcArgumentContext context = {
|
|
163
|
+
.className = className,
|
|
164
|
+
.selectorName = selectorName,
|
|
165
|
+
.argumentIndex = (int)argIndex,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
ExtractJSArgumentToBuffer(env, jsValue, argEncoding, bufferPtr, context);
|
|
169
|
+
NOBJC_LOG("ExtractRegularArgument: Extracted argument %zu (size: %zu)", argIndex, argSize);
|
|
170
|
+
|
|
171
|
+
// For object types, log the actual pointer value
|
|
172
|
+
if (simpleArgEncoding[0] == '@') {
|
|
173
|
+
[[maybe_unused]] id* objPtr = (id*)bufferPtr;
|
|
174
|
+
NOBJC_LOG("ExtractRegularArgument: Argument %zu is object: buffer=%p, contains id=%p",
|
|
175
|
+
argIndex, bufferPtr, *objPtr);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
ctx.argValues.push_back(bufferPtr);
|
|
179
|
+
ctx.argBuffers.push_back(std::move(buffer));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// MARK: - ExtractMethodArguments
|
|
183
|
+
|
|
184
|
+
/// Extract all method arguments from JS values.
|
|
185
|
+
inline void ExtractMethodArguments(
|
|
186
|
+
Napi::Env env,
|
|
187
|
+
const Napi::CallbackInfo& info,
|
|
188
|
+
size_t argStartIndex,
|
|
189
|
+
NSMethodSignature* methodSig,
|
|
190
|
+
Class superClass,
|
|
191
|
+
const std::string& selectorName,
|
|
192
|
+
FFIArgumentContext& ctx) {
|
|
193
|
+
|
|
194
|
+
NOBJC_LOG("ExtractMethodArguments: Processing %zu method arguments...",
|
|
195
|
+
info.Length() - argStartIndex);
|
|
196
|
+
|
|
197
|
+
std::string className = class_getName(superClass);
|
|
198
|
+
|
|
199
|
+
for (size_t i = argStartIndex; i < info.Length(); i++) {
|
|
200
|
+
size_t argIndex = i - argStartIndex + 2; // +2 for self and _cmd
|
|
201
|
+
const char* argEncoding = [methodSig getArgumentTypeAtIndex:argIndex];
|
|
202
|
+
SimplifiedTypeEncoding simpleArgEncoding(argEncoding);
|
|
203
|
+
|
|
204
|
+
NOBJC_LOG("ExtractMethodArguments: Processing JS arg %zu (method arg %zu), encoding=%s",
|
|
205
|
+
i - argStartIndex, argIndex, argEncoding);
|
|
206
|
+
|
|
207
|
+
// Try out-param first
|
|
208
|
+
if (ExtractOutParamArgument(simpleArgEncoding, i - argStartIndex, ctx)) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Regular argument
|
|
213
|
+
ExtractRegularArgument(env, info[i], argEncoding, simpleArgEncoding,
|
|
214
|
+
className, selectorName, i - argStartIndex, ctx);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
NOBJC_LOG("ExtractMethodArguments: Finished preparing %zu argument buffers",
|
|
218
|
+
ctx.argBuffers.size());
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MARK: - LogFFICallSetup
|
|
222
|
+
|
|
223
|
+
/// Log the FFI call setup for debugging.
|
|
224
|
+
inline void LogFFICallSetup(
|
|
225
|
+
[[maybe_unused]] void* msgSendFn,
|
|
226
|
+
[[maybe_unused]] const std::vector<void*>& argValues,
|
|
227
|
+
[[maybe_unused]] const struct objc_super& superStruct,
|
|
228
|
+
[[maybe_unused]] Class superClass,
|
|
229
|
+
[[maybe_unused]] NSMethodSignature* methodSig) {
|
|
230
|
+
|
|
231
|
+
NOBJC_LOG("LogFFICallSetup: ========== FFI CALL SETUP ==========");
|
|
232
|
+
NOBJC_LOG("LogFFICallSetup: Function to call: objc_msgSendSuper at %p", msgSendFn);
|
|
233
|
+
NOBJC_LOG("LogFFICallSetup: Number of arguments: %zu", argValues.size());
|
|
234
|
+
|
|
235
|
+
if (argValues.size() > 0) {
|
|
236
|
+
NOBJC_LOG("LogFFICallSetup: Arg 0 (objc_super*): argValues[0]=%p", argValues[0]);
|
|
237
|
+
[[maybe_unused]] objc_super** superPtrPtr = (objc_super**)argValues[0];
|
|
238
|
+
NOBJC_LOG("LogFFICallSetup: Buffer contains pointer: %p", *superPtrPtr);
|
|
239
|
+
NOBJC_LOG("LogFFICallSetup: objc_super.receiver=%p", superStruct.receiver);
|
|
240
|
+
NOBJC_LOG("LogFFICallSetup: objc_super.super_class=%p (%s)",
|
|
241
|
+
superStruct.super_class, class_getName(superClass));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (argValues.size() > 1) {
|
|
245
|
+
NOBJC_LOG("LogFFICallSetup: Arg 1 (SEL*): argValues[1]=%p", argValues[1]);
|
|
246
|
+
[[maybe_unused]] SEL* selPtr = (SEL*)argValues[1];
|
|
247
|
+
NOBJC_LOG("LogFFICallSetup: Buffer contains SEL: %p (%s)",
|
|
248
|
+
*selPtr, sel_getName(*selPtr));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (size_t i = 2; i < argValues.size(); i++) {
|
|
252
|
+
const char* argEncoding = [methodSig getArgumentTypeAtIndex:i];
|
|
253
|
+
SimplifiedTypeEncoding simpleArgEncoding(argEncoding);
|
|
254
|
+
NOBJC_LOG("LogFFICallSetup: Arg %zu: argValues[%zu]=%p, encoding=%s",
|
|
255
|
+
i, i, argValues[i], simpleArgEncoding.c_str());
|
|
256
|
+
if (simpleArgEncoding[0] == '@') {
|
|
257
|
+
[[maybe_unused]] id* objPtrLocation = (id*)argValues[i];
|
|
258
|
+
NOBJC_LOG("LogFFICallSetup: Object pointer at %p points to id=%p",
|
|
259
|
+
objPtrLocation, *objPtrLocation);
|
|
260
|
+
} else if (simpleArgEncoding[0] == '^') {
|
|
261
|
+
[[maybe_unused]] void** ptrLocation = (void**)argValues[i];
|
|
262
|
+
NOBJC_LOG("LogFFICallSetup: Pointer at %p contains: %p",
|
|
263
|
+
ptrLocation, *ptrLocation);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// MARK: - ExecuteFFICallAndConvert
|
|
269
|
+
|
|
270
|
+
/// Execute the FFI call and convert the return value to JS.
|
|
271
|
+
inline Napi::Value ExecuteFFICallAndConvert(
|
|
272
|
+
Napi::Env env,
|
|
273
|
+
ffi_cif* cif,
|
|
274
|
+
void* msgSendFn,
|
|
275
|
+
FFIArgumentContext& ctx,
|
|
276
|
+
const char* returnEncoding,
|
|
277
|
+
size_t returnSize) {
|
|
278
|
+
|
|
279
|
+
// Prepare return buffer
|
|
280
|
+
std::unique_ptr<uint8_t[]> returnBuffer;
|
|
281
|
+
if (returnEncoding[0] != 'v') {
|
|
282
|
+
size_t bufferSize = returnSize > 0 ? returnSize : nobjc::kMinReturnBufferSize;
|
|
283
|
+
returnBuffer = std::make_unique<uint8_t[]>(bufferSize);
|
|
284
|
+
memset(returnBuffer.get(), 0, bufferSize);
|
|
285
|
+
NOBJC_LOG("ExecuteFFICallAndConvert: Allocated return buffer of %zu bytes at %p",
|
|
286
|
+
bufferSize, returnBuffer.get());
|
|
287
|
+
} else {
|
|
288
|
+
NOBJC_LOG("ExecuteFFICallAndConvert: No return buffer needed (void return)");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Make the FFI call
|
|
292
|
+
NOBJC_LOG("ExecuteFFICallAndConvert: About to call ffi_call...");
|
|
293
|
+
ffi_call(cif, FFI_FN(msgSendFn),
|
|
294
|
+
returnBuffer ? returnBuffer.get() : nullptr,
|
|
295
|
+
ctx.argValues.data());
|
|
296
|
+
NOBJC_LOG("ExecuteFFICallAndConvert: ffi_call completed successfully!");
|
|
297
|
+
|
|
298
|
+
// Convert return value
|
|
299
|
+
Napi::Value result;
|
|
300
|
+
if (returnEncoding[0] == 'v') {
|
|
301
|
+
result = env.Undefined();
|
|
302
|
+
} else {
|
|
303
|
+
result = ConvertFFIReturnToJS(env, returnBuffer.get(), returnEncoding);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
NOBJC_LOG("ExecuteFFICallAndConvert: Returning result");
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// MARK: - ValidateSuperMethod
|
|
311
|
+
|
|
312
|
+
/// Validate that the method exists on the superclass.
|
|
313
|
+
/// Returns the method signature on success, throws on failure.
|
|
314
|
+
inline NSMethodSignature* ValidateSuperMethod(
|
|
315
|
+
Napi::Env env,
|
|
316
|
+
Class superClass,
|
|
317
|
+
SEL selector,
|
|
318
|
+
const std::string& selectorName,
|
|
319
|
+
size_t providedArgCount) {
|
|
320
|
+
|
|
321
|
+
// Get method signature from superclass
|
|
322
|
+
NSMethodSignature* methodSig = [superClass instanceMethodSignatureForSelector:selector];
|
|
323
|
+
if (methodSig == nil) {
|
|
324
|
+
NOBJC_ERROR("ValidateSuperMethod: Selector '%s' not found on superclass %s",
|
|
325
|
+
selectorName.c_str(), class_getName(superClass));
|
|
326
|
+
throw Napi::Error::New(
|
|
327
|
+
env, "Selector '" + selectorName + "' not found on superclass");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
NOBJC_LOG("ValidateSuperMethod: Method signature: %s", [methodSig description].UTF8String);
|
|
331
|
+
|
|
332
|
+
// Get the super method's IMP directly
|
|
333
|
+
Method superMethod = class_getInstanceMethod(superClass, selector);
|
|
334
|
+
if (superMethod == nil) {
|
|
335
|
+
NOBJC_ERROR("ValidateSuperMethod: Could not get method implementation for selector '%s'",
|
|
336
|
+
selectorName.c_str());
|
|
337
|
+
throw Napi::Error::New(
|
|
338
|
+
env, "Could not get method implementation for selector '" + selectorName +
|
|
339
|
+
"' from superclass");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
NOBJC_LOG("ValidateSuperMethod: Found method implementation at %p",
|
|
343
|
+
method_getImplementation(superMethod));
|
|
344
|
+
|
|
345
|
+
// Validate argument count
|
|
346
|
+
const size_t expectedArgCount = [methodSig numberOfArguments] - 2;
|
|
347
|
+
|
|
348
|
+
NOBJC_LOG("ValidateSuperMethod: Expected %zu args, provided %zu args",
|
|
349
|
+
expectedArgCount, providedArgCount);
|
|
350
|
+
|
|
351
|
+
if (providedArgCount != expectedArgCount) {
|
|
352
|
+
NOBJC_ERROR("ValidateSuperMethod: Argument count mismatch for selector '%s'",
|
|
353
|
+
selectorName.c_str());
|
|
354
|
+
throw Napi::Error::New(
|
|
355
|
+
env, "Selector " + selectorName + " expected " +
|
|
356
|
+
std::to_string(expectedArgCount) + " argument(s), but got " +
|
|
357
|
+
std::to_string(providedArgCount));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return methodSig;
|
|
361
|
+
}
|