1 #include "MethodMetadata.h"
2 #include "JSIInteropModuleRegistry.h"
3 #include "JavaScriptValue.h"
4 #include "JavaScriptObject.h"
5 #include "JavaScriptTypedArray.h"
6 #include "CachedReferencesRegistry.h"
7 #include "Exceptions.h"
8 
9 #include <utility>
10 
11 #include "react/jni/ReadableNativeMap.h"
12 #include "react/jni/ReadableNativeArray.h"
13 
14 namespace jni = facebook::jni;
15 namespace jsi = facebook::jsi;
16 namespace react = facebook::react;
17 
18 namespace expo {
19 
20 // Modified version of the RN implementation
21 // https://github.com/facebook/react-native/blob/7dceb9b63c0bfd5b13bf6d26f9530729506e9097/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp#L57
22 jni::local_ref<react::JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
23   jsi::Function &&function,
24   jsi::Runtime &rt,
25   std::shared_ptr<react::CallInvoker> jsInvoker
26 ) {
27   auto weakWrapper = react::CallbackWrapper::createWeak(std::move(function), rt,
28                                                         std::move(jsInvoker));
29 
30   // This needs to be a shared_ptr because:
31   // 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
32   // not.
33   // 2. It cannot be weak_ptr since we need this object to live on.
34   // 3. It cannot be a value, because that would be deleted as soon as this
35   // function returns.
36   auto callbackWrapperOwner =
37     std::make_shared<react::RAIICallbackWrapperDestroyer>(weakWrapper);
38 
39   std::function<void(folly::dynamic)> fn =
40     [weakWrapper, callbackWrapperOwner, wrapperWasCalled = false](
41       folly::dynamic responses) mutable {
42       if (wrapperWasCalled) {
43         throw std::runtime_error(
44           "callback 2 arg cannot be called more than once");
45       }
46 
47       auto strongWrapper = weakWrapper.lock();
48       if (!strongWrapper) {
49         return;
50       }
51 
52       strongWrapper->jsInvoker().invokeAsync(
53         [weakWrapper, callbackWrapperOwner, responses]() mutable {
54           auto strongWrapper2 = weakWrapper.lock();
55           if (!strongWrapper2) {
56             return;
57           }
58 
59           jsi::Value args =
60             jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
61           auto argsArray = args.getObject(strongWrapper2->runtime())
62             .asArray(strongWrapper2->runtime());
63           jsi::Value arg = argsArray.getValueAtIndex(strongWrapper2->runtime(), 0);
64 
65           strongWrapper2->callback().call(
66             strongWrapper2->runtime(),
67             (const jsi::Value *) &arg,
68             (size_t) 1
69           );
70 
71           callbackWrapperOwner.reset();
72         });
73 
74       wrapperWasCalled = true;
75     };
76 
77   return react::JCxxCallbackImpl::newObjectCxxArgs(fn);
78 }
79 
80 std::vector<jvalue> MethodMetadata::convertJSIArgsToJNI(
81   JSIInteropModuleRegistry *moduleRegistry,
82   JNIEnv *env,
83   jsi::Runtime &rt,
84   const jsi::Value *args,
85   size_t count,
86   bool returnGlobalReferences
87 ) {
88   std::vector<jvalue> result(count);
89 
90   auto makeGlobalIfNecessary = [env, returnGlobalReferences](jobject obj) -> jobject {
91     if (returnGlobalReferences) {
92       return env->NewGlobalRef(obj);
93     }
94     return obj;
95   };
96 
97   for (unsigned int argIndex = 0; argIndex < count; argIndex++) {
98     const jsi::Value *arg = &args[argIndex];
99     jvalue *jarg = &result[argIndex];
100     int desiredType = desiredTypes[argIndex];
101 
102     if (desiredType & CppType::JS_VALUE) {
103       jarg->l = makeGlobalIfNecessary(
104         JavaScriptValue::newObjectCxxArgs(
105           moduleRegistry->runtimeHolder->weak_from_this(),
106           // TODO(@lukmccall): make sure that copy here is necessary
107           std::make_shared<jsi::Value>(jsi::Value(rt, *arg))
108         ).release()
109       );
110     } else if (desiredType & CppType::JS_OBJECT) {
111       jarg->l = makeGlobalIfNecessary(
112         JavaScriptObject::newObjectCxxArgs(
113           moduleRegistry->runtimeHolder->weak_from_this(),
114           std::make_shared<jsi::Object>(arg->getObject(rt))
115         ).release()
116       );
117     } else if (desiredType & CppType::TYPED_ARRAY) {
118       jarg->l = makeGlobalIfNecessary(
119         JavaScriptTypedArray::newObjectCxxArgs(
120           moduleRegistry->runtimeHolder->weak_from_this(),
121           std::make_shared<jsi::Object>(arg->getObject(rt))
122         ).release()
123       );
124     } else if (arg->isNull() || arg->isUndefined()) {
125       jarg->l = nullptr;
126     } else if (arg->isNumber()) {
127       auto &doubleClass = CachedReferencesRegistry::instance()
128         ->getJClass("java/lang/Double");
129       jmethodID doubleConstructor = doubleClass.getMethod("<init>", "(D)V");
130       jarg->l = makeGlobalIfNecessary(
131         env->NewObject(doubleClass.clazz, doubleConstructor, arg->getNumber()));
132     } else if (arg->isBool()) {
133       auto &booleanClass = CachedReferencesRegistry::instance()
134         ->getJClass("java/lang/Boolean");
135       jmethodID booleanConstructor = booleanClass.getMethod("<init>", "(Z)V");
136       jarg->l = makeGlobalIfNecessary(
137         env->NewObject(booleanClass.clazz, booleanConstructor, arg->getBool()));
138     } else if (arg->isString()) {
139       jarg->l = makeGlobalIfNecessary(env->NewStringUTF(arg->getString(rt).utf8(rt).c_str()));
140     } else if (arg->isObject()) {
141       const jsi::Object object = arg->getObject(rt);
142 
143       // TODO(@lukmccall): stop using dynamic
144       auto dynamic = jsi::dynamicFromValue(rt, *arg);
145       if (arg->getObject(rt).isArray(rt)) {
146         jarg->l = makeGlobalIfNecessary(
147           react::ReadableNativeArray::newObjectCxxArgs(std::move(dynamic)).release());
148       } else {
149         jarg->l = makeGlobalIfNecessary(
150           react::ReadableNativeMap::createWithContents(std::move(dynamic)).release());
151       }
152     } else {
153       // TODO(@lukmccall): throw an exception
154       jarg->l = nullptr;
155     }
156   }
157 
158   return result;
159 }
160 
161 MethodMetadata::MethodMetadata(
162   std::string name,
163   int args,
164   bool isAsync,
165   std::unique_ptr<int[]> desiredTypes,
166   jni::global_ref<jobject> &&jBodyReference
167 ) : name(std::move(name)),
168     args(args),
169     isAsync(isAsync),
170     desiredTypes(std::move(desiredTypes)),
171     jBodyReference(std::move(jBodyReference)) {}
172 
173 std::shared_ptr<jsi::Function> MethodMetadata::toJSFunction(
174   jsi::Runtime &runtime,
175   JSIInteropModuleRegistry *moduleRegistry
176 ) {
177   if (body == nullptr) {
178     if (isAsync) {
179       body = std::make_shared<jsi::Function>(toAsyncFunction(runtime, moduleRegistry));
180     } else {
181       body = std::make_shared<jsi::Function>(toSyncFunction(runtime, moduleRegistry));
182     }
183   }
184 
185   return body;
186 }
187 
188 jsi::Function MethodMetadata::toSyncFunction(
189   jsi::Runtime &runtime,
190   JSIInteropModuleRegistry *moduleRegistry
191 ) {
192   return jsi::Function::createFromHostFunction(
193     runtime,
194     jsi::PropNameID::forAscii(runtime, name),
195     args,
196     [this, moduleRegistry](
197       jsi::Runtime &rt,
198       const jsi::Value &thisValue,
199       const jsi::Value *args,
200       size_t count
201     ) -> jsi::Value {
202       try {
203         return this->callSync(
204           rt,
205           moduleRegistry,
206           args,
207           count
208         );
209       } catch (jni::JniException &jniException) {
210         jni::local_ref<jni::JThrowable> unboxedThrowable = jniException.getThrowable();
211         if (unboxedThrowable->isInstanceOf(CodedException::javaClassLocal())) {
212           auto codedException = jni::static_ref_cast<CodedException>(unboxedThrowable);
213           auto code = codedException->getCode();
214           auto message = codedException->getLocalizedMessage();
215 
216           if (rt.global().hasProperty(rt, "ExpoModulesCore_CodedError")) {
217             auto jsCodedError = rt.global()
218               .getProperty(rt, "ExpoModulesCore_CodedError")
219               .asObject(rt)
220               .asFunction(rt);
221 
222             throw jsi::JSError(
223               message.value_or(""),
224               rt,
225               jsCodedError.callAsConstructor(
226                 rt,
227                 jsi::String::createFromUtf8(rt, code),
228                 jsi::String::createFromUtf8(rt, message.value_or(""))
229               )
230             );
231           }
232         }
233 
234         // Rethrow error if we can't wrap it.
235         throw;
236       }
237     });
238 }
239 
240 jsi::Value MethodMetadata::callSync(
241   jsi::Runtime &rt,
242   JSIInteropModuleRegistry *moduleRegistry,
243   const jsi::Value *args,
244   size_t count
245 ) {
246   if (this->jBodyReference == nullptr) {
247     return jsi::Value::undefined();
248   }
249 
250   JNIEnv *env = jni::Environment::current();
251 
252   /**
253    * This will push a new JNI stack frame for the LocalReferences in this
254    * function call. When the stack frame for this lambda is popped,
255    * all LocalReferences are deleted.
256    */
257   jni::JniLocalScope scope(env, (int) count);
258 
259   std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count,
260                                                           false);
261 
262   // TODO(@lukmccall): Remove this temp array
263   auto tempArray = env->NewObjectArray(
264     convertedArgs.size(),
265     CachedReferencesRegistry::instance()->getJClass("java/lang/Object").clazz,
266     nullptr
267   );
268   for (size_t i = 0; i < convertedArgs.size(); i++) {
269     env->SetObjectArrayElement(tempArray, i, convertedArgs[i].l);
270   }
271 
272   // Cast in this place is safe, cause we know that this function is promise-less.
273   auto syncFunction = jni::static_ref_cast<JNIFunctionBody>(this->jBodyReference);
274   auto result = syncFunction->invoke(
275     tempArray
276   );
277 
278   if (result == nullptr) {
279     return jsi::Value::undefined();
280   }
281 
282   return jsi::valueFromDynamic(rt, result->cthis()->consume())
283     .asObject(rt)
284     .asArray(rt)
285     .getValueAtIndex(rt, 0);
286 }
287 
288 jsi::Function MethodMetadata::toAsyncFunction(
289   jsi::Runtime &runtime,
290   JSIInteropModuleRegistry *moduleRegistry
291 ) {
292   return jsi::Function::createFromHostFunction(
293     runtime,
294     jsi::PropNameID::forAscii(runtime, name),
295     args,
296     [this, moduleRegistry](
297       jsi::Runtime &rt,
298       const jsi::Value &thisValue,
299       const jsi::Value *args,
300       size_t count
301     ) -> jsi::Value {
302       JNIEnv *env = jni::Environment::current();
303 
304       /**
305        * This will push a new JNI stack frame for the LocalReferences in this
306        * function call. When the stack frame for this lambda is popped,
307        * all LocalReferences are deleted.
308        */
309       jni::JniLocalScope scope(env, (int) count);
310       std::vector<jvalue> convertedArgs = convertJSIArgsToJNI(moduleRegistry, env, rt, args, count,
311                                                               true);
312 
313       auto Promise = rt.global().getPropertyAsFunction(rt, "Promise");
314       // Creates a JSI promise
315       jsi::Value promise = Promise.callAsConstructor(
316         rt,
317         createPromiseBody(rt, moduleRegistry, std::move(convertedArgs))
318       );
319       return promise;
320     }
321   );
322 }
323 
324 jsi::Function MethodMetadata::createPromiseBody(
325   jsi::Runtime &runtime,
326   JSIInteropModuleRegistry *moduleRegistry,
327   std::vector<jvalue> &&args
328 ) {
329   return jsi::Function::createFromHostFunction(
330     runtime,
331     jsi::PropNameID::forAscii(runtime, "promiseFn"),
332     2,
333     [this, args = std::move(args), moduleRegistry](
334       jsi::Runtime &rt,
335       const jsi::Value &thisVal,
336       const jsi::Value *promiseConstructorArgs,
337       size_t promiseConstructorArgCount
338     ) {
339       if (promiseConstructorArgCount != 2) {
340         throw std::invalid_argument("Promise fn arg count must be 2");
341       }
342 
343       jsi::Function resolveJSIFn = promiseConstructorArgs[0].getObject(rt).getFunction(rt);
344       jsi::Function rejectJSIFn = promiseConstructorArgs[1].getObject(rt).getFunction(rt);
345 
346       auto &runtimeHolder = moduleRegistry->runtimeHolder;
347       jobject resolve = createJavaCallbackFromJSIFunction(
348         std::move(resolveJSIFn),
349         rt,
350         runtimeHolder->jsInvoker
351       ).release();
352 
353       jobject reject = createJavaCallbackFromJSIFunction(
354         std::move(rejectJSIFn),
355         rt,
356         runtimeHolder->jsInvoker
357       ).release();
358 
359       JNIEnv *env = jni::Environment::current();
360 
361       auto &jPromise = CachedReferencesRegistry::instance()->getJClass(
362         "com/facebook/react/bridge/PromiseImpl");
363       jmethodID jPromiseConstructor = jPromise.getMethod(
364         "<init>",
365         "(Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V"
366       );
367 
368       // Creates a promise object
369       jobject promise = env->NewObject(
370         jPromise.clazz,
371         jPromiseConstructor,
372         resolve,
373         reject
374       );
375 
376       auto argsSize = args.size();
377       // TODO(@lukmccall): Remove this temp array
378       auto tempArray = env->NewObjectArray(
379         argsSize,
380         CachedReferencesRegistry::instance()->getJClass("java/lang/Object").clazz,
381         nullptr
382       );
383       for (size_t i = 0; i < argsSize; i++) {
384         env->SetObjectArrayElement(tempArray, i, args[i].l);
385       }
386 
387       // Cast in this place is safe, cause we know that this function expects promise.
388       auto asyncFunction = jni::static_ref_cast<JNIAsyncFunctionBody>(this->jBodyReference);
389       asyncFunction->invoke(
390         tempArray,
391         promise
392       );
393 
394       // We have to remove the local reference to the promise object.
395       // It doesn't mean that the promise will be deallocated, but rather that we move
396       // the ownership to the `JNIAsyncFunctionBody`.
397       env->DeleteLocalRef(promise);
398 
399       for (const auto &arg: args) {
400         env->DeleteGlobalRef(arg.l);
401       }
402       env->DeleteLocalRef(tempArray);
403 
404       return jsi::Value::undefined();
405     }
406   );
407 }
408 
409 } // namespace expo
410