$Abstract^2 Interpretation$
本文提出 \(A^2I\),即 \(Abstract^2 Interpretation\),也叫做 meta-abstract interpretation,是指使用抽象解释(abstract interpretation)分析静态程序分析器(static program analyser)的特性。
静态程序分析(static program analysis)是自动提取程序动态行为特性的技术。抽象解释是一种生成静态程序分析方法的理论框架,思想是提取程序特性以逼近程序语义(semantics)。
要理解 \(A^2I\),就得先理解 AI(抽象解释,下同)。
我们以单调数据流分析框架(monotone dataflow analysis framework)为基础开始讲。单调框架包括三部分:
- 特性格(property lattice),这里说一下,所谓特性,一般就是指抽象值(abstract value);
- 格中元素的含义,即如何解释分析的结果;
- 转移函数(transfer function)。
单调框架只是一种表示静态分析的数学格式,它的问题在于其正确性证明一般依赖于寻找不变量(invariant),每个分析的正确性证明是独立的(ad-hoc)。
为此,抽象解释提出一种构建程序分析的系统化的方法。需要:
- 特性格;
- 表示函数(representation function),即如何解释格中的元素。
然后可以定义出满足一定条件的可计算函数作为转移函数。
抽象解释对分析的定义是增量的,分析由正确性易证的“分析”开始,即收集语义(collecting semantics),收集程序的准确信息,但是直接对其静态分析是困难甚至不可能的,因为特性空间(property space)太大或不满足升链条件(ACC: Ascending Chain Condition,满足ACC也称为诺特的,Noetherian。\(a_1 \leq a_2 \leq \cdots, \exist n, \forall m > n, a_m = a_n\))。接着我们计算得到收集语义的一个近似,即由基础分析(basic analysis)得到精化分析(refined analysis)。最后不断迭代精化(近似)过程直到得到一个可计算函数。
具体程序执行(concrete program execution)可以表示如下:
\(V = \lbrace v_i \rbrace\) 是值或状态的集合,\(\rightsquigarrow\) 是传递关系,当具体执行是确定性的(deterministic)时候,传递关系就是一个函数。
而程序分析,即程序的抽象执行(abstract execution)可以表示为:
其中 \(l_{i+1} = f_L(l_i)\),\(f_L : L \rightarrow L\) 是一个函数,L 是完全格(complete lattice)。在 \(A_2I\) 的论文中,作者严格区分了完全格与:1)dcpo(有向偏序),每个子集都有上确界;2)cpo(完全偏序),包含最小元素的dcpo。
即 \(l_i\) 是 \(v_i\) 的抽象,由此为了提供正确性证明的机制,需要定义两者之间一种正确性关系(correctness relation),\(R \subseteq V \times L\)。
- \(\forall v, l_1, l_2, (vRl_1) \bigwedge (l_1 \sqsubseteq l_2) \rightarrow (vRl_2)\),即在特性格 L 中,\(l_1\) 是比 \(l_2\) 更精确的对 v 的抽象;
- \(\forall v, \forall L^{'} \subseteq L, (\forall l \subseteq L^{'}, (vRl)) \rightarrow vR(\sqcap L^{'})\),即 \(\sqcap L^{'}\) 是 v 的最精确近似(valid approximation)。
特性空间是格是为了比较抽象值之间的精度,更小更精确,更大更安全(仍然正确)。
由此已经证明了抽象的正确性,由归纳也易证传递的正确性。即我们已经得到了基础分析。
给定一个程序点 p,由 \(v_0\) 出发的所有路径都会达到 p,但计算所有路径的抽象值得到特性格中的一个元素是不可行的。由数据流方程(dataflow equation)可以计算不动点(fixed point),但需要满足两个条件:
- 单调;
- L满足ACC。
这是传统的计算程序分析结果的方法。
但是很多时候分析收敛(converge)很慢,或者不满足 ACC。为此抽象解释使用一个更小(更精确)的格 M 来对 L 进一步抽象,L 和 M 之间满足伽罗瓦连接(Galois connection)\(\langle L, \alpha, \gamma, M\rangle\)。其中 \(\alpha\) 和 \(\gamma\) 是单调函数,\(\alpha\) 把 L 中的元素映射到 M 中,称为抽象化函数(abstraction function);\(\gamma\) 把 M 中的元素映射到 L 中,称为具体化函数(concretization function)。且满足两个约束:
- \(\alpha \circ \gamma \sqsubseteq_M \lambda m.m\),即具体化不损失精度;
- \(\gamma \circ \alpha \sqsupseteq_L \lambda l.l\),即抽象化可能损失精度。
由此我们得到精化分析。(可以证明S也满足正确性关系。)
到这里我们就说明了 slides 第 8 页的:
- 语义是抽象解释的实例;
- 收集语义是抽象解释的实例;
- 对抽象解释实例的抽象是抽象解释的实例。
所以,由对程序特性的抽象(AI)上升到对程序分析特性的抽象(\(A^2I\))是可行的。
回到之前的讨论,有了精化分析之后,不动点的计算可以转化为求 AKS(Ascending Kleene Sequence)的极限,AKS 可以表示为 \((f^n(\tau))_n\),其中 \(f : L \rightarrow L\) 是单调的传递函数,\(\tau\) 以 L 的底元作为起始值。若 AKS 会趋于稳定(连续格,有限格上连续和单调等价),则其稳定于 f 的最小不动点 lfp(f)。但有时并不会趋于稳定,或者是迭代太慢,为此又引入 widening 技术来计算 AKS 的上近似(upper approximation),AAS(Ascending Approximation Sequence),它有着更快的收敛速度。由此求得 lfp(f) 的上近似。(不考虑抽象解释,数据流分析也可以使用widening,有略微不同。)
widening 算子 \(\nabla : L \times L \rightarrow L\) 定义如下:
- \(\nabla\) 是一个上界(upper bound)算子,即 \(\forall l_1, l_2, l_1 \sqsubseteq l_1 \nabla l2, l_2 \sqsubseteq l_1 \nabla l2\);
- 对 L 中所有的上升链 \((l_n)_n\),上升链 \((l_n^{\nabla})_n\)(也在 L 中)必趋于稳定。
由链的构造易知:
稳定性易证。将上升链扩展到单调函数也同理。
narrowing 算子与 widening 类似,而这两个算子的实质就是:分析程序分析的不动点迭代的变化,来计算下一个迭代。其实已经有了 \(A^2I\) 的雏形。作者把这一类归入 online meta-analysis(during program analysis),此外还有 offline meta-analysis(before/after program analysis)。
为了支持A2I,需要对抽象解释做一些修改,把收集语义扩展到跟踪语义(trace semantics),即由可达抽象值集合扩展到抽象解释器不动点迭代的序列。以间隔分析(interval analysis)为例,考虑下面的程序:
收集语义下的 Jacobi 迭代为:
对于由底元开始的序列
推导规则为:
跟踪语义下的Jacobi迭代则是:
其包含过程信息。
将对其抽象定义为:
根据该定义手推前5项:
通过对常量传播(constant propagation)分析的抽象,可以将以上迭代加快如下:
其推导规则为:
这里可能看不明白,同样我们手推前5项(在第5项就收敛了)。
这里看到利用常量传播格 \(D_c\) 的积格(product lattice)\(D_c^2\),大幅提升了收敛速度。
后面作者举了很多 online/offline \(A^2I\) 的例子,都是一些比较新的静态分析的工作,\(A^2I\) 可以看作是对它们通用的理论上的提炼,一作 Cousot 就是 AI 的提出者。作者指出从 1977 年提出 AI 至今,软件的规模、复杂性不断上升,很多软件也并不是安全攸关的(safety-critical),希望这一框架可以使传统抽象解释步入现代,指导高效精确的程序分析算法设计。