Парадигма | Императивный , функциональный |
---|---|
Разработано | К. Рустан М. Лейно |
Разработчик | Microsoft Research , AWS [ требуется ссылка ] |
Впервые появился | 2009 ( 2009 ) |
Стабильный релиз | 3.7.2 / 14 июля 2022 г. ( 2022-07-14 ) |
Дисциплина набора текста | Статичный, сильный, безопасный |
Лицензия | Лицензия Массачусетского технологического института |
Расширения имени файла | .dfy |
Веб-сайт | dafny.org |
Dafny — это императивный и функциональный компилируемый язык , который компилируется в другие языки программирования , такие как C# , Java , JavaScript , Go и Python . Он поддерживает формальную спецификацию через предусловия , постусловия , инварианты циклов , варианты циклов , спецификации завершения и спецификации фрейминга чтения/записи. Язык объединяет идеи из функциональной и императивной парадигм; он включает поддержку объектно-ориентированного программирования . Возможности включают универсальные классы , динамическое распределение , индуктивные типы данных и вариацию логики разделения, известную как неявные динамические фреймы [1] для рассуждений о побочных эффектах. [2] Dafny был создан Растаном Лейно в Microsoft Research после его предыдущей работы по разработке ESC/Modula-3, ESC/Java и Spec#.
Dafny широко используется в обучении [ требуется ссылка ] , поскольку он обеспечивает простое, комплексное введение в формальную спецификацию и верификацию; он регулярно участвует в конкурсах по верификации программного обеспечения (например, VSTTE'08, [3] VSCOMP'10, [4] COST'11, [5] и VerifyThis'12 [6] ).
Dafny был разработан как язык программирования, поддерживающий проверку, требующий проверки вместе с разработкой кода. Таким образом, он соответствует парадигме разработки программного обеспечения «Correct by Construction». Доказательства проверки поддерживаются математическим набором инструментов, который включает в себя математические целые и действительные числа, битовые векторы, последовательности, множества, мультимножества, бесконечные последовательности и множества, индукцию, коиндукцию и вычислительные доказательства. Обязательства по проверке выполняются автоматически при наличии достаточной спецификации. Dafny использует некоторый программный анализ для вывода многих утверждений спецификации, что снижает нагрузку на пользователя по написанию спецификаций. Общая структура доказательства — это логика Хоара .
Dafny создан на основе промежуточного языка Boogie , который использует автоматизированную доказательную машину теорем Z3 для выполнения обязательств по доказательству. [7] [8]
Dafny предоставляет методы для реализации, которые могут иметь побочные эффекты , и функции для использования в спецификации, которые являются чистыми . [9] Методы состоят из последовательностей операторов, следующих знакомому императивному стилю, в то время как, напротив, тело функции представляет собой просто выражение. Любые операторы с побочными эффектами в методе (например, назначение элемента параметра массива) должны быть учтены путем указания того, какие параметры могут быть изменены, с помощью modifies
предложения. Dafny также предоставляет ряд неизменяемых типов коллекций, включая: последовательности (например, seq<int>
), наборы (например, set<int>
), карты ( map<int,int>
), кортежи, индуктивные типы данных и изменяемые массивы (например, array<int>
).
Ниже проиллюстрированы многие функции Dafny, включая использование предусловий, постусловий, инвариантов циклов и вариантов циклов.
method max ( arr : array < int > ) returns ( max : int ) // Массив должен иметь хотя бы один элемент require arr . Length > 0 // Возвращаемое значение не может быть меньше любого элемента в массиве ensure forall j : int :: j >= 0 && j < arr . Length ==> max >= arr [ j ] // Возвращаемое значение должно соответствовать некоторому элементу в массиве ensure exist j : int :: j >= 0 && j < arr . Length && max == arr [ j ] { max : = arr [ 0 ] ; var i : int : = 1 ; // while ( i < arr . Length ) // Индекс не больше arr.Length (необходимо показать i==arr.Length после цикла) инвариант i <= arr . Длина // Пока не обнаружено элементов, больших, чем max invariant forall j : int :: j >= 0 && j < i ==> max >= arr [ j ] // Пока не обнаружено элементов, соответствующих max invariant exist j : int :: j >= 0 && j < i && max == arr [ j ] // arr. Длина - i уменьшается на каждом шаге и ограничена снизу 0, уменьшается arr . Длина - i { // Обновить max, если обнаружен больший элемент if ( arr [ i ] > max ) { max : = arr [ i ] ; } // Продолжить по массиву i : = i + 1 ; } }
В этом примере вычисляется максимальный элемент массива. Предусловие и постусловие метода задаются с помощью предложений requires
и ensures
(соответственно). Аналогично, инвариант цикла и вариант цикла задаются с помощью предложений invariant
и decreases
(соответственно).
Обработка инвариантов цикла в Dafny отличается от традиционной логики Хоара . Переменные, мутирующие в цикле, обрабатываются таким образом, что (большая часть) информации, известной о них до цикла, отбрасывается. Информация, необходимая для доказательства свойств таких переменных, должна быть явно выражена в инварианте цикла. Напротив, переменные, не мутирующие в цикле, сохраняют всю известную о них заранее информацию. Следующий пример иллюстрирует использование циклов:
метод sumAndZero ( arr : array <int> ) возвращает ( sum : nat ) требует forall i :: 0 < = i < arr.Length == > arr [ i ] > = 0 изменяет arr { var i : int : = 0 ; sum : = 0 ; // while ( i < arr.Length ) { sum : = sum + arr [ i ] ; arr [ i ] : = arr [ i ] ; i : = i + 1 ; } }
Это не проходит проверку, поскольку Dafny не может установить, что (sum + arr[i]) >= 0
выполняется при назначении. Из предварительного условия, интуитивно, forall i :: 0 <= i < arr.Length ==> arr[i] >= 0
выполняется в цикле, поскольку arr[i] := arr[i];
является NOP . Однако это назначение заставляет Dafny рассматривать arr
как изменяемую переменную и удалять информацию, известную о ней до цикла. Чтобы проверить эту программу в Dafny, мы можем либо (a) удалить избыточное назначение arr[i] := arr[i];
; либо (b) добавить инвариант циклаinvariant forall i :: 0 <= i < arr.Length ==> arr[i] >= 0
Dafny дополнительно использует ограниченный статический анализ для вывода простых инвариантов цикла, где это возможно. В приведенном выше примере, кажется, что инвариант цикла invariant i >= 0
также требуется, поскольку переменная i
мутирует внутри цикла. Хотя базовая логика требует такого инварианта, Dafny автоматически выводит его, и, следовательно, его можно опустить на уровне источника.
Dafny включает в себя функции, которые дополнительно поддерживают его использование в качестве помощника доказательства . Хотя доказательства простых свойств в пределах function
или method
(как показано выше) не являются необычными для инструментов такого рода, Dafny также позволяет доказывать свойства между одним function
и другим. Как это обычно бывает с помощником доказательства , такие доказательства часто являются индуктивными по своей природе. Dafny, возможно, необычен в использовании вызова метода в качестве механизма для применения индуктивной гипотезы. Ниже приведена иллюстрация:
тип данных Список = Ноль | Ссылка ( данные : целое число , следующее : Список ) функция сумма ( l : Список ): int { match l case Nil => 0 case Link ( d , n ) => d + сумма ( n ) } предикат isNatList ( l : List ) { совпадение l case Nil => true case Link ( d , n ) => d >= 0 && isNatList ( n ) } лемма NatSumLemma ( l : List , n : int ) требует isNatList ( l ) && n == sum ( l ) гарантирует n >= 0 { match l case Nil => // Выполняется автоматически case Link ( data , next ) => { // Применяем индуктивную гипотезу NatSumLemma ( next , sum ( next )); // Проверяем, что известно Dafny assert data >= 0 ; } }
Здесь, NatSumLemma
доказывает полезное свойство между sum()
и isNatList()
(т.е. что isNatList(l) ==> (sum(l) >= 0)
). Использование a ghost method
для кодирования лемм и теорем является стандартным в Dafny с рекурсией, используемой для индукции (обычно структурной индукции ). Анализ случаев выполняется с использованием match
утверждений, а неиндуктивные случаи часто выгружаются автоматически. Проверяющий также должен иметь полный доступ к содержимому a function
или predicate
для того, чтобы развернуть их по мере необходимости. Это имеет последствия при использовании в сочетании с модификаторами доступа . В частности, сокрытие содержимого a function
с помощью protected
модификатора может ограничить то, какие свойства могут быть установлены о нем.