Files
Nim/doc/drnim.md
Andrey Makarov 417b90a7e5 Improve Markdown code blocks & start moving docs to Markdown style (#19954)
- add additional parameters parsing (other implementations will just
  ignore them). E.g. if in RST we have:

  .. code:: nim
     :test: "nim c $1"

     ...

  then in Markdown that will be:

  ```nim test="nim c $1"
  ...
  ```

- implement Markdown interpretation of additional indentation which is
  less than 4 spaces (>=4 spaces is a code block but it's not
implemented yet). RST interpretes it as quoted block, for Markdown it's
just normal paragraphs.
- add separate `md2html` and `md2tex` commands. This is to separate
  Markdown behavior in cases when it diverges w.r.t. RST significantly —
most conspicously like in the case of additional indentation above, and
also currently the contradicting inline rule of Markdown is also turned
on only in `md2html` and `md2tex`. **Rationale:** mixing Markdown and
RST arbitrarily is a way to nowhere, we need to provide a way to fix the
particular behavior. Note that still all commands have **both** Markdown
and RST features **enabled**. In this PR `*.nim` files can be processed
only in Markdown mode, while `md2html` is for `*.md` files and
`rst2html` for `*.rst` files.
- rename `*.rst` files to `.*md` as our current default behavior is
  already Markdown-ish
- convert code blocks in `docgen.rst` to Markdown style as an example.
  Other code blocks will be converted in the follow-up PRs
- fix indentation inside Markdown code blocks — additional indentation
  is preserved there
- allow more than 3 backticks open/close blocks (tildas \~ are still not
  allowed to avoid conflict with RST adornment headings) see also
https://github.com/nim-lang/RFCs/issues/355
- better error messages
- (other) fix a bug that admonitions cannot be used in sandbox mode; fix
  annoying warning on line 2711
2022-07-15 19:27:54 +02:00

5.6 KiB

=================================== DrNim User Guide

:Author: Andreas Rumpf :Version: |nimversion|

.. default-role:: code .. include:: rstcommon.rst .. contents::

Introduction

This document describes the usage of the DrNim tool. DrNim combines the Nim frontend with the Z3 <https://github.com/Z3Prover/z3>_ proof engine, in order to allow verify/validate software written in Nim. DrNim's command-line options are the same as the Nim compiler's.

DrNim currently only checks the sections of your code that are marked via staticBoundChecks: on:

.. code-block:: nim

{.push staticBoundChecks: on.}

<--- code section here ---->

{.pop.}

DrNim currently only tries to prove array indexing or subrange checks, overflow errors are not prevented. Overflows will be checked for in the future.

Later versions of the Nim compiler will assume that the checks inside the staticBoundChecks: on environment have been proven correct and so it will omit the runtime checks. If you do not want this behavior, use instead {.push staticBoundChecks: defined(nimDrNim).}. This way the Nim compiler remains unaware of the performed proofs but DrNim will prove your code.

Installation

Run koch drnim:cmd:, the executable will afterwards be in $nim/bin/drnim.

Motivating Example

The follow example highlights what DrNim can easily do, even without additional annotations:

.. code-block:: nim

{.push staticBoundChecks: on.}

proc sum(a: openArray[int]): int = for i in 0..a.len: result += a[i]

{.pop.}

echo sum([1, 2, 3])

This program contains a famous "index out of bounds" bug. DrNim detects it and produces the following error message::

cannot prove: i <= len(a) + -1; counter example: i -> 0 a.len -> 0 [IndexCheck]

In other words for i == 0 and a.len == 0 (for example!) there would be an index out of bounds error.

Pre-, postconditions and invariants

DrNim adds 4 additional annotations (pragmas) to Nim:

  • requires:idx:
  • ensures:idx:
  • invariant:idx:
  • assume:idx:

These pragmas are ignored by the Nim compiler so that they don't have to be disabled via when defined(nimDrNim).

Invariant

An invariant is a proposition that must be true after every loop iteration, it's tied to the loop body it's part of.

Requires

A requires annotation describes what the function expects to be true before it's called so that it can perform its operation. A requires annotation is also called a precondition:idx:.

Ensures

An ensures annotation describes what will be true after the function call. An ensures annotation is also called a postcondition:idx:.

Assume

An assume annotation describes what DrNim should assume to be true in this section of the program. It is an unsafe escape mechanism comparable to Nim's cast statement. Use it only when you really know better than DrNim. You should add a comment to a paper that proves the proposition you assume.

Example: insertionSort

Note: This example does not yet work with DrNim.

.. code-block:: nim

import std / logic

proc insertionSort(a: var openArray[int]) {. ensures: forall(i in 1..<a.len, a[i-1] <= a[i]).} =

for k in 1 ..< a.len:
  {.invariant: 1 <= k and k <= a.len.}
  {.invariant: forall(j in 1..<k, i in 0..<j, a[i] <= a[j]).}
  var t = k
  while t > 0 and a[t-1] > a[t]:
    {.invariant: k < a.len.}
    {.invariant: 0 <= t and t <= k.}
    {.invariant: forall(j in 1..k, i in 0..<j, j == t or a[i] <= a[j]).}
    swap a[t], a[t-1]
    dec t

Unfortunately, the invariants required to prove that this code is correct take more code than the imperative instructions. However, this effort can be compensated by the fact that the result needs very little testing. Be aware though that DrNim only proves that after insertionSort this condition holds::

forall(i in 1..<a.len, a[i-1] <= a[i])

This is required, but not sufficient to describe that a sort operation was performed. For example, the same postcondition is true for this proc which doesn't sort at all:

.. code-block:: nim

import std / logic

proc insertionSort(a: var openArray[int]) {. ensures: forall(i in 1..<a.len, a[i-1] <= a[i]).} = # does not sort, overwrites a's contents! for i in 0..<a.len: a[i] = i

Syntax of propositions

The basic syntax is ensures|requires|invariant: <prop>. A prop is either a comparison or a compound::

prop = nim_bool_expression | prop 'and' prop | prop 'or' prop | prop '->' prop # implication | prop '<->' prop | 'not' prop | '(' prop ')' # you can group props via () | forallProp | existsProp

forallProp = 'forall' '(' quantifierList ',' prop ')' existsProp = 'exists' '(' quantifierList ',' prop ')'

quantifierList = quantifier (',' quantifier)* quantifier = 'in' nim_iteration_expression

nim_iteration_expression here is an ordinary expression of Nim code that describes an iteration space, for example 1..4 or 1..<a.len.

nim_bool_expression here is an ordinary expression of Nim code of type bool like a == 3 or 23 > a.len.

The supported subset of Nim code that can be used in these expressions is currently underspecified but let variables, function parameters and result (which represents the function's final result) are amenable for verification. The expressions must not have any side-effects and must terminate.

The operators forall, exists, ->, <-> have to imported from std / logic.