Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ jobs:
- name: Load every built module and run the offline commands
run: node scripts/smoke-lib.js

- name: Check late abort errors on the oldest supported Node.js
run: >-
node tests/fixtures/hermes-async-check.cjs
'{"operation":"abort","modulePath":"./lib/utils/hermes-base.js"}'

- name: Check debug-output failure cleanup on Node 18
env:
HERMES_TEST_NODE: node
run: bun test tests/hermes-blockers.test.ts

publish-dry-run:
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 10
Expand Down Expand Up @@ -134,3 +144,80 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm publish --dry-run --access public --tag dry-run

hermes-integration:
name: hermes-hbc-${{ matrix.hbc }}${{ matrix.suffix }}
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- hbc: 96
suffix: ""
package: react-native@0.77.3
directory: react-native
executable: sdks/hermesc/linux64-bin/hermesc
- hbc: 98
suffix: ""
package: hermes-compiler@250829098.0.16
directory: hermes-compiler
executable: hermesc/linux64-bin/hermesc
- hbc: 98
suffix: -patch17
package: hermes-compiler@250829098.0.17
directory: hermes-compiler
executable: hermesc/linux64-bin/hermesc
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v7
with:
node-version: '24.x'
- run: bun install --frozen-lockfile
- name: Install the pinned real compiler
shell: bash
env:
COMPILER_PACKAGE: ${{ matrix.package }}
COMPILER_DIRECTORY: ${{ matrix.directory }}
COMPILER_EXECUTABLE: ${{ matrix.executable }}
EXPECTED_HBC: ${{ matrix.hbc }}
run: |
set -euo pipefail
root="$RUNNER_TEMP/hermes-tests/$COMPILER_DIRECTORY"
mkdir -p "$root"
archive=$(npm pack "$COMPILER_PACKAGE" --pack-destination "$RUNNER_TEMP" --silent)
tar -xzf "$RUNNER_TEMP/$archive" --strip-components=1 -C "$root" \
"package/$COMPILER_EXECUTABLE" package/package.json
export HERMESC="$root/$COMPILER_EXECUTABLE"
test -x "$HERMESC"
"$HERMESC" -version
bun -e 'import {probeHbcVersion} from "./src/utils/hermes-base"; if (probeHbcVersion(process.env.HERMESC) !== Number(process.env.EXPECTED_HBC)) throw new Error("unexpected HBC version");'
echo "HERMESC=$HERMESC" >> "$GITHUB_ENV"
- name: Fetch pinned real Metro fixtures
shell: bash
run: |
set -euo pipefail
root="$RUNNER_TEMP/hermes-metro-fixtures"
mkdir -p "$root"
base="https://raw.githubusercontent.com/sunnylqm/hbc-diff-benchmark/e6a870a1c26c4b64c7860d7e1aa575707d22ad88"
for file in base.jsbundle s3-medium-feature.jsbundle; do
curl --fail --location --retry 2 --max-time 60 "$base/fixtures/$file" -o "$root/$file"
done
curl --fail --location --retry 2 --max-time 60 "$base/LICENSE" -o "$root/LICENSE"
echo "11c8ad8f7e8c7c59ee45582c77d896a35fa646617f3ba0f5b338a425a7c93b7d $root/base.jsbundle" | sha256sum --check
echo "a693e68254b6c13fae8f839d20c14f9d11c5ab98d4be1b8d13ba1e929a12d752 $root/s3-medium-feature.jsbundle" | sha256sum --check
echo "HERMES_METRO_FIXTURES=$root" >> "$GITHUB_ENV"
- name: Run real compiler and fallback regressions
run: bun test tests/hermes-*.test.ts
- name: Run seeded differential fuzzing
run: bun run fuzz:hermes-base --rounds 50 --seed ${{ matrix.hbc }} --out "${{ runner.temp }}/hermes-fuzz"
- name: Preserve failing fuzz cases
if: failure()
uses: actions/upload-artifact@v7
with:
name: hermes-fuzz-hbc-${{ matrix.hbc }}${{ matrix.suffix }}
path: ${{ runner.temp }}/hermes-fuzz
if-no-files-found: ignore
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const publishResult = await provider.publish({

Hermes projects: `bundle` always runs hermesc with `-output-source-map`, so the debug info section is stripped from the bytecode (15–40% smaller, same as React Native's own release builds). The Hermes sourcemap stays in the intermediate directory (`.pushy/intermedia/<platform>/<bundle>.map`, never packed into the ppk) and is composed with the packager map — `--sourcemap` is on by default since 2.23 (`--no-sourcemap` opts out). When `bundle` publishes, that final map is uploaded and archived with the version (`sourceMapKey`), so `pushy symbolicate` can map crash stacks — including Hermes `address at` frames — back to source later. `pushy publish <ppk> --sourcemap <file.map>` archives a map for a ppk built elsewhere; publishing without a map prints a warning.

Hermes delta mode (`-base-bytecode`): by default (`--hermesBase auto`) `bundle` compiles against the previous HBC of the same app, which keeps Hermes string IDs stable and makes hot-update patches 5–30× smaller. The base comes from the server (`GET /app/:id/hermesBase`), verified by sha256 and kept in a local cache (`.pushy/cache/<sha256>`, 500 MB / 20 files, `PUSHY_CACHE_DIR` / `--cacheMaxMb` to tune, `pushy cache [clean]` to inspect or clear). `--hermesBase none` disables it; `--hermesBase <file.hbc|.ppk|.apk|.ipa>` uses a local artifact (for example the store build). `--verifyHermesBase` (default on) additionally compiles without the base (concurrently with the base compile) and compares both disassemblies — literal buffers by content, then function by function, with only representation differences (string ids, operand widths, jump distances, buffer offsets) folded away; on any mismatch or failure the CLI falls back to the plain compile, so the feature can never block a release. The log names the first difference (function, line, both sides) or, separately, a dump that could not be read; set `PUSHY_HERMES_BASE_DEBUG=1` to keep both disassemblies (`hermes-base-dump-base.txt` / `hermes-base-dump-plain.txt` next to the intermediate directory) for a bug report. What the check covers, what it does not yet, and how to triage a rejection: [docs/hermes-base-verification.md](docs/hermes-base-verification.md). The result is reported at publish (`hermesBaseOutcome`: `used` / `rejected` / `dump-failed` / `none`, plus the first difference) so the server can watch the rejection rate across apps. `HERMESC=<path> bun run fuzz:hermes-base --rounds 300` compiles random programs against random bases and reports any build the check would wrongly reject (differential fuzzing of the normalization rules). Only hermesc builds that include the upstream delta-mode fix are used (classic `react-native/sdks/hermesc`, or `hermes-compiler` ≥ 250829098). If a base compile fails, the full hermesc output is written to `hermes-base-error.log` next to the intermediate directory. `--resetCache false` skips Metro's `--reset-cache` and reuses its transform cache, which makes repeated bundles much faster.
Hermes delta mode (`-base-bytecode`): by default (`--hermesBase auto`) `bundle` compiles against the previous HBC of the same app, which keeps Hermes string IDs stable and makes hot-update patches 5–30× smaller. The base comes from the server (`GET /app/:id/hermesBase`), verified by sha256 and kept in a local cache (`.pushy/cache/<sha256>`, 500 MB / 20 files, `PUSHY_CACHE_DIR` / `--cacheMaxMb` to tune, `pushy cache [clean]` to inspect or clear). `--hermesBase none` disables it; `--hermesBase <file.hbc|.ppk|.apk|.ipa>` uses a local artifact (for example the store build). `--verifyHermesBase` (default on) additionally compiles without the base (concurrently with the base compile) and checks both artifacts in two passes: readable disassembly followed by raw operands resolved against complete binary strings, constants, function references and control-flow targets. Quoted whitespace and branch destinations are preserved; unsupported or unreadable layouts fail closed. On a mismatch, verification failure or compiler timeout the CLI falls back to the plain compile; failures of the plain compiler or the final sourcemap still fail the build. The raw pass adds verification time and holds both HBC files in memory, but does not add another compile. The log names the first difference (function, line, both sides) or, separately, a dump that could not be read; set `PUSHY_HERMES_BASE_DEBUG=1` to keep both disassemblies (`hermes-base-dump-base.txt` / `hermes-base-dump-plain.txt` next to the intermediate directory) for a bug report. Probe, verification and compile/sourcemap process deadlines default to 30/120/300 seconds respectively, configurable in milliseconds via `PUSHY_HERMES_PROBE_TIMEOUT_MS`, `PUSHY_HERMES_VERIFY_TIMEOUT_MS` and `PUSHY_HERMES_COMPILE_TIMEOUT_MS`. What the check covers, what it does not yet, and how to triage a rejection: [docs/hermes-base-verification.md](docs/hermes-base-verification.md). The result is reported at publish (`hermesBaseOutcome`: `used` / `rejected` / `dump-failed` / `none`, plus the first difference) so the server can watch the rejection rate across apps. `HERMESC=<path> bun run fuzz:hermes-base --rounds 300` compiles random programs against random bases and reports any build the check would wrongly reject (differential fuzzing of the normalization rules). Only hermesc builds that include the upstream delta-mode fix are used (classic `react-native/sdks/hermesc`, or `hermes-compiler` ≥ 250829098). If a base compile fails, the full hermesc output is written to `hermes-base-error.log` next to the intermediate directory. `--resetCache false` skips Metro's `--reset-cache` and reuses its transform cache, which makes repeated bundles much faster.

### Version

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const publishResult = await provider.publish({

Hermes 工程:`bundle` 调用 hermesc 时始终带 `-output-source-map`,因此字节码不含 debug info 段(小 15%~40%,与 React Native 自身 release 构建一致)。Hermes sourcemap 保留在中间目录(`.pushy/intermedia/<platform>/<bundle>.map`,不会打进 ppk),并与 packager map 合成——自 2.23 起 `--sourcemap` 默认开启(`--no-sourcemap` 关闭)。`bundle` 发布时会把这份最终 map 上传并随版本归档(`sourceMapKey`),之后用 `pushy symbolicate` 即可把崩溃堆栈(含 Hermes 的 `address at` 帧)还原到源码。别处打好的 ppk 可用 `pushy publish <ppk> --sourcemap <file.map>` 归档;不带 map 发布会打印警告。

Hermes delta 模式(`-base-bytecode`):默认 `--hermesBase auto`,`bundle` 会以同一应用上一版的 HBC 为 base 编译,让 Hermes 字符串 ID 跨版本稳定,热更 patch 可缩小 5~30 倍。base 由服务端(`GET /app/:id/hermesBase`)给出、按 sha256 校验并存入本地缓存(`.pushy/cache/<sha256>`,默认 500 MB / 20 个,可用 `PUSHY_CACHE_DIR` / `--cacheMaxMb` 调整,`pushy cache [clean]` 查看或清空)。`--hermesBase none` 关闭;`--hermesBase <file.hbc|.ppk|.apk|.ipa>` 指定本地文件(比如商店包)作 base。`--verifyHermesBase`(默认开)会并行再做一次普通编译并比对两份反汇编——先按内容比对字面量缓冲区,再逐函数比对,只折叠纯表示层差异(字符串 id、操作数宽度、跳转距离、缓冲区偏移);任何不一致或失败都回退到普通编译,不会阻塞发版。日志会给出第一处差异(函数、行、两侧内容),dump 读取失败会单独说明;设置 `PUSHY_HERMES_BASE_DEBUG=1` 可把两份反汇编保留在中间目录旁(`hermes-base-dump-base.txt` / `hermes-base-dump-plain.txt`)用于提 issue。校验覆盖什么、还缺什么、如何排查一次拒绝:见 [docs/hermes-base-verification.md](docs/hermes-base-verification.md)。校验结果会随发布上报(`hermesBaseOutcome`:`used` / `rejected` / `dump-failed` / `none`,附第一处差异),服务端可据此观察全体应用的拒绝率。`HERMESC=<path> bun run fuzz:hermes-base --rounds 300` 会用随机程序配随机 base 编译并报告校验误杀的构建(对归一化规则做差分模糊测试)。只有包含上游 delta 模式修复的 hermesc 才会启用(经典 `react-native/sdks/hermesc`,或 `hermes-compiler` ≥ 250829098)。base 编译失败时,完整的 hermesc 输出会写到中间目录旁边的 `hermes-base-error.log`。`--resetCache false` 可跳过 Metro 的 `--reset-cache`,复用其转换缓存,重复打包会快很多。
Hermes delta 模式(`-base-bytecode`):默认 `--hermesBase auto`,`bundle` 会以同一应用上一版的 HBC 为 base 编译,让 Hermes 字符串 ID 跨版本稳定,热更 patch 可缩小 5~30 倍。base 由服务端(`GET /app/:id/hermesBase`)给出、按 sha256 校验并存入本地缓存(`.pushy/cache/<sha256>`,默认 500 MB / 20 个,可用 `PUSHY_CACHE_DIR` / `--cacheMaxMb` 调整,`pushy cache [clean]` 查看或清空)。`--hermesBase none` 关闭;`--hermesBase <file.hbc|.ppk|.apk|.ipa>` 指定本地文件(比如商店包)作 base。`--verifyHermesBase`(默认开)会并行再做一次普通编译并进行两遍校验:先比较易读反汇编,再将 raw 操作数与二进制中的完整字符串、常量、函数引用和控制流目标核对。字符串内部空白和分支目的地不会被抹掉;不支持或无法解析的布局不能判为等价。发现差异、无法完成校验或 base 编译超时会回退到普通编译;真正的 plain 编译或最终 sourcemap 失败仍会使构建失败。raw 核对增加验证时间并在内存中持有两份 HBC,但不增加编译次数。日志会给出第一处差异(函数、行、两侧内容),dump 读取失败会单独说明;设置 `PUSHY_HERMES_BASE_DEBUG=1` 可把两份反汇编保留在中间目录旁(`hermes-base-dump-base.txt` / `hermes-base-dump-plain.txt`)用于提 issue。版本探测、完整验证、单个编译/sourcemap 子进程的期限默认分别为 30/120/300 秒,可用 `PUSHY_HERMES_PROBE_TIMEOUT_MS`、`PUSHY_HERMES_VERIFY_TIMEOUT_MS`、`PUSHY_HERMES_COMPILE_TIMEOUT_MS`(毫秒)调整。校验覆盖什么、还缺什么、如何排查一次拒绝:见 [docs/hermes-base-verification.md](docs/hermes-base-verification.md)。校验结果会随发布上报(`hermesBaseOutcome`:`used` / `rejected` / `dump-failed` / `none`,附第一处差异),服务端可据此观察全体应用的拒绝率。`HERMESC=<path> bun run fuzz:hermes-base --rounds 300` 会用随机程序配随机 base 编译并报告校验误杀的构建(对归一化规则做差分模糊测试)。只有包含上游 delta 模式修复的 hermesc 才会启用(经典 `react-native/sdks/hermesc`,或 `hermes-compiler` ≥ 250829098)。base 编译失败时,完整的 hermesc 输出会写到中间目录旁边的 `hermes-base-error.log`。`--resetCache false` 可跳过 Metro 的 `--reset-cache`,复用其转换缓存,重复打包会快很多。

### Version

Expand Down
Loading