Recognise and run BC bundles

Reviewed By: javache

Differential Revision: D4067425

fbshipit-source-id: fade9adebfa8a59dc49aeadfd01a782f7b686082
This commit is contained in:
Ashok Menon 2016-11-01 10:14:00 -07:00 committed by Facebook Github Bot
parent 94711bfb06
commit 16290851aa
5 changed files with 147 additions and 51 deletions

View File

@ -9,7 +9,40 @@
#import <UIKit/UIKit.h>
extern uint32_t const RCTRAMBundleMagicNumber;
#import "RCTDefines.h"
/**
* RCTScriptTag
*
* Scripts given to the JS Executors to run could be in any of the following
* formats. They are tagged so the executor knows how to run them.
*/
typedef NS_ENUM(NSInteger) {
RCTScriptString = 0,
RCTScriptRAMBundle,
RCTScriptBCBundle,
} RCTScriptTag;
/**
* RCTMagicNumber
*
* RAM bundles and BC bundles begin with magic numbers. For RAM bundles this is
* 4 bytes, for BC bundles this is 8 bytes. This structure holds the first 8
* bytes from a bundle in a way that gives access to that information.
*/
typedef union {
uint64_t allBytes;
uint32_t first4;
uint64_t first8;
} RCTMagicNumber;
/**
* RCTParseMagicNumber
*
* Takes the first 8 bytes of a bundle, and returns a tag describing the
* bundle's format.
*/
RCT_EXTERN RCTScriptTag RCTParseMagicNumber(RCTMagicNumber magic);
extern NSString *const RCTJavaScriptLoaderErrorDomain;
@ -42,7 +75,7 @@ typedef void (^RCTSourceLoadBlock)(NSError *error, NSData *source, int64_t sourc
* @experimental
* Attempts to synchronously load the script at the given URL. The following two conditions must be met:
* 1. It must be a file URL.
* 2. It must point to a RAM bundle.
* 2. It must not point to a text/javascript file.
* If the URL does not meet those conditions, this method will return nil and supply an error with the domain
* RCTJavaScriptLoaderErrorDomain and the code RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously.
*/

View File

@ -18,10 +18,24 @@
#include <sys/stat.h>
uint32_t const RCTRAMBundleMagicNumber = 0xFB0BD1E5;
static uint32_t const RCTRAMBundleMagicNumber = 0xFB0BD1E5;
static uint64_t const RCTBCBundleMagicNumber = 0xFF4865726D657300;
NSString *const RCTJavaScriptLoaderErrorDomain = @"RCTJavaScriptLoaderErrorDomain";
RCTScriptTag RCTParseMagicNumber(RCTMagicNumber magic)
{
if (NSSwapLittleIntToHost(magic.first4) == RCTRAMBundleMagicNumber) {
return RCTScriptRAMBundle;
}
if (NSSwapLittleLongLongToHost(magic.first8) == RCTBCBundleMagicNumber) {
return RCTScriptBCBundle;
}
return RCTScriptString;
}
@implementation RCTLoadingProgress
- (NSString *)description
@ -112,7 +126,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return nil;
}
uint32_t magicNumber;
RCTMagicNumber magicNumber = {.allBytes = 0};
size_t readResult = fread(&magicNumber, sizeof(magicNumber), 1, bundle);
fclose(bundle);
if (readResult != 1) {
@ -125,13 +139,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return nil;
}
magicNumber = NSSwapLittleIntToHost(magicNumber);
if (magicNumber != RCTRAMBundleMagicNumber) {
RCTScriptTag tag = RCTParseMagicNumber(magicNumber);
if (tag == RCTScriptString) {
if (error) {
*error = [NSError errorWithDomain:RCTJavaScriptLoaderErrorDomain
code:RCTJavaScriptLoaderErrorCannotBeLoadedSynchronously
userInfo:@{NSLocalizedDescriptionKey:
@"Cannot load non-RAM bundled files synchronously"}];
@"Cannot load text/javascript files synchronously"}];
}
return nil;
}

View File

