Введение в метод CEGAR уточнение абстракции по контрпримерам

Введение в метод CEGAR — уточнение абстракции по контрпримерам

Мандрыкин М. У., Мутилин В. С., Хорошилов А. В. mandrykin@ispras.ru, mutilin@ispras.ru, khoroshilov@ispras.ru

Аннотация. Точность, полнота и масштабируемость применяемых на практике инструментов статической верификации значительно возросла за последнее десятилетие. В частности, успешный автоматизированный анализ программных систем среднего размера с использованием проверки моделей, получаемых при помощи предикатной абстракции, стал возможен благодаря развитию метода уточнения абстракции по контрпримерам — CEGAR (Counter Example Guided Abstraction Refinement). Этот метод так или иначе используется в таких инструментах, как SLAM, BLAST, SATABS и CPAchecker. В рамках данной статьи мы рассмотрим метод CEGAR в том виде, как он реализован в инструментах статической верификации BLAST и CPAchecker.

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

1 Введение

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

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

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

Статические методы выполняют анализ корректности исходного или бинарного кода программы без его выполнения и потенциально имеют возможность для обнаружения всех ошибок и доказательства корректности программы. Доказанная в 1936 году теорема об алгоритмической неразрешимости проблемы останова [2] показала невозможность существования полного и корректного алгоритма для автоматического доказательства корректности программ в рамках любой достаточно полной модели вычислителя. Поэтому исследования по развитию статических методов велись по двум направлениям: более или менее полное

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

1.1 Методы полуавтоматического анализа программ

Изначально преобладало направление разработки методов ручного доказательства, а также аксиоматической семантики и логических методов для работы с программами как с логическими объектами [3, 4, 5]. По мере роста размера и сложности программных систем ручное доказательство корректности их работы становилось слишком обременительным и порой давало повод усомниться в том, что получаемые в результате большие и сложные формальные доказательства заслуживают доверия [6].

Так появилась тенденция к автоматизации процесса верификации, при которой человек управляет процессом доказательства корректности, предоставляя инструменту необходимые утверждения (например, пред- и постусловия функций и инварианты циклов [3]). Эго позволило расширить область применения методов автоматизации доказательства корректности программ, как в смысле их размеров, так и в смысле разнообразия проверяемых свойств.

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

• Развитие подходов к решению задачи выполнимости, сформулированной в рамках различных логических теорий, и непосредственно связанные с этим разработки решателей [7, 8, 9]. Эти инструменты предоставили собой практически применимое средство автоматического доказательства утверждений в бесконечных пространствах состояний.

• Разработка техник автоматической проверки моделей программ [10,

11, 12] на выполнимость в них свойств, сформулированных на языке темпоральных логик [13, 14], и соответствующих инструментов, основанных на обходе конечного пространства состояний модели.

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

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

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

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

1.2 Методы автоматической верификации

Ключевыми характеристиками методов автоматической проверки программ являются

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

• точность анализа, то есть количество выдаваемых ложных сообщений об ошибках;

• скорость их работы.

На основании этих характеристик существующие подходы можно условно разделить на два больших класса - легковесные и тяжеловесные.

1.2.1 Легковесные подходы

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

221

достаточно быстро обрабатываться большие объёмы исходного кода. Для достижения скорости работы, сравнимой по порядку величины со временем компиляции анализируемого приложения, в данных подходах обычно применяется анализ потока данных [15] в сочетании с множеством различных эвристик, использование которых может привести к пропуску ошибок и снижению точности анализа. На сегодняшний день легковесные подходы развиты достаточно хорошо и существует большое количество реализующих их инструментов, как для индустриального, так и для академического использования [19, 20, 21, 22, 23].

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

1.2.2 Тяжеловесные подходы

Тяжеловесные подходы характеризуются тем, что они предъявляют существенно меньшие требования к скорости анализа. Это позволяет применять более полные и точные методы, которые при выполнении определенных ограничений на код программы позволяют доказать отсутствие в программе ошибок определенных видов. Большинство тяжеловесных подходов предназначено для работы с ошибками, выражаемыми в виде свойства достижимости определенной точки в программе. Примером таких ошибок является нарушение assertion'oB, расставленных в коде программы, или некорректное использование библиотечных функций.

Два наиболее развитых на сегодняшний день тяжеловесных метода анализа программ - это ограничиваемая проверка моделей [24] и уточнение абстракции по контрпримерам [25].

Ограничиваемая проверка моделей

Метод ограничиваемой проверки моделей (от англ. Bounded Model Checking, ВМС) изначально рассматривался как одна из наиболее удачных техник верификации применительно к разработке полупроводниковых устройств. Схемы, используемые в таких устройствах, часто вначале моделируют программой на языке высокого уровня, например, Си, а затем, после тщательного тестирования или верификации, реализуют на языке описания аппаратуры (например, Verilog [26]).

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

Значительные достижения в области производительности современных SAT-и SMT-решателей (SAT от англ. Satisfiability - задача проверки выполнимости для формул пропозициональной логики, SMT от англ. Satisfiability Modulo Theories - задача проверки выполнимости для логических формул с учетом лежащих в их основе теорий, например, линейной арифметики) позволили анализировать поведение программ методом ограничиваемой проверки моделей с очень высокой точностью через побитовое кодирование значений переменных и операций над ними. Метод ограничиваемой проверки моделей позволяет проверять для языка программирования широкий набор конструкций, включая динамически выделяемые структуры в памяти, адресную арифметику, операции с массивами и побитовые операции. На сегодняшний день данный метод является наилучшим для поиска неглубоких

- в смысле длины пути выполнения - ошибок в небольших программных системах.

Инструменты, реализующие метод ограничиваемой проверки моделей, такие как СВМС [27] или F-Soft [28] с успехом используются для поиска ошибок в системном программном обеспечении, например, в автомобильной промышленности [29]. Есть также примеры использования этого метода для доказательства отсутствия состояний гонки в низкоуровневом программном обеспечении [30].

Уточнение абстракции по контрпримерам

Метод уточнения абстракции по контрпримерам (от англ. Counter-Example Guided Abstraction Refinement, CEGAR) применяется для проверки достижимости определенной точки в программе. Основная идея метода заключается в следующем. Детальная модель анализируемой программы (то есть наиболее точная модель, семантически эквивалентная самой программе) слишком большая и сложная, чтобы за разумное время можно было проверить свойства всех возможных путей в ней, поэтому достижимость заданной точки проверяется на упрощенной модели. Но это может привести к некорректному результату: анализ упрощенной модели может показать, что точка

недостижима, хотя в действительности она достижима, или наоборот. Проблемы первого вида метод уточнения абстракции по контрпримерам решает за счет того, что выбирается такой способ построения упрощённой модели, при котором такой ситуации не может произойти «по построению». То есть всегда, когда анализ упрощённой модели говорит, что точка

недостижима, то она является недостижимой и в детальной модели программы.

Для борьбы с проблемами второго вида применяется «уточнение абстракции по контрпримерам». Если анализ упрощённой модели показывает, что заданная точка является достижимой, то это сопровождается предъявлением пути в исходной программе, по которому можно достичь целевую точку. Этот путь называется контрпримером. Поскольку задача проверки реализуемости одного конкретного пути в детальной модели программы уже не столь сложна, как анализ всех путей и, более того, текущий уровень развития инструментов позволяет её решать достаточно эффективно, то именно это и делается для всех контрпримеров, обнаруживаемых при помощи анализа упрощённой модели. Если проверка подтверждает реализуемость найденного пути, то на вопрос о достижимости заданной точки получен корректный ответ. В противном случае, контрпример является следствием абстрагирования от существенных деталей при построении упрощённой модели. Тогда необходимо провести анализ контрпримера, чтобы выявить те детали, из-за игнорирования которых появился некорректный контрпример, и уточнить упрощённую модель, чтобы учесть в ней эти детали. Работа с новой упрощённой моделью повторяется по тому же самому алгоритму.

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

Метод уточнения абстракции по контрпримерам лежит в основе инструмента статической верификации SLAM [58], который входит в состав системы Static Driver Verifier (SDV) [31] и в настоящее время активно используется разработчиками драйверов устройств для операционной системы Microsoft Windows. Другие инструменты тяжеловесного статического анализа, такие как BLAST [32], SATabs [33] и CPAchecker [34], также работающие по методу уточнения абстракции, имеют широкую известность среди исследователей в области автоматической верификации. В рамках проекта Linux Driver Verification (LDV) [35, 36] разрабатывается система статической верификации драйверов операционной системы Linux, в которой на практике активно используются инструменты BLAST и CPAchecker. Результаты использования этих инструментов говорят о возможности их успешного применения для покомпонентного анализа больших объёмов промышленного кода.

2 План статьи

Данная статья описывает основные принципы метода уточнения абстракции по контрпримерам (CEGAR) и затрагивает основные моменты реализации CEGAR в инструментах статической верификации BLAST и CPAchecker.

В следующем (третьем) разделе статьи вводятся основные понятия и определения, необходимые для изложения сущности рассматриваемого метода. Затем рассматривается сам метод в том его варианте, в котором он реализован в инструментах BLAST и CPAchecker. Поскольку инструменты, работающие на основе CEGAR, устроены достаточно сложно, рассмотрение метода проводится в несколько этапов. Сначала в разделах 4, 5, 6 и 7 рассматривается наиболее простой вариант предикатной абстракции и её уточнения по методу CEGAR. Рассмотрение проводится на примере верификации очень простой программы на языке С с указанным для неё свойством недостижимости. Затем в разделе 8 рассмотренный вариант CEGAR с предикатной абстракцией расширяется для применения его к более сложным программам, в этом же разделе рассказывается об используемых модификациях и оптимизациях метода, направленных на увеличение его точности, масштабируемости и производительности. В конце (в разделе 9) описываются внешние инструменты, которые используются при реализации метода CEGAR в BLAST и CPAchecker (такие как SAT-, SMT- и интерполирующие решатели).

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

3.1 Простейшие программы

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

• Простейшая программа состоит из одной главной функции main,

первый оператор которой считается единственной точкой входа в программу. Функция main имеет объявление void main(void);

Таким образом, эта функция не принимает никаких параметров и не возвращает никакого значения.

• В простейшей программе могут быть объявлены только локальные переменные функции main целочисленного типа int. Объявления переменных допускаются только до первого оператора функции main. Все переменные должны быть явно инициализированы при объявлении недетерминированными значениями, то есть произвольными, не заданными наперёд значениями типа int. Для этого используется специальный инициализатор nondet.

• В качестве операторов допускаются только следующие:

о присваивание переменной выражения без побочных эффектов;

о ветвление с использованием оператора if (if-else) по условию, заданному выражением без побочных эффектов;

о безусловный переход на метку (оператор goto).

Данное ограничение, в частности, означает, что пустые операторы в простейших программах недопустимы.

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

• В качестве операций в выражениях без побочных эффектов

допускаются только линейные арифметические операции +, -,

* (умножение на константу), операции сравнения >, с, ==, !=, >=,

<= и логическая операция !.

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

Пример простейшей программы с ошибочной меткой ERR представлен на рис. 1а. Приведенная программа вычисляет модуль (абсолютное значение) разности двух произвольно заданных чисел - х и у и сохраняет его в переменной z. Меткой ERR помечен ошибочный оператор, который не должен достигаться в ходе корректного выполнения данной программы.

void main

{

int x = nondet; int у = nondet; int z = nondet;

LI: if (x > y) {

L2: z = x - у;

} else {

L3 : z = у - x;

}

L4: if

ERR: goto ERR;

L5:1

Puc. la. Пример простейшей программы.

Рис. 16. Граф потока управления для данной программы

3.2 Граф потока управления

Введем для простейшей программы понятие графа потока управления (ГПУ, от англ. Control flow graph, CFG). Граф потока управления -ориентированный граф, вершинам которого соответствуют уникальные метки, которыми помечен каждый оператор программы, а также точка выхода из неё, а направленным помеченным дугам соответствуют операторы программы. При этом каждому оператору присваивания или безусловного перехода однозначно соответствует единственная дуга в ГПУ, а оператору ветвления -две дуги: одна для указанного в операторе условия перехода и одна для отрицания этого условия. Вершина ГПУ, соответствующая метке первого оператора программы, называется его начальной вершиной. Для программы из

примера 1 на рис. 1а соответствующий граф потока управления представлен на рисунке 16.

3.3 Состояния программы

Назовём состоянием простейшей программы пару (Ъ, V), где Ь - уникальная метка следующего за ней (то есть помеченного этой меткой) выполняемого оператора программы или точки выхода, V - означивание всех переменных (то есть отображение имен всех переменных программы в их конкретные значения) непосредственно перед выполнением оператора, помеченного меткой Ь. Отметим, что состояние программы может как быть достижимо в ходе какого-либо её реального выполнения, так и не быть таковым.

Для программы в примере 1 состояниями считаются, например, такие пары:

(II, {х н> 0, у н> 0, г н> 0}),

(12, {х и1,уи0,ги 1}),

(15, {х и1,уи0,ги 1}),

(15, {х и1,уи0,ги -1}).

Состояния программы (Ъ, V), в которых Ь — имя метки первого оператора программы, назовём начальными состояниями этой программы. Примеры начальных состояний простейшей программы из примера 1:

(II, {х нО.уиО^н 0}),

(II, {х н1,уи0,2н 1}),

(II, {х н> 1,у н> 2,г н> -1}).

Если ГПУ простейшей программы не содержит кратных дуг (формальное описание процесса построения ГПУ без кратных дуг приведено в секции 4.1), то любой упорядоченной паре состояний программы можно поставить в соответствие не более одной дуги ГПУ.

Например, в нашей программе упорядоченным парам состояний

((Ы, {х и1,уи0,ги 0}), (12, {х н1,уи0,хи 0})) и

((II, {х иО.уиО.хи 0}), (12, {х и1,уи0,2и 1})) в ГПУ соответствует

одна и та же дуга И -> 12. А паре состояний ((И,{х >-> 0,у ^ 0,г ^

0,17, Х1-^1, у 1-^0, г 1-^1) не соответствует никакой дуги ГПУ.

Для упорядоченной пары состояний, которой соответствует некоторая дуга ГПУ, может быть выполнено отношение непосредственной достижимости. Смысл этого отношения в том, что второе состояние получается из первого в результате выполнения оператора, которым помечена соответствующая дуга ГПУ. В случае оператора ветвления по некоторому условию с договоримся считать, что одна из соответствующих ему дуг ГПУ помечена оператором перехода по условию с, а другая — оператором перехода по условию -\С. 228

Будем обозначать непосредственную достижимость состояния из

СОСТОЯНИЯ (11,171) по дуге ГПУ, помеченной оператором ор, с помощью

ор

символа —(X,, и,) —> (Ь2, и2). Формально отношение непосредственной достижимости для произвольной упорядоченной пары состояний и произвольного оператора ор можно определить следующим образом:

ор ор

(11( г^) -> (Ь2, г?2) о в ГПУ есть дуга Ь1^Ь2 и , у2 = уг, если ор — оператор доЬо

У2 = Уг{хк н* е^^), ...,1?!(%„))},

< если ор — оператор присваивания хк = е

У2 = ^ИС^СхО, ....^(Хп)),

.если ор — оператор перехода по условию с

Здесь х1( ...,хп — переменные простейшей программы;

--г71(хп))} — означивание, которое получается из V, заменой значения переменной хк на значение выражения е(х,,, хп) (из правой части оператора присваивания), вычисленное при значениях переменных, заданных означиванием х>ъ с(х,,, хп) — условие, которым помечена одна из двух дуг ГПУ, соответствующих оператору ветвления.

