Парадигма | Императив, Функциональный |
---|---|
Разработан | К. Рустан М. Лейно |
Разработчик | Microsoft Research |
Впервые появилось | 2009 г.; 11 лет назад (2009 г.) |
Стабильный выпуск | 2.3.0 / 7 мая 2019 г.; 17 месяцев назад (2019-05-07) |
Печатная дисциплина | Статическая, сильная, безопасная |
Лицензия | Лицензия MIT |
Веб-сайт | исследование.microsoft.com / dafny |
Dafny - это императивный скомпилированный язык, предназначенный для C # и поддерживающий предварительные условия формальных спецификаций с по , постусловия, инварианты цикла и варианты цикла. Язык сочетает в себе идеи в основном из функциональной и императивной парадигм и включает ограниченную поддержку объектно-ориентированного программирования. Возможности включают универсальные классы, динамическое размещение, индуктивные типы данных и разновидность логики разделения, известную как неявные динамические кадры для рассуждений о побочных эффектах. Dafny был создан Рустаном Лейно из Microsoft Research после его предыдущей работы по разработке ESC / Modula-3, ESC / Java и Spec #. Dafny широко используется в обучении и регулярно участвует в соревнованиях по проверке программного обеспечения (например, VSTTE'08, VSCOMP'10, COST'11 и VerifyThis'12).
Дафни был разработан, чтобы обеспечить простое введение в формальную спецификацию и проверку, и широко использовался в обучении. Dafny построен на промежуточном языке Boogie , который использует автоматическое средство доказательства теорем Z3 для выполнения обязательств по доказательству.
Dafny предоставляет методы для реализации, которые могут иметь побочные эффекты и функции для использования в спецификации чистый. Методы состоят из последовательностей операторов, соответствующих знакомому императивному стилю, тогда как тело функции, напротив, является просто выражением. Любые побочные инструкции в методе (например, присвоение элемента параметра массива) должны быть учтены, отмечая, какие параметры могут быть изменены в предложении модифицирует
. Dafny также предоставляет ряд неизменяемых типов коллекций, включая: последовательности (например, seq
), наборы (например, set
) и карты (map
). Кроме того, предоставляются изменяемые массивы (например, array
).
Дафни поддерживает доказательства императивных программ, основанные на идеях логики Хоара. Ниже показано множество таких функций в Dafny, включая использование предварительных условий, постусловий, инвариантов цикла и вариантов цикла.
method max (arr: array) returns (max: int) // В массиве должен быть хотя бы один элемент, требуется arr! = Null arr.Length>0; // Возвращаемое значение не может быть меньше, чем гарантирует любой элемент в массиве (forall j: int :: (j>= 0 j < arr.Length ==>max>= arr [j])); // Возврат должен соответствовать некоторому элементу в массиве гарантирует (существует j: int :: j>= 0 j < arr.Length max==arr[j]); { max:=arr[0]; var i:int :=1; // while(i < arr.Length) // Indent at most arr.Length (needed to show i==arr.Length after loop) invariant (i<=arr.Length); // No element seen so far larger than max invariant (forall j:int :: j>= 0 j max>= arr [j]); // Некоторый видимый элемент соответствует максимальному инварианту (существует j: int :: j>= 0 j max) {max: = arr [i];} // Продолжаем через массив i: = i + 1; }}
В этом примере вычисляется максимальный элемент массива. Предусловие и постусловие метода задаются с помощью предложений requires
и requires
(соответственно). Аналогично, инвариант цикла и вариант цикла задаются с помощью предложений инвариант
и уменьшения
(соответственно).
Обработка инвариантов цикла в Dafny отличается от традиционной логики Хоара. Переменные, измененные в цикле, обрабатываются таким образом, что (большая часть) информации, известной о них до цикла, отбрасывается. Информация, необходимая для доказательства свойств таких переменных, должна быть явно выражена в инварианте цикла. Напротив, переменные, не измененные в цикле, сохраняют всю информацию, известную о них заранее. Следующее обеспечивает иллюстрацию:
метод sumAndZero (ns: array) возвращает (sum: nat) требует ns! = Null требуется для всех i :: 0 <= i < ns.Length ==>ns [i]>= 0 изменяет ns {var i : int: = 0; var arr: array : = ns; // потому что параметры не могут быть присвоены sum: = 0; // while (i < arr.Length) { sum := sum + arr[i]; arr[i] := arr[i]; i := i + 1; } }
Проверка не пройдена, потому что Дафни не может установить, что (sum + arr [i])>= 0
выполняется при присваивании. Из предварительного условия интуитивно понятно, что forall i: : 0 <= i < arr.Length ==>arr [i]>= 0
сохраняется в цикле, поскольку arr [i]: = arr [i];
является NOP. Однако это присвоение заставляет Дафни рассматривать arr
как изменяемую переменную и отбрасывать информацию, известную о ней до цикла. Чтобы проверить эту программу в Dafny, мы можем: удалить избыточное присвоение arr [i ]: = arr [i];
; или добавьте инвариант цикла , инвариант forall i :: 0 <= i < arr.Length ==>arr [i]>= 0
Дафни дополнительно использует ограниченный статический анализ, чтобы вывести простые инварианты цикла, где это возможно. В приведенном выше примере может показаться, что инвариант цикла инвариант i>= 0
также требуется, поскольку переменная i
изменяется внутри Цикл. Хотя базовая логика требует наличия такого инварианта, Дафни автоматически делает это, и, следовательно, его можно опустить на уровне источника.
Dafny включает функции, которые дополнительно поддерживают его использование в качестве помощника по проверке. Хотя доказательства простых свойств внутри функции
или метода
(как показано выше) не являются чем-то необычным для инструментов такого рода, Dafny также позволяет доказывать свойства между одной функцией и другое. Как обычно бывает с помощником по доказательству, такие доказательства часто имеют индуктивный характер. Дафни, возможно, необычен в использовании вызова метода в качестве механизма для применения индуктивной гипотезы. Ниже показано:
список типов данных = Nil | Link (data: int, next: List) function sum (l: List): int {match l case Nil =>0 case Link (d, n) =>d + sum (n)} predicate isNatList (l: List) {match l case Nil =>true case Link (d, n) =>d>= 0 isNatList (n)} Призрачный метод NatSumLemma (l: List, n: int) требует isNatList (l) n == sum ( l) гарантирует, что n>= 0 {соответствует l case Nil =>// Разряжается автоматически case Link (data, next) =>{// Применить индуктивную гипотезу NatSumLemma (next, sum (next)); // Проверяем, что известно Dafny assert data>= 0; }}
Здесь NatSumLemma
доказывает полезное свойство между sum ()
и isNatList ()
(то есть, что isNatList (l) ==>(сумма (l)>= 0)
). Использование метода-призрака
для кодирования лемм и теорем является стандартным в Dafny с рекурсией, применяемой для индукции (обычно, структурная индукция ). Анализ случаев выполняется с использованием операторов match
, а неиндуктивные случаи часто удаляются автоматически. Верификатор также должен иметь полный доступ к содержимому функции или
предиката
, чтобы при необходимости развернуть их. Это имеет значение при использовании вместе с модификаторами доступа. В частности, скрытие содержимого функции с помощью модификатора
protected
может ограничить, какие свойства могут быть установлены для нее.