@ -66,6 +66,11 @@ struct RandomAccessBundleStartupCode {
}
};
struct TaggedScript {
const RCTScriptTag tag;
const NSData *script;
};
#if RCT_PROFILE
@interface RCTCookieMap : NSObject
{
@ -280,15 +285,20 @@ static NSThread *newJavaScriptThread(void)
JSContext:(JSContext *)context
error:(NSError **)error
{
BOOL isRAMBundle = NO;
script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, error);
if (!script) {
TaggedScript taggedScript = loadTaggedScript(script, sourceURL, _performanceLogger, _randomAccessBundle, error);
if (!taggedScript.script) {
return NO;
}
if (isRAMBundle) {
if (taggedScript.tag == RCTScriptRAMBundle) {
registerNativeRequire(context, self);
}
NSError *returnedError = executeApplicationScript(script, sourceURL, _jscWrapper, _performanceLogger, _context.context.JSGlobalContextRef);
NSError *returnedError = executeApplicationScript(taggedScript, sourceURL,
_jscWrapper,
_performanceLogger,
_context.context.JSGlobalContextRef);
if (returnedError) {
if (error) {
*error = returnedError;
@ -652,16 +662,16 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName)
RCTAssertParam(script);
RCTAssertParam(sourceURL);
BOOL isRAMBundle = NO;
{
NSError *error;
script = loadPossiblyBundledApplicationScript(script, sourceURL, _performanceLogger, isRAMBundle, _randomAccessBundle, &error);
if (script == nil) {
if (onComplete) {
onComplete(error);
}
return;
NSError *loadError;
TaggedScript taggedScript = loadTaggedScript(script, sourceURL,
_performanceLogger,
_randomAccessBundle,
&loadError);
if (!taggedScript.script) {
if (onComplete) {
onComplete(loadError);
}
return;
}
RCTProfileBeginFlowEvent();
@ -671,11 +681,13 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName)
return;
}
if (isRAMBundle) {
if (taggedScript.tag == RCTScriptRAMBundle) {
registerNativeRequire(self.context.context, self);
}
NSError *error = executeApplicationScript(script, sourceURL, self->_jscWrapper, self->_performanceLogger,
NSError *error = executeApplicationScript(taggedScript, sourceURL,
self->_jscWrapper,
self->_performanceLogger,
self->_context.context.JSGlobalContextRef);
if (onComplete) {
onComplete(error);
@ -683,33 +695,42 @@ RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)contextName)
}];
}
static NSData *loadPossiblyBundledApplicationScript(NSData *script, NSURL *sourceURL,
RCTPerformanceLogger *performanceLogger,
BOOL &isRAMBundle, RandomAccessBundleData &randomAccessBundle,
NSError **error)
static TaggedScript loadTaggedScript(NSData *script,
NSURL *sourceURL,
RCTPerformanceLogger *performanceLogger,
RandomAccessBundleData &randomAccessBundle,
NSError **error)
{
RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / prepare bundle", nil);
// The RAM bundle has a magic number in the 4 first bytes `(0xFB0BD1E5)`.
uint32_t magicNumber = 0;
RCTMagicNumber magicNumber = {.allBytes = 0};
[script getBytes:&magicNumber length:sizeof(magicNumber)];
isRAMBundle = NSSwapLittleIntToHost(magicNumber) == RCTRAMBundleMagicNumber;
if (isRAMBundle) {
[performanceLogger markStartForTag:RCTPLRAMBundleLoad];
script = loadRAMBundle(sourceURL, error, randomAccessBundle);
[performanceLogger markStopForTag:RCTPLRAMBundleLoad];
[performanceLogger setValue:script.length forTag:RCTPLRAMStartupCodeSize];
} else {
// JSStringCreateWithUTF8CString expects a null terminated C string.
// RAM Bundling already provides a null terminated one.
NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1];
[nullTerminatedScript appendData:script];
[nullTerminatedScript appendBytes:"" length:1];
script = nullTerminatedScript;
RCTScriptTag tag = RCTParseMagicNumber(magicNumber);
NSData *loadedScript = NULL;
switch (tag) {
case RCTScriptRAMBundle:
[performanceLogger markStartForTag:RCTPLRAMBundleLoad];
loadedScript = loadRAMBundle(sourceURL, error, randomAccessBundle);
[performanceLogger markStopForTag:RCTPLRAMBundleLoad];
[performanceLogger setValue:loadedScript.length forTag:RCTPLRAMStartupCodeSize];
break;
case RCTScriptBCBundle:
loadedScript = script;
break;
case RCTScriptString: {
NSMutableData *nullTerminatedScript = [NSMutableData dataWithData:script];
[nullTerminatedScript appendBytes:"" length:1];
loadedScript = nullTerminatedScript;
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
return script;
return { .tag = tag, .script = loadedScript };
}
static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor)
@ -718,22 +739,46 @@ static void registerNativeRequire(JSContext *context, RCTJSCExecutor *executor)
context[@"nativeRequire"] = ^(NSNumber *moduleID) { [weakExecutor _nativeRequire:moduleID]; };
}
static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, RCTJSCWrapper *jscWrapper,
RCTPerformanceLogger *performanceLogger, JSGlobalContextRef ctx)
static NSError *executeApplicationScript(TaggedScript taggedScript,
NSURL *sourceURL,
RCTJSCWrapper *jscWrapper,
RCTPerformanceLogger *performanceLogger,
JSGlobalContextRef ctx)
{
RCT_PROFILE_BEGIN_EVENT(0, @"executeApplicationScript / execute script", (@{
@"url": sourceURL.absoluteString, @"size": @(script.length)
@"url": sourceURL.absoluteString, @"size": @(taggedScript.script.length)
}));
[performanceLogger markStartForTag:RCTPLScriptExecution];
JSValueRef jsError = NULL;
JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)script.bytes);
JSStringRef bundleURL = jscWrapper->JSStringCreateWithUTF8CString(sourceURL.absoluteString.UTF8String);
jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);
switch (taggedScript.tag) {
case RCTScriptRAMBundle:
/* fallthrough */
case RCTScriptString: {
JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString((const char *)taggedScript.script.bytes);
jscWrapper->JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);
jscWrapper->JSStringRelease(execJSString);
break;
}
case RCTScriptBCBundle: {
file_ptr source(fopen(sourceURL.path.UTF8String, "r"), fclose);
int sourceFD = fileno(source.get());
jscWrapper->JSEvaluateBytecodeBundle(ctx, NULL, sourceFD, bundleURL, &jsError);
break;
}
}
jscWrapper->JSStringRelease(bundleURL);
jscWrapper->JSStringRelease(execJSString);
[performanceLogger markStopForTag:RCTPLScriptExecution];
NSError *error = jsError ? RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper) : nil;
NSError *error = jsError
? RCTNSErrorFromJSErrorRef(jsError, ctx, jscWrapper)
: nil;
RCT_PROFILE_END_EVENT(0, @"js_call");
return error;
}

