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 "ABI48_0_0CxxNativeModule.h"
9 #include "ABI48_0_0Instance.h"
10 
11 #include <folly/json.h>
12 #include <glog/logging.h>
13 #include <iterator>
14 
15 #include "ABI48_0_0JsArgumentHelpers.h"
16 #include "ABI48_0_0MessageQueueThread.h"
17 #include "ABI48_0_0SystraceSection.h"
18 
19 #include <ABI48_0_0logger/ABI48_0_0React_native_log.h>
20 
21 using ABI48_0_0facebook::xplat::module::CxxModule;
22 namespace ABI48_0_0facebook {
23 namespace ABI48_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     ABI48_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 ABI48_0_0ReactMethodId)83 std::string CxxNativeModule::getSyncMethodName(unsigned int ABI48_0_0ReactMethodId) {
84   if (ABI48_0_0ReactMethodId >= methods_.size()) {
85     throw std::invalid_argument(folly::to<std::string>(
86         "methodId ",
87         ABI48_0_0ReactMethodId,
88         " out of range [0..",
89         methods_.size(),
90         "]"));
91   }
92   return methods_[ABI48_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 ABI48_0_0ReactMethodId,folly::dynamic && params,int callId)121 void CxxNativeModule::invoke(
122     unsigned int ABI48_0_0ReactMethodId,
123     folly::dynamic &&params,
124     int callId) {
125   if (ABI48_0_0ReactMethodId >= methods_.size()) {
126     throw std::invalid_argument(folly::to<std::string>(
127         "methodId ",
128         ABI48_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_[ABI48_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   const auto &moduleName = name_;
189   SystraceSection s(
190       "CxxMethodCallQueue", "module", moduleName, "method", method.name);
191   messageQueueThread_->runOnQueue([method,
192                                    moduleName,
193                                    params = std::move(params),
194                                    first,
195                                    second,
196                                    callId]() {
197 #ifdef WITH_FBSYSTRACE
198     if (callId != -1) {
199       fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
200     }
201 #else
202     (void)(callId);
203 #endif
204     SystraceSection s(
205         "CxxMethodCallDispatch", "module", moduleName, "method", method.name);
206     try {
207       method.func(std::move(params), first, second);
208     } catch (const ABI48_0_0facebook::xplat::JsArgumentException &ex) {
209       throw;
210     } catch (std::exception &e) {
211       LOG(ERROR) << "std::exception. Method call " << method.name.c_str()
212                  << " failed: " << e.what();
213       std::terminate();
214     } catch (std::string &error) {
215       LOG(ERROR) << "std::string. Method call " << method.name.c_str()
216                  << " failed: " << error.c_str();
217       std::terminate();
218     } catch (...) {
219       LOG(ERROR) << "Method call " << method.name.c_str()
220                  << " failed. unknown error";
221       std::terminate();
222     }
223   });
224 }
225 
callSerializableNativeHook(unsigned int hookId,folly::dynamic && args)226 MethodCallResult CxxNativeModule::callSerializableNativeHook(
227     unsigned int hookId,
228     folly::dynamic &&args) {
229   if (hookId >= methods_.size()) {
230     throw std::invalid_argument(folly::to<std::string>(
231         "methodId ", hookId, " out of range [0..", methods_.size(), "]"));
232   }
233 
234   const auto &method = methods_[hookId];
235 
236   if (!method.syncFunc) {
237     throw std::runtime_error(folly::to<std::string>(
238         "Method ", method.name, " is asynchronous but invoked synchronously"));
239   }
240 
241   emitWarnIfWarnOnUsage(method.name, getName());
242 
243   return method.syncFunc(std::move(args));
244 }
245 
lazyInit()246 void CxxNativeModule::lazyInit() {
247   if (module_ || !provider_) {
248     return;
249   }
250 
251   // TODO 17216751: providers should never return null modules
252   module_ = provider_();
253   provider_ = nullptr;
254   if (module_) {
255     module_->setInstance(instance_);
256     methods_ = module_->getMethods();
257   }
258 }
259 
260 } // namespace ABI48_0_0React
261 } // namespace ABI48_0_0facebook
262