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 
9 #include <utility>
10 
11 #include "react/jni/ReadableNativeMap.h"
12 #include "react/jni/ReadableNativeArray.h"
13 #include "JSReferencesCache.h"
14 
15 namespace jni = facebook::jni;
16 namespace jsi = facebook::jsi;
17 namespace react = facebook::react;
18 
19 namespace expo {
20 
21 // Modified version of the RN implementation
22 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
23 jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
24   jsi::Function &&function,
25   jsi::Runtime &rt,
26   std::shared_ptr<react::CallInvoker> jsInvoker
27 ) {
28   auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
29                                                         std::move(jsInvoker));
30 
31   // This needs to be a shared_ptr because:
32   // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
33   // not.
34   // 2. It cannot be weak_ptr since we need this object to live on.
35   // 3. It cannot be a value, because that would be deleted as soon as this
36   // function returns.
37   auto callbackWrapperOwner =
38     std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
39 
40   std::function<void(folly::dynamic)> fn =
41     [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false](
42       folly::dynamic responses) mutable {
43       if (wrapperWasCalled) {
44         throw std::runtime_error(
45           "callback 2 arg cannot be called more than once");
46       }
47 
48       auto strongWrapper = weakWrapper.lock();
49       if (!strongWrapper) {
50         return;
51       }
52 
53       strongWrapper->jsInvoker().invokeAsync(
54         [weakWrapper, callbackWrapperOwner, responses]() mutable {
55           auto strongWrapper2 = weakWrapper.lock();
56           if (!strongWrapper2) {
57             return;
58           }
59 
60           jsi::Value args =
61             jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
62           auto argsArray = args.getObject(strongWrapper2->runtime())
63             .asArray(strongWrapper2->runtime());
64           jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0);
65 
66           strongWrapper2->callback().call(
67             strongWrapper2->runtime(),
68             (const jsi::Value *) &arg,
69             (size_t) 1
70           );
71 
72           callbackWrapperOwner.reset();
73         });
74 
75       wrapperWasCalled = true;
76     };
77 
78   return react::JCxxCallbackImpl::newObjectCxxArgs(fn);
79 }
80 
81 std::vector<jobject> MethodMetadata::convertJSIArgsToJNI(
82   JSIInteropModuleRegistry *moduleRegistry,
83   JNIEnv *env,
84   jsi::Runtime &rt,
85   const jsi::Value *args,
86   size_t count,
87   bool returnGlobalReferences
88 ) {
89   std::vector<jobject> result(count);
90 
91   auto makeGlobalIfNecessary = [env, returnGlobalReferences](jobject obj) -> jobject {
92     if (returnGlobalReferences) {
93       return env->NewGlobalRef(obj);
94     }
95     return obj;
96   };
97 
98   for (unsigned int argIndex = 0; argIndex < count; argIndex++) {
99     const jsi::Value &arg = args[argIndex];
100     auto &type = argTypes[argIndex];
101     if (arg.isNull() || arg.isUndefined()) {
102       // If value is null or undefined, we just passes a null
103       // Kotlin code will check if expected type is nullable.
104       result[argIndex] = nullptr;
105     } else {
106       if (type->converter->canConvert(rt, arg)) {
107         result[argIndex] = makeGlobalIfNecessary(
108           type->converter->convert(rt, env, moduleRegistry, arg)
109         );
110       } else {
111         auto stringRepresentation = arg.toString(rt).utf8(rt);
112         jni::throwNewJavaException(
113           UnexpectedException::create(
114             "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
115         );
116       }
117     }
118   }
119 
120   return result;
121 }
122 
123 MethodMetadata::MethodMetadata(
124   std::string name,
125   int args,
126   bool isAsync,
127   jni::local_ref<jni::JArrayClass<ExpectedType>> expectedArgTypes,
128   jni::global_ref<jobject> &&jBodyReference
129 ) : name(std::move(name)),
130     args(args),
131     isAsync(isAsync),
132     jBodyReference(std::move(jBodyReference)) {
133   argTypes.reserve(args);
134   for (size_t i = 0; i < args; i++) {
135     auto expectedType = expectedArgTypes->getElement(i);
136     argTypes.push_back(
137       std::make_unique<AnyType>(std::move(expectedType))
138     );
139   }
140 }
141 
142 MethodMetadata::MethodMetadata(
143   std::string name,
144   int args,
145   bool isAsync,
146   std::vector<std::unique_ptr<AnyType>> &&expectedArgTypes,
147   jni::global_ref<jobject> &&jBodyReference
148 ) : name(std::move(name)),
149     args(args),
150     isAsync(isAsync),
151     argTypes(std::move(expectedArgTypes)),
152     jBodyReference(std::move(jBodyReference)
153     ) {}
154 
155 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
156   jsi::Runtime &runtime,
157   JSIInteropModuleRegistry *moduleRegistry
158 ) {
159   if (body == nullptr) {
160     if (isAsync) {
161       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
162     } else {
163       body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
164     }
165   }
166 
167   return body;
168 }
169 
170 jsi::Function MethodMetadata::toSyncFunction(
171   jsi::Runtime &runtime,
172   JSIInteropModuleRegistry *moduleRegistry
173 ) {
174   return jsi::Function::createFromHostFunction(
175     runtime,
176     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
177     args,
178     [this, moduleRegistry](
179       jsi::Runtime &rt,
180       const jsi::Value &thisValue,
181       const jsi::Value *args,
182       size_t count
183     ) -> jsi::Value {
184       try {
185         return this->callSync(
186           rt,
187           moduleRegistry,
188           args,
189           count
190         );
191       } catch (jni::JniException &jniException) {
192         rethrowAsCodedError(rt, moduleRegistry, jniException);
193       }
194     });
195 }
196 
197 jsi::Value MethodMetadata::callSync(
198   jsi::Runtime &rt,
199   JSIInteropModuleRegistry *moduleRegistry,
200   const jsi::Value *args,
201   size_t count
202 ) {
203   if (this->jBodyReference == nullptr) {
204     return jsi::Value::undefined();
205   }
206 
207   JNIEnv *env = jni::Environment::current();
208 
209   /**
210    * This will push a new JNI stack frame for the LocalReferences in this
211    * function call. When the stack frame for this lambda is popped,
212    * all LocalReferences are deleted.
213    */
214   jni::JniLocalScope scope(env, (int) count);
215 
216   std::vector<jobject> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count,
217                                                            false);
218 
219   // TODO(@lukmccall): Remove this temp array
220   auto tempArray = env->NewObjectArray(
221     convertedArgs.size(),
222     JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
223     nullptr
224   );
225   for (size_t i = 0; i < convertedArgs.size(); i++) {
226     env->SetObjectArrayElement(tempArray, i, convertedArgs[i]);
227   }
228 
229   // Cast in this place is safe, cause we know that this function is promise-less.
230   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
231   auto result = syncFunction->invoke(
232     tempArray
233   );
234 
235   if (result == nullptr) {
236     return jsi::Value::undefined();
237   }
238 
239   return jsi::valueFromDynamic(rt, result->cthis()->consume())
240     .asObject(rt)
241     .asArray(rt)
242     .getValueAtIndex(rt, 0);
243 }
244 
245 jsi::Function MethodMetadata::toAsyncFunction(
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       JNIEnv *env = jni::Environment::current();
260 
261       /**
262        * This will push a new JNI stack frame for the LocalReferences in this
263        * function call. When the stack frame for this lambda is popped,
264        * all LocalReferences are deleted.
265        */
266       jni::JniLocalScope scope(env, (int) count);
267 
268       try {
269         std::vector<jobject> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args,
270                                                                  count,
271                                                                  true);
272         auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
273           JSReferencesCache::JSKeys::PROMISE
274         );
275         // Creates a JSI promise
276         jsi::Value promise = Promise.callAsConstructor(
277           rt,
278           createPromiseBody(rt, moduleRegistry, std::move(convertedArgs))
279         );
280         return promise;
281       } catch (jni::JniException &jniException) {
282         rethrowAsCodedError(rt, moduleRegistry, jniException);
283       }
284     }
285   );
286 }
287 
288 jsi::Function MethodMetadata::createPromiseBody(
289   jsi::Runtime &runtime,
290   JSIInteropModuleRegistry *moduleRegistry,
291   std::vector<jobject> &&args
292 ) {
293   return jsi::Function::createFromHostFunction(
294     runtime,
295     moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
296     2,
297     [this, args = std::move(args), moduleRegistry](
298       jsi::Runtime &rt,
299       const jsi::Value &thisVal,
300       const jsi::Value *promiseConstructorArgs,
301       size_t promiseConstructorArgCount
302     ) {
303       if (promiseConstructorArgCount != 2) {
304         throw std::invalid_argument("Promise fn arg count must be 2");
305       }
306 
307       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
308       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
309 
310       auto &runtimeHolder = moduleRegistry->runtimeHolder;
311       jobject resolve = createJavaCallbackFromJSIFunction(
312         std::move(resolveJSIFn),
313         rt,
314         runtimeHolder->jsInvoker
315       ).release();
316 
317       jobject reject = createJavaCallbackFromJSIFunction(
318         std::move(rejectJSIFn),
319         rt,
320         runtimeHolder->jsInvoker
321       ).release();
322 
323       JNIEnv *env = jni::Environment::current();
324 
325       auto &jPromise = JavaReferencesCache::instance()->getJClass(
326         "com/facebook/react/bridge/PromiseImpl");
327       jmethodID jPromiseConstructor = jPromise.getMethod(
328         "<init>",
329         "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"
330       );
331 
332       // Creates a promise object
333       jobject promise = env->NewObject(
334         jPromise.clazz,
335         jPromiseConstructor,
336         resolve,
337         reject
338       );
339 
340       auto argsSize = args.size();
341       // TODO(@lukmccall): Remove this temp array
342       auto tempArray = env->NewObjectArray(
343         argsSize,
344         JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
345         nullptr
346       );
347       for (size_t i = 0; i < argsSize; i++) {
348         env->SetObjectArrayElement(tempArray, i, args[i]);
349       }
350 
351       // Cast in this place is safe, cause we know that this function expects promise.
352       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
353       asyncFunction->invoke(
354         tempArray,
355         promise
356       );
357 
358       // We have to remove the local reference to the promise object.
359       // It doesn't mean that the promise will be deallocated, but rather that we move
360       // the ownership to the `JNIAsyncFunctionBody`.
361       env->DeleteLocalRef(promise);
362 
363       for (const auto &arg: args) {
364         env->DeleteGlobalRef(arg);
365       }
366       env->DeleteLocalRef(tempArray);
367 
368       return jsi::Value::undefined();
369     }
370   );
371 }
372 
373 } // namespace expo
374