Алгоритм интервального анализа для обнаружения дефектов в исходном коде программ

программные и аппаратные средства X

УДК 004.052.42:004.415.5

алгоритм интервального анализа для обнаружения дефектов в исходном коде программ

В. М. Ицыксон,

канд. техн. наук, доцент М. Ю. Моисеев, старший преподаватель В. А. Цесько, аспирант А. В. Захаров, аспирант М. Х. Ахин, магистрант

Санкт-Петербургский государственный политехнический университет

Предлагается алгоритм интервального анализа с интерпретацией условий в операторах ветвления, предназначенный для обнаружения дефектов в исходном коде программ на языке C. Особенностью данного алгоритма является формирование оценки вероятности наличия дефекта. Рассматриваются способы управления точностью и полнотой анализа. Применение предложенного алгоритма иллюстрируется на примере программы.

Ключевые слова — статический анализ, интервальный анализ, обнаружение дефектов, программные дефекты.

Введение

Одной из важнейших проблем при промышленной разработке программных систем является обеспечение требуемого качества. Наличие ошибок в программных системах существенно влияет на такие характеристики качества как функциональность, надежность, эффективность, сопровождаемость и другие [1]. Обнаружение и исправление программных ошибок является трудоемким процессом, который в общем случае не может быть автоматизирован.

Можно выделить особый класс нефункциональных программных ошибок, которые вносятся на этапе разработки. Будем называть такие ошибки программными дефектами. Обнаружение программных дефектов может выполняться автоматически с использованием различных методов и алгоритмов статического анализа.

В данной статье рассматриваются вопросы автоматического обнаружения дефектов на основе статического анализа исходного кода программ на языке ^ Выбор языка C обусловлен тем, что он широко используется при разработке прикладно-

го и системного программного обеспечения (ПО), а также ПО для встраиваемых систем. Кроме того, выбор языка C определяется отсутствием в нем автоматического управления ресурсами и контроля инициализации переменных, а также наличием потенциально опасных конструкций, таких как конструкции адресной арифметики, вызова функций через указатели и возможность неконтролируемого приведения типов.

Существует несколько классов алгоритмов статического анализа, используемых при обнаружении дефектов: анализ указателей [2], интервальный анализ [3], анализ типов [4]. По данным ряда авторитетных организаций [5, 6], одними из наиболее распространенных дефектов в программах на языке C являются переполнение буфера и использование неинициализированных переменных. Для обнаружения перечисленных дефектов целесообразно использовать методы интервального анализа.

Интервальный анализ обеспечивает получение интервалов значений для объектов программы. К объектам программы относятся переменные, элементы массивов и поля структур, а так-

же объекты в памяти, адресуемые через указатели. В данной статье предлагается алгоритм интервального анализа для обнаружения дефектов в программах на языке C.

Для оценки качества алгоритмов анализа введем понятие эффективности обнаружения дефектов. Эффективность обнаружения дефектов — комплексное свойство, отражающее качество получаемого результата, ресурсоемкость, применимость для программ различного размера и класса. Показателями эффективности обнаружения дефектов являются полнота и точность результатов анализа. Полнота результатов анализа — это показатель, характеризующий долю обнаруживаемых дефектов среди всех дефектов в программе. Точность результатов анализа — это показатель, характеризующий долю истинных дефектов среди всех обнаруженных дефектов.

Сформулируем общие требования к алгоритму интервального анализа. Алгоритм должен:

• формировать интервалы значений объектов в отдельных конструкциях программы;

• учитывать неинициализированные объекты;

• анализировать условия в операторах ветвления;

• обеспечивать возможность управления точностью и полнотой анализа.

Интервальный анализ

Существует несколько теорий, на которых базируются методы статического анализа [3]: абстрактная интерпретация, системы типов и эффектов, теория решеток. В качестве математического аппарата для интервального анализа предлагается использовать теорию решеток [7], поскольку известны эффективные алгоритмы поиска решения [3] в рамках этой теории.

Теория решеток

Решеткой называется частично упорядоченное множество L, для каждого подмножества X которого определена единственная точная верхняя грань sup(X) и единственная точная нижняя грань inf(X).

Пусть L — некоторая конечная решетка, а Д(х) — монотонная функция на L:

^ L: С —— У" (л!) С f (*2 )•

Наименьшей неподвижной точкой функции Д(х) называется минимальный элемент хЬ¥р решетки L, для которого справедливо

/ (хЬрр) = хЬрр.

Любая монотонная функция имеет единственную наименьшую неподвижную точку.