Для нашей программы (на рис. 1) примеры состояний, связанных отношением непосредственной достижимости:

(II, {х н> 1, у н> 0, г н> 0})-----> (12, {х и1,уи0,ги 0}),

(12, {х и 1,у и -1,г н> -1})----^ (14, {х и 1,у и -1,г н> 2}),

, , доьоЕян

(ЯййДх н> 1, у н> 0, г н> -1})-----> (ЯййДх и1,у и0,ги -1}).

3.4 Граф достижимости

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

Если в графе достижимости есть путь Р из вершины (1Л,и,) в вершину ([Ьп, Vп).

fi \°Pl f! л°Р2 л п \

достижимо из состояния (L,, и,) по пути Р. Будем также говорить о достижимости состояния (Ln,vn), подразумевая его достижимость из какого-либо начального состояния.

В примере 1 достижимо, например, состояние (L5, {х н> 1,у н> о, z н> 1}). из начального состояния (L1, (х и 1,у н 0,г и 0}) по пути

("Х^ V") Z = X V

(LI, {х ^IjhO.zh 0})-------> (L2,{x ^IjhO.zh 0})---------->

!

3.5 Задача инструмента верификации

Недостижимость, то есть отсутствие достижимости, всех состояний с ошибочной меткой (состояний (ERR,v), где ERR — ошибочная метка) соответствует для простейшей программы недостижимости самой ошибочной метки (в ходе любого выполнения программы). Состояния с ошибочной меткой будем также называть ошибочными состояниями. Формально можно сказать, что задача инструмента верификации - проверить для заданной простейшей программы достижимость какого-либо ошибочного состояния, то есть существование в графе достижимости пути из какого-либо начального состояния в какое-либо состояние с ошибочной меткой. В случае достижимости ошибочного состояния найденный инструментом путь из начального состояния в ошибочное будем называть примером ошибочного пути. Итак, задача инструмента верификации - либо доказать для данной на вход простейшей программы недостижимость ошибочных состояний, либо привести соответствующий пример ошибочного пути. В программе из примера 1 ошибочные состояния с меткой ERR (и соответственно сама ошибочная метка ERR) являются недостижимыми.

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

(232)3 = 23'32 = 296 > 7,9 • 1028 > (2,5 • 1012) • 365 • 24 • 602 • 109

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

Проблема резкого («взрывного») увеличения числа состояний программы с ростом числа её переменных и операторов получила название проблемы комбинаторного взрыва. Для её решения применяются различные подходы. Рассмотрим один из способов борьбы с проблемой комбинаторного взрыва, который называется предикатной абстракцией.

3.6 Предикатная абстракция и метод СЕ6А11

При использовании абстракции вместо состояний исходной программы рассматриваются абстрактные состояния, которые представляют собой множества состояний программы. Эти множества могут пересекаться или быть вложены одно в другое. На основе некоторых заданных вначале абстрактных состояний строится абстрактное дерево достижимости (АДД, определение АДД вводится в разделе 3.9), состоящее из абстрактных состояний и переходов между ними по операторам программы. Строится оно таким образом, что если существует переход между какими-либо двумя состояниями программы, то существует и переход между соответствующими им абстрактными состояниями, которые включают эти состояния. По построению получается, что АДД обязательно включает все достижимые в исходной программе состояния (как элементы соответствующих абстрактных состояний), а также, возможно, и некоторые другие. Поэтому пути, выполнение по которым возможно в исходной программе, обязательно оказываются представленными в АДД в виде последовательности переходов между абстрактными состояниями. Обратное, вообще говоря, неверно. Из-за использования абстрактных состояний вместо состояний исходной программы в АДД могут быть представлены и фиктивные пути, выполнение по которым на самом деле невозможно.

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

Для описания абстрактных состояний будем использовать частный случай абстракций — предикатные абстракции — то есть абстракции, в которых абстрактные состояния представляются логическими выражениями (предикатами) над переменными программы. В таких абстракциях состояния программы объединяются во множества (абстрактные состояния) по признаку истинности в них некоторых логических выражений (например, состояния, где х > 0, состояния, где X = у или состояния, где X > О Л у = 0).

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

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

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

Для установления истинности ошибки по контрпримеру составляется формула сильнейшего постусловия пути (определяется в разделе 6.3), описывающая существование аналогичного пути в графе достижимости по состояниям программы. Если формула выполнима, то программа содержит ошибку. Иначе данная формула используется для уточнения предикатной абстракции, то есть для пополнения набора выбранных предикатов. После этого АДД перестраивается заново. АДД, которое строится после добавления в набор новых предикатов, не должно более включать тот же самый ложный ошибочный путь.

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

Это общая схема работы метода CEGAR - уточнения абстракции (в данном случае - абстрактного дерева достижимости) по контрпримерам. Начнем рассмотрение его реализации в инструментах BLAST и CPAchecker с более подробного разбора понятия абстрактного состояния и предикатной абстракции (точнее, ее частного случая - декартовой предикатной абстракции) на примере.

3.7 Абстрактные состояния и декартова предикатная абстракция

3.7.1 Предикатная абстракция на примере

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

void main() {

int x = nondet;

LI: if

L2: if (x <= 10) {

L3 : x = x + 1;

L4: if

ERROR: goto ERROR;

}

L5 : }

Puc. 2. Простейшая программа с одной переменной х.

На рис. 3 схематически изображено множество всех возможных значений (фактически, означиваний) единственной переменной х этой программы.

Рис. 3. Множество всех возможных значений переменной х.

Будем считать далее, что переменная х может принимать любые целочисленные значения, в том числе вне диапазона типа int. Таким образом, схематически изображенные на рисунке значения -2147483648 и 2147483647 являются просто одними из возможных значений этой переменной, а не наименьшим и наибольшим возможным её значением соответственно. В тексте программы встречаются условия х > 5 и х < 10, поэтому выберем их для примера. Рассмотрим систему подмножеств.

представляющую свойства х > 5 и х < 10 для входящих в эти подмножества означиваний.

На рис. 4 схематически изображена такая система подмножеств.

Рис. 4. Система подмножеств множества значений переменной х.

На рисунке выделены два подмножества таких означиваний переменной х, в которых выполнены условия х > 5 и х < 10 соответственно. Эти условия -предикаты над переменными (в данном случае - единственной переменной) простейшей программы. Показано также пересечение этих подмножеств, которому соответствует предикат х>5Лх<10, и множество всех означиваний Т («топ»), которому соответствует тождественная истина. Такие

системы подмножеств называют предикатными абстракциями над

множеством означиваний переменных программы.

В данной статье будем рассматривать только предикатные абстракции, в которые входят:

• подмножества означиваний, элементы которых удовлетворяют некоторому заранее заданному предикату (например, х > 5),

• пересечения этих подмножеств (соответствующие конъюнкциям предикатов, например, х > 5 Лх < 10),

• подмножество всех означиваний (соответствующее тождественной истине) и

• пустое подмножество означиваний А. («ботом», «дно», соответствующее тождественной лжи).

В эти абстракции не входят объединения, разности, дополнения и результаты каких-либо других теоретико-множественных операций над подмножествами означиваний и соответственно не рассматриваются дизъюнкции и отрицания заданных предикатов. Такие предикатные абстракции называют декартовыми. Подмножества означиваний всех переменных программы, пересечения этих подмножеств, а также специальные подмножества Т и _1_ называют

абстрактными состояниями данных, а соответствующие им предикаты и их конъюнкции (в том числе тождественную истину и особый случай -тождественную ложь) - регионами абстрактных состояний. В абстрактное состояние программы также входит состояние её потока управления (имя уникальной метки следующего выполняемого оператора), поэтому абстрактным состоянием программы (или просто абстрактным состоянием) будем называть пару (I, г), где Ь - имя уникальной метки, задающее состояние потока управления программы, г - регион абстрактного состояния (конъюнкция предикатов или тождественная ложь, соответствующая абстрактному состоянию данных).

Рассмотрим теперь один из операторов нашей простейшей программы с одной переменной. Возьмём оператор х = х + 1, помеченный меткой 13. В данном разделе будем изображать на одном рисунке означивания переменной х в двух последовательных состояниях программы, например, на метке 13 (непосредственно перед выполнением оператора х = х+1) и на метке Ь4 (перед проверкой условия ! (х > 5)). Переходы между значениями переменной х при выполнении перехода из одного состояния в другое (например, из состояния с меткой 13 в состояние с меткой 14) будем изображать стрелками. На рис. 5 показаны переходы между означиваниями переменной х при выполнении выбранного нами оператора х = х + 1.

х > 5 Ах < 10

Рис. 5. Переходы между означиваниями переменной х при выполнении оператора

х = х + 1.

На рисунке показано, что множество значений переменной рассматривается как неограниченное. Предположим, что при некотором состоянии потока управления (в нашем случае - перед выполнением оператора х = х + 1, помеченного меткой 13) все возможные в этом месте программы означивания переменной х входят в абстрактное состояние с регионом х>5Лх<10. Рассмотрим теперь переходы по оператору х = х + 1 только из означиваний, входящих в это абстрактное состояние. Эти переходы показаны на рис. 6.

Рис. 6. Переходы по оператору х = х + 1 из абстрактного состояния с регионом

х > 5 Л х < 10.

На рисунке выделены возможные значения переменной х после выполнения оператора х = х + 1 из абстрактного состояния с регионом х>5Лх<10. Это подмножество выделенных возможных значений переменной X можно покрыть подмножеством х > 5, так как все выделенные значения принадлежат этому подмножеству. Таким образом, можно сказать, что после выполнения оператора х = х + 1 из абстрактного состояния (1.3, х > 5 Л х < 10), все возможные означивания переменной х можно ограничить (покрыть) абстрактным состоянием с регионом х > 5. В этом случае говорят, что переход из абстрактного состояния (13, х>5Лх<10) по оператору х = х + 1 ведет в абстрактное состояние (14, х > 5). Вместо абстрактного состояния х > 5, вообще говоря, можно рассматривать абстрактное состояние Т, которое включает его. Но при использовании описанного далее (в

следующем подразделе) метода построения переходов между абстрактными состояниями в данном случае будет получаться именно х > 5. Построенный переход изображен на рис. 7.

Рис. 7. Переход из абстрактного состояния с регионом х > 5 Л х < 10 по оператору х = х + 1 в абстрактное состояние с регионом х > 5.

3.7.2 Построение переходов между абстрактными

состояниями с помощью решателей на примере

Для построения переходов между абстрактными состояниями на практике заметим, что отношение вложенности подмножеств означиваний соответствует отношению логического следования (импликации) между соответствующими логическими предикатами (в широком смысле). Множество означиваний А вложено в подмножество означиваний В тогда и

только тогда, когда для соответствующих предикатов а и Ь выполнено отношение а -» Ь (рис. 8).

Рис. 8. Иллюстрация к соответствию отношений А £ В и а -> Ь.

Это означает, что абстрактное состояние данных А вложено в абстрактное состояние данных В тогда и только тогда, когда для соответствующих им регионов а и Ь выполнено логическое отношение а -» Ь.

Воспользуемся этим свойством для проверки вложенности подмножества всевозможных означиваний переменной х из предыдущего примера (после выполнения оператора х = х + 1 из абстрактного состояния (13, х > 5 Л х < 10)) в различные абстрактные состояния данных (х > 5 и х < 10). В предположении неограниченности множества значений переменной х подмножеству всевозможных её означиваний можно поставить в соответствие предикат (в широком смысле) х, > 5 Л х, < 10 Л х2 = х, + 1. Это предикат от переменных х1 и х2, соответствующих означиваниям переменной х до и после выполнения оператора х = х + 1 соответственно. Вложенность подмножества означиваний х после выполнения оператора х = х + 1 в подмножество х > 5

можно проверить, проверив импликацию (х, > 5 Л хг < 10 Л х2 = хг + 1) -»

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

—I ((хх > 5 Л хх < 10 Л х2 = хх + 1) -» х2 > 5) =

-пС-^Х! > 5 Ахг < 10 Лх2 = хг + 1) V х2 > 5) =

Аналогично можно проверить вложенность в абстрактное состояние данных х < 10. Для проверки выполнимости таких логических формул - нулевого порядка, то есть без кванторов, с равенством и линейной целочисленной арифметикой - можно использовать специальные инструменты, называемые 8МТ -решателями.

Когда подмножество одновременно вложено в два или более абстрактных состояния данных, оно является вложенным в пересечение этих абстрактных состояний данных. Регион, соответствующий пересечению абстрактных состояний данных, является конъюнкцией регионов соответствующих абстрактных состояний.

При этом следует учитывать, что абстрактное состояние данных _1_ является вложенным в любое абстрактное состояние данных. В зависимости от заданных предикатов, абстрактное состояние данных 1. может совпадать с

пересечением двух или более (в том числе, всех) абстрактных состояний данных (например, х>5Лх<5=1), а может быть и строго вложено в пересечение всех абстрактных состояний данных (как в данном примере). В то же время абстрактное состояние данных _1_ имеет большое значение,

поскольку соответствует пустому множеству означиваний и, как следствие, пустому множеству состояний программы и неосуществимости перехода по некоторому оператору из какого-либо абстрактного состояния. В дальнейшем неосуществимость такого перехода будет использована для доказательства недостижимости ошибочной метки. Поэтому, чтобы отличать состояние 1. от

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

В нашем примере предикат х1 > 5 Л х1 < 10 Л х2 = х1 + 1 как логическая формула является выполнимым. Новое абстрактное состояние данных после перехода не равно _1_. В качестве нового абстрактного состояния данных берем

конъюнкцию всех абстрактных состояний данных, в которые вложено подмножество всевозможных означиваний переменной х после перехода. Формула (х, > 5 Л х, < 10 Л х2 = х, + 1) Л -|(х2 > 5) невыполнима, а формула (х, > 5 Л х, < 10 Л х2 = х, + 1) Л -|(х2 < 10) выполнима. В качестве результата берем конъюнкцию из одного абстрактного состояния данных х > 5.

Так как в примере оператор х = х + 1 помечен меткой Ь 3, а следующий за ним оператор - меткой 14, то графически построенный переход можно изобразить так, как показано на рис. 9а.

Рис. 9а. Переход из абстрактного состояния (13, х < 5 Л х > 10) по оператору х = х + 1 в абстрактное состояние (14, х > 5).

Рис. 96. Переход из абстрактного состояния (L4, х > у A z > 0) в абстрактное

состояние (ERR.1).

Приведем еще один пример. Для программы из примера 1 построим переход из абстрактного состояния (L4, х > у A z > 0) по оператору ветвления с условием (z < 0). Предикат на возможные означивания переменных после перехода: x>yAz>0Az<0 - не является выполнимым как логическая формула. Следовательно, новое абстрактное состояние равно (ERR, 1). Построенный переход показан на рисунке 96.