View File

@ -25,6 +25,7 @@ typedef JSStringRef (*JSValueCreateJSONStringFuncType)(JSContextRef, JSValueRef,
typedef bool (*JSValueIsUndefinedFuncType)(JSContextRef, JSValueRef);
typedef bool (*JSValueIsNullFuncType)(JSContextRef, JSValueRef);
typedef JSValueRef (*JSEvaluateScriptFuncType)(JSContextRef, JSStringRef, JSObjectRef, JSStringRef, int, JSValueRef *);
typedef JSValueRef (*JSEvaluateBytecodeBundleFuncType)(JSContextRef, JSObjectRef, int, JSStringRef, JSValueRef *);
typedef struct RCTJSCWrapper {
JSStringCreateWithCFStringFuncType JSStringCreateWithCFString;
@ -41,6 +42,7 @@ typedef struct RCTJSCWrapper {
JSValueIsUndefinedFuncType JSValueIsUndefined;
JSValueIsNullFuncType JSValueIsNull;
JSEvaluateScriptFuncType JSEvaluateScript;
JSEvaluateBytecodeBundleFuncType JSEvaluateBytecodeBundle;
Class JSContext;
Class JSValue;
} RCTJSCWrapper;

View File

@ -26,7 +26,7 @@ static void *RCTCustomLibraryHandler(void)
static dispatch_once_t token;
static void *handler;
dispatch_once(&token, ^{
handler = dlopen("@executable_path/Frameworks/JSC.framework/JSC", RTLD_LAZY | RTLD_LOCAL);
handler = dlopen("@loader_path/Frameworks/JSC.framework/JSC", RTLD_LAZY | RTLD_LOCAL);
if (!handler) {
const char *err = dlerror();
@ -57,6 +57,7 @@ static void RCTSetUpSystemLibraryPointers(RCTJSCWrapper *wrapper)
wrapper->JSValueIsUndefined = JSValueIsUndefined;
wrapper->JSValueIsNull = JSValueIsNull;
wrapper->JSEvaluateScript = JSEvaluateScript;
wrapper->JSEvaluateBytecodeBundle = NULL;
wrapper->JSContext = [JSContext class];
wrapper->JSValue = [JSValue class];
}
@ -83,6 +84,7 @@ static void RCTSetUpCustomLibraryPointers(RCTJSCWrapper *wrapper)
wrapper->JSValueIsUndefined = (JSValueIsUndefinedFuncType)dlsym(libraryHandle, "JSValueIsUndefined");
wrapper->JSValueIsNull = (JSValueIsNullFuncType)dlsym(libraryHandle, "JSValueIsNull");
wrapper->JSEvaluateScript = (JSEvaluateScriptFuncType)dlsym(libraryHandle, "JSEvaluateScript");
wrapper->JSEvaluateBytecodeBundle = (JSEvaluateBytecodeBundleFuncType)dlsym(libraryHandle, "JSEvaluateBytecodeBundle");
wrapper->JSContext = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSContext");
wrapper->JSValue = (__bridge Class)dlsym(libraryHandle, "OBJC_CLASS_$_JSValue");