2016-05-04 02:29:58 +00:00
|
|
|
// Copyright 2004-present Facebook. All Rights Reserved.
|
|
|
|
|
|
|
|
#include "JSCExecutor.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <condition_variable>
|
|
|
|
#include <mutex>
|
|
|
|
#include <sstream>
|
|
|
|
#include <string>
|
|
|
|
#include <glog/logging.h>
|
|
|
|
#include <folly/json.h>
|
2016-09-29 16:08:28 +00:00
|
|
|
#include <folly/Exception.h>
|
2016-05-14 00:15:06 +00:00
|
|
|
#include <folly/Memory.h>
|
2016-05-12 21:32:42 +00:00
|
|
|
#include <folly/Conv.h>
|
2016-09-29 16:08:28 +00:00
|
|
|
#include <fcntl.h>
|
2016-05-04 02:29:58 +00:00
|
|
|
#include <sys/time.h>
|
2016-12-17 12:46:50 +00:00
|
|
|
#include <system_error>
|
2016-05-04 02:29:58 +00:00
|
|
|
|
2016-11-01 18:38:43 +00:00
|
|
|
#include <jschelpers/JSCHelpers.h>
|
|
|
|
#include <jschelpers/Value.h>
|
2017-04-06 12:50:28 +00:00
|
|
|
|
2017-06-23 23:49:55 +00:00
|
|
|
#include "JSBigString.h"
|
2017-02-15 15:09:20 +00:00
|
|
|
#include "JSBundleType.h"
|
2016-05-04 02:29:58 +00:00
|
|
|
#include "Platform.h"
|
2016-05-14 00:15:06 +00:00
|
|
|
#include "SystraceSection.h"
|
2016-10-11 14:19:31 +00:00
|
|
|
#include "JSCNativeModules.h"
|
2016-07-15 18:51:10 +00:00
|
|
|
#include "JSCSamplingProfiler.h"
|
2016-11-01 18:38:43 +00:00
|
|
|
#include "JSCUtils.h"
|
2016-10-03 12:07:41 +00:00
|
|
|
#include "JSModulesUnbundle.h"
|
|
|
|
#include "ModuleRegistry.h"
|
2016-12-17 12:46:50 +00:00
|
|
|
#include "RecoverableError.h"
|
2016-07-15 18:51:10 +00:00
|
|
|
|
2017-06-23 23:49:55 +00:00
|
|
|
#ifdef WITH_INSPECTOR
|
|
|
|
#include <jschelpers/InspectorInterfaces.h>
|
|
|
|
#endif
|
|
|
|
|
2017-02-01 22:10:39 +00:00
|
|
|
#if defined(WITH_JSC_EXTRA_TRACING) || (DEBUG && defined(WITH_FBSYSTRACE))
|
2016-05-04 02:29:58 +00:00
|
|
|
#include "JSCTracing.h"
|
2016-05-14 00:15:03 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_JSC_EXTRA_TRACING
|
|
|
|
#include "JSCLegacyTracing.h"
|
2017-01-11 17:29:50 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if !defined(__APPLE__) && defined(WITH_JSC_EXTRA_TRACING)
|
2016-05-04 02:29:58 +00:00
|
|
|
#include <JavaScriptCore/API/JSProfilerPrivate.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_JSC_MEMORY_PRESSURE
|
|
|
|
#include <jsc_memory.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_FB_MEMORY_PROFILING
|
|
|
|
#include "JSCMemory.h"
|
|
|
|
#endif
|
|
|
|
|
2016-08-10 11:16:39 +00:00
|
|
|
#if defined(WITH_FB_JSC_TUNING) && defined(__ANDROID__)
|
2016-05-04 02:29:58 +00:00
|
|
|
#include <jsc_config_android.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef JSC_HAS_PERF_STATS_API
|
|
|
|
#include "JSCPerfStats.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace react {
|
|
|
|
|
2016-05-12 21:32:37 +00:00
|
|
|
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 {
|
2016-11-18 14:25:24 +00:00
|
|
|
auto executor = Object::getGlobalObject(ctx).getPrivate<JSCExecutor>();
|
2017-06-06 18:30:40 +00:00
|
|
|
if (executor && executor->getJavaScriptContext()) { // Executor not invalidated
|
|
|
|
return (executor->*method)(argumentCount, arguments);
|
|
|
|
}
|
2016-05-12 21:32:37 +00:00
|
|
|
} catch (...) {
|
2016-07-07 11:48:45 +00:00
|
|
|
*exception = translatePendingCppExceptionToJSError(ctx, function);
|
2016-05-12 21:32:37 +00:00
|
|
|
}
|
2017-06-06 18:30:40 +00:00
|
|
|
return Value::makeUndefined(ctx);
|
2016-05-12 21:32:37 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return &funcWrapper::call;
|
|
|
|
}
|
|
|
|
|
2016-10-11 14:19:31 +00:00
|
|
|
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 {
|
2016-11-18 14:25:24 +00:00
|
|
|
auto executor = Object::getGlobalObject(ctx).getPrivate<JSCExecutor>();
|
2017-06-06 18:30:40 +00:00
|
|
|
if (executor && executor->getJavaScriptContext()) { // Executor not invalidated
|
|
|
|
return (executor->*method)(object, propertyName);
|
|
|
|
}
|
2016-10-11 14:19:31 +00:00
|
|
|
} catch (...) {
|
|
|
|
*exception = translatePendingCppExceptionToJSError(ctx, object);
|
|
|
|
}
|
2017-06-06 18:30:40 +00:00
|
|
|
return Value::makeUndefined(ctx);
|
2016-10-11 14:19:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return &funcWrapper::call;
|
|
|
|
}
|
|
|
|
|
2016-05-12 21:32:37 +00:00
|
|
|
}
|
|
|
|
|
2016-09-30 19:18:58 +00:00
|
|
|
#if DEBUG
|
2016-05-04 02:29:58 +00:00
|
|
|
static JSValueRef nativeInjectHMRUpdate(
|
|
|
|
JSContextRef ctx,
|
|
|
|
JSObjectRef function,
|
|
|
|
JSObjectRef thisObject,
|
|
|
|
size_t argumentCount,
|
|
|
|
const JSValueRef arguments[],
|
2016-09-30 19:18:58 +00:00
|
|
|
JSValueRef *exception) {
|
|
|
|
String execJSString = Value(ctx, arguments[0]).toString();
|
|
|
|
String jsURL = Value(ctx, arguments[1]).toString();
|
|
|
|
evaluateScript(ctx, execJSString, jsURL);
|
2016-11-18 14:25:24 +00:00
|
|
|
return Value::makeUndefined(ctx);
|
2016-09-30 19:18:58 +00:00
|
|
|
}
|
|
|
|
#endif
|
2016-05-04 02:29:58 +00:00
|
|
|
|
|
|
|
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
|
2016-05-18 19:46:01 +00:00
|
|
|
std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<MessageQueueThread> jsQueue) {
|
2017-04-25 12:29:45 +00:00
|
|
|
return folly::make_unique<JSCExecutor>(delegate, jsQueue, m_jscConfig);
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-18 19:46:01 +00:00
|
|
|
JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
|
|
|
|
std::shared_ptr<MessageQueueThread> messageQueueThread,
|
2016-06-16 23:00:22 +00:00
|
|
|
const folly::dynamic& jscConfig) throw(JSException) :
|
2016-05-18 19:46:01 +00:00
|
|
|
m_delegate(delegate),
|
2016-05-04 02:29:58 +00:00
|
|
|
m_messageQueueThread(messageQueueThread),
|
2016-10-31 21:59:29 +00:00
|
|
|
m_nativeModules(delegate ? delegate->getModuleRegistry() : nullptr),
|
2016-05-04 02:29:58 +00:00
|
|
|
m_jscConfig(jscConfig) {
|
|
|
|
initOnJSVMThread();
|
2016-05-18 19:46:01 +00:00
|
|
|
|
|
|
|
{
|
2016-10-11 14:19:31 +00:00
|
|
|
SystraceSection s("nativeModuleProxy object");
|
|
|
|
installGlobalProxy(m_context, "nativeModuleProxy",
|
|
|
|
exceptionWrapMethod<&JSCExecutor::getNativeModule>());
|
2016-05-18 19:46:01 +00:00
|
|
|
}
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
JSCExecutor::~JSCExecutor() {
|
|
|
|
CHECK(*m_isDestroyed) << "JSCExecutor::destroy() must be called before its destructor!";
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSCExecutor::destroy() {
|
|
|
|
*m_isDestroyed = true;
|
2016-11-02 21:24:42 +00:00
|
|
|
if (m_messageQueueThread.get()) {
|
|
|
|
m_messageQueueThread->runOnQueueSync([this] () {
|
|
|
|
terminateOnJSVMThread();
|
|
|
|
});
|
|
|
|
} else {
|
2016-05-04 02:29:58 +00:00
|
|
|
terminateOnJSVMThread();
|
2016-11-02 21:24:42 +00:00
|
|
|
}
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-10-14 18:29:47 +00:00
|
|
|
void JSCExecutor::setContextName(const std::string& name) {
|
2016-11-18 14:25:29 +00:00
|
|
|
String jsName = String(m_context, name.c_str());
|
2016-11-22 14:05:38 +00:00
|
|
|
JSC_JSGlobalContextSetName(m_context, jsName);
|
2016-10-14 18:29:47 +00:00
|
|
|
}
|
|
|
|
|
2017-05-03 04:29:04 +00:00
|
|
|
#ifdef WITH_INSPECTOR
|
|
|
|
static bool canUseInspector(JSContextRef context) {
|
|
|
|
#if defined(__APPLE__)
|
|
|
|
return isCustomJSCPtr(context); // WITH_INSPECTOR && Apple
|
|
|
|
#else
|
|
|
|
return true; // WITH_INSPECTOR && Android
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-06-16 23:00:22 +00:00
|
|
|
void JSCExecutor::initOnJSVMThread() throw(JSException) {
|
2017-05-13 00:57:12 +00:00
|
|
|
SystraceSection s("JSCExecutor::initOnJSVMThread");
|
2016-05-14 00:15:00 +00:00
|
|
|
|
2016-11-23 11:57:58 +00:00
|
|
|
#if defined(__APPLE__)
|
|
|
|
const bool useCustomJSC = m_jscConfig.getDefault("UseCustomJSC", false).getBool();
|
|
|
|
if (useCustomJSC) {
|
2017-01-25 22:28:02 +00:00
|
|
|
JSC_configureJSCForIOS(true, toJson(m_jscConfig));
|
2016-11-23 11:57:58 +00:00
|
|
|
}
|
|
|
|
#else
|
2016-11-22 14:05:38 +00:00
|
|
|
const bool useCustomJSC = false;
|
2016-11-23 11:57:58 +00:00
|
|
|
#endif
|
|
|
|
|
2016-08-10 11:16:39 +00:00
|
|
|
#if defined(WITH_FB_JSC_TUNING) && defined(__ANDROID__)
|
2016-05-04 02:29:58 +00:00
|
|
|
configureJSCForAndroid(m_jscConfig);
|
|
|
|
#endif
|
2016-05-12 21:32:37 +00:00
|
|
|
|
2016-09-30 19:18:58 +00:00
|
|
|
// Create a custom global class, so we can store data in it later using JSObjectSetPrivate
|
2016-05-17 11:21:19 +00:00
|
|
|
JSClassRef globalClass = nullptr;
|
|
|
|
{
|
2017-05-11 11:46:44 +00:00
|
|
|
SystraceSection s_("JSClassCreate");
|
2017-01-25 19:33:06 +00:00
|
|
|
JSClassDefinition definition = kJSClassDefinitionEmpty;
|
|
|
|
definition.attributes |= kJSClassAttributeNoAutomaticPrototype;
|
|
|
|
globalClass = JSC_JSClassCreate(useCustomJSC, &definition);
|
2016-05-17 11:21:19 +00:00
|
|
|
}
|
|
|
|
{
|
2017-05-11 11:46:44 +00:00
|
|
|
SystraceSection s_("JSGlobalContextCreateInGroup");
|
2016-11-22 14:05:38 +00:00
|
|
|
m_context = JSC_JSGlobalContextCreateInGroup(useCustomJSC, nullptr, globalClass);
|
2016-05-17 11:21:19 +00:00
|
|
|
}
|
2016-11-22 14:05:38 +00:00
|
|
|
JSC_JSClassRelease(useCustomJSC, globalClass);
|
2016-05-12 21:32:37 +00:00
|
|
|
|
|
|
|
// Add a pointer to ourselves so we can retrieve it later in our hooks
|
2016-11-18 14:25:24 +00:00
|
|
|
Object::getGlobalObject(m_context).setPrivate(this);
|
2016-05-12 21:32:37 +00:00
|
|
|
|
2017-05-03 04:29:04 +00:00
|
|
|
#ifdef WITH_INSPECTOR
|
|
|
|
if (canUseInspector(m_context)) {
|
2017-07-14 14:44:18 +00:00
|
|
|
const std::string ownerId = m_jscConfig.getDefault("OwnerIdentity", "main").getString();
|
2017-05-03 04:29:04 +00:00
|
|
|
IInspector* pInspector = JSC_JSInspectorGetInstance(true);
|
2017-07-14 14:44:18 +00:00
|
|
|
pInspector->registerGlobalContext(ownerId, m_context);
|
2017-05-03 04:29:04 +00:00
|
|
|
}
|
|
|
|
#endif
|
2016-11-02 19:18:11 +00:00
|
|
|
|
2016-05-12 21:32:42 +00:00
|
|
|
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
|
2016-09-30 19:18:58 +00:00
|
|
|
installNativeHook<&JSCExecutor::nativeCallSyncHook>("nativeCallSyncHook");
|
|
|
|
|
2017-06-07 17:17:58 +00:00
|
|
|
installGlobalFunction(m_context, "nativeLoggingHook", JSCNativeHooks::loggingHook);
|
|
|
|
installGlobalFunction(m_context, "nativePerformanceNow", JSCNativeHooks::nowHook);
|
2016-05-04 02:29:58 +00:00
|
|
|
|
2016-09-30 19:18:58 +00:00
|
|
|
#if DEBUG
|
|
|
|
installGlobalFunction(m_context, "nativeInjectHMRUpdate", nativeInjectHMRUpdate);
|
|
|
|
#endif
|
|
|
|
|
2017-02-01 22:10:39 +00:00
|
|
|
#if defined(WITH_JSC_EXTRA_TRACING) || (DEBUG && defined(WITH_FBSYSTRACE))
|
2016-05-04 02:29:58 +00:00
|
|
|
addNativeTracingHooks(m_context);
|
2016-05-14 00:15:03 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef WITH_JSC_EXTRA_TRACING
|
|
|
|
addNativeTracingLegacyHooks(m_context);
|
2017-01-10 19:19:09 +00:00
|
|
|
#endif
|
2016-07-15 18:51:10 +00:00
|
|
|
|
2017-06-07 17:17:58 +00:00
|
|
|
JSCNativeHooks::installPerfHooks(m_context);
|
2017-02-17 13:47:28 +00:00
|
|
|
|
2017-01-11 17:29:50 +00:00
|
|
|
#if defined(__APPLE__) || defined(WITH_JSC_EXTRA_TRACING)
|
|
|
|
if (JSC_JSSamplingProfilerEnabled(m_context)) {
|
|
|
|
initSamplingProfilerOnMainJSCThread(m_context);
|
|
|
|
}
|
|
|
|
#endif
|
2016-05-04 02:29:58 +00:00
|
|
|
|
|
|
|
#ifdef WITH_FB_MEMORY_PROFILING
|
|
|
|
addNativeMemoryHooks(m_context);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef JSC_HAS_PERF_STATS_API
|
|
|
|
addJSCPerfStatsHooks(m_context);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void JSCExecutor::terminateOnJSVMThread() {
|
2017-06-06 18:30:40 +00:00
|
|
|
JSGlobalContextRef context = m_context;
|
|
|
|
m_context = nullptr;
|
|
|
|
Object::getGlobalObject(context).setPrivate(nullptr);
|
2016-10-11 14:19:31 +00:00
|
|
|
m_nativeModules.reset();
|
|
|
|
|
2017-05-03 04:29:04 +00:00
|
|
|
#ifdef WITH_INSPECTOR
|
2017-06-06 18:30:40 +00:00
|
|
|
if (canUseInspector(context)) {
|
2017-05-03 04:29:04 +00:00
|
|
|
IInspector* pInspector = JSC_JSInspectorGetInstance(true);
|
2017-06-06 18:30:40 +00:00
|
|
|
pInspector->unregisterGlobalContext(context);
|
2017-05-03 04:29:04 +00:00
|
|
|
}
|
|
|
|
#endif
|
2016-11-02 19:18:11 +00:00
|
|
|
|
2017-06-06 18:30:40 +00:00
|
|
|
JSC_JSGlobalContextRelease(context);
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-12-17 12:46:50 +00:00
|
|
|
#ifdef WITH_FBJSCEXTENSIONS
|
2017-01-11 14:11:43 +00:00
|
|
|
static const char* explainLoadSourceStatus(JSLoadSourceStatus status) {
|
|
|
|
switch (status) {
|
|
|
|
case JSLoadSourceIsCompiled:
|
2016-12-17 12:46:50 +00:00
|
|
|
return "No error encountered during source load";
|
|
|
|
|
|
|
|
case JSLoadSourceErrorOnRead:
|
|
|
|
return "Error reading source";
|
|
|
|
|
2017-01-11 14:11:43 +00:00
|
|
|
case JSLoadSourceIsNotCompiled:
|
2016-12-17 12:46:50 +00:00
|
|
|
return "Source is not compiled";
|
|
|
|
|
|
|
|
case JSLoadSourceErrorVersionMismatch:
|
|
|
|
return "Source version not supported";
|
|
|
|
|
|
|
|
default:
|
|
|
|
return "Bad error code";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-05-10 10:48:13 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2017-01-10 15:04:53 +00:00
|
|
|
void JSCExecutor::loadApplicationScript(std::unique_ptr<const JSBigString> script, std::string sourceURL) {
|
2017-01-10 15:04:49 +00:00
|
|
|
SystraceSection s("JSCExecutor::loadApplicationScript",
|
|
|
|
"sourceURL", sourceURL);
|
|
|
|
|
2017-05-10 10:48:13 +00:00
|
|
|
std::string scriptName = simpleBasename(sourceURL);
|
|
|
|
ReactMarker::logTaggedMarker(ReactMarker::RUN_JS_BUNDLE_START, scriptName.c_str());
|
2016-11-18 15:14:20 +00:00
|
|
|
String jsSourceURL(m_context, sourceURL.c_str());
|
2016-11-18 13:01:09 +00:00
|
|
|
|
2017-02-15 15:09:20 +00:00
|
|
|
// TODO t15069155: reduce the number of overrides here
|
2017-01-10 15:04:49 +00:00
|
|
|
#ifdef WITH_FBJSCEXTENSIONS
|
|
|
|
if (auto fileStr = dynamic_cast<const JSBigFileString *>(script.get())) {
|
2017-01-11 14:11:43 +00:00
|
|
|
JSLoadSourceStatus jsStatus;
|
2017-02-23 19:38:38 +00:00
|
|
|
auto bcSourceCode = JSCreateSourceCodeFromFile(fileStr->fd(), jsSourceURL, nullptr, &jsStatus);
|
2016-12-08 13:08:16 +00:00
|
|
|
|
2017-01-11 14:11:43 +00:00
|
|
|
switch (jsStatus) {
|
|
|
|
case JSLoadSourceIsCompiled:
|
2017-01-10 15:04:49 +00:00
|
|
|
if (!bcSourceCode) {
|
|
|
|
throw std::runtime_error("Unexpected error opening compiled bundle");
|
|
|
|
}
|
2016-12-09 00:23:35 +00:00
|
|
|
|
2017-01-10 15:04:49 +00:00
|
|
|
evaluateSourceCode(m_context, bcSourceCode, jsSourceURL);
|
2016-12-08 13:08:16 +00:00
|
|
|
|
2017-03-14 22:28:53 +00:00
|
|
|
flush();
|
2016-12-08 13:08:16 +00:00
|
|
|
|
2017-04-25 09:29:29 +00:00
|
|
|
ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
|
|
|
|
ReactMarker::logMarker(ReactMarker::RUN_JS_BUNDLE_STOP);
|
2017-01-10 15:04:49 +00:00
|
|
|
return;
|
2016-11-18 13:01:09 +00:00
|
|
|
|
2017-01-10 15:04:49 +00:00
|
|
|
case JSLoadSourceErrorVersionMismatch:
|
2017-01-11 14:11:43 +00:00
|
|
|
throw RecoverableError(explainLoadSourceStatus(jsStatus));
|
2016-11-18 13:01:09 +00:00
|
|
|
|
2017-01-10 15:04:49 +00:00
|
|
|
case JSLoadSourceErrorOnRead:
|
2017-01-11 14:11:43 +00:00
|
|
|
case JSLoadSourceIsNotCompiled:
|
2017-01-10 15:04:49 +00:00
|
|
|
// Not bytecode, fall through.
|
|
|
|
break;
|
|
|
|
}
|
2016-11-18 13:01:09 +00:00
|
|
|
}
|
2017-02-15 15:09:20 +00:00
|
|
|
#elif defined(__APPLE__)
|
Simplifying Struct definition.
Summary:
Since we are reading from a file, we should make sure this struct is packed, just in case we change it down the line and the compiler decides it might want to introduce padding, we're now protected against that.
There was also a discussion about the fact that people might use `ptr += sizeof(BundleHeader)` as an idiom in their code, which would currently be incorrect, if padding was introduced at the end of the file. Actually, it remains incorrect to do that now, because a RAM bundle header is a different size to a BC Bundle header. If people are properly testing their code, they should spot this pretty quickly, because it will always be an incorrect thing to do with a RAM bundle, so this isn't as bad as previously thought: where the code only succeeds when the compiler deigns to not pad the struct at the end.
This diff also cleans up how headers are initialised. `BundleHeader` has a constructor that explicitly zero-initialises it so we can rely on the default initializer to do the right thing now.
Reviewed By: mhorowitz
Differential Revision: D4572032
fbshipit-source-id: 7dc50cfa9438dfdfb9f842dc39d8f15334813c63
2017-02-20 12:28:32 +00:00
|
|
|
BundleHeader header;
|
2017-02-15 15:09:20 +00:00
|
|
|
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) {
|
2017-05-26 10:43:20 +00:00
|
|
|
throw JSException(m_context, jsError, jsSourceURL);
|
2017-02-15 15:09:20 +00:00
|
|
|
}
|
|
|
|
} else
|
2016-11-18 13:01:09 +00:00
|
|
|
#endif
|
2017-02-15 15:09:20 +00:00
|
|
|
{
|
2017-05-13 00:57:12 +00:00
|
|
|
String jsScript;
|
|
|
|
{
|
|
|
|
SystraceSection s_("JSCExecutor::loadApplicationScript-createExpectingAscii");
|
|
|
|
ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_START);
|
2017-05-15 16:12:24 +00:00
|
|
|
jsScript = adoptString(std::move(script));
|
2017-05-13 00:57:12 +00:00
|
|
|
ReactMarker::logMarker(ReactMarker::JS_BUNDLE_STRING_CONVERT_STOP);
|
|
|
|
}
|
2017-05-15 16:12:24 +00:00
|
|
|
#ifdef WITH_FBSYSTRACE
|
|
|
|
fbsystrace_end_section(TRACE_TAG_REACT_CXX_BRIDGE);
|
|
|
|
#endif
|
2016-05-04 02:29:58 +00:00
|
|
|
|
2017-05-13 00:57:12 +00:00
|
|
|
SystraceSection s_("JSCExecutor::loadApplicationScript-evaluateScript");
|
2017-02-15 15:09:20 +00:00
|
|
|
evaluateScript(m_context, jsScript, jsSourceURL);
|
|
|
|
}
|
2016-06-21 17:08:31 +00:00
|
|
|
|
2017-03-14 22:28:53 +00:00
|
|
|
flush();
|
2017-02-15 15:09:20 +00:00
|
|
|
|
2017-04-25 09:29:29 +00:00
|
|
|
ReactMarker::logMarker(ReactMarker::CREATE_REACT_CONTEXT_STOP);
|
|
|
|
ReactMarker::logMarker(ReactMarker::RUN_JS_BUNDLE_STOP);
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void JSCExecutor::setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle> unbundle) {
|
|
|
|
if (!m_unbundle) {
|
2016-05-12 21:32:42 +00:00
|
|
|
installNativeHook<&JSCExecutor::nativeRequire>("nativeRequire");
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
m_unbundle = std::move(unbundle);
|
|
|
|
}
|
|
|
|
|
2016-06-21 17:08:31 +00:00
|
|
|
void JSCExecutor::bindBridge() throw(JSException) {
|
2016-09-02 19:43:14 +00:00
|
|
|
SystraceSection s("JSCExecutor::bindBridge");
|
2017-03-20 20:03:04 +00:00
|
|
|
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()) {
|
2017-05-26 10:43:20 +00:00
|
|
|
throw JSException("Could not get BatchedBridge, make sure your bundle is packaged correctly");
|
2017-03-20 20:03:04 +00:00
|
|
|
}
|
|
|
|
}
|
2016-06-21 17:08:31 +00:00
|
|
|
|
2017-03-20 20:03:04 +00:00
|
|
|
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();
|
|
|
|
});
|
2016-06-21 17:08:31 +00:00
|
|
|
}
|
|
|
|
|
2016-09-01 21:59:25 +00:00
|
|
|
void JSCExecutor::callNativeModules(Value&& value) {
|
2016-09-02 19:43:14 +00:00
|
|
|
SystraceSection s("JSCExecutor::callNativeModules");
|
2017-03-20 20:03:04 +00:00
|
|
|
// 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";
|
2016-07-22 00:32:42 +00:00
|
|
|
try {
|
2016-09-01 21:59:25 +00:00
|
|
|
auto calls = value.toJSONString();
|
2016-09-19 11:43:09 +00:00
|
|
|
m_delegate->callNativeModules(*this, folly::parseJson(calls), true);
|
2016-07-22 00:32:42 +00:00
|
|
|
} catch (...) {
|
2016-09-02 19:43:14 +00:00
|
|
|
std::string message = "Error in callNativeModules()";
|
2016-07-22 00:32:42 +00:00
|
|
|
try {
|
2016-09-01 21:59:25 +00:00
|
|
|
message += ":" + value.toString().str();
|
2016-07-22 00:32:42 +00:00
|
|
|
} catch (...) {
|
|
|
|
// ignored
|
|
|
|
}
|
|
|
|
std::throw_with_nested(std::runtime_error(message));
|
|
|
|
}
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-09-01 21:59:25 +00:00
|
|
|
void JSCExecutor::flush() {
|
2016-09-02 19:43:14 +00:00
|
|
|
SystraceSection s("JSCExecutor::flush");
|
2017-03-20 20:03:04 +00:00
|
|
|
|
|
|
|
if (m_flushedQueueJS) {
|
2017-03-14 22:28:53 +00:00
|
|
|
callNativeModules(m_flushedQueueJS->callAsFunction({}));
|
2017-03-20 20:03:04 +00:00
|
|
|
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));
|
2017-03-14 22:28:53 +00:00
|
|
|
}
|
2016-09-01 21:59:25 +00:00
|
|
|
}
|
|
|
|
|
2016-07-22 00:32:42 +00:00
|
|
|
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
|
2016-09-02 19:43:14 +00:00
|
|
|
SystraceSection s("JSCExecutor::callFunction");
|
2017-05-13 00:57:12 +00:00
|
|
|
|
2016-09-01 21:59:25 +00:00
|
|
|
// This weird pattern is because Value is not default constructible.
|
|
|
|
// The lambda is inlined, so there's no overhead.
|
|
|
|
auto result = [&] {
|
|
|
|
try {
|
2017-03-20 20:03:04 +00:00
|
|
|
if (!m_callFunctionReturnResultAndFlushedQueueJS) {
|
|
|
|
bindBridge();
|
|
|
|
}
|
2016-09-01 21:59:25 +00:00
|
|
|
return m_callFunctionReturnFlushedQueueJS->callAsFunction({
|
2016-11-18 14:25:29 +00:00
|
|
|
Value(m_context, String::createExpectingAscii(m_context, moduleId)),
|
|
|
|
Value(m_context, String::createExpectingAscii(m_context, methodId)),
|
2016-09-01 21:59:25 +00:00
|
|
|
Value::fromDynamic(m_context, std::move(arguments))
|
|
|
|
});
|
|
|
|
} catch (...) {
|
|
|
|
std::throw_with_nested(
|
2017-02-15 14:12:46 +00:00
|
|
|
std::runtime_error("Error calling " + moduleId + "." + methodId));
|
2016-09-01 21:59:25 +00:00
|
|
|
}
|
|
|
|
}();
|
|
|
|
|
|
|
|
callNativeModules(std::move(result));
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-07-22 00:32:42 +00:00
|
|
|
void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
|
2016-09-02 19:43:14 +00:00
|
|
|
SystraceSection s("JSCExecutor::invokeCallback");
|
2016-09-01 21:59:25 +00:00
|
|
|
auto result = [&] {
|
|
|
|
try {
|
2017-03-20 20:03:04 +00:00
|
|
|
if (!m_invokeCallbackAndReturnFlushedQueueJS) {
|
|
|
|
bindBridge();
|
|
|
|
}
|
2016-09-01 21:59:25 +00:00
|
|
|
return m_invokeCallbackAndReturnFlushedQueueJS->callAsFunction({
|
2016-11-18 14:25:24 +00:00
|
|
|
Value::makeNumber(m_context, callbackId),
|
2016-09-01 21:59:25 +00:00
|
|
|
Value::fromDynamic(m_context, std::move(arguments))
|
|
|
|
});
|
|
|
|
} catch (...) {
|
|
|
|
std::throw_with_nested(
|
2017-02-15 14:12:46 +00:00
|
|
|
std::runtime_error(folly::to<std::string>("Error invoking callback ", callbackId)));
|
2016-09-01 21:59:25 +00:00
|
|
|
}
|
|
|
|
}();
|
|
|
|
|
|
|
|
callNativeModules(std::move(result));
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-09-26 23:01:43 +00:00
|
|
|
Value JSCExecutor::callFunctionSyncWithValue(
|
|
|
|
const std::string& module, const std::string& method, Value args) {
|
|
|
|
SystraceSection s("JSCExecutor::callFunction");
|
|
|
|
|
2017-03-20 20:03:04 +00:00
|
|
|
if (!m_callFunctionReturnResultAndFlushedQueueJS) {
|
|
|
|
bindBridge();
|
|
|
|
}
|
2016-09-26 23:01:43 +00:00
|
|
|
Object result = m_callFunctionReturnResultAndFlushedQueueJS->callAsFunction({
|
2016-11-18 14:25:29 +00:00
|
|
|
Value(m_context, String::createExpectingAscii(m_context, module)),
|
|
|
|
Value(m_context, String::createExpectingAscii(m_context, method)),
|
2016-09-26 23:01:43 +00:00
|
|
|
std::move(args),
|
|
|
|
}).asObject();
|
|
|
|
|
|
|
|
Value length = result.getProperty("length");
|
|
|
|
|
|
|
|
if (!length.isNumber() || length.asInteger() != 2) {
|
|
|
|
std::runtime_error("Return value of a callFunction must be an array of size 2");
|
|
|
|
}
|
|
|
|
|
|
|
|
callNativeModules(result.getPropertyAtIndex(1));
|
|
|
|
return result.getPropertyAtIndex(0);
|
|
|
|
}
|
|
|
|
|
2016-07-22 00:32:42 +00:00
|
|
|
void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) {
|
|
|
|
try {
|
2017-05-13 00:57:12 +00:00
|
|
|
SystraceSection s("JSCExecutor::setGlobalVariable", "propName", propName);
|
2017-06-06 10:04:56 +00:00
|
|
|
auto valueToInject = Value::fromJSON(adoptString(std::move(jsonValue)));
|
2016-11-18 14:25:24 +00:00
|
|
|
Object::getGlobalObject(m_context).setProperty(propName.c_str(), valueToInject);
|
2016-07-22 00:32:42 +00:00
|
|
|
} catch (...) {
|
|
|
|
std::throw_with_nested(std::runtime_error("Error setting global variable: " + propName));
|
|
|
|
}
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2017-05-15 16:12:24 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-05-04 02:29:58 +00:00
|
|
|
void* JSCExecutor::getJavaScriptContext() {
|
|
|
|
return m_context;
|
|
|
|
}
|
|
|
|
|
2017-06-26 12:48:40 +00:00
|
|
|
#ifdef WITH_JSC_MEMORY_PRESSURE
|
|
|
|
void JSCExecutor::handleMemoryPressure(int pressureLevel) {
|
|
|
|
JSHandleMemoryPressure(this, m_context, static_cast<JSMemoryPressure>(pressureLevel));
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
2017-06-26 12:48:40 +00:00
|
|
|
#endif
|
2016-05-04 02:29:58 +00:00
|
|
|
|
2016-09-19 11:43:09 +00:00
|
|
|
void JSCExecutor::flushQueueImmediate(Value&& queue) {
|
|
|
|
auto queueStr = queue.toJSONString();
|
|
|
|
m_delegate->callNativeModules(*this, folly::parseJson(queueStr), false);
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void JSCExecutor::loadModule(uint32_t moduleId) {
|
|
|
|
auto module = m_unbundle->getModule(moduleId);
|
2016-11-18 14:25:29 +00:00
|
|
|
auto sourceUrl = String::createExpectingAscii(m_context, module.name);
|
|
|
|
auto source = String::createExpectingAscii(m_context, module.code);
|
2016-05-04 02:29:58 +00:00
|
|
|
evaluateScript(m_context, source, sourceUrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Native JS hooks
|
2016-05-12 21:32:37 +00:00
|
|
|
template<JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
|
|
|
|
void JSCExecutor::installNativeHook(const char* name) {
|
|
|
|
installGlobalFunction(m_context, name, exceptionWrapMethod<method>());
|
|
|
|
}
|
|
|
|
|
2016-10-11 14:19:31 +00:00
|
|
|
JSValueRef JSCExecutor::getNativeModule(JSObjectRef object, JSStringRef propertyName) {
|
2016-11-22 14:05:38 +00:00
|
|
|
if (JSC_JSStringIsEqualToUTF8CString(m_context, propertyName, "name")) {
|
2016-11-18 14:25:29 +00:00
|
|
|
return Value(m_context, String(m_context, "NativeModules"));
|
2016-10-11 14:19:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return m_nativeModules.getModule(m_context, propertyName);
|
|
|
|
}
|
|
|
|
|
2016-05-04 02:29:58 +00:00
|
|
|
JSValueRef JSCExecutor::nativeRequire(
|
|
|
|
size_t argumentCount,
|
2016-05-12 21:32:42 +00:00
|
|
|
const JSValueRef arguments[]) {
|
2016-05-04 02:29:58 +00:00
|
|
|
|
|
|
|
if (argumentCount != 1) {
|
2016-05-12 21:32:42 +00:00
|
|
|
throw std::invalid_argument("Got wrong number of args");
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-12 21:32:42 +00:00
|
|
|
double moduleId = Value(m_context, arguments[0]).asNumber();
|
2017-05-25 22:16:06 +00:00
|
|
|
if (moduleId < 0) {
|
2016-11-01 23:09:49 +00:00
|
|
|
throw std::invalid_argument(folly::to<std::string>("Received invalid module ID: ",
|
|
|
|
Value(m_context, arguments[0]).toString().str()));
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
2016-11-01 23:09:49 +00:00
|
|
|
|
2017-04-25 09:29:29 +00:00
|
|
|
ReactMarker::logMarker(ReactMarker::NATIVE_REQUIRE_START);
|
2016-11-01 23:09:49 +00:00
|
|
|
loadModule(moduleId);
|
2017-04-25 09:29:29 +00:00
|
|
|
ReactMarker::logMarker(ReactMarker::NATIVE_REQUIRE_STOP);
|
2016-11-18 14:25:24 +00:00
|
|
|
return Value::makeUndefined(m_context);
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
JSValueRef JSCExecutor::nativeFlushQueueImmediate(
|
|
|
|
size_t argumentCount,
|
2016-05-12 21:32:42 +00:00
|
|
|
const JSValueRef arguments[]) {
|
2016-05-04 02:29:58 +00:00
|
|
|
if (argumentCount != 1) {
|
2016-05-12 21:32:42 +00:00
|
|
|
throw std::invalid_argument("Got wrong number of args");
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-09-19 11:43:09 +00:00
|
|
|
flushQueueImmediate(Value(m_context, arguments[0]));
|
2016-11-18 14:25:24 +00:00
|
|
|
return Value::makeUndefined(m_context);
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
JSValueRef JSCExecutor::nativeCallSyncHook(
|
|
|
|
size_t argumentCount,
|
2016-05-12 21:32:42 +00:00
|
|
|
const JSValueRef arguments[]) {
|
2016-05-04 02:29:58 +00:00
|
|
|
if (argumentCount != 3) {
|
2016-05-12 21:32:42 +00:00
|
|
|
throw std::invalid_argument("Got wrong number of args");
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
2016-05-12 21:32:42 +00:00
|
|
|
unsigned int moduleId = Value(m_context, arguments[0]).asUnsignedInteger();
|
|
|
|
unsigned int methodId = Value(m_context, arguments[1]).asUnsignedInteger();
|
2017-01-30 14:39:51 +00:00
|
|
|
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()));
|
|
|
|
}
|
2016-05-04 02:29:58 +00:00
|
|
|
|
2016-05-18 19:46:01 +00:00
|
|
|
MethodCallResult result = m_delegate->callSerializableNativeHook(
|
|
|
|
*this,
|
2016-05-12 21:32:42 +00:00
|
|
|
moduleId,
|
|
|
|
methodId,
|
2017-01-30 14:39:51 +00:00
|
|
|
std::move(args));
|
2017-02-02 13:07:28 +00:00
|
|
|
if (!result.hasValue()) {
|
2016-11-18 14:25:24 +00:00
|
|
|
return Value::makeUndefined(m_context);
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
2017-02-02 13:07:28 +00:00
|
|
|
return Value::fromDynamic(m_context, result.value());
|
2016-05-04 02:29:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} }
|