CORDIS - EU research results

Verification of Concurrent Data Structures

Final Report Summary - VERCORS (Verification of Concurrent Data Structures)

Concurrent software is inherently error-prone, due to the possible interactions and subtle interplays between parallel computations. As a result, error prediction and tracing the sources of errors is difficult. In particular, rerunning an execution with exactly the same input might not lead to the same error. The consequences of this are tremendous: the use of concurrent software is widespread, and the unpredictability of its behavior makes that errors can occur at unexpected, seemingly random moments. Because of the inherent complexity of the parallel computations, formal techniques are needed to provide guarantees about the behaviour of concurrent programs.

In earlier work, we developed a variant of permission-based separation logic that is particularly suited to reason about multithreaded Java programs with dynamic thread creation and termination, and reentrant locks. The VerCors project uses and extends this logic to specify and verify more advanced concurrent programs, using different synchronisation constructs and/or different programming languages, and considering more complex properties to be verified.

A central point of the VerCors project is to reason about functional behaviour of concurrent programs. Where many approaches to verification of concurrent data structures focus on showing the absence of data races, causing unpredictable behaviour, in addition our aim is to show that a program also does something useful. We advocate abstraction as a way to achieve this. Moreover, we create the abstractions in such a way that we can prove that there is a correspondence with the underlying concrete program. Concretely, we introduce histories as a way to provide such abstractions for concurrent programs with terminating threads. Histories are created in several steps. First, for each thread, an abstract local history is established and proven correct. Second, the local histories are merged into a single global history, capturing all the possible interleavings of the program. Finally, reasoning about the program behaviour is done over the global history. Extensions of this approach deal with non-terminating threads, progress properties, and distribution.

Additionally, we are also looking at the verification of algorithms using atomic variables. We have shown in particular how our approach can be used to verify correctness of an implementation of several synchronisation patterns, including a lock-free queue and reentrant locks. For this, we advocate a layered approach, where the complete application is verified in several layers. The bottom layer is used to prove data race freedom, using the essential synchronisation mechanisms. On top of this, the second layer adds some specification-only information, that can be used to transfer local knowledge to other threads. The top-level layer considers functional properties, where verification is relatively simple, because all the basic ingredients have already been dealt with.

All results are integrated in the VerCors tool set, which leverages existing verification tools, and adds sufficient power to reason about realistic concurrent programs. In addition, tool support for interactive program verification is also under development. This allows to use a combination of the two approaches for the verification of realistic concurrent programs: the interactive approach is used to establish the additional annotations for complex parts of the code; the automated approach is used to establish correctness of the complete application. To ensure efficient tool-based reasoning, a new variation of the permission system has been developed, where permissions are treated symbolically (instead of as fractional numbers).