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