123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- /*
- * Copyright (c) Facebook, Inc. and its 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 <folly/json.h>
- #include <glog/logging.h>
- #include <iterator>
- #include "JsArgumentHelpers.h"
- #include "MessageQueueThread.h"
- #include "SystraceSection.h"
- using facebook::xplat::module::CxxModule;
- namespace facebook {
- namespace react {
- std::function<void(folly::dynamic)> makeCallback(
- std::weak_ptr<Instance> 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<dynamic>, makeCallback returns
- * a callback that accepts a dynamic, adapt the second into the first.
- * TODO: Callback types should be made equal (preferably
- * function<void(dynamic)>) to avoid the extra copy and indirect call.
- */
- CxxModule::Callback convertCallback(
- std::function<void(folly::dynamic)> callback) {
- return [callback = std::move(callback)](std::vector<folly::dynamic> args) {
- callback(folly::dynamic(
- std::make_move_iterator(args.begin()),
- std::make_move_iterator(args.end())));
- };
- }
- } // namespace
- std::string CxxNativeModule::getName() {
- return name_;
- }
- std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
- lazyInit();
- std::vector<MethodDescriptor> descs;
- for (auto &method : methods_) {
- descs.emplace_back(method.name, method.getType());
- }
- 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 &¶ms,
- int callId) {
- if (reactMethodId >= methods_.size()) {
- throw std::invalid_argument(folly::to<std::string>(
- "methodId ",
- reactMethodId,
- " out of range [0..",
- methods_.size(),
- "]"));
- }
- if (!params.isArray()) {
- throw std::invalid_argument(folly::to<std::string>(
- "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<std::string>(
- "Method ", method.name, " is synchronous but invoked asynchronously"));
- }
- if (params.size() < method.callbacks) {
- throw std::invalid_argument(folly::to<std::string>(
- "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
- 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);
- }
- #else
- (void)(callId);
- #endif
- SystraceSection s(method.name.c_str());
- 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<std::string>(
- "methodId ", hookId, " out of range [0..", methods_.size(), "]"));
- }
- const auto &method = methods_[hookId];
- if (!method.syncFunc) {
- throw std::runtime_error(folly::to<std::string>(
- "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_);
- }
- }
- } // namespace react
- } // namespace facebook
|