Рассмотрим систему уравнений вида xi = fi(xl,..., xN). Пусть xieL и все функции fi: LN^L монотонны, тогда вектор функций F = (fp ..., f^) является монотонной функцией Ln^Ln. Исходная система может быть записана в виде X = F(X), где X — элемент LN. Наименьшая неподвижная точка для функции F(X) является решением данной системы уравнений.

Основные определения

При проведении интервального анализа формируется система уравнений над решеткой LjnT Уравнения строятся для вершин графа потока управления (Control Flow Graph, CFG) программы по определенным правилам. Неизвестными в уравнениях являются состояния программы S1 в соответствующих вершинах CFG. Состояние программы соответствует некоторому элементу решетки LjnT

Элементами LjnT являются подмножества множества всех возможных кортежей {a, i), где a — объект программы, i — интервал принимаемых им значений. Отношением порядка на решетке LmT является отношение включения подмножеств:

A С B о V(a, i} е A ^ (a, i) e B,

(a, i) = (b, j) о (a = b)/(i = j).

Интервал i обладает следующими атрибутами: ilow — нижняя граница интервала; ihigh — верхняя граница интервала.

Кроме обычных интервалов вводится специальный интервал inoninit, соответствующий неинициализированному значению, который в дальнейшем будет использоваться для обнаружения дефектов.

Предлагается расширить кортежи дополнительным атрибутом г. Атрибут r является оценкой вероятности существования кортежа {a, i) в вершине CFG (далее — вероятность кортежа) и используется при обнаружении дефектов. Для этого атрибута выполняется условие нормировки:

Va ^ a, i, j е H, ^ rj 1, i

где H — произвольный горизонтальный срез CFG. Горизонтальный срез представляет собой множество вершин, которые принадлежат альтернативным путям выполнения программы. Для каждого возможного пути срез H содержит ровно одну вершину CFG. Отметим, что атрибут r вычисляется в уравнениях, но не входит в решетку LjnT и, следовательно, не влияет на монотонность функций.

Обеспечение условий нормировки для циклов в CFG является отдельным вопросом. В силу ограниченного объема статьи анализ циклов в CFG остается за рамками рассмотрения.

Обозначим тип переменой а как Т . Для обозначения максимального и минимального значения, которое могут принимать переменные типа Та, будем использовать Ти Т0а°°ш соответственно.

Правила построения уравнений

Рассмотрим правила построения уравнений для вершин CFG, содержащих конструкции программы. Типы конструкций, для которых строятся уравнения, будем называть интерпретируемыми.

Правило построения уравнения для конструкции объявления переменной заключается в добавлении к предыдущему состоянию программы кортежа, состоящего из объявляемой переменной а и интервала in°ninit, при этом вероятность кортежа равна 1:

^ес1аге(Т а)] : Б1 = Б1 и(а, inonin.it, 1,

ОI

где о — множество кортежей всех непосредственно предшествующих состояний для вершины I.

При выходе переменной из области видимости удаляются все кортежи для данной переменной:

\ип4ес1аге(аУ\1 : Б1 =

= Б1 \ У (а, и г).

еБ1:х=а

Обозначим сумму вероятностей кортежей для объекта а в конструкции I как Rla:

= е -

х,1,г 1\х=а

Правило для оператора присваивания, в правой части которого стоит константа, порождает уравнение

[а = С]1 : Б1 = Б1 \ У (а, I, г) и

х,Ь,г) ЕБ 1\х=а

и!уа, [С, С], Rla''}.

В этом уравнении из предыдущего состояния программы удаляются все кортежи, содержащие объект программы а, и добавляется кортеж для объекта а с соответствующим интервалом и вероятностью Rla.

Правило для оператора присваивания а = Ь состоит в удалении всех кортежей для объекта а и добавлении для него всех кортежей, которые имеются для объекта Ь:

[а = Ь] : Sl = S1 \ У (а, и г) и

х^,г} еЯ 1:х=а

и и (аЛ^)-

х^,г) ЕЯ 1:х=Ь\ “Ь I

Правило для оператора присваивания, в правой части которого стоит бинарная арифметическая (сложение, вычитание, умножение) или логическая (дизъюнкция, конъюнкция) операция, имеет вид

[а = Ь  d]l : Sl = Sl \ У (а, и

У^ х^,г} Е&1:х=а

и и.

У( х,1,г),(у,7, р Е&1:

x=b, У=d, 1^1пойти, ^п

ии

У( х,Ь,г),(у,7, р) Е&1:

а, У (а, і, ]), г) и

(а, 1попыи, г'/,

х=Ь, y=d, і=іт

і V]=1п

