diff --git a/ReactCommon/cxxreact/JSCExecutor.cpp b/ReactCommon/cxxreact/JSCExecutor.cpp index 01a68fb4d..c88da5f5b 100644 --- a/ReactCommon/cxxreact/JSCExecutor.cpp +++ b/ReactCommon/cxxreact/JSCExecutor.cpp @@ -2,22 +2,22 @@ #include "JSCExecutor.h" +#include +#include +#include #include #include -#include #include #include #include -#include -#include #include #include #include #include -#include #include #include +#include #include #include #include @@ -44,682 +44,759 @@ #endif namespace facebook { - namespace react { +namespace react { - namespace { +namespace { - template - 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(); - 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 +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(); + if (executor && + executor->getJavaScriptContext()) { // Executor not invalidated + return (executor->*method)(argumentCount, arguments); + } + } catch (...) { + *exception = translatePendingCppExceptionToJSError(ctx, function); } - - template - inline JSObjectGetPropertyCallback exceptionWrapMethod() { - struct funcWrapper { - static JSValueRef call( - JSContextRef ctx, - JSObjectRef object, - JSStringRef propertyName, - JSValueRef *exception) { - try { - auto executor = Object::getGlobalObject(ctx).getPrivate(); - if (executor && executor->getJavaScriptContext()) { // Executor not invalidated - return (executor->*method)(object, propertyName); - } - } catch (...) { - *exception = translatePendingCppExceptionToJSError(ctx, object); - } - return Value::makeUndefined(ctx); - } - }; - - return &funcWrapper::call; - } - - } - -#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); } + }; + + return &funcWrapper::call; +} + +template +inline JSObjectGetPropertyCallback exceptionWrapMethod() { + struct funcWrapper { + static JSValueRef call( + JSContextRef ctx, + JSObjectRef object, + JSStringRef propertyName, + JSValueRef* exception) { + try { + auto executor = Object::getGlobalObject(ctx).getPrivate(); + 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 JSCExecutorFactory::createJSExecutor( - std::shared_ptr delegate, std::shared_ptr jsQueue) { - return folly::make_unique(delegate, jsQueue, m_jscConfig, m_nativeExtensionsProvider); - } +std::unique_ptr JSCExecutorFactory::createJSExecutor( + std::shared_ptr delegate, + std::shared_ptr jsQueue) { + return folly::make_unique( + delegate, jsQueue, m_jscConfig, m_nativeExtensionsProvider); +} - JSCExecutor::JSCExecutor(std::shared_ptr delegate, - std::shared_ptr messageQueueThread, - const folly::dynamic& jscConfig, - NativeExtensionsProvider nativeExtensionsProvider) throw(JSException) : - m_delegate(delegate), - m_messageQueueThread(messageQueueThread), - m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr), - m_jscConfig(jscConfig), - m_nativeExtensionsProvider(nativeExtensionsProvider) { - initOnJSVMThread(); +JSCExecutor::JSCExecutor( + std::shared_ptr delegate, + std::shared_ptr messageQueueThread, + const folly::dynamic& jscConfig, + NativeExtensionsProvider nativeExtensionsProvider) throw(JSException) + : m_delegate(delegate), + m_messageQueueThread(messageQueueThread), + m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr), + m_jscConfig(jscConfig), + m_nativeExtensionsProvider(nativeExtensionsProvider) { + initOnJSVMThread(); - { - SystraceSection s("nativeModuleProxy object"); - installGlobalProxy(m_context, "nativeModuleProxy", - exceptionWrapMethod<&JSCExecutor::getNativeModule>()); - } - if (nativeExtensionsProvider) { - installGlobalProxy(m_context, "nativeExtensions", - exceptionWrapMethod<&JSCExecutor::getNativeExtension>()); - } - } + { + SystraceSection s("nativeModuleProxy object"); + installGlobalProxy( + m_context, + "nativeModuleProxy", + exceptionWrapMethod<&JSCExecutor::getNativeModule>()); + } + if (nativeExtensionsProvider) { + installGlobalProxy( + m_context, + "nativeExtensions", + exceptionWrapMethod<&JSCExecutor::getNativeExtension>()); + } +} - JSCExecutor::~JSCExecutor() { - CHECK(*m_isDestroyed) << "JSCExecutor::destroy() must be called before its destructor!"; - } +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::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); - } +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) { +static bool canUseInspector(JSContextRef context) { #ifdef WITH_INSPECTOR #if defined(__APPLE__) - return isCustomJSCPtr(context); // WITH_INSPECTOR && Apple + return isCustomJSCPtr(context); // WITH_INSPECTOR && Apple #else - return true; // WITH_INSPECTOR && Android + return true; // WITH_INSPECTOR && Android #endif #else - return false; // !WITH_INSPECTOR + return false; // !WITH_INSPECTOR #endif - } +} - static bool canUseSamplingProfiler(JSContextRef context) { +static bool canUseSamplingProfiler(JSContextRef context) { #if defined(__APPLE__) || defined(WITH_JSC_EXTRA_TRACING) - return JSC_JSSamplingProfilerEnabled(context); + return JSC_JSSamplingProfilerEnabled(context); #else - return false; + return false; #endif - } +} - void JSCExecutor::initOnJSVMThread() throw(JSException) { - SystraceSection s("JSCExecutor::initOnJSVMThread"); +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)); - } + const bool useCustomJSC = + m_jscConfig.getDefault("UseCustomJSC", false).getBool(); + if (useCustomJSC) { + JSC_configureJSCForIOS(true, toJson(m_jscConfig)); + } #else - const bool useCustomJSC = false; + const bool useCustomJSC = false; #endif #if defined(WITH_FB_JSC_TUNING) && defined(__ANDROID__) - configureJSCForAndroid(m_jscConfig); + 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); + // 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); + // 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); - }; + 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); - } + auto& globalInspector = facebook::react::getInspectorInstance(); + JSC_JSGlobalContextEnableDebugger( + m_context, globalInspector, ownerId.c_str(), checkIsInspectedRemote); + } - installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate"); - installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook"); + installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>( + "nativeFlushQueueImmediate"); + installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook"); - installGlobalFunction(m_context, "nativeLoggingHook", JSCNativeHooks::loggingHook); - installGlobalFunction(m_context, "nativePerformanceNow", JSCNativeHooks::nowHook); + installGlobalFunction( + m_context, "nativeLoggingHook", JSCNativeHooks::loggingHook); + installGlobalFunction( + m_context, "nativePerformanceNow", JSCNativeHooks::nowHook); #if DEBUG - installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate); + installGlobalFunction( + m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate); #endif - addNativeTracingHooks(m_context); - addNativeTracingLegacyHooks(m_context); - addJSCMemoryHooks(m_context); - addJSCPerfStatsHooks(m_context); + addNativeTracingHooks(m_context); + addNativeTracingLegacyHooks(m_context); + addJSCMemoryHooks(m_context); + addJSCPerfStatsHooks(m_context); - JSCNativeHooks::installPerfHooks(m_context); + JSCNativeHooks::installPerfHooks(m_context); - if (canUseSamplingProfiler(m_context)) { - initSamplingProfilerOnMainJSCThread(m_context); - } - } + if (canUseSamplingProfiler(m_context)) { + initSamplingProfilerOnMainJSCThread(m_context); + } +} - bool JSCExecutor::isNetworkInspected(const std::string &owner, const std::string &app, const std::string &device) { +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(owner, folly::UriEscapeMode::QUERY); - std::string escapedApp = folly::uriEscape(app, folly::UriEscapeMode::QUERY); - std::string escapedDevice = folly::uriEscape(device, folly::UriEscapeMode::QUERY); - std::string msg = folly::to( - "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; + auto connect_socket = [](int socket_desc, std::string address, int port) { + if (socket_desc < 0) { ::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(); + 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; + } - if (canUseInspector(context)) { - auto &globalInspector = facebook::react::getInspectorInstance(); - JSC_JSGlobalContextDisableDebugger(context, globalInspector); + 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; } - - JSC_JSGlobalContextRelease(context); } +#else //! defined(__ANDROID__) + return false; +#endif // defined(__ANDROID__) + } + + std::string escapedOwner = + folly::uriEscape(owner, folly::UriEscapeMode::QUERY); + std::string escapedApp = + folly::uriEscape(app, folly::UriEscapeMode::QUERY); + std::string escapedDevice = + folly::uriEscape(device, folly::UriEscapeMode::QUERY); + std::string msg = folly::to( + "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"; +static const char* explainLoadSourceStatus(JSLoadSourceStatus status) { + switch (status) { + case JSLoadSourceIsCompiled: + return "No error encountered during source load"; - case JSLoadSourceErrorOnRead: - return "Error reading source"; + case JSLoadSourceErrorOnRead: + return "Error reading source"; - case JSLoadSourceIsNotCompiled: - return "Source is not compiled"; + case JSLoadSourceIsNotCompiled: + return "Source is not compiled"; - case JSLoadSourceErrorVersionMismatch: - return "Source version not supported"; + case JSLoadSourceErrorVersionMismatch: + return "Source version not supported"; - default: - return "Bad error code"; - } - } + 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; - } +// 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 script, std::string sourceURL) { - SystraceSection s("JSCExecutor::loadApplicationScript", - "sourceURL", sourceURL); +void JSCExecutor::loadApplicationScript( + std::unique_ptr 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()); + 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 +// TODO t15069155: reduce the number of overrides here #ifdef WITH_FBJSCEXTENSIONS - if (auto fileStr = dynamic_cast(script.get())) { - JSContextLock lock(m_context); - JSLoadSourceStatus jsStatus; - auto bcSourceCode = JSCreateSourceCodeFromFile(fileStr->fd(), jsSourceURL, nullptr, &jsStatus); + if (auto fileStr = dynamic_cast(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; + switch (jsStatus) { + case JSLoadSourceIsCompiled: + if (!bcSourceCode) { + throw std::runtime_error("Unexpected error opening compiled bundle"); } - } -#elif defined(__APPLE__) - BundleHeader header; - memcpy(&header, script->c_str(), std::min(script->size(), sizeof(BundleHeader))); - auto scriptTag = parseTypeFromHeader(header); + evaluateSourceCode(m_context, bcSourceCode, jsSourceURL); - if (scriptTag == ScriptTag::BCBundle) { - using file_ptr = std::unique_ptr; - file_ptr source(fopen(sourceURL.c_str(), "r"), fclose); - int sourceFD = fileno(source.get()); + flush(); - 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 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({})); + ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP); + ReactMarker::logTaggedMarker( + ReactMarker::RUN_JS_BUNDLE_STOP, scriptName.c_str()); 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)); - } + 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); - 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)); + if (scriptTag == ScriptTag::BCBundle) { + using file_ptr = std::unique_ptr; + 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); } - - 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("Error invoking callback ", callbackId))); - } - }(); - callNativeModules(std::move(result)); - } - - void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr 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"; + } 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); } - String JSCExecutor::adoptString(std::unique_ptr script) { + 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 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("Error invoking callback ", callbackId))); + } + }(); + callNativeModules(std::move(result)); +} + +void JSCExecutor::setGlobalVariable( + std::string propName, + std::unique_ptr 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 script) { #if defined(WITH_FBJSCEXTENSIONS) - const JSBigString* string = script.release(); - auto jsString = JSStringCreateAdoptingExternal(string->c_str(), string->size(), (void*)string, [](void* s) { + const JSBigString* string = script.release(); + auto jsString = JSStringCreateAdoptingExternal( + string->c_str(), string->size(), (void*)string, [](void* s) { delete static_cast(s); }); - return String::adopt(m_context, jsString); + 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()); + 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; - } +void* JSCExecutor::getJavaScriptContext() { + return m_context; +} - bool JSCExecutor::isInspectable() { - return canUseInspector(m_context); - } +bool JSCExecutor::isInspectable() { + return canUseInspector(m_context); +} #ifdef WITH_JSC_MEMORY_PRESSURE - void JSCExecutor::handleMemoryPressure(int pressureLevel) { - JSHandleMemoryPressure(this, m_context, static_cast(pressureLevel)); - } +void JSCExecutor::handleMemoryPressure(int pressureLevel) { + JSHandleMemoryPressure( + this, m_context, static_cast(pressureLevel)); +} #endif - void JSCExecutor::flushQueueImmediate(Value&& queue) { - auto queueStr = queue.toJSONString(); - m_delegate->callNativeModules(*this, folly::parseJson(queueStr), false); - } +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(new JSBigStdString(module.code))); - evaluateScript(m_context, source, sourceUrl); - } +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(new JSBigStdString(module.code))); + evaluateScript(m_context, source, sourceUrl); +} - // Native JS hooks - template - void JSCExecutor::installNativeHook(const char* name) { - installGlobalFunction(m_context, name, exceptionWrapMethod()); - } +// Native JS hooks +template +void JSCExecutor::installNativeHook(const char* name) { + installGlobalFunction(m_context, name, exceptionWrapMethod()); +} - JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName) { - if (JSC_JSStringIsEqualToUTF8CString(m_context, propertyName, "name")) { - return Value(m_context, String(m_context, "NativeModules")); - } +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); - } + return m_nativeModules.getModule(m_context, propertyName); +} - JSValueRef JSCExecutor::getNativeExtension(JSObjectRef object, JSStringRef propertyName) { - if (m_nativeExtensionsProvider) { - folly::dynamic value = m_nativeExtensionsProvider(String::ref(m_context, propertyName).str()); - return Value::fromDynamic(m_context, std::move(value)); - } - return JSC_JSValueMakeUndefined(m_context); - } +JSValueRef JSCExecutor::getNativeExtension( + JSObjectRef object, + JSStringRef propertyName) { + if (m_nativeExtensionsProvider) { + folly::dynamic value = + m_nativeExtensionsProvider(String::ref(m_context, propertyName).str()); + return Value::fromDynamic(m_context, std::move(value)); + } + return JSC_JSValueMakeUndefined(m_context); +} - JSValueRef JSCExecutor::nativeRequire(size_t count, const JSValueRef arguments[]) { - if (count > 2 || count == 0) { - throw std::invalid_argument("Got wrong number of args"); - } +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(Value(m_context, arguments[0]).getNumberOrThrow()); - uint32_t bundleId = count == 2 ? folly::to(Value(m_context, arguments[1]).getNumberOrThrow()) : 0; + uint32_t moduleId = + folly::to(Value(m_context, arguments[0]).getNumberOrThrow()); + uint32_t bundleId = count == 2 + ? folly::to(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); - } + 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"); - } +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); - } + 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"); - } +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()); + 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("method parameters should be array, but are ", args.typeName())); - } + if (!args.isArray()) { + throw std::invalid_argument(folly::to( + "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()); - } + 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