Skip to content

v0.1.11

Latest

Choose a tag to compare

@github-actions github-actions released this 28 Sep 16:23
· 3 commits to main since this release
v0.1.11
ffe8487

修复循环头栈溢出守卫被整条丢弃(arm64)

这一版只有一处代码改动,但它修的是控制流语义:产物此前会让读者误判函数在每次循环迭代都做了栈检查。

缺陷

Dart 把栈溢出检查放在循环头,而真正的循环条件在下一个块:

块10(循环头): ldr BARRIER,[THR,#0x48]; cmp SP,BARRIER; b.ls <handler>   ← 守卫
块11:           cmp r1, #4; b.ge <exit>                                  ← 循环条件
0x4bbdec:       b 0x4bbdac                                               ← 回边指向循环头

loop_shape 的兜底臂返回 succ(h, 0),也就是分支目标,于是 out-of-line 的溢出处理块被当成了循环体入口。产物变成:

改前:  while (true) {
         BARRIER = mem(THR, 0x48); // 0x4bbdac
         sub_0x4c3c40();           // 0x4bbe30     ← 每圈无条件调用溢出 stub
         if (x1 >= 4) { break; }

两条语句地址相差 0x64,正是「把远端处理块内联到了加载后面」的痕迹;守卫的 if 因为循环头路径 continue 跳过整个分支处理而彻底消失。溢出 stub 于是从「仅 SP <= BARRIER 时调用」变成「每圈无条件调用」。

改后:  while (true) {
         BARRIER = mem(THR, 0x48); // 0x4bbdac
         if (SP <= BARRIER) {
           sub_0x4c3c40(); // 0x4bbe30
         }
         if (x1 >= 4) { break; }

判别依据

关键是区分「头块的分支是循环条件」与「头块的分支只是守卫」。用的是侧块的形状,不是循环归属:溢出处理块的形态是 bl <stub>; b <落空块>,即它的无条件跳转目标正好等于头块的落空后继(is_rejoin_side_block)。成立时头块的分支就是守卫,于是兜底臂改为从落空边进入循环体,并在 body 顶部把守卫补发成 if。

第一版用「分支目标在循环外」判别,失败了:处理块 b 回循环内,被循环检测标成 in_loop,判据恒假;结果它在别处挪动了 15 个 if 而目标缺陷一点没修,已完整撤回(撤回后 material_3_demo 1011 个文件与改动前逐字节一致)。教训写进了 docs/DECOMPILER.md:结构化率 7 语料全不变 + 所有门禁全绿,仍不足以说明改动是对的——必须直接去看目标实例的产物。

验证

项 结果
sample_arm64 无守卫处 113 → 0(有守卫 758 → 875,if ( 5445 → 5562)
Reqable(arm64 真机商业应用) 守卫 1218 处 / 无守卫 0 处(改前是 1149 / 69,69 处全部补回,与独立审计的数字精确吻合)
结构化率 7 语料 逐一完全不变:1060/115、1047/127、1051/116、1063/156、13947/1135、1716/92、1073/115
dart analyze material_3_demo 全量 0 错误、Reqable 全量 0 错误、T4_blank(真机安卓、压缩指针)0 错误
非 dart/ 产物 逐字节一致
性能 material_3_demo 带 --decompile 0.84–0.85 s(v0.1.10 为 1.09–1.22 s)、不带 0.45–0.47 s
门禁 65 测试全绿、full_scorecard 26 样本 / 291 文件 / 24 253 函数 / 0 错误、regress_all 25/25、check_profiles 47/47、clippy 0

棘轮门禁 stack_check_guards_do_not_regress 的上限已收到 0,从此强制保持。

另得到一个否定结论:empty_if_without_else_does_not_grow 仍是 109、没有跟着下降,说明那 139 个「无 else 的空 if 丢分支边」与本次不同源,是另一个根因(已在文档中记录,含三个尚未试过的判别方向)。

发版前抽查

22 条子命令逐条冒烟测试全部 rc=0 且输出非空(arrays/maps 是刻意不提供的子命令——它们是 text/ 下的单列 dump,grep text/arrays.txt 就够,CLI 会把 dae arrays X 读成 dae <bin> <out> 快捷形并报「读不到名为 arrays 的文件」,这是正确行为)。