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 #include "JavaCallback.h"
9 #include "types/JNIToJSIConverter.h"
10 
11 #include <utility>
12 #include <functional>
13 
14 #include "JSReferencesCache.h"
15 
16 namespace jni = facebook::jni;
17 namespace jsi = facebook::jsi;
18 namespace react = facebook::react;
19 
20 namespace expo {
21 
22 // Modified version of the RN implementation
23 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
24 jni::local_ref<JavaCallback::JavaPart> createJavaCallbackFromJSIFunction(
25   jsi::Function &&function,
26   jsi::Runtime &rt,
27   JSIInteropModuleRegistry *moduleRegistry,
28   bool isRejectCallback = false
29 ) {
30   std::shared_ptr<react::CallInvoker> jsInvoker = moduleRegistry->runtimeHolder->jsInvoker;
31   auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
32                                                         std::move(jsInvoker));
33 
34   // This needs to be a shared_ptr because:
35   // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
36   // not.
37   // 2. It cannot be weak_ptr since we need this object to live on.
38   // 3. It cannot be a value, because that would be deleted as soon as this
39   // function returns.
40   auto callbackWrapperOwner =
41     std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
42 
43   std::function<void(folly::dynamic)> fn =
44     [
45       weakWrapper,
46       callbackWrapperOwner = std::move(callbackWrapperOwner),
47       wrapperWasCalled = false,
48       isRejectCallback
49     ](
50       folly::dynamic responses) mutable {
51       if (wrapperWasCalled) {
52         throw std::runtime_error(
53           "callback 2 arg cannot be called more than once");
54       }
55 
56       auto strongWrapper = weakWrapper.lock();
57       if (!strongWrapper) {
58         return;
59       }
60 
61       strongWrapper->jsInvoker().invokeAsync(
62         [
63           weakWrapper,
64           callbackWrapperOwner = std::move(callbackWrapperOwner),
65           responses = std::move(responses),
66           isRejectCallback
67         ]() mutable {
68           auto strongWrapper2 = weakWrapper.lock();
69           if (!strongWrapper2) {
70             return;
71           }
72 
73           jsi::Value arg = jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
74           if (!isRejectCallback) {
75             strongWrapper2->callback().call(
76               strongWrapper2->runtime(),
77               (const jsi::Value *) &arg,
78               (size_t) 1
79             );
80           } else {
81             auto &rt = strongWrapper2->runtime();
82             auto jsErrorObject = arg.getObject(rt);
83             auto errorCode = jsErrorObject.getProperty(rt, "code").asString(rt);
84             auto message = jsErrorObject.getProperty(rt, "message").asString(rt);
85 
86             auto codedError = makeCodedError(
87               rt,
88               std::move(errorCode),
89               std::move(message)
90             );
91 
92             strongWrapper2->callback().call(
93               strongWrapper2->runtime(),
94               (const jsi::Value *) &codedError,
95               (size_t) 1
96             );
97           }
98 
99           callbackWrapperOwner.reset();
100         });
101 
102       wrapperWasCalled = true;
103     };
104 
105   return JavaCallback::newObjectCxxArgs(std::move(fn));
106 }
107 
108 jobjectArray MethodMetadata::convertJSIArgsToJNI(
109   JSIInteropModuleRegistry *moduleRegistry,
110   JNIEnv *env,
111   jsi::Runtime &rt,
112   const jsi::Value &thisValue,
113   const jsi::Value *args,
114   size_t count
115 ) {
116   // This function takes the owner, so the args number is higher because we have access to the thisValue.
117   if (takesOwner) {
118     count++;
119   }
120 
121   // The `count < this->args` case is handled by the Kotlin part
122   if (count > this->args) {
123     throwNewJavaException(
124       InvalidArgsNumberException::create(
125         count,
126         this->args
127       ).get()
128     );
129   }
130 
131   auto argumentArray = env->NewObjectArray(
132     count,
133     JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
134     nullptr
135   );
136 
137   std::vector<jobject> result(count);
138 
139   const auto getCurrentArg = [&thisValue, args, takesOwner = takesOwner](
140     size_t index
141   ) -> const jsi::Value & {
142     if (!takesOwner) {
143       return args[index];
144     } else {
145       if (index != 0) {
146         return args[index - 1];
147       }
148       return thisValue;
149     }
150   };
151 
152   for (size_t argIndex = 0; argIndex < count; argIndex++) {
153     const jsi::Value &arg = getCurrentArg(argIndex);
154     auto &type = argTypes[argIndex];
155     if (arg.isNull() || arg.isUndefined()) {
156       // If value is null or undefined, we just passes a null
157       // Kotlin code will check if expected type is nullable.
158       result[argIndex] = nullptr;
159     } else {
160       if (type->converter->canConvert(rt, arg)) {
161         auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg);
162         env->SetObjectArrayElement(argumentArray, argIndex, converterValue);
163         env->DeleteLocalRef(converterValue);
164       } else {
165         auto stringRepresentation = arg.toString(rt).utf8(rt);
166         throwNewJavaException(
167           UnexpectedException::create(
168             "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
169         );
170       }
171     }
172   }
173 
174   return argumentArray;
175 }
176 
177 MethodMetadata::MethodMetadata(
178   std::string name,
179   bool takesOwner,
180   int args,
181   bool isAsync,
182   jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
183   jni::global_ref<jobject> &&jBodyReference
184 ) : name(std::move(name)),
185     takesOwner(takesOwner),
186     args(args),
187     isAsync(isAsync),
188     jBodyReference(std::move(jBodyReference)) {
189   argTypes.reserve(args);
190   for (size_t i = 0; i < args; i++) {
191     auto expectedType = expectedArgTypes->getElement(i);
192     argTypes.push_back(
193       std::make_unique<AnyType>(std::move(expectedType))
194     );
195   }
196 }
197 
198 MethodMetadata::MethodMetadata(
199   std::string name,
200   bool takesOwner,
201   int args,
202   bool isAsync,
203   std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes,
204   jni::global_ref<jobject> &&jBodyReference
205 ) : name(std::move(name)),
206     takesOwner(takesOwner),
207     args(args),
208     isAsync(isAsync),
209     argTypes(std::move(expectedArgTypes)),
210     jBodyReference(std::move(jBodyReference)) {
211 }
212 
213 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
214   jsi::Runtime &runtime,
215   JSIInteropModuleRegistry *moduleRegistry
216 ) {
217   if (body == nullptr) {
218     if (isAsync) {
219       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
220     } else {
221       body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
222     }
223   }
224 
225   return body;
226 }
227 
228 jsi::Function MethodMetadata::toSyncFunction(
229   jsi::Runtime &runtime,
230   JSIInteropModuleRegistry *moduleRegistry
231 ) {
232   return jsi::Function::createFromHostFunction(
233     runtime,
234     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
235     args,
236     [this, moduleRegistry](
237       jsi::Runtime &rt,
238       const jsi::Value &thisValue,
239       const jsi::Value *args,
240       size_t count
241     ) -> jsi::Value {
242       try {
243         return this->callSync(
244           rt,
245           moduleRegistry,
246           thisValue,
247           args,
248           count
249         );
250       } catch (jni::JniException &jniException) {
251         rethrowAsCodedError(rt, jniException);
252       }
253     });
254 }
255 
256 jni::local_ref<jobject> MethodMetadata::callJNISync(
257   JNIEnv *env,
258   jsi::Runtime &rt,
259   JSIInteropModuleRegistry *moduleRegistry,
260   const jsi::Value &thisValue,
261   const jsi::Value *args,
262   size_t count
263 ) {
264   if (this->jBodyReference == nullptr) {
265     return nullptr;
266   }
267 
268   auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
269 
270   // Cast in this place is safe, cause we know that this function is promise-less.
271   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
272   auto result = syncFunction->invoke(
273     convertedArgs
274   );
275 
276   env->DeleteLocalRef(convertedArgs);
277   return result;
278 }
279 
280 jsi::Value MethodMetadata::callSync(
281   jsi::Runtime &rt,
282   JSIInteropModuleRegistry *moduleRegistry,
283   const jsi::Value &thisValue,
284   const jsi::Value *args,
285   size_t count
286 ) {
287   JNIEnv *env = jni::Environment::current();
288   /**
289   * This will push a new JNI stack frame for the LocalReferences in this
290   * function call. When the stack frame for this lambda is popped,
291   * all LocalReferences are deleted.
292   */
293   jni::JniLocalScope scope(env, (int) count);
294 
295   auto result = this->callJNISync(env, rt, moduleRegistry, thisValue, args, count);
296   return convert(moduleRegistry, env, rt, std::move(result));
297 }
298 
299 jsi::Function MethodMetadata::toAsyncFunction(
300   jsi::Runtime &runtime,
301   JSIInteropModuleRegistry *moduleRegistry
302 ) {
303   return jsi::Function::createFromHostFunction(
304     runtime,
305     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
306     args,
307     [this, moduleRegistry](
308       jsi::Runtime &rt,
309       const jsi::Value &thisValue,
310       const jsi::Value *args,
311       size_t count
312     ) -> jsi::Value {
313       JNIEnv *env = jni::Environment::current();
314 
315       /**
316        * This will push a new JNI stack frame for the LocalReferences in this
317        * function call. When the stack frame for this lambda is popped,
318        * all LocalReferences are deleted.
319        */
320       jni::JniLocalScope scope(env, (int) count);
321 
322       auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
323         JSReferencesCache::JSKeys::PROMISE
324       );
325 
326       try {
327         auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
328         auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs);
329         env->DeleteLocalRef(convertedArgs);
330 
331         // Creates a JSI promise
332         jsi::Value promise = Promise.callAsConstructor(
333           rt,
334           createPromiseBody(rt, moduleRegistry, globalConvertedArgs)
335         );
336         return promise;
337       } catch (jni::JniException &jniException) {
338         jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
339         if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
340           unboxedThrowable = UnexpectedException::create(jniException.what());
341         }
342 
343         auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
344         auto code = codedException->getCode();
345         auto message = codedException->getLocalizedMessage().value_or("");
346 
347         jsi::Value promise = Promise.callAsConstructor(
348           rt,
349           jsi::Function::createFromHostFunction(
350             rt,
351             moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"),
352             2,
353             [code, message](
354               jsi::Runtime &rt,
355               const jsi::Value &thisVal,
356               const jsi::Value *promiseConstructorArgs,
357               size_t promiseConstructorArgCount
358             ) {
359               if (promiseConstructorArgCount != 2) {
360                 throw std::invalid_argument("Promise fn arg count must be 2");
361               }
362 
363               jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
364               rejectJSIFn.call(
365                 rt,
366                 makeCodedError(
367                   rt,
368                   jsi::String::createFromUtf8(rt, code),
369                   jsi::String::createFromUtf8(rt, message)
370                 )
371               );
372               return jsi::Value::undefined();
373             }
374           )
375         );
376 
377         return promise;
378       }
379     }
380   );
381 }
382 
383 jsi::Function MethodMetadata::createPromiseBody(
384   jsi::Runtime &runtime,
385   JSIInteropModuleRegistry *moduleRegistry,
386   jobjectArray globalArgs
387 ) {
388   return jsi::Function::createFromHostFunction(
389     runtime,
390     moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
391     2,
392     [this, globalArgs, moduleRegistry](
393       jsi::Runtime &rt,
394       const jsi::Value &thisVal,
395       const jsi::Value *promiseConstructorArgs,
396       size_t promiseConstructorArgCount
397     ) {
398       if (promiseConstructorArgCount != 2) {
399         throw std::invalid_argument("Promise fn arg count must be 2");
400       }
401 
402       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
403       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
404 
405       jobject resolve = createJavaCallbackFromJSIFunction(
406         std::move(resolveJSIFn),
407         rt,
408         moduleRegistry
409       ).release();
410 
411       jobject reject = createJavaCallbackFromJSIFunction(
412         std::move(rejectJSIFn),
413         rt,
414         moduleRegistry,
415         true
416       ).release();
417 
418       JNIEnv *env = jni::Environment::current();
419 
420       auto &jPromise = JavaReferencesCache::instance()->getJClass(
421         "expo/modules/kotlin/jni/PromiseImpl");
422       jmethodID jPromiseConstructor = jPromise.getMethod(
423         "<init>",
424         "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V"
425       );
426 
427       // Creates a promise object
428       jobject promise = env->NewObject(
429         jPromise.clazz,
430         jPromiseConstructor,
431         resolve,
432         reject
433       );
434 
435       // Cast in this place is safe, cause we know that this function expects promise.
436       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
437       asyncFunction->invoke(
438         globalArgs,
439         promise
440       );
441 
442       // We have to remove the local reference to the promise object.
443       // It doesn't mean that the promise will be deallocated, but rather that we move
444       // the ownership to the `JNIAsyncFunctionBody`.
445       env->DeleteLocalRef(promise);
446       env->DeleteGlobalRef(globalArgs);
447 
448       return jsi::Value::undefined();
449     }
450   );
451 }
452 } // namespace expo
453