3.7.3 Общий случай декартовой предикатной абстракции

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

в себя очень большое число состояний, например, 296 или даже намного больше. В подмножество объединяют некоторые состояния, имеющие одинаковые метки. Мы рассматриваем случай, когда в подмножество объединяются состояния, в которых переменные программы удовлетворяют некоторому набору логических предикатов нулевого порядка (без кванторов). Пусть задан некоторый предикат р(х1, ...,хп) над переменными программы х1,...,хп (предикат не обязательно существенно зависит от всех переменных программы). В каждом состоянии программы (I, г?) означивание V задаёт значения всех переменных х1,...,хп. Подставим эти значения вместо соответствующих переменных в предикат р(хх, ...,хп). Получим р(у(х1),, г?(хп)) —значение предиката р в состоянии (Ь,у). Это значение всегда можно вычислить, оно будет являться логической (булевой) константой и будет равно либо истине, которую мы обозначим символом Т, либо лжи, которую мы обозначим символом _1_. В случае р(и(х1),..., и(хп)) = Т будем писать V Ь р (х,,... ,хп) или просто VI-р.

Пусть теперь дана некоторая фиксированная уникальная метка Ь какого-либо оператора исходной простейшей программы. Для данного предиката р можно рассмотреть подмножество всех возможных состояний программы с заданной меткой I, в которых значение предиката р равно тождественной истине. Обозначим это подмножество через 1р]/,:

[р]^ = {(/,, г?): V I- р},Ь фиксировано.

Одним из возможных способов задания и представления набора множеств состояний исходной программы (для последующей работы с полученным набором представлений этих множеств) является декартова предикатная абстракция. Пусть задан некоторый конечный набор предикатов {р,,..., рп}. Рассмотрим некоторое подмножество предикатов из этого набора: {р11, ...,р1т] £ [р11 ...,рп}. В декартовой предикатной абстракции подмножеству предикатов {рг , ...,рг } £ [р11 ...,рп} ставят в соответствие конъюнкцию этих предикатов

которую, в свою очередь, рассматривают как новый предикат, и ставят в соответствие этому предикату подмножество состояний исходной программы с фиксированной меткой Ь:

т

к=1

Др«к = Ъч А-ЛР4я

/с=1 11/,

= {(1, у): г I- р£ Л ...Лр;т},1 фиксировано.

Формально в декартовой предикатной абстракции абстрактным состоянием называют пару (Ь, г), где I — уникальная метка, а г — либо конъюнкция подмножества предикатов из заранее заданного набора, г = р1± Л ... Лр1т,{р11,... ,р1т} £ {р1:... ,рп}, 0< т<п, либо тождественная ложь, г =1. В частности, если гп = 0, то г = Т и и 1-г для любого V; это означает что абстрактное состояние (I, Т) задаёт все возможные состояния программы с заданной меткой Ь. Если же г = 1, то VI-г не верно ни для какого означивания V, то есть абстрактное состояние (Ъ, 1) задаёт пустое множество состояний программы, которому формально приписана некоторая уникальная метка Ь. Обозначим множество состояний программы, заданных абстрактным состоянием (Ъ,г) через [(!,г)]. Тогда формально можно записать:

1(1, г)] = Мь = Ь г},Ь фиксировано.

В абстрактном состоянии (Ъ, г) г является предикатом, который называют регионом абстрактного состояния. Для состояния программы (X, г?) такого, ЧТО V I- гили, что то же самое, (Ъ, V) 6 [[(1, г)] будем говорить, что состояние программы (Ъ, V) входит в абстрактное состояние (£, г).

В качестве пояснения приведём примеры возможных регионов для заданного набора из двух предикатов:

= х > О р2=х = О

Возможные регионы:

гг = Т Г2=Р! = (х> 0) г3 = р2 = (х = 0) г4 = рг Л р2 = (х > 0) Л (х = 0) =1 г5 = 1= г4

Здесь, к примеру, абстрактное состояние (1,г2) = (1,рл) = (1,х> 0) (х — целое) задаёт множество конкретных состояний с меткой Ъ, в которых

переменная х принимает строго положительные значения (х = 1,2,...), а остальные переменные принимают произвольные значения.

3.8 Сильнейшие постусловия и вычисление регионов

В инструментах статической верификации на основе предикатной абстракции, в том числе в ВЬА8Т и СРАсИссксг. логические формулы являются основным средством представления абстрактных состояний и работы с ними. Теперь для дальнейшего изложения нам требуется определить некоторую аналогию непосредственной достижимости для абстрактных состояний.

Пусть дано некоторое абстрактное состояние (Ъ, г) и дуга в ГПУ с меткой ор. Будем формально говорить для заданной метки Ь и предиката от переменных программы р(х1,... , хп), что все состояния программы из множества 'tp}L = {(/,, р): V I- р} заданы меткой I и предикатом р. Сильнейшим постусловием оператора ор при данном предусловии р называется предикат от переменных программы, задающий вместе с некоторой меткой V те и только те состояния программы, которые непосредственно достижимы из состояний, заданных предусловием (предикатом) р и некоторой меткой Ъ, по дуге, помеченной

ор

оператором ор. Если задать дугу Ь —> I! в ГПУ, и некоторое предусловие р(хг,... , хп), то можно сказать, что сильнейшее постусловие р' оператора ор при заданном предусловии р задаёт вместе с меткой V множество [р']^ =

ор

