CxxNativeModule.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #include "CxxNativeModule.h"
  8. #include "Instance.h"
  9. #include <folly/json.h>
  10. #include <glog/logging.h>
  11. #include <iterator>
  12. #include "JsArgumentHelpers.h"
  13. #include "MessageQueueThread.h"
  14. #include "SystraceSection.h"
  15. using facebook::xplat::module::CxxModule;
  16. namespace facebook {
  17. namespace react {
  18. std::function<void(folly::dynamic)> makeCallback(
  19. std::weak_ptr<Instance> instance,
  20. const folly::dynamic &callbackId) {
  21. if (!callbackId.isNumber()) {
  22. throw std::invalid_argument("Expected callback(s) as final argument");
  23. }
  24. auto id = callbackId.asInt();
  25. return [winstance = std::move(instance), id](folly::dynamic args) {
  26. if (auto instance = winstance.lock()) {
  27. instance->callJSCallback(id, std::move(args));
  28. }
  29. };
  30. }
  31. namespace {
  32. /**
  33. * CxxModule::Callback accepts a vector<dynamic>, makeCallback returns
  34. * a callback that accepts a dynamic, adapt the second into the first.
  35. * TODO: Callback types should be made equal (preferably
  36. * function<void(dynamic)>) to avoid the extra copy and indirect call.
  37. */
  38. CxxModule::Callback convertCallback(
  39. std::function<void(folly::dynamic)> callback) {
  40. return [callback = std::move(callback)](std::vector<folly::dynamic> args) {
  41. callback(folly::dynamic(
  42. std::make_move_iterator(args.begin()),
  43. std::make_move_iterator(args.end())));
  44. };
  45. }
  46. } // namespace
  47. std::string CxxNativeModule::getName() {
  48. return name_;
  49. }
  50. std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
  51. lazyInit();
  52. std::vector<MethodDescriptor> descs;
  53. for (auto &method : methods_) {
  54. descs.emplace_back(method.name, method.getType());
  55. }
  56. return descs;
  57. }
  58. folly::dynamic CxxNativeModule::getConstants() {
  59. lazyInit();
  60. if (!module_) {
  61. return nullptr;
  62. }
  63. folly::dynamic constants = folly::dynamic::object();
  64. for (auto &pair : module_->getConstants()) {
  65. constants.insert(std::move(pair.first), std::move(pair.second));
  66. }
  67. return constants;
  68. }
  69. void CxxNativeModule::invoke(
  70. unsigned int reactMethodId,
  71. folly::dynamic &&params,
  72. int callId) {
  73. if (reactMethodId >= methods_.size()) {
  74. throw std::invalid_argument(folly::to<std::string>(
  75. "methodId ",
  76. reactMethodId,
  77. " out of range [0..",
  78. methods_.size(),
  79. "]"));
  80. }
  81. if (!params.isArray()) {
  82. throw std::invalid_argument(folly::to<std::string>(
  83. "method parameters should be array, but are ", params.typeName()));
  84. }
  85. CxxModule::Callback first;
  86. CxxModule::Callback second;
  87. const auto &method = methods_[reactMethodId];
  88. if (!method.func) {
  89. throw std::runtime_error(folly::to<std::string>(
  90. "Method ", method.name, " is synchronous but invoked asynchronously"));
  91. }
  92. if (params.size() < method.callbacks) {
  93. throw std::invalid_argument(folly::to<std::string>(
  94. "Expected ",
  95. method.callbacks,
  96. " callbacks, but only ",
  97. params.size(),
  98. " parameters provided"));
  99. }
  100. if (method.callbacks == 1) {
  101. first = convertCallback(makeCallback(instance_, params[params.size() - 1]));
  102. } else if (method.callbacks == 2) {
  103. first = convertCallback(makeCallback(instance_, params[params.size() - 2]));
  104. second =
  105. convertCallback(makeCallback(instance_, params[params.size() - 1]));
  106. }
  107. params.resize(params.size() - method.callbacks);
  108. // I've got a few flawed options here. I can let the C++ exception
  109. // propagate, and the registry will log/convert them to java exceptions.
  110. // This lets all the java and red box handling work ok, but the only info I
  111. // can capture about the C++ exception is the what() string, not the stack.
  112. // I can std::terminate() the app. This causes the full, accurate C++
  113. // stack trace to be added to logcat by debuggerd. The java state is lost,
  114. // but in practice, the java stack is always the same in this case since
  115. // the javascript stack is not visible, and the crash is unfriendly to js
  116. // developers, but crucial to C++ developers. The what() value is also
  117. // lost. Finally, I can catch, log the java stack, then rethrow the C++
  118. // exception. In this case I get java and C++ stack data, but the C++
  119. // stack is as of the rethrow, not the original throw, both the C++ and
  120. // java stacks always look the same.
  121. //
  122. // I am going with option 2, since that seems like the most useful
  123. // choice. It would be nice to be able to get what() and the C++
  124. // stack. I'm told that will be possible in the future. TODO
  125. // mhorowitz #7128529: convert C++ exceptions to Java
  126. messageQueueThread_->runOnQueue(
  127. [method, params = std::move(params), first, second, callId]() {
  128. #ifdef WITH_FBSYSTRACE
  129. if (callId != -1) {
  130. fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
  131. }
  132. #else
  133. (void)(callId);
  134. #endif
  135. SystraceSection s(method.name.c_str());
  136. try {
  137. method.func(std::move(params), first, second);
  138. } catch (const facebook::xplat::JsArgumentException &ex) {
  139. throw;
  140. } catch (std::exception &e) {
  141. LOG(ERROR) << "std::exception. Method call " << method.name.c_str()
  142. << " failed: " << e.what();
  143. std::terminate();
  144. } catch (std::string &error) {
  145. LOG(ERROR) << "std::string. Method call " << method.name.c_str()
  146. << " failed: " << error.c_str();
  147. std::terminate();
  148. } catch (...) {
  149. LOG(ERROR) << "Method call " << method.name.c_str()
  150. << " failed. unknown error";
  151. std::terminate();
  152. }
  153. });
  154. }
  155. MethodCallResult CxxNativeModule::callSerializableNativeHook(
  156. unsigned int hookId,
  157. folly::dynamic &&args) {
  158. if (hookId >= methods_.size()) {
  159. throw std::invalid_argument(folly::to<std::string>(
  160. "methodId ", hookId, " out of range [0..", methods_.size(), "]"));
  161. }
  162. const auto &method = methods_[hookId];
  163. if (!method.syncFunc) {
  164. throw std::runtime_error(folly::to<std::string>(
  165. "Method ", method.name, " is asynchronous but invoked synchronously"));
  166. }
  167. return method.syncFunc(std::move(args));
  168. }
  169. void CxxNativeModule::lazyInit() {
  170. if (module_ || !provider_) {
  171. return;
  172. }
  173. // TODO 17216751: providers should never return null modules
  174. module_ = provider_();
  175. provider_ = nullptr;
  176. if (module_) {
  177. methods_ = module_->getMethods();
  178. module_->setInstance(instance_);
  179. }
  180. }
  181. } // namespace react
  182. } // namespace facebook