VerCors Tutorial

Introduction

Welcome to the VerCors tutorial! In this tutorial, we will look at what VerCors is, what it can do, and how you can use it.

VerCors is a toolset for software verification. It can be used to reason about programs written in Java, C, OpenCL and PVL, which is a Prototypal Verification Language that is developed alongside VerCors and often used to demonstrate and test the capabilities of VerCors.

In this tutorial, we will first take a brief look at where VerCors fits into the bigger picture of software verification. As this is only a brief look, we recommend users that are unfamiliar with software verification to take a look at the chapter "Background" that gives more information on software verification and some of the terms used below. Then, we will discuss the syntax of PVL and Specifications in VerCors. Once we have a basic idea of how things work, we look at several more advanced concepts, either of VerCors (e.g. resources) or of the input languages (e.g. inheritance). You can find an overview of the chapters on the right.

VerCors

VerCors is a verification tool that uses static analysis to prove partial correctness of a given piece of code. It requires users (e.g. software developers) to add annotations in the code, which specify the expected behaviour. The chapter "Specification Syntax" of this tutorial describes the syntax for these annotations. VerCors particularly targets parallel and concurrent programs, using the permission-based Concurrent Separation Logic (CSL) as its logical foundation. CSL is a program logic that has a very strong notion of ownership in the form of (fractional) permissions: A thread can only read from, or write to, shared memory if it owns enough permission to do so. An advantage of concurrent separation logic is that, due to the explicit handling of ownership, we get properties like data-race freedom and memory safety for free; these properties are consequences of the soundness argument of the logic. A disadvange is that it causes significant overhead to always deal with permissions explicitly. Therefore, if you only wish to verify sequential algorithms, it may be worthwhile to look at alternative tools such as OpenJML and KeY, which do not use CSL as their logical foundation. For more info on CSL, ownership and permissions, see the chapter "Permissions".

VerCors' analysis is modular, proving each method in isolation: Each method has a contract specifying the pre-conditions that must be met before calling the method, and the post-conditions that are certain when the method call finishes. Analysis of a method body happens solely based on the contract, not considering any actual calling environments.

While VerCors currently supports Java, C (incl. OpenMP), OpenCL and PVL, it is designed to be extensible, in the sense that support for other input languages with parallel and concurrent language constructs (for example C#) can be added without much difficulty. For that, the aim for VerCors is to allow reasoning over many different general concurrency structures, like statically-scoped concurrency (e.g. GPU kernels), dynamically-scoped concurrency (e.g. fork/join parallelism), and automated parallelism (e.g. programs that are automatically parallelised using OpenMP).

Note that VerCors checks for partial correctness, meaning that if the program terminates, then it satisfies its post-conditions. No proof is attempted to check whether the program actually terminates.

It is worth noting that VerCors is not the only tool that can perform static verification on annotated programs; there are actually many tools that can do a very similar job. Examples of such tools are: Dafny, OpenJML, KeY, VeriFast, and VCC. However, VerCors distinguishes itself by focussing on different parallel and concurrent language constructs (e.g. Dafny, OpenJML, and KeY only allow verifying sequential programs) of various high-level programming languages (e.g. VCC only allows to verify C programs). Moreover, VerCors is not designed to be language-dependent, and instead focusses on verification techniques for general concurrency patterns.

Internally, VerCors uses the Viper backend, which in turn uses the SMT solver Z3. VerCors architecture

Background

In this chapter, we explain some of the terms related to VerCors, for example static analysis. If you are already an experienced user of software verification tools, feel free to skip it. If you are new to the topic, we recommend reading this chapter to better understand the other chapters.

Software Verification

Nowadays, we as a society rely heavily on software, and software errors can have a major impact, even causing deaths. Thus, software developers strive to reduce the number of software errors as much as possible. Software verification is the task of reasoning about the behaviour of the software, in order to ensure that it behaves correctly. The most basic case could be considered to be the compiler, which checks that the program e.g. does not misspell a name. Only if the compiler does not find any errors, then the program can be executed. However, many errors that are more intricate can slip past the compiler. To catch these, there are two possibilities: Static analysis and dynamic analysis. Dynamic analysis runs the program and watches its behaviour. One example is testing, where you provide the software with a concrete input, let it compute an output and check that it is the expected one. While this can show errors, it cannot guarantee the absence of errors: Maybe it only works for this specific input, and even that only in non-leap years. Static analysis looks at the source code itself, and can thus reason in more general terms. The compiler is part of this category, and so is VerCors.

Different tools provide different levels of analysis. As an example, let's take a division by zero, which is not allowed in mathematics and would cause the software to misbehave. In the most simple case, we could search for expressions like 1/0, which we recognise as bad immediately. But what about 1/x? Maybe x is zero, maybe not. Some tools will check the program to find all possible values that x can take. But this is often difficult to decide, and the tools often approximate (e.g. instead of saying "-1, 1 or 2", they say "the interval [-1,2]", thereby also including the value 0 even though it cannot occur in reality). This can lead to false results, e.g. complaining about programs that are actually correct. Other tools require the user (meaning the software developer) to specify which values x can take. This requires much more effort by the user, who has to annotate the code, i.e. provide additional information inside the program that is not needed to run the program, but only for analysing it. As a reward for the additional effort, the user can get more accurate results. VerCors follows this approach, requiring the user to provide specifications as annotations. The chapter "Specification Syntax" of this tutorial describes the syntax for these annotations.

Annotations can have different purposes, and two are particularly relevant: Assertions or proof obligations are properties the user wants to be proven, for instance that a function computes the correct value. Assumptions provide the tool with additional knowledge to help its proof; the tool takes them as given facts and does not prove them. A common example are method pre-conditions, where the user specifies the context in which the method is called. That way, when analysing the method itself, the tool can reason more accurately e.g. about the values of input parameters. However, that means that the analysis results is only applicable if the respective assumptions are met. So users (particularly new users) should pay attention what facts were actually proven, and what was assumed, and be aware under which conditions the analysis result holds.

As an example, consider the following code:

int foo(int arg) {
  return 10/arg;
}

If we assume that the method is only invoked with arguments arg>0, then we can assert (or prove) that no division by zero occurs, and even that the result is between 0 and 10. Note that if the assumption is unsatisfiable (e.g. two contradictory conditions, or a simple false), then we can derive any boolean statement, because the implication "if assumption then statement" is trivially true. Thus, users must be careful in their choice of assumptions.

Separation Logic

VerCors particularly targets parallel and concurrent programs, as they are more difficult to understand intuitively and thus are more error-prone than sequential programs. One typical example is two parts of the program accessing the same memory location at the same time. This can lead to the unintuitive fact that, right after one thread wrote a value to a variable, that variable might already have an entirely different value due to the other thread jumping in between and changing it. To avoid one thread invalidating the properties and invariants maintained and observed by the other threads, VerCors uses Permission-Based Separation Logic (PBSL) as its logical foundation. PBSL is a program logic that has a very strong notion of ownership in the form of (fractional) permissions: A thread can only read from, or write to, shared memory if it owns enough permission to do so. So just because a variable is on the heap, shared and technically accessible by everyone, that does not mean that just anyone can go ahead and use it; they need to coordinate with the others by getting permission first. The specification language of VerCors has constructs to deal with ownership and permissions (see the chapter "Permissions"). An advantage of this approach is that, due to the explicit handling of ownership, we get properties like data-race freedom and memory safety for free; these properties are consequences of the soundness argument of the logic. You will notice that the handling of permissions makes up a significant part of the verification effort, and "Insufficient Permissions" is a frequent complaint by VerCors in the beginning. And while VerCors can be used to analyse sequential programs, it always requires this overhead of managing permissions. Therefore, if you only wish to verify sequential algorithms, it may be worthwhile to look at alternative tools such as OpenJML and KeY, which do not use PBSL as their logical foundation.

Installing and Running VerCors

The installation and running instructions can be found on the Github homepage of this project.

Syntax highlighting

When writing a program in PVL, the Prototypal Verification Language of VerCors, syntax highlighting can be obtained in the following way:

VerCors provides syntax highlighting support for PVL in TextMate 2 (MacOS X) and Sublime Text (Linux and Windows) as a TextMate Bundle. The bundle is located at ./util/VercorsPVL.tmbundle. On MacOS X for TextMate 2 you can simply double click the .tmbundle file to install it. Sublime Text requires you to copy the bundle content to some directory:

  1. In Sublime Text, click on the Preferences > Browse Packages… menu.
  2. Create a new directory and name it VercorsPVL.
  3. Move the contents of VercorsPVL.tmbundle (that is, the ./Syntaxes directory) into the directory you just created.
  4. Restart Sublime Text.

Visual Studio Code (VS Code) also has support for TextMate bundles to do syntax highlighting (VS Code runs on Windows, Linux and OSX). Click here for instructions on how to install this (this has not been tested however).

Prototypical Verification Language

This page discusses the language features of PVL, the Prototypal Verification Language of VerCors. The language is similar to Java, so it has classes, methods, etc. It doesn't have modifiers like public and private. This language is used for research too, so some special constructs like the par-block have been added to it. This section elaborates on the basic language features of PVL. The more advanced features are described in later sections. A complete reference overview of the PVL language and specification syntax can be found here.

Basic types and expressions

Currently, VerCors supports the types int, boolean, and void (for return types of methods). Identifiers, e.g. variables and method names, can consist of the characters a-z, A-Z, 0-9 and _. However, they must start with a letter (a-z, A-Z). The following words are reserved and can therefore not be used as identifiers:

create, action, destroy, send, recv, use, open, close, atomic, from, merge, split, process, apply, label, \result, write, read, none, empty, current_thread

Standard operators can be used to form expressions from values and variables of type int or boolean:

Other expressions:

Classes, fields, methods

A program consists of one of more classes. Classes have a name, zero or more fields, zero or more constructors, and zero or more methods. Below we show a small example class:

class MyForest {
    int nrTrees;
    
    MyForest(int nr) {
        nrTrees = nr;
    }

    void plantTrees(int nr) {
        nrTrees = nrTrees + nr;
    }
}

The keyword this can be used to refer to the current object. The modifier static can be added to methods that do not change any fields or call any non-static methods.

Control flow: return, if, while, for

A method body consists of statements. The basic statements of PVL are:

Below we show a method using these constructs:

int myExampleMethod(int nr) {
    nr = nr + 3;
    if(nr > 10) { /* here is a multi-line comment
                     in the if-branch */
        nr = nr-3;
        for(int i = 0; i < 2 && nr > 5; i++) {
            nr = nr/2;
        }
    } else { //we subtract a bit
        while (nr > 2) {
            nr--;
        }
    }
    return nr + 5;
}

Specification Syntax

This section describes the syntax of the specification language, which is independent of the target language. Thus, unless otherwise stated, all features are supported for all input languages.

In this part of the tutorial, we will take a look at how specifications are expressed in VerCors. While this tutorial provides more detailed explanations of the various constructs, a concise list can be found in the PVL Syntax Reference in the annex. The specification language is similar to JML.

Depending on the input language, specification are embedded into the target code in two different ways: In most languages, such as Java and C, the specifications are provided in special comments. These special comments are either line comments starting with //@, or block comments wrapped in /*@ and @*/. Since these are simply a kind of comment, regular compilers ignore them and can still compile the program. However, the @ signals VerCors to interpret them as annotations. Note that this style of comment comes from JML, so VerCors is not the only tool using them. However, the exact interpretation of the comments may vary between tools, so a valid specification in VerCors may not be recognised by another tool and vice versa.

As an example, a method pre-condition (see following section) can look like this:

//@ requires x>0;
public int increment(int x) {...}

/*@
requires x>0;
requires y>0;
@*/
public int add(int x, int y) {...}

Tip Always remember to place the @! It can be aggravating to spend significant time debugging, just to realise that parts of the specification were put in regular comments and ignored by the tool.

For PVL, the specifications are inserted directly into the code, without the special comments around them:

requires x>0;
public int increment(int x) {...}

requires x>0;
requires y>0;
public int add(int x, int y) {...}

Given that the syntax is otherwise identical, we will use the commented version in the rest of the tutorial, to make the separation between specifications and target code more obvious. The examples in this chapter are in Java, but you can find the respective examples in other languages on the website (as well as a Java file of all the examples below).

Most annotation constructs have to end with a semicolon.

In principle, we can distinguish two kinds of annotations: Specifications in the form of method contracts that specify the observable behaviour of the respective method; and auxiliary annotations that guide the prover towards its goal and are for example used inside a method's body, or to define helper functions. We will first look at the former, then at the latter, and conclude with a description of the kind of expressions that VerCors supports (e.g. boolean connectives). For the earlier parts, it suffices to know that expressions in VerCors specifications are an extension of whatever expressions the target language supports.

Method Contracts

Method contracts specify the behaviour of the method that is visible to the calling environment, by means of pre-conditions and post-conditions. Pre-conditions use the keyword requires and restrict the situations in which the method can be invoked, e.g. restricting parameters to be non-null. Post-conditions use the keyword ensures and describe the behaviour of the method by defining the program state after the call finishes. The method contract is placed above the method header, and these keywords can only be used in combination with a method header.

Two useful keywords to define the post-state (i.e. the state after the method finishes) are \result to refer to the return value of the (non-void) method, and \old(expr) to refer to the value of expr before the method's execution.

  /*@
  requires x >= 0;
  requires y >= 0;
  ensures \result == x+y;
  ensures \result >= 0;
  @*/
  public int add(int x, int y) {
    return x+y;
  }

  /*@
  requires xs != null;
  ensures \result != null;
  ensures \result.length == \old(xs.length)+1;
  ensures \result.length > 0;
  @*/
  public int[] append(int[] xs, int y) {
    ...
  }
}

Recall that VerCors analyses methods in isolation, purely based on their contract and independent from any actual calling contexts. It tries to prove that if the pre-condition is satisfied before the method body is executed, then the post-condition holds afterwards. In the example of the add method above, if the input parameters are non-negative, then the result is, too. Note that the pre-condition x>=0 is not actually required for executing the method body, but it is necessary to prove the post-condition. In contrast, in the example of append, the pre-condition xs!=null is actually needed for \old(xs.length) to be well-defined, and potentially to prove absence of null-pointer dereferences in the method body. Note that, to fully specify the correct behaviour of append, one would have to compare the values inside of xs and \result. Since these values are stored on the heap (and not on the stack like the reference xs itself), this would require access permissions, which will be introduced in the next chapter "Permissions".

Note Method contracts must not have side effects, so for example calls to (non-pure) methods are forbidden inside contracts. Pre- and post-conditions are processed in-order, so for example swapping the order of two pre-conditions could result in a failed verification (e.g. you need to specify that an integer variable is within range before you can use it as an array index in another pre-condition). Note that unsatisfiable pre-conditions (e.g. contradictory conditions, or simply false) can lead to unexpected results, because the implication "if pre-condition before, then post-condition after" becomes trivially true for any post-condition, and VerCors is able to prove any arbitrary statement.

Sometimes, the same expression is needed as a pre- and as a post-condition, for example the fact that a global variable is not null. In that case, the keyword context can be used as a short-hand notation: context xs != null; stands for requires xs!=null; ensures xs!=null;. An even further reaching short-hand is context_everywhere expr;, which adds expr as a pre- and as a post-condition, as well as a loop invariant for all loops inside the method body (see below).

Auxiliary Annotations

When method bodies become more complicated than the toy examples above, it becomes more difficult for VerCors to prove by itself that the post-conditions hold after executing the method, and additional annotations are needed inside the method body to guide the verification. These can take the form of loop invariants, assumptions and assertions, or ghost code. Ghost code can also occur outside of methods, for instance to define helper functions.

Loop Invariants

Loop invariants are similar to the context in a method contracts, but for loops: They specify expressions that must hold before entering the loop (like a pre-condition), as well as after each loop iteration, incl. the last iteration (like a post-condition). Loop invariants use the keyword loop_invariant and are placed above the loop header:

//@ requires a>0 && b>0;
//@ ensures \result == a*b;
public int mult(int a, int b) {
  int res = 0;
  //@ loop_invariant res == i*a;
  //@ loop_invariant i <= b;
  for (int i=0; i<b; i++) {
    res = res + a;
  }
  return res;
}

Let us examine why this program verifies (you can skip this paragraph if you are familiar with software verification and loop invariants): The first loop invariant holds before we start the loop, because we initialise both i and res to zero, and 0 == 0*a is true. Then in each iteration, we increase i by one and res by a, so if res == i*a held before the iteration, then it will also hold afterwards. Looking at the second invariant, we see that it holds before the loop execution because i is initialised to zero and b is required to be greater than zero. To understand why the invariant holds after each iteration, we also have to consider the loop condition, i<b. This condition has to be true at the beginning of the iteration, otherwise the loop would have stopped iterating. Since i and b are integers and i<b at the beginning of the iteration, an increment of i by one during the iteration will ensure that at the end of the iteration, i<=b still holds. Note that if i or b were floating point numbers, i might have gone from b-0.5 to b+0.5, and we would not have been able to assert the loop invariant. Likewise, any increment by more than one could step over b. Only the combination of all these factors lets us assert the invariant at the end of the loop iteration. When the loop stops iterating, we know three things: Each of the two loop invariants holds, and the loop condition does no longer hold (otherwise we would still be iterating). Note that the combination of the second loop invariant and the negated loop condition, i<=b && !(i<b), ensures that i==b. Combining that with the first invariant ensures the post-condition of the method.

Remember that VerCors checks for partial correctness, so there is no check whether the loop actually stops iterating at some point. It just asserts that if the loop stops, then the post-condition holds.

As mentioned above, context_everywhere expr; in the method contract will implicitly add expr as a loop invariant to all loops in the method body. This can be useful for expressions that hold for the entirety of the method, e.g. in the case of xs!=null; but it can also be a subtle cause for errors if it adds invariants to a loop that were not intended there (especially in the case of multiple loops). For example, a method to insert an element into a sorted array may add the new value at the end, and then step by step restore the ordering of the array; in that case, adding a sortedness property as context_everywhere would not verify.

Assumptions and Assertions

Sometimes, VerCors has trouble deriving a post-condition from the given code and established facts (like pre-conditions), and it requires explicitly specifying an intermediate step to guide the verification. VerCors first proves these intermediate steps, and then can use them to derive the desired facts. This can be done with the keyword assert:

//@ ensures a==0 ? \result == 0 : \result >= 10;
public int mult10(int a) {
  int res = a;
  if (res < 0) {
    res = 0-res;
  }
  //@ assert res >= 0;
  return 10*res;
}

(Note that VerCors would be able to verify this example even without the assertion, but this demonstrates how to use assert.)

Tip Assertions can also be useful for debugging: If VerCors fails to prove the post-condition, adding assert statements throughout the method body can help to pinpoint where the verification fails, either because of a shortcoming of VerCors or because of an actual error in the code. Additionally, assert false; should always fail, so if the verification does not seem to terminate, adding such statements throughout the method can show where VerCors gets stuck. Finally, remember that a contradiction e.g. in the pre-conditions can imply anything; even false. So assert false; can be used to check that no such contradiction occurred: If it verifies, something went seriously wrong.

While assertions add additional proof obligations, assumptions introduce additional facts that VerCors just takes for granted afterwards, similar to pre-conditions. This is done via the keyword assume. This can be dangerous if the assumptions contradict the actual state of the program:

int x = 2;
//@ assume x == 1;
int y = x + 3;
//@ assert y == 4;

This snippet verifies, even though the actual value of y at the end is 5. However, based on the (wrong) assumptions that x==1 on Line 2, the assertion holds. Therefore, assumptions should be used with caution!