{(//, г/): 3(1, у), такое, что V Ь р и (I, г?) -> (V, V1)}. Предикат сильнейшего постусловия можно задавать в виде, зависящем только от предусловия р и оператора ор. Поэтому сильнейшее постусловие оператора ор при данном предусловии р мы будем обозначать через 5Р(р, ор) (то есть р' = 5Р(р, ор)). Регион является предикатом от переменных программы и может быть использован в качестве предусловия. Рассмотрим сильнейшее постусловие 5Р(г, ор) для региона г абстрактного состояния (I, г) и оператора ор на дуге

ор

Ь —> V ГПУ. Использование декартовой предикатной абстракции даёт возможность достаточно просто подобрать регион, являющийся следствием сильнейшего постусловия 5Р(г, ор) и таким образом задать новое абстрактное состояние (//, г'), в которое входят все конкретные состояния, достижимые из данного абстрактного состояния (Ъ, г) по дуге с пометкой ор. В это абстрактное состояние могут также войти и другие конкретные состояния, в которые не входит дуга с пометкой ор, выходящая из (£, г). Поэтому (£', г') называют верхним приближением (от англ. оуег-арргохтайоп) множества конкретных состояний, достижимых из (£, г) по дуге с меткой ор. По сути, верхнее приближение для абстрактных состояний означает то же, что и надмножество.

Введём обозначение выполнимости логической формулы: будем писать ср — Б АТ. если формула ср выполнима1 и ср — иЫБАТ. если ср — невыполнима. Для того чтобы подобрать регион, являющийся следствием сильнейшего постусловия 5Р(г, ор), воспользуемся свойством для произвольных предикатов Р,р±,... ,рт:

Р -> р1 А ... Л рт ФФ -,Р V (рх Л ... Л рт) ФФ (-,Р V рх) Л ... Л (-,Р V рт). Последняя конъюнкция выполнена тогда и только тогда, когда Р Л -Iр± — им Б АТ,... ,Р А —фт — иМБАТ. Поэтому если выбрать из заданного набора предикатов {р,, ...,рп} те и только те предикаты р1/;, 1 < к < гп, для которых Р Л -1 р^ - иN5АТ. то для региона Л}'1=1 р1/£б> дет выполнено Р -> Л}'1=1 р1/;. Исходя из этих соображений будем пользоваться для вычисления региона г' абстрактного состояния (Ь'.г') следующим правилом:

• В декартовой предикатной абстракции для множества предикатов

{р,, ...,рп} регион г' абстрактного состояния которое является

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

сильнейшим постусловием 5Р(г, ор) оператора ор для предусловия г,

равен:

• _1_, если формула 5-Р(г, ор) невыполнима, т.е. 5Р(г, ор) — иМБАТ:

• конъюнкции предикатов рг ,...,р1т. для каждого из которых формула 5Р(г, ор) А -|р1/;невыполнима, т.е.

Л р'*'

Р1йе{Р1.-.Рп}:

5Р(г,ор)Л-1Р;й-У№5ЛГ

если 5Р(г, ор) — Б АТ.

Пустая дизъюнкция считается соответствующей тождественной истине (Т). Формальное доказательство того, что использование этого правила всегда даёт верхнее приближение множества конкретных состояний, достижимых из (Ъ, г) по оператору ор, можно найти в статье [37], в которой оно и было впервые

1 Логическая формула (в логике первого порядка) на основе теорий (ЭМТ-формула) называется выполнимой, если можно назначить всем входящим в неё неинтерпретируемым символам, то есть символам (константам, функциям, предикатам), не являющимся препозиционными связками (такими как Т,Л, -1) или символами теорий (такими как 2, +, <), значения из соответствующих доменов (областей определения) так, чтобы формула стала истинной.

предложено. Регион, соответствующий абстрактному состоянию (L',r'), вычисленный по приведённому правилу для множества предикатов п = {Pi, и предусловия г, будем в дальнейшем обозначать через

poster, op).

3.9 Абстрактное дерево достижимости

Дадим теперь, наконец, определение основной структуры данных, используемой инструментами BLAST и CPAchecker для представления верхнего приближения множества всех достижимых состояний исходной программы, то есть для приближенного решения поставленной перед ними задачи проверки свойства недостижимости. Абстрактное дерево достижимости или АДД (от англ. Abstract Reachability Tree, ART) - это ориентированное дерево с помеченными дугами и вершинами, представляющее верхнее приближение некоторой части состояний исходной программы, достижимых из её начального состояния. Каждая вершина АДД помечена абстрактным состоянием (L,r), представленным в виде пары (метка, регион). Будем обозначать помеченные вершины АДД так: N\(L,r) или просто N. где N — уникальный идентификатор вершины. Договоримся использовать в качестве уникальных идентификаторов вершин АДД имена меток соответствующих абстрактных состояний, приписывая к ним разделитель # и дополнительный индекс для обеспечения уникальности.

Например, для вершины с меткой (L6,x>yAz>0) будем использовать идентификатор L6#l. а для вершины с меткой (L6, Т) — идентификатор L6#2. Дуги АДД помечаются теми же операторами, что и соответствующие дуги ГПУ. Соответствие это устанавливается во время построения АДД уже рассмотренным ранее способом по именам меток в вершинах АДД. Путь от корня до произвольной вершины в АДД может соответствовать одному из представленных в графе достижимости путей Р возможного выполнения программы до достижения соответствующей метки. Регион же в вершине АДД представляет (с точностью до метки) верхнее приближение множества состояний программы, достижимых при условии её выполнения по этому пути р.

Будем называть АДД полным, если:

1. Его корень помечен (L,, Т). где L, — метка первого оператора

программы, или, иначе, метка начального состояния программы.

2. АДД замкнуто относительно постусловий, то есть для любой его внутренней вершины N\(L,r), такой, что г — SAT (г S1), и для любой дуги ГПУ, исходящей из его вершины с меткой L и

ор

помеченной оператором op — L —> L — из вершины N АДД исходит

дуга с меткой ор в его вершину ЛГ: (//,г'), такую что ров^ (г, ор) -» г', где 7г — выбранное множество предикатов.

3. Для любого листа АДД Л/: (Ь, г) верно что:

a. либо у вершины с меткой Ь в ГПУ нет исходящих дуг,

b. либо ср =1 (то же, что и ср — \JNSAT),

c. либо в АДД существует внутренняя вершина ЛГ: (Ь, г'), такая, что г -» г'.

В случае Зс говорят, что вершина Л/:(1, г) покрыта вершиной /V': потому что выполнена вложенность соответствующих множеств состояний программы

([(!,г)] £ [[(/,,г')]) и, как следствие, любое выполнение программы из вершины N с точностью до выбранной абстракции возможно также из вершины Л/'.

Полное АДД приближает сверху множество достижимых состояний программы. Интуитивно АДД представляет собой конечную развёртку ГПУ, вершины которого помечены регионами.

Полное АДД называют безопасным по отношению к конфигурации (Е, р), где Е — ошибочная метка, р — предикат над переменными программы, если для любой его вершины Ы\ (Е,г) конъюнкция г Ар невыполнима (г Ар— UNSAT).

Полное АДД, безопасное по отношению к конфигурации (Е, Т), является доказательством недостижимости в исходной программе ошибочного оператора с меткой Е. Эго частный случай применения следующей теоремы: Теорема 1. Пусть С — ГПУ, Т — полное АДД для С, а р — предикат. Для любой вершины Ь из С если Т безопасно по отношению к конфигурации (1,р), то ни одно конкретное состояние с меткой Ь и значениями переменых, обращающими р в истину, не является достижимым в программе, соответствующей С.

Доказательство этой теоремы можно найти в [38].

4 Построение абстрактных деревьев достижимости

4.1 Построение ГПУ

Формально построение ГПУ по данной простейшей программе с уникальными метками можно описать следующим образом:

• оператору присваивания ора, помеченному меткой 1Л. за которым

следует оператор (или точка выхода) с меткой Ъ2 в ГПУ ставится в

соответствие дуга из вершины в вершину 12. помеченная оператором ора:

• оператору ветвления по условию сь. помеченному меткой который передаёт управление на метку Ъ2 в случае выполнения условия сь или на метку L:i в противном случае, в ГПУ ставится в соответствие две дуги - одна из вершины Ьг в вершину 12 с пометкой (сь), а другая - из вершины в вершину L:i с пометкой ! (сй);

• оператору безусловного перехода на метку 12. помеченному ^. в

ГПУ ставится в соответствие дуга с меткой доЬо 12. направленная из

вершины Ьг в вершину 12.

Таким образом, более одной исходящей дуги могут иметь только вершины ГПУ, соответствующие оператору ветвления. Но так как в простейших программах недопустимы пустые операторы, дуги, исходящие из этих вершин, будут всегда входить в различные вершины (соответствующие меткам операторов в ветвях оператора 11). Поэтому в построенном ГПУ не будет кратных дуг.

4.2 Представление предикатов

При построении АДД будем представлять предикаты с помощью логических формул. В логических формулах будем использовать функции сложения (+), вычитания (—), умножения на константу (), предикаты >,<,=,Ф,>,<, логические связки А и V, а также логическое отрицание Таким образом, каждому допустимому выражению простейшей программы можно будет однозначно поставить в соответствие логическую формулу, заменив используемые в этом выражении операции соответствующими функциями и предикатами.

При установлении такого соответствия делается упрощающее предположение о неограниченности диапазона возможных значений типа л_г^ и о соответствии конкретных аппаратных реализаций операций сложения, вычитания, умножения, сравнения и т.п. (с учётом возможных переполнений) их математическим аналогам. Поскольку на практике такие упрощающие предположения не верны, использование логических формул с функциями +, —, и предикатами >, <, =, Ф, >, < снижает точность верификации и может

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

Строго говоря, к примеру, при выполнении на компьютере с архитектурой, в которой размер типа int равен 32 битам, программа на языке С, аналогичная простейшей программе из примера 1, является некорректной в соответствии со свойством недостижимости ошибочной метки ERR. Вот выполнимый пример ошибочного пути:

(Ll,{x ^ 2147483647,у ^ -2147483648, z ^ 0})------->

(L2, {х н* 2147483647,у ^ -2147483648, z ^ 0}) ^

(L4, {х н* 2147483647,у ^ -2147483648, z ^ -1})----->

На практике все существующие на сегодняшний день инструменты статической верификации, использующие предикатную абстракцию (в частности, декартову) с указанными функциями и предикатами, не гарантируют не только абсолютную, но даже самую высокую точность верификации среди вообще всех существующих инструментов. К примеру, инструмент СВМС, использующий подход ВМС (ограничиваемую проверку моделей), найдёт для программы из примера 1 выполнимый пример ошибочного пути. Инструменты BLAST и CPAchecker, использующие CEGAR с предикатной абстракцией, считают приведённую в примере 1

программу корректной. Существуют, впрочем, и гибридные подходы к

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

найти для данной программы пример ошибочного пути (пример такого инструмента - SATabs). Основным преимуществом инструментов BLAST и CPAchecker является их применимость к реальным промышленным программам среднего размера (порядка 20 тыс. строк кода на С), которые практически не поддаются верификации с помощью более точных инструментов.

Выполнимость логических формул с указанными нами функциями и предикатами могут проверять инструменты проверки выполнимости (SMT-решателц). Подробнее о таких инструментах рассказано в 8 разделе данной статьи.

4.3 Задание сильнейших постусловий с помощью представления SSA

Сильнейшие постусловия можно задавать непосредственно для дуг ГПУ в виде логических формул, например, с использованием так называемого представления SSA [39]. В представлении SSA (от англ. Single State

А881|*тпеп1) каждой переменной значение присваивается лишь единожды. Для этого к каждой переменной программы приписывается индекс, который увеличивается при каждом присваивании.

Предполагая, что индексы всех переменных в 88А представлении изначально равны 1, сильнейшие постусловия для операторов простейшей программы можно записать так:

5Р(р(х1, ...,хк, ...,хп),хк = е(х1, ...,хк,...,хпУ) =

р

А (хк2 = е(х11,..., Х/с1,..., хп1)), индекс хк теперь равен 2

5Р

5Р

Здесь верхний индекс переменной указывает её индекс в представлении 88А. Аналогично сильнейшие постусловия можно выписать при других значениях индексов переменных. Если обозначить индекс произвольной переменной хк через <т(х/с), то:

5Р

= р

Л ^хЛст(ж,с)+1 = е(х1ет(Х1), ...,хпст(Жп))) ;а(хк) <- а(хк) + 1

5Р

Здесь а(хк) <— а(хк) + 1 означает увеличение на 1 индекса переменной х/с.

4.4 Вычисление регионов через сильнейшие постусловия

При вычислении регионов по приведённому ранее (в разделе 3.7) правилу будем использовать уточнённую 88А-индексами запись формулы конъюнкции предикатов для случая 5Р(г, ор) — Б АТ:

..рп}

Д рфг^.............................Хпа^

Р1к£{Р1,-,Рп}'-БР (г,ор)Л-1Р1к (хг а(х1\...,хп°'(хп)) - иN5АТ

Здесь р^(х1ст(Ж1),... ,хпст(Жп)) получается из р1/£(х,,..., хп) приписыванием самых последних индексов 88А представления всем его переменным.

4.5 Построение АДД на основе ГПУ

Будем строить соответствующее полное АДД, последовательно перебирая пути в ГПУ с помощью обхода в глубину. Взяв начальный регион Т, мы построим начальную вершину АДД, соответствующую начальной вершине ГПУ. Затем для последней построенной вершины АДД Ы\ (Ъ,г) будем строить переход по одной из соответствующих исходящих дуг ГПУ (из Ь в I! с меткой

ор

ор — 1-»!') в новую вершину ЛГ:(//,г'), где

г' = роБ1л.(г, ор), 7г — выбранное множество предикатов. Для каждой вновь построенной вершины ЛГ: (V ,ср') будем проверять наличие соответствующих исходящих дуг в ГПУ, выполнимость соответствующего региона ср'. а также покрытие какой-либо уже построенной вершиной ЛГ': (Ь',(р"), где (р' —> (р". При выполнении хотя бы одного из этих условий будем прекращать построение дуг из последней вершины и возвращаться к предыдущей построенной вершине. Построение АДД будем заканчивать тогда, когда для каждой его непокрытой вершины с выполнимым регионом будут построены переходы по всем соответствующим ей исходящим дугам в ГПУ. Полученное АДД будет являться полным по построению, поэтому для проверки свойства достижимости ошибочной метки Е достаточно будет проверить его безопасность по отношению к конфигурации (Е, Т), то есть, по сути, проверить выполнимость регионов в его вершинах с меткой Е (что на самом деле уже будет сделано во время построения).

Конечность АДД обеспечивается конечностью числа дуг ГПУ и описанным способом построения, обеспечивающим, в частности, конечность числа дуг АДД, соответствующих одной дуге ГПУ, за счёт проверки покрытия вершин. Дуге ГПУ, исходящей из вершины I, может соответствовать лишь конечное число дуг АДД, не большее, чем число различных возможных абстрактных состояний с меткой I, то есть 2™ + 1, где п — число предикатов в выбранной абстракции. Эго объясняется тем, что если из какой-либо уже построенной вершины АДД М\ (Ъ, г) исходит дуга, то другая его вершина с тем же абстрактным состоянием ЛГ: (Ъ, г) окажется покрыта ранее построенной вершиной N (т.к. \/(р. ср —»ср) и, как следствие, не будет иметь исходящих дуг.

5 Пример построения АДД

Чтобы лучше осознать основные проблемы, которые позволяет решить метод CEGAR, рассмотрим в начале процесс построения АДД по заранее заданному набору предикатов на примере уже рассмотренной ранее простейшей программы (пример 1). Для этой программы мы попытаемся с помощью полного АДД доказать недостижимость ошибочного оператора, помеченного меткой ERR. Данный простой пример наглядно демонстрирует, что подбор предикатов, подходящих для доказательства недостижимости ошибочного состояния даже в очень простых программах, является непростой задачей. Итак. ГПУ для примера 1 представлен на рис. 16.

Выберем множество из пяти предикатов п = {р,,..., р5}\

р1 = х > О р2=х = О Рз = У > О р4=У = О р5 = z > О

Построим АДД описанным выше способом:

1. Строим вершину L 1#1: (LI, Т).

2. Вершина 11#1имеет соответствующую исходящую дугу L1

X > у

—»L2 в ГПУ, её регион Т — SAT и она, очевидно, не покрыта никакой ранее построенной вершиной.

3. Строим дугу Ll#l: (LI, Т) —н> L2#l: (L2, post^T, (х > у))).

4. Вычисляем postjr(T, (х > у)), пользуясь соответствующим правилом:

предполагаем все индексы SSA равными 1 и пользуемся представлением SSA для записи сильнейшего постусловия:

5Р

рх:5Р

— БАТ

значит р1не входит в искомую конъюнкцию р2:5Р(т,(х>у))л-,р2

Е ТА

— Б АТ (хг = 1, у1 = 0) р3:5Р(т, (х>у))л-,р3

= Т Л

— Б АТ (хх = 1, ух = 0) р4:5Р(т, (х > у)) Л —,р4

Е ТА

— 5Л7’(х1 = 2,ух = 1) р5:5Р(т, (х>у))л-,р5

Е ТА

— БАТ

Искомая конъюнкция пуста. Значит, роБ^Т, (х > у)) = Т.

5. Переходим к вершине Ь2#1\ (1,2, Т). Она имеет соответствующую

2 — Х—у

исходящую дугу Ь2-------»14 в ГПУ, её регион Т — Б АТ и она не

покрыта никакой ранее построенной вершиной.

6. Строим дугу Ь2#1\ (1,2, Т)---н> /,4#1: (14, роБ^Т, г = х — у)).

7. Вычисляем роБ^Т, г = х — у), пользуясь правилом:

полагаем индексы переменных равными 1 и пользуемся представлением 88А для записи сильнейшего постусловия:

5Р(Т,г = х - у) е Т Лг2

= хг — уг — БАТ

p1\SP(T,z = x — y)A -iрг = T Л z2

= xt — уг А -\{хг > 0) — SAT

р5: SP(T, z = х - у) Л -,р5 = Т Л z2

= х1 — y1 A —I

Значит, post^T.z = х — у) = Т.

!

8. Переходим к вершине L4#1:(L4, Т). Дуга L4-------------------> L5b ГПУ,

регион Т — SAT. вершина не покрыта.

!(z< 0) , ч

9. Строим дугу L4#l: (L4, Т)-------> L5#l: (L5,post7r(T,! (z < 0))).

10. Вычисляем post7r(T,! (z < 0)) по правилу:

полагаем индексы переменных равными 1 и пользуемся представлением SSA:

5Р

Р!:5Р

р5:5Р

= —I (Zi < 0) Л (zx < 0) - UNSAT Значит, post7r(T,! (z < 0)) = z > 0

11. Переходим к вершине L5#l: (L5,z > 0). Эта вершина не имеет соответствующих исходящих дуг в ГПУ (соответствующая ей вершина L5 не имеет исходящих дуг).

12. Возвращаемся к предыдущей построенной вершине L4#l. Рассматриваем следующую соответствующую ей исходящую дугу

L4------> ERR в ГПУ.

13. Строим дугу L4#l: (L64T) ------> ERR#1\ (ERR, post7r(T, (z < 0))).

14. Вычисляем post7r(T, (z < 0)). Получаем post7r(T, (z < 0)) = T.

15. Переходим к вершине ERR#1\ (ERR, T). Рассматриваем дугу

goto ERR

ERR--------> ERR в ГПУ. Регион T — SAT, вершина не покрыта.

16. Строим дугу

v goto ERR

ERR#1: (ERR, T)---------> ERR#2\ (ERR,postn(T,goto ERR)).

17. Вычисляем результат post^T, goto ERR) = T.

18. Переходим к вершине ERR#2(ERR,T). Она имеет исходящую дугу

goto ERR

ERR--------> ERRb ГПУ. Регион Т — SAT. Но в АДД есть уже

построенная вершина ERR#1\ (ERR, Т), такая, что Т -» Т. Значит, вершина ERR#2 покрыта вершиной ERR# 1.

19. Возвращаемся к предыдущей построенной вершине ERR#1. Соответствующая ей единственная исходящая дуга ГПУ (ERR

goto ERR

-------> ERR) уже рассмотрена.

20. Возвращаемся к предыдущей построенной вершине L4#l. Все

!

соответствующие ей исходящие дуги ГПУ L4-------------»L5 и L4

----> ERR уже рассмотрены.

21. Возвращаемся к предыдущей построенной вершине L2#l.

z = х — у

Соответствующая ей исходящая дуга ГПУ L2-------------------»L4 уже

рассмотрена.

22. Возвращаемся к предыдущей построенной вершине L 1#1.

Рассматриваем следующую ещё не рассмотренную

!

соответствующую ей исходящую дугу в ГПУ L1----------»L 3.

!

23. Строим дугу Ll#l: (L3, Т)------> L3#1: (L3, post7r(T,! (х > у))).

24. Вычисляем регион post^T,! (х > у)) = Т.

25. Переходим к вершине L3#1:(L3, Т). Рассматриваем дугу L3

z = у — X

------> L4b ГПУ. Регион Т — SAT. вершина L3#1 не покрыта.

26. Строим дугу L3#l: (L5, Т) —-—> L4#2: (L4, post^CXz = у — х)).

27. Вычисляем регион post„.(T,z = у — х) = Т.

28. Переходим к вершине L4#2:(L4, Т). Она покрыта вершиной L4#l: (L4, Т).

29. Убеждаемся, что для каждой непокрытой вершины АДД с

выполнимым регионом построены переходы по всем

соответствующим ей исходящим дугам в ГПУ. Таким образом,

построение АДД закончено.

Полученное АДД показано на рис. 10. Регионы, соответствующие вершинам дерева, показаны в прямоугольниках рядом с вершинами. Покрытие обозначено пунктирной стрелкой, помеченной словами “covered by”, в направлении от покрываемой вершины к покрывающей.

Очевидно, что построенное АДД не является безопасным по отношению к конфигурации (ERR, Т) и таким образом не позволяет доказать недостижимость ошибочной метки ERR. В то же время легко видно, что (в рамках наших упрощающих предположений) в исходной программе эта метка не является достижимой.

Рис. 10. Абстрактное дерево достижимости для заранее заданного набора

предикатов Ьг — Ь5.

На рассмотренном примере хорошо видно, что подбор набора предикатов, подходящего для доказательства недостижимости ошибочного состояния, является непростой задачей. Подобрать нужные предикаты заранее трудно, так как часто они сильно зависят от конкретной программы. Поэтому исследования в области методов поиска набора предикатов для абстракции ведутся в основном в направлении получения новых предикатов на основе исходной программы и какой-либо уже построенной для неё абстракции (например, АДД), которая не позволила доказать корректность программы относительно свойства недостижимости.

6 Метод CEGAR

6.1 Контрпример

В этом разделе контрпримером будем называть некоторый путь в ГПУ из его начальной вершины в вершину, помеченную ошибочной меткой. Основное отличие этого понятия от примера ошибочного пути в графе достижимости состоит в том, что контрпример не включает состояния программы. Это, в частности, означает, что контрпример может не соответствовать никакому пути в графе достижимости. В таком случае контрпример называют ложным. Для рассмотренной простейшей программы (пример 1) ложным контрпримером является, например, такой путь:

х = nondet у = nondet

L1--------> L2-------> L3---> L4-----> L6----> ERR

В этом разделе мы рассмотрим метод CEGAR - Counter Example Guided Abstraction Refinement, то есть метод уточнения абстракции по контрпримеру, суть которого, как следует из названия, заключается в получении новых предикатов на основе ложного контрпримера. Ложный контрпример получают в результате построения АДД, которое не позволят доказать корректность исходной программы относительно свойства недостижимости.

6.2 Уточнение абстракции на примере

Рассмотрим сначала простейшую программу на рис. 11:

void main

int х = nondet;

LI: x = 0;

L2: if

ERROR: goto ERROR;

Puc 11. Простейшая программа

Будем считать, что для неё построено АДД для пустого набора предикатов, так что во всех его вершинах регионы абстрактных состояний равны Т.

х=0

Соответствующий простейший ложный контрпример: Ы —> L2-------------»ERROR.

Запишем для первого перехода в этом пути сильнейшее постусловие: 5Р(Т, х = 0) = (х2 = 0) (верхние индексы, как и ранее, обозначают индексы в представлении SSA). Запишем теперь постусловие для второго перехода, подставив в него вместо региона Т первое сильнейшее постусловие: SP(SP(T, х = 0),х < 0) = (х2 = 0) Л (х2 < 0). Полученная конъюнкция состоит из двух частей. Первая задаёт состояние переменной х перед выполнением оператора условного перехода, а вторая - дополнительные ограничения, наложенные на переменную х в результате этого перехода. Эта конъюнкция как логическая формула является невыполнимой.

Вспомнив правила построения абстрактных состояний с помощью оператора post^, можно попытаться подобрать новый подходящий предикат для исключения найденного ложного контрпримера из АДД, исходя из следующих соображений:

При вычислении региона абстрактного состояния (L2,post1I(T, х = 0)) новый предикат должен войти в результирующую конъюнкцию, чтобы изменить (уточнить) абстрактное состояние. Для этого должно выполняться условие 5Р(Т, х = 0) -» р(х2), или в более общем виде, 5Р(Т, орх) -»р(хст(ж)), где р(х) - новый предикат, <т(х) — значение SSA-индекса переменной х в состоянии (Z^.postnCT, opi)).

Исходя из предыдущего соображения, р(х) войдёт в регион абстрактного состояния (L2, post1T(T,x = 0)). Будем считать post7r(T,х = 0) = р(х). При вычислении абстрактного состояния (L3, post1T(p(x), х < 0)) нужно получить результат J_. Эго покажет недостижимость метки ERROR. Для этого

должно выполняться условие 5Р(р(х), х < 0) — UNSAT. то есть р(х’) Л (х1 < 0) — UNSAT. В более общем виде: 5Р(р(х), ор2) — UNSAT.

• В условиях 5Р(Т, х = 0) -» р(х2) и 5Р(р(х),х < 0) — UNSAT, то есть (х2 = 0) -» р(х2) и р(х’) Л (х1 < 0) — UNSAT в предикат р(х) подставляются, вообще говоря, различные переменные (в данном случае х1 и х2). Однако эти условия можно рассмотреть и совместно, если воспользоваться конъюнкцией 5Р(5Р(Т, х = 0), х < 0) = (х2 = 0) Л (х2 < 0) и взять предикат р(х) только над общими переменными обеих частей этой конъюнкции. Тогда, взяв условия (х2 = 0) -» р(х2) и р(х2) Л (х2 < 0) — UNSAT. мы можем найти удовлетворяющий им предикат в виде р(х2), то есть в общем случае р(хгт<ху). При этом простое переименование переменных х2 -» X, во

втором условии даст в точности нужное исходное условие р(хл) Л (х1 < 0) - UNSAT.

В более общем случае для конъюнкции SP(SP(T,op1),op2) = SP(cp, ор2) = (р А гр следует искать предикат р над общими переменными ср игр такой, что q> -> р и р Лхр — UNSAT. Для такого р будут выполняться условия

5Р(Т,ор1) -> рСхГ/Ч ...,x^Xlm)) и SP(p(xil, ...,xim),op2) - UNSAT, где xt|,, xlm — общие переменные, используемые как в операторах ор1иор2.

