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