/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "CxxNativeModule.h" #include "Instance.h" #include #include #include #include "JsArgumentHelpers.h" #include "MessageQueueThread.h" #include "SystraceSection.h" #include using facebook::xplat::module::CxxModule; namespace facebook { namespace react { std::function makeCallback( std::weak_ptr instance, const folly::dynamic &callbackId) { if (!callbackId.isNumber()) { throw std::invalid_argument("Expected callback(s) as final argument"); } auto id = callbackId.asInt(); return [winstance = std::move(instance), id](folly::dynamic args) { if (auto instance = winstance.lock()) { instance->callJSCallback(id, std::move(args)); } }; } namespace { /** * CxxModule::Callback accepts a vector, makeCallback returns * a callback that accepts a dynamic, adapt the second into the first. * TODO: Callback types should be made equal (preferably * function) to avoid the extra copy and indirect call. */ CxxModule::Callback convertCallback( std::function callback) { return [callback = std::move(callback)](std::vector args) { callback(folly::dynamic( std::make_move_iterator(args.begin()), std::make_move_iterator(args.end()))); }; } } // namespace bool CxxNativeModule::shouldWarnOnUse_ = false; void CxxNativeModule::setShouldWarnOnUse(bool value) { shouldWarnOnUse_ = value; } void CxxNativeModule::emitWarnIfWarnOnUsage( const std::string &method_name, const std::string &module_name) { if (shouldWarnOnUse_) { std::string message = folly::to( "Calling ", method_name, " on Cxx NativeModule (name = \"", module_name, "\")."); react_native_log_warn(message.c_str()); } } std::string CxxNativeModule::getName() { return name_; } std::string CxxNativeModule::getSyncMethodName(unsigned int reactMethodId) { if (reactMethodId >= methods_.size()) { throw std::invalid_argument(folly::to( "methodId ", reactMethodId, " out of range [0..", methods_.size(), "]")); } return methods_[reactMethodId].name; } std::vector CxxNativeModule::getMethods() { lazyInit(); std::vector descs; for (auto &method : methods_) { descs.emplace_back(method.name, method.getType()); } return descs; } folly::dynamic CxxNativeModule::getConstants() { lazyInit(); if (!module_) { return nullptr; } emitWarnIfWarnOnUsage("getConstants()", getName()); folly::dynamic constants = folly::dynamic::object(); for (auto &pair : module_->getConstants()) { constants.insert(std::move(pair.first), std::move(pair.second)); } return constants; } void CxxNativeModule::invoke( unsigned int reactMethodId, folly::dynamic &¶ms, int callId) { if (reactMethodId >= methods_.size()) { throw std::invalid_argument(folly::to( "methodId ", reactMethodId, " out of range [0..", methods_.size(), "]")); } if (!params.isArray()) { throw std::invalid_argument(folly::to( "method parameters should be array, but are ", params.typeName())); } CxxModule::Callback first; CxxModule::Callback second; const auto &method = methods_[reactMethodId]; if (!method.func) { throw std::runtime_error(folly::to( "Method ", method.name, " is synchronous but invoked asynchronously")); } emitWarnIfWarnOnUsage(method.name, getName()); if (params.size() < method.callbacks) { throw std::invalid_argument(folly::to( "Expected ", method.callbacks, " callbacks, but only ", params.size(), " parameters provided")); } if (method.callbacks == 1) { first = convertCallback(makeCallback(instance_, params[params.size() - 1])); } else if (method.callbacks == 2) { first = convertCallback(makeCallback(instance_, params[params.size() - 2])); second = convertCallback(makeCallback(instance_, params[params.size() - 1])); } params.resize(params.size() - method.callbacks); // I've got a few flawed options here. I can let the C++ exception // propagate, and the registry will log/convert them to java exceptions. // This lets all the java and red box handling work ok, but the only info I // can capture about the C++ exception is the what() string, not the stack. // I can std::terminate() the app. This causes the full, accurate C++ // stack trace to be added to logcat by debuggerd. The java state is lost, // but in practice, the java stack is always the same in this case since // the javascript stack is not visible, and the crash is unfriendly to js // developers, but crucial to C++ developers. The what() value is also // lost. Finally, I can catch, log the java stack, then rethrow the C++ // exception. In this case I get java and C++ stack data, but the C++ // stack is as of the rethrow, not the original throw, both the C++ and // java stacks always look the same. // // I am going with option 2, since that seems like the most useful // choice. It would be nice to be able to get what() and the C++ // stack. I'm told that will be possible in the future. TODO // mhorowitz #7128529: convert C++ exceptions to Java const auto &moduleName = name_; SystraceSection s( "CxxMethodCallQueue", "module", moduleName, "method", method.name); messageQueueThread_->runOnQueue([method, moduleName, params = std::move(params), first, second, callId]() { #ifdef WITH_FBSYSTRACE if (callId != -1) { fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId); } #else (void)(callId); #endif SystraceSection s( "CxxMethodCallDispatch", "module", moduleName, "method", method.name); try { method.func(std::move(params), first, second); } catch (const facebook::xplat::JsArgumentException &ex) { throw; } catch (std::exception &e) { LOG(ERROR) << "std::exception. Method call " << method.name.c_str() << " failed: " << e.what(); std::terminate(); } catch (std::string &error) { LOG(ERROR) << "std::string. Method call " << method.name.c_str() << " failed: " << error.c_str(); std::terminate(); } catch (...) { LOG(ERROR) << "Method call " << method.name.c_str() << " failed. unknown error"; std::terminate(); } }); } MethodCallResult CxxNativeModule::callSerializableNativeHook( unsigned int hookId, folly::dynamic &&args) { if (hookId >= methods_.size()) { throw std::invalid_argument(folly::to( "methodId ", hookId, " out of range [0..", methods_.size(), "]")); } const auto &method = methods_[hookId]; if (!method.syncFunc) { throw std::runtime_error(folly::to( "Method ", method.name, " is asynchronous but invoked synchronously")); } emitWarnIfWarnOnUsage(method.name, getName()); return method.syncFunc(std::move(args)); } void CxxNativeModule::lazyInit() { if (module_ || !provider_) { return; } // TODO 17216751: providers should never return null modules module_ = provider_(); provider_ = nullptr; if (module_) { module_->setInstance(instance_); methods_ = module_->getMethods(); } } } // namespace react } // namespace facebook