Nevertheless, assumptions can be useful, e.g. for debugging purposes. Also, while still being in the process of verifying the code, it can be useful to assume certain basic facts to prove the bigger picture, and then drill deeper and prove that the assumed facts actually hold.

VerCors also supports a refute statement, which is the opposite of assert. Internally, refute expr; is transformed into assert !(expr);, i.e. asserting the negation. Note that a failing assert expr; is not equivalent to a successful refute expr;. For example, if we know nothing about the value of a variable a, then both assert a>0; and refute a>0; will fail, as a could be greater than zero, but it also could be less.

Ghost Code

Recall that annotations must never have side-effects on the program state, otherwise the results of methods would be different between VerCors (which takes the annotations into account) and the compiler (which treats them as comments). However, it can be useful to extend the program state, for example introducing additional helper variables to keep track of intermediate results. This helper code, which only exists for the purpose of verification, is called ghost code, and the respective variables form the ghost state.

Ghost code can occur inside method bodies, but also outside, for instance as global ghost variables. In principle, ghost code can be any construct that the target language supports; for example when verifying Java programs, you could define ghost classes, and in C programs, you may use pointers in ghost code. Additionally, the ghost code can use the extensions that the specification language adds to the target language, such as the new type seq<T> (see section "Expressions" below).

Keep in mind that ghost code does not exist for the compiler, so it cannot have side effects on the program state, and "real" code cannot refer e.g. to ghost variables. However, it is possible to read from "regular" variables (e.g. to create a ghost variable with the same value).

When using constructs from the target language (such as variable declarations), the keyword ghost is required before the use of the construct.

Here is an example for ghost code inside a method:

/*@
  requires x>=0 && y>=0;
  ensures \result == (x<y ? 5*x : 5*y);
@*/
public void minTimes5(int x, int y) {
  //@ ghost int min = (x<y ? x : y);
  int res = 0;
  //@ loop_invariant i<=5;
  //@ loop_invariant res == i*min;
  //@ loop_invariant min<=x && min<=y && (min==x || min==y); 
  for (int i=0; i<5; i++) {
    if (x<y) {
      res = res + x;
    } else {
      res = res + y;
    }
  }
  /*@ 
  ghost if (x<y) {
    assert res == 5*x;
  } else {
    assert res == 5*y;
  }
  @*/
  return res;
}

Note the definition of a ghost variable min at the beginning of the method, and the ghost if statement at its end.

Ghost Methods and Functions

You can use ghost code not only within methods but also to declare entire ghost methods:

/*@
requires x > 0;
ensures \result == (cond ? x+y : x);
ghost static int cond_add(bool cond, int x, int y) {
  if (cond) {
    return x+y;
  } else {
    return x;
  }
}
@*/

//@ requires val1 > 0 && val2>0 && z==val1+val2;
void some_method(int val1, int val2, int z) {
  //@ ghost int z2 = cond_add(val2>0, val1, val2);
  //@ assert z == z2;
}

The conditional addition cond_add is defined as a ghost method. Otherwise, it looks like any normal method, including having a method contract (in this case, just a single precondition). Note that the precondition is not wrapped in the special comment //@, like the precondition of some_method is, because the entire ghost method already is inside a special comment /*@. We then use this ghost method in the body of some_method (but only inside annotations!).

Pure Functions and Methods

Pure functions are, in a way, a special kind of ghost methods. They look like any method, but have an additional keyword pure in the method head and their body is a single expression following an =, rather than a block of statements:

/*@
requires x > 0;
static pure int cond_add(bool cond, int x, int y) 
  = cond ? x+y : x;
@*/

Note that the ghost keyword is not used: It is only required when using a construct from the target language (e.g. if, variable declarations, etc), not for specification-internal constructs like defining a pure function. However, using a stand-alone call statement to call the function (e.g. invoke a lemma directly and not in an assert) still needs the ghost keyword, as method calls are constructs from the target language: //@ ghost myLemma();. The important distinction to normal methods, apart from the fact that they have just one expression, is that pure functions must be without side-effects, even on the ghost state. Thus, the body cannot contain for instance calls to non-pure methods.

Remember that in VerCors, only the contract of a method is used to reason about the behaviour of a method call, the actual behaviour of the method body is not taken into account at this point. For functions, this restriction is not as strict: For simple functions like the one above, VerCors actually uses the "real" behaviour of the function to analyse the behaviour of a call to that function. Thus, the behaviour does not need to be specified explicitly in a post-condition, like it does for other methods. However, this only works for simple functions, and for example a recursive function may still need a post-condition specifying its behaviour.

You can also add the pure keyword to a "real" method. This indicates that the method does not have side-effects, and can therefore be used e.g. in method contracts, while still being accessible by the "real" code. However, for a method to be pure, it has to be actually without side-effects, therefore only a limited number of constructs are allowed in methods that are designated pure: if statements, return statements and variable declarations.

/*@
requires x > 0;
@*/
static /*@ pure @*/ int cond_add(boolean cond, int x, int y) {
  if (cond) {
    return x+y;
  } else {
    return x;
  }
}
Abstract Methods and Functions

Ghost functions and methods do not require a body. Sometimes, you are only interested in the assertions that a function or method gives in its post-condition, and do not care about the actual implementation. In such a case, you can omit the body, turning the method or function abstract. Given that method calls are usually reasoned about only based on the contract, the call site does not see a difference between an abstract method and a "real" method. However, this removes the assurance that it is actually possible to derive the post-condition from the pre-condition by some computation. Consider a post-condition false: The call site will simply assume that the method establishes its post-condition, and treat it as a known fact. Normally, verifying the method body will check that the post-condition is actually fulfilled; but for an abstract method, this check is missing. Since false implies everything, this can easily lead to unsoundness. Therefore, from the perspective of correctness, it is always better to have a body proving that the post-condition can actually be established based on the pre-condition.

However, making a method abstract relieves VerCors from the effort to check that the method body actually adheres to the contract. Therefore, it can be an interesting option to speed up the re-run of an analysis: Once you proved that a method can indeed derive its post-condition, you can turn the method abstract and focus on other parts of the code without checking this method every time you re-run the analysis.

Syntactically, an abstract method or function has a semicolon in place of its body:

//@ requires a>0 && b>0;
//@ ensures \result == a*b;
public int mult(int a, int b);
// commented out body for the sake of speeding up the analysis:
//{
//  int res = 0;
//  //@ loop_invariant res == i*a;
//  //@ loop_invariant i <= b;
//  for (int i=0; i<b; i++) {
//    res += a;
//  }
//  return res;
//}

/*@
requires a>0 && b>0;
ensures \result == a+b;
public pure int add(int a, int b);
@*/

Note that VerCors can also work with abstract methods that are not ghost methods (like mult in the example above), but your compiler may complain about missing method definitions.

Inline Functions

Inline functions are like macros in C: You can write an expression that occurs frequently in your code as a macro and then use it as if it were a function; but before verifying the program, VerCors replaces every call to the inline function with the function's body as if you had written out the body in place of the function call. Therefore, during the analysis, the "real" behaviour of the function is taken into account, rather than just the specification of the function's contract. However, inline functions are only possible if the body of the function is simple enough; for example a recursive function cannot be used in that way, otherwise there would be an infinite loop of replacing a function call with the body, which again contains a call. Inline functions are declared using the inline keyword.

//@ pure inline int min(int x, int y) = (x<y ? x : y);

Note that the inline keyword is also used for inline predicates (see chapter "Predicates"), which are a similar concept.

Ghost Parameters and Results

You can extend the header of an existing method, by adding additional parameters and return values to a method. This is done by using the given and yields keywords in the method contract, respectively. To assign and retrieve the values when calling the method, use with and then, respectively:

/*@
given int x;
given int y;
yields int modified_x;
requires x > 0;
ensures modified_x > 0;
@*/
int some_method(boolean real_arg) {
  int res = 0;
  ...
  //@ ghost modified_x = x + 1;
  ...
  return res;
}

void other_method() {
  //@ ghost int some_ghost;
  int some_result = some_method(true) /*@ with {y=3; x=2;} then {some_ghost=modified_x;} @*/;
}

There are several points of interest in this example: Note that the pre- and post-condition of some_method can reference the ghost parameter and result just like normal parameters. However, as stated before, the method contract is processed in the order it is given, so the given int x; must appear before the requires that mentions x. If the ghost parameters and results are not just primitive integers, but heap objects, then permissions are needed to access them, just like with normal parameters and results (see following chapter). There is no explicit return statement for the ghost result; instead, the ghost result is whatever value the variable has when the method returns. Therefore, make sure when your method returns that you always have a defined value assigned to your ghost result variable! In other_method, the with keyword is followed by a block of statements that assign values to the ghost parameters. The parameters are named, so the assignment can be in any order, and need not adhere to the order in which the ghost parameters are defined. Make sure to assign a value to each ghost parameter! Otherwise, the method will be missing a parameter, just as if you called a method void foo(int x) as foo();. Similarly, the then keyword is followed by a block of statements that can use the ghost result, in particular storing their value in local variables. The respective local variable, some_ghost, must be a ghost variable, otherwise you would affect the "real" program state from inside an annotation. Both the with and the then are placed in a specification comment between the method call and the respective semicolon.

Expressions

As mentioned above, expressions in specifications are an extension of the expressions available in the target language. So if you analyse a Java program, you can use expressions like a&&b or x==y+1 in the specifications just like in Java. However, specifications must be free of side-effects (on the program state) and for example calls to non-pure methods are not allowed.

VerCors extends the expressions of the target language by a few more features that you can use in specifications. One of them is the implication operator ==>, which works like you would expect from a logical implication. A common usage is requires x!=null ==> <some statement about fields of x>. Note that the implication binds less than equality or conjunction, so a==>b&&c is equivalent to a==>(b&&c). You need to explicitly use parentheses if the operators shall associate differently.

A related new operator is the single arrow ->. It can be used as expr->fun(args), which is a shorthand for expr!=null ==> expr.fun(args). One use-case, where this comes in handy, is when using predicates in the place of the function fun (see chapter "Predicates").

Two other new operators are ** and -* from separation logic. ** is the separating conjunct, while -* is the separating implication (or "magic wand"). For more on this, see the chapters on Permissions and Magic Wands.

The target language is also extended to include new data types. A simple case is the boolean type bool, which can be useful in specifications if the target language has no boolean type (e.g. old C). If the target language does support boolean (e.g. Java), this is not needed (but can be used nonetheless). More interestingly, the new types include generic axiomatic data types such as set<T> and option<T> (with T being a type). For more information on them and their supported operations (such as getting the size, and indexing elements), see the respective chapter.

An important new type are fractions, frac. VerCors uses concurrent separation logic (CSL) to manage the ownership and permissions to access heap locations. A permission is a value from the interval [0,1], and the type frac represents such a value. To give a value to a variable of type frac, the new operator of fractional division can be used: While 2/3 indicates the classical integer division, which in this example gives 0, using the backslash instead gives a fraction: 2\3. For more on this topic, including some additional keywords for short-hand notations, see the chapter "Permissions".

Sometimes, you might create complicated expressions and want to use helper variables to simplify them. However, certain constructs only allow for expressions, and not for statements such as variable declarations. To alleviate that, there is the \let construct, which defines a variable just for a single expression: ( \let type name = expr1 ; expr2 ), where type is the type of the helper variable, name its name, expr1 defines its value, and expr2 the complicated expression that you can now simplify by using the helper variable. Example:

//@ assert (\let int abs_x = (x<0 ? -x : x); y==(z==null ? abs_x : 5*abs_x));

Quantifiers

Note that most target languages, such as Java and C, support array types, such as int[]. Sometimes, you might want to reason about all elements of the array. To do that, VerCors supports using quantifiers in the specifications: (\forall varDecl; cond; expr). The syntax is similar to the header of a for loop: varDecl declares a variable (e.g. int i); cond is a boolean expression describing a boundary condition, restricting the declared variable to the applicable cases (e.g. defining the range of the integer 0<=i && i<arr.length); and expr is the boolean expression you are interested in, such as a statement you want to assert. Note that the parentheses are mandatory. Here is an example to specify that all elements in the given array are positive:

//@ requires arr != null;
//@ requires (\forall int i ; 0<=i && i<arr.length ; arr[i]>0);
void foo(int[] arr) {...}

Note that in practice, you would also have to specify permissions to access the values in the array. More on that in the next chapter.

Tip If you want to quantify over more than one variable (e.g. saying arr1[i] != arr2[j] for all i and j), use nesting: (\forall int i; 0<=i && i<arr1.length; (\forall int j; 0<=j && j<arr2.length; arr1[i]!=arr2[j])).

If your boundary condition is an interval (like in the examples above), you can use the shorter notation (\forall type name = e1 .. e2 ; expr), where type is a type that supports comparison with < (e.g. integer, fraction), name is the name of the quantified variable, e1 and e2 are expressions defining the interval bounds (lower bound inclusive, upper bound exclusive) and expr is the expression you are interested in: (\forall int i = 0 .. arr.length ; arr[i]>0). Note that, depending on the circumstances, spaces are necessary around the ...

There also is an \exists quantifier analogous to the forall quantifier: (\exists varDecl; cond; expr). For instance, we could use a similar example as above, but requiring that at least one array element is positive:

//@ requires (\exists int i ; 0<=i && i<arr.length ; arr[i]>0);
void foo(int[] arr) {/*...*/}

Note \forall quantifiers are more easy to reason about than \exists, because they can be applied indiscriminately whenever an element is encountered, while \exists needs to find the element to which it can be applied. Therefore, \exists quantifiers are more likely to cause problems with VerCors (e.g. non-termination of the analysis), and they should be used with care!

Triggers

Note: This an advanced concept and new users may skip this section.

A quantifier (\forall int i = 0 .. arr.length ; arr[i]>0) is a rather generic piece of knowledge; to apply it to a concrete case, for example when encountering arr[1], the quantifier must be instantiated. This basically replaces the quantified variable(s), in this case i, with concrete values. But this is only done when necessary, so when the concrete case arr[1] is actually encountered. Recognising that the quantifier must be instantiated was fairly easy in this case, but for more complex expression it can become rather difficult. In those cases, VerCors might use heuristics, and even randomisation. This can lead to VerCors verifying a program successfully, and when you call it again with the exact same program, the analysis takes forever. So if you experience such behaviour, quantified expressions are a likely cause.

To avoid that, you can explicitly tell VerCors what kind of expression should cause instantiating the quantifier, disabling the internal heuristics. This is called a trigger (or pattern, e.g. by Z3); for more information see the Viper tutorial on triggers. In VerCors, to mark a part of an expression as a trigger, it is enclosed in {: and :}:

//@ requires arr!=null && arr.length>3;
//@ requires (\forall int i ; 0<=i && i<arr.length ; {: arr[i] :} > 0);
void foo(int[] arr) {
  assert arr[3]>0;   // the trigger recognises "arr[3]" and instantiates the quantifier, setting i to 3
}

(Again, Permissions were omitted.)

Note that the trigger must involve all quantified variables. So if you have a nested quantifier (\forall int i; ... ; (\forall int j; ... ; expr)), a trigger in expr must be about both i and j. Also note that you cannot use complex expressions like arithmetic expressions or relations as triggers. For instance in the example above, {: arr[i]>0 :} would not be a valid trigger.

Warning A wrong choice of triggers can lead to failed verifications of true statements:

/*@ pure @*/ int f(int a, int b);
    
//@ requires x>0;
//@ requires (\forall int k ; 0<=k && k<=x ; {: f(k, y) :} == 0);
//@ requires (\forall int k ; 0<=k && k<=x ; {: f(k, y) :} == 0 ==> f(k, z) == 0);
void bar(int x, int y, int z) {
    //@ assert f(x, y) == 0;     // this assertion verifies, because "f(x,y)" triggers the first quantifier
    //@ assert f(x/2, z) == 0;   // this assertion fails, even though the quantifiers includes this knowledge
}

In this example, the first quantifier asserts that f(x/2, y) == 0. From that, the second quantifier could derive f(x/2, z) == 0, so the second assertion should hold. However, the second quantifier has no trigger for f(k,z), so the quantifier does not get instantiated and the knowledge remains hidden. Removing the trigger around f(k,y) in the second quantifier leads to a successful verification, because without explicit triggers, VerCors' heuristics finds the correct trigger f(k,z) automatically.

Permissions

This feature is supported for all languages.

This section discusses the basics of handling ownership using a simple toy example. Ownership is used to express whether a thread (or synchronization object) has access to a specific element on the heap. This access can be shared among multiple threads (or synchronization objects), which allows the threads to read the value of this element, or it can be unique to one, in which case the value can written to or read from. Permission annotations are used to express ownership. We start by considering the following simple example program, written in Java:

class Counter {
  public int val;

  void incr(int n) {
    this.val = this.val + n;
  }
}

If you wish, you can store this file as, say, counter.java, and try to run VerCors on this file by running vct --silicon counter.java in a console (assuming you have VerCors installed). This program currently does not contain any annotations, but we will eventually annotate the program to verify the following simple property: after calling the incr function, the value of val has been increased by an amount n. This can be expressed as a postcondition for the incr method: ensures this.val == \old(this.val) + n.

However, if you run VerCors on this example, as it is now, you will see that VerCors fails to verify the correctness of the program and reports an 'AssignmentFailed: InsufficientPermission' error, since the caller of the method has insufficient permission to access this.val. First observe that this.val is shared-memory; there may be other threads that also access the val field, since the field is public. In order to prevent other threads from accessing this.val while the calling thread is executing incr, we need to specify that threads may only call c.incr (on any object c that is an instance of Counter) when they have write permission for c.val:

class Counter {
  public int val;

  /*@
    requires Perm(this.val, 1);
    ensures Perm(this.val, 1);
    ensures this.val == \old(this.val) + n;
  */
  void incr(int n) {
    this.val = this.val + n;
  }
}

We added three things to the counter program. The first is a requires clause, which is a precondition expressing that incr can only be called when the calling thread has permission to write to val. The second is an ensures clause, which is a postcondition expressing that, given that the incr function terminates (which is trivial in the above example), the function returns write permission for val to the thread that made the call to incr. The third is a postcondition that states that after incr has terminated, the value of this.val has indeed been increased by n. If you run this annotated program with VerCors, you will see that it now passes. The remainder of this section mostly focuses on how to use the Perm ownership predicates.

Observe that the clause Perm(this.val, 1) expresses write permission for this.val. Recall that VerCors has a very explicit notion of ownership, and that ownership is expressed via fractional permissions. The second argument to the Perm predicate is a fractional permission; a rational number q in the range 0 < q <= 1. The ownership system in VerCors enforces that all permissions for a shared memory location together cannot exceed 1. This implies that, if some thread has permission 1 for a shared-memory location at some point, then no other thread can have any permission predicate for that location at that point, for otherwise the total amount of permissions for that location exceeds 1 (since fractional permissions are strictly larger than 0). For this reason, we refer to permission predicates of the form Perm(o.f, 1) as write permissions, and Perm(o.f, q) with q < 1 as read permissions. Threads are only allowed to read from shared memory if they have read permission for that shared memory, and may only write to shared memory if they have write permission. In the above example, the function incr both reads and writes this.val, which is fine: having write permission for a field implies having read permission for that field.

Let us now go a bit deeper into the ownership system of VerCors. If one has a permission predicate Perm(o.f, q), then this predicate can be split into Perm(o.f, q\2) ** Perm(o.f, q\2). Moreover, a formula of the form Perm(o.f, q1) ** Perm(o.f, q2) can be merged back into Perm(o.f, q1 + q2). For example, if we change the program annotations as shown below, the program still verifies successfully:

/*@
  requires Perm(this.val, 1\2) ** Perm(this.val, 1\2);
  ensures Perm(this.val, 1\2) ** Perm(this.val, 1\2);
  ensures this.val == \old(this.val) + n;
*/
void incr(int n) {
  this.val = this.val + n;
}

For splitting and merging we use the ** operator, which is known as the separating conjunction, a connective from separation logic. A formula of the form P ** Q can be read as: "P, and separately Q", and comes somewhat close to the standard logical conjunction. In essence, P ** Q expresses that the subformulas P and Q both hold, and in addition, that all ownership resources in P and Q are together disjoint, meaning that all the permission components together do not exceed 1 for any field. Consider the formula Perm(x.f, 1) ** Perm(y.f, 1). The permissions for a field f cannot exceed 1, therefore we can deduce that x != y.

One may also try to verify the following alteration, which obviously does not pass, since write permission for this.val is needed, but only read permission is obtained via the precondition. VerCors will again give an 'InsufficientPermission' failure on this example.

/*@
  requires Perm(this.val, 1\2);
  ensures Perm(this.val, 1\2);
  ensures this.val == \old(this.val) + n;
*/
void incr(int n) {
  this.val = this.val + n;
}

If you replace both ownership predicates for Perm(this.val, 3/2), then the tool will report a 'MethodPreConditionUnsound: MethodPreConditionFalse' error because the precondition can then never by satisfied by any caller, since no thread can have permission greater than 1 for any shared-memory location. VerCors is a verification tool for partial correctness; if the precondition of a method cannot be satisfied because it is absurd, then the program trivially verifies. To illustrate this, try to change the precondition into requires false and see what happens when running VerCors. Note that VerCors does try to help the user identify these cases by showing a 'MethodPreConditionUnsound' if it can derive that the precondition is unsatisfiable. But one has to be a bit careful about the assumptions made on the program as preconditions.

Self-framing

Consider the following variant on our program. Would this verify?

/*@
  requires Perm(this.val, 1);
  ensures this.val == \old(this.val) + n;
*/
void incr(int n) {
  this.val = this.val + n;
}

This program does not verify and gives an 'InsufficientPermission' failure when given to VerCors. The reason is that, also in the specifications one cannot read from shared-memory without the required permissions. In this program, the ensures clause accesses this.val, however no ownership for this.val is ensured by the incr method. Note that, without a notion of ownership, one cannot establish the postcondition: perhaps some other thread changed the contents of this.val while evaluating the postcondition. By having a notion of ownership, no other thread can change the contents of this.val while we evaluate the postcondition of the call, since no other threads can have resources to do so.

Moreover, the order of specifying permissions and functional properties does matter. For example, the following program also does not verify, even though it ensures enough permissions to establish the postcondition:

/*@
  requires Perm(this.val, 1);
  ensures this.val == \old(this.val) + n;
  ensures Perm(this.val, 1);
*/
void incr(int n) {
  this.val = this.val + n;
}

VerCors enforces that shared-memory accesses are framed by ownership resources. Before accessing this.val in the first ensures clause, the permissions for this.val must already be known! In the program given above, the access to this.val in the postcondition is not framed by the ownership predicate: it comes too late.

Permission leaks

So what about the following change? Can VerCors successfully verify the following program?

/*@
  requires Perm(this.val, 1);
  ensures Perm(this.val, 1\2);
  ensures this.val == \old(this.val) + n;
*/
void incr(int n) {
  this.val = this.val + n;
}

VerCors is able to verify the example program given above. However, less permission for this.val is ensured then is required, meaning that any thread that calls c.incr will need to give up write permission for c.val, but only receives read permission back in return, after incr has terminated. So this example has a permission leak. Recall that threads need full permission in order to write to shared heap locations, so essentially, calling c.incr has the effect of losing the ability to ever write to c.val again. In some cases this can be problematic, while in other cases this can be helpful, as losing permissions in such a way causes shared-memory to become read-only, specification-wise. However, in the scenario given below the permission leak will prevent successful verification:

/*@
  requires Perm(this.val, 1);
  ensures Perm(this.val, 1\2);
  ensures this.val == \old(this.val) + n;
*/
void incr(int n) {
  this.val = this.val + n;
}
  
/*@
  requires Perm(this.val, 1);
*/
void incr2(int n) {
  incr(n);
  incr(n);
}

In the incr2 method, the first call incr(n) will consume write permission for this.val, but only produce read permission in return. Therefore, the requirements of the second call incr(n) cannot be satisfied, which causes the verification to be unsuccessful.

Some extra notation

We end the section by mentioning some notational enhancements for handling permissions. Instead of writing Perm(o.f, 1), one may also write Perm(o.f, write), which is perhaps a more readable way to express write permission for o.f.

Similarly, one can write Perm(o.f, read) to express a non-zero read permission. Note that if this is used in a pre- and postcondition, it is not guaranteed to be the same amount of permissions. The underlying amount of permissions is an unspecified fraction and can therefore not be merged back into a write permission. This can be observed in the following program where the assert fails:

class Counter {
    int val;

    /*@
    requires Perm(this.val, write);
    ensures Perm(this.val, write);
    ensures this.val == \old(this.val) + n;
    */
    void incr(int n) {
        int oldValue = getValue();
        //@ assert Perm(this.val, write);
        this.val = oldValue + n;
    }
    
    /*@
    requires Perm(this.val, read);
    ensures Perm(this.val, read);
    ensures \result == this.val;
    */
    int getValue() {
        return this.val;
    }
}

read is mostly useful for specifying immutable data structures. One can also write Value(o.f) to express read permission to o.f, where the value of the fractional permission does not matter. Consequently, Value ownership predicates can be split indefinitely.

If you want to express that a thread has no ownership over a certain heap element, then one can use the keyword none, e.g. Perm(o.f, none).

