控制流平坦化的还原思路:从调用点出发,而不是从 switch 出发
📝 这是一篇骨架。 每个
<!-- 写这里 -->都是你要填的地方,填完把这段提示删掉。 没写完不想让它出现在列表里,就在 frontmatter 加一行draft: true。写的时候记住三条:
- 写你自己踩的坑,不写网上抄得到的通识
- 写方法论,不写针对特定目标的可用绕过方案
- 每个结论后面跟一段代码或数据,空谈没有说服力
背景:为什么"全局展开 switch"行不通
换个方向:从真实调用点反向追踪
核心思路是不追求全局展开,只处理入口和参数能够静态证明的执行路径。
整条流水线是这样的:
text
识别 dispatcher 调用
→ 解析 case ID 与参数数组
→ 追踪真实 case 链
→ 规范化路径内代理调用
→ 递归处理嵌套 dispatcher
→ 绑定参数并选择平铺 / IIFE / 命名 helper
→ 将 dispatcher/helper 上下文交给字符串运行时第一步:确认它真的是 dispatcher
只有绑定到带 switch 的函数才会被当作 dispatcher。
第二步:静态入口与静态参数容器
第三步:case 链追踪与环路保护
第四步:输出形态的选择
还原完的代码有三种落地方式,选哪种取决于 return 的位置、调用点所处的上下文,以及参数数组是否被当作对象身份使用:
| 形态 | 适用场景 |
|---|---|
| 直接平铺 | |
| IIFE | |
| 命名 helper |
职责边界:还原引擎不该做字符串解密
安全约束:宁可不还原,也不能还原错
这套东西最难的不是"能还原多少",而是"在什么情况下必须停手"。
- 只展开静态入口和静态参数容器
- 未知分支可能影响字符串栈时,回滚整次事务
- 未知循环只处理必然执行的部分,不模拟循环次数
- 延迟函数体不因为遍历到定义就提前执行
- 运行时对象和字符串栈在每次求值后恢复
目前的边界在哪
诚实地列出没做到的部分,比吹能做到什么更可信:
| 未完成 | 原因 |
|---|---|
| 通用返回闭包上下文 | 需要把创建时的上下文持久化到闭包绑定,并在真实调用点恢复 |
| 延迟回调 | 事件、计时器、宿主回调的执行时机未知,必须等调用关系被证明 |
| async / generator | 涉及挂起和恢复点,不能沿用同步模型 |
| 动态 case ID | 缺少可靠静态入口时保留原调用 |
小结
本文只讨论通用的 AST 还原方法,不涉及针对特定目标的可用方案。