1 /*
2 * Copyright (c) Meta Platforms, Inc. and 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
8 #include "ABI47_0_0CxxNativeModule.h"
9 #include "ABI47_0_0Instance.h"
10
11 #include <folly/json.h>
12 #include <glog/logging.h>
13 #include <iterator>
14
15 #include "ABI47_0_0JsArgumentHelpers.h"
16 #include "ABI47_0_0MessageQueueThread.h"
17 #include "ABI47_0_0SystraceSection.h"
18
19 #include <ABI47_0_0logger/ABI47_0_0React_native_log.h>
20
21 using ABI47_0_0facebook::xplat::module::CxxModule;
22 namespace ABI47_0_0facebook {
23 namespace ABI47_0_0React {
24
makeCallback(std::weak_ptr<Instance> instance,const folly::dynamic & callbackId)25 std::function<void(folly::dynamic)> makeCallback(
26 std::weak_ptr<Instance> instance,
27 const folly::dynamic &callbackId) {
28 if (!callbackId.isNumber()) {
29 throw std::invalid_argument("Expected callback(s) as final argument");
30 }
31
32 auto id = callbackId.asInt();
33 return [winstance = std::move(instance), id](folly::dynamic args) {
34 if (auto instance = winstance.lock()) {
35 instance->callJSCallback(id, std::move(args));
36 }
37 };
38 }
39
40 namespace {
41
42 /**
43 * CxxModule::Callback accepts a vector<dynamic>, makeCallback returns
44 * a callback that accepts a dynamic, adapt the second into the first.
45 * TODO: Callback types should be made equal (preferably
46 * function<void(dynamic)>) to avoid the extra copy and indirect call.
47 */
convertCallback(std::function<void (folly::dynamic)> callback)48 CxxModule::Callback convertCallback(
49 std::function<void(folly::dynamic)> callback) {
50 return [callback = std::move(callback)](std::vector<folly::dynamic> args) {
51 callback(folly::dynamic(
52 std::make_move_iterator(args.begin()),
53 std::make_move_iterator(args.end())));
54 };
55 }
56
57 } // namespace
58
59 bool CxxNativeModule::shouldWarnOnUse_ = false;
60
setShouldWarnOnUse(bool value)61 void CxxNativeModule::setShouldWarnOnUse(bool value) {
62 shouldWarnOnUse_ = value;
63 }
64
emitWarnIfWarnOnUsage(const std::string & method_name,const std::string & module_name)65 void CxxNativeModule::emitWarnIfWarnOnUsage(
66 const std::string &method_name,
67 const std::string &module_name) {
68 if (shouldWarnOnUse_) {
69 std::string message = folly::to<std::string>(
70 "Calling ",
71 method_name,
72 " on Cxx NativeModule (name = \"",
73 module_name,
74 "\").");
75 ABI47_0_0React_native_log_warn(message.c_str());
76 }
77 }
78
getName()79 std::string CxxNativeModule::getName() {
80 return name_;
81 }
82
getSyncMethodName(unsigned int ABI47_0_0ReactMethodId)83 std::string CxxNativeModule::getSyncMethodName(unsigned int ABI47_0_0ReactMethodId) {
84 if (ABI47_0_0ReactMethodId >= methods_.size()) {
85 throw std::invalid_argument(folly::to<std::string>(
86 "methodId ",
87 ABI47_0_0ReactMethodId,
88 " out of range [0..",
89 methods_.size(),
90 "]"));
91 }
92 return methods_[ABI47_0_0ReactMethodId].name;
93 }
94
getMethods()95 std::vector<MethodDescriptor> CxxNativeModule::getMethods() {
96 lazyInit();
97
98 std::vector<MethodDescriptor> descs;
99 for (auto &method : methods_) {
100 descs.emplace_back(method.name, method.getType());
101 }
102 return descs;
103 }
104
getConstants()105 folly::dynamic CxxNativeModule::getConstants() {
106 lazyInit();
107
108 if (!module_) {
109 return nullptr;
110 }
111
112 emitWarnIfWarnOnUsage("getConstants()", getName());
113
114 folly::dynamic constants = folly::dynamic::object();
115 for (auto &pair : module_->getConstants()) {
116 constants.insert(std::move(pair.first), std::move(pair.second));
117 }
118 return constants;
119 }
120
invoke(unsigned int ABI47_0_0ReactMethodId,folly::dynamic && params,int callId)121 void CxxNativeModule::invoke(
122 unsigned int ABI47_0_0ReactMethodId,
123 folly::dynamic &¶ms,
124 int callId) {
125 if (ABI47_0_0ReactMethodId >= methods_.size()) {
126 throw std::invalid_argument(folly::to<std::string>(
127 "methodId ",
128 ABI47_0_0ReactMethodId,
129 " out of range [0..",
130 methods_.size(),
131 "]"));
132 }
133 if (!params.isArray()) {
134 throw std::invalid_argument(folly::to<std::string>(
135 "method parameters should be array, but are ", params.typeName()));
136 }
137
138 CxxModule::Callback first;
139 CxxModule::Callback second;
140
141 const auto &method = methods_[ABI47_0_0ReactMethodId];
142
143 if (!method.func) {
144 throw std::runtime_error(folly::to<std::string>(
145 "Method ", method.name, " is synchronous but invoked asynchronously"));
146 }
147
148 emitWarnIfWarnOnUsage(method.name, getName());
149
150 if (params.size() < method.callbacks) {
151 throw std::invalid_argument(folly::to<std::string>(
152 "Expected ",
153 method.callbacks,
154 " callbacks, but only ",
155 params.size(),
156 " parameters provided"));
157 }
158
159 if (method.callbacks == 1) {
160 first = convertCallback(makeCallback(instance_, params[params.size() - 1]));
161 } else if (method.callbacks == 2) {
162 first = convertCallback(makeCallback(instance_, params[params.size() - 2]));
163 second =
164 convertCallback(makeCallback(instance_, params[params.size() - 1]));
165 }
166
167 params.resize(params.size() - method.callbacks);
168
169 // I've got a few flawed options here. I can let the C++ exception
170 // propagate, and the registry will log/convert them to java exceptions.
171 // This lets all the java and red box handling work ok, but the only info I
172 // can capture about the C++ exception is the what() string, not the stack.
173 // I can std::terminate() the app. This causes the full, accurate C++
174 // stack trace to be added to logcat by debuggerd. The java state is lost,
175 // but in practice, the java stack is always the same in this case since
176 // the javascript stack is not visible, and the crash is unfriendly to js
177 // developers, but crucial to C++ developers. The what() value is also
178 // lost. Finally, I can catch, log the java stack, then rethrow the C++
179 // exception. In this case I get java and C++ stack data, but the C++
180 // stack is as of the rethrow, not the original throw, both the C++ and
181 // java stacks always look the same.
182 //
183 // I am going with option 2, since that seems like the most useful
184 // choice. It would be nice to be able to get what() and the C++
185 // stack. I'm told that will be possible in the future. TODO
186 // mhorowitz #7128529: convert C++ exceptions to Java
187
188 messageQueueThread_->runOnQueue(
189 [method, params = std::move(params), first, second, callId]() {
190 #ifdef WITH_FBSYSTRACE
191 if (callId != -1) {
192 fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
193 }
194 #else
195 (void)(callId);
196 #endif
197 SystraceSection s(method.name.c_str());
198 try {
199 method.func(std::move(params), first, second);
200 } catch (const ABI47_0_0facebook::xplat::JsArgumentException &ex) {
201 throw;
202 } catch (std::exception &e) {
203 LOG(ERROR) << "std::exception. Method call " << method.name.c_str()
204 << " failed: " << e.what();
205 std::terminate();
206 } catch (std::string &error) {
207 LOG(ERROR) << "std::string. Method call " << method.name.c_str()
208 << " failed: " << error.c_str();
209 std::terminate();
210 } catch (...) {
211 LOG(ERROR) << "Method call " << method.name.c_str()
212 << " failed. unknown error";
213 std::terminate();
214 }
215 });
216 }
217
callSerializableNativeHook(unsigned int hookId,folly::dynamic && args)218 MethodCallResult CxxNativeModule::callSerializableNativeHook(
219 unsigned int hookId,
220 folly::dynamic &&args) {
221 if (hookId >= methods_.size()) {
222 throw std::invalid_argument(folly::to<std::string>(
223 "methodId ", hookId, " out of range [0..", methods_.size(), "]"));
224 }
225
226 const auto &method = methods_[hookId];
227
228 if (!method.syncFunc) {
229 throw std::runtime_error(folly::to<std::string>(
230 "Method ", method.name, " is asynchronous but invoked synchronously"));
231 }
232
233 emitWarnIfWarnOnUsage(method.name, getName());
234
235 return method.syncFunc(std::move(args));
236 }
237
lazyInit()238 void CxxNativeModule::lazyInit() {
239 if (module_ || !provider_) {
240 return;
241 }
242
243 // TODO 17216751: providers should never return null modules
244 module_ = provider_();
245 provider_ = nullptr;
246 if (module_) {
247 module_->setInstance(instance_);
248 methods_ = module_->getMethods();
249 }
250 }
251
252 } // namespace ABI47_0_0React
253 } // namespace ABI47_0_0facebook
254