If you want to express permissions to multiple locations, one may use \forall* vars; range; expr. For example, (\forall* int j; j >= 0 && j < array.length; Perm(array[j], write) denotes that the thread has write access to all elements in array. It is equivalent to writing Perm(array[0], write) ** Perm(array[1], write) ** … ** Perm(array[array.length-1], write). Another way to specify permissions of all array elements is to use Perm(array[*], write) which is equivalent to the previous expression.

If you want to reason about the value that the variable refers to as well then you can use PointsTo(var, p, val) which denotes that you have permission pfor variable var which has value val. It is similar to saying Perm(var, p) and var == val.

GPGPU Verification

This section explains how to verify GPGPU programs in VerCors. The tool supports both OpenCL and CUDA languages. The synchronization feature (i.e., barrier) in these languages is also supported by VerCors. To demonstrate GPGPU verification, we show two examples in both OpenCl and CUDA, one without barrier and the other one with barrier.

OpenCL/CUDA without Barrier

This simple method shows an OpenCL program that adds two arrays and stores the result in another array:

1   #include <opencl.h>
2
3   /*@
4   context \pointer_index(a, get_global_id(0), read);
5   context \pointer_index(b, get_global_id(0), read);
6   context \pointer_index(c, get_global_id(0), write);
7   ensures c[get_global_id(0)] == a[get_global_id(0)] + b[get_global_id(0)];
8   @*/
9   __kernel void openCLAdd(int a[], int b[], int c[]) {
10     int tid = get_global_id(0);
11     c[tid] = a[tid] + b[tid];
12  }  

and this method shows the same example in CUDA:

1   #include <cuda.h>
2
3   /*@
4   context \pointer_index(a, blockIdx.x*blockDim.x + threadIdx.x, read);
5   context \pointer_index(b, blockIdx.x*blockDim.x + threadIdx.x, read);
6   context \pointer_index(c, blockIdx.x*blockDim.x + threadIdx.x, write);
7   ensures c[blockIdx.x*blockDim.x + threadIdx.x] == a[blockIdx.x*blockDim.x + threadIdx.x)] + 
8                                                     b[blockIdx.x*blockDim.x + threadIdx.x];
9   @*/
10  __global__ void CUDAAdd(int a[], int b[], int c[]) {
11     int tid = blockIdx.x*blockDim.x + threadIdx.x;
12     c[tid] = a[tid] + b[tid];
13  }  

In both examples, first we should include the header files (i.g., opencl.h and cuda.h) as in line 1. Next we obtain thread identifiers and then each thread does the computation (lines 10-11 of OpenCL and 11-12 of CUDA examples). As we can see obtaining the global thread identifiers are different in OpenCL and CUDA.

In the specification of the methods, we specify read permission for each thread in arrays "a" and "b" and write permission in array "c" as pre- and postcondition. The keyword "\pointer_index" is used with three arguments, array name, index and permission to indicate which thread has what permission to which location. Finally in line 7 we specify the result of the methods that each location in array "c" contains the sum of the values in the corresponding locations in arrays "a" and "b". Note that in the specification we can use the shorthand keyword "\gid" instead of "get_global_id(0)" and "blockIdx.x*blockDim.x + threadIdx.x" in the tool.

OpenCL/CUDA with Barrier

In GPU programming, when there is only one thread block, there is a mechanism to sunchronize threads. This example shows an OpenCL program that uses barrier to synchronize threads:

1   #include <opencl.h>
2
3   /*@
4   context_everywhere opencl_gcount == 1;
5   context_everywhere array.length == size;
6   requires get_local_id(0) != size-1 ==> \pointer_index(array, get_local_id(0)+1, 1\2);
7   requires get_local_id(0) == size-1 ==> \pointer_index(array, 0, 1\2);
8   ensures \pointer_index(array, get_local_id(0), 1);
9   ensures get_local_id(0) != size-1 ==> array[get_local_id(0)] == \old(array[get_local_id(0)+1]);
10  ensures get_local_id(0) == size-1 ==> array[get_local_id(0)] == \old(array[0]);
11  @*/
12  __kernel void openCLLeftRotation(int array[], int size) {
13     int tid = get_local_id(0);
14     int temp;
15     if(tid != size-1){
16     temp = array[tid+1];
17     }else{
18     temp = array[0];
19     }
20
21     /*@
22     requires get_local_id(0) != size-1 ==> \pointer_index(array, get_local_id(0)+1, 1\2);
23     requires get_local_id(0) == size-1 ==> \pointer_index(array, 0, 1\2);
24     ensures \pointer_index(array, get_local_id(0), 1);
25     @*/
26     barrier(CLK_LOCAL_MEM_FENCE);
27
28     array[tid] = temp;
29 }  

And this is the CUDA version of the example:

1   #include <cuda.h>
2
3   /*@
4   context_everywhere opencl_gcount == 1;
5   context_everywhere array.length == size;
6   requires threadIdx.x != size-1 ==> \pointer_index(array, threadIdx.x+1, 1\2);
7   requires threadIdx.x == size-1 ==> \pointer_index(array, 0, 1\2);
8   ensures \pointer_index(array, threadIdx.x, 1);
9   ensures threadIdx.x != size-1 ==> array[threadIdx.x] == \old(array[threadIdx.x+1]);
10  ensures threadIdx.x == size-1 ==> array[threadIdx.x] == \old(array[0]);
11  @*/
12  __global__ void CUDALeftRotation(int array[], int size) {
13     int tid = threadIdx.x;
14     int temp;
15     if(tid != size-1){
16     temp = array[tid+1];
17     }else{
18     temp = array[0];
19     }
20
21     /*@
22     requires threadIdx.x != size-1 ==> \pointer_index(array, threadIdx.x+1, 1\2);
23     requires threadIdx.x == size-1 ==> \pointer_index(array, 0, 1\2);
24     ensures \pointer_index(array, threadIdx.x, 1);
25     @*/
26     __syncthreads();
27
28     array[tid] = temp;
29 }  

The above examples illustrate GPU programs that rotates the elements of an array to the left. Each thread ("tid") stores its right neighbor in a temporary location (i.e., "temp"), except thread "size-1" which stores the first element in the array (lines 15-19). Then each thread synchronizes in a barrier (line 26). After that, each thread writes the value read into its own location at index "tid" in the array (line 28).

We specify that there is only one thread block in the specification of the programs using the keyword "opencl_gcount" (line 4). The precondition of the barrier follows from the precondition of the function and the computations before the barrier. If the precondition of the barrier holds, the postcondition can be assumed after the barrier. Then, the postcondition of the method can follow from the postcondition of the barrier. Note that in the specification we can use the shorthand keyword "\lid" instead of "get_local_id(0)" and "threadIdx.x" in the tool.

Axiomatic Data Types

This section discusses axiomatic data types supported by VerCors.

Axiomatic data types are data types that are defined by a set of uninterpreted functions and axioms that define the behavior of these functions. This is in contrast to concrete data types which are defined by their implementation in a programming language such as Java or C++.

In the sections below, we are going to go over all ADTs supported by VerCors, give the definition of each, and show examples of common operations defined on them. A reference table can be found at the end showing all the operations defined on the ADTs.

The axiomatizations used by VerCors and its back end Viper can be found here (for sequences sets and bags) and here (for the other ADTs).

Sequence

Sequences are an ordered/enumerated collection also commonly referred to as lists. Sequences are finite and immutable (i.e. once created they cannot be altered).

There are two ways to construct a sequence: the explicit syntax and the implicit syntax. The explicit syntax requires the type of the sequence to be explicitly defined. For example, s1 is a sequence of integers initialized with the values 1, 2 and 3 and s2 is an empty sequence of integers.

seq<int> s1 = seq<int> { 1, 2, 3 };
seq<int> s2 = seq<int> { };

The implicit syntax is a shorter syntax. If the sequence is initialized with at least one value (using the implicit syntax), VerCors infers the type of the sequence. An empty sequence can be constructed by providing the type of the sequence. For example, s3 is equivalent to s1 and s4 is equivalent to s2.

seq<int> s3 = [ 1, 2, 3 ];
seq<int> s4 = [ t:int ];

Once we have a sequence, we can query different properties of that sequence. The syntax s5[i] can be used to retrieve the element at index i from sequence s5. The function head(s5) is shorthand for s5[0]. The length of a sequence s5 can be obtained by using the notation |s5|. Two sequences can be compared using the == operator where they are equal iff they are of the same length and the elements at the same indices are equal.

seq<int> s5 = [ 1, 2, 4, 8, 16 ];
assert s5[3] == 8;
assert |s5| == 5;
assert s5 == seq<int> { 1, 2, 4, 8, 16 };

Constructing sequences from existing sequences

As mentioned above sequences are immutable, however it is possible to construct a new sequence from an existing sequence.

Elements can be added to a sequence in two ways. The first way is by concatenating two sequences s6 + s7. This expression represents a new sequence with the elements of s6 followed by the elements of s7. The second way is to add a single value to either the front or the back of a sequence. The syntax e::s6 is used to prepend e at the front of s6 and the syntax s6 ++ e is used to append e at the end of s6.

seq<int> s6 = [ 1, 2, 4, 8, 16 ];
assert 0::s6 == [ 0, 1, 2, 4, 8, 16 ];
assert s6 ++ 32 == [ 1, 2, 4, 8, 16, 32 ];

seq<int> s7 = [ 32, 64, 128 ];
assert s6 + s7 == [ 1, 2, 4, 8, 16, 32, 64, 128 ];

It is also possible to take a subsequence from an existing sequence. The syntax s8[i..j] is used to take the elements from s8 from index i to index j (exclusive). By omitting either the lower bound or upper bound, one can take or drop from a sequence. The function tail(s8) can be used as a shorthand for s8[1..].

seq<int> s8 = [ 1, 2, 4, 8, 16 ];
assert s8[1..4] == [ 2, 4, 8 ];
assert s8[..2] == [ 1, 2 ];
assert tail(s8) == s8[1..]; 

Examples

Sortedness

Sortedness is a property that is often used in the verification of sorting algorithms. The sortedness property for a sequence of integers could be defined as follows:

pure static boolean sorted(seq<int> xs) = (\forall int i ; 0 <= i && i < |xs|-1; xs[i] <= xs[i+1]);

Distinct Element

Another property on sequences is the uniqueness of the elements. This property could be expressed for a sequence of integers as follows:

pure boolean distinct(seq<int> s) =
    (\forall int i; 0 <= i && i < s.length; 
        (\forall int j; 0 <= j && j < s.length && s[i] == s[j]; i == j)
    );

Maximum element

Recursive functions are often used to transform or reduce a sequence. The example below goes over a sequence to get its maximum value. This function could be defined for a sequence of integers as follows:

requires |xs| > 0;
ensures (\forall int i; 0 <= i && i < |xs|; xs[i] <= \result);
pure static int max(seq<int> xs) =
    1 == |xs| ?
        xs[0]:
        (xs[0] > max(xs[1..]) ?
            xs[0]:
            max(xs[1..]
        )
);

Set

Sets are an unordered collection with unique values. Sets are finite and immutable (i.e. once created they cannot be altered).

Similar to sequences, there are two ways to construct a set: the explicit syntax and the implicit syntax. The explicit syntax requires the type of the set explicitly defined. For example, s1 is a set of integers initialized with the values 1, 2 and 3 and s2 is an empty set of integers.

set<int> s1 = set<int> { 1, 2, 3, 2 };
set<int> s2 = set<int> { };

The implicit syntax is a shorter syntax. If the set is initialized with at least one value (using the implicit syntax), VerCors infers the type of the set. An empty set can be constructed by providing the type of the set. For example, s3 is equivalent to s1 and s4 is equivalent to s2.

set<int> s3 = { 1, 2, 2, 3 };
set<int> s4 = { t:int };

Once we have a set, we can query different properties of that set. The length of a set s5 can be obtained by using the notation |s5|. Two set s6 and s7 can be compared using the == operator where they are equal iff all elements of s6 are in s7 and all elements of s7 are in s6. The syntax e1 in s8 is used to check if set s8 contains the element e1. The < and <= operators denote the strict subset and subset operators respectively.

set<int> s5 = { 1, 2, 2, 3 };
set<int> s6 = { 3, 4, 5, 6 };
set<int> s7 = { 1, 2 };
set<int> s8 = { 2, 3 };
int e1 = 5;

assert |s5| == 4;
assert s6 != s7;
assert e1 in s8;
assert s8 < s5;

Constructing sets from existing sets

As mentioned above sets are immutable, however it is possible to construct a new set from an existing set.

There are several operations defined on sets that are part of set theory. The union of two sets is denoted by s5 + s6, the difference between two sets is denoted by s5 - s6 and the intersection of two sets is denoted by s5 * s6.

set<int> s5 = { 1, 2, 2, 3 };
set<int> s6 = { 3, 4, 5, 6 };

assert s5 + s6 == { 1, 2, 3, 4, 5, 6 };
assert s5 - s6 == { 1, 2 };
assert s6 - s5 == { 4, 5, 6};
assert s5 * s6 == { 3 };

Set comprehension

An advanced way to create a set is using set comprehension. The syntax is set<T> { main | variables; selector } and consists of three parts: the main, the variables and the selector.

The variables are the variables that are quantified over and these variables can be bound or unbound. From the quantified variables, we can select specific values by using the selector expression. This is a Boolean expression that selects (a part of) the quantified variables. With the selected set of variables to quantify over, we express the value that is going to be part of the resulting set using the main expression.

For example, we can construct the set of all even integers from 0 to 9 as follows:

set<int> {x | int x <- {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; x % 2 == 0};

To use more complex main expressions (i.e. non-identity functions), it is recommended to wrap the expression in a function and use that function in the main expression. For example, suppose we have a set of integers from 0 to 5 (inclusive) and we want to construct the set of integers where each element is doubled, we could use set comprehension as follows:

class SetComp {
    requires 0 <= j && j < 5;
    void myMethod(int j) {
       set<int> a = set<int> {SetComp.plus(x, x) | int x; x >= 0 && x <= 5 };
       assert plus(1, 1) in a; 
       assert plus(j, j) in a; 
    }

    pure static int plus(int a, int b) = a+b;
}

Here we define a function plus to wrap the expression a+b and use an invokation of the plus function as the main of our set comprehension.

Bag

Bags are an unordered collection. Bags are finite and immutable (i.e. once created they cannot be altered).

Similar to sequences and sets, there are two ways to construct a bag: the explicit syntax and the implicit syntax. The explicit syntax requires the type of the bag explicitly defined. For example, b1 is a bag of integers initialized with the values 1, 2, 2 and 3 and b2 is an empty bag of integers.

bag<int> b1 = bag<int> { 1, 2, 3, 2 };
bag<int> b2 = bag<int> { };

The implicit syntax is a shorter syntax. If the bag is initialized with at least one value (using the implicit syntax), VerCors infers the type of the bag. An empty bag can be constructed by providing the type of the bag. For example, b3 is equivalent to b1 and b4 is equivalent to b2.

bag<int> b3 = b{ 1, 2, 2, 3 };
bag<int> b4 = b{ t:int };

Once we have a bag, we can query different properties of that bag. The length of a bag b5 can be obtained by using the notation |b5|. The syntax e1 in b6 is used to get the multiplicity (or number of occurrences) of an element e1 in the bag b6. Two bags b7 and b8 can be compared using the == operator where they are equal iff for all elements e2, the multiplicity of e2 in b7 and b8 are equal. The < and <= operators denote the strict subset and subset operators respectively.

bag<int> b5 = b{ 1, 2, 2, 3 };
bag<int> b6 = b{ 3, 4, 5, 6, 5 };
bag<int> b7 = b{ 1, 2, 3, 2, 2, 6 };
bag<int> b8 = b{ 6, 1, 2, 2, 2, 3 };
int e1 = 5;


assert |b5| == 4;
assert (e1 in b6) == 2;
assert b5 <= b7 && b5 < b8;
assert b7 == b8;

Constructing bags from existing bags

As mentioned above bags are immutable, however it is possible to construct a new bag from an existing bag.

The union of two bags is denoted by b9 + b10, the difference between two bags is denoted by b11 - b12 and the intersection of two bags is denoted by b13 * b14.

bag<int> b9 = bag<int> { 3 };
bag<int> b10 = bag<int> { 3, 4 };
bag<int> b11 = bag<int> { 1, 2, 2, 3 };
bag<int> b12 = bag<int> { 2, 3, 3, 4 };
bag<int> b13 = bag<int> { 1, 1, 2, 3 };
bag<int> b14 = bag<int> { 2, 3, 3, 4 };

assert b9 + b10 == bag<int> { 3, 3, 4 };
assert b12 - b11 == bag<int> { 3, 4 };
assert b11 - b12 == bag<int> { 1, 2 };
assert b13 * b14 == bag<int> { 2, 3 };

Examples

TODO

Map

Maps are an unordered, collection of key/value pairs with unique keys. Maps are finite and immutable (i.e. once created they cannot be altered).

A map can be constructed using the syntax map<K,V> { k1 -> v1, k2 -> v2, ...}. This syntax constructs a map with keys/value pairs (k1, v1) and (k2, v2) (of type K and type V respectively). An empty map can be constructed by declaring no key/value pair.

map<int, boolean> m1 = map<int,boolean>{1 -> true, 2 -> true, 0 -> false};
map<int, boolean> m2 = map<int,boolean>{1 -> true, 2 -> true, 0 -> false, 1 -> false, 1 -> true};
assert m1 == m2;

Once we have a map, we can query different properties of that map. Given a key k1, we can get its associated value in map m1 using the syntax map[k1] or getFromMap(m1, k1). The cardinality/size of the map can be queried using the syntax |m1| or cardMap(m1). The size of a map is equivalent to the size of its key set. The key, value and key/value pair sets can be obtained using the functions keysMap(m1), valuesMap(m1) and itemsMap(m1) respectively.

map<int, boolean> m1 = map<int,boolean>{1 -> true, 2 -> true, 0 -> false};

assert |m1| == 3;
assert m1[0] == false;
assert m1[1] == true;
assert m1[2] == true;

assert keysMap(m1) == { 1, 2, 0 };
assert valuesMap(m1) == { true, false };
assert tuple<int, boolean> { 0, false } in itemsMap(m1);
assert tuple<int, boolean> { 1, true } in itemsMap(m1);
assert tuple<int, boolean> { 2, true } in itemsMap(m1);

We can add a key/value pair (k4, v4) to the map m4 using the syntax m4 + (k4, v4) or buildMap(m4, k4, v4). If the key was already present in m4, its value gets updated. We can also remove a key k5 from a map m5 using the function removeFromMap(m1, k1).

map<int, int> m1 = map<int,int>{};
map<int, int> m2 = m1 ++ (1, 1) ++ (2, 4) ++ (3, 9);
map<int, int> m3 = removeFromMap(m2, 2);
map<int, int> m4 = removeFromMap(removeFromMap(m3, 3), 1);

assert |m1| == 0;
assert m2[1] == 1;
assert m2[2] == 4;
assert m2[3] == 9;

assert !(2 in keysMap(m3));
assert m3[1] == 1;
assert m3[3] == 9;

assert |m4| == 0;

Examples

TODO

Tuple

A tuple is an ordered, immutable collection containing exactly two elements.

A tuple can be constructed using the syntax tuple<F,S> { fst, snd }. This syntax constructs a tuple with the first value fst of type F and the second value snd of type S. A tuple can also be deconstructed into its first and second value by using the functions getFst(t2) and getSnd(t2) respectively. For example, a tuple t1 with values (1, false) is constructed as:

tuple<int, boolean> t1 = tuple<int, boolean> { 1, false };
assert getFst(t1) == 1;
assert getSnd(t1) == false;

Examples

TODO

Option

The option data type is a container that either represents the presence of an element in the container or the absence of it.

An option type can be constructed with the constructors Some(e) or None. The constructor Some(e) represents the presence of the value e in the option type and the constructor None represents the absence of an element.

option<int> o1 = None;
option<int> o2 = Some(3);

Option types can be compared to each other where two options o3 and o4 are equivalent iff they both contain an element e3 and e4 respectively and e3 equals e4.

option<int> o3 = Some(6);
option<int> o4 = Some(6);
option<int> o5 = Some(1);
option<int> o6 = None;

assert o3 == o4;
assert o4 != o5;
assert o5 != o6;

Examples

TODO

ADT Reference

The tables below are a reference for all the functions/operations that are defined on the different ADTs.

Sequence

Short Description Return type Syntax
Constructor seq<T> seq<T> { e1, e2, ... }
Constructs a sequence with elements of type T initiliazed with values e1, e2, etc. When the sequence is not initialized, an empty sequence is constructed.
Constructor seq<T> [ e1, e2, ... ]
Constructs a sequence with elements of type T initiliazed with values e1, e2, etc. This syntax requires the sequence to be initiliazed.
Constructor seq<T> [ t: T ]
Constructs an empty sequence with element of type T
Length int `
Comparison boolean s1 == s2
Returns true if s1 and s2 are equivalent
Concatenation seq<T> s1 + s2
Denotes the sequence with elements from s1 followed by the elements of s2
Contains boolean (e \memberof xs). True if e is in xs. Note that the parenthesis around the \memberof expression are mandatory.
Indexing T s1[i]
Returns the element at index i of sequence s1
Head T head(s1)
Returns the first element of sequence s1
Tail seq<T> tail(s1)
Denotes the sequence with the elements from s1 excluding the first element
Append seq<T> xs ++ x
Denotes the sequence with elements of xs appended with the element x
Prepend seq<T> x::xs
Denotes the sequence with elements of xs prepended with the element x
Slicing seq<T> s1[i..j]
Denotes the subsequence of sequence s1 from index i until index j (exlusive)
Updating seq<T> s1[i -> v]
Denotes the sequence with elements from s1 where index i has been updated to value v
Summation int (\sum int i; low <=i && i< up; s1[i]);
Sum the elements of a sequence of integers s1 between the indices low and up (exclusive)

Set

Short Description Return type Syntax
Constructor set<T> set<T> { e1, e2, ... }
Constructs a set with elements of type T initiliazed with values e1, e2, etc. When the set is not initialized, an empty set is constructed.
Constructor set<T> { e1, e2, ... }
Constructs a set with elements of type T initiliazed with values e1, e2, etc. This syntax requires the set to be initiliazed.
Constructor set<T> { t: T }
Constructs an empty set with element of type T
Length int `
Comparison boolean s1 == s2
Returns true if s1 and s2 are equivalent
Membership boolean e in s1
Returns true if the value e is in the set s1
Set Union set<T> s1 + s2
Denotes the set of all elements from sets s1 and s2 of the same type T
Set Difference set<T> s1 - s2
Denotes the set of all elements from set s1 that are not in set s2
Subset boolean s1 <= s2
Returns true if set s1 is a subset of set s2
Strict Subset boolean s1 < s2
Returns true if set s1 is a strict subset of set s2
Intersection set<T> s1 * s2
Denotes the set of all elements that are both in sets s1 and s2
Set Comprehension set<T> `set { main

Bag

Short Description Return type Syntax
Constructor bag<T> bag<T> { e1, e2, ... }
Constructs a bag with elements of type T initiliazed with values e1, e2, etc. When the bag is not initialized, an empty bag is constructed.
Constructor bag<T> b{ e1, e2, ... }
Constructs a bag with elements of type T initiliazed with values e1, e2, etc. This syntax requires the bag to be initiliazed.
Constructor bag<T> b{ t: T }
Constructs an empty bag with element of type T
Length int `
Multiplicity int e in b1
Returns the number of occurrences of value e in bag b1
Comparison boolean b1 == b2
Returns true if b1 and b2 are equivalent
Bag Union bag<T> b1 + b2
Denotes the bag of all elements from bags b1 and b2 of the same type T
Bag Difference bag<T> b1 - b2
Denotes the bag of all elements from bag b1 that are not in bag b2
Subset boolean b1 <= b2
Returns true if bag b1 is a subset of bag b2
Strict Subset boolean b1 < b2
Returns true if bag b1 is a strict subset of bag b2
Intersection bag<T> b1 * b2
Denotes the bag of all elements that are both in bags b1 and b2

Map

Short Description Return type Syntax
Constructor map<K,V> map<K,V> { k1 -> v1, k2 -> v2, ...}
Constructs a map with key-value pairs of type K and V respectively initiliazed with pairs k1,v1, k2,v2, etc.
Build/Add map<K,V> buildMap(m1, k1, v1)or m1 + (k1, v1)
Adds the key/value pair (k1,v1) to the map m1. If the key is already present in m1, update the value of the key.
GetByKey V getFromMap(m1, k1) or map[k1]
Gets the value mapped by the key k1 from the map m1.
Remove map<K,V> removeFromMap(m1, k1)
Removes the key k1 and its associated value from the map m1.
Length int cardMap(m1) or `
Comparison boolean equalsMap(m1, m2) or m1 == m2
Returns true if the keysets of m1 and m2 are equivalent and the keys map to the same values.
Key Set set<K> keysMap(m1)
Returns a set of keys in map m1
Value Set set<V> valuesMap(m1)
Returns a set of the values in map m1
Item Set set<tuple<K,V>> itemsMap(m1)
Returns a set of tuples corresponding to the key-value pairs in map m1
Disjoint boolean disjointMap(m1, m2)
Returns true if no key is in both the key set of m1 and the key set of m2.

Tuple

Short Description Return type Syntax
Constructor tuple<F,S> tuple<F,S> {fst, snd}
Construct a tuple with first element fst (of type F) and second element snd (of type S)
Get First Element F getFst(t)
Get the first element of the tuple t (of type tuple<F,S>)
Get Second Element S getSnd(t)
Get the second element of the tuple t (of type tuple<F,S>)

Option

Short Description Return type Syntax
Constructor option<T> Some(e)
Constructs an option which contains the element e of type T
Constructor option<T> None
Returns the option None
Comparison boolean o1 == o2
Returns true if the element contained in the option o1 is equivalent to the element wrapped in the option o2

Arrays and Pointers

This tutorial explains how to specify arrays and pointers in vercors. Java and PVL support arrays, whereas C and the GPGPU frontends only support pointers. The tutorial assumes you are familiar with arrays and pointers already.

Array permissions

We have learned already how to specify ownership of variables on the heap. Arrays generalize this concept by treating each element of the array as a separate location on the heap. For example, you might specify:

/*@
requires ar != null && ar.length == 3;
context Perm(ar[0], write) ** Perm(ar[1], write) ** Perm(ar[2], write);
*/
void example1(int[] ar);

This means we require the length to be 3, and require and return permission over each of the elements of the array. Length is treated in a special way here: even though it is a "field", we do not need permission to read it, because the length of an array cannot be changed and it is baked into Vercors.

Of course writing a specific length and manually asserting permission over each location is cumbersome, so we can write a contract that accepts arrays of any length as such:

/*@
requires ar != null;
context (\forall* int i; 0 <= i && i < ar.length; Perm(ar[i], write));
*/
void example2(int[] ar);

As mentioned in the permissions tutorial, the permissions are combined with ** to Perm(ar[0], write) ** Perm(ar[1], write) ** … ** Perm(ar[ar.length-1], write). Please note the * after forall, which denotes that the body of the forall is combined with separating conjunction (**) and not boolean conjunction (&&).

As you might expect, we can also use forall to specify properties about the values of the array:

/*@
requires ar != null;
context (\forall* int i; 0 <= i && i < ar.length; Perm(ar[i], write));
ensures (\forall int i; 0 <= && i < ar.length; ar[i] == i);
*/
void identity(int[] ar) {
    for(int i = 0; i < ar.length; i++)
    /*@
    loop_invariant (\forall* int i; 0 <= i && i < ar.length; Perm(ar[i], write));
    loop_invariant (\forall int j; 0 <= j && j < i; ar[j] == j); */
    {
        ar[i] = i;
    }
}

Syntactic sugar

Specifying arrays quickly leads to prefix a lot of statements with \forall* int i; 0 <= i && i < ar.length;, so there is some syntactic sugar. First, you might want to use context_everywhere for the permission specification of the array, so that it is automatically propagates to loop invariants:

/*@
context_everywhere (\forall* int i; 0 <= i && i < ar.length; Perm(ar[i], write));
ensures (\forall int i; 0 <= && i < ar.length; ar[i] == i);
*/
void identity(int[] ar) {
    for(int i = 0; i < ar.length; i++)
    /*@
    loop_invariant (\forall int j; 0 <= j && j < i; ar[j] == j); */
    {
        ar[i] = i;
    }
}

This whole forall* can also be written as Perm(ar[*], write), which still means write permission over all the elements of the array:

/*@
requires ar != null;
context_everywhere Perm(ar[*], write);
ensures (\forall int i; 0 <= && i < ar.length; ar[i] == i);
*/
void identity(int[] ar) {
    for(int i = 0; i < ar.length; i++)
    /*@
    loop_invariant (\forall int j; 0 <= j && j < i; ar[j] == j); */
    {
        ar[i] = i;
    }
}

If you want to specify the length of an array, you can write as well: \array(ar, N) which is equivalent to ar != null && ar.length == N. More interestingly, there is also \matrix(mat, M, N), which is equivalent to:

mat != null ** mat.length == M **
(\forall* int i; 0 <= i && i < M; Perm(mat[i], read)) **
(\forall int i; 0 <= i && i < M; mat[i].length == N) **
(\forall int i; 0 <= i && i < M; (\forall int j; 0 <= j && j < M && mat[i] == mat[j]; i == j))

The last line is interesting here. In Java there is no such thing as a true matrix: instead we can make an array of arrays. However, there is nothing preventing you from putting the same row array instance in multiple rows. The last statement says that if we have valid row indices i, j, we know that i != j ==> mat[i] != mat[j] (and the contrapositive).

Pointers

Pointers themselves are quite well-supported, but we don't support casting and structs in C, making the end result quite limited. For the support that we do have, pointers can be specified with two constructs: \pointer and \pointer_index.

We write \pointer(p, size, perm) to express that p != NULL, the pointer p is valid from (at least) 0 to size-1, and we assert perm permission over those locations.

We write \pointer_index(p, index, perm) to express that p != NULL, (p+i) is a valid location, and we have permission perm at that location.

/*@
requires \pointer(a, 10, write);
*/
void test(int *a) {
    //@ assert \pointer(a, 10, write);
    //@ assert \pointer(a, 8, write);
    //@ assert (\forall* int i; 0 <= i && i < 10; \pointer_index(a, i, write));
    int *b = a+5;
    //@ assert a[5] == b[0];
    //@ assert \pointer(b, 5, write);
}

Injectivity, object arrays and efficient verification

This section contains advanced information, and can be skipped on a first read.

Due to a technical limitation in the backend of VerCors, the location denoted by a permission expression in a quantified expression must be unique for each binding of the quantifier. For example, if we write:

class Box {
    int value;

    void test() {
        Box box1 = new Box();
        seq<Box> boxes = seq<Box>{box1, box1};
        exhale (\forall* int i; 0 <= i && i < |boxes|; Perm(boxes[i].value, 1\10));
    }
}

This could be correct: we only exhale 2\10 permission of box1.value. The expression is however not well-formed: i==0 and i==1 cause the expression boxes[i].value to point to the same location. (Please note that it seems that the error from our backend currently is not propagated correctly. Silicon reports "Receiver of boxes[i].value might not be injective." and VerCors translates this into "ExhaleFailed:InsufficientPermission".)

The reason we refer to this as injectivity, is that we should be able to construct a function that assigns bindings for a location, e.g. for a given Box we can either find a unique i that points to the Box, or the Box does not appear in boxes. That function is the inverse of the expression in the Perm. The truth value of a \forall* carries with it this property of injectivity. This means, for example, that in a method definition a requirement assumes that \forall* statements are injective with repsect to locations, whereas we must prove it in ensures statements. This usually follows directly from a requirement. In method invokations then as per usual it's the opposite: we must prove that \forall* statements in the requirements are injective and may assume it for ensures statements.

This limitation is especially important in for example arrays of objects:

class Box {
    int value;

    requires a != null && (\forall* int i; 0 <= i && i < a.length; Perm(a[i], read));
    requires (\forall* int i; 0 <= i && i < a.length; a[i] != null && Perm(a[i].value, write));
    void test(Box[] a) {
        
    }

    void use() {
        Box box = new Box();
        Box[] boxes = new Box[]{box, box};
        test(boxes); // fails
    }
}

In common use however we have to know about duplicate elements anyway. For example, in the example above we require write permission over the boxes, so indirectly we already require all the boxes to be distinct. As for the array elements themselves: we have an internal axiom about arrays that expresses exactly that the locations of an array correspond to only one index and one array.

Parallel Blocks

In this section we explain how to verify parallel algorithms by creating parallel blocks in PVL. First, we give an example of a simple method using parallel block. Then, we discuss a more complex example with a barrier inside the parallel block. A barrier is used to synchronize threads inside a parallel block.

Simple Parallel Blocks

Caution: barrier syntax

In this chapter syntax is introduced for barriers. There is a caveat to using barriers, which we want to explain first so it is not missed. Barrier syntax in VerCors has two forms. Form 1:

barrier(barrierName) {
  requires P;
  ensures Q;
}

Form 2:

barrier(barrierName)
  requires P;
  ensures Q;
{ }

Notice how in form 1, the contract is inside the curly braces. In form 2, the contract resides between the barrier declaration and the curly braces.

There is a big difference between these two syntaxes. In form 1, the contract of the barrier is not actually checked by VerCors. This means that if you would add ensures false; to your barrier using the syntax of form 1, VerCors will not indicate this is a problematic contract. For form 2, however, the contract is actually checked. Therefore, when using barriers, form 2 should be preferred. Form 1 should only be used if there is a proof external to VerCors.

Parallel Block without Barrier

This example shows a simple method that adds two arrays in parallel and stores the result in another array:

1 context_everywhere a != null && b != null && c != null;
2 context_everywhere a.length == size && b.length == size && c.length == size;
3 context (\forall* int i; i >= 0 &&  i < size; Perm(a[i], 1\2));
4 context (\forall* int i; i >= 0 &&  i < size; Perm(b[i], 1\2));
5 context (\forall* int i; i >= 0 &&  i < size; Perm(c[i], 1));
6 ensures (\forall int i; i >= 0 &&  i < size; c[i] == a[i] + b[i]);
7 void Add(int[] a, int[] b, int[] c, int size){
8
9    par threads (int tid = 0 .. size)
10    context Perm(a[tid], 1\2) ** Perm(b[tid], 1\2) ** Perm(c[tid], 1);
11    ensures c[tid] == a[tid] + b[tid];
12   {
13      c[tid] = a[tid] + b[tid];
14   }
15 }

In this method there is a parallel block (lines 9-14) named "threads". The keyword "par" is used to define a parallel block, followed by an arbitrary name after that defines the name of that block. Moreover, we define the number of threads in the parallel block as well as a name for the thread identifier. In this example, we have "size" threads in the range from 0 to "size-1" and "tid" is used as thread identifier to refer to each thread.

In addition to the specification of the method (lines 1-6), we add thread-level specification to the parallel block (lines 10-11). The precondition of the method indicates that we have read permission over all locations in arrays "a" and "b" and write permission for array "c" (lines 3-5). In the parallel block, we specify that each thread ("tid") has read permission to its own location in arrays "a" and "b" and write permission to its own location in array "c" (line 10). After termination of the parallel block as postcondition (1) we have the same permission (line 10) and (2) the result of each location in array "c" is the sum of the two corresponding locations in arrays "a" and "b" (line 11). From the postcondition of the parallel block, we can derive the postcondition of the method using universal quantifier for all locations in the arrays (line 3-6).

Parallel Block with Barrier

Next we show an example of a parallel block which uses a barrier to synchronize threads:

1  context_everywhere array != null && array.length == size;
2  requires (\forall* int i; i >= 0 &&  i < size; Perm(array[i], 1\2));
3  ensures (\forall* int i; i >= 0 &&  i < size; Perm(array[i], 1));
4  ensures (\forall int i; i >= 0 &&  i < size; (i != size-1 ==> array[i] == \old(array[i+1])) && 
5                                               (i == size-1 ==> array[i] == \old(array[0])) );
6  void leftRotation(int[] array, int size){
7
8    par threads (int tid = 0 .. size)
9     requires tid != size-1 ==> Perm(array[tid+1], 1\2);
10    requires tid == size-1 ==> Perm(array[0], 1\2);
11    ensures Perm(array[tid], 1);
12    ensures tid != size-1 ==> array[tid] == \old(array[tid+1]);
13    ensures tid == size-1 ==> array[tid] == \old(array[0]);
14   {
15      int temp;
16  if(tid != size-1){
17      temp = array[tid+1];
18  }else{
19      temp = array[0];
20  }
21
22      barrier(threads)
23      {
24        requires tid != size-1 ==> Perm(array[tid+1], 1\2);
25    requires tid == size-1 ==> Perm(array[0], 1\2);
26    ensures Perm(array[tid], 1);
27      }
28
29      array[tid] = temp;
30   }
31 }

This example illustrates a method named "leftRotation" that rotates the elements of an array to the left. In this example, we also have "size" threads in the range from 0 to "size-1" and "tid" is used as thread identifier. Inside the parallel block each thread ("tid") stores its right neighbor in a temporary location (i.e., "temp"), except thread "size-1" which stores the first element in the array (lines 15-20). Then each thread synchronizes at the barrier (line 22). The keyword "barrier" and the name of the parallel block as an argument (e.g., "threads" in the example) are used to define a barrier in PVL. After that, each thread writes the value read into its own location at index "tid" in the array (line 29).

To verify this method in VerCors, we annotate the barrier, in addition to the method and the parallel block. As precondition of the method, we have read permission over all locations in the array (line 2). At the beginning of the parallel block, each thread reads from its right neighbor, except thread "size-1" which reads from location 0 (lines 16-20). Therefore, we specify read permissions as precondition of the parallel block in lines 9-10. Since after the barrier each thread ("tid") writes into its own location at index ("tid"), we change the permissions in the barrier in such that each thread has write permissions to its own location (lines 24-26). When a thread reaches the barrier, it has to fulfill the barrier preconditions, and then it may assume the barrier postconditions. Moreover, the barrier postconditions must follow from the barrier preconditions.

As postcondition of the parallel block (1) first each thread has write permission to its own location (this comes from the postcondition of the barrier) in line 11 and (2) the elements are truly shifted to the left (lines 12-13). From the postcondition of the parallel block, we can establish the same postcondition for the method (lines 3-5).

Complicated Parallel Blocks

Nested Parallel Blocks

In the previous examples, we defined a parallel block of threads to work on one-dimensional arrays. It is also possible to define two-dimensional thread layouts to work on two-dimensional arrays. As an example we define a two-dimensional thread layout to transpose a matrix in parallel:

1  context_everywhere inp != null && out == size;
2  context_everywhere inp.length == size && out.length == size; 
3  context_everywhere (\forall int i; 0 <= i && i < size; inp[i].length == size && out[i].length == size);
4  context (\forall* int i; 0 <= i && i < size; 
5           (\forall* int j; 0 <= j && j < size; Perm(inp[i][j], read)));
6  context (\forall* int i; 0 <= i && i < size; 
7           (\forall* int j; 0 <= j && j < size; Perm(out[i][j], write)));
8  ensures (\forall int i; 0 <= i && i < size; 
9           (\forall* int j; 0 <= j && j < size; out[i][j] == inp[j][i]));
10 void transpose(int[][] inp, int[][] out, int size){
11
12   par threadX (int tidX = 0 .. size)
13    context (\forall* int i; i >= 0 && i < size; Perm(inp[i][tidX], read));
14    context (\forall* int i; i >= 0 && i < size; Perm(out[tidX][i], write));
15    ensures (\forall int i; i >= 0 && i < size; out[tidX][i] == inp[i][tidX]);
16   {
17     par threadY (int tidY = 0 .. size)
19      context Perm(inp[tidY][tidX], read);
20      context Perm(out[tidX][tidY], write);
21      ensures out[tidX][tidY] == inp[tidY][tidX];
22     {
23       out[tidX][tidY] = inp[tidY][tidX]; 
24     } 
25   }
26 }

As we can see defining nested parallel blocks allow us to define thread layout in different dimensions. We can simplify the above example syntactically into one parallel block:

1  context_everywhere inp != null && out != null;
2  context_everywhere inp.length == size && out.length == size; 
3  context_everywhere (\forall int i; 0 <= i && i < size; inp[i].length == size && out[i].length == size);
4  context (\forall* int i; 0 <= i && i < size; 
5           (\forall* int j; 0 <= j && j < size; Perm(inp[i][j], read)));
6  context (\forall* int i; 0 <= i && i < size; 
7           (\forall* int j; 0 <= j && j < size; Perm(out[i][j], write)));
8  ensures (\forall int i; 0 <= i && i < size; 
9           (\forall* int j; 0 <= j && j < size; out[i][j] == inp[j][i]));
10 void transpose(int[][] inp, int[][] out, int size){
11
12   par threadXY (int tidX = 0 .. size, int tidY = 0 .. size)
13    context Perm(inp[tidY][tidX], read);
14    context Perm(out[tidX][tidY], write);
15    ensures out[tidX][tidY] == inp[tidY][tidX];
16   {
17     out[tidX][tidY] = inp[tidY][tidX]; 
18   } 
19 }

Simultaneous Parallel Blocks

There might be some scenarios that we have multiple parallel blocks and all threads in each parallel block are working in disjoint memory locations. In this case we can define multiple parallel blocks to run simultaneously. That means, threads in each parallel block run independently of other threads in different parallel blocks. Below is an example of such a scenario:

1  context_everywhere a != null && b != null && c != null;
2  context_everywhere a.length == size && b.length == size && c.length == size; 
3  context (\forall* int i; i >= 0 &&  i < size; Perm(a[i], write));
4  context (\forall* int i; i >= 0 &&  i < size; Perm(b[i], write));
5  context (\forall* int i; i >= 0 &&  i < size; Perm(c[i], write));
6  ensures (\forall int i; i >= 0 &&  i < size; a[i] == \old(a[i]) + 1 && b[i] == \old(b[i]) + 1 && 
7                                               c[i] == \old(c[i]) + 1);
8  void simPar(int[] a, int[] b, int[] c, int size){
9
10   par thread1 (int tid1 = 0 .. size)
11    context Perm(a[tid1], write);
12    ensures a[tid1] == \old(a[tid1]) + 1;
13   {
14     a[tid1] = a[tid1] + 1; 
15   } and
16   par thread2 (int tid2 = 0 .. size)
17    context Perm(b[tid2], write);
18    ensures b[tid2] == \old(b[tid2]) + 1;
19   {
20     b[tid2] = b[tid2] + 1;
21   } and
22   par thread3 (int tid3 = 0 .. size)
23    context Perm(c[tid3], write);
24    ensures c[tid3] == \old(c[tid3]) + 1;
25   {
26     c[tid3] = c[tid3] + 1;
27   }
28 }

As we can see we use "and" between each parallel block (lines 15 and 21) to define simultaneous parallel blocks. This construction can be used in a situation where we decide to run simultaneous instructions. That means, we do not define threads in the parallel blocks, but the par blocks run simultaneously. Below is an example in this situation where "a", "b" and "c" are objects included a field "val":

1  context Perm(a.val, write) ** Perm(b.val, write) ** Perm(c.val, write);
2  ensures a.val == \old(a.val) + 1 && b.val == \old(b.val) + 1 && c.val == \old(c.val) + 1;                                            
3  void IncPar(Object a, Object b, Object c){
4
5    par 
6     context Perm(a.val, write);
7     ensures a.val == \old(a.val) + 1;
8    {
9      a.val = a.val + 1; 
10   } and
11   par 
12    context Perm(b.val, write);
13    ensures b.val == \old(b.val) + 1;
14   {
15     b.val = b.val + 1;
16   } and
17   par 
18    context Perm(c.val, write);
19    ensures c.val == \old(c.val) + 1;
20   {
21     c.val = c.val + 1;
22   }
23 }

In the above example, for each object we increase its "val" by one in parallel. As we can see, thread blocks are without names and thread identifiers in this case.

Atomics and Locks

TODO @Raul (+invariants)

Example folders of interest for atomics/locks writing

Subjects

Process Algebra Models

TODO @Raul (Histories + Futures)

Predicates

This section discusses syntax and semantics of predicates. First a motivating example is given why predicates are needed. Then an overview is given of the syntax and semantics of predicates. The introductory example is then rewritten using predicates. Finally, we discuss some internal and reserved predicates, and finish this part of the tutorial with a brief overview of advanced usages of predicates and known problems.

In this section, the language used for the code examples is Java. However, the concepts presented apply to PVL as well.

Why predicates?

Permissions are useful for indicating access rights and modifications to data. But, adding permissions to private members in the contract leaks implementation details. Consider the following example:

class Counter {
  int count;
  
  //@ context Perm(count, write);
  //@ ensures count == \old(count) + n;
  void increment(int n);
}

class MyProgram {
  //@ requires c != null;
  //@ requires Perm(c.count, write);
  //@ requires c.count == 0;
  void foo(Counter c) {
    //@ assert c.count == 0;
    c.increment(2);
    //@ assert c.count == 2;
  }
}

It is clear from the contract of increment that it modifies the internal state of the Counter object. Therefore, when Counter changes its internal layout, client code will have to be modified as well. For example, if in the previous example Counter adds some internal field, the client code will also have to be changed. This is shown in the next example:

class CounterExtended {
  int count;
  int numIncrements;
  
  //@ context Perm(count, write) ** Perm(numIncrements, write);
  //@ ensures count == \old(count) + n
  //@ ensures numIncrements == \old(numIncrements) + 1;
  void increment(int n);
}

class MyProgram {
  //@ requires c != null;
  //@ requires Perm(c.count, write);
  //@ requires c.count == 0;
  void foo(Counter c) {
    //@ assert c.count == 0;
    c.increment(2);
    //@ assert c.count == 2;
  }
}

It is desirable that permissions are managed such that client code does not depend on irrelevant details, to avoid this rippling effect of modifications. Predicates help with this.

Predicate syntax and usage

A predicate is declared as follows:

//@ resource myPredicate(int a, bool b, ...) = body;

A predicate consists of the name of the predicate, zero or more arguments, and a body where arguments can be used. In Java, the declaration must be written as a verification annotation (e.g. using //@). In PVL, it is a plain declaration.

One can think of a predicate as a function returning type resource, similar to how a permission (Perm) is a resource. Except that, predicates are never called, but created and destroyed using unfold and folds. We will explain this next.

To create a predicate instance, the contents of the predicate body must be exchanged for a predicate instance. This is called "folding". Specifically, folding a predicate means any permissions present in the predicate body are removed from the program state. In return, an instance of the predicate is added to the state. In the following example folding is shown. The method foo receives a permission for the field x. At the end of the method, the permission for this field x has been exchanged for an instance of the predicate state. Note how the permission for x and the predicate state are mutually exclusive: both cannot be in the program state simultaneously.

int x;

//@ resource state(int val) = Perm(x, write) ** x == val;

//@ requires Perm(x, write) ** x == n;
//@ ensures state(n);
void foo(int n) {
    // Now we have Perm(x, write).
    //@ fold state(n);
    // Now Perm(x, write) is gone, but state(n) is present.
}

A predicate can also be "unfolded". This exchanges a predicate instance for its body.

int x;

//@ resource state(int val) = Perm(x, write) ** x == val;

//@ requires state(n);
//@ ensures Perm(x, write) ** x == n;
void foo(int n) {
    // Now we have state(n);
    //@ unfold state(n);
    // Now state(n) is gone, but Perm(x, write) is present.
}

The previous two examples also show that fields of the current class can be used in the predicate body. This is because a predicate instance is implicitly bound to an instance of the class it is defined in. The following example verifies, because the this part of the predicate instance is implicit. It also shows how to name predicates on objects other than this.

class Cell {
    int x;

    //@ resource state(int val) = Perm(x, Write) ** x == val;

    // "this" is implicit:
    //@ requires this.state(n);
    //@ ensures state(n);
    void foo(int n) {

    }

    // Predicates can appear on distinct objects too:
    //@ given int n;
    //@ given int m;
    //@ context other.state(m);
    //@ requires state(n)
    //@ ensures state(n+m);
    void combine(Cell other) {
        //@ unfold state(n);
        //@ unfold other.state(m);
        x += other.x;
        //@ fold other.state(m);
        //@ fold state(n+m);
    }
}

Predicates can be used anywhere Perm can be used, and hence must also be composed using the separating conjunction (**) operator.

The Counter example, now with predicates

In the next snippet we redefine the Counter class to use predicates, instead of plain permissions:

class Counter {
  int count;

  //@ resource state(int val) = Perm(count, write) ** count == val;
  
  //@ given int oldCount;
  //@ requires state(oldCount);
  //@ ensures state(oldCount + n);
  void increment(int n);
}

class MyProgram {
  //@ requires c != null ** c.state(0);
  //@ ensures c.state(2);
  void foo(Counter c) {
    //@ assert c.state(0);
    c.increment(2) /*@ with { oldCount = 0; } @*/;
    //@ assert c.state(2);
  }
}

The client only uses the predicate when expressing how it manipulates the counter. Therefore, the counter class is now free to change its internal composition without affecting any of its clients. This is possible because only relevant details are exposed through the predicate. In this case, this is the value of the counter. For example, adding a plain field does not break the client.

If the interface of Counter changes, the client code will have to change too. However, the client code does not have to manage any extra permissions: it only has to specify how it uses the interface from CounterExtended. The next example shows this.

class CounterExtended {
  int count;
  int numIncrements;

  /*@ resource state(int val, int val2) = Perm(count, write) ** count == val 
        ** Perm(numIncrements, write) ** numIncrements = val2;
    @*/
  
  //@ given int oldCount;
  //@ given int oldIncrements;
  //@ requires state(oldCount, oldIncrements);
  //@ ensures state(oldCount + n, oldIncrements + 1);
  void increment(int n);
}

class MyProgram {
  //@ given int oldIncrements;
  //@ requires c != null ** c.state(0, oldIncrements);
  //@ ensures c.state(2, oldIncrements + 1);
  void foo(Counter c) {
    //@ assert c.state(0, oldIncrements);
    c.increment(2) /*@ with { oldCount = 0; oldIncrements = oldIncrements; } @*/;
    //@ assert c.state(2, oldIncrements + 1);
  }
}

To summarise, predicates are very effective at hiding implementation details. They allow encapsulation. However, predicates do not protect the client from breaking changes. If the interface changes, any clients will have to change too.

Reserved/internal predicates

There are a few situations where VerCors attaches special meanings to certain predicate names.

For atomics/locks

For atomics and locks a predicate lock_invariant is defined. This predicate cannot be folded/unfolded, and has to be manipulated through locking and unlocking.

Additionally, it can be checked if a lock is currently held through the held(x) syntax. held is in this case effectively a predicate that cannot be unfolded. Therefore, any semantics and techniques for manipulating predicates discussed in this part of the tutorial can be applied to held, such as splitting and scaling, except for folding and unfolding.

For more information, see the Atomics and Locks section.

Threads

A thread can be in several states, such as idle and running. The syntax for this is idle(thread) and running(thread). idle and running are effectively predicates that cannot be unfolded. Therefore, any semantics and techniques for manipulating predicates discussed in this part of the tutorial can be applied to held (such as splitting and scaling).

See the PVL Syntax Reference section for more details.

Advanced usage of predicates

Unfolding predicates within expressions using \unfolding

Note: use of \unfolding causes an error in the Java compiler. To work around this, VerCors will support the \Unfolding (notice the capital U) syntax instead of \unfolding for the Java frontend in the future. For progress, please see the relevant issue

Sometimes it is necessary to briefly open a predicate within an expression, for example, to temporarily gain access to a field. This can be achieved using the \unfolding operator. It has the following concrete syntax:

\unfolding myPredicate(arg1, arg2, ...) \in expression

It takes the instance of the predicate that needs unfolding, followed by \in, and then an expression in which the predicate must be unfolded. After evaluating the internal expression, the predicate is folded again, without any changes to the predicate or the program state.

Below is an example usage of \unfolding in a Cell class where the predicate does not have a parameter for the value inside the Cell. To work around this, \unfolding is used.

class Cell {
  //@ ensures state() ** \unfolding state() \in x == 0;
  Cell() {
    x = 0;
    //@ fold state();
  }

  //@ resource state() = Perm(x, write);
  int x;
}

public static void main() {
  Cell c = new Cell();
  //@ assert \unfolding c.state() \in c.x == 0;
}

Recursive predicates

Predicates can directly and indirectly recurse without problems, provided they are not inline. This can be used to for example model permissions over data structures with a size that cannot be known statically, such as a linked list. In the below code example, an instance of state predicate of the class LinkedListNode contains the permission for one field, as well as permissions for all fields of the tail of the list. Additionally, the argument of the state predicate reflects the numbers stored in the linked list.

class LinkedListNode {
  //@ resource state(seq<int> data) = Perm(v, write) ** Perm(next, write) ** v == data[0] ** (next != null ==> next.state(tail(data)));

  int v;
  LinkedListNode next;
}

Scaling & splitting

VerCors offers syntax to specify fractions of predicates, similar to how permissions can be specified in fractions. This can be useful to, for example, allow one half of a state predicate to be passed to one thread, and one to another. For a predicate state(args) on a variable x, the syntax for specifying a fraction f of the predicate is: [f]x.state(args). The code below shows an example of how scaling can be used.

class Cell {
  //@ ensures state(0);
  Cell() {
    //@ fold state(0);
  }

  //@ resource state(int v) = Perm(x, write) ** v == x;
  int x;
}

//@ requires [1\2]c.state(0);
public void startThread(Cell c);

public static void main() {
  Cell c = new Cell();
  // Constructing a cell yields a state predicate
  //@ assert c.state(0);
  // We can split this into fractions of a predicate
  //@ assert [1\2]c.state(0) ** [1\2]c.state(0);

  // startThread will claim half of the predicate
  startThread(c);

  // The other half can be used for something else
  //@ assert [1\2]c.state(0);
}

Inline predicates

Predicates can be given the inline attribute:

class C {
  //@ inline resource state() = Perm(x, write);
  int x;
}

If the inline attribute is given to a predicate, VerCors will inline the definition of that predicate wherever the predicate occurs. This means that the following code snippet:

//@ requires c.state();
void foo(C c) {
  //@ assert c.state();
}

Is equal to:

//@ requires Perm(c.x, write);
void foo(C c) {
  //@ assert Perm(c.x, write);
}

Because of this inlining semantics, inline predicates cannot be recursive. However, besides that limitation they are equal to regular predicates, and hence support arguments and scaling.

Furthermore, because of this inlining semantics, folding and unfolding inline predicates do not change the program state. However, for the ease of flexibility, VerCors allows them, interpreting them as a check for whether or not the contents of a predicate is present.

Inheritance

Inheritance is not yet supported for predicates.

Known problems

VerCors crashes when predicates are unfolded

When using predicates, VerCors might yield the following error:

Verification aborted exceptionally: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.util.NoSuchElementException: None.get
  Cause: java.util.concurrent.ExecutionException: java.util.NoSuchElementException: None.get

This is because the current implementation of Java inheritance in VerCors is incomplete. There are two workarounds for this.

First, a predicate declaration can be marked as final. This ensures that the inheritance-related logic in VerCors is not applied to that predicate, which means it can be used as described in this chapter. In the code below we show an example usage of this workaround.

int x;

//@ final resource state(int val) = Perm(x, write) ** x == val;

//@ requires Perm(x, write) ** x == n;
//@ ensures state(n);
void foo(int n) {
    //@ fold state(n);
}

Second, if a class has many predicates, or the first workaround does not seem to work, the whole class can be marked as final. This ensures the inheritance-related logic in VerCors is not applied to the class at all.

Both workarounds do not change the semantics of Java code that does not use inheritance.

Finally, if neither of the workarounds work, please file an issue.

Interaction between loop conditions and predicates

Consider the following example:

//@ resource wrapX(int val) = Perm(x, write) ** x == val;
int x;

//@ requires wrapX(5);
void m() {
  //@ ghost int i = 5;

  //@ loop_invariant wrapX(i);
  //@ loop_invariant \unfolding wrapX(i) \in 5 <= x && x <= 10;
  while (x < 10) {
    //@ unfold wrapX(i);
    x++;
    //@ ghost i++;
    //@ fold wrapX(i);
  } 
}

This will give an error on the while condition, stating that there is no permission for x. This is to be expected, since permission for x is contained in the predicate wrapX(i). Since we did not give instructions to unfold it, the loop condition cannot access x. Intuitively, the following extra syntax should work:

//@ resource wrapX(int val) = Perm(x, write) ** x == val;
int x;

//@ requires wrapX(5);
void m() {
  //@ ghost int i = 5;

  //@ loop_invariant wrapX(i);
  //@ loop_invariant \unfolding wrapX(i) \in 5 <= x && x <= 10;
  while (/*@ \unfolding wrapX(i) \in @*/ x < 10) {
    //@ unfold wrapX(i);
    x++;
    //@ ghost i++;
    //@ fold wrapX(i);
  } 
}

However, this syntax is currently not supported by VerCors. Instead, it is recommended to use an inline predicate if possible (see the Inline Predicates section for more info). Applying this workaround results in the following program:

//@ inline resource wrapX(int val) = Perm(x, write) ** x == val;
int x;

//@ requires wrapX(5);
void m() {
  //@ ghost int i = 5;

  //@ loop_invariant wrapX(i);
  //@ loop_invariant 5 <= x && x <= 10;
  while (x < 10) {
    x++;
    //@ ghost i++;
  } 
}

Alternatively, the condition can be lifted into its own boolean variable as in the example below. However, this 1) leads to verbose code, and 2) requires modification of non-ghost code. Note that, for presentation reasons, the loop condition p is also more strict than in the previous examples.

//@ resource wrapX(int val) = Perm(x, write) ** x == val;
int x;

//@ requires wrapX(5);
void m() {
  // Put the loop condition in a separate variable. This requires unfolding the predicate temporarily.
  //@ unfold wrapX(5);
  boolean p = 5 <= x && x < 10;
  //@ fold wrapX(5);

  //@ ghost int i = 5;

  //@ loop_invariant wrapX(i);
  //@ loop_invariant \unfolding wrapX(i) \in 5 <= x && x <= 10;
  // Require that loop maintains that p equals the previous loop condition
  //@ loop_invariant p == \unfolding wrapX(i) \in (5 <= x && x < 10);
  while (p) {
    //@ unfold wrapX(i);
    x++;
    //@ ghost i++;

    // Before the while loop ends, check the loop condition manually
    // And put the result in p again.
    p = 5 <= x && x < 10;
    //@ fold wrapX(i);
  }

  //@ assert wrapX(10);
}

Inheritance

Currently, support for inheritance is very limited. Bob Rubbens is working on improving support for inheritance as part of his PhD position. You can read theoretical work on this in his masters thesis: https://essay.utwente.nl/81338/, or contact Bob at r.b.rubbens@utwente.nl. As support improves, this wiki page will improve accordingly.

Exceptions & Goto

This section discusses support for exceptions and goto in VerCors. The following topics are discussed:

Exceptions

Support

VerCors currently only supports Java exceptions. They are not supported in PVL. We also do not support signal handlers in C. The table below lists which facets of exceptions in Java are currently supported in VerCors.

Feature Supported
throw Yes
throws Yes
try-catch Yes
try-finally Yes
try-catch-finally Yes
try-with-resources No
Multi-catch No
Defining custom exceptions Yes, but only if directly inheriting from one of: Exception, RuntimeException, Throwable, Error. This limitation is temporary.
JML signals Yes
JML signals_only No

Support for exceptions is still being worked on currently. Progress on the implementation can be followed here.

Java Exceptions Example

We will now discuss a basic artificial example of exception usage in Java. For a more thorough overview, we refer the reader to the Java tutorial on exceptions: https://docs.oracle.com/javase/tutorial/essential/exceptions/index.html.

In the following code example, the find method determines if an array contains a specific integer value:

class MyFind {
    public static boolean find(int[] xs, int value) throws Exception {
        for (int i = 0; i < xs.length; i++) {
            if (xs[i] == value) {
                return true;
            } else if (xs[i] < 0) {
                throw new Exception();
            }
        }

        return false;
    }

    public static void main(String[] args) {
        int[] myXs = int[3];
        myXs[0] = 1;
        myXs[1] = 10;
        myXs[2] = -3;
       
        try {
            find(myXs, 22);
        } catch (Exception e) {
            System.out.println("An error occurred");
        }
    }
}

If the find method encounters a negative array element while searching, it throws an exception. The fact that the method throws an exception at all is indicated by the throws Exception modifier when the find method is defined. Specifically, the exception is thrown by the throw new Exception() statement in the find method.

The thrown exception is handled in the main method by wrapping the call to find in a try-catch block. Whenever an exception is thrown within a try-catch block, the exception is passed to the catch block of the type of the exception that matches the type of the catch block. This catch block can then handle the exception to allow the program to continue, or perform cleanup such that the program can exit safely. If none of the catch blocks can handle the exception, it is passed further up the call stack, possibly terminating the program if it is not caught and handled.

The signals clause

The signals clause supported in VerCors is very similar to the signals clauses supported in JML (documented here). It is a contract element like requires or ensures, and declares the postcondition in case an exception is thrown. The declared postcondition holds when the thrown type matches the type stated in the signals clause. When an exception is thrown, normal ensures post-conditions never hold, and instead only relevant signals clauses hold.

As an artificial example, we can define a signals clause for a method that sums two numbers. The method throws an exception if one of the numers is equal to five.

//@ signals (Exception e) a == 5 || b == 5;
//@ ensures \result == (a + b);
void sum(int a, int b) throws Exception {
    if (a == 5 || b == 5) {
        throw new Exception();
    } else {
        return a + b;
    }
}

Similar to the throws attribute, the signals clause can name both checked and unchecked exceptions. The only limitation is that the type must extend Throwable.

Signals does not guarantee an exception

A frequently occurring use-case is to guarantee that an exception is thrown, if a certain condition occurs. Furthermore, this is also how the semantics of the signals clause is sometimes misinterpreted. Applying this line of though to the previous example, one might expect the method sum to always throw if one of the arguments equals five. However, this is not the case. The implementation for pickySum below demonstrates this. The implementation for pickySum also satisfies the contract for sum, but clearly pickySum does not always throw an exception if one of the arguments equals 5:

//@ signals (Exception e) a == 5 || b == 5;
//@ ensures \result == (a + b);
void pickySum(int a, int b) {
    if ((a == 5 || b == 5) && dayOfTheWeek() == "tuesday") {
        throw new Exception();
    } else {
        return a + b;
    }
}

Instead, pickySum only throws an exception if one of the arguments equals five, and today is tuesday. Would pickySum be called on a monday with 5 and 10, an exception would not be thrown, and instead 15 would be returned.

This artificial example shows how a signals clause should be interpreted: when an exception of the appropriate type is thrown, the signals clause can be assumed to hold. We call this the "reactive" semantics.

It is not guaranteed that an exception is thrown if a signals condition occurs. We call this the "causal" semantics. VerCors does not implement this currently.

If needed, there is a way to model the causal semantics using an additional ensures clause. To do this, an ensures clause needs to be added that implies false when the signals condition occurs. For example, pickySum can be made consistent as follows:

//@ signals (Exception e) a == 5 || b == 5;
//@ ensures (a == 5 || b == 5) ==> false;
//@ ensures \result == (a + b);
void consistentSum(int a, int b) {
    if (a == 5 || b == 5) {
        throw new Exception();
    } else {
        return a + b;
    }
}

By ensuring that the method cannot terminate normally if one of the arguments equals 5, it is guaranteed that an exception is thrown when one of the arguments equals 5. This encodes the causal semantics using the reactive semantics supported by VerCors.

Exception guarantees

Java guarantees that methods only throw checked exceptions if they are explicitly mentioned in the throws attribute. Unchecked exceptions can always be thrown.

VerCors does not implement this exact semantics. Instead, it assumes that any exception that can be thrown is mentioned in either the throws attribute or in a signals clause. In other words, if a method has no throws clauses, nor signals clauses, it is 100% exception safe according to VerCors. A downside of this is that the VerCors exception semantics does not 100% reflect the Java semantics. The upside is that VerCors can now guarantee that all specified exceptions are caught, as all relevant exceptions are stated explicitly.

For example, if a method does not have a signals clause stating it throws a RuntimeException, VerCors assumes this exception will never be thrown by the method. Another example, if a throw new RuntimeException() statement occurs in method M, VerCors will give an error if M does not have a signals clause for RuntimeException.

In some situations it might be necessary to opt into the more realistic Java semantics of unchecked exceptions. VerCors does not support this directly, but it can be moddeled with an additional signals clause. To do this, an additional signals clause must be added with the condition true. For example, we can modify the contract of the earlier presented sum method to allow throwing a RuntimeException randomly:

//@ signals (ArithmeticException e) a == 5 || b == 5;
//@ signals (RuntimeException e) true;
//@ ensures \result == (a + b);
void sum(int a, int b) {
    if (a == 5 || b == 5) {
        throw new ArithmeticException();
    } else {
        return a + b;
    }
}

void useSum() {
    int result = sum(5, 10);
    System.out.println("Result: " + result);
}

If this example is checked by VerCors, it will yield an error in the useSum method. The error will complain that sum might throw a RuntimeException, but that it is not specified in the contract nor handled in the useSum body.

A way to resolve this would be to catch the RuntimeException by wrapping the call to sum in a try-catch block. However, since catching a RuntimeException is bad practice, it is sometimes better to indicate in the contract of useSum that useSum might also throw a RuntimeException. This propagates the responsibility for handling the unchecked exception to the caller.

The signals_only clause

The signals_only clause from JML (documented here) is not supported by VerCors. It guarantees that the only types that will be thrown by the method are the types listed in the signals_only clause.

To simulate the functionality of signals_only T1, ..., Tn, a signals clause with the condition true can be added for each Ti. For example, the clause signals_only T1, T2; can be simulated by adding two signals clauses to a method contract as follows:

//@ signals (T1 e) true;
//@ signals (T2 e) true;
void m() {
    
}

Alternatively, the abbreviation of signals_only documented in Section 9.9.5 of the JML reference manual can also be used to approximate the semantics of signals_only in VerCors.

Goto and labels

PVL has support for goto and label. They are useful for modeling control flow primitives not yet supported in VerCors. The semantics are standard: when the goto l statement is encountered, control flow is immediately transferred to the location indicated by the label l. For example, the following program verifies:

int x = 3;
goto l;
x = 4;
label l;
assert x == 3;

As PVL does not have a construct like finally in Java, there are no exceptions for the semantics of goto in PVL.

One example where use of goto might lead to confusing results is the following. In the below example, goto is used to exit the while loop. Because goto redirects control flow to the destination label immediately, checking the loop invariant is skipped.

int r = 10;
loop_invariant r == 10;
while (true) {
  r = 20;
  goto lbl2;
}
assert r == 10; // Never executed
label lbl2;
assert r == 20;

If it is desired that the loop invariant is checked at goto as well, this can be modeled by adding asserts at the exit labels of the while loop.

VeyMont

VeyMont is a tool for verification of concurrent message passing programs. The tool has been build on top of VerCors. VeyMont decomposes the global program from the input files into several local programs that can be executed in parallel. The program from the input files has to adhere to the syntax of a 'global program'. Syntax violations result in VeyMont Fail messages. The decomposition preserves the behaviour of the global program. This implies that all functional properties proven (with VerCors) for the global program also hold for the local program. Also, both global programs and their decomposed local programs are deadlock-free by construction. Memory and thread safety can be checked by running VerCors on the file produced by VeyMont.

User documentation.

Installing and running VeyMont

First build VerCors from source (as there has not been a release with VeyMont yet), following the instructions from the README on GitHub. Then run VeyMont by executing the following command:

./bin/vct --veymont <filepath-to-output-file-for-local-programs> <filepath-to-global-program>

Here <filepath-to-global-program> is the input file you provide to VeyMont, and <filepath-to-output-file-for-local-programs> is the name of the file VeyMont should write to. Both files need to be .pvl files. Example global programs can be found in examples/veymont-global-programs.

Using VeyMont for verifying concurrent message-passing programs

Please read the paper that has been submitted to FM2021. In particular section 2 "Overview of the approach" walks the reader through an example. This section elaborates each of the following steps:

  1. Write a global program in PVL (an input language of VerCors).
  2. Add annotations to the global program for proving memory safety and func- tional correctness.
  3. Provide the global program to VerCors for verification. If verification fails, go back to step 1 (incorrect global program) or step 2 (incorrect annotations).
  4. Provide the verified global program to VeyMont for decomposition. If Vey- Mont rejects, go back to step 1; if it accepts, it generates local programs.
  5. Provide the local programs to VerCors for additional verification of memory safety and thread safety (optional step).

More details on the syntax requirements to global programs are given in section 4.1 "Global programs" of the paper. The global programs from examples/veymont-global-programs are discussed in section 5 "Case studies".

Developer documentation

VeyMont executes the following passes:

Term Rewriting Rules

VerCors allows you to define your own term rewriting rules via jspec files. This chapter shows you how.

Magic Wands

The handling of magic wands is non-trivial, so this chapter takes a closer look at that. For further reading, see e.g. "Witnessing the elimination of magic wands". Blom, S.; and Huisman, M. STTT, 17(6): 757–781. 2015.

Also take a look at the Viper tutorial for wands: http://viper.ethz.ch/tutorial/?page=1&section=#magic-wands

What are Magic Wands?

The "Magic Wand" or separating implication is a connective in separation logic using the symbol -*. It represents a part of a resource (see "Resources and Predicates") or "resource with a hole": A -* B means "if you give me an A, I can give you a B in return". Thus, it is similar to a logical implication A ==> B; however, it consumes the A when it is exchanged for a B.

A common use case is if you have a predicate defining a recursive data structure like a linked list or a binary tree, and you take a part of that data structure to reason about explicitly, like a sub-tree. You can use the recursive predicate to reason about the sub-tree, but it is more difficult to reason about the remainder of the original tree: Since you took out the sub-tree and have explicit permission for it, the recursive predicate for the whole tree can no longer have the permission for that part, so it would have to stop recursing at a certain point. With a wand, you can resolve this: For a tree T with left sub-tree L and right sub-tree R, and a recursive predicate Tree(x), you can split Tree(T) into Tree(L) ** (Tree(L)-*Tree(T)). So you have the explicit resource for the left sub-tree, and a wand such that if you join the sub-tree back into it, you get the predicate for the whole tree back. This joining is called applying the wand.

Syntax in VerCors

We will use the following binary tree as example:

class Tree {
  int value;
  int priority;
  Tree left;
  Tree right;

  /*@
  static resource tree(Tree t) 
  = t != null ==> Perm(t.value, write) ** Perm(t.priority, write) ** Perm(t.left, write) ** Perm(t.right, write) 
                  ** tree(t.left) ** tree(t.right);

  requires tree(t);
  static pure boolean sorted(Tree t) 
  = t!=null ==> \unfolding tree(t) \in 
    (t.left != null ==> sorted(t.left) && \unfolding tree(t.left) \in t.priority >= t.left.priority) 
    && (t.right!= null ==> sorted(t.right) && \unfolding tree(t.right) \in t.priority >= t.right.priority);
  @*/
}

So a Tree contains a data item value, a priority and two references to the sub-trees; the tree resource contains write permissions for all those fields, as well as recursive permissions for the sub-trees; and the sorted function ensures that the priority in the root node is the largest in the Tree (like in a priority queue).

Creating a Wand

To create a wand, use the keyword create {...}. Inside the curly braces, you have to provide the necessary information to create the wand:

You need to explicitly specify which resources from the current context should be folded into the wand. The keyword for this is use. Note that, like with folding a normal predicated, any permissions folded into the wand are no longer available in the environment. You can also use boolean facts from the current context, e.g. use a==b;.

When creating a wand A-*B, you need to describe how exactly to merge A with the content of the wand to obtain B. For example, A may contain permissions for the left sub-tree and the wand those for the right sub-tree, and to get the full tree, you need to perform a fold. All necessary permissions and boolean facts for such a merger must be provided via use.

The final statement within the curly braces of create has to be qed A-*B;, with A-*B being replaced by the wand you created.

Note that the wand operator -* has the precedence like an implication, thus it is less binding than the separating conjunct ** and boolean connectives like &&.

/*@
requires t != null;
requires tree(t);
ensures tree(\result);
ensures tree(\result) -* tree(t);
@*/
static Tree get_left(Tree t) {
  //@ unfold tree(t);
  Tree res = t.left;
  /*@
  create {
    use Perm(t.value, write) ** Perm(t.priority, write) ** Perm(t.left, write) ** Perm(t.right, write);
    use tree(t.right);
    use res == t.left;
    fold tree(t);
    qed tree(res) -* tree(t);
  }
  @*/
  return res;
}

Notice how in the example, all the permissions necessary for folding tree(t) are provided with use, except the part that is in the premise of the wand, tree(t.left). If we had provided that as well, it would have become part of the wand, rather than being the "missing piece", and the first post-condition ensures tree(\result) would have failed for lack of permission (since this permission is bundled into the wand, and no longer directly available). Additionally, we had to provide the fact that res is the left sub-tree of t, so that fold tree(t) knows that the premise of the wand fits into the missing part of the predicate.

Applying a Wand

To combine the "missing piece" with the wand and obtain the full resource, use the keyword apply. Since we already specified how the merging works when we created the wand, no further logic is needed now:

/*@
context t != null;
context tree(t);
requires \unfolding tree(t) \in t.left != null;
@*/
static boolean check_left_priority(Tree t, int max_priority) {
  Tree left = get_left(t);
  // now we have tree(left) and a wand tree(left) -* tree(t)
  //@ unfold tree(left);
  boolean res = left.priority <= max_priority;
  //@ fold tree(left);
  //@ apply tree(left) -* tree(t);
  // now tree(left) is no longer directly available, but tree(t) is back
  return res;
}

More Complex Expressions

Sometimes, we may want to reason about the things contained in a wand; for example we decompose a sorted tree, and want to say it will be sorted again after the merger. However, as long as tree(t) is split into the sub-tree and the wand, we cannot call sorted(t) as it needs the full predicate tree(t). But simply ignoring the sortedness when splitting and later merging again would mean that we can no longer make any assertions about sortedness afterwards. As a solution, we encode the property into the wand, by adding conjuncts to the premise and antecedence of the wand: A**B -* C**D. We can only apply such a wand if we hold the permissions of A and the properties in B hold. This stricter requirement is rewarded with the fact that we will get both the permissions in C and the properties of D after applying the wand.

Note that VerCors currently only allows method calls as conjuncts in wands, so properties like an equality a==b must be wrapped in a function in order to be used.

/*@
requires tree(t) ** t != null;
static boolean wand_helper(Tree t, int max_prio)
  = \unfolding tree(t) \in t.priority <= max_prio;
@*/

/*@
yields int root_prio;
requires t != null;
requires tree(t) ** sorted(t);
requires \unfolding tree(t) \in t.left != null;
ensures \result != null ** tree(\result);
ensures sorted(\result) && wand_helper(\result, root_prio);
ensures tree(\result) ** sorted(\result) ** wand_helper(\result, root_prio) 
        -* 
        tree(t) ** sorted(t);
@*/
static Tree get_left_sorted(Tree t) {
  //@ unfold tree(t);
  //@ ghost root_prio = t.priority;
  Tree res = t.left;
  /*@
  create {
    // necessary parts for tree(t)
    use Perm(t.value, write) ** Perm(t.priority, write) ** Perm(t.left, write) ** Perm(t.right, write);
    use tree(t.right);
    use res == t.left;
    // necessary parts for sorted(t)
    use res != null;
    use t.priority == root_prio;
    use t.right!= null ==> sorted(t.right) && \unfolding tree(t.right) \in t.priority >= t.right.priority;
    // assemble parts
    fold tree(t);
    qed tree(res) ** sorted(res) ** wand_helper(res, root_prio) 
        -* 
        tree(t) ** sorted(t);
  }
  @*/
  return res;
}

/*@
context t != null;
context tree(t) ** sorted(t);
requires \unfolding tree(t) \in t.left != null;
@*/
static boolean check_left_priority_sorted(Tree t, int max_priority) {
  //@ ghost int root_prio;
  Tree left = get_left_sorted(t) /*@ then{root_prio = root_prio;} @*/;
  // now we have tree(left), sorted(left) and wand_helper(left, root_prio), as well as the wand
  //@ unfold tree(left);
  boolean res = left.priority <= max_priority;
  //@ fold tree(left);
  //@ apply (tree(left) ** sorted(left) ** wand_helper(left, root_prio)) -* (tree(t) ** sorted(t));
  // now tree(left) is no longer directly available, but tree(t) is back and sorted
  return res;
}

Now, when creating the wand, we need to use additional information in order to be able to ensure sortedness after the merger: The condition for the right side can simply be provided. The condition for the left side is part of the premise of the wand (in particular sorted). The comparison between the priority of the root and the priority of the left node is wrapped into a helper function, wand_helper. Since the premise of the wand contains no permissions for t.priority, we use a ghost variable root_prio to communicate that value.

Applying the wand is nearly as simple as before. However, we again need to use the ghost variable root_prio as an intermediate storage for the priority of t.

You see that adding such additional constraints about the wand's resources is possible, but can carry significant overhead of helper functions, use statements, etc.

Inhale and exhale

// TODO: Explain inhale and exhale statements (add warning!)

What does this VerCors error message mean?

This page is to explain what the different error messages in VerCors mean. We try to keep this page alphabetically ordered by error message (case insensitive sorting. For sentences: spaces come before letters in the alphabet). If you have a question about error messages, please add a question to this wiki page. If you know the answer to a question asked here, please replace the question with the answer (or with a link to the answer elsewhere on the wiki).

AssignmentFailed: insufficient permission

This means you are trying to write to a variable (at the left hand side of an assignment) to which VerCors cannot establish a write-permission. The statement assert(Perm(lhs,1)); in which lhs is the left hand side of this assignment will fail at this point in your code, but should be provable.

Illegal argument count

This is a internal VerCors error, that might be due to a parse-error in your script. We have opened a issue for this here.

Illegal iteration invariant

This seems to happen when you try to specify a loop_invariant in a for-loop, where you're using a resource as invariant. Try using context (short for ensures and requires combined) instead. Are you getting this message in a different situation? Do let us know!

java.lang.*

This is never supposed to happen, but unfortunately, it did. Thank you for finding this bug in VerCors. Try searching our issue tracker to see if you believe this bug is already being addressed. If not, please let us know about it by adding it!

No viable alternative at ...

This is a parsing error, you may have made a typo, or inadvertently used a reserved keyword as a variable. Check your code at the position indicated. If this is the start of a line, make sure there is a semicolon ; at the previous line.

NotWellFormed:InsufficientPermission

This error is shown at a specification. We require rules to be 'self framing', this means that you need read-permission in order to access variables, even inside specifications. Furthermore, checks like array accesses being within bounds need to hold. The order of the specifications makes a difference here: the lines of your specifications are checked from top to bottom, and from left to right. In order to establish permissions and bounds, add a line of specification before the line that gives you trouble, asserting that you have read permission, or that the access is within bounds.

If you see this error in the \old(..) part of an ensures expression, you need permission to that variable before executing the function. For constructors (e.g. foo method of class foo), there is no \old(this.x), as the variable this.x is only created when the constructor is called.

Pre-condition of constructor may not refer to this

When calling the constructor method of a class, the class variables are not yet initialised. Therefore, you should not refer to them. Do you need write-permission? Don't worry, the class constructor already has it by definition (as it implicitly creates the variables)!

Type of left/right argument is Resource rather than Boolean:

This is a type error. You are using a symbol like && that requires boolean values on both sides. Changing && to **, or breaking up your specification into multiple lines might solve the problem for you.

Verification aborted exceptionally: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: java.util.NoSuchElementException: None.get

See VerCors crashes when predicates are unfolded.

PVL Syntax Reference

On this page you can find a description of the syntax of PVL, Prototypal Verification Language; one of the languages for which VerCors supports verification.

General

class ClassName {
    // Here you can add any constructors, class variables and method declarations
}
// This is a single-line comment
/* 
    This is a 
    multi-line comment 
*/

Types and Data Structures

Type Description
int Integer
boolean true or false
void Used when a method does not return anything
resource Boolean-like type that also allows reasoning about permissions
frac Fraction
zfrac Fraction that can also be zero.
process Type of actions and defined processes in histories. For more information on histories & futures, have a look at the section on Histories & Futures.
T[] Array which contains elements of type T. T should be replaced by a type. Note that when you initialize a new array, you should always define the length of the array, e.g. new int[3] instead of new int[].
seq<T> var Defines an immutable ordered list (sequence) named var. T should be replaced by a type.
set<T> var Defines an immutable orderless collection (set) that does not allow duplicates. T should be replaced by a type.
bag<T> var Defines an immutable orderless collection (bag) that does allow duplicates. T should be replaced by a type.
option<T> Extends type T with an extra element None. Each element is then either of the type None or of the type Some(e) where e is of type T. T should be replaced by a type. Options cannot be unpacked at the moment.

For more information on sequences, sets, bags and options, have a look at the wiki page on Axiomatic Data Types.

Expressions

Infix Operators

Code Description
==, != Equals and not equals for reasoning about the equality of expressions
&&, `
<, <=, >, >= Smaller than, smaller than equals, greater than and greater than equals respectively. They are used to compare integers.
+, -, *, /, \ Addition, subtraction, multiplication, integer division and fractional division respectively.
++, -- Increase by 1 and decrease by 1 respectively. Unlike the other operators, this only requires an variable on the left-hand side.
==> Implication. This statement evaluates to true unless the statement before the arrow evaluates to true and the statement after the arrow evaluates to false.
** Separating conjunction. a ** b denotes that a and bpoint to different variables on the heap and that both expressions mus tevaluate to true. This is used to reason about multiple resources.
-* Magic wand or separating implication. This is used to reason about resources. //TODO: investigate what is supported.
new T() Creates a new object of type T.
new T[length] Creates a new array which contains objects of type T with length number of items.
boolExpr ? exprA : exprB; Evaluates boolExpr, if this is true it will return exprA and otherwise exprB.
(\let T t = exprA; exprB) Evaluates exprB, replacing every occurence of t with the result of evaluatingexprA. The surrounding parens ( ) are required.

Quantified Expressions

Code Description
(\forall vars; range; boolExpr) Construct that allows you to repeat a certain expression for several variables. For example, (\forall int j; j >= 0 && j < n; array[j] >= 0) denotes that all elements in array nonnegative. It is equal to the following statement: array[0] >= 0 && array[1] >= 0 && ... && array[n-1] >= 0. vars should declare the variables over which we will reason. range can be any boolean expression, often used to describe the range of vars. boolExpr is some expression that should evaluate to a boolean for all variables in the given range.
(\forall* vars; range; expr) Similar construct to the \forall except that the expressions are separated by ** instead of &&. One can for example write (\forall* int j; j >= 0 && j < array.length; Perm(array[j], write) which denotes that the thread has writing access to all elements in array.
(\exists Type id; range; expr) Evaluates to true if there exists an element, called id, such that the final expression evaluates to true.
array[*] This is a simplified \forall* expression that ranges over all elements in the array array. Instead of the example mentioned above for \forall*, you can then write Perm(array[*], write). This cannot be used within nested \forall* expressions.

Specification-only Expressions

Code Description
\result Keyword that refers to the object that the method returns
\old(expr) Refers to the value of the specified expression in the pre-state. This can be used in a postcondition (ensures) or loop invariant in which case the pre-state is when the method was called.
held(x) Check whether you are holding a non-reentrant lock. x should refer to the lock invariant. See also: Queue.pvl.
idle(t) Returns true if thread t is idle (before calling t.fork() or after calling t.join()). Overall a thread starts as idle, if the thread is forked, it goes to a 'runnning' state. If join is called on a running thread, then it goes back to an 'idle' state.
running(t) Returns true if thread t is running. Overall a thread can go through the following states: idle --[t.fork()]--> running --[t.join()]--> idle

Resources

Code Description
Perm(var, p) Defines permissions for variable var. If p is 1 or write then it denotes write permission, anything between 0 and 1 or read denotes read permission. Be aware that you cannot use arithmetic when using read such as read/2 or dividing read among multiple threads! Perm() is of the type Resource.
PointsTo(var, p, val) Denotes permissions p for variable var similar to Perm(). Moreover, variable var points to the value val. PointsTo() is of the type Resource.
Value(var) Defines a read-only permission on the given variable. This read-only permission cannot become a write permission and it can duplicate as often as necessary.

Control flow constructs

Control flow construct Example
Function contract returnType functionName(paramType paramName, ...) { ... }. contract should describe the specification of the method. returnType should be replaced by a specific type or void depending on what (if anything) the method returns. functionName should be replaced by the name of the function. paramType paramName, ... is a comma-separated list of parameters, for every parameter you should define its type and its name. It is also possible to have no parameters. For example: ensures \result == a + b; int sum(int a, int b) { return a + b; } is a function which returns an integer that is the sum of the parameters given.
Pure and Abstract Functions: Pure functions are declared by prepending modifiers static and pure in that order. They should be side-effect free and their postconditions must not contain resource assertions such as accessibility predicates. All accessibility constraints for the body of the function and for the postcondition should be ensured by the preconditions. In fact, since pure functions applications are side-effect-free, pre- and post-states of a function application are the same. Example:
requires a != null;
requires 0 <= n && n <= a.length;
requires (\forall* int j; 0 <= j && j < a.length; Perm(a[j],1\2));
ensures (n==a.length)? \result == 0 : \result == a[n] + sumAll(a,n+1);
static pure int sumAll(int []a, int n);
Notice that in the example the function has no body. By doing so we declare an abstract function. When calling this function, its pre-conditions will be checked and its postconditions assumed. No correspondence should be assumed between pure and abstract functions.
Return return can be used to exit the current method. return expr can be used within a method to return a specific object as a result.
If-statement
  • Single-line option: if (boolExpr) ...;
  • Multi-line option: if (boolExpr) { ... }
If-then-else if (boolExpr) { ... } else { ... }
For-loop loop_invariant p; for (...) { ... }
While-loop loop_invariant p; while (boolExpr) { ... }
Parallel block par contract { ... } OR par identifier(iters) contract { ... }. The identifier is optional and iters can be empty. iters specifies what variables are iterated over. Note that you can also extend a parallel block with another parallel block as follows: par contract { ... } and contract { ... } OR par identifier(iters) contract { ... } and identifier(iters) contract { ... }.
Vector block vec (iters) { ... } is a variant of the parallel block where every step is executed in lock step. You do not need to specify a pre- and postcondition. iters should define what variables are iterated over, e.g., int i = 0..10.
Atomic block atomic(inv) { ... } performs the actions within the block atomically. As a result, other threads will not be able to see any intermediate results, only the result before or after executing the atomic block. inv refers to an invariant which stores permissions that are necessary when executing the atomic block.
Barrier barrier(identifier) contract { } waits for all threads to reach this point in the program, then permissions can be redistributed amongst all threads, as specified in the contract, after which the threads are allowed to continue. The barrier needs to know how many threads should reach it before everyone is allowed to continue, this is done by specifying identifier which refers to a parallel block before the barrier.
Fork a thread fork expr starts a new thread.
Join a thread join expr waits for the specified thread to complete.
Wait wait expr will pause the thread until it is notified to continue.
Notify notify expr will notify another thread that it may continue if it is waiting.
Acquire a lock lock expr
Release a lock unlock expr
Label label l indicates a location in the program that can be jumped to with goto l.
Goto goto l indicates control flow must be transferred to the location of the label l.

Note that for-loops and while-loops should be preceded with a contract consisting of one or more loop invariants. Parallel blocks require a contract in the form of requires/ensures clauses.

Verification flow constructs

Code Description
assert expr Defines an assert statement expr which describes a condition that should hold at the program point where this statement is defined.
assume expr Defines a statement that assumes that expr holds. It can be put anywhere within the code.
requires expr Defines the precondition as expr, i.e., expr must hold when calling this method. The precondition should be specified before the method declaration.
ensures expr Defines the postcondition as expr, i.e., expr must hold when completing this method. The postcondition should be specified before the method declaration.
context expr This is an abbreviation that combines the statements requires expr and ensures expr. This statement should be specified before the method declaration.
loop_invariant expr Defines a loop invariant expr which is a condition that must hold when entering the loop and after each loop iteration. A loop invariant should be specified before the corresponding loop.
context_everywhere expr This is an abbreviation that combines the statement requires expr and ensures expr. Moreover, it also adds loop_invariant expr to all loops within the method. This statement should be specified before the method declaration.
given T p Defines that a ghost input parameter (specification-only parameter) of type T with the name p is passed when this method is called. This statement should be specified before the method declaration.
yields x Returns a ghost output parameter (specification-only parameter) to the callee of this method. This statement should be specified before the method declaration.
with ... then ... with is used to pass a parameter to a method (which has a given statement) and then can be used to store a returned value from a yields statement. This statement is specified after the corresponding method call (on the same line) where you want to pass the parameter. All the ... should be replaced by an assignment.
unfolding ... in expr Temporarily unfold definitions in (pure) expressions. The ... should be replaced by a predicate.
refute expr Disproves expr at the given program point. This statement can be put anywhere in the code. Internally it is translated to assert !(expr).
inhale p Take in the specified permissions and properties.
exhale p Discard the specified permissions
fold x Wrap permissions inside the definition
unfold x Unwrap a bundle of permissions
witness x Declares witness names. This feature is deprecated and should no longer be used.

Histories & Futures

Defining processes

Code Description
process Type of actions and defined processes in histories.
process1 + process 2 Do either process1 or process2
process1 * process 2 Sequential composition, do process1 followed by process2
`process1
Code Description
History hist Declare a History object called hist
HPerm(var, p) History-specific permissions where var refers to a variable in the history and p is the amount of permissions (a value between 0 and 1)
Hist(var, p, val) Similar to PointsTo, it denotes permissions p for variable var (which is in a history). Moreover, variable var points to the value val.
AbstractState(h, boolExpr) This can be used to check that the given boolean expression holds in the history (or future) h
action(h, perm, preState, postState) { ... } The action describes how the state of the history (or future) is changed by the code within { ... }. The pre- and post-state describe the state of the history (or future) in terms of processes. perm describes the permissions we have on the history (or future) and h is the name of the history (or future)
create h Create a history. Note that the History class should be instantiated beforehand.
destroy h, val Destroy a history h which has the value val
split h, p1, val1, p2, val2 Split a history (or future) into two such that the first part has permissions p1 and value val1 and the second part has permissions p2 and value val2
merge h, p1, val1, p2, val2 Merge two histories (or futures) such that resulting history h has permissions p1+p2 and consists of combination of the actions described in val1 and val2
modifies vars List of locations that might be modified
accessible vars List of locations that might be accessed
Code Description
Future f Declare a Future object called f
Future(f, p, val) It denotes that we have permissions p on future f of which the state is val
choose f, p1, pre, post //TODO check this support and definition
create f, val Create a future f with the initial state val
destroy f Destroy the future f

Other

VerCors usage & debugging cheat sheet

Information & questions

https://github.com/utwente-fmt/vercors/wiki

If you're doing a project with VerCors, ask us about the VerCors community chat.

Getting VerCors up and running

Required: at least java 11

Pre-build

Should be just unzip and go.

Compile from source

Requires java 11 & sbt. Benefits: allows setting breakpoints in intellij. See VerCors README/Wiki for details.

General usage

vercors inputFile.java --backend

Where backend can be either silicon or carbon.

General flags

Of course, --help

Backends: --carbon (slower, but based on boogie, so sometimes more powerful), --silicon (faster, built by ETH Zurich, sometimes not as smart as Carbon)

Progress printing of individual passes: --progress

Slows down execution but prints more debug info upon crashes: --debug vct.main.Main

Triggers a system notification upon completion: --notify

Advanced verification flags

Detection of unsound preconditions

Flag: --disable-sat

Turns off detection of unsound preconditions. Not needed in general use of VerCors. Might be necessary if you have a lot of foralls in preconditions, and verification is slow.

Java example:

class C {
    //@ requires 1 == 2; // <-- Normally this causes an error, --disable-sat turns this off    
    void m() {

    }
}

Debugging flags

Flags: --show-before passName, --show-after passName

Show textual representation of internal AST before and after a pass. Pass names can be seen by running VerCors with --progress.

VSCode plugin + flags needed

Dump AST given to silicon/carbon into a text file: --encoded temp.sil

This can be opened in VScode with the ETH Zurich Viper plugin/extension installed (see the vscode store for this).

Note:

Some starter example files

Case Studies

This page lists all the case studies that have been done with VerCors.

Published case studies

Student projects

Be aware that not all student projects verify complete functional correctness!

Master projects

Bachelor projects

Meta

This page lists the software, deployments, credentials and documentation in the orbit of the VerCors project.

Primary projects:

Secondary maintained tooling:

VerCors (the tool)

VerCors Website

VerCors Server (online tool runner)

This simply makes the tool available to run on the internet.

GitLab Mirror

tool-server

Minimal piece of software that makes a command line tool available to stream over a websocket. Not meant to be deployed directly.

ANTLR4 Fork

The Wiki

Docker Hub

Phabricator

Maintained by UTwente, used for various projects

Developing for VerCors

Workflow

Below you can find a description of what a typical workflow looks like.

  1. Determine what you want to fix.
  2. If there is an issue for what you want to fix, assign yourself to the issue. This can be done by navigating to the specific page of the issue. Then on the right-hand side there is an "Assignees" header. Beneath this header click on "assign yourself".
  3. Create a fork of VerCors if you have not done this yet. The installation instructions can be found on the main Github page (https://github.com/utwente-fmt/vercors).
  4. Navigate to the VerCors project on your computer in your terminal.
  5. When you're inside the vercors directory, create a new branch by executing the following command: git branch branch_name. Replace branch_name by the name for your branch. This name can reflect what you will fix. For example if you are going to fix issue 1, then you can name the branch "issue1". Or if you add support for automatic generation of invariants, then you can name the branch "automaticInvariants".
  6. Then switch to the new branch using the command git checkout branch_name.
  7. You can now start working on your problem!
  8. When you are done with your fix, commit and push your changed files to the branch. To push to your new branch you can use the following command:git push origin branch_name.
  9. Navigate to the main GitHub page of VerCors (https://github.com/utwente-fmt/vercors/).
  10. Create a new pull request (see also: https://help.github.com/articles/creating-a-pull-request-from-a-fork/).
  11. As soon as this pull request has been reviewed by at least one person, and is accepted, then your fix can be merged into the master branch. Congratulations you're done with this PR (Pull Request)!

Review guidelines

Below you can find a list of things to take into account when reviewing a pull request. Note that these are also things that you can take into account before submitting a pull request.

All questions above should typically be answered with "Yes". If not, then you may want to request changes on the pull request before accepting the changes.

Project Structure

Functional Overview

VerCors verifies programs by going through three stages:

There are also a few cross-cutting concerns:

Technical Setup

The VerCors project sources are managed on GitHub, at https://github.com/utwente-fmt/vercors. The unstable development branch is dev, from where we branch to develop new features and bugfixes. Those with access may push feature branches to the main repository, or create a fork if they prefer. A pull request is then made and reviewed by someone appropriate.

Pushed commits as well as pull requests are checked via continuous integration, currently Travis + Sonarcloud. Travis builds Vercors and runs the test suite. The (committed) code is linted by Sonarcloud. Once the checks pas and a code review is completed, the PR is merged in dev. In dev is also where we hope to notice bad interactions between new features.

We aim to make a new VerCors release once per month, which is done via a tagged merge commit to the master branch, followed by a GitHub release.

VerCors is written in java and scala. Code is accepted in either language, but all things being equal scala is preferred. The build structure is defined in the scala build tool. We use the sbt-native-packager plugin to package the tool as a .deb and a .tar.gz (compiled). The sbt-buildinfo plugin supplies the tool with run-time access to versioning and build time information.

Project Structure

NB: this information quickly becomes out of date as vercors is refactored.

/

/.travis

/bin

Convenience run scripts that targets the last-compiled classes in the vercors directory. This is the least stable method of running Vercors. If bleeding edge features are not needed, use the release version. If possible, run Vercors imported in an IDE instead. Failing that, the run scripts can be useful.

/examples

This directory serves the dual purpose of being an archive of past case studies and competition results, as well as the test suite of VerCors. Files in this directory have a header denoting how they are used in the test suite. Names denoted by "case" or "cases" are collected, where overlapping names are joined together as one test case. Files may occur in more than one "case." "tools" denotes the backend used (silicon by default), "verdict" the expected final result (Pass by default).

/parsers

A sub-project of Vercors that implements the parsing infrastructure of Vercors.

/project

An artifact of how SBT works. SBT projects are layered, not in the sense that we have sub-project dependencies, but that you can define meta-projects. This directory defines a meta-project, which means it is available in compiled form in the upper build definition. We just use it to add some plugins to the compiler.

/SplitVerify

This tool is not part of VerCors itself, but interacts with VerCors to cache the verification result of parts of a test case. That means that changing part of a test case only requires re-verifying the changed part. Refer to the readme there for further information.

/util

/src

The sources associated with the top-level SBT project, i.e. the main part of VerCors.

/src/universal/res

Here live the resources of VerCors. Normally they would be in a directory res or so in the root, but we have a custom solution to ensure that they remain regular files, even when packed into a jar by the build tool. Universal refers to the native-packager also being called the "Universal" plugin. "res" is the name of the directory in which the resources end up in .tar.gz deployment.

/src/main/scala

Please don't use this directory anymore. Scala sources also compile fine when included in the java directory, so this "mirror" of the package structure only serves to confuse where classes are defined. Only a few rewrite passes are defined here.

/src/main/java

/src/main/java/vct

/viper

More logic to interface with the viper backend(s), unclear where exactly the cut is with regards to /src/main/java/vct/silver.

/viper/hre

The general utilities project ("Hybrid Runtime Environment"), located here so /viper may depend on it. From here under src/main/java:

IDE Import

This document gives instructions on how to configure a development environment for VerCors, using either Eclipse or Intellij IDEA. Below the instructions there is a list of encountered errors, you might want to check there if you encounter one. The setup for IntelliJ IDEA is considerably easier, so we recommend using that.

Configuring VerCors for Eclipse

First make sure that Eclipse is installed. The installation instructions described below have been tested with Ubuntu 18.04 and Eclipse version 4.11.0 (March 2019) from snap, not apt. The version from the eclipse website should be equivalent. Also make sure that VerCors has been installed according to the instructions given on the main Git page.

Install the Scala IDE plugin for Eclipse. Download- and installation instructions can be found here. After providing the installation/update site in Eclipse, make sure that all the suggested options are selected for installation. After installation has completed, navigate to Help > Install new software > Manage, and go to Scala > Compiler. We have to give scalac more memory to compile VerCors than is available by default, so enter "-J-Xmx2048m" under "Additional command line options" to increase the limit to 2048MB.

Install the eclipse plugin for sbt, instructions for which can be found here. It is recommended that you install it in the global file, and not in the vercors file, as we do not want to add eclipse-specific configuration in the repository. Create a global settings file for SBT (e.g. in ~/.sbt/1.0/global.sbt) and add the setting EclipseKeys.skipParents in ThisBuild := false

Configuring the VerCors modules

  1. Navigate to the vercors subdirectory using a terminal, and run sbt eclipse. This will generate all the eclipse projects necessary to build vercors within eclipse.
  2. In eclipse, go to File > Import and select General > Existing Projects into Workspace. Select the vercors subdirectory, and tick Search for nested projects. If all went well, it should list all the vercors subprojects (vercors, hre, parsers, viper, silicon, etc.). Click finish.
  3. Eclipse will automatically build vercors; wait for it to finish. If eclipse fails to build the projects in this stage, unfortunately the projects are left in a broken state. You should remove all the projects or clear the workspace, and try again.

Running VerCors from Eclipse

To run VerCors from Eclipse, and thereby allow debugging within Eclipse, a Run Configuration needs to be created and used. To create a run configuration, do the following. In the menu, navigate to "Run > Run Configurations...". Select "Java Application" in the left menu and press the "New" button/icon in the toolbar. From here you can assign a name to the new configuration, for example "VerCors". Under "Project" select the "Vercors" project. Under "Main class" select or type vct.main.Main. Under the "Arguments" tab, type ${string_prompt} in the "Program arguments" field, so that every time VerCors is run with this new configuration, a pop-up window will be given in which arguments to VerCors can be specified. Moreover, in the "VM arguments" field, type -Xss16m, which increases the stack size to 16MB for VerCors runs (the default stack size may not be enough). Set the working directory to the root directory of the git repository.

After performing these steps, you should be able to run VerCors from Eclipse via the "Run" option (do not forget selecting the new run configuration). The output of VerCors is then printed in Eclipse console view. To validate the configuration, try for example "--silicon examples/manual/fibonacci.pvl". If you get an error like NoClassDefFoundError: scala/Product, perform the workaround listed under "I got an error!" below.

Configuring VerCors for IntelliJ IDEA

The steps below were testing with Ubuntu 18.04 and Intellij IDEA community edition, version 2019.1.1 retrieved via snap. Also make sure that VerCors has been installed according to the instructions given on the main Git page.

When first installed, go to Configure > Plugins, or when already installed go to File > Settings > Plugins. Install the Scala plugin, and restart the IDE as suggested.

Configuring the project

  1. Go to Open when first installed, File > Open otherwise, and open vercors/build.sbt. Open it as a project as suggested. (For older IDEA versions: in the dialog "Import Project from sbt", you should tick "Use sbt shell: for imports" and "for builds")
  2. Wait for the running process to complete, which configures the whole project.
  3. Open File > Settings, go to Build, Execution, Deployment > Build Tools > sbt, select the "vercors" sbt project (likely the only one) and tick use for [x] project reload and [x] builds. (Not needed in old versions of IDEA)

Running VerCors from IntelliJ IDEA

Go to Run > Edit configurations, and add a Java application with the following settings:

You should now be able to run and debug VerCors from within IntellJ IDEA! The first time the run configuration is used the project will be built in full, so that will take some time.

Configure SonarLint for VerCors in IntelliJ

SonarCloud runs code quality checks e.g. when you merge a branch. SonarLint gives you the same warnings as SonarCloud but now inside IntelliJ, while you work on the code. SonarLint also supports Eclipse, Visual Studio and VS code. While this instruction focusses on IntelliJ, the installation steps for other IDEs will likely be similar.

  1. Install the SonarLint Plugin by following the installation instructions.
  2. Add a connection which synchronises the rules of SonarLint with those of SonarCloud.
  3. Select a Sonarcloud project for SonarLint.

I encountered an error!

Here is a collection of errors we encountered, with a solution. Is your problem not here? Please file an issue!

ScalaTest

Running ScalaTest

ScalaTest can be run by the commandline or via the Intellij. For running with Intellij you need the Scala plugin of JetBrains. VerCors uses a lot of memory that is why you have to add an VM option to the ScalaTest template. Go to Run/Debug Configuration. Click on the blue text "Edit configuration templates..." in the left corner. Select ScalaTest. Select Configuration. Set VM options to "-Xss128M". You can run the test by right clicking a test file and selecting the option "Run '{filename}'". To run a single test you can open the file and clicking on the run arrow between the line number and the text of the file. The test files are in "src/test/scala/". Intellij also supports running the test in debug mode by selecting debug mode. Running from the commandline can be done using sbt. The following command runs all the tests. sbt test The following command runs all the test in the file integration.Generated1Tests. sbt "testOnly integration.Generated1Tests"

More settings can be found in https://www.scalatest.org/user_guide/using_scalatest_with_sbt Note that the run settings like classpath and available memory can be different from Intellij and sbt.

Wiki Code Snippet Testing Harness

VerCors supports extracting tests from the tutorial and automatically running the extracted tests. The following syntax is recognized in the tutorial. For example usages of this syntax, see the raw version of the chapter of Axiomatic Data Types. As the annotations will be hidden when viewing the rendered markdown, click on "edit page"

Self-contained code blocks

Testcases defined by template, e.g.:

<!-- testBlock Fail -->
```java
assert false;
```

There are three possible code block annotations:

testBlock and testMethod are compatible with Java and PVL.

The case name of the generated test is derived from the heading structure.

Snippets

To combine multiple code blocks into one test case (potentially with hidden glue code), you can use Snippets. The markdown for that might look like this:

<!-- standaloneSnip smallCase
//:: cases smallCase
//:: verdict Fail
class Test {
-->

<!-- codeSnip smallCase -->
```java
boolean never() {
  return false;
}
```
*Here could be some more explanation about "never".*

<!-- standaloneSnip smallCase
void test() {
-->

Then the following example will **fail**:

<!-- codeSnip smallCase -->
```java
assert never();
```

<!-- standaloneSnip smallCase
}
}
-->

Note that the hidden glue code uses standaloneSnip, and includes the code inside the HTML comment tags <!-- and -->. The visible code snippets use codeSnip, and the code is outside the comment tags, enclosed in triple back-ticks like any displayed code block.

Feature Rainbow

If you are reading this you are probably frustrated with the feature system, so first allow me to explain why we have a feature system :)

Why have a feature system?

VerCors is a transformation pipeline, consisting of a large number of passes. Currently, many passes make assumptions about the structure of the AST (justly or unjustly). Often this means that passes assume a particular pass has run before it.

In the absence of a way to make these assumptions explicit, bugs that violate them may survive for dozens of passes before crashing VerCors. Especially for developers that are not well-versed in all of the passes (i.e. everyone), it can then be very hard to diagnose the cause of a bug: the true cause may be a bug in pass #5, violating an implicit assumption in pass #17, crashing VerCors in pass #25!

We have thus chosen to make assumptions about the AST explicit, by way of binary predicates about the AST: features. This introduces a burden on developers writing new passes, because it takes some work to integrate a new pass with the feature system. In particular, usually a feature is needed to convince VerCors that a pass actually does something. Furthermore, the developer must think about the assumptions that their own pass makes, and specify which feature are allowed and introduced. We believe the tradeoff is worth it.

How does it work?

TL;DR

Clearer separation of real and ghost state

VerCors does not discern between ghost state and real state. This means real state can be changed from ghost code, and vice versa. This is not an issue, as in practice it is feasible to maintain this separation manually. But, for future reference, below two solutions are documented which can prevent the two types of state from being mixed.

Frama-C solution

Frama-C prevents mixing ghost and real state by tracking the type of state in the type of variables. An int declared in a ghost block is actually of type int \ghost (with the \ghost qualifier listed after the type, similar to how the const qualifier can be listed after the type). If a pointer is taken from such a ghost int, this yields a value of type int \ghost *. By keeping track of the kind of state in the type, mixing of the two kinds of state is prevented. The code snippet below shows an example of referring to both normal pointers and ghost pointers in Frama-C ghost code.

/*@ requires \valid(a);
  @*/
void testGhost(int *a)
{
  /*@ ghost 
    int x;
    int \ghost *ghostPtr;
    int *realPtr;

    ghostPtr = &x;
    ghostPtr = a; // Not ok! Error

    realPtr = &x; // Not ok! Error
    realPtr = a;
     
    @*/
}  

This solution could also be used by VerCors, and seems the most straightforward to implement and explain. A possible drawback of this approach is that ghost methods defined to work over ghost pointers cannot be used on regular pointers, so this might require users to define ghost functions twice in some cases.

Another drawback is that the type system of the language to be checked needs to be changed/extended. For languages that do not have const-like modifiers like Java, it's not immediately clear how the type system has to be extended. But, for a language like Java, maybe generics can be used.

Permission based solution

Since VerCors supports permissions, they could also possibly be used to discern between real and ghost state. For example, VerCors could support a ghost permission and a real permission. A benefit of this is that the type system of the language to be checked does not have to be modified.

A drawback is that still duplicate function definitions are needed for ghost functions that want to operate over both ghost and regular state. Maybe this can be resolved by having some kind of permission that can be both a ghost or regular permission, which disallows writing, and is therefore safe to use in both regular method and ghost method contracts. But this seems like a non-trivial extension of separation logic, and also difficult to encode in Silver.

Distributing read permissions between threads

A possible use case of read permissions is to distribute read permission between threads:

par (int tid = 0 .. N)
requires x != null ** Perm(x.f, read);
{
  m1(x.f)
} and 
requires x != null ** Perm(x.f, read);
{
  m2(x.f);
}

This results in the following quantifier for the parallel region:

(\forall int tid = 0 .. N; Perm(x.f, read))

However, because of viper's injectivity requirement, this expression is not well-formed.

The following rewrite rules can be used to resolve this:

axiom read_spreads_l {
  Perm(g1, read * i)
  ==
  i > 0 ==> Perm(g1, read)
}

axiom read_spreads_r {
  Perm(g1, i * read)
  ==
  i > 0 ==> Perm(g1, read)
}

axiom resource_independent_read_quant {
  (\forall* int tid; (tid \memberof {(e1!tid)..(e2!tid)}); (Perm(g1, read)!tid))
  ==
  (e1<e2) ==> Perm(g1, read)
}

Unfortunately, these rules cause some problems. Besides sidestepping this injectivity requirement, using these rewrite rules does not accurately reflect Viper's intended semantics of read (see: https://github.com/viperproject/silicon/issues/539). Furthermore, at the time of writing this it is also not allowed to have complex expressions composed of `read (see: https://github.com/viperproject/silicon/issues/569), which would be needed to use more complicated alternative rewrite rules (see below).

Currently, the only way of working around this is by using the fact that the number of threads is usually known (in the above example, the number of threads is N). This allows permissions to be distributed in portions of 1\N. A downside of this is that when the expressions become more complicated (say, when you have N producers, and one consumer, portions are of size 1\(N+1)), Viper/Z3 can sometimes not deal with the resulting non-linear arithmetic, which can cause long verification times and non-determinism.

Alternative formulations of the rewrite rules

Briefly the following alternative formulation was used in vercors:

axiom read_spreads_l {
  read * i
  ==
  (\let int canary = i; i == 0 ? read : read)
}

axiom read_spreads_r {
  i * read
  ==
  (\let int canary = i; i == 0 ? read : read)
}

These leave the value i outside of the multiplication, but do keep i around because it needs to be well-formed. For example, if i were 1/0, removing it would be unsound. However, these rules are currently not allowed because of https://github.com/viperproject/silicon/issues/569.