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