Чтобы проанализировать возможные источники какого-либо значения, хорошей идеей будет превратить все переменные в неизменяемые, вводя новый символ всякий раз, когда исходный был изменен, и используя новый символ для всех последующих вхождений (исходный символ не будет использоваться после место, где оно было переназначено в исходном коде).
Рассмотрим следующий код:
// 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
.
person
grek40
schedule
18.07.2019
s
указывает наa
для вызова какой-либо функции в вашем коде. Взгляните на stackoverflow.com/a/15755160/5265292. - person grek40   schedule 17.07.2019