// Copyright 2004-present Facebook. All Rights Reserved. #include "CxxNativeModule.h" #include "Instance.h" #include #include #include "JsArgumentHelpers.h" #include "SystraceSection.h" using facebook::xplat::module::CxxModule; namespace facebook { namespace react { std::function makeCallback( std::weak_ptr instance, const folly::dynamic& callbackId) { if (!callbackId.isInt()) { throw std::invalid_argument("Expected callback(s) as final argument"); } auto id = callbackId.getInt(); 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()))); }; } } std::string CxxNativeModule::getName() { return name_; } std::vector CxxNativeModule::getMethods() { lazyInit(); std::vector descs; for (auto& method : methods_) { assert(method.func || method.syncFunc); auto methodType = method.func ? (method.callbacks == 2 ? "promise" : "async") : "sync"; descs.emplace_back(method.name, methodType); } return descs; } folly::dynamic CxxNativeModule::getConstants() { lazyInit(); if (!module_) { return nullptr; } 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&& params, 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")); } 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 // propogate, 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 messageQueueThread_->runOnQueue([method, params=std::move(params), first, second, callId] () { #ifdef WITH_FBSYSTRACE if (callId != -1) { fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId); } #endif SystraceSection s(method.name.c_str()); try { method.func(std::move(params), first, second); } catch (const facebook::xplat::JsArgumentException& ex) { throw; } catch (...) { // This means some C++ code is buggy. As above, we fail hard so the C++ // developer can debug and fix it. 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")); } 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_) { methods_ = module_->getMethods(); module_->setInstance(instance_); } } } }