“下推CFA保留无界栈,普通下推转移只看栈顶,而GC需要整栈的根地址。二者结合需用可达根摘要或受限的栈检查机制。简单把有限栈扫描代码移到无界下推模型上,会越过其可判定算法所依赖的接口。”
形式陈述
下推控制流分析把高阶程序的环境和存储有限化,却保留未设深度上界的调用栈。它从抽象机器出发,将状态写成
表达式e来自有限程序,环境和存储采用有限地址及有限值近似,所以控制状态集Q有限;栈帧集合
选择无第一类续延的传值高阶核心,使一步执行只需有限控制和栈顶:调用可压一帧,返回弹指定帧,尾调用可不改变栈。于是机器成为一个下推系统,转移标记为
与七元组 PDA 接口对应时,另加永久栈底符号
分析目标是从初始控制
直觉
k-CFA把历史限制在有限上下文中,因此容易让多次调用共享返回信息。下推方法不把整条栈压成有界历史,而用一套有限摘要表示无界嵌套:一段从压帧到匹配弹帧的计算可以整体视为净栈变化为零。
这类似括号语言。push a, push b, pop b, pop a合法;push a, pop b非法。增加递归深度只会增加嵌套层数,不会要求把所有栈逐条列出来。
图中+a表示压入a,−a表示弹出a;虚线B是净栈变化为空的平衡路径摘要,不是新增的程序指令。
例子与边界
相同函数入口,不同返回帧
设程序先计算 u=f(0),再计算 v=f(1),两个调用后分别还有不同工作。进入f时分别压入帧
f结束时,只能弹当前栈顶。第一配置返回u后的位置,第二配置返回v后的位置;即使q完全相同,也不产生交叉返回。这正是过程间合法路径在抽象机层面的直接实现。
若f递归调用自身,栈可以成为
用有限平衡关系代表无限嵌套
定义
,每条内部 边也加入B- 若
,加入 - 若
、 、 ,加入
第三条必须匹配同一个帧符号。设有
从空栈出发的合法路径末端可能仍有未返回调用。它能分成若干平衡片段和若干尚未匹配的push。因此可从
推论与应用
终止与证明责任
尽管栈配置无限,B最多只有
最简单的全扫描饱和可用非常保守的多项式界描述:至多
如果把每一种抽象存储都放进q,Q本身可能随程序大小指数增长。某些PDCFA变体通过统一存储的widening等手段取得源程序规模上的多项式界,同时付出别处精度损失。因此“保留无界栈仍可判定”与“分析整个程序很便宜”是两条不同结论。
栈精确不使所有值都精确
若进入f的两个实参已经在同一抽象地址合并,返回路径虽严格匹配,返回值仍可能混合。下推方法消除的是由错误调用返回配对造成的路径,不能恢复前面主动丢掉的堆或数值区别。
加入第一类续延、可从栈中间恢复的控制对象,或每一步都查看整条栈的操作时,原来“只读栈顶”的下推接口可能失效。抽象GC就需要知道所有续延帧持有的根地址;将它与下推分析结合需额外的栈根摘要或introspective构造,不能直接扫描无界栈后仍宣称使用同一个普通下推系统。
练习与解答
给边
验收须给出内外两次构造,并解释为什么不需要枚举运行栈深度。把四条边忽略标签后做普通传递闭包,无法通过这一检查。
参考资料
- Christopher Earl, Matthew Might, David Van Horn, “Pushdown Control-Flow Analysis of Higher-Order Programs”, Scheme, 2010,§§4–6:CESK到下推系统;§§7–8:Dyck状态图;§9:通过widening获得多项式版本
- J. Ian Johnson et al., “Pushdown Flow Analysis with Abstract Garbage Collection”, JFP 24(2–3), 2014,§1.1:整栈根信息与普通下推接口的冲突