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