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#import "ABI48_0_0RCTNativeModule.h"
9
10#import <ABI48_0_0React/ABI48_0_0RCTBridge.h>
11#import <ABI48_0_0React/ABI48_0_0RCTBridgeMethod.h>
12#import <ABI48_0_0React/ABI48_0_0RCTBridgeModule.h>
13#import <ABI48_0_0React/ABI48_0_0RCTCxxUtils.h>
14#import <ABI48_0_0React/ABI48_0_0RCTFollyConvert.h>
15#import <ABI48_0_0React/ABI48_0_0RCTLog.h>
16#import <ABI48_0_0React/ABI48_0_0RCTProfile.h>
17#import <ABI48_0_0React/ABI48_0_0RCTUtils.h>
18#import <ABI48_0_0Reactperflogger/ABI48_0_0BridgeNativeModulePerfLogger.h>
19
20#ifdef WITH_FBSYSTRACE
21#include <fbsystrace.h>
22#endif
23
24namespace {
25enum SchedulingContext { Sync, Async };
26}
27
28namespace ABI48_0_0facebook {
29namespace ABI48_0_0React {
30
31static MethodCallResult invokeInner(
32    ABI48_0_0RCTBridge *bridge,
33    ABI48_0_0RCTModuleData *moduleData,
34    unsigned int methodId,
35    const folly::dynamic &params,
36    int callId,
37    SchedulingContext context);
38
39ABI48_0_0RCTNativeModule::ABI48_0_0RCTNativeModule(ABI48_0_0RCTBridge *bridge, ABI48_0_0RCTModuleData *moduleData)
40    : m_bridge(bridge), m_moduleData(moduleData)
41{
42}
43
44std::string ABI48_0_0RCTNativeModule::getName()
45{
46  return [m_moduleData.name UTF8String];
47}
48
49std::string ABI48_0_0RCTNativeModule::getSyncMethodName(unsigned int methodId)
50{
51  return m_moduleData.methods[methodId].JSMethodName;
52}
53
54std::vector<MethodDescriptor> ABI48_0_0RCTNativeModule::getMethods()
55{
56  std::vector<MethodDescriptor> descs;
57
58  for (id<ABI48_0_0RCTBridgeMethod> method in m_moduleData.methods) {
59    descs.emplace_back(method.JSMethodName, ABI48_0_0RCTFunctionDescriptorFromType(method.functionType));
60  }
61
62  return descs;
63}
64
65folly::dynamic ABI48_0_0RCTNativeModule::getConstants()
66{
67  ABI48_0_0RCT_PROFILE_BEGIN_EVENT(ABI48_0_0RCTProfileTagAlways, @"[ABI48_0_0RCTNativeModule getConstants] moduleData.exportedConstants", nil);
68  NSDictionary *constants = m_moduleData.exportedConstants;
69  folly::dynamic ret = convertIdToFollyDynamic(constants);
70  ABI48_0_0RCT_PROFILE_END_EVENT(ABI48_0_0RCTProfileTagAlways, @"");
71  return ret;
72}
73
74void ABI48_0_0RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &&params, int callId)
75{
76  id<ABI48_0_0RCTBridgeMethod> method = m_moduleData.methods[methodId];
77  if (method) {
78    ABI48_0_0RCT_PROFILE_BEGIN_EVENT(
79        ABI48_0_0RCTProfileTagAlways,
80        @"[ABI48_0_0RCTNativeModule invoke]",
81        @{@"method" : [NSString stringWithUTF8String:method.JSMethodName]});
82    ABI48_0_0RCT_PROFILE_END_EVENT(ABI48_0_0RCTProfileTagAlways, @"");
83  }
84
85  const char *moduleName = [m_moduleData.name UTF8String];
86  const char *methodName = m_moduleData.methods[methodId].JSMethodName;
87
88  dispatch_queue_t queue = m_moduleData.methodQueue;
89  const bool isSyncModule = queue == ABI48_0_0RCTJSThread;
90
91  if (isSyncModule) {
92    BridgeNativeModulePerfLogger::syncMethodCallStart(moduleName, methodName);
93    BridgeNativeModulePerfLogger::syncMethodCallArgConversionStart(moduleName, methodName);
94  } else {
95    BridgeNativeModulePerfLogger::asyncMethodCallStart(moduleName, methodName);
96  }
97
98  // capture by weak pointer so that we can safely use these variables in a callback
99  __weak ABI48_0_0RCTBridge *weakBridge = m_bridge;
100  __weak ABI48_0_0RCTModuleData *weakModuleData = m_moduleData;
101  // The BatchedBridge version of this buckets all the callbacks by thread, and
102  // queues one block on each.  This is much simpler; we'll see how it goes and
103  // iterate.
104  dispatch_block_t block = [weakBridge, weakModuleData, methodId, params = std::move(params), callId, isSyncModule] {
105#ifdef WITH_FBSYSTRACE
106    if (callId != -1) {
107      fbsystrace_end_async_flow(TRACE_TAG_REACT_APPS, "native", callId);
108    }
109#else
110    (void)(callId);
111#endif
112    @autoreleasepool {
113      invokeInner(weakBridge, weakModuleData, methodId, std::move(params), callId, isSyncModule ? Sync : Async);
114    }
115  };
116
117  if (isSyncModule) {
118    block();
119    BridgeNativeModulePerfLogger::syncMethodCallReturnConversionEnd(moduleName, methodName);
120  } else if (queue) {
121    BridgeNativeModulePerfLogger::asyncMethodCallDispatch(moduleName, methodName);
122    dispatch_async(queue, block);
123  }
124
125#ifdef ABI48_0_0RCT_DEV
126  if (!queue) {
127    ABI48_0_0RCTLog(
128        @"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) without a method queue.",
129        methodId,
130        m_moduleData.name);
131  }
132#endif
133
134  if (isSyncModule) {
135    BridgeNativeModulePerfLogger::syncMethodCallEnd(moduleName, methodName);
136  } else {
137    BridgeNativeModulePerfLogger::asyncMethodCallEnd(moduleName, methodName);
138  }
139}
140
141MethodCallResult ABI48_0_0RCTNativeModule::callSerializableNativeHook(unsigned int ABI48_0_0ReactMethodId, folly::dynamic &&params)
142{
143  return invokeInner(m_bridge, m_moduleData, ABI48_0_0ReactMethodId, params, 0, Sync);
144}
145
146static MethodCallResult invokeInner(
147    ABI48_0_0RCTBridge *bridge,
148    ABI48_0_0RCTModuleData *moduleData,
149    unsigned int methodId,
150    const folly::dynamic &params,
151    int callId,
152    SchedulingContext context)
153{
154  if (!bridge || !bridge.valid || !moduleData) {
155    if (context == Sync) {
156      /**
157       * NOTE: moduleName and methodName are "". This shouldn't be an issue because there can only be one ongoing sync
158       * call at a time, and when we call syncMethodCallFail, that one call should terminate. This is also an
159       * exceptional scenario, so it shouldn't occur often.
160       */
161      BridgeNativeModulePerfLogger::syncMethodCallFail("N/A", "N/A");
162    }
163    return folly::none;
164  }
165
166  id<ABI48_0_0RCTBridgeMethod> method = moduleData.methods[methodId];
167  if (ABI48_0_0RCT_DEBUG && !method) {
168    ABI48_0_0RCTLogError(@"Unknown methodID: %ud for module: %@", methodId, moduleData.name);
169  }
170
171  const char *moduleName = [moduleData.name UTF8String];
172  const char *methodName = moduleData.methods[methodId].JSMethodName;
173
174  if (context == Async) {
175    BridgeNativeModulePerfLogger::asyncMethodCallExecutionStart(moduleName, methodName, (int32_t)callId);
176    BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionStart(moduleName, methodName, (int32_t)callId);
177  }
178
179  NSArray *objcParams = convertFollyDynamicToId(params);
180
181  if (context == Sync) {
182    BridgeNativeModulePerfLogger::syncMethodCallArgConversionEnd(moduleName, methodName);
183  }
184
185  ABI48_0_0RCT_PROFILE_BEGIN_EVENT(
186      ABI48_0_0RCTProfileTagAlways,
187      @"[ABI48_0_0RCTNativeModule invokeInner]",
188      @{@"method" : [NSString stringWithUTF8String:method.JSMethodName]});
189  @try {
190    if (context == Sync) {
191      BridgeNativeModulePerfLogger::syncMethodCallExecutionStart(moduleName, methodName);
192    } else {
193      BridgeNativeModulePerfLogger::asyncMethodCallExecutionArgConversionEnd(moduleName, methodName, (int32_t)callId);
194    }
195
196    id result = [method invokeWithBridge:bridge module:moduleData.instance arguments:objcParams];
197
198    if (context == Sync) {
199      BridgeNativeModulePerfLogger::syncMethodCallExecutionEnd(moduleName, methodName);
200      BridgeNativeModulePerfLogger::syncMethodCallReturnConversionStart(moduleName, methodName);
201    } else {
202      BridgeNativeModulePerfLogger::asyncMethodCallExecutionEnd(moduleName, methodName, (int32_t)callId);
203    }
204
205    return convertIdToFollyDynamic(result);
206  } @catch (NSException *exception) {
207    if (context == Sync) {
208      BridgeNativeModulePerfLogger::syncMethodCallFail(moduleName, methodName);
209    } else {
210      BridgeNativeModulePerfLogger::asyncMethodCallExecutionFail(moduleName, methodName, (int32_t)callId);
211    }
212
213    // Pass on JS exceptions
214    if ([exception.name hasPrefix:ABI48_0_0RCTFatalExceptionName]) {
215      @throw exception;
216    }
217
218#if ABI48_0_0RCT_DEBUG
219    NSString *message = [NSString
220        stringWithFormat:@"Exception '%@' was thrown while invoking %s on target %@ with params %@\ncallstack: %@",
221                         exception,
222                         method.JSMethodName,
223                         moduleData.name,
224                         objcParams,
225                         exception.callStackSymbols];
226    ABI48_0_0RCTFatal(ABI48_0_0RCTErrorWithMessage(message));
227#else
228    ABI48_0_0RCTFatalException(exception);
229#endif
230  } @finally {
231    ABI48_0_0RCT_PROFILE_END_EVENT(ABI48_0_0RCTProfileTagAlways, @"");
232  }
233
234  return folly::none;
235}
236
237}
238}
239