Fix parseTypeFromHeader for Hermes bytecode
Summary: Changelog: [Internal] Fix bug where Hermes bytecode bundles were not always recognized Reviewed By: motiz88 Differential Revision: D34718511 fbshipit-source-id: 5bbebe49962d098988d99153b0938fbb5d449ae6
This commit is contained in:
Родитель
4b6c0b92c0
Коммит
be0ff779b1
|
@ -11,10 +11,7 @@
|
|||
|
||||
extern NSString *const RCTJavaScriptLoaderErrorDomain;
|
||||
|
||||
extern const UInt32 RCT_BYTECODE_ALIGNMENT;
|
||||
|
||||
UInt32 RCTReadUInt32LE(NSData *script, UInt32 offset);
|
||||
bool RCTIsBytecodeBundle(NSData *script);
|
||||
extern const uint32_t RCT_BYTECODE_ALIGNMENT;
|
||||
|
||||
NS_ENUM(NSInteger){
|
||||
RCTJavaScriptLoaderErrorNoScriptURL = 1,
|
||||
|
|
|
@ -19,19 +19,7 @@
|
|||
|
||||
NSString *const RCTJavaScriptLoaderErrorDomain = @"RCTJavaScriptLoaderErrorDomain";
|
||||
|
||||
const UInt32 RCT_BYTECODE_ALIGNMENT = 4;
|
||||
UInt32 RCTReadUInt32LE(NSData *script, UInt32 offset)
|
||||
{
|
||||
return [script length] < offset + 4 ? 0 : CFSwapInt32LittleToHost(*(((uint32_t *)[script bytes]) + offset / 4));
|
||||
}
|
||||
|
||||
bool RCTIsBytecodeBundle(NSData *script)
|
||||
{
|
||||
static const UInt32 BYTECODE_BUNDLE_MAGIC_NUMBER = 0xffe7c3c3;
|
||||
return (
|
||||
[script length] > 8 && RCTReadUInt32LE(script, 0) == BYTECODE_BUNDLE_MAGIC_NUMBER &&
|
||||
RCTReadUInt32LE(script, 4) > 0);
|
||||
}
|
||||
const uint32_t RCT_BYTECODE_ALIGNMENT = 4;
|
||||
|
||||
@interface RCTSource () {
|
||||
@public
|
||||
|
@ -47,12 +35,18 @@ bool RCTIsBytecodeBundle(NSData *script)
|
|||
|
||||
static RCTSource *RCTSourceCreate(NSURL *url, NSData *data, int64_t length) NS_RETURNS_RETAINED
|
||||
{
|
||||
using facebook::react::ScriptTag;
|
||||
facebook::react::BundleHeader header;
|
||||
[data getBytes:&header length:sizeof(header)];
|
||||
|
||||
RCTSource *source = [RCTSource new];
|
||||
source->_url = url;
|
||||
// Multipart responses may give us an unaligned view into the buffer. This ensures memory is aligned.
|
||||
source->_data = (RCTIsBytecodeBundle(data) && ((long)[data bytes] % RCT_BYTECODE_ALIGNMENT))
|
||||
? [[NSData alloc] initWithData:data]
|
||||
: data;
|
||||
if (parseTypeFromHeader(header) == ScriptTag::MetroHBCBundle && ((long)[data bytes] % RCT_BYTECODE_ALIGNMENT)) {
|
||||
source->_data = [[NSData alloc] initWithData:data];
|
||||
} else {
|
||||
source->_data = data;
|
||||
}
|
||||
source->_length = length;
|
||||
source->_filesChangedCount = RCTSourceFilesChangedCountNotBuiltByBundler;
|
||||
return source;
|
||||
|
@ -177,7 +171,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|||
|
||||
facebook::react::ScriptTag tag = facebook::react::parseTypeFromHeader(header);
|
||||
switch (tag) {
|
||||
case facebook::react::ScriptTag::HBCBundle:
|
||||
case facebook::react::ScriptTag::MetroHBCBundle:
|
||||
case facebook::react::ScriptTag::RAMBundle:
|
||||
break;
|
||||
|
||||
|
|
|
@ -113,13 +113,6 @@ class GetDescAdapter : public JSExecutorFactory {
|
|||
|
||||
}
|
||||
|
||||
static bool isRAMBundle(NSData *script)
|
||||
{
|
||||
BundleHeader header;
|
||||
[script getBytes:&header length:sizeof(header)];
|
||||
return parseTypeFromHeader(header) == ScriptTag::RAMBundle;
|
||||
}
|
||||
|
||||
static void notifyAboutModuleSetup(RCTPerformanceLogger *performanceLogger, const char *tag)
|
||||
{
|
||||
NSString *moduleName = [[NSString alloc] initWithUTF8String:tag];
|
||||
|
@ -1498,6 +1491,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL
|
|||
[self executeApplicationScript:script url:url async:NO];
|
||||
}
|
||||
|
||||
static uint32_t RCTReadUInt32LE(NSData *script, uint32_t offset)
|
||||
{
|
||||
return [script length] < offset + 4 ? 0 : CFSwapInt32LittleToHost(*(((uint32_t *)[script bytes]) + offset / 4));
|
||||
}
|
||||
|
||||
- (void)executeApplicationScript:(NSData *)script url:(NSURL *)url async:(BOOL)async
|
||||
{
|
||||
[self _tryAndHandleError:^{
|
||||
|
@ -1506,18 +1504,22 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL
|
|||
object:self->_parentBridge
|
||||
userInfo:@{@"bridge" : self}];
|
||||
|
||||
BundleHeader header;
|
||||
[script getBytes:&header length:sizeof(header)];
|
||||
ScriptTag scriptType = parseTypeFromHeader(header);
|
||||
|
||||
// hold a local reference to reactInstance in case a parallel thread
|
||||
// resets it between null check and usage
|
||||
auto reactInstance = self->_reactInstance;
|
||||
if (reactInstance && RCTIsBytecodeBundle(script)) {
|
||||
UInt32 offset = 8;
|
||||
if (reactInstance && scriptType == ScriptTag::MetroHBCBundle) {
|
||||
uint32_t offset = 8;
|
||||
while (offset < script.length) {
|
||||
UInt32 fileLength = RCTReadUInt32LE(script, offset);
|
||||
uint32_t fileLength = RCTReadUInt32LE(script, offset);
|
||||
NSData *unit = [script subdataWithRange:NSMakeRange(offset + 4, fileLength)];
|
||||
reactInstance->loadScriptFromString(std::make_unique<NSDataBigString>(unit), sourceUrlStr.UTF8String, false);
|
||||
offset += ((fileLength + RCT_BYTECODE_ALIGNMENT - 1) & ~(RCT_BYTECODE_ALIGNMENT - 1)) + 4;
|
||||
}
|
||||
} else if (isRAMBundle(script)) {
|
||||
} else if (scriptType == ScriptTag::RAMBundle) {
|
||||
[self->_performanceLogger markStartForTag:RCTPLRAMBundleLoad];
|
||||
auto ramBundle = std::make_unique<JSIndexedRAMBundle>(sourceUrlStr.UTF8String);
|
||||
std::unique_ptr<const JSBigString> scriptStr = ramBundle->getStartupCode();
|
||||
|
|
|
@ -276,7 +276,7 @@ void CatalystInstanceImpl::jniLoadScriptFromFile(
|
|||
const std::string &sourceURL,
|
||||
bool loadSynchronously) {
|
||||
auto reactInstance = instance_;
|
||||
if (reactInstance && Instance::isHBCBundle(fileName.c_str())) {
|
||||
if (reactInstance && Instance::isMetroHBCBundle(fileName.c_str())) {
|
||||
std::unique_ptr<const JSBigFileString> script;
|
||||
RecoverableError::runRethrowingAsRecoverable<std::system_error>(
|
||||
[&fileName, &script]() {
|
||||
|
|
|
@ -77,7 +77,7 @@ loadScriptFromAssets(AAssetManager *manager, const std::string &assetName) {
|
|||
// script to ensure we have a terminator.
|
||||
const BundleHeader *header =
|
||||
reinterpret_cast<const BundleHeader *>(script->c_str());
|
||||
if (parseTypeFromHeader(*header) == ScriptTag::HBCBundle) {
|
||||
if (isHermesBytecodeBundle(*header)) {
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ void Instance::loadScriptFromString(
|
|||
}
|
||||
}
|
||||
|
||||
bool Instance::isHBCBundle(const char *sourcePath) {
|
||||
bool Instance::isMetroHBCBundle(const char *sourcePath) {
|
||||
std::ifstream bundle_stream(sourcePath, std::ios_base::in);
|
||||
BundleHeader header;
|
||||
|
||||
|
@ -118,7 +118,7 @@ bool Instance::isHBCBundle(const char *sourcePath) {
|
|||
return false;
|
||||
}
|
||||
|
||||
return parseTypeFromHeader(header) == ScriptTag::HBCBundle;
|
||||
return parseTypeFromHeader(header) == ScriptTag::MetroHBCBundle;
|
||||
}
|
||||
|
||||
bool Instance::isIndexedRAMBundle(const char *sourcePath) {
|
||||
|
|
|
@ -56,7 +56,7 @@ class RN_EXPORT Instance {
|
|||
std::unique_ptr<const JSBigString> string,
|
||||
std::string sourceURL,
|
||||
bool loadSynchronously);
|
||||
static bool isHBCBundle(const char *sourcePath);
|
||||
static bool isMetroHBCBundle(const char *sourcePath);
|
||||
static bool isIndexedRAMBundle(const char *sourcePath);
|
||||
static bool isIndexedRAMBundle(std::unique_ptr<const JSBigString> *string);
|
||||
void loadRAMBundleFromString(
|
||||
|
|
|
@ -7,20 +7,21 @@
|
|||
|
||||
#include "JSBundleType.h"
|
||||
|
||||
#include <folly/Bits.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
static uint32_t constexpr RAMBundleMagicNumber = 0xFB0BD1E5;
|
||||
static uint32_t constexpr HBCBundleMagicNumber = 0xffe7c3c3;
|
||||
static uint32_t constexpr MetroHBCBundleMagicNumber = 0xFFE7C3C3;
|
||||
|
||||
// "Hermes" in ancient Greek encoded in UTF-16BE and truncated to 8 bytes.
|
||||
static uint64_t constexpr HermesBCBundleMagicNumber = 0x1F1903C103BC1FC6;
|
||||
|
||||
ScriptTag parseTypeFromHeader(const BundleHeader &header) {
|
||||
switch (folly::Endian::little(header.magic)) {
|
||||
switch (header.magic32.value) {
|
||||
case RAMBundleMagicNumber:
|
||||
return ScriptTag::RAMBundle;
|
||||
case HBCBundleMagicNumber:
|
||||
return ScriptTag::HBCBundle;
|
||||
case MetroHBCBundleMagicNumber:
|
||||
return ScriptTag::MetroHBCBundle;
|
||||
default:
|
||||
return ScriptTag::String;
|
||||
}
|
||||
|
@ -32,11 +33,15 @@ const char *stringForScriptTag(const ScriptTag &tag) {
|
|||
return "String";
|
||||
case ScriptTag::RAMBundle:
|
||||
return "RAM Bundle";
|
||||
case ScriptTag::HBCBundle:
|
||||
return "HBC Bundle";
|
||||
case ScriptTag::MetroHBCBundle:
|
||||
return "Metro Hermes Bytecode Bundle";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool isHermesBytecodeBundle(const BundleHeader &header) {
|
||||
return header.magic64 == HermesBCBundleMagicNumber;
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
|
|
@ -19,20 +19,18 @@ namespace facebook {
|
|||
namespace react {
|
||||
|
||||
/*
|
||||
* ScriptTag
|
||||
*
|
||||
* 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.
|
||||
* Hermes bytecode bundles (as encoded by hermesc, not metro) are not treated
|
||||
* in a special way, they will be identified as ScriptTag::String.
|
||||
*/
|
||||
enum struct ScriptTag {
|
||||
String = 0,
|
||||
RAMBundle,
|
||||
HBCBundle,
|
||||
MetroHBCBundle,
|
||||
};
|
||||
|
||||
/**
|
||||
* BundleHeader
|
||||
*
|
||||
* RAM bundles and BC bundles begin with headers. For RAM bundles this is
|
||||
* 4 bytes, for BC bundles this is 12 bytes. This structure holds the first 12
|
||||
* bytes from a bundle in a way that gives access to that information.
|
||||
|
@ -43,27 +41,33 @@ struct FOLLY_PACK_ATTR BundleHeader {
|
|||
std::memset(this, 0, sizeof(BundleHeader));
|
||||
}
|
||||
|
||||
uint32_t magic;
|
||||
uint32_t reserved_;
|
||||
union {
|
||||
struct {
|
||||
uint32_t value;
|
||||
uint32_t reserved_;
|
||||
} magic32;
|
||||
uint64_t magic64;
|
||||
};
|
||||
uint32_t version;
|
||||
};
|
||||
FOLLY_PACK_POP
|
||||
|
||||
/**
|
||||
* parseTypeFromHeader
|
||||
*
|
||||
* Takes the first 8 bytes of a bundle, and returns a tag describing the
|
||||
* bundle's format.
|
||||
*/
|
||||
RN_EXPORT ScriptTag parseTypeFromHeader(const BundleHeader &header);
|
||||
|
||||
/**
|
||||
* stringForScriptTag
|
||||
*
|
||||
* Convert an `ScriptTag` enum into a string, useful for emitting in errors
|
||||
* and diagnostic messages.
|
||||
*/
|
||||
RN_EXPORT const char *stringForScriptTag(const ScriptTag &tag);
|
||||
|
||||
/**
|
||||
* Check whether a given bundle is hermesc-generated bytecode
|
||||
*/
|
||||
RN_EXPORT bool isHermesBytecodeBundle(const BundleHeader &header);
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
|
Загрузка…
Ссылка в новой задаче