概述
数据流分析解答的是“数据如何在控制流图(CFG)上流动”的问题。而这里的“数据”具体而言就是指程序特有的数据,在CFG上流动便是指流过其节点与边。“数据”代表的是抽象手段,而“流过”代表的是近似手段。
may analysis
输出可能为真的结果(过近似 over-approximation)
只要有一种为真的可能就应输出
must analysis
输出一定为真的结果(欠近似 under-approximation)
只有全部情况下为真才能输出
无论哪种近似手段,都是追求safe的近似。
在节点上,我们使用转移函数(Transfer function)的方式,而在边上,我们使用控制流处理(Control-flow handling)的方式。
对于不同的数据流分析应用,都有不同的数据抽象、流近似、转移函数和控制流处理。
在基本的数据流分析中,我们不考虑方法调用和别名这两种情况。
基础概念
输入状态和输出状态
每一条IR语句都将一个输入状态转变为一个新的输出状态。这两种状态与语句执行前后的程序状态关联。
显然的,对于相邻的两条语句,第一条语句的输出状态和第二条语句的输入状态是相等的。
值得注意的是,当两条控制流汇聚到一条语句时,我们使用∧来表示结果。这个运算根据应用来定义。
在所有数据流分析的应用中,我们把每一个程序点(program point)关联于一个数据流对象来表示这个点时所有可能程序状态的抽象。
而数据流分析的目的是通过预先设定的规则来对所有语句的输入、输出状态进行求解。这种规则基于安全近似导向的约束,其基于语句的语义(转移函数)和控制流。
转移函数约束
正向分析:
OUT[s]=fs(IN[s])反向分析:
IN[s]=fs(OUT[s])控制流约束
在有n条语句的基本块内:
IN[si+1]=OUT[si],i=1,2,......,n−1基本块之间:
IN[B]=In[s1]OUT[B]=OUT[sn]这两句揭示了基本块和具体语句的状态关系
OUT[B]=fB(IN[B]),fB=fsn∘...∘fs1IN[B]=⋀P a predecessor of BOUT[P]这两句揭示了正向分析中基本块间的状态关系
IN[B]=fB(OUT[B]),fB=fs1∘...∘fsnOUT[B]=⋀S a successor of BIN[S]这两句揭示了反向分析中基本块间的状态关系
Reaching Definitions
定义
在程序的p点上的定义d到达了程序的q点。所谓到达,就是指存在一条路径,其没有出现重复定义(没有被新的定义kill掉)
该分析的经典应用是检测未初始化变量。如果未初始化定义能够到达目标点,那么就有使用未定义变量的风险。
抽象
程序中所有的定义可以用一个位向量来表示。
如D1,D2,…,D100(100个定义),可以用00000……00(100位)表示。第i位表示Di定义
转移函数
对于以下语句:
D: v = x op y
这句产生了对变量v的定义D,同时消灭了其他对v的定义,而x和y的定义状态不受影响。
于是我们得到转移函数
OUT[B]=genB∪(IN[B]−killB)控制流
某个定义只要有一条道路能到达,那么它就是可达的,即有:
IN[B]=⋃P apredecessor of BOUT[P]算法
输入:CFG(每个基本块的killB和genB都已算出)
输出:每个基本块的IN[B]和OUT[B]
方法:
OUT[entry] = EMPTY
for(each entry block B\entry)
OUT[B] = EMPTY; //may analysis一般为EMPTY, must analysis一般为FULL
while(changes to any OUT occur)
for(each basic block B\entry){
IN[B] = union(OUT[B.predecessors]);
OUT[B] = union(gen(B), IN[B]-kill(B));
}
该迭代算法的结构也是很多分析算法的模板。
例如对以下程序:
我们用八位向量表达一个状态,每位表示对应的定义的存活情况。
初始时,所有OUT均被初始化为00000000
第一轮迭代中,B1首先产生1100 0000的OUT,随后B2的IN为B1和B4的OUT的并集,即为1100 0000。B2生成D3和D4,消灭D2,OUT为1011 0000。随后B3和B4分别输出0011 0010和0011 1100(B3和B4的计算顺序不影响最终结果)。B5输入0011 1110,输出0011 1011,第一轮迭代结束。
在第一轮迭代中,存在基本块的输出发生了变化,我们开始新一轮迭代。
在第二轮迭代中,B2和B3的输出发生了变化,进行第三轮迭代。
在第三轮迭代中,所有基本块的输出未发生变化,迭代结束。
在第三轮迭代中,我们可以发现,当IN[B]不变时,OUT[B]是不变的。
解析
为什么该算法能够停止
对于转移函数
OUT[B]=genB∪(IN[B]−killB)gen和kill是和in与out无关的,在分析过程中保持不变。于是当in不变时,out便不变。
当某个点的IN中的某个值变成1后,它在之后的相同位置的IN便永远都会变成1。于是输出便会最终固定。而位数是有限的,便会最终到达一个不动状态。
终止条件是否正确
当达到终止条件后,再次迭代已经不会改变任何状态。因为
OUT[B]=genB∪(IN[B]−killB)如果IN不变,OUT就不变。
IN[B]=⋃P apredecessor of BOUT[P]如果OUT不变,IN就不变。