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<jvalue> 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<jvalue> 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     jvalue *jarg = &result[argIndex];
101     int desiredType = desiredTypes[argIndex];
102 
103     if (desiredType & CppType::JS_VALUE) {
104       jarg->l = makeGlobalIfNecessary(
105         JavaScriptValue::newObjectCxxArgs(
106           moduleRegistry->runtimeHolder->weak_from_this(),
107           // TODO(@lukmccall): make sure that copy here is necessary
108           std::make_shared<jsi::Value>(jsi::Value(rt, *arg))
109         ).release()
110       );
111     } else if (desiredType & CppType::JS_OBJECT) {
112       jarg->l = makeGlobalIfNecessary(
113         JavaScriptObject::newObjectCxxArgs(
114           moduleRegistry->runtimeHolder->weak_from_this(),
115           std::make_shared<jsi::Object>(arg->getObject(rt))
116         ).release()
117       );
118     } else if (desiredType & CppType::TYPED_ARRAY) {
119       jarg->l = makeGlobalIfNecessary(
120         JavaScriptTypedArray::newObjectCxxArgs(
121           moduleRegistry->runtimeHolder->weak_from_this(),
122           std::make_shared<jsi::Object>(arg->getObject(rt))
123         ).release()
124       );
125     } else if (arg->isNull() || arg->isUndefined()) {
126       jarg->l = nullptr;
127     } else if (arg->isNumber()) {
128       auto &doubleClass = JavaReferencesCache::instance()
129         ->getJClass("java/lang/Double");
130       jmethodID doubleConstructor = doubleClass.getMethod("<init>", "(D)V");
131       jarg->l = makeGlobalIfNecessary(
132         env->NewObject(doubleClass.clazz, doubleConstructor, arg->getNumber()));
133     } else if (arg->isBool()) {
134       auto &booleanClass = JavaReferencesCache::instance()
135         ->getJClass("java/lang/Boolean");
136       jmethodID booleanConstructor = booleanClass.getMethod("<init>", "(Z)V");
137       jarg->l = makeGlobalIfNecessary(
138         env->NewObject(booleanClass.clazz, booleanConstructor, arg->getBool()));
139     } else if (arg->isString()) {
140       jarg->l = makeGlobalIfNecessary(env->NewStringUTF(arg->getString(rt).utf8(rt).c_str()));
141     } else if (arg->isObject()) {
142       const jsi::Object object = arg->getObject(rt);
143 
144       // TODO(@lukmccall): stop using dynamic
145       auto dynamic = jsi::dynamicFromValue(rt, *arg);
146       if (arg->getObject(rt).isArray(rt)) {
147         jarg->l = makeGlobalIfNecessary(
148           react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamic)).release());
149       } else {
150         jarg->l = makeGlobalIfNecessary(
151           react::ReadableNativeMap::createWithContents(std::move(dynamic)).release());
152       }
153     } else {
154       auto stringRepresentation = arg->toString(rt).utf8(rt);
155       jni::throwNewJavaException(
156         UnexpectedException::create(
157           "Cannot convert '" + stringRepresentation + "' to a Kotlin type.").get()
158       );
159     }
160   }
161 
162   return result;
163 }
164 
165 MethodMetadata::MethodMetadata(
166   std::string name,
167   int args,
168   bool isAsync,
169   std::unique_ptr<int[]> desiredTypes,
170   jni::global_ref<jobject> &&jBodyReference
171 ) : name(std::move(name)),
172     args(args),
173     isAsync(isAsync),
174     desiredTypes(std::move(desiredTypes)),
175     jBodyReference(std::move(jBodyReference)) {}
176 
177 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
178   jsi::Runtime &runtime,
179   JSIInteropModuleRegistry *moduleRegistry
180 ) {
181   if (body == nullptr) {
182     if (isAsync) {
183       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
184     } else {
185       body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
186     }
187   }
188 
189   return body;
190 }
191 
192 jsi::Function MethodMetadata::toSyncFunction(
193   jsi::Runtime &runtime,
194   JSIInteropModuleRegistry *moduleRegistry
195 ) {
196   return jsi::Function::createFromHostFunction(
197     runtime,
198     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
199     args,
200     [this, moduleRegistry](
201       jsi::Runtime &rt,
202       const jsi::Value &thisValue,
203       const jsi::Value *args,
204       size_t count
205     ) -> jsi::Value {
206       try {
207         return this->callSync(
208           rt,
209           moduleRegistry,
210           args,
211           count
212         );
213       } catch (jni::JniException &jniException) {
214         rethrowAsCodedError(rt, moduleRegistry, jniException);
215       }
216     });
217 }
218 
219 jsi::Value MethodMetadata::callSync(
220   jsi::Runtime &rt,
221   JSIInteropModuleRegistry *moduleRegistry,
222   const jsi::Value *args,
223   size_t count
224 ) {
225   if (this->jBodyReference == nullptr) {
226     return jsi::Value::undefined();
227   }
228 
229   JNIEnv *env = jni::Environment::current();
230 
231   /**
232    * This will push a new JNI stack frame for the LocalReferences in this
233    * function call. When the stack frame for this lambda is popped,
234    * all LocalReferences are deleted.
235    */
236   jni::JniLocalScope scope(env, (int) count);
237 
238   std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count,
239                                                           false);
240 
241   // TODO(@lukmccall): Remove this temp array
242   auto tempArray = env->NewObjectArray(
243     convertedArgs.size(),
244     JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
245     nullptr
246   );
247   for (size_t i = 0; i < convertedArgs.size(); i++) {
248     env->SetObjectArrayElement(tempArray, i, convertedArgs[i].l);
249   }
250 
251   // Cast in this place is safe, cause we know that this function is promise-less.
252   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
253   auto result = syncFunction->invoke(
254     tempArray
255   );
256 
257   if (result == nullptr) {
258     return jsi::Value::undefined();
259   }
260 
261   return jsi::valueFromDynamic(rt, result->cthis()->consume())
262     .asObject(rt)
263     .asArray(rt)
264     .getValueAtIndex(rt, 0);
265 }
266 
267 jsi::Function MethodMetadata::toAsyncFunction(
268   jsi::Runtime &runtime,
269   JSIInteropModuleRegistry *moduleRegistry
270 ) {
271   return jsi::Function::createFromHostFunction(
272     runtime,
273     moduleRegistry->jsRegistry->getPropNameID(runtime, name),
274     args,
275     [this, moduleRegistry](
276       jsi::Runtime &rt,
277       const jsi::Value &thisValue,
278       const jsi::Value *args,
279       size_t count
280     ) -> jsi::Value {
281       JNIEnv *env = jni::Environment::current();
282 
283       /**
284        * This will push a new JNI stack frame for the LocalReferences in this
285        * function call. When the stack frame for this lambda is popped,
286        * all LocalReferences are deleted.
287        */
288       jni::JniLocalScope scope(env, (int) count);
289 
290       try {
291         std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args,
292                                                                 count,
293                                                                 true);
294         auto &Promise = moduleRegistry->jsRegistry->getObject<jsi::Function>(
295           JSReferencesCache::JSKeys::PROMISE
296         );
297         // Creates a JSI promise
298         jsi::Value promise = Promise.callAsConstructor(
299           rt,
300           createPromiseBody(rt, moduleRegistry, std::move(convertedArgs))
301         );
302         return promise;
303       } catch (jni::JniException &jniException) {
304         rethrowAsCodedError(rt, moduleRegistry, jniException);
305       }
306     }
307   );
308 }
309 
310 jsi::Function MethodMetadata::createPromiseBody(
311   jsi::Runtime &runtime,
312   JSIInteropModuleRegistry *moduleRegistry,
313   std::vector<jvalue> &&args
314 ) {
315   return jsi::Function::createFromHostFunction(
316     runtime,
317     moduleRegistry->jsRegistry->getPropNameID(runtime, "promiseFn"),
318     2,
319     [this, args = std::move(args), moduleRegistry](
320       jsi::Runtime &rt,
321       const jsi::Value &thisVal,
322       const jsi::Value *promiseConstructorArgs,
323       size_t promiseConstructorArgCount
324     ) {
325       if (promiseConstructorArgCount != 2) {
326         throw std::invalid_argument("Promise fn arg count must be 2");
327       }
328 
329       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
330       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
331 
332       auto &runtimeHolder = moduleRegistry->runtimeHolder;
333       jobject resolve = createJavaCallbackFromJSIFunction(
334         std::move(resolveJSIFn),
335         rt,
336         runtimeHolder->jsInvoker
337       ).release();
338 
339       jobject reject = createJavaCallbackFromJSIFunction(
340         std::move(rejectJSIFn),
341         rt,
342         runtimeHolder->jsInvoker
343       ).release();
344 
345       JNIEnv *env = jni::Environment::current();
346 
347       auto &jPromise = JavaReferencesCache::instance()->getJClass(
348         "com/facebook/react/bridge/PromiseImpl");
349       jmethodID jPromiseConstructor = jPromise.getMethod(
350         "<init>",
351         "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"
352       );
353 
354       // Creates a promise object
355       jobject promise = env->NewObject(
356         jPromise.clazz,
357         jPromiseConstructor,
358         resolve,
359         reject
360       );
361 
362       auto argsSize = args.size();
363       // TODO(@lukmccall): Remove this temp array
364       auto tempArray = env->NewObjectArray(
365         argsSize,
366         JavaReferencesCache::instance()->getJClass("java/lang/Object").clazz,
367         nullptr
368       );
369       for (size_t i = 0; i < argsSize; i++) {
370         env->SetObjectArrayElement(tempArray, i, args[i].l);
371       }
372 
373       // Cast in this place is safe, cause we know that this function expects promise.
374       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
375       asyncFunction->invoke(
376         tempArray,
377         promise
378       );
379 
380       // We have to remove the local reference to the promise object.
381       // It doesn't mean that the promise will be deallocated, but rather that we move
382       // the ownership to the `JNIAsyncFunctionBody`.
383       env->DeleteLocalRef(promise);
384 
385       for (const auto &arg: args) {
386         env->DeleteGlobalRef(arg.l);
387       }
388       env->DeleteLocalRef(tempArray);
389 
390       return jsi::Value::undefined();
391     }
392   );
393 }
394 
395 } // namespace expo
396