Verification of Erlang Programs

Thomas Arts
Computer Science Laboratory, Ericsson Telecom AB,
thomas@cslab.ericsson.se

Dilian Gurov, Mads Dam, Lars-åke Fredlund
Swedish Institute of Computer Science,
{dilian, mfd, fred}@sics.se

Introduction

One of the major concerns of most programmers is to produce bug-free programs. Bug-free is a vague notion if you have not described how the program should behave. With a wishlist of the demanded behavioural properties we are, however, able to state that the program does or does not behave correctly. A behavioural property should be stated in a specification language which is precise enough to allow rigorous treatment. Having specified a certain property, the big question is: how can we make sure the program has this property?

First of all, we may obtain trust in a program's correctness by examining the way the program is written, and by the experience of its programmer/designer. However, the more complex the programs get, the easier it is to overlook a flaw in the program. Therefore, secondly, a program is tested as exaustively as possible. Testing a program means that you take a certain configuration of the program and its environment and that for this configuration you run the program and see what happens. The more configurations you try, the more you believe that you have covered the program behaviour in general. Since most programs have infinitely many configurations to start with, testing implies that you might have overlooked that configuration that is causing your program to fail.

If we are able to formulate the property that we want the program to meet in a very precise, mathematical way, then we can use verification techniques to actually prove that the property holds for all possible configurations.

Verification

The aim of our project is to develop a method for verification of rather general properties of arbitrary distributed and concurrent Erlang programs. In particular we focus on developing a tool such that large parts of the verification process can be handled automatically.

Before one can prove anything about a program, one has first to make clear what to prove, which then should be formalised. This specification process involving "deep thinking" turns out to be probably half as effective as the actual verification. By explaining yourself, or better, a colleague what you did and why it should work correctly, many serious mistakes and pitfalls show up.

After that it has become clear which property of the program should be verified, one can consider using our verification technique. For using this technique we have designed:

In our approach we focus on verifying programs written in Erlang. In contrast to several other approaches, this results in a method in which one can directly read in and work on an Erlang program, instead of abstracting from it in a different language. As a consequence, we really verify the program and not an abstraction of it. The price to pay, of course, is that we have to consider more details in our verification.

In the formal language, we directly address the Erlang primitives and the Erlang processes. Additional logical connectives and quantifiers enable us to express properties like:

The propery that we want to prove about a program is called a goal and a proof consists of reducing a goal to subgoals and recursively applying this technique until every subgoal is a trivial fact. One can view this as building a tree, where all the nodes are subgoals and the leaves of the tree represent simple facts or axioms.
The difficulty is of course to reduce the goal into subgoals, such that in the end we end up with something trivial. We developed a set of approximately 50 proof rules that, depending on the form of the goal, can be used to devide a goal into subgoals. Often several of these proof rules are applicable to a certain goal and the hard part is to choose the right one.

The tool we have built enables to apply the proof rules to a certain goal, where the user has to choose the proof rule that should be applied. In order to partly automate this, and in order to obtain a higher level of abstract reasoning, several heuristics have been developed that help the user in the hard task of choosing the right rules. For keeping an overview of what is proved and which subgoals are still open, the proof can be visualized by the daVinci graph manipulation tool, developed at the university of Bremen.

Case studies

We used the verification tool for proving some interesting properties of several small programs. In order to give an impression of what is feasable by using the tool, we describe our two major examples.
Billing agent
In this example we want to verify some security properties that deal with buying something via the Internet. In our simplified example, we consider the software that enables a user to access a resource from the Web paying for this resource via a virtual bank. The user issues a request to a resource manager which responds by dynamically creating a billing-agent process. The billing agent acts as an intermediate between the user, the resource owner, and the bank with the account of the user.
The billing agent asks the user for a secret account number and the resource owner for the resource. The billing agent makes sure that the money is transfered from the account of the user to the account of the owner of the resource, and only than hands over the resource to the user.

The user is clearly taking a risk by exposing the account number and so is the resource owner by giving away the resource before any money has been payed. The software written for performing these tasks should be trusted. Both the user as the resource owner, and probably even the bank want to be sure that the software meets certain security criteria. Typical properties that have been verified for this example are: (a) the number of transfers from the user account does not exceed the number of requests to use the resource; and (b) the billing agent does not communicate the user-account key to any other party than the bank.

Query binding in Mnesia
In the kernel of the code for the Mnesia database management system there is some code for setting up a query. The query is devided in sub-queries and those sub-queries are distributed over the network. A request for retrieving data according to such a distributed query results in starting a certain protocol that sends a message to the first sub-query, which gathers part of the information, passes that to the next sub-query etc. Eventually, the last sub-query sends answers back to the original process, which may ask for more data if required.

Typical properties to verify here are those concerning the termination of the search, and the guarantee that all data that should be found eventually arrives.

Conclusion

Verification of software can provide more robust software, and in particular, it can provide assurance about certain properties that the software fulfils. Experiments with the prototype of the verification tool demonstrated that interesting properties can be proved, but at the same time taught that the functionality of the tool is not yet sufficient for addressing verification of properties of large programs. The problems that we have encountered during our experiments are addressed in the next version of the tool, which is currently being developed. The techniques are scaleable, and we hope to be able to check properties of larger complexity with the forthcomming tool.