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 jni::local_ref<jobject> MethodMetadata::callJNISync(
284   JNIEnv *env,
285   jsi::Runtime &rt,
286   JSIInteropModuleRegistry *moduleRegistry,
287   const jsi::Value &thisValue,
288   const jsi::Value *args,
289   size_t count
290 ) {
291   if (this->jBodyReference == nullptr) {
292     return nullptr;
293   }
294 
295   auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
296 
297   // Cast in this place is safe, cause we know that this function is promise-less.
298   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
299   auto result = syncFunction->invoke(
300     convertedArgs
301   );
302 
303   env->DeleteLocalRef(convertedArgs);
304   return result;
305 }
306 
307 jsi::Value MethodMetadata::callSync(
308   jsi::Runtime &rt,
309   JSIInteropModuleRegistry *moduleRegistry,
310   const jsi::Value &thisValue,
311   const jsi::Value *args,
312   size_t count
313 ) {
314   JNIEnv *env = jni::Environment::current();
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 result = this->callJNISync(env, rt, moduleRegistry, thisValue, args, count);
323 
324   if (result == nullptr) {
325     return jsi::Value::undefined();
326   }
327   auto unpackedResult = result.get();
328   auto cache = JavaReferencesCache::instance();
329   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Double").clazz)) {
330     return {jni::static_ref_cast<jni::JDouble>(result)->value()};
331   }
332   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Integer").clazz)) {
333     return {jni::static_ref_cast<jni::JInteger>(result)->value()};
334   }
335   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Long").clazz)) {
336     return {(double) jni::static_ref_cast<jni::JLong>(result)->value()};
337   }
338   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/String").clazz)) {
339     return jsi::String::createFromUtf8(
340       rt,
341       jni::static_ref_cast<jni::JString>(result)->toStdString()
342     );
343   }
344   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Boolean").clazz)) {
345     return {(bool) jni::static_ref_cast<jni::JBoolean>(result)->value()};
346   }
347   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Float").clazz)) {
348     return {(double) jni::static_ref_cast<jni::JFloat>(result)->value()};
349   }
350   if (env->IsInstanceOf(
351     unpackedResult,
352     cache->getJClass("com/facebook/react/bridge/WritableNativeArray").clazz
353   )) {
354     auto dynamic = jni::static_ref_cast<react::WritableNativeArray::javaobject>(result)
355       ->cthis()
356       ->consume();
357     return jsi::valueFromDynamic(rt, dynamic);
358   }
359   if (env->IsInstanceOf(
360     unpackedResult,
361     cache->getJClass("com/facebook/react/bridge/WritableNativeMap").clazz
362   )) {
363     auto dynamic = jni::static_ref_cast<react::WritableNativeMap::javaobject>(result)
364       ->cthis()
365       ->consume();
366     return jsi::valueFromDynamic(rt, dynamic);
367   }
368   if (env->IsInstanceOf(unpackedResult, JavaScriptModuleObject::javaClassStatic().get())) {
369     auto anonymousObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>(result)
370       ->cthis();
371     anonymousObject->jsiInteropModuleRegistry = moduleRegistry;
372     auto jsiObject = anonymousObject->getJSIObject(rt);
373 
374     jni::global_ref<jobject> globalRef = jni::make_global(result);
375     std::shared_ptr<expo::ObjectDeallocator> deallocator = std::make_shared<ObjectDeallocator>(
376       [globalRef = globalRef]() mutable {
377         globalRef.reset();
378       });
379 
380     auto descriptor = JavaScriptObject::preparePropertyDescriptor(rt, 0);
381     descriptor.setProperty(rt, "value", jsi::Object::createFromHostObject(rt, deallocator));
382     JavaScriptObject::defineProperty(rt, jsiObject.get(), "__expo_object_deallocator__",
383                                      std::move(descriptor));
384 
385     return jsi::Value(rt, *jsiObject);
386   }
387 
388   return jsi::Value::undefined();
389 }
390 
391 jsi::Function MethodMetadata::toAsyncFunction(
392   jsi::Runtime &runtime,
393   JSIInteropModuleRegistry *moduleRegistry
394 ) {
395   return jsi::Function::createFromHostFunction(
396     runtime,
397     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
398     args,
399     [this, moduleRegistry](
400       jsi::Runtime &rt,
401       const jsi::Value &thisValue,
402       const jsi::Value *args,
403       size_t count
404     ) -> jsi::Value {
405       JNIEnv *env = jni::Environment::current();
406 
407       /**
408        * This will push a new JNI stack frame for the LocalReferences in this
409        * function call. When the stack frame for this lambda is popped,
410        * all LocalReferences are deleted.
411        */
412       jni::JniLocalScope scope(env, (int) count);
413 
414       auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
415         JSReferencesCache::JSKeys::PROMISE
416       );
417 
418       try {
419         auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
420         auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs);
421         env->DeleteLocalRef(convertedArgs);
422 
423         // Creates a JSI promise
424         jsi::Value promise = Promise.callAsConstructor(
425           rt,
426           createPromiseBody(rt, moduleRegistry, globalConvertedArgs)
427         );
428         return promise;
429       } catch (jni::JniException &jniException) {
430         jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
431         if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
432           unboxedThrowable = UnexpectedException::create(jniException.what());
433         }
434 
435         auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
436         auto code = codedException->getCode();
437         auto message = codedException->getLocalizedMessage().value_or("");
438 
439         jsi::Value promise = Promise.callAsConstructor(
440           rt,
441           jsi::Function::createFromHostFunction(
442             rt,
443             moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"),
444             2,
445             [code, message](
446               jsi::Runtime &rt,
447               const jsi::Value &thisVal,
448               const jsi::Value *promiseConstructorArgs,
449               size_t promiseConstructorArgCount
450             ) {
451               if (promiseConstructorArgCount != 2) {
452                 throw std::invalid_argument("Promise fn arg count must be 2");
453               }
454 
455               jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
456               rejectJSIFn.call(
457                 rt,
458                 makeCodedError(
459                   rt,
460                   jsi::String::createFromUtf8(rt, code),
461                   jsi::String::createFromUtf8(rt, message)
462                 )
463               );
464               return jsi::Value::undefined();
465             }
466           )
467         );
468 
469         return promise;
470       }
471     }
472   );
473 }
474 
475 jsi::Function MethodMetadata::createPromiseBody(
476   jsi::Runtime &runtime,
477   JSIInteropModuleRegistry *moduleRegistry,
478   jobjectArray globalArgs
479 ) {
480   return jsi::Function::createFromHostFunction(
481     runtime,
482     moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
483     2,
484     [this, globalArgs, moduleRegistry](
485       jsi::Runtime &rt,
486       const jsi::Value &thisVal,
487       const jsi::Value *promiseConstructorArgs,
488       size_t promiseConstructorArgCount
489     ) {
490       if (promiseConstructorArgCount != 2) {
491         throw std::invalid_argument("Promise fn arg count must be 2");
492       }
493 
494       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
495       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
496 
497       jobject resolve = createJavaCallbackFromJSIFunction(
498         std::move(resolveJSIFn),
499         longLivedObjectCollection_,
500         rt,
501         moduleRegistry
502       ).release();
503 
504       jobject reject = createJavaCallbackFromJSIFunction(
505         std::move(rejectJSIFn),
506         longLivedObjectCollection_,
507         rt,
508         moduleRegistry,
509         true
510       ).release();
511 
512       JNIEnv *env = jni::Environment::current();
513 
514       auto &jPromise = JavaReferencesCache::instance()->getJClass(
515         "expo/modules/kotlin/jni/PromiseImpl");
516       jmethodID jPromiseConstructor = jPromise.getMethod(
517         "<init>",
518         "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V"
519       );
520 
521       // Creates a promise object
522       jobject promise = env->NewObject(
523         jPromise.clazz,
524         jPromiseConstructor,
525         resolve,
526         reject
527       );
528 
529       // Cast in this place is safe, cause we know that this function expects promise.
530       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
531       asyncFunction->invoke(
532         globalArgs,
533         promise
534       );
535 
536       // We have to remove the local reference to the promise object.
537       // It doesn't mean that the promise will be deallocated, but rather that we move
538       // the ownership to the `JNIAsyncFunctionBody`.
539       env->DeleteLocalRef(promise);
540       env->DeleteGlobalRef(globalArgs);
541 
542       return jsi::Value::undefined();
543     }
544   );
545 }
546 } // namespace expo
547