// Copyright (c) 2004-present, Facebook, Inc.

// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

#include "JSCExecutor.h"

#include <fcntl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <algorithm>
#include <condition_variable>
#include <mutex>
#include <sstream>
#include <string>
#include <system_error>

#include <arpa/inet.h>
#include <folly/Conv.h>
#include <folly/Exception.h>
#include <folly/Memory.h>
#include <folly/String.h>
#include <folly/json.h>
#include <glog/logging.h>
#include <jschelpers/JSCHelpers.h>
#include <jschelpers/Value.h>
#include <jsinspector/InspectorInterfaces.h>

#include "JSBigString.h"
#include "JSBundleType.h"
#include "JSCLegacyTracing.h"
#include "JSCMemory.h"
#include "JSCPerfStats.h"
#include "JSCSamplingProfiler.h"
#include "JSCTracing.h"
#include "JSCUtils.h"
#include "JSModulesUnbundle.h"
#include "MessageQueueThread.h"
#include "ModuleRegistry.h"
#include "Platform.h"
#include "RAMBundleRegistry.h"
#include "RecoverableError.h"
#include "SystraceSection.h"

#if defined(WITH_FB_JSC_TUNING) && defined(__ANDROID__)
#include <jsc_config_android.h>
#endif

namespace facebook {
namespace react {

namespace {

template <JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
inline JSObjectCallAsFunctionCallback exceptionWrapMethod() {
  struct funcWrapper {
    static JSValueRef call(
        JSContextRef ctx,
        JSObjectRef function,
        JSObjectRef thisObject,
        size_t argumentCount,
        const JSValueRef arguments[],
        JSValueRef* exception) {
      try {
        auto executor = Object::getGlobalObject(ctx).getPrivate<JSCExecutor>();
        if (executor &&
            executor->getJavaScriptContext()) { // Executor not invalidated
          return (executor->*method)(argumentCount, arguments);
        }
      } catch (...) {
        *exception = translatePendingCppExceptionToJSError(ctx, function);
      }
      return Value::makeUndefined(ctx);
    }
  };

  return &funcWrapper::call;
}

template <JSValueRef (
    JSCExecutor::*method)(JSObjectRef object, JSStringRef propertyName)>
inline JSObjectGetPropertyCallback exceptionWrapMethod() {
  struct funcWrapper {
    static JSValueRef call(
        JSContextRef ctx,
        JSObjectRef object,
        JSStringRef propertyName,
        JSValueRef* exception) {
      try {
        auto executor = Object::getGlobalObject(ctx).getPrivate<JSCExecutor>();
        if (executor &&
            executor->getJavaScriptContext()) { // Executor not invalidated
          return (executor->*method)(object, propertyName);
        }
      } catch (...) {
        *exception = translatePendingCppExceptionToJSError(ctx, object);
      }
      return Value::makeUndefined(ctx);
    }
  };

  return &funcWrapper::call;
}

} // namespace

#if DEBUG
static JSValueRef nativeInjectHMRUpdate(
    JSContextRef ctx,
    JSObjectRef function,
    JSObjectRef thisObject,
    size_t argumentCount,
    const JSValueRef arguments[],
    JSValueRef* exception) {
  String execJSString = Value(ctx, arguments[0]).toString();
  String jsURL = Value(ctx, arguments[1]).toString();
  evaluateScript(ctx, execJSString, jsURL);
  return Value::makeUndefined(ctx);
}
#endif

std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
    std::shared_ptr<ExecutorDelegate> delegate,
    std::shared_ptr<MessageQueueThread> jsQueue) {
  return folly::make_unique<JSCExecutor>(
      delegate, jsQueue, m_jscConfig);
}

JSCExecutor::JSCExecutor(
    std::shared_ptr<ExecutorDelegate> delegate,
    std::shared_ptr<MessageQueueThread> messageQueueThread,
    const folly::dynamic& jscConfig) throw(JSException)
    : m_delegate(delegate),
      m_messageQueueThread(messageQueueThread),
      m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr),
      m_jscConfig(jscConfig) {
  initOnJSVMThread();

  {
    SystraceSection s("nativeModuleProxy object");
    installGlobalProxy(
        m_context,
        "nativeModuleProxy",
        exceptionWrapMethod<&JSCExecutor::getNativeModule>());
  }
}

JSCExecutor::~JSCExecutor() {
  CHECK(*m_isDestroyed)
      << "JSCExecutor::destroy() must be called before its destructor!";
}

