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