为了分析某些值的可能来源,最好将所有变量转换为不可变变量,方法是在更改原始符号时引入一个新符号,并对所有后续出现使用新符号(原始符号不会在原始代码中重新分配的位置之后使用)。
考虑以下代码:
// control flow block 1
int i = 1;
if (some_condition()) {
// control flow block 2
i = 2;
}
// control flow block 3
int j = i;
用控制流图
[1]
| \ <- if (some_condition())
| [2]
| / <- join of control flow after the if block ends
[3]
您可以在控制流图中块的入口和出口点编写所有活动符号的列表(具有稍后在控制流图中任何地方使用的值):
[1] entry: nothing; exit: i
[2] entry: nothing; exit: i
[3] entry: i; exit: i, j (I assume i, j are re-used after the end of this example)
注意[2] entry 是空的,因为i 永远不会被读取并且总是写入块[2]。这种表示的问题是,i 在所有块的退出列表中,但每个块的可能值不同。
那么,让我们在伪代码中引入不可变符号:
// control flow block 1
i = 1;
if (some_condition()) {
// control flow block 2
i_1 = 2;
}
// control flow block 3
// join-logic of predecessor [1] and [2]
i_2 = one_of(i, i_1);
j = i_2;
现在每个变量都与它的第一个(也是唯一一个)赋值完全耦合。意思是,可以通过分析分配中涉及的符号来构建依赖图
i -> i_2
i_1 -> i_2
i_2 -> j
现在,如果j 的允许值存在任何约束,静态检查器可能会要求j 的所有前辈(即i_2 ,依次源自i 和i_1),满足此要求。
在函数调用的情况下,依赖图将包含从每个调用参数到函数定义中相应参数的边。
如果我们只关注数组变量并忽略对数组内容的更改,那么将其应用于您的示例是直截了当的(我不太确定静态检查器会在多大程度上跟踪单个数组项的内容以便发现路上的危险):
示例 1:
void f1(char *s)
{
s[20] = 0;
}
void f2()
{
char a[10];
if (x + y == 2) {
f1(a);
}
}
转换为
f1(s)
{
s[20] = 0;
}
f2()
{
a = char[10];
if (x + y == 2) {
call f1(a);
}
}
依赖图包括通过函数调用传递的参数
a -> s
所以很明显,a 必须被考虑用于对s[20] 安全性的静态分析。
示例 2:
void f1(char *s)
{
char str_arry[30];
s= str_arry;
s[20] = 0;
}
转换为
f1(s)
{
// control flow block 1
str_arry = char[30];
s_1 = str_arry;
s_1[20] = 0;
}
有依赖图
str_arry -> s_1
因此很明显,对s_1[20] 的安全性进行静态分析时要考虑的唯一值是str_arry。