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:
Pieter De Baets 2022-03-15 05:58:52 -07:00 коммит произвёл Facebook GitHub Bot
Родитель 4b6c0b92c0
Коммит be0ff779b1
9 изменённых файлов: 58 добавлений и 56 удалений

Просмотреть файл

@ -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