Рабочим названием платформы .NET было |
Верификация CIL-кода. Библиотеки для создания метаинструментов
Говорят, что код программ не разрушает память, если эти программы, будучи запущенными в одном адресном пространстве, корректно изолированы друг от друга, то есть ошибки в одной программе не могут привести к изменению структур данных другой программы.
Верификация кода - это процесс автоматического доказательства того, что этот код не разрушает память.
В этой главе мы рассмотрим общие вопросы верификации кода и алгоритм верификации, приведенный в спецификации CLI и реализованный в Microsoft .NET Framework.
Классификация применяемых на практике алгоритмов верификации
Алгоритмы верификации являются метавычислительными алгоритмами (верификаторы кода стоят в одном ряду с такими программами, как частичные вычислители и суперкомпиляторы). Поэтому совершенно неудивительно, что алгоритмы верификации в общем случае имеют проблемы, свойственные любым метавычислительным алгоритмам, а именно: экспоненциальная сложность и потенциальная недетерминируемость.
Понятно, что алгоритмы, работающие неизвестно сколько времени (а возможно, вообще никогда не завершающиеся) и потребляющие неизвестно какое количество ресурсов, представляют скорее теоретический, нежели практический интерес. Поэтому их разработчикам приходится каким-то образом ограничивать задачу, чтобы получить применимые на практике результаты. Для алгоритмов верификации кода известно два подхода, позволяющие снизить их сложность и добиться терминируемости:
- Концентрация усилий на выявлении только таких ошибок в программах, поиск которых не требует экспоненциальной сложности и выполняется за конечное время.
Для этого подхода характерно то, что существуют программы, в которых верификатор не находит ни одной ошибки и которые, тем не менее, разрушают память.
- Работа лишь с некоторым подмножеством программ, для которых факт неразрушения ими памяти можно доказать за конечное время с использованием алгоритма приемлемой сложности. Этот тип верификаторов отличается тем, что существуют не разрушающие память программы, которые не могут быть доказаны алгоритмом.
Если задача некоторого метавычислительного алгоритма была как-то ограничена, то сразу же возникает вопрос о том, какие входные данные (программы) он сможет обработать. Для суперкомпиляторов и частичных вычислителей этот вопрос до сих пор остается открытым (ответ на него приходится искать методом проб и ошибок). Для верификаторов ответ зависит от того, какой подход (первый или второй) был выбран для ограничения задачи:
- Верификаторы, предназначенные для выявления определенного класса ошибок в программах, используются при разработке программ на этапе тестирования. Тем самым, на вход таких верификаторов подаются любые программы в надежде, что в них будут обнаружены ошибки.
Запуск такого верификатора можно рассматривать как один из тестов, которым подвергается программа. Если в некоторой программе верификатор не обнаружил ни одной ошибки, то можно считать, что его запуск был напрасным.
- Верификаторы, способные доказать, что программа, принадлежащая некоторому классу программ, не разрушает память, служат для обеспечения безопасности. Они гарантируют, что запуск прошедшей верификацию программы не может привести к сбою всей системы.
Использование таких верификаторов оправдано и удобно в случаях, если существуют специальные компиляторы, генерирующие такой код, который не только заведомо не разрушает память, но и гарантированно проходит верификацию.
Тем самым ответ на вопрос о том, для каких программ верификатор может доказать, что они не разрушают память, становится тривиально прост: для тех программ, которые были откомпилированы специальным компилятором.