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