在本章中,所有的分析算法的约束函数形式都如下:
$$ [[v]]=t_v (JOIN(v)) $$
对于某些函数$t_v : L\rightarrow L$,其中L是描述抽象状态的格;$JOIN(v)={\bigsqcup}_{w \in dep^{-1}(v)} [[w]]$ (描述节点之间的关系)。函数t_v 是CFG节点 v 的转移函数,用于描述当前分析算法如何对在v的程序语句进行建模。对于前向分析(这是最常见的数据流分析类型),传递函数的输入表示语句之前的程序点的抽象状态,而其输出表示语句之后的程序点的抽象状态(对于后向分析则相反)。因此,在为数据流分析指定约束时,只需提供所有控制流图节点的传递函数即可。作为一个示例,在符号分析中,晶格是$L = Vars \rightarrow Sign$,对于赋值节点形如 $X = E$的转移函数为:(函数是t,作用的对象是节点v的语句X=E,入参s表示之前的状态。计算的结果是将s里面,X替换为eval(s,E)的结果。eval(s,E)是自己定义的,基于状态s的情况下,计算E的符号!)
$$ t_{X=E}(s) = s[X \mapsto eval(s,E)] $$
在朴素的工作列表算法中,JOIN(v)在while循环中的每次迭代都会计算。然而,通常情况下,[[w]] 自上次处理 v 以来并没有发生变化,因此其中的大部分计算可能是多余的。(当我们在第8章引入跨过程分析时,我们将看到 dep−1(v) 可能变得很大。)我们现在提出了另一种基于传递函数的工作列表算法,可以避免一些冗余计算。对于前向分析,这个算法中每个变量 xi 表示与对应的控制流图节点 vi 之前的程序点的抽象状态,与我们之前见过的其他固定点求解器不同,其中 xi 表示与 vi 之后程序点的抽象状态(反之亦然,对于后向分析)。
与简单的工作列表算法相比,这个变种通常避免了许多冗余的最小上界计算。在 while 循环的每次迭代中,应用当前节点 vi 的传递函数,然后将得到的抽象状态传播到所有依赖节点(因此算法的名称中包含了“传播”一词)。那些发生变化的节点将被添加到工作列表中。因此,对于所有节点 v 和 w,其中 w 属于 succ(v),我们都有 $t_v([[v]]) \sqsubseteq [[w]]$ (意思是后续比之前的结果偏序关系更大,单调性!)。