void JSCExecutor::destroy() {
  *m_isDestroyed = true;
  if (m_messageQueueThread.get()) {
    m_messageQueueThread->runOnQueueSync([this]() { terminateOnJSVMThread(); });
  } else {
    terminateOnJSVMThread();
  }
}

void JSCExecutor::setContextName(const std::string& name) {
  String jsName = String(m_context, name.c_str());
  JSC_JSGlobalContextSetName(m_context, jsName);
}

static bool canUseInspector(JSContextRef context) {
#ifdef WITH_INSPECTOR
#if defined(__APPLE__)
  return isCustomJSCPtr(context); // WITH_INSPECTOR && Apple
#else
  return true; // WITH_INSPECTOR && Android
#endif
#else
  return false; // !WITH_INSPECTOR
#endif
}

static bool canUseSamplingProfiler(JSContextRef context) {
#if defined(__APPLE__) || defined(WITH_JSC_EXTRA_TRACING)
  return JSC_JSSamplingProfilerEnabled(context);
#else
  return false;
#endif
}

void JSCExecutor::initOnJSVMThread() throw(JSException) {
  SystraceSection s("JSCExecutor::initOnJSVMThread");

#if defined(__APPLE__)
  const bool useCustomJSC =
      m_jscConfig.getDefault("UseCustomJSC", false).getBool();
  if (useCustomJSC) {
    JSC_configureJSCForIOS(true, toJson(m_jscConfig));
  }
#else
  const bool useCustomJSC = false;
#endif

#if defined(WITH_FB_JSC_TUNING) && defined(__ANDROID__)
  configureJSCForAndroid(m_jscConfig);
#endif

  // Create a custom global class, so we can store data in it later using
  // JSObjectSetPrivate
  JSClassRef globalClass = nullptr;
  {
    SystraceSection s_("JSClassCreate");
    JSClassDefinition definition = kJSClassDefinitionEmpty;
    definition.attributes |= kJSClassAttributeNoAutomaticPrototype;
    globalClass = JSC_JSClassCreate(useCustomJSC, &definition);
  }
  {
    SystraceSection s_("JSGlobalContextCreateInGroup");
    m_context =
        JSC_JSGlobalContextCreateInGroup(useCustomJSC, nullptr, globalClass);
  }
  JSC_JSClassRelease(useCustomJSC, globalClass);

  // Add a pointer to ourselves so we can retrieve it later in our hooks
  Object::getGlobalObject(m_context).setPrivate(this);

  if (canUseInspector(m_context)) {
    const std::string ownerId =
        m_jscConfig.getDefault("OwnerIdentity", "unknown").getString();
    const std::string appId =
        m_jscConfig.getDefault("AppIdentity", "unknown").getString();
    const std::string deviceId =
        m_jscConfig.getDefault("DeviceIdentity", "unknown").getString();
    auto checkIsInspectedRemote = [ownerId, appId, deviceId]() {
      return isNetworkInspected(ownerId, appId, deviceId);
    };

    auto& globalInspector = facebook::react::getInspectorInstance();
    JSC_JSGlobalContextEnableDebugger(
        m_context, globalInspector, ownerId.c_str(), checkIsInspectedRemote);
  }

  installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>(
      "nativeFlushQueueImmediate");
  installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");

  installGlobalFunction(
      m_context, "nativeLoggingHook", JSCNativeHooks::loggingHook);
  installGlobalFunction(
      m_context, "nativePerformanceNow", JSCNativeHooks::nowHook);

#if DEBUG
  installGlobalFunction(
      m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
#endif

  addNativeTracingHooks(m_context);
  addNativeTracingLegacyHooks(m_context);
  addJSCMemoryHooks(m_context);
  addJSCPerfStatsHooks(m_context);

  JSCNativeHooks::installPerfHooks(m_context);

  if (canUseSamplingProfiler(m_context)) {
    initSamplingProfilerOnMainJSCThread(m_context);
  }
}

bool JSCExecutor::isNetworkInspected(
    const std::string& owner,
    const std::string& app,
    const std::string& device) {
#ifdef WITH_FB_DBG_ATTACH_BEFORE_EXEC
  auto connect_socket = [](int socket_desc, std::string address, int port) {
    if (socket_desc < 0) {
      ::close(socket_desc);
      return false;
    }

    struct timeval tv;
    tv.tv_sec = 1;
    tv.tv_usec = 0;
    auto sock_opt_rcv_resp = setsockopt(
        socket_desc,
        SOL_SOCKET,
        SO_RCVTIMEO,
        (const char*)&tv,
        sizeof(struct timeval));
    if (sock_opt_rcv_resp < 0) {
      ::close(socket_desc);
      return false;
    }

    auto sock_opt_snd_resp = setsockopt(
        socket_desc,
        SOL_SOCKET,
        SO_SNDTIMEO,
        (const char*)&tv,
        sizeof(struct timeval));
    if (sock_opt_snd_resp < 0) {
      ::close(socket_desc);
      return false;
    }

    struct sockaddr_in server;
    server.sin_addr.s_addr = inet_addr(address.c_str());
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    auto connect_resp =
        ::connect(socket_desc, (struct sockaddr*)&server, sizeof(server));
    if (connect_resp < 0) {
      ::close(socket_desc);
      return false;
    }

    return true;
  };

  int socket_desc = socket(AF_INET, SOCK_STREAM, 0);

  if (!connect_socket(socket_desc, "127.0.0.1", 8082)) {
#if defined(__ANDROID__)
    socket_desc = socket(AF_INET, SOCK_STREAM, 0);
    if (!connect_socket(socket_desc, "10.0.2.2", 8082) /* emulator */) {
      socket_desc = socket(AF_INET, SOCK_STREAM, 0);
      if (!connect_socket(socket_desc, "10.0.3.2", 8082) /* genymotion */) {
        return false;
      }
    }
#else //! defined(__ANDROID__)
    return false;
#endif // defined(__ANDROID__)
  }

  std::string escapedOwner =
      folly::uriEscape<std::string>(owner, folly::UriEscapeMode::QUERY);
  std::string escapedApp =
      folly::uriEscape<std::string>(app, folly::UriEscapeMode::QUERY);
  std::string escapedDevice =
      folly::uriEscape<std::string>(device, folly::UriEscapeMode::QUERY);
  std::string msg = folly::to<std::string>(
      "GET /autoattach?title=",
      escapedOwner,
      "&app=",
      escapedApp,
      "&device=",
      escapedDevice,
      " HTTP/1.1\r\n\r\n");
  auto send_resp = ::send(socket_desc, msg.c_str(), msg.length(), 0);
  if (send_resp < 0) {
    ::close(socket_desc);
    return false;
  }

  char server_reply[200];
  server_reply[199] = '\0';
  auto recv_resp =
      ::recv(socket_desc, server_reply, sizeof(server_reply) - 1, 0);
  if (recv_resp < 0) {
    ::close(socket_desc);
    return false;
  }

  std::string response(server_reply);
  if (response.size() < 25) {
    ::close(socket_desc);
    return false;
  }
  auto responseCandidate = response.substr(response.size() - 25);
  auto found =
      responseCandidate.find("{\"autoattach\":true}") != std::string::npos;
  ::close(socket_desc);
  return found;
#else //! WITH_FB_DBG_ATTACH_BEFORE_EXEC
  return false;
#endif // WITH_FB_DBG_ATTACH_BEFORE_EXEC
}

void JSCExecutor::terminateOnJSVMThread() {
  JSGlobalContextRef context = m_context;
  m_context = nullptr;
  Object::getGlobalObject(context).setPrivate(nullptr);
  m_nativeModules.reset();

  if (canUseInspector(context)) {
    auto& globalInspector = facebook::react::getInspectorInstance();
    JSC_JSGlobalContextDisableDebugger(context, globalInspector);
  }

  JSC_JSGlobalContextRelease(context);
}

#ifdef WITH_FBJSCEXTENSIONS
static const char* explainLoadSourceStatus(JSLoadSourceStatus status) {
  switch (status) {
    case JSLoadSourceIsCompiled:
      return "No error encountered during source load";

    case JSLoadSourceErrorOnRead:
      return "Error reading source";

    case JSLoadSourceIsNotCompiled:
      return "Source is not compiled";

    case JSLoadSourceErrorVersionMismatch:
      return "Source version not supported";

    default:
      return "Bad error code";
  }
}
#endif

// basename_r isn't in all iOS SDKs, so use this simple version instead.
static std::string simpleBasename(const std::string& path) {
  size_t pos = path.rfind("/");
  return (pos != std::string::npos) ? path.substr(pos) : path;
}

void JSCExecutor::loadApplicationScript(
    std::unique_ptr<const JSBigString> script,
    std::string sourceURL) {
  SystraceSection s(
      "JSCExecutor::loadApplicationScript", "sourceURL", sourceURL);

  std::string scriptName = simpleBasename(sourceURL);
  ReactMarker::logTaggedMarker(
      ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
  String jsSourceURL(m_context, sourceURL.c_str());

// TODO t15069155: reduce the number of overrides here
#ifdef WITH_FBJSCEXTENSIONS
  if (auto fileStr = dynamic_cast<const JSBigFileString*>(script.get())) {
    JSContextLock lock(m_context);
    JSLoadSourceStatus jsStatus;
    auto bcSourceCode = JSCreateSourceCodeFromFile(
        fileStr->fd(), jsSourceURL, nullptr, &jsStatus);

    switch (jsStatus) {
      case JSLoadSourceIsCompiled:
        if (!bcSourceCode) {
          throw std::runtime_error("Unexpected error opening compiled bundle");
        }
        evaluateSourceCode(m_context, bcSourceCode, jsSourceURL);

        flush();

        ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
        ReactMarker::logTaggedMarker(
            ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
        return;

      case JSLoadSourceErrorVersionMismatch:
        throw RecoverableError(explainLoadSourceStatus(jsStatus));

      case JSLoadSourceErrorOnRead:
      case JSLoadSourceIsNotCompiled:
        // Not bytecode, fall through.
        break;
    }
  }
#elif defined(__APPLE__)
  BundleHeader header;
  memcpy(
      &header, script->c_str(), std::min(script->size(), sizeof(BundleHeader)));
  auto scriptTag = parseTypeFromHeader(header);

  if (scriptTag == ScriptTag::BCBundle) {
    using file_ptr = std::unique_ptr<FILE, decltype(&fclose)>;
    file_ptr source(fopen(sourceURL.c_str(), "r"), fclose);
    int sourceFD = fileno(source.get());

    JSValueRef jsError;
    JSValueRef result = JSC_JSEvaluateBytecodeBundle(
        m_context, NULL, sourceFD, jsSourceURL, &jsError);
    if (result == nullptr) {
      throw JSException(m_context, jsError, jsSourceURL);
    }
  } else
#endif
  {
    String jsScript;
    JSContextLock lock(m_context);
    {
      SystraceSection s_(
          "JSCExecutor::loadApplicationScript-createExpectingAscii");
      ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_START);
      jsScript = adoptString(std::move(script));
      ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP);
    }

    SystraceSection s_("JSCExecutor::loadApplicationScript-evaluateScript");
    evaluateScript(m_context, jsScript, jsSourceURL);
  }

  flush();

  ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
  ReactMarker::logTaggedMarker(
      ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str());
}

void JSCExecutor::setBundleRegistry(
    std::unique_ptr<RAMBundleRegistry> bundleRegistry) {
  if (!m_bundleRegistry) {
    installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");
  }
  m_bundleRegistry = std::move(bundleRegistry);
}

void JSCExecutor::registerBundle(
    uint32_t bundleId,
    const std::string& bundlePath) {
  if (m_bundleRegistry) {
    m_bundleRegistry->registerBundle(bundleId, bundlePath);
  } else {
    auto stPath = JSCExecutor::getSyntheticBundlePath(bundleId, bundlePath);
    auto sourceUrlStr = String(m_context, stPath.c_str());
    auto source = adoptString(JSBigFileString::fromPath(bundlePath));
    evaluateScript(m_context, source, sourceUrlStr);
  }
}

void JSCExecutor::bindBridge() throw(JSException) {
  SystraceSection s("JSCExecutor::bindBridge");
  std::call_once(m_bindFlag, [this] {
    auto global = Object::getGlobalObject(m_context);
    auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
    if (batchedBridgeValue.isUndefined()) {
      auto requireBatchedBridge =
          global.getProperty("__fbRequireBatchedBridge");
      if (!requireBatchedBridge.isUndefined()) {
        batchedBridgeValue = requireBatchedBridge.asObject().callAsFunction({});
      }
      if (batchedBridgeValue.isUndefined()) {
        throw JSException(
            "Could not get BatchedBridge, make sure your bundle is packaged correctly");
      }
    }

    auto batchedBridge = batchedBridgeValue.asObject();
    m_callFunctionReturnFlushedQueueJS =
        batchedBridge.getProperty("callFunctionReturnFlushedQueue").asObject();
    m_invokeCallbackAndReturnFlushedQueueJS =
        batchedBridge.getProperty("invokeCallbackAndReturnFlushedQueue")
            .asObject();
    m_flushedQueueJS = batchedBridge.getProperty("flushedQueue").asObject();
    m_callFunctionReturnResultAndFlushedQueueJS =
        batchedBridge.getProperty("callFunctionReturnResultAndFlushedQueue")
            .asObject();
  });
}

void JSCExecutor::callNativeModules(Value&& value) {
  SystraceSection s("JSCExecutor::callNativeModules");
  // If this fails, you need to pass a fully functional delegate with a
  // module registry to the factory/ctor.
  CHECK(m_delegate) << "Attempting to use native modules without a delegate";
  try {
    auto calls = value.toJSONString();
    m_delegate->callNativeModules(*this, folly::parseJson(calls), true);
  } catch (...) {
    std::string message = "Error in callNativeModules()";
    try {
      message += ":" + value.toString().str();
    } catch (...) {
      // ignored
    }
    std::throw_with_nested(std::runtime_error(message));
  }
}

void JSCExecutor::flush() {
  SystraceSection s("JSCExecutor::flush");

  if (m_flushedQueueJS) {
    callNativeModules(m_flushedQueueJS->callAsFunction({}));
    return;
  }

  // When a native module is called from JS, BatchedBridge.enqueueNativeCall()
  // is invoked.  For that to work, require('BatchedBridge') has to be called,
  // and when that happens, __fbBatchedBridge is set as a side effect.
  auto global = Object::getGlobalObject(m_context);
  auto batchedBridgeValue = global.getProperty("__fbBatchedBridge");
  // So here, if __fbBatchedBridge doesn't exist, then we know no native calls
  // have happened, and we were able to determine this without forcing
  // BatchedBridge to be loaded as a side effect.
  if (!batchedBridgeValue.isUndefined()) {
    // If calls were made, we bind to the JS bridge methods, and use them to
    // get the pending queue of native calls.
    bindBridge();
    callNativeModules(m_flushedQueueJS->callAsFunction({}));
  } else if (m_delegate) {
    // If we have a delegate, we need to call it; we pass a null list to
    // callNativeModules, since we know there are no native calls, without
    // calling into JS again.  If no calls were made and there's no delegate,
    // nothing happens, which is correct.
    callNativeModules(Value::makeNull(m_context));
  }
}

void JSCExecutor::callFunction(
    const std::string& moduleId,
    const std::string& methodId,
    const folly::dynamic& arguments) {
  SystraceSection s("JSCExecutor::callFunction");
  // This weird pattern is because Value is not default constructible.
  // The lambda is inlined, so there's no overhead.
  auto result = [&] {
    JSContextLock lock(m_context);
    try {
      if (!m_callFunctionReturnResultAndFlushedQueueJS) {
        bindBridge();
      }
      return m_callFunctionReturnFlushedQueueJS->callAsFunction(
          {Value(m_context, String::createExpectingAscii(m_context, moduleId)),
           Value(m_context, String::createExpectingAscii(m_context, methodId)),
           Value::fromDynamic(m_context, std::move(arguments))});
    } catch (...) {
      std::throw_with_nested(
          std::runtime_error("Error calling " + moduleId + "." + methodId));
    }
  }();
  callNativeModules(std::move(result));
}

void JSCExecutor::invokeCallback(
    const double callbackId,
    const folly::dynamic& arguments) {
  SystraceSection s("JSCExecutor::invokeCallback");
  auto result = [&] {
    JSContextLock lock(m_context);
    try {
      if (!m_invokeCallbackAndReturnFlushedQueueJS) {
        bindBridge();
      }
      return m_invokeCallbackAndReturnFlushedQueueJS->callAsFunction(
          {Value::makeNumber(m_context, callbackId),
           Value::fromDynamic(m_context, std::move(arguments))});
    } catch (...) {
      std::throw_with_nested(std::runtime_error(
          folly::to<std::string>("Error invoking callback ", callbackId)));
    }
  }();
  callNativeModules(std::move(result));
}

void JSCExecutor::setGlobalVariable(
    std::string propName,
    std::unique_ptr<const JSBigString> jsonValue) {
  try {
    SystraceSection s("JSCExecutor::setGlobalVariable", "propName", propName);
    auto valueToInject = Value::fromJSON(adoptString(std::move(jsonValue)));
    Object::getGlobalObject(m_context).setProperty(
        propName.c_str(), valueToInject);
  } catch (...) {
    std::throw_with_nested(
        std::runtime_error("Error setting global variable: " + propName));
  }
}

std::string JSCExecutor::getDescription() {
#if defined(__APPLE__)
  if (isCustomJSCPtr(m_context)) {
    return "Custom JSC";
  } else {
    return "System JSC";
  }
#else
  return "JSC";
#endif
}

String JSCExecutor::adoptString(std::unique_ptr<const JSBigString> script) {
#if defined(WITH_FBJSCEXTENSIONS)
  const JSBigString* string = script.release();
  auto jsString = JSStringCreateAdoptingExternal(
      string->c_str(), string->size(), (void*)string, [](void* s) {
        delete static_cast<JSBigString*>(s);
      });
  return String::adopt(m_context, jsString);
#else
  return script->isAscii()
      ? String::createExpectingAscii(m_context, script->c_str(), script->size())
      : String(m_context, script->c_str());
#endif
}

void* JSCExecutor::getJavaScriptContext() {
  return m_context;
}

bool JSCExecutor::isInspectable() {
  return canUseInspector(m_context);
}

void JSCExecutor::handleMemoryPressure(int pressureLevel) {
#ifdef WITH_JSC_MEMORY_PRESSURE
  JSHandleMemoryPressure(
      this, m_context, static_cast<JSMemoryPressure>(pressureLevel));
#endif
}

void JSCExecutor::flushQueueImmediate(Value&& queue) {
  auto queueStr = queue.toJSONString();
  m_delegate->callNativeModules(*this, folly::parseJson(queueStr), false);
}

void JSCExecutor::loadModule(uint32_t bundleId, uint32_t moduleId) {
  auto module = m_bundleRegistry->getModule(bundleId, moduleId);
  auto sourceUrl = String::createExpectingAscii(m_context, module.name);
  auto source = adoptString(
      std::unique_ptr<JSBigString>(new JSBigStdString(module.code)));
  evaluateScript(m_context, source, sourceUrl);
}

// Native JS hooks
template <JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
void JSCExecutor::installNativeHook(const char* name) {
  installGlobalFunction(m_context, name, exceptionWrapMethod<method>());
}

JSValueRef JSCExecutor::getNativeModule(
    JSObjectRef object,
    JSStringRef propertyName) {
  if (JSC_JSStringIsEqualToUTF8CString(m_context, propertyName, "name")) {
    return Value(m_context, String(m_context, "NativeModules"));
  }

  return m_nativeModules.getModule(m_context, propertyName);
}

JSValueRef JSCExecutor::nativeRequire(
    size_t count,
    const JSValueRef arguments[]) {
  if (count > 2 || count == 0) {
    throw std::invalid_argument("Got wrong number of args");
  }

  uint32_t moduleId =
      folly::to<uint32_t>(Value(m_context, arguments[0]).getNumberOrThrow());
  uint32_t bundleId = count == 2
      ? folly::to<uint32_t>(Value(m_context, arguments[1]).getNumberOrThrow())
      : 0;

  ReactMarker::logMarker(ReactMarker::NATIVE_REQUIRE_START);
  loadModule(bundleId, moduleId);
  ReactMarker::logMarker(ReactMarker::NATIVE_REQUIRE_STOP);
  return Value::makeUndefined(m_context);
}

JSValueRef JSCExecutor::nativeFlushQueueImmediate(
    size_t argumentCount,
    const JSValueRef arguments[]) {
  if (argumentCount != 1) {
    throw std::invalid_argument("Got wrong number of args");
  }

  flushQueueImmediate(Value(m_context, arguments[0]));
  return Value::makeUndefined(m_context);
}

JSValueRef JSCExecutor::nativeCallSyncHook(
    size_t argumentCount,
    const JSValueRef arguments[]) {
  if (argumentCount != 3) {
    throw std::invalid_argument("Got wrong number of args");
  }

  unsigned int moduleId = Value(m_context, arguments[0]).asUnsignedInteger();
  unsigned int methodId = Value(m_context, arguments[1]).asUnsignedInteger();
  folly::dynamic args =
      folly::parseJson(Value(m_context, arguments[2]).toJSONString());

  if (!args.isArray()) {
    throw std::invalid_argument(folly::to<std::string>(
        "method parameters should be array, but are ", args.typeName()));
  }

  MethodCallResult result = m_delegate->callSerializableNativeHook(
      *this, moduleId, methodId, std::move(args));
  if (!result.hasValue()) {
    return Value::makeUndefined(m_context);
  }
  return Value::fromDynamic(m_context, result.value());
}

} // namespace react
} // namespace facebook
