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 &&params,
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