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