Итого для постусловия вида SP(SP(T, op-J, ор2) = SP(cp, ор2) = срЛір и искомого предиката р имеем следующие три условия:

• <Р ^ Р

• р Alp- UNSAT

• р — формула над общими переменными, входящими в формулы (р и 1р.

Задача поиска такого предиката р является известной [8, 19, 53, 54, 55, 56, 57, 58] задачей построения интерполянта Крейга для конъюнкции двух логических формул (в данном случае, двух SMT-формул без кванторов). Такие предикаты при определенных ограничениях можно искать с помощью специальных инструментов - интерполирующих решателей. Для дальнейшего использования с целью уточнения абстракции предикат р при этом должен быть получен в рамках логики нулевого порядка, то есть не содержать кванторов. В этом случае новый предикат для абстракции (то есть без индексов) можно получить из р, опустив индексы всех входящих в него переменных.

6.3 Сильнейшее постусловие пути

Итак, вернувшись к рассмотренному в разделе 5 примеру, заметим, что построенное АДД не только не доказывает недостижимость ошибочной метки ERR, но также позволяет легко получить ложный контрпример, рассмотрев какой-либо путь из своей начальной вершины (соответствующей начальной вершине ГПУ) до одной из вершин с выполнимым регионом, помеченных ошибочной меткой ERR. По построению этот путь будет соответствовать

259

некоторому пути в ГПУ из его начальной вершины в вершину с ошибочной меткой.

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

Найденный контрпример, то есть путь Р в ГПУ, с другой стороны, состоит из последовательности меток и операторов исходной программы, выполнение которых по нашему предположению приводит к достижению метки ERR. Вспомним, что данное нами определение сильнейшего постусловия оператора ор при данном предусловии р позволяет получить предикат, задающий (вместе с некоторой меткой) ограничения на состояния программы, непосредственно достижимые из состояний, заданных предусловием р (и некоторой меткой), в результате выполнения оператора ор. Пусть мы имеем два последовательно выполняемых оператора орг и ор2 (орл; ор2). Если в качестве предусловия р2 для оператора ор2 взять сильнейшее постусловие оператора орг при некотором предусловии р (р2 = SP(p, орл)). то сильнейшее постусловие оператора ор2 при предусловии р2

(SP(p2,op2) = SP(SP(p,op1),op2)) будет задавать состояния программы, достижимые из состояний, заданных предусловием р, в результате последовательного выполнения операторов орг и ор2 (орл; ор2). Аналогично можно построить сильнейшее постусловие для трех и более последовательно выполняемых операторов орг; ...;орп(п > 3). Имея теперь последовательность операторов найденного пути Р: Lx: op,; L2\op2;...; Ln\ орп, мы можем по индукции определить сильнейшее постусловие для всей этой последовательности операторов при некотором предусловии г0, задающем начальные состояния. Данное сильнейшее постусловие будет представлять собой предикат, задающий состояния, достижимые из какого-либо начального состояния по этому пути Р. Так как Р — путь в вершину с ошибочной меткой, то полученный предикат будет задавать ошибочные состояния, достижимые из какого-либо начального состояния по пути Р. Возьмём в качестве г0 тождественную истину г0 = Т. Получим:

5Р

d=f 5Р(5Р(Т,ор1;ор2;...) орп_г),орп) = •••

= 5Р(5Р(...5Р(5Р(Т,ор1),ор2)  > opn_-i), ор^

Здесь через 5Р(Р) мы обозначили сильнейшее постусловие пути Р.

Если представить предикат SP(P) в виде логической формулы, можно говорить о выполнимости, либо невыполнимости этой формулы, а также в случае её выполнимости — о модели, обращающей эту формулу в тождественную истину.

Если формула 5Р(Р) выполнима, то это означает, что она задаёт хотя бы одно достижимое из начального по пути Р ошибочное состояние. Таким образом, исходная программа не является корректной относительно свойства недостижимости, а найденный контрпример Р не является ложным. Кроме этого, при записи постусловий с помощью представления SSA, модель этой формулы, обращающая её в тождественную истину, будет задавать значения некоторых переменных программы непосредственно до выполнения каждого оператора найденного контрпримера. Эти значения можно использовать для указания соответствующего примера ошибочного пути в графе достижимости. Инструменты верификации (в частности, BLAST и CPAchecker) в таком случае, как правило, просто выдают найденный контрпример Р в качестве результата своей работы.

Если формула 5Р(Р) невыполнима, можно говорить о том, что путь Р до ошибочной метки, полученный в результате построения АДД, не соответствует никакому возможному выполнению исходной программы. В этом случае инструменты верификации обычно пытаются получить на основе невыполнимой формулы 5Р(Р) новые предикаты, а затем построить на основе объединения множеств старых и новых предикатов новое АДД, в котором ошибочная метка будет недостижима, по крайней мере, по пути Р. В случае BLAST и CPAchecker получение множества новых предикатов основано на использовании интерполяции Крейга для невыполнимой конъюнкции двух или более логических формул.

6.4 Интерполяция Крейга

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

1. ср^р;

2. р -»гр;

3. каждый неинтерпретируемый (не заданный какой-либо логической теорией) символ в формулер является общим для формул сригр.

Теорема была впервые доказана для логики первого порядка У. Крейгом в 1957 году [40]. Третье условие для формулы р в этой теореме, в частности, означает, что р может содержать только те переменные и

неинтерпретируемые функции, которые являются общими для формул (р и гр. то есть входят в обе эти формулы.

Подставим вместо формулы гр в условие интерполяционной теоремы формулу вида -Iгр!. Тогда общезначимость <р -»гр = -,<р V гр, (-пер Vгр)\^=^1 = -кр V -I гр! будет эквивалентна невыполнимости -|(-|<р V = (р Л грл, Значит можно сказать, что соответствующий интерполянт р существует для невыполнимой конъюнкции двух логических формул ср А грг и удовлетворяет трём условиям:

1. ср ^ р

2. р —> —1 гр! фф —1 р V —1 гр! фф

3. каждый неинтерпретируемый (не заданный какой-либо логической теорией) символ в формуле р является общим для формул ср игрг.

6.5 Уточнение абстракции

Рассмотрим теперь невыполнимую формулу 5Р(Р). Она получена в результате индуктивного выписывания постусловий операторов в пути Р\ор!, Ь2‘. ор2‘, ', Ьп\орп, причем при использовании представления 88А каждое следующее постусловие получается из предыдущего добавлением к нему через конъюнкцию новой логической подформулы2 (тождественной истины Т в случае оператора до1;о) с возможным увеличением индекса одной

из переменных программы в представлении 88А (см. соответствующие формулы для 5Р(р, ор) в разделе 4.3). Эго означает, что полученную формулу 5Р(Р) можно представить в виде

5Р(Р) = Т А гр! А гр2 А ... А грп = грг А (гр2 А ... А грп), где подформулы гр!,гр2,...,грп соответствуют операторам ор,; ор2;...; орп пути Р.

Рассмотрим интерполянт Крейга р} для невыполнимой конъюнкции формул гр! и гр2 А ... А грп. Так как по определению интерполянта в формулу р} могут входить только переменные, общие для формул грги гр2 А ... А грп, то учитывая формулы, используемые для построения сильнейших постусловий, каждая переменная в формулу р{ может входить только с одним индексом представления 88А. Эго означает, что соответствующим переименованием переменных формуле рл можно поставить в соответствие новый предикат р'г над переменными программы, опустив индексы представления 88А у входящих в эту формулу переменных. Эго можно записать так:

2 Подформулой называется часть формулы, сама являющаяся формулой.

То есть после приписывания переменным программы в новом предикате р[ соответствующих индексов представления 88А (имеющихся после вычисления 5Р(Т, орл)). из этого предиката получится интерполянт рл. Предположим теперь, что мы строим АДД, имея множество предикатов п\. включающее в себя р[. Тогда при вычислении региона

из условия (1) для интерполянта Крейга рл (фл —> рл). учитывая, что по построению = 5Р(Т, орл). мы получим

5Р

Это означает, что

(Т, орх) = р[ А гг, гх — некоторая конъюнкция

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

Теперь рассмотрим оставшийся путь Р': 12\ ор2;...; Ьп\ орп.

5Р

= Рі

Переименуем переменные в этой формуле, начав индексирование с последних индексов 88А, которые были получены для 5Р(Т,орг). После замены переменных вида ХХХ -» х1ет(х1\х21 -> Хг^2-1, -> хп0'(Жп:),х12 ->

Эта формула выполнима тогда и только тогда, когда выполнима формула

д, ет(х1) +1 д. ет(х2)+1

, Л2 ' Л2 , ... и

Рі(хі^Жі\ ..., хпс7('Жп'1) Л (гр2 А ... Л хрп) лг1(х1'7(;Ч ..., хпс7('Жп'1) = Рі Л (ф2 А... А 4>п) А Гг

получим:

Поэтому из условия (2) для интерполянта Крейга рх

(рг A (ip2 А ...Агрп) — UNSAT), получаем, что SP^post^/ (Т,op-J,Р') —

UNSAT. Таким образом, невыполнимость сильнейшего постусловия 5Р(Т,Р) для пути Р сохраняется для оставшейся части пути Р', взятой начиная со второго оператора ор2. Отметим, что для невыполнимости

SP^postj,./ (Т, opi), Р') существенно лишь присутствие в п[ нового предиката

р|. Помимо него п\ может включать и другие произвольные предикаты, в частности, предикаты, использованные при построении предыдущего АДД (тт с 7г'). Это позволяет в ходе уточнения абстракции всё время лишь включать в соответствующие множества новые предикаты, не исключая при этом уже найденных новых предикатов. Поэтому будем предполагать п с = п U {рО с п'2 = п и {р’г,р2} С •••.

Далее аналогично тому, как мы рассмотрели невыполнимую формулу 5Р(Т, Р), рассмотрим последовательно невыполнимые формулы

SP( post^ (Т, opi), Р'), SP (post,,/ (post,,/ («Pi, opi), op2), P"),... Через n[,n2>...

обозначены соответствующие новые множества предикатов, а через Р',Р",... — соответствующие оставшиеся части пути Р. Обозначим post^(jp^op^) = r[. post^post„.'(«Pi,орДорг) = r2,... Рано или поздно, то есть, по крайней мере, при рассмотрении 5Р ^post7r/i i(...,opn_1),opn^ = 5Р(г^_1,орп)-UN SAT мы по правилу вычисления регионов (см. секцию 4) получим post^ ^ (г,'_ ,, орп) =1 (независимо от множества предикатов п'п_л). Условие SPir^^opk) — UNSAT может быть выполнено и при меньших значениях к < п, но т.к. путь Р состоит из конечного числа помеченных операторов, оно

обязательно будет выполнено, по крайней мере, для самого последнего

орп

перехода Ln —» ERR при к = п. Это означает, что в новом АДД, которое затем будет построено для набора предикатов = 7Г U {р|,... ,р^_ ,} (здесь к < п\ SPir^^.op];) — UNSAT) либо не будет присутствовать соответствующая вершина с ошибочной меткой ERR, либо регион в этой вершине будет невыполним.

При построении АДД описанным ранее способом одному и тому же пути в ГПУ не может соответствовать более одного пути в АДД, так как все пути в АДД по построению различаются хотя бы одной дугой. Поэтому если множество используемых предикатов будет изменяться только за счёт включения в него новых предикатов, то в результате работы инструмента верификации больше не будет получен контрпример, содержащий уже рассмотренный невыполнимый путь Р до ошибочной метки. Таким образом, однократное успешное уточнение абстракции позволяет исключить из АДД, по крайней мере, один путь, не соответствующий какому-либо возможному выполнению исходной программы.

На практике в инструментах BLAST и CPAchecker реализация процесса уточнения абстракции отличается от рассмотренного индуктивного варианта.

После получения формулы SP(T, Р) для сильнейшего постусловия пути Р в виде Т Л Л гр2 А ... Л фп инструмент верификации всего за одно обращение к интерполирующему решателю получает все интерполянты Крейга, необходимые для уточнения набора предикатов.

Для п логических формул фл,..., фп. конъюнкция которых невыполнима (фг А ...Афп — UN SAT), набором интерполянтое Крейга называется набор из гг — 1 формулы рг,..., рп- \. удовлетворяющих условиям:

1. ф1 ^ р1,ф1Аф2 фг- Афп_г ^ рп_г

2. р1 А (ф2 А ... А фп) - UNSAT, ...,рк А (фк+1 А ... А фп) -

UNSAT,..., рп_г А фп- UNSAT

3. для любого к = 1,п — 1 каждый неинтерпретируемый символ в

формуле рк является общим для формул фл А ... А фк и 4>к+1 А ... А грп.

При п > 2 для невыполнимой конъюнкции формул (ip] A ...A ipn — UNSAT) можно также определить набор индуктивных интерполянтов Крейга Pi.---.Pn-i, Д-1Я которых вместо условий (1) выполняются условия: ipt —> p1,p1Aip2 ^р2. -.Pfc-1 Л^/с ^ Рк. -.Р„-2 ^-фп-1^рп-1- Набор

индуктивных интерполянтов является частным случаем набора интерполянтов Крейга. Действительно, предположим, что не выполнено одно из условий (1) для некоторого 1 < к < п — 1, то есть не выполнено фі А ...А фк -» рк. По определению импликации это означает, что фг,..., фк истины, в то время как рк — ложь. Но тогда по условию для индуктивных интерполянтов учитывая,

ЧТО ф-1, .... фк ИСТИНЫ, получим фг -> Pi,Pi -» р2, --..Pfc-1 Рк, т0 есть Рк

истина, что противоречит предположению. Легко видеть, что для индуктивных интерполянтов Крейга одновременное получение всех интерполянтов с помощью одного вызова интерполирующего решателя по определению эквивалентно уже рассмотренному индуктивному способу их получения. Для общего случая интерполянтов Крейга при некоторых ограничениях на формулы фг, ...,фп и с существенным использованием условия (3) можно доказать исключение соответствующего невыполнимого ошибочного пути из нового АДД, как это делается, к примеру, в статье [41]. Таким образом, на практике успешное уточнение абстракции также означает исключение из АДД, по крайней мере, одного пути, не соответствующего какому-либо возможному выполнению исходной программы.

6.6 Цикл CEGAR

Верификация исходной программы методом CEGAR в инструментах BLAST и CPAchecker представляет собой цикл, на каждой итерации которого выполняются следующие последовательные шаги:

1. Построение АДД по текущему набору предикатов.

2. Проверка безопасности построенного АДД по отношению к конфигурации (Е, Т), где Е — ошибочная метка.

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

б) Иначе происходит переход к шагу 3.

3. В построенном АДД находится вершина ЛЛ(Я, <р), ср Ф±. Строится соответствующий путь Р из начальной вершины АДД в вершину N. Путь Р затем рассматривается в качестве пути контрпримера. Вычисляется сильнейшее постусловие этого пути — 5Р(Р) = 5Р(Т, Р).

4. Проверка выполнимости 5Р(Р).

а) Если постусловие 5Р(Р) как логическая формула оказывается

выполнимой, то найденный контрпример Р выдаётся в качестве результата работы инструмента, как пример выполнения

исходной программы, при котором достигается заданная

ошибочная метка.

б) Иначе происходит переход к шагу 5.

5. Выполняется уточнение текущего набора предикатов с

использованием невыполнимого ошибочного пути Р с помощью

интерполяции Крейга. При успешном уточнении набора предикатов происходит переход к шагу 1.

Таким образом, цикл СЕвАЯ либо завершается на шаге 2 (с доказательством недостижимости), либо на шаге 4 (с контрпримером пути до ошибочной метки), либо выполняется вплоть до исчерпания выделенных инструменту верификации ресурсов (памяти и процессорного времени), то есть

теоретически бесконечно. Бесконечное выполнение возможно, например, при

существовании подходящей бесконечной последовательности предикатов, выводимых на шаге 5.

7 Пример применения метода СЕСАР

Рассмотрим применение метода СЕСАЯ для доказательства недостижимости ошибочной метки в программе из примера 1.

1. Выберем пустой начальный набор предикатов п = 0. Построим АДД,

соответствующее пустому набору предикатов. Получим результат,

показанный на рис. 12.

Рис. 12. Абстрактное дерево достижимости для пустого набора предикатов п = 0.

2. Построенное АДД не является безопасным по отношению к конфигурации (ERR, Т), так как в нём присутствует вершина ERR# 1: (ERR, Т).

3. Рассмотрим путь Р от начальной вершины АДД L 1#1 до вершины ERR#1:

L2: z = х - у;

L4 : (z < 0) ;

Рассмотрим этот путь в качестве контрпримера. Вычислим сильнейшее постусловие SP(P):

SPG?!, Р) = Т Л гр1 А гр2 А гр3 =

Т Ах1 > y1Az2 = х1 — y1 A z2 < 0 — UNSAT

гр2 гр3

4. Постусловие невыполнимо. Выполним уточнение набора предикатов 7г на основе интерполянтов Крейга:

Pi = Xi > ух р2 = z2> 0

Новое множество предикатов тс2 = , р2], р\ = х > у,р'2 = z > 0.

5. Строим АДД, соответствующее новому набору предикатов п' = {х > y,z > 0}.

5.1. Обрабатываем переход

х>у

Ll#l:

Результат: L2#l: (L2,x > у).

5.2. Обрабатываем переход

L2#l:

SP(x > y,z = х — у) = хх > у! Az2 = хх — ух — 5ЛГ(хх = 1,ух = 0, z2 = 1) Pi: xi > Ji Л z2 = х1—у1 А —|(хх > ух) — UNSAT

р2: хх > ух А г2 = хх — ух А -і(г2 > 0) — ИМБАТ Результат:і4#1: (І4, х > у А 2 > 0).

5.3. Обрабатываем переход Ь4#1:

!

----->15#1:

5Р(х > у А г > 0,! (г < 0)) = хг > уг Агг > 0 А -і(г1 < 0) — БАТ(хг = 1 ,у1 = 0,22 = 1) р'1:х1 > Уі Агх > 0 А -1(2! < 0) А -і(х-^ > ух) — ЦЫБАТ р'2\хх > ух А > О А -1(2! < 0) А -1(2! > 0) — иЫБАТ Результат:і5#1: (ЬВ,х > у А 2 > 0).

5.4. Обрабатываем переход Ь4#1: (І4,х > у А 2

>0)

г<0 ,

—> £7?й#1: (£7?й, роБ^ (х > у А 2 > 0, (2 < 0))) 5Р(х > у А 2 > 0, (2 < 0)) = хх > Ух А 2х > О А 2Х < 0 — иN5АТ Ошибочное состояние недостижимо, результат: £йй#1:(£йй,±).

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

5.5. Вычисляем переходы

\

Ь1#1: (Ь1, Т)------> ЬЗ#1(ЬЗ, Т),

ІЗ#1

!

L4#2:

L4#2:

goto ERR

ERR#2:

Вершина ERR#3 покрыта вершиной ERR#2. Завершаем построение АДД. Результирующее дерево показано на рис.

Рис. 13. Абстрактное дерево достижимости для набора предикатов п'2.

6. Построенное АДД вновь не является безопасным по отношению к конфигурации (ERR, Т), т.к. в нём присутствует вершина ERR#2(ERR, Т).

7. Соответствующий путь от начальной вершины АДД до вершины ERR#2:

L1: ! (х > у) ;

L 3: z = у - х ;

Построим сильнейшее постусловие пути:

5Р(Р) = Т Л -1 (хг > Ух) Л г2 = уг — хг Аг2 < 0 - иМБАТ

1/>1 1р2 1/|3

8. Постусловие невыполнимо. Выполним уточнение набора предикатов 7г' на основе интерполянтов Крейга:

р1 = х2< у2 Р2=22> О

Новое множество предикатов п2 = п2 и {р",р2} = {рг,р'2,р" ,р2}

р[ = X > у р'2 = г > О р” = х < у р2 = г > О

9. Покажем, как будет построено поддерево АДД, существенно отличающееся от соответствующего поддерева в предыдущем АДД.

9.1. Переход Ы\

5Р

р": Т А -|(хх > ух) А -|(хх < ух) — UNSAT р2: Т А -|(хх > ух) А > 0) — (хх = 1,ух = 2^ = —1) Результат: 13#1: (13, х < у).

9.2. Переход 13#1: (13, х < у)——»14#2: (/ДроБ^/^х < у, г =

у-х)

5Р(х < у,г = у — х) = хх <угАг2 = ух — хх — 5Л7’(х1 = 1,ух = 0,г2 = 1) р": хх < ух Лг2 = ух — хх Л -|(хх < ух) — ЦМБАТ р2\ х1 < у1 А г2 = у1 — х1 А -п(г2 > 0) — ЦЫБАТ Результат: Ь6#2\ (Ъ6, х < у А г > 0).

9.3. Переход

!

14#2:

SP

Pi: x1 < y1 A > 0 A —і(z1 < 0) A -i(x1 < y-J — UNSAT Р2-х1 <угАгг < 0 A -i (z1 < 0) A -i (z1 > 0) — UNSAT Результат: L5#2: (L5,x < у Az > 0).

9.4. Переход L4#2: (x < у A z

>0)

z< 0 , 4

—>ERR#2: (ERR, postjr//(x < у Az > 0, (z < 0))) SP(x < у A z > 0, (z < 0)) = x1<y1Az1>OAz1<0 - UNSAT Результат: ERR#2: (ERR, 1).

Результирующее АДД показано на Рис. 14.

Рис. 14. АДД для набора предикатов л^.

10. Полученное АДД безопасно по отношению к конфигурации (ERR, Т), т.к. обе вершины ERR#1 и ERR#2, соответствующие ошибочной метке ERR в этом дереве, имеют невыполнимые регионы -L.

Таким образом метод CEGAR в рассмотренном варианте позволил доказать недостижимость ошибочной метки в программе из примера 1 за 3 итерации рассмотренного цикла верификации (шаги 1-4, 5-8, 9-10).

8 Оптимизации и расширения метода CEGAR

Мы рассмотрели применение метода CEGAR для построения и уточнения предикатной абстракции простейшей программы на языке С. В этой программе определена всего одна функция (main), используются только глобальные переменные типа int. Кроме этого в рассмотренном примере отсутствуют циклы. Рассмотрим возможности ослабления данных ограничений и применяемые при этом оптимизации построения абстракции (АДД) программы.

8.1 Ленивая абстракция

В рассмотренном примере 1 (из предыдущего раздела) можно заметить, что процесс построения предикатной абстракции можно несколько упростить, если

• не перестраивать заново те части абстрактного дерева достижимости, где уточнение абстракции не повлияет на достижимость ошибочной инструкции (недостижимость уже доказана существующими предикатами);

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

В нашем примере это будет означать следующее:

• не пересчитывать значение предикатов = х < у и b’z = z > 0 в вершинах L1#1,L2#1,L4#1,L5#1 и ERR# 1, т.к. недостижимость ошибочной инструкции из этих вершин перед добавлением предикатов Ь\ и Ъ'2 уже доказана предикатами b1 = x>ynb2=z> 0. По сути это означает, что при уточнении абстракции можно полностью оставить без изменения всё АДД, кроме поддерева с корнем в вершине L3#1.

• не пересчитывать значение предикатов Ьг и Ь2в вершинах L3#1,L4-#2,L5#1,L5#2,ERR#1 и ERR#2, потому что они были получены для вершин L2#l и L 4#1, и для доказательства недостижимости ошибочной метки их значения существенно требуются только в этих вершинах.

Абстракция, которая строится с учетом предложенных оптимизаций, имеет таким образом не один фиксированный набор предикатов для всех вершин АДД, а свой набор предикатов для каждой вершины. Предикаты в таких наборах называют локальными, а соответствующий метод построения АДД -ленивой абстракцией (от англ. lazy abstraction). Именно этот метод построения АДД реализован в инструментах CPAchecker и BLAST (который поэтому называется Berkeley Lazy Abstraction Software verification Tool).

8.2 Программы с циклами и крупноблочное кодирование

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

В примере 1 при построении АДД по заранее заданному набору предикатов (в разделе 5) выполнялось покрытие вершины L4#2:(L4, Т) другой ранее построенной вершиной L4#l: (L4, Т) с таким же абстрактным состоянием. В этом случае вложенность состояний была установлена тривиально. В общем случае для предикатной абстракции покрытие проверяется как импликация, т.е. состояние <рг покрывает состояние (р2. если (р2 Ч>\  В случае с циклом,

как правило, какая-либо вершина, соответствующая предыдущей итерации цикла будет покрывать вершину для того же места в программе на следующей его итерации, если в обеих вершинах будет выполнен один и тот же набор

предикатов. Этот набор предикатов может, в частности, содержать инвариант

3

цикла .

Рассмотрим пример простейшей программы, представленной на рис. 15. На рис. 16 для этой программы показан абстрактный граф достижимости, построенный для набора предикатов п = {Ъг = z > О, Ъ2 = х > у, Ь3 = х < у }.

вИнвариантом в программировании называется логическое выражение, истинное после каждого прохода тела цикла и перед началом выполнения цикла и зависящее от переменных, изменяющихся в теле цикла.

void main() {

int x = nondet; int у = nondet; int z = nondet;

LI: z = 0;

LOOP: if (x >0) {

L2: if (y > 0) {

L3: if (x > y) {

L4: z = x - y;

L5 : x = z ;

} else {

L6: z = у - x;

L7: у = z;

}

L8: goto LOOP;

}

}

L9: if

ERR: goto ERR;

L10: }

Puc. 15. Пример простейшей программы 2

Рис. 16. Абстрактный граф достижимости для простейшей программы из

примера 2

На этом примере видно, что по существу для обеспечения конечности абстрактного дерева достижимости и доказательства недостижимости ошибочной метки важны лишь состояния абстракции в вершинах, помеченных абстрактными состояниями (LOOP, z > 0) и (ERR, 1). Также видно, что предикат = z > 0 наиболее важен для доказательства недостижимости. Остальные вершины представляют по сути некоторые промежуточные состояния абстракции, а предикаты Ь2 и Ь3 служат для представления некоторой промежуточной информации. Эти рассуждения наталкивают на мысль об оптимизации процесса построения предикатной абстракции за счет склеивания промежуточных вершин.

В инструменте CPAchecker при построении предикатной абстракции применяется крупноблочное кодирование, или LBE (кодирование большими блоками. Large Block Encoding) [42]. Его суть заключается в склеивании вершин на линейных участках графа достижимости и кодировании сильнейшего постусловия сразу для нескольких соответствующих последовательных переходов. В примере 2 можно склеить, к примеру.

вершины І4#1: (І4,х > у Аг > 0), І5#1: (І5,х > у Л г > 0) и І8#1(і8,г > 0). В таком случае сильнейшее постусловие

БР(г > 0,(х > у);г = х — у; х = г) = > 0 Л х1 > у1 А г2 = х1 — у1 А х2 =

г2-

Ъг: > 0 Л хг > У\А22 = хг — уг А х2

= г2 А —\(г2 > 0) — иN5АТ

В результате получим дугу

х>у,г=х—у,х=г

І3#1: (ІЗ, г > 0)-------------> І4 - І8#1: (І8, г > 0). Метка І4 - І8#1 дана

новой вершине, полученной в результате склеивания. Аналогично можно склеить вершины І6#1: (І6, х < у А г > 0), І7#1: (І7, х < у Л г > 0) и

Ь8#2: (1,8,г > 0). Пометим новую вершину, полученную в результате этой склейки меткой Ь6 — Ь8#1. После этого предикаты Ь2=х<уиЬ3=х>у окажутся ненужными для доказательства недостижимости в данном примере. Последовательности инструкций х > у, г = х — у, х = г и !(х>у), г = у — х, у = г называют блоками кодирования. Полученный граф достижимости показан на рис. 17.

Склеивать можно не только вершины на линейных участках графа достижимости, но и вершины, соответствующие одному и тому же месту в программе, в котором происходит слияние потока управления. В нашем примере такими вершинами являются вершины L6 — L8#l и L4 — L8#l. Склеивание таких вершин называется слиянием (от англ. merge). При слиянии необходимо также осуществить объединение двух альтернативных блоков кодирования, в нашем случае между вершинами L3#1 и L4 — L8#l. а также между L3#l и L6 — L8#l. Для этого можно использовать дизъюнкцию. Пометим новую вершину L4 — L8\\L6 — L8#l и посчитаем абстракцию в ней через сильнейшее постусловие:

SP(z > 0,х > y,z = х — у,х = z \ \! (х > y),z = у — х,у = z) = (z1 > О Л х1 > уг A z2 = х1 — уг А х2 = z2) V (zx > О Л -1 (хх > ух) Л z2

= У1~х1Ау2 =z2) bt\ ((zr > О Л xr > yt A z2 = — уг А х2 = z2) V (zx > О Л -|(хх > ух) Л z2

= у1-х1Ау2 =z2))A A —I (z2 > 0) — UNSAT В результате получаем дугу

x>y,z=x—y,x=z И\(x>y),z=y—x,y=z

L3#l\(L3,z> 0)-----------------------------» L4 — L8\\L6 — L8#1(L8, z > 0).

Применяя последовательно аналогичные операции склейки и слияния вершин, получим абстрактный граф достижимости для крупноблочного кодирования с пересчётом состояния абстракции только в заголовке цикла и на ошибочной метке, показанный на рисунке 10 (для получения этого графа необходимо

также предварительно дублировать вершину L2#l).

Рис. 10. Граф достижимости для крупноблочного кодирования со слияниями

В инструменте CPAchecker размер блока кодирования является настраиваемым, как вручную, так и динамически во время построения абстракции. Подробнее об этом написано в статье [43]. Построение абстракции при крупноблочном кодировании в CPAchecker осуществляется аналогично рассмотренному ранее, при помощи вычисления абстрактных постусловий и решения задач о выполнимости формул. CPAchecker сразу строит абстракцию с заданным размером блока, не делая никаких последовательных преобразований АДД, то есть осуществляет склейку и слияние вершин «на лету». Размер блока кодирования в этом инструменте можно ограничивать максимальным количеством объединяемых в один переход инструкций или условием пересчёта абстракции (например, только на заголовке цикла или на заголовке цикла и операторах ветвления).

8.3 Поддержка указателей

В инструменте BLAST помимо типа данных int реализована поддержка некоторых выражений с указателями на основе анализа алиасов. В программировании словом алиасинг (от англ. alias - имя, прозвище) описывается ситуация, при которой какая-либо ячейка с данными в памяти программы оказывается доступна в исходном тексте (коде) этой программы под различными обозначениями (именами). Таким образом, изменение данных с использованием одного из таких обозначений неявно ведет к изменению значений, доступных по всем остальным обозначениям той же ячейки памяти. В результате алиасинг значительно осложняет как анализ и оптимизацию, так и верификацию соответствующих программ. Анализ алиасов предназначен для извлечения из кода программы полезной информации о присутствующем в ней алиасинге.

Например, пусть в программе имеются объявления: int а;

int *р;

Тогда в точке программы, где выполнено условие р == &а, разыменование

* р будет являться алиасом переменной а.

Другой пример: * (<7 + 1) может являться алиасом для b[ 1], если q == b.

Если в результате анализа алиасов оказалось, что для некоторого выражения в программе найденное множество возможных значений указателя включает лишь один вариант (иными словами данное выражение всегда адресует один и тот же объект), то говорят, что такое выражение является обязательным алиасом (от англ. must-alias) этого объекта. В других случаях говорят о возможных алиасах (от англ. may-aliases).

Для методов статической верификации, которые сами по себе анализируют различные пути выполнения программы, бывает достаточно не чувствительного к потоку управления метода анализа возможных алиасов. В инструменте BLAST информация об алиасах используется при генерации ограничений, которые на состояние памяти программы накладывает операция присваивания. При анализе контрпримера или вычислении абстрактного постусловия для каждого возможного алиаса цели присваивания генерируется проверка, является ли его адрес равным адресу цели. В зависимости от результата этой проверки с помощью логических формул выражается либо обновление соответствующего выражения-алиаса, либо сохранение прежнего значения этого выражения. Выборка возможных алиасов для каждой цели присваивания, таким образом, производится на основе результатов работы нечувствительного к потоку анализа возможных алиасов. В качестве нечувствительного к потоку управления алгоритма анализа алиасов BLAST используется алгоритм на основе BDD, описанный в [44].

Пусть, например, в некоторой программе переменные а,ра и pb имеют тип int, int * и int * соответственно. Анализ алиасов показал, что выражение * pb является возможным алиасом выражения * ра ив момент генерации формулы перехода для операции присваивания выражениям pa, pb, а, * pa, * pb соответствовали следующие индексы в SSA-представлении: {pa -»i,pb -»j,a -» к,* pa -» I* pb -» т}. Тогда для операции присваивания *ра = a BLAST сгенерирует формулу перехода:

* pal+1 = ак A {

V

Такой подход к использованию анализа возможных алиасов для генерации ограничений в формулах пути был расширен в [41] на анализ алиасов с участием структурных типов. В статье приводится алгоритм генерации ограничений, соответствующих присваиваниям и условиям в ветвлениях рассматриваемой программы, с учетом наличия структурных типов. В статье вводятся ограничения на точность анализа в случае рекурсивных структур (к примеру, связных списков) и сильно вложенных указателей на структуры (переменные, генерируемые при анализе, не могут отличаться от встречаемых в программе более, чем на фиксированное количество разыменований). Для целей присваивания обновляются не только непосредственно выражение в левой части и его алиасы (в случае равенства их адресов), но и их поля, если это структурные типы, и поля тех структур, на которые они указывают, если это указатели на структуру. Обновляемые таким образом значения генерируются рекурсивно не более чем на заданную глубину, которая измеряется количеством разыменований.

8.4 Анализ реальных программ на С

Реальные программы, в отличие от простейших

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

• используют глобальные и локальные переменные различных типов,

• размещают составные объекты в статической и динамической памяти, в том числе структуры, массивы и объединения;

• могут содержать все конструкции (операторы и выражения) языка С, в том числе нелинейные и побитовые операции.

Кроме этого реальные инструменты статической верификации (в том числе BLAST и CPAchecker) позволяют использовать недетерминированные значения в любом месте программы, где допустим вызов функции без параметров.

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

BLAST и CPAchecker перед верификацией применяют к данной на вход программе некоторые упрощающие преобразования, которые, в частности, позволяют свести все виды циклов, короткую логику и тернарный оператор к использованию только операторов if и goto. В BLAST эти преобразования выполняются с использованием инструмента CIL [45].

Поддержка вызовов функций в BLAST и CPAchecker реализована с помощью встраивания тела функции в месте её вызова. При этом каждому формальному параметру функции ставится в соответствие переменная, которой присваивается значение соответствующего фактического параметра в месте вызова функции. Возвращаемое значение функции присваивается временной переменной непосредственно перед выходом из функции. При такой реализации BLAST и CPAchecker в случае рекурсивных функций поддерживают только небольшую конечную глубину рекурсивных вызовов. Некоторые инструменты, использующие метод CEGAR, например, Yogi [46] реализуют возможности так называемого обобщения (от англ. summarization) вызовов функций. Такие инструменты в месте вызова функции сначала пытаются переиспользовать некоторую информацию о ней, собранную во время анализа тела функции. Например, Yogi анализирует вызовы функций в три этапа.

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

• Если первое обобщение оказывается неудачным, что происходит в результате построения ложного контрпримера (проверку контрпримера Yogi выполняет с помощью интерпретации программы), инструмент пытается использовать следующее обобщение. Оно состоит из двух пар регионов - (<Pi,i/;i) и (ср2,ф2). Первая пара представляет собой обобщение достижимости (от англ. must-summary). Его смысл в том, что для каждого состояния, заданного ср1 (для любого места вызова функции) существует выполнение вызываемой функции, приводящее в какое-либо состояние, заданное ф {. Вторая пара - это обобщение недостижимости (от англ. not-may summary), оно означает, что не существует выполнений вызываемой функции, приводящих из какого-либо состояния, заданного ср2 в какое-либо состояние, заданное гр2. Предположим, что регион абстрактного состояния программы непосредственно перед вызовом функции равен а, а непосредственно после возврата из неё - /?. Тогда если выполнены условия срг -» а и П /? Ф 0, то это означает, что можно попытаться построить уточненный контрпример, включающий вызов функции, в два этапа. Сначала построить такой контрпример, чтобы перед вызовом функции был выполнен предикат <рл. а после её вызова -предикат . Затем построить контрпример выполнения тела функции, который существует по определению пары регионов ((Pi.ipi). Если же выполнены условия а -»ср2 и /? -»гр2, то можно исключить построенный ложный контрпример, уточнив регион а соответствующего абстрактного состояния с помощью конъюнкции с регионом (р2 (конъюнкция соответствует пересечению абстрактных состояний данных). По определению пары (<р2,4>2) при условии Р —> ф2 все состояния, заданные /? окажутся недостижимыми из уточненного абстрактного состояния. Использование обобщений, таким образом, позволяет переиспользовать информацию о достижимости, полученную ранее при анализе тела функции, оптимизируя построение контрпримера и позволяя уточнять абстракцию без вызова интерполирующего SMT-решателя.

• Если использовать второе обобщение не удается, Yogi подставляет и анализирует тело вызываемой функции в месте вызова. При этом могут уточняться имеющиеся для этой функции пары регионов Oi-V'i) и (ср2,гр2).

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

Решение и интерполяция логических формул в теории битовых векторов поддерживается некоторыми SMT-решателями, например, MathSАТ 5 [47]. Использование битовых векторов позволяет также точно кодировать большинство нелинейных и побитовых операций над переменными различных типов. При использовании целых и вещественных чисел в BLAST и CPAchecker нелинейные и побитовые операции кодируются неинтерпретируемыми функциями, что снижает точность анализа.

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

9 Использование решателей

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

В широком смысле решающие процедуры (от англ. decision procedures) или решатели, (от англ. solvers) - это алгоритмы, которые приняв на вход проблему разрешимости, то есть вопрос, сформулированный в рамках какой-либо формальной системы (аксиоматической теории), и требующий ответа «да» или «нет»; и выдают на выходе соответствующий корректный результат. В узком смысле в рамках данной статьи нас интересуют решатели для алгоритмически разрешимых задач, сформулированных в рамках теорий первого порядка, и используемых на практике в областях верификации, доказательства корректности, оптимизации и др. Возможности многих технологий в этих областях ограничены возможностями используемых решателей. Поэтому последние остаются объектом многих активных исследований во всём мире, как в академической среде, так и в промышленности.

Решающие процедуры можно разделять по поддерживаемым ими теориям. Решатели для логики высказываний, т.е. классической логики нулевого порядка, которые по сути являются инструментами для решения задачи выполнимости булевых формул, называются SAT-решателями. Решатели для формул классической логики первого порядка с равенством, заданных в рамках комбинации некоторых аксиоматических теорий, называются SMT-решателями. Такие решатели могут поддерживать только формулы нулевого порядка (без кванторов). Среди наиболее часто используемых на практике теорий - вещественная и целочисленная линейная арифметика, неинтерпретируемые функции, массивы и битовые векторы.

Практически все современные решатели для пропозициональной (логической) части формулы используют либо схему DPLL (Davis-Putnam-Logemann-Loveland), основанную на поиске с возвратом, для решения задачи в виде КНФ, либо основаны на суперпозиционном исчислении (от англ. superposition calculus), расширении резолютивного вывода [48]. Алгоритм DPLL описан, например, в книге [49]. Для взаимодействия с решающими процедурами и для подформул, заданных в рамках какой-либо теории, используются в основном техники комбинирования Нельсона-Оппена (Nelson-Oppen Combination Procedure), пропозиционального кодирования (от англ. prepositional encodings) и отложенного комбинирования теорий (DTC, Delayed Theory Combination). Техники комбинирования решателей Нельсона-Оппена и пропозиционального кодирования приводится, например, в книге [49]. Отложенное комбинирование теорий предложено в статье [50] и используется в решателе MathSAT.

Также некоторые решатели могут в результате своей работы помимо основного ответа «выполнимо»/«невыполнимо», давать также некоторую дополнительную информацию, например, для выполнимой формулы - её модель, для невыполнимой - опровержение (например, в виде дерева вывода тождественной лжи), набор дизъюнктов, использованных для доказательства невыполнимости (англ. unsatisfiable core), для невыполнимой конъюнкции пары формул - интерполянт Крейга (Craig interpolant) [40].

Решатели, позволяющие находить интерполянты Крейга, называются интерполирующими процедурами, или интерполирующими решателями, или просто интерполяторами. В существующих инструментах интерполянты строятся из дерева вывода для доказательства невыполнимости. Некоторые методы построения интерполянтов из дерева вывода для комбинации определённого класса теорий описаны в статье [51]. Они требуют предварительного построения частичных интерполянтов для используемых теорий. Методы их построения для вещественной линейной арифметики предлагаются в статье [52], а для теории неинтерпретируемых функций - в статье [53]. На данных методах основывается интерполирующий решатель CSIsat [54] для логических формул без кванторов в рамках теорий вещественной линейной арифметики и неинтерпретируемых функций. Этот интерполирующий решатель используется инструментом верификации BLAST [4]. Другой интерполирующий решатель для формул без кванторов, MathSAT, поддерживающий также целочисленную линейную арифметику, использует свой метод построения и комбинирования частичных интерполянтов, описанный в статье [55] и диссертации [56]. MathSAT используется как основной интерполирующий решатель в инструменте CPAchecker [34]. SLAM2 использует для проверки выполнимости пути и извлечения предикатов SMT-решатель Z3 [57], о чём говорится, например, в статье [58].

Таким образом, практическая эффективность работы современных инструментов верификации (SLAM2, BLAST, CPAchecker) во многом 284

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

10 Заключение

Мы подробно рассмотрели метод CEGAR для декартовой предикатной абстракции, применив его для доказательства недостижимости ошибочной метки в коде примера простейшей программы на языке С. На рассмотренном примере мы показали такие этапы работы инструментов статической верификации BLAST и CPAchecker, основанных на CEGAR, как построение графа потока управления программы, обход этого графа, построение абстрактного дерева достижимости, в том числе вычисление сильнейших постусловий и регионов абстрактных состояний, а также процесс уточнения абстракции по ложному контрпримеру с использованием интерполяции Крейга. В конце мы также рассказали о некоторых наиболее распространённых модификациях и оптимизациях, используемых при реализации подхода CEGAR в современных инструментах.

Инструменты, основанные на методе CEGAR, занимают первые места в международных соревнованиях по верификации Си-программ [59], [60]. Их текущее состояние позволяет успешно решать задачи верификации моделей аппаратных систем [61], драйверов устройств в операционных системах Windows [31, 58] и Linux [18, 35, 36, 62, 63, 64], а также систем, образующих так называемые product lines [65, 66].

Тем не менее, в реальном коде встречается немало сложностей, которые вызывают проблемы даже у самых современных инструментов, реализующих подход CEGAR. Открытые направления для развития включают в себя:

• поддержку нелинейной арифметики, возможно, с помощью комбинирования предикатной абстракции с другими видами анализа [67, 68];

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

• поддержку преобразования типов и объединений (union);

• анализ указателей, в том числе поддержку алиасинга, массивов, адресной арифметики, конструкций вида container_of [69] и др.;

• анализ сложных типов данных, таких как ссылочные структуры, хэш-таблицы и др.;

• анализ многопоточных программ.

Литература

[ 1 ]. Dershowitz N. Software horror stories.

URL: http://www.cs.tau.ac .iV~nachumd/horror.html

[2]. Turing A. M. On Computable numbers, with an application to the Entscheidungsproblem // Proceedings of the London Mathematical Society. 1936. pp. 230—265.

[3]. Floyd R. Assigning Meanings to Programs // Mathematical Aspects of Computer Science. 1967. pp. 19—32.

[4]. Hoare C. An Axiomatic Basis for Computer Programming // Communications of the ACM. 1969. vol. 12. pp. 576—580.

[5]. Dijkstra E. A Discipline of Programming // Prentice-Hall, 1976.

[6]. Millo R. D., Lipton R., Perlis A. Social Processes and Proofs of Theorems and

Programs // Communications of the ACM. 1979. vol. 22. pp.271—280.

[7]. Nelson G. Techniques for Program Verification // Tech. Rep. CSL81-10: Xerox Palo Alto Research Center, 1981.

[8]. Nelson G., Oppen D. Fast Decision Procedures Based on Congruence Closure // Journal of the ACM. 1980. vol. 27. pp. 356—364.

[9]. Shostak R. Deciding Combinations of Theories // Journal of the ACM. 1984. vol. 31. pp. 1—12.

[10]. Clarke E. M., Emerson E. A. Synthesis of Synchronization Skeletons for Branching Time Temporal Logic//Logic ofPrograms. 1981. vol. 131. pp. 52—71.

[11]. Queille J., Sifakis J. Specification and Verification of Concurrent Systems in Cesar

// Fifth International Symposium on Programming / Ed. by M. Dezani-Ciancaglini,

U. Montanari. Lecture Notes in Computer Science. Springer-Verlag, 1981. pp. 337— 351.

[12]. Vardi М., Wolper P. Reasoning about Infinite Computations // Information and Computation. 1994. vol. 115. pp. 1—37.

[13]. Pnueli A. The Temporal Logic of Programs // Proceedings of the 18th Annual Symposium on Foundations of Computer Science. IEEE Computer Society Press, 1977. pp. 46—57.

[14]. Emerson E. Temporal and Modal Logic // Handbook of Theoretical Computer Science / Ed. by J. van Leeuwen. Elsevier Science Publishers, 1990. vol. B. pp. 995—1072.

[15]. Khedker Uday P., Sanyal Amitabha, Karkare Bageshri. Data Flow Analysis: Theory and Practice // CRC Press (Taylor and Francis Group), 2009.

[16]. D'Silva V., Kroening D., Weissenbacher G. A Survey of Automated Techniques for Formal Software Verification // Computer-Aided Design of Integrated Circuits and Systems. 2008. vol. 27, no. 7. pp. 1165—1178. On IEEE Transactions.

[17]. Jhala R., Majumdar R. Software model cheking // ACM Computing Surveys. 2009.

[18]. Д. Бейер, А.К. Петренко. Верификация драйверов операционной системы Linux // Труды Института системного программирования РАН, том 23, ISSN 2220-6426 (Online), ISSN 2079-8156 (Print), 2012 г. стр. 405-412.

[19]. Vladimir Nesov. Automatically Finding Bugs in Open Source Programs // Third International Workshop on Foundations and Techniques for Open Source Software Certification. OpenCert2009 vol. 20 2009. pp. 19—29.

[20]. Dawson Engler, Benjamin Chelf, Andy Chou. Checking system rules using system-specific, programmer-written compiler extensions // Proceedings of the 4th conference on Symposium on Operating System Design & Implementation vol. 4 OSDI'OO. 2000.

pp. 1—16.

[21]. Julia L. Lawall, Julien Brunei, Nicolas Palix, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller. WYSIWIB: A declarative approach to finding API protocols and bugs in Linux code // DSN'09 - The 39th Annual IEEE/IFIP International Conference on Dependable Systems and Networks. 2009. pp. 43—52.

[22]. Арутюн Аветисян, Алексей Бородин. Механизмы расширения системы статического анализа Svace детекторами новых видов уязвимостей и критических ошибок // Труды Института системного программирования РАН, том 21, ISSN 2220-6426 (Online), ISSN 2079-8156 (Print). 2011. стр. 39-54.

[23]. B.H. Игнатьев. Использование легковесного статического анализа для проверки настраиваемых семантических ограничений языка программирования // Труды Института системного программирования РАН, том 22, ISSN 2220-6426 (Online), ISSN 2079-8156 (Print). 2012. стр. 169-188.

Biere A., Cimatti A, Clarke E., Strichman O., Zhu Y. Bounded model checking // Advances in Computers. 2003.

Clarke E., Grumberg O., Jha S., Lu Y., Veith H. Counterexample-Guided Abstraction Refinement for Symbolic Model Checking // Journal of the ACM. 2003.

Thomas Donald, Moorby Phillip. The Verilog Hardware Description Language // Norwell, MA.: Kluwer Academic Publishers.

Clarke E., Kroening D., Lerda F. A tool for checking ANSI-С programs // Tools and Algorithms for the Construction and Analysis of Systems. 2004.

Ivancic F., Yang Z., Ganai M.K., Gupta A., I. Shlyakhter, Ashar P. F-soft: Software verification platform // Computer Aided Verification, vol. 3576 of Lecture Notes in Computer Science. Springer, 2005. pp. 301—306.

Post H., Sinz C., Merz F., Gorges Т., Kropf T. Linking functional requirements and software verification // 17th IEEE International Requirements Engineering Conferene. 2009. pp. 295—302.

Donaldson A. F., Kroening D., Ruemmer P. Automatic analysis of DMA races using model checking and k-induction // Formal Methods in System Design. 2011. vol. 39. pp. 83—113.

Ball Т., Bounimova E., Levin V., Kumar R., Lichtenberg J. The Static Driver Verifier Research Platform // Computer Aided Verification. 2010.

D. Beyer, T. A. Henzinger, R. Jhala, R. Majumdar. The software model checker BLAST: Applications to software engineering // Int. J. Softw. Tools Technol. Transf.,

2007. vol. 9, № 5, ISSN 1433-2779. Springer-Verlag, Berlin, Heidelberg, pp. 505— 525.

E. Clarke, D. Kroening, N. Sharygina, K. Yorav. SATABS: SAT-based Predicate Abstraction for ANSI-C // Tools and Algorithms for the Construction and Analysis of Systems (TACAS 2005), 2005. Lecture Notes in Computer Science, Springer Verlag. vol. 3440. ISBN 3-540-25333-5. pp.570—574.

D. Beyer, М. E. Keremoglu. CPAchecker: a tool for configurable software verification // Proceedings of the 23rd international conference on Computer aided verification. 2011. ISBN 978-3-642-22109-5. Springer-Verlag. pp. 184—190.

B.C. Мутилин, E.M. Новиков, А.В. Страх, А.В. Хорошилов, П.Е. Швед. Архитектура Linux Driver Verification. // Труды Института системного программирования РАН, том 20, стр. 163—187, 2011.

[36]. A. Khoroshilov, V. Mutilin, E. Novikov, P. Shved, A. Strakh. Towards an Open Framework for C Verification Tools Benchmarking // Proceedings of the Eighth International Andrei Ershov Memorial Conference “Perspectives of Systems Informatics” (PSI 2011), pp. 82—91,2011.

[37]. S. Graf, H. Saidi. Construction of Abstract State Graphs with PVS // Computer Aided Verification, 9th International Conference, CAV '97, Haifa, Israel, pp. 72—83.

[38]. Henzinger, T.A., Jhala, R., Majumdar, R., Sutre, G. Lazy abstraction // Proc. POPL. ACM, New York. pp. 58—70, 2002.

[39]. Cytron, R., Ferrante, J., Rosen, B.K.,Wegman,M.N., Zadek, F.K. Efficiently computing static single-assignment form and the program dependence graph // ACM Trans. Program. Languages Systems 13(4), pp. 451—490. 1991.

[40]. W. Craig. Linear reasoning // J. Symbolic Logic. 1957. vol. 22, pp. 250—268.

[41]. T. A. Henzinger, K. L. McMillan, R. Jhala, R. Majumdar. Abstractions from Proofs. //POPL 2004.

[42]. D. Beyer, A. Cimatti, A. Griggio, M.E. Keremoglu, R. Sebastiani. Software Model Checking via Large-Block Encoding // Proc. FMCAD, pp. 25—32. IEEE, 2009.

[43]. D. Beyer, M. E. Keremoglu, P. Wendler. Predicate abstraction with adjustable-block encoding // Proceedings of the 2010 Conference on Formal Methods in Computer-Aided Design. Lugano, Switzerland. 2010. pp. 189—198.

[44]. M. Bemdl, O. Lhotak, F. Qian, L. Hendren, N. Umanee. Points-to analysis using BDDs // Proceedings of the ACM SIGPLAN 2003 conference on Programming language design and implementation (PLDI '03). ISBN 1-58113-662-5. San Diego, California, USA. pp.103—114.

[45]. George C. Necula, Scott McPeak, S. P. Rahul, Westley Weimer. CIL: Intermediate Language and Tools for Analysis and Transformation of C Programs. // in Proc. of Conference on Compiler Construction, 2002, pp. 213—228.

[46]. Aditya V. Nori and Sriram K. Rajamani. An Empirical Study of Optimizations in Yogi // ICSE '10: International Conference on Software Engineering, May 2010.

[47]. Alessandro Cimatti, Alberto Griggio, Bastiaan Joost Schaafsma, Roberto Sebastiani. The MathSAT5 SMT Solver. // Tools and Algorithms for the Construction and Analysis of Systems. Lecture Notes in Computer Science, volume 7795, 2013, pp. 93—107.

Robert Nieuwenhuis, Alberto Rubio. Paramodulation-Based Theorem Proving // Handbook of Automated Reasoning I (7), Elsevier Science and MIT Press, 2001.

D. Kroening, O. Strichman. Decision Procedures. An Algorithmic Point of View // Springer, 2008.

M. Bozzano, R. Bruttomesso, A. Cimatti, T. Junttila, S. Ranise, P. van Rossum,

R. Sebastiani. Efficient Satisfiability Modulo Theories via Delayed Theory Combination// CAV'05.

G. Yorsh, M. Musuvathi. A Combination Method for Generating Interpolants // CADE 2005.

A.Rybalchenko, V.Sofronie-Stokkermans. Constraint Solving for Interpolation //

A VACS TR No. 56, 2009.

K. L. McMillan. An Interpolating Theorem Prover // TCS'05.

D.Beyer, D.Zufferey, R.Majumdar. CSIsat: Interpolation forLA+EUF. Tool paper.

2008.

A.Cimatti, A.Griggio, R.Sebastiani. Efficient Generation of Craig Interpolants in Satisfiability Modulo Theories // ACM Transactions on Computational Logic, vol.

12, issue 1, October 2010.

Thi Thieu Hoa Le. A Novel Technique for Computing Craig Interpolants in Satisfiabilility modulo the Theory of Integer Linear Arithmetic. PhD thesis.

Leonardo De Moura, Nikolaj Bjemer. Z3: an efficient SMT solver // TACAS'08/ETAPS'08. Proceedings of the Theory and practice of software, 14th international conference on Tools and algorithms for the construction and analysis of systems, pp. 337—340.

T. Ball, E. Bounimova, R. Kumar, V. Levin. SLAM2: Static driver verification with under 4% false alarms // Formal Methods in Computer-Aided Design (FMCAD), oct. 2010. pp. 35—42.

D. Beyer. Competition on Software Verification // C. Flanagan, B. Konig eds. Tools and Algorithms for the Construction and Analysis of Systems. Lecture Notes in Computer Science, vol. 7214. ISBN 978-3-642-28755-8. Springer Berlin Heidelberg, 2012. pp. 504—524.

D. Beyer. Second Competition on Software Verification //N. Piterman, S. A. Smolka eds. Tools and Algorithms for the Construction and Analysis of Systems. Lecture

Notes in Computer Science, vol. 7795. ISBN 978-3-642-36741-0. Springer Berlin Heidelberg, 2013. pp. 594-609.

[61]. A. Cimatti, A. Micheli, I. Narasamdya, M. Roveri. Verifying SystemC: A Software Model Checking Approach. // in Proc. FMCAD, FMCAD Inc. 2010. pp. 51-59.

[62]. A. Galloway, G. Luttgen, J. T. Muhlberg, R. I. Siminiceanu. Model-Checking the Linux Virtual File System //N. D. Jones, M. Muller-Olm (eds.). VMCAI 2009. LNCS, vol. 5403, pp. 74—88. Springer, Heidelberg. 2009.

[63]. J. T. Muhlberg, G. Luttgen. Blasting Linux Code // L. Brim, B. R. Haverkort, M. Leucker, J. van de Pol

[64]. W. Penninckx, J. T. Muhlberg, J. Smans, B. Jacobs, F. Piessens. Sound Formal Verification of Linux’s USB BP Keyboard Driver // A. E. Goodloe, S. Person (eds.). NFM2012. LNCS, vol. 7226, pp. 210—215. Springer, Heidelberg. 2012.

[65]. S. Apel, H. Speidel, P. Wendler, A. von Rhein, D. Beyer. Detection of feature interactions using feature-aware verification // 26th IEEE/ACM International Conference on Automated Software Engineering (ASE), 2011, pp.372—375. 6-10 Nov. 2011.

[66]. A. Classen, P. Heymans, P.-Y. Schobbens, A. Legay. Symbolic model checking of software product lines // in Proceedings of the International Conference on Software Engineering (ICSE). ACM, 2011, pp. 321—330.

[67]. D. Beyer, T. A. Henzinger, G. Theoduloz. Program Analysis with Dynamic Precision Adjustment // Proc. ASE, pp. 29—38. IEEE

[68]. P. Cousot, R. Cousot, J. Feret, L. Mauborgne, A. Mine, D. Monniaux, X. Rival. Combination of Abstractions in the ASTREE Static Analyzer // M. Okada, I. Satoh

[69]. J. Corbet, A. Rubini, G. Kroah-Hartman. Linux Device Drivers, 3rd Edition, Chapter

3, section “The open Method”, pp. 58—59 // O'Reilly Media. 2005.