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   auto argumentArray = env->NewObjectArray(
145     count,
146     JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
147     nullptr
148   );
149 
150   std::vector<jobject> result(count);
151 
152   const auto getCurrentArg = [&thisValue, args, takesOwner = takesOwner](
153     size_t index
154   ) -> const jsi::Value & {
155     if (!takesOwner) {
156       return args[index];
157     } else {
158       if (index != 0) {
159         return args[index - 1];
160       }
161       return thisValue;
162     }
163   };
164 
165   for (size_t argIndex = 0; argIndex < count; argIndex++) {
166     const jsi::Value &arg = getCurrentArg(argIndex);
167     auto &type = argTypes[argIndex];
168     if (arg.isNull() || arg.isUndefined()) {
169       // If value is null or undefined, we just passes a null
170       // Kotlin code will check if expected type is nullable.
171       result[argIndex] = nullptr;
172     } else {
173       if (type->converter->canConvert(rt, arg)) {
174         auto converterValue = type->converter->convert(rt, env, moduleRegistry, arg);
175         env->SetObjectArrayElement(argumentArray, argIndex, converterValue);
176         env->DeleteLocalRef(converterValue);
177       } else {
178         auto stringRepresentation = arg.toString(rt).utf8(rt);
179         throwNewJavaException(
180           UnexpectedException::create(
181             "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
182         );
183       }
184     }
185   }
186 
187   return argumentArray;
188 }
189 
190 MethodMetadata::MethodMetadata(
191   std::weak_ptr<react::LongLivedObjectCollection> longLivedObjectCollection,
192   std::string name,
193   bool takesOwner,
194   int args,
195   bool isAsync,
196   jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
197   jni::global_ref<jobject> &&jBodyReference
198 ) : name(std::move(name)),
199     takesOwner(takesOwner),
200     args(args),
201     isAsync(isAsync),
202     jBodyReference(std::move(jBodyReference)),
203     longLivedObjectCollection_(std::move(longLivedObjectCollection)) {
204   argTypes.reserve(args);
205   for (size_t i = 0; i < args; i++) {
206     auto expectedType = expectedArgTypes->getElement(i);
207     argTypes.push_back(
208       std::make_unique<AnyType>(std::move(expectedType))
209     );
210   }
211 }
212 
213 MethodMetadata::MethodMetadata(
214   std::weak_ptr<react::LongLivedObjectCollection> longLivedObjectCollection,
215   std::string name,
216   bool takesOwner,
217   int args,
218   bool isAsync,
219   std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes,
220   jni::global_ref<jobject> &&jBodyReference
221 ) : name(std::move(name)),
222     takesOwner(takesOwner),
223     args(args),
224     isAsync(isAsync),
225     argTypes(std::move(expectedArgTypes)),
226     jBodyReference(std::move(jBodyReference)),
227     longLivedObjectCollection_(std::move(longLivedObjectCollection)) {
228 }
229 
230 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
231   jsi::Runtime &runtime,
232   JSIInteropModuleRegistry *moduleRegistry
233 ) {
234   if (body == nullptr) {
235     if (isAsync) {
236       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
237     } else {
238       body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
239     }
240   }
241 
242   return body;
243 }
244 
245 jsi::Function MethodMetadata::toSyncFunction(
246   jsi::Runtime &runtime,
247   JSIInteropModuleRegistry *moduleRegistry
248 ) {
249   return jsi::Function::createFromHostFunction(
250     runtime,
251     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
252     args,
253     [this, moduleRegistry](
254       jsi::Runtime &rt,
255       const jsi::Value &thisValue,
256       const jsi::Value *args,
257       size_t count
258     ) -> jsi::Value {
259       try {
260         return this->callSync(
261           rt,
262           moduleRegistry,
263           thisValue,
264           args,
265           count
266         );
267       } catch (jni::JniException &jniException) {
268         rethrowAsCodedError(rt, jniException);
269       }
270     });
271 }
272 
273 jsi::Value MethodMetadata::callSync(
274   jsi::Runtime &rt,
275   JSIInteropModuleRegistry *moduleRegistry,
276   const jsi::Value &thisValue,
277   const jsi::Value *args,
278   size_t count
279 ) {
280   if (this->jBodyReference == nullptr) {
281     return jsi::Value::undefined();
282   }
283 
284   JNIEnv *env = jni::Environment::current();
285 
286   /**
287    * This will push a new JNI stack frame for the LocalReferences in this
288    * function call. When the stack frame for this lambda is popped,
289    * all LocalReferences are deleted.
290    */
291   jni::JniLocalScope scope(env, (int) count);
292 
293   auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
294 
295   // Cast in this place is safe, cause we know that this function is promise-less.
296   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
297   auto result = syncFunction->invoke(
298     convertedArgs
299   );
300 
301   env->DeleteLocalRef(convertedArgs);
302   if (result == nullptr) {
303     return jsi::Value::undefined();
304   }
305   auto unpackedResult = result.get();
306   auto cache = JavaReferencesCache::instance();
307   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Double").clazz)) {
308     return {jni::static_ref_cast<jni::JDouble>(result)->value()};
309   }
310   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Integer").clazz)) {
311     return {jni::static_ref_cast<jni::JInteger>(result)->value()};
312   }
313   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Long").clazz)) {
314     return {(double) jni::static_ref_cast<jni::JLong>(result)->value()};
315   }
316   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/String").clazz)) {
317     return jsi::String::createFromUtf8(
318       rt,
319       jni::static_ref_cast<jni::JString>(result)->toStdString()
320     );
321   }
322   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Boolean").clazz)) {
323     return {(bool) jni::static_ref_cast<jni::JBoolean>(result)->value()};
324   }
325   if (env->IsInstanceOf(unpackedResult, cache->getJClass("java/lang/Float").clazz)) {
326     return {(double) jni::static_ref_cast<jni::JFloat>(result)->value()};
327   }
328   if (env->IsInstanceOf(
329     unpackedResult,
330     cache->getJClass("com/facebook/react/bridge/WritableNativeArray").clazz
331   )) {
332     auto dynamic = jni::static_ref_cast<react::WritableNativeArray::javaobject>(result)
333       ->cthis()
334       ->consume();
335     return jsi::valueFromDynamic(rt, dynamic);
336   }
337   if (env->IsInstanceOf(
338     unpackedResult,
339     cache->getJClass("com/facebook/react/bridge/WritableNativeMap").clazz
340   )) {
341     auto dynamic = jni::static_ref_cast<react::WritableNativeMap::javaobject>(result)
342       ->cthis()
343       ->consume();
344     return jsi::valueFromDynamic(rt, dynamic);
345   }
346   if (env->IsInstanceOf(unpackedResult, JavaScriptModuleObject::javaClassStatic().get())) {
347     auto anonymousObject = jni::static_ref_cast<JavaScriptModuleObject::javaobject>(result)
348       ->cthis();
349     anonymousObject->jsiInteropModuleRegistry = moduleRegistry;
350     auto jsiObject = anonymousObject->getJSIObject(rt);
351 
352     jni::global_ref<jobject> globalRef = jni::make_global(result);
353     std::shared_ptr<expo::ObjectDeallocator> deallocator = std::make_shared<ObjectDeallocator>(
354       [globalRef = globalRef]() mutable {
355         globalRef.reset();
356       });
357 
358     auto descriptor = JavaScriptObject::preparePropertyDescriptor(rt, 0);
359     descriptor.setProperty(rt, "value", jsi::Object::createFromHostObject(rt, deallocator));
360     JavaScriptObject::defineProperty(rt, jsiObject.get(), "__expo_object_deallocator__",
361                                      std::move(descriptor));
362 
363     return jsi::Value(rt, *jsiObject);
364   }
365 
366   return jsi::Value::undefined();
367 }
368 
369 jsi::Function MethodMetadata::toAsyncFunction(
370   jsi::Runtime &runtime,
371   JSIInteropModuleRegistry *moduleRegistry
372 ) {
373   return jsi::Function::createFromHostFunction(
374     runtime,
375     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
376     args,
377     [this, moduleRegistry](
378       jsi::Runtime &rt,
379       const jsi::Value &thisValue,
380       const jsi::Value *args,
381       size_t count
382     ) -> jsi::Value {
383       JNIEnv *env = jni::Environment::current();
384 
385       /**
386        * This will push a new JNI stack frame for the LocalReferences in this
387        * function call. When the stack frame for this lambda is popped,
388        * all LocalReferences are deleted.
389        */
390       jni::JniLocalScope scope(env, (int) count);
391 
392       auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
393         JSReferencesCache::JSKeys::PROMISE
394       );
395 
396       try {
397         auto convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, thisValue, args, count);
398         auto globalConvertedArgs = (jobjectArray) env->NewGlobalRef(convertedArgs);
399         env->DeleteLocalRef(convertedArgs);
400 
401         // Creates a JSI promise
402         jsi::Value promise = Promise.callAsConstructor(
403           rt,
404           createPromiseBody(rt, moduleRegistry, globalConvertedArgs)
405         );
406         return promise;
407       } catch (jni::JniException &jniException) {
408         jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
409         if (!unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
410           unboxedThrowable = UnexpectedException::create(jniException.what());
411         }
412 
413         auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
414         auto code = codedException->getCode();
415         auto message = codedException->getLocalizedMessage().value_or("");
416 
417         jsi::Value promise = Promise.callAsConstructor(
418           rt,
419           jsi::Function::createFromHostFunction(
420             rt,
421             moduleRegistry->jsRegistry->getPropNameID(rt, "promiseFn"),
422             2,
423             [code, message](
424               jsi::Runtime &rt,
425               const jsi::Value &thisVal,
426               const jsi::Value *promiseConstructorArgs,
427               size_t promiseConstructorArgCount
428             ) {
429               if (promiseConstructorArgCount != 2) {
430                 throw std::invalid_argument("Promise fn arg count must be 2");
431               }
432 
433               jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
434               rejectJSIFn.call(
435                 rt,
436                 makeCodedError(
437                   rt,
438                   jsi::String::createFromUtf8(rt, code),
439                   jsi::String::createFromUtf8(rt, message)
440                 )
441               );
442               return jsi::Value::undefined();
443             }
444           )
445         );
446 
447         return promise;
448       }
449     }
450   );
451 }
452 
453 jsi::Function MethodMetadata::createPromiseBody(
454   jsi::Runtime &runtime,
455   JSIInteropModuleRegistry *moduleRegistry,
456   jobjectArray globalArgs
457 ) {
458   return jsi::Function::createFromHostFunction(
459     runtime,
460     moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
461     2,
462     [this, globalArgs, moduleRegistry](
463       jsi::Runtime &rt,
464       const jsi::Value &thisVal,
465       const jsi::Value *promiseConstructorArgs,
466       size_t promiseConstructorArgCount
467     ) {
468       if (promiseConstructorArgCount != 2) {
469         throw std::invalid_argument("Promise fn arg count must be 2");
470       }
471 
472       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
473       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
474 
475       jobject resolve = createJavaCallbackFromJSIFunction(
476         std::move(resolveJSIFn),
477         longLivedObjectCollection_,
478         rt,
479         moduleRegistry
480       ).release();
481 
482       jobject reject = createJavaCallbackFromJSIFunction(
483         std::move(rejectJSIFn),
484         longLivedObjectCollection_,
485         rt,
486         moduleRegistry,
487         true
488       ).release();
489 
490       JNIEnv *env = jni::Environment::current();
491 
492       auto &jPromise = JavaReferencesCache::instance()->getJClass(
493         "expo/modules/kotlin/jni/PromiseImpl");
494       jmethodID jPromiseConstructor = jPromise.getMethod(
495         "<init>",
496         "(Lexpo/modules/kotlin/jni/JavaCallback;Lexpo/modules/kotlin/jni/JavaCallback;)V"
497       );
498 
499       // Creates a promise object
500       jobject promise = env->NewObject(
501         jPromise.clazz,
502         jPromiseConstructor,
503         resolve,
504         reject
505       );
506 
507       // Cast in this place is safe, cause we know that this function expects promise.
508       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
509       asyncFunction->invoke(
510         globalArgs,
511         promise
512       );
513 
514       // We have to remove the local reference to the promise object.
515       // It doesn't mean that the promise will be deallocated, but rather that we move
516       // the ownership to the `JNIAsyncFunctionBody`.
517       env->DeleteLocalRef(promise);
518       env->DeleteGlobalRef(globalArgs);
519 
520       return jsi::Value::undefined();
521     }
522   );
523 }
524 } // namespace expo
525