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