Viper cheat sheet
This page briefly summarizes the Viper features that we have seen in the first lecture. If you have not installed Viper yet, please first follow the installation steps.
Further details about Viper are found in the official Viper tutorial. However, do not expect to understand parts about features that we have not covered yet in the course.
Viper programs are stored in .vpr
files. They can be verified with two backends: silicon
and carbon
. silicon
is the default and carbon
is close to the approach that we will follow throughout the lecture. Ideally, your programs should always verify with both backends. However, for complex examples, one backend might be significantly slower than the other.
Methods
Most Viper code must be placed in methods of the form
method <name>(<inputs>)
returns <outputs>
requires <precondition>
ensures <postcondition>
{
<statements>
}
Here, <name>
is a name that is unique for the whole Viper program.
<inputs>
is a comma-separated list of variable names and their types (so far: Int
or Bool
). For example, a list of inputs could be x: Int, y: Bool, z: Bool
. All input variables are read-only.
Analogously, <outputs>
is a comma-separated list of variable names and their types of outputs. Outputs are read-write, but initially have an unknown value. Viper does not have a return statement. The last value assigned to each output variable is returned implicitly when the method terminates.
The variable names used in inputs and outputs are not allowed to overlap.
A method can have zero or more preconditions (predicates such as x > 0 && x < n
) indicated by requires
. Preconditions may contain input variables but no output variables.
A method can have zero or more postconditions (predicates such as x > 0 && x < n
) indicated by ensures
. Preconditions may contain both input and output variables.
A method is called abstract if it has no implementation { <statements> }
.
Statements
Implementations of Viper methods consist of:
- local variable declarations
var <name>:<type>
, for examplevar x: Int
; all variable names within a Viper method (including input and output parameters) must be unique; - assignments
<var> := <expression>
, where<var>
is either an output variable or a previously declared local variable and<expression>
is a standard arithmetic expression; - assertions
assert <predicate>
, for exampleassert x > 0
, which causes an error if we can reach the assertion in a state that does not satisfy the predicate (otherwise nothing happens); - assumptions
assume <predicate>
, which let the program proceed only with states in which the predicate holds; - standard conditionals
if (<boolean expression>) { <statement> } else { <statement> }
; and - standard sequential composition
;
between statements; for statements in different lines, the semicolon can be omitted.
Example
For example, the three methods below cover all Viper features that we have encountered in the first lecture. The comments (//
) indicate the role of each line.
// every method must have a unique name
method first(x: Int, y: Int, z: Bool) // method three read-only input parameters
returns (a: Int, b: Bool) // uninitialized output parameters
requires x < y // precondition, can only refer to input parameters
requires y > 0 // another precondition
requires z == true // another precondition
ensures !b // postcondition, can refer to input and output parameters
ensures a == x + 2 * y // another precondition
{ // begin of method body
var u: Int // declaration of local integer variable
// every variable name can appear at most once in every method
u := x + y // assignment to local variable
a := u + y // assignment to output variable
// the final value of each output is returned
if (z) { // conditional
b := false // assignment to output variable of type Bool
} else {
b := true
}
} // end of method body
// An abstract method is a method without a body.
// Such methods are trusted and their verification never fails.
// By default, the input parameters are empty.
// By default, pre- and postconditions are true.
method second() returns (x: Int)
ensures x > 0
// the "returns" can be omitted if a method has no outputs
method third()
{
var a: Int
a := second() // a method call that assigns the output to a
assert a > 0 // assertion; verification fails if we cannot guarantee the condition
var b: Int
assume b > 2 // an assumption; from now on, the verifier proceeds as if b > 2
var c: Bool
// all method outputs need to be assigned during a call
// here, the first output is assigned to b and the second to c.
b, c := first(2, b, true)
}