где г = Г р ; функция у(а, i, ]) возвращает ин-

Е>1 Е>1

Щ'

тервал, получаемый в результате арифметической или логической операции над интервалами, которые соответствуют инициализированному значению объекта:

у (а, I, ]) = (тах (min(G), Т'1аош),

min(max(G), Т^ёк )),

к¿Шк) ( к¿Шк 0 .к¿шк

Правило для операции деления сводится к правилу для умножения на выражение, обратное делителю:

[а = Ь/Л]1 ^[а = Ь-Л-1]1.

Кортежи для d-1 вычисляются по правилу

[а-1]1 =

d , i, г'1

У(х, i,г) е Б1 : х = ^i = Попыш

(а-1,р^-1,1 / ^, 1 / ^ош), ^,

у(х i, г)е Б1 : X = ^i ^ Поппи Л ({°} ^ i); ^-1, p(d-1, Т1™, 1 / ^ош), Г / 2 и d-1, p(d-1, 1 /, Т^ёН), Г / 2^,

У(хi, г)е Б1 :.х = ^i ^ Попыи Л ({0} е ^

где функция Р(а, Ь1, Ь2) приводит значения Ь1, Ь2 к диапазону типа объекта а:

Р(а, 1>1, Ь2) = (тах, та°ш'), т1п(Ь2 > ))•

В стандартной библиотеке языка С существуют функции, которые возвращают неизвестное

значение. Примером такой функции является функция получения символа из стандартного потока ввода getchar(). Правило для этой функции выглядит следующим образом:

[a = getchar()]г : S1 =

= S1 \ У (a, i, r) и

x,i,r) ES1 :x=a

и( a,[Tla0W, T^igh ], Rla).

Для других подобных функций правила составляются аналогично.

Оператор ветвления

Вершины CFG, содержащие операторы ветвления, распределяют входное множество кортежей Sl на два подмножества Slrue и Slalse. Условие в операторе ветвления может быть интерпретируемым или неинтерпретируемым. Для оператора ветвления с интерпретируемым условием начальные состояния программы для каждой из веток определяются следующим образом:

[if(cond) Strue else Sfalse ]l :

Strue = rç(S 1> cond),

Sfalse = ^(^1> cond),

где n(S, cond) — функция, пересекающая кортежи из состояния S с условием cond.

Для интерпретируемого условия функция П определяет множество кортежей из S, которые удовлетворяют условию cond, и вычисляет оценку вероятности перехода для соответствующей

ветки true (RCOnd) или false (R~d)• °ценка

Rcond

рассчитывается как сумма оценок r для всех кортежей, удовлетворяющих условию cond. После этого множество кортежей для S пересекается с условием cond, в результате чего формируется множество кортежей для Strue. Для кортежей, объекты которых не используются в условии cond, выполняется нормировка оценок вероятности:

V(a,i,r) G Strue • r * r ' Rcond,

V( a,Ur) G Sfalse : r ^ rRCn

Для неинтерпретируемых условий выполняется аналогичная процедура без расчета оценок вероятностей переходов: они принимаются равными 0,5.

Рассмотрим несколько примеров функции п для интерпретируемых условий. Для условия типа a < C (переменная меньше константы) функция п имеет вид

п(й, а < С) =

= и (а, I П Т™, С), г) и

У(х,иг)£Sx=a, ШПотпи

и У (и Цюпыа, и и

У( х,1,г) ^^а 1=1Попти

и У ^ ¿, г• ^а<с))

У( х,1,г }eS х^а

Для условия типа а = С (переменная равна константе) можно предложить следующую форму функции п:

Л(5, а = С) =

