diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h index 49d31beceeb..a8a95f72639 100755 --- a/React/Base/RCTJavaScriptLoader.h +++ b/React/Base/RCTJavaScriptLoader.h @@ -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, diff --git a/React/Base/RCTJavaScriptLoader.mm b/React/Base/RCTJavaScriptLoader.mm index aa89b99f1c7..133851a1d7f 100755 --- a/React/Base/RCTJavaScriptLoader.mm +++ b/React/Base/RCTJavaScriptLoader.mm @@ -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; diff --git a/React/CxxBridge/RCTCxxBridge.mm b/React/CxxBridge/RCTCxxBridge.mm index e3b4ac19ff5..2454980a9bb 100644 --- a/React/CxxBridge/RCTCxxBridge.mm +++ b/React/CxxBridge/RCTCxxBridge.mm @@ -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(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(sourceUrlStr.UTF8String); std::unique_ptr scriptStr = ramBundle->getStartupCode(); diff --git a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp index 18abeb88b2f..9f017d082e4 100644 --- a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp +++ b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp @@ -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 script; RecoverableError::runRethrowingAsRecoverable( [&fileName, &script]() { diff --git a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp index 16fcc43d4fa..dc87d09bac8 100644 --- a/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp +++ b/ReactAndroid/src/main/jni/react/jni/JSLoader.cpp @@ -77,7 +77,7 @@ loadScriptFromAssets(AAssetManager *manager, const std::string &assetName) { // script to ensure we have a terminator. const BundleHeader *header = reinterpret_cast(script->c_str()); - if (parseTypeFromHeader(*header) == ScriptTag::HBCBundle) { + if (isHermesBytecodeBundle(*header)) { return script; } } diff --git a/ReactCommon/cxxreact/Instance.cpp b/ReactCommon/cxxreact/Instance.cpp index a48d5930ecf..85993f43a93 100644 --- a/ReactCommon/cxxreact/Instance.cpp +++ b/ReactCommon/cxxreact/Instance.cpp @@ -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) { diff --git a/ReactCommon/cxxreact/Instance.h b/ReactCommon/cxxreact/Instance.h index 8b2263679a0..1b125d37f4b 100644 --- a/ReactCommon/cxxreact/Instance.h +++ b/ReactCommon/cxxreact/Instance.h @@ -56,7 +56,7 @@ class RN_EXPORT Instance { std::unique_ptr 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 *string); void loadRAMBundleFromString( diff --git a/ReactCommon/cxxreact/JSBundleType.cpp b/ReactCommon/cxxreact/JSBundleType.cpp index 8a7c38af9c2..036397c8113 100644 --- a/ReactCommon/cxxreact/JSBundleType.cpp +++ b/ReactCommon/cxxreact/JSBundleType.cpp @@ -7,20 +7,21 @@ #include "JSBundleType.h" -#include - 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 diff --git a/ReactCommon/cxxreact/JSBundleType.h b/ReactCommon/cxxreact/JSBundleType.h index f6c0d7a3fbb..a9cdbc806a7 100644 --- a/ReactCommon/cxxreact/JSBundleType.h +++ b/ReactCommon/cxxreact/JSBundleType.h @@ -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