1 #include "MethodMetadata.h"
2 #include "JSIInteropModuleRegistry.h"
3 #include "JavaScriptValue.h"
4 #include "JavaScriptObject.h"
5 #include "JavaScriptTypedArray.h"
6 #include "JavaReferencesCache.h"
7 #include "Exceptions.h"
8 
9 #include <utility>
10 
11 #include "react/jni/ReadableNativeMap.h"
12 #include "react/jni/ReadableNativeArray.h"
13 #include "JSReferencesCache.h"
14 
15 namespace jni = facebook::jni;
16 namespace jsi = facebook::jsi;
17 namespace react = facebook::react;
18 
19 namespace expo {
20 
21 // Modified version of the RN implementation
22 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
23 jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
24   jsi::Function &&function,
25   jsi::Runtime &rt,
26   std::shared_ptr<react::CallInvoker> jsInvoker
27 ) {
28   auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
29                                                         std::move(jsInvoker));
30 
31   // This needs to be a shared_ptr because:
32   // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
33   // not.
34   // 2. It cannot be weak_ptr since we need this object to live on.
35   // 3. It cannot be a value, because that would be deleted as soon as this
36   // function returns.
37   auto callbackWrapperOwner =
38     std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
39 
40   std::function<void(folly::dynamic)> fn =
41     [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false](
42       folly::dynamic responses) mutable {
43       if (wrapperWasCalled) {
44         throw std::runtime_error(
45           "callback 2 arg cannot be called more than once");
46       }
47 
48       auto strongWrapper = weakWrapper.lock();
49       if (!strongWrapper) {
50         return;
51       }
52 
53       strongWrapper->jsInvoker().invokeAsync(
54         [weakWrapper, callbackWrapperOwner, responses]() mutable {
55           auto strongWrapper2 = weakWrapper.lock();
56           if (!strongWrapper2) {
57             return;
58           }
59 
60           jsi::Value args =
61             jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
62           auto argsArray = args.getObject(strongWrapper2->runtime())
63             .asArray(strongWrapper2->runtime());
64           jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0);
65 
66           strongWrapper2->callback().call(
67             strongWrapper2->runtime(),
68             (const jsi::Value *) &arg,
69             (size_t) 1
70           );
71 
72           callbackWrapperOwner.reset();
73         });
74 
75       wrapperWasCalled = true;
76     };
77 
78   return react::JCxxCallbackImpl::newObjectCxxArgs(fn);
79 }
80 
81 jobjectArray MethodMetadata::convertJSIArgsToJNI(
82   JSIInteropModuleRegistry *moduleRegistry,
83   JNIEnv *env,
84   jsi::Runtime &rt,
85   const jsi::Value *args,
86   size_t count
87 ) {
88   auto argumentArray = env->NewObjectArray(
89     count,
90     JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
91     nullptr
92   );
93 
94   std::vector<jobject> result(count);
95 
96   for (unsigned int argIndex = 0; argIndex < count; argIndex++) {
97     const jsi::Value &arg = args[argIndex];
98     auto &type = argTypes[argIndex];
99     if (arg.isNull() || arg.isUndefined()) {
100       // If value is null or undefined, we just passes a null
101       // Kotlin code will check if expected type is nullable.
102       result[argIndex] = nullptr;
103     } else {
104       if (type->converter->canConvert(rt, arg)) {
105         auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg);
106         env->SetObjectArrayElement(argumentArray, argIndex, converterValue);
107         env->DeleteLocalRef(converterValue);
108       } else {
109         auto stringRepresentation = arg.toString(rt).utf8(rt);
110         jni::throwNewJavaException(
111           UnexpectedException::create(
112             "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
113         );
114       }
115     }
116   }
117 
118   return argumentArray;
119 }
120 
121 MethodMetadata::MethodMetadata(
122   std::string name,
123   int args,
124   bool isAsync,
125   jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
126   jni::global_ref<jobject> &&jBodyReference
127 ) : name(std::move(name)),
128     args(args),
129     isAsync(isAsync),
130     jBodyReference(std::move(jBodyReference)) {
131   argTypes.reserve(args);
132   for (size_t i = 0; i < args; i++) {
133     auto expectedType = expectedArgTypes->getElement(i);
134     argTypes.push_back(
135       std::make_unique<AnyType>(std::move(expectedType))
136     );
137   }
138 }
139 
140 MethodMetadata::MethodMetadata(
141   std::string name,
142   int args,
143   bool isAsync,
144   std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes,
145   jni::global_ref<jobject> &&jBodyReference
146 ) : name(std::move(name)),
147     args(args),
148     isAsync(isAsync),
149     argTypes(std::move(expectedArgTypes)),
150     jBodyReference(std::move(jBodyReference)
151     ) {}
152 
153 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
154   jsi::Runtime &runtime,
155   JSIInteropModuleRegistry *moduleRegistry
156 ) {
157   if (body == nullptr) {
158     if (isAsync) {
159       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
160     } else {
161       body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
162     }
163   }
164 
165   return body;
166 }
167 
168 jsi::Function MethodMetadata::toSyncFunction(
169   jsi::Runtime &runtime,
170   JSIInteropModuleRegistry *moduleRegistry
171 ) {
172   return jsi::Function::createFromHostFunction(
173     runtime,
174     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
175     args,
176     [this, moduleRegistry](
177       jsi::Runtime &rt,
178       const jsi::Value &thisValue,
179       const jsi::Value *args,
180       size_t count
181     ) -> jsi::Value {
182       try {
183         return this->callSync(
184           rt,
185           moduleRegistry,
186           args,
187           count
188         );
189       } catch (jni::JniException &jniException) {
190         rethrowAsCodedError(rt, moduleRegistry, jniException);
191       }
192     });
193 }
194 
195 jsi::Value MethodMetadata::callSync(
196   jsi::Runtime &rt,
197   JSIInteropModuleRegistry *moduleRegistry,
198   const jsi::Value *args,
199   size_t count
200 ) {
201   if (this->jBodyReference == nullptr) {
202     return jsi::Value::undefined();
203   }
204 
205   JNIEnv *env = jni::Environment::current();
206 
207   /**
208    * This will push a new JNI stack frame for the LocalReferences in this
209    * function call. When the stack frame for this lambda is popped,
210    * all LocalReferences are deleted.
211    */
212   jni::JniLocalScope scope(env, (int) count);
213 
214   auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count);
215 
216   // Cast in this place is safe, cause we know that this function is promise-less.
217   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
218   auto result = syncFunction->invoke(
219     convertedArgs
220   );
221 
222   env->DeleteLocalRef(convertedArgs);
223   if (result == nullptr) {
224     return jsi::Value::undefined();
225   }
226 
227   return jsi::valueFromDynamic(rt, result->cthis()->consume())
228     .asObject(rt)
229     .asArray(rt)
230     .getValueAtIndex(rt, 0);
231 }
232 
233 jsi::Function MethodMetadata::toAsyncFunction(
234   jsi::Runtime &runtime,
235   JSIInteropModuleRegistry *moduleRegistry
236 ) {
237   return jsi::Function::createFromHostFunction(
238     runtime,
239     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
240     args,
241     [this, moduleRegistry](
242       jsi::Runtime &rt,
243       const jsi::Value &thisValue,
244       const jsi::Value *args,
245       size_t count
246     ) -> jsi::Value {
247       JNIEnv *env = jni::Environment::current();
248 
249       /**
250        * This will push a new JNI stack frame for the LocalReferences in this
251        * function call. When the stack frame for this lambda is popped,
252        * all LocalReferences are deleted.
253        */
254       jni::JniLocalScope scope(env, (int) count);
255 
256       try {
257         auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count);
258         auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs);
259         env->DeleteLocalRef(convertedArgs);
260 
261         auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
262           JSReferencesCache::JSKeys::PROMISE
263         );
264         // Creates a JSI promise
265         jsi::Value promise = Promise.callAsConstructor(
266           rt,
267           createPromiseBody(rt, moduleRegistry, globalConvertedArgs)
268         );
269         return promise;
270       } catch (jni::JniException &jniException) {
271         rethrowAsCodedError(rt, moduleRegistry, jniException);
272       }
273     }
274   );
275 }
276 
277 jsi::Function MethodMetadata::createPromiseBody(
278   jsi::Runtime &runtime,
279   JSIInteropModuleRegistry *moduleRegistry,
280   jobjectArray globalArgs
281 ) {
282   return jsi::Function::createFromHostFunction(
283     runtime,
284     moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
285     2,
286     [this, globalArgs, moduleRegistry](
287       jsi::Runtime &rt,
288       const jsi::Value &thisVal,
289       const jsi::Value *promiseConstructorArgs,
290       size_t promiseConstructorArgCount
291     ) {
292       if (promiseConstructorArgCount != 2) {
293         throw std::invalid_argument("Promise fn arg count must be 2");
294       }
295 
296       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
297       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
298 
299       auto &runtimeHolder = moduleRegistry->runtimeHolder;
300       jobject resolve = createJavaCallbackFromJSIFunction(
301         std::move(resolveJSIFn),
302         rt,
303         runtimeHolder->jsInvoker
304       ).release();
305 
306       jobject reject = createJavaCallbackFromJSIFunction(
307         std::move(rejectJSIFn),
308         rt,
309         runtimeHolder->jsInvoker
310       ).release();
311 
312       JNIEnv *env = jni::Environment::current();
313 
314       auto &jPromise = JavaReferencesCache::instance()->getJClass(
315         "com/facebook/react/bridge/PromiseImpl");
316       jmethodID jPromiseConstructor = jPromise.getMethod(
317         "<init>",
318         "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"
319       );
320 
321       // Creates a promise object
322       jobject promise = env->NewObject(
323         jPromise.clazz,
324         jPromiseConstructor,
325         resolve,
326         reject
327       );
328 
329       // Cast in this place is safe, cause we know that this function expects promise.
330       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
331       asyncFunction->invoke(
332         globalArgs,
333         promise
334       );
335 
336       // We have to remove the local reference to the promise object.
337       // It doesn't mean that the promise will be deallocated, but rather that we move
338       // the ownership to the `JNIAsyncFunctionBody`.
339       env->DeleteLocalRef(promise);
340       env->DeleteGlobalRef(globalArgs);
341 
342       return jsi::Value::undefined();
343     }
344   );
345 }
346 } // namespace expo
347