= У (а, i П [С, С], г^ и

У(х,иг)eS:x=a, ШПатпи и У {a, ¡-поттЬ, г) ^

х,,;'Г)£&-х а, I Поп1пЫ

и У

У( х,1,г :х^а

В вершинах с несколькими входными дугами (ф-функциях) объединяются кортежи, полученные по всем входным дугам, в соответствии со следующим правилом:

[ф (...)]* : = 51.

Кортежи для одного объекта с равными интервалами объединяются; вероятность полученного кортежа равна сумме вероятностей исходных кортежей.

Все правила построения уравнений обеспечивают монотонность функций в правых частях уравнений, что гарантирует сходимость алгоритма интервального анализа.

Обнаружение дефектов

Для обнаружения дефектов в программе необходимо проанализировать решение системы уравнений. Анализ решения системы уравнений заключается в сопоставлении полученных интервалов значений с ограничениями для объектов, выраженными с помощью правил обнаружения дефектов.

Дефекты, выявляемые алгоритмами интервального анализа, можно условно разделить на две группы: ошибки переполнения буфера и ошибки отсутствия инициализации. Ошибки переполнения буфера включают обращение к массиву по неверному индексу, разыменование указателя, выведенного за границу массива, и другие.

Ошибка обращения к массиву по неверному индексу возникает в случае индексации по значению, выходящему за границы массива. Данный дефект обнаруживается следующим правилом:

[p[m]f : 3(p,(a, k))e S1 :

min (k + g) < 0

Vge{nlow,mhigh }

V

V

max (k + g) > a.size

yge{nlow,mhigh }

где кортеж (р,(а,й)) связывает указатель р с й-м элементом объекта а и определяется в результате анализа указателей.

Правило обнаружения ошибок разыменования указателя, выведенного за границу массива, эквивалентно предыдущему случаю при индексе т, равном нулю:

[*pf : 3(p,(a, k))e S1: [(k < 0) V (k > a.size)f

Ошибка использования неинициализированной переменной в арифметических или логических операциях обнаруживается следующим правилом:

[а = ь  с‘] : 3(^Ъ, 1Поп1л,и,г)£

£ ¿3 c, ^попти, г ) £ ^ ,

где  — некоторая арифметическая или логическая операция.

Вероятность наличия дефекта

Приведенные правила последовательно применяются к состояниям программы в соответствующих вершинах CFG. Множество кортежей для некоторого объекта, проверяемого в правиле обнаружения дефекта, можно разделить на два непересекающихся подмножества — DEF и —DEF. В подмножество DEF входят кортежи, приводящие к срабатыванию правила, в подмножество —DEF — все остальные кортежи.

Возможны три ситуации:

1) подмножество DEF пусто — в рассматриваемой конструкции дефект отсутствует;

2) подмножество —DEF пусто — дефект гарантированно присутствует в данной конструкции;

3) оба подмножества DEF и —DEF не пусты — существует неопределенность наличия дефекта.

Первый и второй случаи однозначно свидетельствуют об отсутствии и наличии дефекта соответственно. Для принятия решения в третьем случае будем использовать оценку вероятности наличия дефекта для конструкции I, которая рассчитывается следующим образом:

е -

~1 х,1,г ''¡£DEF:x=a

&а =--------1-------•

К

Полученное значение сравнивается с порогом принятия решения о наличии дефекта Пе[0, 1]. При значении П равном 1 фиксируются только гарантированные дефекты. При нулевом значении порога во всех подозрительных конструкциях программы фиксируются дефекты, что может привести к значительному числу ложных предупреждений. При помощи регулирования порога П можно добиться баланса между точностью и полнотой получаемых результатов для анализируемой программы.

Одним из вариантов использования порога П является его постепенное снижение в процессе анализа программы, что обеспечит обнаружение и исправление дефектов, начиная с наиболее вероятных. Возможность регулировки порога обнаружения дефектов может использоваться при анализе программ различного размера и класса.

Применение предложенного метода

Продемонстрируем применение описанного алгоритма на примере простой программы, содержащей несколько дефектов.

Пример программы

Условия операторов ветвления, обозначенные многоточием, являются неинтерпретируемыми и не приводятся. В операторе ветвления в строке 8 содержится интерпретируемое условие.

1 int main(void) {

2 int a, b, c, i;

3 int d[3];

4

5 if (...) {

6 if (...) {

7 a = 5;

8 if (a < 3) {

9 d[a] = 0;

10 }

11 }

12 b = 1;

13 if (... ) {

14 c = a + b;

15 if (... ) {

16 c = 2;

17 goto LI;

18 } else {

19 b = a + 2;

20 }

21 } else {

22 b = 0; c = 3;

23 }

24 i = b - c;

25 d[i] = 0;

26 } else {

27 return a;

28 L1: b = b + 1; c = 0;

29 }

30 return c;

31 }

Граф потока управления для примера программы

Граф потока управления программы представлен на рисунке. Дуги графа помечены оценками вероятностей. В вершинах графа указаны номера соответствующих строк программы. Конструкции программы, которые потенциально могут содержать дефекты, выделены.

Результаты интервального анализа

В результате выполнения базового интервального анализа было получено следующее решение:

£2 I (а’ ^попіпМ’ !)> (Ь’ І-попіпМ’ !)>

^опіпН ’ 4[)

3 І-попіпМ’ 0)> (с’ І-попіпМ’ 0)> (і’ І-попіпМ’ )}

й10 =

й11 =

(а,

попіпі^>

[С, і

’ 1попіпі^>

1) (а,514) (ь,

1 \(і і . . 1) 2 Р\'> попіпії’ 2 /

попіпі^>

Б2 =

й14 =

й16 =

й19 =

й22 =

Б28 =

• • -1У

попьпь^ 16/’

Б24 =

й27 =

й28 =

S29 =

(а, 1Потпи’ 186)> (а’ 5’ 186)> (^> ^поптЫ ’ 186У (^> 0’ 84)> (^> 7’ 886У (с’ ^поптЫ’ 886У

(а, inonin.it , 186У (а’ 5’ 186У (^’ ^nonin.it , 186У

(ь, ^у (ь, 7,8бу

(ь0,4)(b, 2, '8 У 7,86)’

, 86 У ^ 0, 8У (c, 3, ^У (c, 6, 86 )’ , ШУ ( - ^ '6 У ( - ^ 24 )’

(^^, 4, 24 )’ (^^, 1, 96 У

9 Ыоп1пН9

Применим описанные правила обнаружения дефектов к полученному решению. Оценки веро-

ятностей наличия дефектов в потенциально опасных конструкциях программы таковы:

т>9 _0 Р14 _ 1 Й19 _ 1 &24 _ 11

п = 0, я = Я = 2 Я = 36.

Я25 = 35, Я27 = 1, Я28 = 0, Я30 = -.

36 8

Полученные результаты позволяют сделать вывод о том, что в строках 9 и 28 дефекты отсутствуют, в строке 27 гарантированно присутствует дефект использования неинициализированной переменной. Принятие решения о наличии дефектов в других конструкциях программы зависит от выбранного порога П. Необходимо отметить, что в строке 25 присутствует дефект с оценкой вероятности, близкой к 1, но при обнаружении только гарантированных дефектов этот дефект будет пропущен. Кроме того, интерпретация условий, реализованная в предложенном алгоритме, позволила предотвратить ложное обнаружение дефекта переполнения буфера в строке 9. Полученные результаты являются корректными, в чем можно убедиться, проанализировав программу вручную.

Заключение

В статье предложен алгоритм, позволяющий обнаруживать такие распространенные дефекты в программах на языке С как переполнение буфера и использование неинициализированной переменной.

К достоинствам представленного алгоритма интервального анализа относится интерпретация условий в операторах ветвления. Интерпретация условий в некоторых случаях позволяет существенно повысить точность анализа. Особенностью алгоритма является формирование оценки вероятности проявления дефекта, позволяющей судить о точности полученного решения.

Сравнение оценок вероятностей наличия дефекта с порогом обнаружения позволяет управлять точностью и полнотой обнаружения дефектов. При этом все гарантированно присутствующие в программе дефекты будут обнаружены, однако часть истинных дефектов, для которых имеется неопределенность, может быть пропущена. Применение алгоритма продемонстрировано на примере.

Основным недостатком представленного алгоритма является потеря точности из-за объединения решений, полученных по отдельным путям. Можно предложить несколько способов преодоления указанного недостатка, в том числе извлечение и анализ зависимостей между объектами программы, проведение точного анализа путей для некоторых фрагментов программы.

Исследование выполнено в рамках работ по государственному контракту № 02.514.11.4081 «Исследование и разработка системы автоматического обнаружения дефектов в исходном коде программного обеспечения» Федерального агент-

Литература

1. ГОСТ Р ИСО / МЭК 9126-93. Информационная технология. Оценка программной продукции. Характеристики качества и руководство по их применению. — Введ. 01.07.94. М.: Госстандарт России: Изд-во стандартов, 2004. VI. 12 с.

2. Steensgaard B. Points-to Analysis in Almost Linear Time // Proc. of the 23rd ACM SIGPLAN-SIGACT symposium on Principles of programming languages (POPL’96). ACM, 1996. P. 32-41.

3. Nielson F., Nielson H. R., Hankin C. Principles of Program Analysis. Corr. 2nd printing. Berlin: Springer, 2005. 452 p.

ства по науке и инновациям в рамках Федеральной целевой программы «Исследования и разработки по приоритетным направлениям развития научно-технологического комплекса России на 2007-2012 годы».

4.

5.

6. 7.

Chakraborty S., Kumar R. Precise Static Type Analysis for Object-Oriented Programs // ACM SIGPLAN Notices. N. Y.: ACM, 2007. Vol. 42, Issue 2. P. 17-26. http://www.securecoding.cert.org/confluence/display/ seccode/CERT+C+Secure+Coding+Standard http://www.securityfocus.com

Биркгоф Г. Теория решеток. М.: Наука, 1984. 568 с.

ЙЙ^ 41