Validating Promela Models with the ProB Model Checker

advertisement
INSTITUT FÜR INFORMATIK
Softwaretechnik und Programmiersprachen
Universitätsstr. 1
D–40225 Düsseldorf
Validating Promela Models with the ProB
Model Checker
Dennis Winter
Masterarbeit
Beginn der Arbeit:
Abgabe der Arbeit:
Gutachter:
30. September 2008
27. März 2009
Prof. Dr. Michael Leuschel
Jun.-Prof. Dr. Björn Scheuermann
Erklärung
Hiermit versichere ich, dass ich diese Arbeit selbstständig verfasst habe. Ich habe dazu
keine anderen als die angegebenen Quellen und Hilfsmittel verwendet.
Düsseldorf, den 27. März 2009
Dennis Winter
Abstract
Model checking is a technique applied for formal verification of software systems. Specifications, written in Promela (Process Meta-Language) can be validated by the SPIN (Simple Promela Interpreter) model checker. ProB is a platform which includes an animator
and a model checker for the B-Method. We present the implementation of a Promela
plugin, which allows ProB to animate and validate Promela models. The plugin consists
of two main components, a compiler which compiles Promela code into a self-defined
Prolog syntax, and an interpreter written in Prolog.
We describe how to develop the compiler using the parser generator SableCC and give
a detailed description of the Promela interpreter, which interacts with the ProB model
checker to construct the state graph. Utilizing pre-existing ProB features, we were able to
perform guided animation on Promela models with state properties visible at any time
and concurrent highlighting of the underlying source code.
By extending the ProB LTL (Linear Temporal Logic) model checker we were able to validate ProB models with respect to a given LTL formula. We demonstrate a technique to
verify our interpreter with the concept of Never Claims used by the SPIN model checker.
While the scalability of our interpreter is still inferior to SPIN, it can be further improved
by model checking optimization techniques, which we also present in this master thesis.
CONTENTS
1
Contents
Contents
1
1
Introduction
3
2
Background
3
2.1
Formal Verification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
2.2
SPIN and Promela . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.3
The Model Checker ProB . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
3
4
5
6
A Promela Plugin for ProB
7
3.1
Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.2
Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
The Promela Interpreter
9
4.1
Interpreter Development with Prolog . . . . . . . . . . . . . . . . . . . . .
9
4.2
The ProB Model Checking Interface . . . . . . . . . . . . . . . . . . . . . .
10
4.3
Environment Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
4.3.1
Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
4.3.2
Storage and Lookup of data types . . . . . . . . . . . . . . . . . . .
12
4.3.3
User-defined data types . . . . . . . . . . . . . . . . . . . . . . . . .
13
4.3.4
Execution of a transition . . . . . . . . . . . . . . . . . . . . . . . . .
14
The Promela Compiler
17
5.1
Description of the Compiler Phases . . . . . . . . . . . . . . . . . . . . . . .
17
5.2
SableCC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
5.3
JCPP - A Java C Preprocessor . . . . . . . . . . . . . . . . . . . . . . . . . .
18
5.4
Visitor Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
5.5
PromelaVisitor: Static variables . . . . . . . . . . . . . . . . . . . . . . . . .
21
Validating Models
22
6.1
Model Checking Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
6.2
LTL Model Checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
6.3
Semantic Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
CONTENTS
2
7
Eclipse, Rodin and the BE4
28
8
Never Claims
29
8.1
Verification with Never Claims . . . . . . . . . . . . . . . . . . . . . . . . .
29
8.2
Verifying the Promela Interpreter . . . . . . . . . . . . . . . . . . . . . . . .
30
9
Optimization Strategies
34
9.1
Partial Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
9.2
Partial Order Reduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
9.3
Bitstate Hashing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
10 Performance Analysis
39
10.1 The Sieve of Eratosthenes . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
10.2 Mergesort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
11 Related Work
47
12 Conclusions and Future Work
48
A Promela Statements - A complete list in Prolog syntax
51
B Promela Grammar
54
References
64
List of Figures
67
Listings
67
1
INTRODUCTION
1
Introduction
3
SPIN (Simple Promela Interpreter) is a model checker that is capable of validating highlevel models of concurrent systems. The models are described in Promela (Process Meta
Language), which mainly acts as a system description language, rather than an implementation language. An alternative model checker is ProB, which was originally developed for the B-Method [LB03]. The aim of the present thesis is to extend the ProB
model checker with a Promela plugin, that can validate Promela models. With our plugin, ProB offers guided animation and validation of Promela models. The ProB platform
has an easy-to-use graphical user interface (GUI) and is therefore comparable to XSPIN
or JSPIN, which are GUI implementations of SPIN (see chapter 11). Because ProB uses its
own model checker, we are able to offer properties which SPIN cannot. Especially in the
area of animating models, where ProB can show current state properties and highlight
the source code, we consider ProB to be ahead of SPIN [Hol06].
This work starts with a general introduction to formal verification and the theory of
model checking. In chapter 2.2 and 2.3 we introduce the two different model checkers
SPIN and ProB. Chapter 3 explains the design of the Promela plugin, which we added
to the ProB platform. The plugin consists of a Promela compiler and an interpreter that
cooperates with the ProB model checker. The fourth chapter of this work explains the
functioning of the Promela interpreter, which we implemented in the programming language Prolog. In chapter 5 we describe how we built the Java written Promela compiler.
The sixth chapter deals with the validation of Promela models and how correctness properties can be defined with the help of a linear temporal logic (LTL). Chapter 7 is about a
Promela extension to the BE4 (B Extensible Eclipse Editing Environment) editor used by
the Eclipse Platform. The concept of Never Claims in SPIN is explained in chapter 8 and
used to show a way of verifying our own Promela interpreter. The idea is to verify equality of the generated state spaces by SPIN and ProB. Chapter 9 introduces three different
model checking optimization techniques. In chapter 10, the scalability between SPIN and
ProB is compared and in chapter 11 we present related work. Chapter 12 summarizes the
conclusions of this thesis and gives a prospect of future work.
2
2.1
Background
Formal Verification
Nowadays, our society is increasingly dependent on the reliability of hardware and software systems. We can find them in applications for electronic commerce, network communication, traffic control systems, medical instruments and other fields, too numerous
to list. Failures in these systems are unacceptable, as they either cost a lot of money or
cost lives. Thus, it has become an important task for computer engineers to ensure the
correctness of the underlying hardware or software system [WHS06].
Principal validation methods are simulation and testing, but a more profound analysis
requires using the methods of Formal Verification.
2
BACKGROUND
4
"Formal Verification refers to mathematical analysis of proving or disproving
the correctness of a hardware or software system with respect to a certain
unambigously specification or property." [GG07, p. 2]
There are roughly two fields of formal verification: theorem proving and model checking.
Theorem provers use logical inference and mathematical proofs to show the correctness
of the system. The provers need a deep understanding of the system-to-verify and sometimes interact with the user to get essential information. Verification by model checking,
on the other hand, is ideally completely automatic.
The first task of model checking is to convert the design of the system into a model, that is
a formalism accepted by the model checking tool. This can be a simple compilation task.
The state space of the system must not be finite, but be finitely representable. Usually it
is represented by a Kripke structure [EMCGP99], which is basically a graph with nodes as
states and edges as state transitions.
Second, it is necessary to formulate properties, the design should hold. This is generally
done by the use of temporal logics, although the concept allows all kinds of logics. Temporal logics have proven to be very suitable because they can describe the ordering of events
in time without introducing time explicitly. All properties taken together are called specification. One important issue of a specification is completeness. It is impossible to determine
whether the specification covers all characteristics that the system should satisfy, thus it
is impossible to guarantee a complete verification of a system [GG07, EMCGP99].
The third and final task of model checking is verification. The problem can be stated as
follows:
Given a model K and a property φ, decide if K models φ, denoted by K |= φ.
In practice, model checking techniques will be confronted with the state space explosion
problem, which describes the fact that the number of states is exponential in the number
of state elements in the underlying design.
The explicit model checking technique stores each system state, preferably in a large hash
table. In systems with many concurrent parts, this technique can scale badly, if the number of states grows too large.
In 1987, Ken McMillan realized that by using Bryant’s ordered binary decision diagrams
(OBDDs), much larger systems could be verified [BCM+ 98, Bry86]. With this knowledge, the idea of a symbolic (or implicit) model checking technique was born. Symbolic
model checking stores sets of explored states symbolically by using characteristic functions [GG07, EMCGP99]. The symbolic representation captures some of the regularity
in the state space, allowing researchers to push the state count up to 10100 . With the
combination of model checking and various abstraction techniques, even higher state
counts are possible [EMCGP99, Cla]. A viable alternative to OBDDs are Boolean satisfiability problem (SAT)-solvers, which scale well with properties written in Linear Time Logic
(LTL) [GG07].
2
2.2
BACKGROUND
5
SPIN and Promela
SPIN is a model checker, which has been developed since 1980 by Dr. Gerard J. Holzmann and the Computing Principles Research at Bell Laboratories in Murray Hill, N.J..
In 2002, SPIN received the Software System Award from the Association for Computing
Machinery (ACM), which was a breakthrough for the tool and its underlying technology.
The specification language that SPIN accepts is called Promela, which is an acronym for
Process Meta-Language. The term Meta is supposed to underline the use of abstraction for
successful software verification. SPIN itself was chosen as an acronym for Simple Promela
Interpreter, but it has become much more than that. Today, SPIN can be used in two basic modes: simulation and verification. In addition, there are graphical front-ends to the
SPIN model checker, such as XSPIN and JSPIN (see chapter 11). SPIN uses explicit model
checking with Partial Order Reduction or optionally BDD (Binary Decision Diagram)-like
storage techniques. For verifying large models, SPIN offers Bitstate Hashing (see chapter
9). In verification runs, the user can choose between the default depth-first or breadthfirst search. SPIN supports full LTL model checking [Hol06].
A simple Promela program which alternates between a producer and a consumer
process, taken from [Hol06, p. 11] will serve to introduce some basic features of Promela.
1
2
mtype = { P , C } ;
mtype turn = P ;
3
4
5
6
7
8
9
10
a c t i v e proctype producer ( ) {
do
: : ( turn == P ) −>
p r i n t f ( " Produce\n " ) ;
turn = C
od
}
11
12
13
14
15
16
17
18
a c t i v e proctype consumer ( ) {
do
: : ( turn == C) −>
p r i n t f ( " Consume\n " ) ;
turn = P
od
}
Listing 1: Promela do-loop
The program defines a global variable named turn, that can store ’P’ or ’C’ as a value.
Proctypes can be regarded as processes, in this example we have a producer and a consumer proctype. The keyword active signals that these proctypes start running right
away at program start. Each of them starts with a do-loop for execution. A do-loop is a
repetition construct with the following syntax:
do :: sequence [ :: sequence]* od
2
BACKGROUND
6
do-loops run repeatedly until a break command is reached. In our example, it will
run infinitely. A do-loop can contain multiple sequences, each of them starting with a
double-colon. These sequences are called option sequences, because one of them will be
chosen during the program run. A sequence itself can consist of different statements.
The first statement of an option sequence is called the guard statement. Option sequences
are executable if and only if their guard statement is executable. From all executable
option sequences, one is chosen non-deterministically for execution. The ’->’ separates
statements the same way as a ’;’ does. It is only chosen for optical reasons in order to
easily distinguish the guard statement from the continuing statements.
Running this program in simulation mode, it will repeatedly print out:
Produce
Consume
Produce
Consume
etc.
The alternating output is due to the fact that turn can either be ’P’ or ’C’ and always
evaluates one of the two guard conditions to true. If so, it will print out the name of the
running proctype and then switch the turn variable.
2.3
The Model Checker ProB
ProB is an animator and explicit model checker, originally developed for the input language called B. Software Development based on B is also known as the B-Method. ProB
was developed by Michael Leuschel, Michael Butler, Carla Ferreira, Leonid Mikhailov,
Edward Turner, Phil Turner and Laksono Adhianto, starting in 2003 and is now maintained by Michael Leuschel and the chair of "Software Technique and Programming Languages" at the Heinrich-Heine-University Düsseldorf. The core of ProB is written in a
logical programming language called Prolog. The probability of the state space explosion problem is reduced by a high-level specification such as the B-Method, and Symmetry Reduction techniques [LB03]. A mixed depth-first, breadth-first search strategy of
ProB can encounter programming mistakes where a depth-first search might fail and a
breadth-first search might be too exhaustive [Leu08].
The purpose of ProB is to be a comprehensive tool in the area of formal verification
methods. ProB has an easy to use user interface and offers guided animation of the state
space. The properties of each state are visible during animation (see Fig. 1) and the
complete state space can also be shown graphically (see Fig. 2).
3
A PROMELA PLUGIN FOR PROB
7
Figure 1: ProB GUI snapshot during Animation
3
3.1
A Promela Plugin for ProB
Motivation
Why is it necessary to write a Promela extension for ProB when the SPIN model checker
already exists? For one thing, we will be hardly competitive with SPIN in terms of scalability. SPIN compiles Promela programs into pure C code and uses various techniques
to optimize the runtime. But from our experience in software development, most programming mistakes do not require an exhaustive exploration of the state space. The user
will appreciate tools which help him getting a deeper understanding of the source code.
Only in the advanced stages of software development will a fast model checker come to
the fore, in order to find the mistakes which require a deep search in the state space.
The advantages of ProB compared to SPIN are:
• an easy-to-use animation mode with "step-forward" and "step-backward" capability,
3
A PROMELA PLUGIN FOR PROB
8
Figure 2: Snapshot of a State Graph computed by ProB
• source code highlighting of the code fragment which will be executed next, and
• extensive visualization.
The advantages of SPIN compared to ProB are:
• the verifier can be "tuned" with various compilation flags, and
• fast verification.
In sum, the main advantage of SPIN is its verification mode, whereas the main advantage
of ProB is its animation mode. With the Promela plugin we are able to use most of the
functionality already programmed for the B-Method and extend the usability of ProB.
Supported languages of ProB are:
• B (B-Method),
• Event-B,
• CSP (Communicating Sequencial Processes),
4
THE PROMELA INTERPRETER
9
• Z, and
• Promela (new implemented in this work).
3.2
Architecture
ProB uses the Promela plugin to animate and model-check programs written in Promela.
The components of the plugin are illustrated in Fig. 3. The first component is a compiler,
which transforms the Promela code into a self-defined Prolog syntax. The Prolog syntax
consists of a set of Prolog facts and forms the input for the second component, the interpreter. The interpreter interacts with the ProB model checker to build up the state graph
for the underlying Prolog syntax. The role of the interpreter is to compute the states and
transitions. The ProB model checker repeatedly calls the interpreter and decides what
states and in which order they have to be computed. The two compontents, compiler
and interpreter, will be described in more detail in the following chapters. We will start
with the interpreter.
Figure 3: Promela Plugin Architecture
4
4.1
The Promela Interpreter
Interpreter Development with Prolog
The interpreter is written in the logical programming language Prolog, which was developed around 1972 for use in Artificial Intelligence. The name Prolog was chosen
by Philippe Roussel and is derived from the French words programmation en logique. A
simple Prolog program consists of a list of clauses, with each clause being a fact
or a rule. Clauses with the same definition name belong together and are called
predicates.
A fact starts with a f act_name and its arguments in the following form:
f act_name(t1 , ..., tn ). , where n >= 0 and each ti is a term defined as
4
THE PROMELA INTERPRETER
10
• an atom, which is a general-purpose name regarded as a constant,
• a number,
• a variable,
• a compound term, which is a function symbol containing zero or more terms as
arguments.
Rules are of the form:
• Head :- Body, where
Head consists of rule_name(t1 , ..., tn ). , where n >= 0 and each ti is a term.
Body consists of calls to predicates.
The logical conclusion says: Head is true if Body is true.
:- is read as "follows from".
Rules can be interpreted as Horn clauses and define a Turing-complete subset of the firstorder predicate logic. Horn clauses have the characteristic, that only polynomial time
is needed, to test if there exists a set of variable assignments, which satisfies the Horn
clause. A detailed introduction to Prolog can be found at [CM03] and [Wie].
When describing a predicate, we use the syntax from the SICStus manual [CS] which has
the form:
predicate_name(ArgName, ..., ArgName),
where each ArgName specifies how that argument is used by the predicate in
one of the following forms:
+ArgName
-ArgName
?ArgName
The argument is an input argument.
The argument is an output argument.
The argument may be used for both input and output.
The notation
predicate_name/N
indicates that the predicate consists of N arguments.
4.2
The ProB Model Checking Interface
Both for animating and validating Promela models in ProB it is important to build up the
state space. Therefore the ProB model checker calls the following three predicates from
the Promela interpreter:
• start(-Env)
4
THE PROMELA INTERPRETER
11
• trans(-Statement, +In, -Out)
• prop(+Env, -Prop)
start/1 sets up an empty environment, where variables, proctypes and channels may
be stored during the run of a Promela program. trans/3 executes a transition from one
program state to the next. Each solution of the trans/3 predicate represents a separate
transition, hence the execution of the program can be non-deterministic. If ProB is run
in animation mode, the user makes an explicit decision, which transition to be executed.
The trans/3 predicate takes the environment In as input argument and delivers an executable Statement as well as the resulting output environment Out. prop/2 returns
properties of an environment, a property may be the value of a variable or the content of
a channel. The properties are shown in the left column in Fig. 1 when animating Promela
models in ProB. They help the user to understand the functioning of the code. Especially
when the verifier finds an error, the properties can give useful hints as to why the error
has occured.
4.3
4.3.1
Environment Architecture
Construction
The main task of the Promela interpreter is to maintain the environment. The environment is the sum of all variables, proctypes, and channels and characterizes system states.
Equal environments are equivalent to equal states. The model checker uses the interpreter to build up the state space, therefore it repeatedly makes calls to the interpreter.
Each call has as input the current environment and delivers as output all executable transitions plus the subsequent environments. The transitions are found via the Prolog backtracking technique, that is the model checker asks for all solutions the interpreter can find.
One of the output environments will become the new input environment for the next call
to the interpreter, in case the model checker wants to calculate further transitions from
that state. If the model checker keeps on going on a path depends on the search strategy
and whether the state is new or has been computed before.
The environment itself is a triple env(Vars,Procs,Chans) with variables, proctypes and
channels. It will first be instantiated by the start(-Env) predicate, which is defined as
follows:
1
2
3
4
5
6
7
8
9
10
s t a r t ( Env ) :−
empty_avl ( Vars ) ,
empty_avl ( Procs ) ,
empty_avl ( Chans ) ,
d e f i n e ( env ( Vars , Procs , Chans ) , var ( sys ( pid ) , 0 ) , g l o b a l , In2 ) ,
d e f i n e ( In2 , var ( sys ( chan_id ) , 0 ) , g l o b a l , In3 ) ,
d e f i n e ( In3 , var ( sys ( unsafe ) , f a l s e ) , g l o b a l , In4 ) ,
d e f i n e ( In4 , var ( sys ( mode ) , normal ) , g l o b a l , In5 ) ,
i n i t ( 1 , PC2 , In5 , In6 ) ,
i n i t 2 ( PC2 , In6 , Env ) .
4
THE PROMELA INTERPRETER
12
Listing 2: Promela interpreter: Initialization
empty_avl/4 is a built-in predicate from the AVL (Adelson-Velsky and Landis) library
and generates an empty AVL tree [THC01], so Vars, Procs and Chans will be empty
trees at the beginning. define(-In, -Var, -PID, +Out) gets the input environment In, the
variable Var to be defined, the process identifier PID, and delivers the modified output
environment Out. If the code to be executed is within a Promela proctype, PID will
contain the identifying proctype-id, in any other context, PID is set to ’global’. System
variables are globally visible and stored within the clause sys(Key) to distinguish them
from user variables. There are four system variables with the following meanings:
variable
pid
range
number
chan_id
number
unsafe
true/false
mode
normal/p_atomic/
d_step
description
The process (or proctype) identifier. Each starting
process receives a unique process-id from pid.
pid starts at 0 and will be incremented.
An index variable, which assigns each newly defined
channel a unique channel id. The chan_id will be
incremented after each assignment.
Indicates an assertion violation wich will be shown by
the ProB GUI. If unsafe is true, the property
prop(+Env, unsafe) will also be true.
Indicates if the interpreter performs normal
sequences, atomic sequences, or deterministic steps.
Figure 4: Interpreter: System Variables
The init/4 clause (line 9 in Lisiting 2) reads in all globally declared variables and potentialy initializations. init2/3 does the same for the active proctypes and instantiates them
with its corresponing program counter. Having executed the start(-Env) predicate, the
environment is ready to store and look up variables, proctypes and channels. The initial state has been computed and the first transition of the Promela program can now be
executed.
4.3.2
Storage and Lookup of data types
Within the environment env(Vars,Procs,Chans) Variables, Proctypes and Channels are
stored the way Fig. 5 shows. The Prolog terms indicate what type of information is
stored. Every var/2 predicate is an element of Vars, proc/2 is stored in Procs and
chan/2 in Chans.
The first four types of variables in Fig. 5 store meta information about the program state.
The child(PID1,PID2) predicate indicates a child relation between two proctypes. PID1
(process-id 1) was then instantiated within PID2. type(Key) stores the Type of a variable,
when Key is of an user-defined structured data type (record). The sys(Key) predicate
holds the information from Fig. 4 and chan(Key) returns the channel-id to a given channel
variable.
4
THE PROMELA INTERPRETER
13
A user variable can be a global variable, a record, an array, or a local variable. Assuming
we want to look up the value of a variable that features more than one of these four
properties. Then the ordering of the Prolog terms must remain the way they are nested
in Fig. 5 (see also chapter 4.3.3).
Every proctype stored in Procs is running (active) and can be identified by its PID.
Channels are stored as lists and identified by their CID (channel-id). It is possible for two
different channel variables to reference the same channel.
variables
var(child(PID1,PID2), true)
var(type(Key), Type)
var(sys(Key), Value)
var(chan(Key), CID)
var(Key,Value)
var(record(List), Value)
var(array(Key,K), Value)
var(key(Key,PID),Value)
proctypes
proc(active(PID,Name), PC)
channels
chan(CID,Value)
a child relation between two proctypes1
given a key it returns the matching record type
system variables, given a key it returns its value
given a key it returns its channel-id
global user-defined variable
a user-defined structured data type (record)
returns element K from array Key
local user-defined variable.
PID represents the process-id.
A proctype defined by the process-id and its name.
The value PC (Program Counter) holds the current
line in the source code.
given a channel-id it returns the Value of the
channel, where Value can be:
• empty - if the channel has been defined but not
initialized yet.
• [] - if channel represents an empty
rendezvous-channel
• [[empty]] - if the channel has been initialized with
a buffer size of one.
• [[empty], ..., [empty]] - if the channel has been
|
{z
n
}
initialized with a buffer size of n.
Figure 5: Variables, Proctypes and Channels
4.3.3
User-defined data types
The typedef keyword of the Promela language offers the possibility to declare userdefined structured data types. An example is given in Fig. 3, where a new Msg type
1
This information is important for the destructor, as it can only terminate processes that have no child
relations.
4
THE PROMELA INTERPRETER
14
is defined, which simply contains an int and a byte value. The variable message is
an array of Msg, instantiated with a size of two. The Promela interpreter stores arrays
as lists and initializes all primitive variable values with zero. The value of the message
variable would be [[0,[0,0,0,0,0]],[0,[0,0,0,0,0,]]] after the declaration. Because we have a
user-defined data type, the environment stores two predicates which are given in Listing
4 under a). An example look-up of message[1].a will be shown under b). At this point it
is important to use the record and array keywords, as they indicate how to interpret
the value stored for the variable message.
typedef Msg {
int a ;
byte b [ 5 ] ;
};
init {
Msg message [ 2 ] ;
}
Listing 3: User-defined structured data type (record)
a)
/ ∗ s t o r a g e o f a l o c a l v a r i a b l e Key w i t h p r o c e s s −i d PID ∗ /
/ ∗ v a r ( k e y ( Key , PID ) , V a l u e ) ∗ /
/ ∗ l o c a l v a r i a b l e m e s s a g e w i t h PID = 0 and ∗ /
/ ∗ Value = [ [ 0 , [ 0 , 0 , 0 , 0 , 0 ] ] , [ 0 , [ 0 , 0 , 0 , 0 , 0 , ] ] ] ∗ /
var ( key ( message , 0 ) , [ [ 0 , [ 0 , 0 , 0 , 0 , 0 ] ] , [ 0 , [ 0 , 0 , 0 , 0 , 0 , ] ] ] )
/ ∗ v a r i a b l e m e s s a g e i s o f t y p e Msg ∗ /
var ( type ( message , Msg ) )
b)
/ ∗ e x a m p l e l o o k −up o f t h e marked z e r o : ∗ /
/∗ [[0 ,[0 ,0 ,0 ,0 ,0]] ,[0 ,[0 ,0 ,0 ,0 ,0 ,]]] ∗/
/∗
^
∗/
/ ∗ l o o k −up o f a l o c a l a r r a y r e c o r d v a r i a b l e ∗ /
/ ∗ v a r ( r e c o r d ( [ a r r a y ( k e y ( Key , PID ) ,K ) |_ ] ) , V a l u e ) ∗ /
/ ∗ m e s s a g e [ 1 ] . a == 0 ∗ /
var ( r e c o r d ( [ a r r a y ( key ( message , 0 ) , expr ( 1 ) ) , a ] ) , 0 )
Listing 4: Storage and look-up of a local array record variable
4.3.4
Execution of a transition
Having understood the design of the environment we can now execute a first transition,
beginning from the initial state. For this reason the ProB model checker will call the
4
THE PROMELA INTERPRETER
15
trans(-Statement, +In, -Out) predicate, which evaluates to true if it finds an executable
statement. We will now examine the source code of the trans/3 predicate (Listing 5) step
by step.
1
2
3
4
5
6
7
8
9
10
t r a n s ( i o ( [ Statement ] , proc ( PID , Proc ) , Span ) , In , Out ) :−
lookup ( proc ( a c t i v e ( PID , Proc ) ) , g l o b a l , In , PC) ,
i n s t ( PC , S ) ,
lookup ( var ( sys ( mode ) ) , g l o b a l , In , MValue ) ,
check_mode ( MValue , S ) ,
t r a n s 2 ( S , Statement , PC , PC2 , PID , Span , In , In2 ) ,
check_jump ( PC2 , NewPC) ,
( NewPC \== dead
−> s t o r e ( In2 , proc ( a c t i v e ( PID , Proc ) ,NewPC) , g l o b a l , Out )
; remove_proc ( a c t i v e ( PID , Proc ) , In2 , Out ) ) .
Listing 5: The trans/3 predicate
The returned statement by the trans/3 predicate consists of the executable program code
(Statement), the process-id (PID), the proctype name (Proc) and the line item specification (Span) used by ProB for source code highlighting. Line 2 contains a look-up from
the proctypes to get an active proctype with its PID, Proc and program counter (PC).
The inst(+PC,-S) predicate returns the statement S that is available from the generated
Prolog syntax. Line 4 looks up the mode (MValue), the interpreter is currently running.
The mode indicates whether the interpreter performs normal, atomic, or d_step execution, thus defining different control flows in Promela.
check_mode/2 in line 5 checks if statement S is executable in mode MValue. trans2(+S,Statement,+PC,-PC2,+PID,-Span,+In,-In2) is a subfunction of trans/3 and executes statement S. check_jump(+PC2,-NewPC) checks, whether the program counter PC2 points to
a goto or break statement. In this case the jump-statement will be executed right away
and return the new program counter (NewPC). When a proctype is finished with execution, that is the destructor has been executed successfully, it will return the value "dead"
as NewPC. In this case we can remove the proctype from the active proctype list (line
10), otherwise we store NewPC as the new program counter for the currently executed
proctype.
The trans2/8 predicate (line 6) is responsible for executing the various statements. An
alphabetically sorted list of all statements, trans2/8 can handle is attached in appendix
A.
The Promela interpreter and the ProB model checker build up the state graph for a specified Promela program. Coming back to the producer-consumer example from Listing 1,
the resulting state graph is shown in Fig. 6.
The graph consists of six nodes plus the root node, from which the program execution starts. Each of the six nodes represents a system state and contains the values of
all variables, proctypes and channels. The labels at the transitions show which program line has to be executed to traverse from one state to the next. Regarding the label proc(0,producer).expr(eq(turn,ctype(P))), it starts with proc(PID,Name), telling the
proctype-id and the proctype name. The second part tells that the line to be executed is
4
THE PROMELA INTERPRETER
16
Figure 6: A Promela State Graph
an expression, which tests if turn equals ctype(P). ctype(Name) is a term representing a
constant type. vt(Name,Type) holds a variable and its type.
Now we start looking at the execution of the do-loop in Listing 1 as an example of how
the trans2/8 predicate works in detail.
1
2
3
4
5
6
7
8
t r a n s 2 ( do ( A l t ) , Statement , _ , NewPC, PID , Span , In , Out ) :−
member (A, A l t ) ,
i n s t (A, S ) ,
( S= e l s e
−> d e l e t e ( Alt , A, A l t 2 ) ,
( f i n d _ e x e c ( Alt2 , PID , In ) −> f a i l ; t r u e )
; true ) ,
t r a n s 2 ( S , Statement , A, NewPC, PID , Span , In , Out ) .
Listing 6: Interpreting a do construct
5
THE PROMELA COMPILER
17
The variable Alt, which is passed to the do(Alt) term contains a list of program counters (PC’s). Each PC represents one guard sequence. The member/2 predicate in line 2
chooses one PC among the list and the inst/2 predicate gets the corresponding statement
S. A Promela else statement is executable if and only if no other guard sequence is executable. In the case of an else clause, we need an additional check, which is done with
an if-then-else construct, in Prolog, the syntax is (condition -> if-clause; else-clause). If
S = else we reduce the Alt list of the PC that represents the else and check with the
find_exec/3 predicate, if there remains an executable guard statement. If the answer is
yes, else is not executable and the trans2/8 predicate must fail at that point, that is it
will try to find another solution, trying a new PC from the Alt list. If find_exec/3 evaluates to false, or S 6= else the whole if-then-else construct (line 4 to 7) evaluates to true
and the statement S which was found can be executed within a new call to the trans2/8
predicate (line 8).
Another main part of the Promela interpreter is the evaluation of expressions, done
with the eval(+Condition, +PID, +In, -R) predicate. For boolean expressions, R can be
1 for true, or 0 for false. The eval/4 predicate in Listing 7 decides if X equals Y. In
total, there are 39 different kinds of expressions supported by our interpreter (see also
appendix B).
e v a l ( eq ( X , Y ) , PID , In , R ) :−
e v a l ( X , PID , In , X1 ) , e v a l ( Y , PID , In , Y1 ) ,
( X1==Y1
−> R=1
; R=0 ) .
Listing 7: Evaluation of X equals Y
5
5.1
The Promela Compiler
Description of the Compiler Phases
A compiler is a program that translates the source code of one program into another
language, for example into machine code. The Promela compiler we wrote translates the
Promela source code into a Prolog syntax (see Fig. 3). In order to fulfill the compilation
task, the compiler runs through a number of phases [App02]:
Phase 1: Lexical Analysis
The source code is broken down into tokens, a procedure to distinguish keywords, numbers, operators, etc.
Phase 2: Syntactical Analysis
The compiler checks whether the source code represents a valid program. The decision
is based on the grammar of the source language. This process generates a syntax tree
corresponding to the source code. The part of the compiler that executes the syntactical
analysis is also known as the parser. If there is a mismatch between the grammar and the
source code, the parser produces a syntax error.
5
THE PROMELA COMPILER
18
Phase 3: Semantic Analysis
Checks semantic constraints on the source code. For example, the use of variables has to
conform to their data type.
Phase 4 (optional): Optimization
The compiler generates a temporary program code that is very close to the final program
code. The temporary code is used for certain optimization techniques.
Phase 5: Code Emission
The final code is generated either from the syntax tree or from the optimized temporary
code. When the source code has been compiled into machine code, the result is an executable program.
5.2
SableCC
SableCC is a parser generator written in Java, which generates an object-oriented framework for building compilers, interpreters and other text parsers [Sab]. The generated
parser, which is used for the syntactical analysis (phase 2), uses a LALR(1) grammar
(LookAhead, Left-to-right parse, Rightmost-derivation, 1-token lookahead [App02, p.
64]). SableCC maintains a clean separation between the automatically generated code
and the user-written classes. User-written visitor classes are used to operate along the
syntax tree and to do a semantic analysis. The visitor classes can also work on an abstraction of the syntax tree, the so called abstract syntax tree (AST).
The Promela compiler uses SableCC as compiler generator. The input grammar was
taken from the ETCH (an Enhanced Type CHecking tool for Promela) project [DG05]
and modified in many ways. The parser now accepts all Promela programs we got for
testing. The complete grammar is available in appendix B.
5.3
JCPP - A Java C Preprocessor
JCPP (Java C Preprocessor) is a stand-alone, pure Java implementation of the C preprocessor, written by Shevek in 2007 [She]. The current JCPP version is 1.2.2.
SPIN uses the C preprocessor before compiling the Promela source code into C code. In
order to get all the advantageous features of the C preprocessor, it was also necessary
for us to include a C preprocessor in our Promela compiler. The disadvantage of using
the original C preprocessor is that it might not be available on the hosts operating system. Providing the C preprocessor as a native program would mean adding one version
for each operating system, which is unacceptable. As we already need a Java Runtime
Version for the Promela compiler, it made sense to use a Java implementation of the C
preprocessor. In our opinion, the JCPP was the best choice of a reliable, open-source,
Java written C preprocessor.
5
THE PROMELA COMPILER
ConstVisitor
DeclLstVisitor
DeclLst2Visitor
DeclLst3Visitor
DoVisitor
ExprVisitor
IfVisitor
InlineArgLstVisitor
InlineVisitor
MyLinkedList
Position
ProctypeVisitor
PromelaVisitor
SequenceVisitor
TypenamelstVisitor
VarrefVisitor
Varref2Visitor
19
parses constants
parses global or local declarations
parses parameter declarations in the proctype head
parses user-defined types
parses do-loops
parses expressions
parses if-clauses
parses parameterized calls to inline functions
(not part of basic Promela)
parses inline functions
(not part of basic Promela)
helper class for printing out Prolog syntax
helper class for storing a token’s position
parses proctypes
parses an entire Promela program (entrance point)
parses sequences
parses primitive Promela types
parses variables
parses record structures
Figure 7: The Visitor Classes
5.4
Visitor Classes
The first step of the Promela compiler is to construct the syntax tree. SableCC uses self
written visitor classes, also named tree walker, to perform operations along the nodes of
the syntax tree. We chose a concrete syntax tree in contrast to an abstract syntax tree,
because it was easier to maintain in the development process. All visitor classes are
derived from the auto-generated DepthFirstAdapter class, which performs a depth-first
search on the syntax tree. The PromelaVisitor class starts at the root of the syntax tree and
is always the first instantiation. Other visitor classes are going to be instantiated when
necessary. For example, the ExprVisitor class is instantiated when a Promela expression
is read in. The visitor classes do semantic analysis, as well as code emission and generate
standard and error outputs. The standard output forms the desired result and is called
the Prolog syntax (see Fig. 3). An error output would be for example a type conflict, or
when a variable was not defined before its use. A list of all visitor classes together with a
brief description of their role is given in Fig. 7.
The helper class Position stores the start and end positions of a statement. This is of
use for the source code highlighting in a ProB animation. Position also deals with the
fact that the statements might be read in from different input files. The Prolog syntax of
the starting example from Listing 1 looks like the following one:
1
2
3
4
i n s t ( 1 , a s s i g n ( mtype , [ ’ P ’ , ’C ’ ] ) ) .
i n s t ( 2 , def ( mtype , turn ) ) .
i n s t ( 3 , a s s i g n ( v t ( turn , mtype ) , expr ( ctype ( ’ P ’ ) ) ) ) .
5
5
6
7
8
9
10
11
THE PROMELA COMPILER
20
i n s t ( 4 , proctype ( a c t i v e ( 1 ) , producer , [ ] ) ) .
i n s t ( 5 , do ( [ 6 ] ) ) .
i n s t ( 6 , expr ( ( expr ( eq ( turn , ctype ( ’ P ’ ) ) ) ) ) ) .
i n s t ( 7 , p r i n t f ( " Produce " ) ) .
i n s t ( 8 , a s s i g n ( v t ( turn , mtype ) , expr ( ctype ( ’C ’ ) ) ) ) .
inst (9 , igoto ( 5) ) .
inst (10 , destructor ) .
12
13
14
15
16
17
18
19
i n s t ( 1 1 , proctype ( a c t i v e ( 1 ) , consumer , [ ] ) ) .
i n s t ( 1 2 , do ( [ 1 3 ] ) ) .
i n s t ( 1 3 , expr ( ( expr ( eq ( turn , ctype ( ’C ’ ) ) ) ) ) ) .
i n s t ( 1 4 , p r i n t f ( " Consume " ) ) .
i n s t ( 1 5 , a s s i g n ( v t ( turn , mtype ) , expr ( ctype ( ’ P ’ ) ) ) ) .
inst (16 , igoto (12) ) .
inst (17 , destructor ) .
20
21
22
23
24
25
26
27
28
29
30
31
line (1 ,1 ,1 ,1 ,17) .
line (2 ,2 ,1 ,2 ,11) .
line (3 ,2 ,7 ,2 ,15) .
line (6 ,7 ,12 ,7 ,23) .
line (7 ,8 ,16 ,8 ,33) .
line (8 ,9 ,16 ,9 ,24) .
line (10 ,11 ,1 ,11 ,2) .
line (13 ,16 ,12 ,16 ,23) .
line (14 ,17 ,16 ,17 ,33) .
line (15 ,18 ,16 ,18 ,24) .
line (17 ,20 ,1 ,20 ,2) .
Listing 8: Prolog syntax of the Promela do-loop
Every instruction is identified by the inst(Number,Statement) predicate. Each instruction
Number matches to exactly one Statement, which has the advantage that the instruction numbers can be used as placeholders for statements. The whole Promela syntax is
expressed in Prolog predicates, for example the instruction in line 3 indicates: Assign
the variable turn of type mtype to the constant type P. Note that the right-hand-side
of an assignment must be evaluated before it can be assigned, which is initiated by the
expr(Expression) predicate. The do(List) predicate starts a do-loop in providing a list
with instrucion numbers. If there exists more than one instruction number, the interpreter
chooses one number non-deterministically out of the list and commences execution at the
matching instruction. The igoto(Number) predicate defines an invisible goto statement.
Invisible means that no execution step will be required to execute the goto statement,
so that execution returns immediately to the beginning of the do-loop. When the end of
the proctype is reached, the destructor will be called to remove local variables and
channels. The destructor of a parent proctype is only executable if all destructors of the
child proctypes have been executed. At the end of the Prolog syntax file, the parser generates the line/5 predicates. They are important for the source code highlighting of the
currently executable statements. The first number of the line/5 predicate identifies the
instruction number. The next four numbers are row-start, column-start, row-end, and
column-end of the corresponding statement in the original Promela file.
With this syntax, the source code highlighting works perfectly when the complete source
5
THE PROMELA COMPILER
21
code can be loaded from one file. The posssibility of switching to another file, which
is useful when the interpreter executes code from an included file, has not yet been
implemented. The reason for this is rather the currently missing ability of ProB to handle
different source files than the difficulty of implementing this feature. In fact, the parser
already has the information when a different source file is called. The information is
generated by the C preprocessor in the form of a comment line and is parsed by the
Promela parser. The comment begins with a ’#’ and reports the current line number and
the current file read from. In the example in Listing 9, the parser first reads eight lines
from "mergesort.pml", then "sem.h" and then returns to "mergesort.pml".
# l i n e 8 " mergesort . pml "
# l i n e 1 " sem . h "
i n l i n e wait ( s ) {
atomic { s > 0 ; s−− }
}
i n l i n e s i g n a l ( s ) { s++ }
# l i n e 9 " mergesort . pml "
Listing 9: Preprocessor generated comments
The parser uses these comments to keep track of the position in the main Promela
file, for example when the program returns to the main file after having read in an
included file. But the parser could also show source code highlighting in included
files by providing positions with filenames. The line/5 predicate would then be extended to line(Filename,Inst-Number,Row-Start,Column-Start,Row-End,Column-End).
ProB might be migrated as a plugin to the Integrated Development Environment (IDE)
Eclipse in the near future (see chapter 7). With the powerful tools of an IDE, such as
Eclipse, it would be simple to add this feature.
5.5
PromelaVisitor: Static variables
The PromelaVisitor class contains public accessable, static variables (see Fig. 8), which
are indispensible for all visitor classes.
Every visitor class also contains a private Map typeMap that maps variable names to
their types. typeMap must be declared as local variable, because different namespaces
are necessary for each proctype. The variable mode tells if the code has to be executed
in normal, atomic or d_step mode. The corresponding Prolog syntax uses inst(...),
inst(p_atomic(...)), or inst(d_step(...)).
We need to know if the current statement is a guard condition, because a break, or a
goto statement requires one execution step when used in a guard condition and none
otherwise. The boolean variable guardCondition will contain this information.
The Promela keyword provided sets a global constraint on process execution. We use
the variable constraint to store the constraint that belongs to a specified proctype.
6
VALIDATING MODELS
22
int lineNr
HashMap<Integer, Position> lineMap
current line number of the Prolog syntax
maps lineNr to the corresponding Position.
Seeable in the line/5 clause of the Prolog syntax.
int NORMAL
indicates normal Promela mode
int ATOMIC
indicates atomic Promela mode
int D_STEP
indicates d_step Promela mode
int mode
boolean guardCondition
String constraint
HashMap<String, AInline> inlineMap
can be either NORMAL, ATOMIC or D_STEP
tells, if the current statement is a guard condition
Promela proctype constraint, if one is used
maps inline-functions to their inline-node.
AInline represents a node in the syntax tree.
String fileIn
name of the main Promela filename
String currentFile
name of the current Promela file.
Necessary when include’s are used.
Figure 8: Static variables of the PromelaVisitor class
If a proctype has a constraint, the instructions of the Prolog syntax will be extended to
inst(LineNr, provided(Constraint)).
The inline functions are not part of the basic Promela language described in [Hol06], but
are nevertheless sometimes used. SPIN supports inline functions, so we support them as
well. When an inline function is declared in the Promela file the parser maps its name to
the corresponding node in the syntax tree. This is done with the Map inlineMap. A call
to an inline function will then replace the call with the mapped function. In the case of
an parameterized call, the mapping variables of the inline function are set to the values
of the parameters.
6
6.1
Validating Models
Model Checking Options
The integrated ProB model checker is used to verify Promela models. The model checker
can work with some user inputs to adapt to a certain way of model checking (see Fig. 9).
The user can use the default combination of a depth-first, breadth-first search, or a purely
6
VALIDATING MODELS
23
Figure 9: ProB Model Checking Options
breadth-first search. He also chooses whether he wants to search for deadlocks or not.
If this option is enabled, the model checker will stop when it encounters a deadlock and
print out the trail to the deadlock state. The item Find Invariant Violations will search for
Promela assertion violations. The Promela interpreter will report an assertion violation,
if assert(expr(Name)) evaluates to false (see appendix A). The two disabled items
are for the B-Method only. The option Inspect existing nodes is only relevant if a previous
calculation was canceled and should now be restarted. With the option disabled, the
calculation will be continued at the point of abortion. Enabling the option will restart the
model checking process from the beginning. This is useful if the user wants to change
the settings of one of the first three options after having canceled the calculation.
6.2
LTL Model Checking
Linear Temporal Logic (LTL) is a linear-time logic that provides operators for describing
events along a single computation path. They offer the possibility to describe events
that will eventually become true, a fact that cannot be expressed in first-order logic. LTL
formulas are inductively defined as follows:
• every pi ∈ AP (atomic propositions) is an LTL formula.
• LTL is complete under the boolean operations ∧, ∨, → and ¬.
• If Ï•1 is an LTL formula, then XÏ•, GÏ• and F Ï• are LTL formulas.
• If Ï•1 and Ï•2 are LTL formulas, then Ï•1 U Ï•2 and Ï•1 RÏ•2 are LTL formulas.
The semantics for temporal modal operators are given in Fig. 10.
Comparing LTL to another branching time logic like CTL (Computation Tree Logic), we
can say that LTL has an intersection with CTL without being either a superset or a subset
of CTL. CTL* is a superset of CTL as well as of LTL [EMCGP99, LTL].
An important step in model checking is to phrase the properties that should be verified
at a model. Linear temporal logics are often the first choice, because they can express two
6
VALIDATING MODELS
LTL
XÏ•
Description
Next: Ï• is valid at the next state.
GÏ•
Globally: Ï• is valid on the entire subsequent
path.
Finally: Ï• eventually becomes true.
FÏ•
Ï•1 U Ï•2
Ï•1 RÏ•2
Ï•1 W Ï•2
Until: Ï•2 is valid at the current or at a future
state, and Ï•1 has to be valid until that state.
At that state Ï•1 does not have to be valid
any more.
Release: Ï•1 is valid until the first state in
which Ï•2 is true, or forever if such a
position does not exist.
Weak Until: Ï•1 is valid Until Ï•2 , or forever.
24
Example
−→ −→ −→ −→ Ï•
−→ −→ −→ −→ Ï•
Ï•
Ï•
Ï•
Ï•
−→ −→ −→ −→ Ï•
−→ −→ −→ −→ Ï•1
Ï•1
Ï•1
Ï•2
−→ −→ −→ −→ Ï•1
Ï•1
Ï•1
Ï•1 , Ï•2
(Ï•1 U Ï•2 ) ∨ GÏ•1
Figure 10: Semantics of LTL formulas
main types of properties easily: safety properties and liveness properties. Safety properties state that something bad never happens (G¬Ï•) and liveness properties state that
something good keeps happening (G(Ï•1 −→ F Ï•2 )).
ProB supports checking of LTL formulas for a given model. We would like to check the
safety property
G(¬{turn == C})
at the producer-consumer example of Listing 1. Therefore we open the "Check LTL Formula" option and type in our LTL formula. Note that Promela expressions have to be put
into braces {...}. The ProB LTL model checker evaluates this formula at every node in the
state space and finds a counterexample after three execution steps. The found path to the
violation of the formula is then printed out.
A liveness property would be
G({turn == P } => F {turn == C})
which will be confirmed by ProB at the given model.
The Java class PromelaLTLParser is responsible for transforming the expressions written
in braces into expressions conforming to Prolog syntax. The ProB LTL model checker
therefore writes the expressions into a file called "ltl_prob.mch" on the temporary directory of the operating system. The PromelaLTLParser takes this file as input together with
a file called "typeMap.data" which had already been generated by the Promela compiler
before. "typeMap.data" contains an object of a HashMap, which maps each global
variable to its type (see chapter 5.5). The HashMap is generated when compiling the
Promela file and is also located in the temporary directory of the operating system. The
PromelaLTLParser uses the ExprVisitor (see Fig. 7), which is fed with the expressions
6
VALIDATING MODELS
25
from "ltl_prob.mch" and the HashMap to create a list of compiled, Prolog compatible
expressions and puts them into a file called "ltl_prob.pl". This file is then used by the
LTL model checker to check the validity of the entire LTL formula. The content of the
temporary files related to the liveness property from above is shown in Listing 10.
/∗ c o n t e n t o f " l t l _ p r o b . mch" ( i np ut ) : ∗/
turn==P , turn==C ,
/∗ c o n t e n t o f " typeMap . data " ( i np ut ) : ∗/
turn −> mtype
P −> ctype
C −> ctype
/∗ c o n t e n t o f " l t l _ p r o b . p l " ( output ) : ∗/
[ expr ( eq ( turn , ctype ( ’ P ’ ) ) ) , expr ( eq ( turn , ctype ( ’ C ’ ) ) ) ] .
Listing 10: File contents during LTL model checking
6.3
Semantic Rules
The Semantic Rules of SPIN define the concrete order of possible execution sequences.
The predicates from the Promela interpreter were programmed to adopt exactly the same
behavior. We now examine three examples from [Hol06, p. 162] to show the ambiguity of
execution sequences and verify our results with the SPIN model checker. First, we need
to introduce the Promela syntax for sending messages over channels.
A channel with a buffer size of N can be instantiated with
chan mychan = [N] of { byte }
A rendezvous channel has a buffer size of zero which implies that the channel can pass,
but not store messages. A rendezvous-send operation can only be executed if a matching
receive operation is executable. Both operations are then executed synchronously.
chan rchan = [0] of { byte }
A matching rendezvous-send and receive operation is shown in Listing 11.
chan rchan = [ 0 ] of { byte } ;
a c t i v e proctype A( ) {
rchan ! 5 5
/∗ send ∗/
}
a c t i v e proctype B ( ) {
byte r e s ;
rchan ? r e s
/∗ r e c e i v e ∗/
}
6
VALIDATING MODELS
26
Listing 11: Rendezvous Communication
The Promela unless construct, mainly used to define exception handling routines, has
the syntax:
main-sequence unless escape-sequence
The executability of all statements in the main-sequence is constrained to the nonexecutabilty of all guard statements in the escape-sequence. If one guard statement in
the escape-sequence becomes executable, execution will proceed with the remaining
escape-sequence and will not return to the main-sequence. If all guards of the escapesequence remain unexecutable throughout the execution of the main-sequence, the
whole escape-sequence will be skipped [Hol06].
1
2
3
4
chan x
chan y
active
active
= [ 0 ] of
= [ 0 ] of
proctype
proctype
{ bit
{ bit
A( ) {
B() {
};
};
x ?0 unless y ! 0 }
y ?0 unless x ! 0 }
Listing 12: Promela example 1
1
2
3
4
chan x
chan y
active
active
= [ 0 ] of
= [ 0 ] of
proctype
proctype
{ bit
{ bit
A( ) {
B() {
};
};
x ! 0 unless y ! 0 }
y ?0 unless x ?0 }
Listing 13: Promela example 2
1
2
3
4
chan x
chan y
active
active
= [ 0 ] of
= [ 0 ] of
proctype
proctype
{ bit
{ bit
A( ) {
B() {
};
};
x ! 0 unless y ?0 }
y ! 0 unless x ?0 }
Listing 14: Promela example 3
1
2
3
4
5
6
t r a n s 2 ( u n l e s s (M, E ) , Statement , _ , NewPC, PID , Span , In , Out ) :−
i n s t ( E , Escape ) ,
( t r a n s 2 ( Escape , Statement , E , NewPC, PID , Span , In , Out )
−> t r u e
;
( i n s t (M, Main ) ,
t r a n s 2 ( Main , Statement ,M, NewPC, PID , Span , In , Out ) ) ) .
Listing 15: Promela interpreter: unless statement
The trans2/8 predicate in Listing 15 resolves an unless statement. The Prolog if-thenelse clause simply first tries to execute the escape-sequence E and only if that fails, tries
to execute the main-sequence M.
6
1
2
3
4
5
6
7
8
9
10
11
12
13
VALIDATING MODELS
27
% rendezvous−send
r_send ( CID , Name, Msg , Statement , PID , Span , In , Out ) :−
e v a l _ l i s t ( Msg , PID , In , Msg2 ) ,
s t o r e ( In , chan ( CID , [ Msg2 ] ) , g l o b a l , In2 ) ,
% s e a r c h matching r e c e i v e
lookup ( proc ( a c t i v e ( RPID , Proc ) ) , g l o b a l , In , PC) ,
i n s t ( PC , S ) ,
t r a n s 2 ( S , r e c v (RName, RMsg) , PC , NewPC, RPID , Span , In2 , In3 ) ,
lookup ( chan ( CID ) , g l o b a l , In3 , [ [ empty ] ] ) ,
% r e s e t channel
s t o r e ( In3 , chan ( CID , [ ] ) , g l o b a l , In4 ) ,
s t o r e ( In4 , proc ( a c t i v e ( RPID , Proc ) , NewPC) , g l o b a l , Out ) ,
Statement = ’ . ’ ( r_send (Name, Msg ) , r _ r e c v (RName, RMsg) ) .
Listing 16: Promela interpreter: rendezvous send
Before we reach the r_send/8 predicate from Listing 16, the interpreter has verified that
the channel, referenced by channel-id (CID) equals ’[]’, i.e. that it is a rendezvous channel (see also Fig. 5). eval_list/4 in line 3 evaluates the given send-arguments (Msg) and
store/4 stores the resulting list into the rendezvous channel. The following lookup/4,
inst/2 and trans2/8 clauses are used to determine the matching receive operation. The
recv(RName,RMsg) clause from the trans2/8 predicate in line 8 can be regarded as return value and will only match if an executable receive statement was found. Having
executed the matching receive statement, the value of the rendezvous channel will be
[[empty]], which corresponds to a channel with buffer size one. In the next step (line
11), the interpreter resets the rendezvous channel to [], to guarantee further rendezvoussend operations. The new program counter (NewPC) of the receive-performed proctype
will be stored in line 12 and the returned Statement will be set to a merging of the
rendezvous-send and rendezvous-receive statements (line 13).
Example 1 (Listing 12) is successful in executing either proctype A or proctype B. The
unless statement tries to execute the Escape sequence y?0, which finds a matching receive statement in y!0 (line 8 in Listing 16). The other way around, proctype B first tries
to execute x!0 and succeeds in the matching receive operation x?0.
In example 2 (Listing 13), proctype A succeeds with the rendezvous sequence y!0, y?0
the same way as example 1 does. Proctype B tries to execute x?0, which fails because a
receive statement cannot proceed on an empty channel. The proctype B tries to execute
y?0 which fails for the same reason. x!0 is never regarded for execution.
Proctype A in Example 3 (Listing 14) tries to execute y?0, which fails. It then gets into the
else branch of the unless predicate (line 5 in Listing 15) and tries to execute x!0, which
succeeds with the matching receive operation x?0 in proctype B. But if proctype B first
tries to execute x?0 it will fail, because channel x has not been filled by a matching send
statement. Looking then at the main-sequence of the unless statement, it can execute
y!0 with the receiving operation y?0 from proctype A.
Running these examples in SPIN, we can confirm that exactly the same operation sequences were found for execution.
7
7
ECLIPSE, RODIN AND THE BE4
28
Eclipse, Rodin and the BE4
Eclipse is an open-source framework for almost any kind of software development. It was
first intended as an IDE for the programming language Java, but because of its pluginbased structure it was soon extended for use in various software development tasks. The
Eclipse Rich Client Platform (RCP) has progressively gained importance in developing
powerful stand-alone Java applications. Mandatory plugins for a RCP application are
the Eclipse Core Platform, the Standard Widget Toolkit (SWT) and JFace. Today, there are
many plugins for Eclipse, open-source as well as commercial plugins. Eclipse is written
in the programming language Java [EF].
The Rodin platform is an IDE based on Eclipse. Rodin is used for developing EventB models, which is an evolution of the B-Method developed by Jean-Ramond Abrial.
Event-B uses set theory as a modeling notation, refinements to represent systems at different abstraction levels and mathematical proofs to verify consistency between refinement levels. Development of Rodin is currently supported by the European Union ICT
Project deploy [Dep]. The use of Rodin can be extended with plugins [But].
One plugin available for Rodin is called ProB, because it is based on the ProB model
checker. It serves as a tool for automatic animation of EventB models and can systematically check a model for errors. The fusion of Rodin and ProB leads to a powerful new
tool from the point of view of model checking.
The B Extensible Eclipse Editing Environment (BE4) is an editor for B specifications,
developed by Jens Bendisposto [Ben07]. Because it is based on Eclipse, it can be used in
an RCP application like Rodin. The BE4 supports code completion, syntax highlighting,
code navigation, error indication, and automatic code correction. It can be easily
extended for multiple languages. The BE4 already supports Promela and works almost
error free with the grammar specified in appendix B. A slight difference is that the BE4
does not need to run a C-Preprocessor first, so that the define’s and include’s have
to be parsed by the SableCC-parser. We added the two simple rules from Listing 17 to
the grammar from appendix B. With the new grammar, the BE4 editor can parse Promela
programs accurately.
Tokens
define = ’# define ’ ;
i n c l u d e = ’ # in c l u d e ’ ;
Productions
module =
{ predefine } predefine |
{ preinclude } include |
[...]
predefine =
d e f i n e name expr ;
preinclude =
8
NEVER CLAIMS
29
include s t r i n g ;
Listing 17: Rules added to the BE4 parser
8
8.1
Never Claims
Verification with Never Claims
SPIN uses Never Claims for expressing properties of distributed systems. A Never Claim
is used to specify either finite or infinite system behavior that should never occur [Hol06,
p. 85]. A termination of a Never Claim is interpreted as a full match of the behavior specified, behavior which is never supposed to happen. All behaviors expressed in LTL can
be written in a Never Claim, therefore SPIN is equipped with an LTL translator, which
translates LTL formulas into Never Claims. Only when SPIN is running in verification
mode Never Claims will be interpreted.
Assuming we want to verify the following behavior, taken from [Hol06, p.85-91].
"Every system state in which p is true eventually leads to a system state in which
q is true, and in the interim p remains true."
The SPIN model checker is not interested in system executions that satisfy this behavior,
but in executions that can violate it. Therefore we negate the behavior to the following
LTL formula.
¬G(p → (p U q))
Using SPIN’s built-in LTL translator, we can translate this LTL formula into the Never
Claim given in Listing 18. We only deleted dispensable parentheses to increase readability.
8
NEVER CLAIMS
30
never {
T0_init :
if
: : p && ! q −> goto a c c e p t _ S 4
: : t r u e −> goto T 0 _ i n i t
fi ;
accept_S4 :
if
: : ! q −> goto a c c e p t _ S 4
: : ! p && ! q −> goto a c c e p t _ a l l
fi ;
accept_all :
skip
}
Listing 18: LTL formula translated into Never Claim
For a better understanding, this Never Claim can also be described as an automaton
given in Fig. 11.
Figure 11: Control Flow Structure
If a Promela program specifies a Never Claim that can be fulfilled, the SPIN verifier generates an error. This behavior raises the idea of trying to verify our own Promela interpreter by means of a Never Claim.
8.2
Verifying the Promela Interpreter
A possibility to verify the calculation of our Promela interpreter with respect to a given
input program is to use Never Claims. The idea is to compare the generated state spaces
8
NEVER CLAIMS
31
of SPIN and ProB and report an error in case they are not alike.
The Promela interpreter, together with the ProB model checker construct the state space,
which can be printed out in a self-defined way. We implemented a Never Claim printer
that converts a state space into a Never Claim syntax, in which each transition of the
state graph corresponds to one execution step in the Never Claim. An error should be
reported, if the input program can execute a transition which the Never Claim cannot.
This desired behavior is a little tricky, because whenever the Never Claim cannot execute
a transition, it is not an error, but a desirable condition. It means that the undesired
behavior that is captured in the Never Claim cannot fully be matched by the program
execution. We can change this behavior by using the keyword assert when listing the
variables with our Never Claim printer. assert gets an expression as input and tries
to evaluate it to true. If this fails, the SPIN verifier raises an error. With asserts,
we do not give the verifier the chance to get stuck in the Never Claim by forcing it at
every transition to execute a corresponding statement in the Never Claim. It should be
mentioned that only desired behavior bound to global variables can be verified, because
these only can be addressed in a Never Claim or an LTL formula.
In the ProB graphical user interface, we added the menu point "Verify → External Tools
→ Save State to Never Claim". Before we execute this option, the whole state space must
have been built up with the "Model Check" option. The producer-consumer example
from Listing 1 will produce a file called "prod_cons_neverclaim.pml" that has the content
of Listing 19.
# include " prod_cons . pml "
never {
Nroot :
a s s e r t ( t r u e ) −> goto N0 ;
N0 :
a s s e r t ( turn == P && t r u e )
N1 :
a s s e r t ( turn == P && t r u e )
N2 :
a s s e r t ( turn == C) −> goto
N3 :
a s s e r t ( turn == C && t r u e )
N4 :
a s s e r t ( turn == C && t r u e )
N5 :
a s s e r t ( turn == P ) −> goto
}
−> goto N1 ;
−> goto N2 ;
N3 ;
−> goto N4 ;
−> goto N5 ;
N0 ;
Listing 19: Producer Consumer expressed in a Never Claim
The state graph consists of the nodes N0 to N5 plus a root node. The assert expressions
check the current values of the global variables. A transition in the state graph can only be
executed if there is a matching statement in the Never Claim. As long as the evaluation of
assert remains true, the state graph is considered correct. If we let SPIN verify our selfgenerated Never Claim, we will be able to make one of the following logical conclusions:
8
NEVER CLAIMS
32
The SPIN verifier reports an error → The ProB generated state graph cannot express
the intended behavior.
The SPIN verifier runs error-free → The ProB generated state graph or a subset of
this state graph can express the same behavior
as intended.
We can use the ProB model checker interface to walk in the constructed state graph.
Important are the following two predicates:
• visited_expression(Node,Env,Body)
• transition(NodeA,Trans,NodeB)
visited_expression/3 returns every node from the state space via the Prolog backtracking
technique. It starts with the root node, which is the entrance point to our state graph.
Node is the node identifier, Env the environment to the node, and Body is of no concern
in this context. transition/3 returns to a node (NodeA) a possible transition Trans and
the resulting end node NodeB. Analyzing the environment Env to each node, our Never
Claim printer can print out the variables and their values.
The SPIN verifier always executes one statement in the program, followed by one statement in the Never Claim. A goto statement hereby is regarded as no execution step, so it
will always be an assert statement in the Never Claim to be executed.
The aim of the Never Claim is to represent the same granularity as the execution of the
original program. So, when states are not distinguisable by their global variables, we
make sure that the same number of steps in the program and in the Never Claim are necessary to reach a state where the value of a global variable changes. In the intermediate
states, we check the values of all global variables to make sure they remain the same.
The verification with Never Claims is successful with the producer-consumer example,
but has its problems because of different granularity between SPIN and our Promela
interpreter. Unfortunately, the Promela Language Specification does not tell much about
the number of execution steps it takes to execute a statement. But it is possible to find
out about the granularity with small self-generated examples. We will now consider the
code from Listing 20.
byte a = 0 ;
init {
byte b = 0 ;
a = 1;
}
Listing 20: Granularity of Promela programs
Running this program in ProB, the user can execute the following statements in a fixed
order.
byte b;
(definition)
7−→
b = 0;
7−→
(assignment)
a = 1;
7−→
(assignment)
destructor
8
NEVER CLAIMS
33
Because a is a global variable, its definition and initialization requires no execution step.
The granularity in this case amounts four states. The generated Never Claim, which
considers only the value of the global variable a in each state, consists of the lines in
Listing 21.
never {
Nroot :
a s s e r t ( t r u e ) −> goto N0 ;
N0 :
a s s e r t ( a == 0 && t r u e ) −> goto N1 ;
N1 :
a s s e r t ( a == 0 && t r u e ) −> goto N2 ;
N2 :
a s s e r t ( a == 1 ) −> goto N3 ;
N3 :
a s s e r t ( a == 1 && t r u e ) −> goto N4 ;
N4 :
do
: : true
od ;
}
Listing 21: State graph expressed in a Never Claim
The final state N4 in Listing 21 contains an infinite do-loop, because the verifier is not
supposed to leave the Never Claim, as this would fulfill the undesired behavior and
raise an error. Thus, finite programs will end up in an infinite loop. If we try to verify
this Never Claim, we will get an error. With the SPIN compiling flag DVERBOSE we can
generate useful debugging output and generate the error message given below.
0: Down - claim non-accepting [pids 1-1]
New state 0
Pr: 0 Tr: 3
0: proc 0 exec 3, 1 to 3, assert(1)
non-accepting [tau=4]
1: Down - program non-accepting [pids 1-1]
Pr: 1 Tr: 9
1: proc 1 exec 9, 1 to 2, a = 1
non-accepting [tau=0]
2: Down - claim non-accepting [pids 1-1]
New state 1
Pr: 0 Tr: 4
pan: assertion violated ((a==0)&&1) (at depth 2)
pan: wrote granularity_neverclaim.pml.trail
The penultimate line is important, which reports that our generated state N0 in Listing
21 causes an assertion violation. A tentative fix for this Never Claim would be to replace
the code of the state Nroot with the following line:
Nroot:
assert ( true ) -> goto N2;
9
OPTIMIZATION STRATEGIES
34
Now, we can successfully verify the program. This result is interesting, because it demonstrates that SPIN uses no execution step for defining and possibly initializing local variables. We can also verify this result by using the SPIN animator with the flag "-p", which
shows at each execution step in the animation run which process changed its state and
which statement was executed [Hol06, p. 519]. SPIN creates, besides the root state, just
one state, with the property a = 1.
$ spin -p granularity.pml
Starting :init: with pid 0
0:
proc - (:root:) creates proc 0 (:init:)
1:
proc 0 (:init:) line 5 "granularity.pml" (state 1) [a = 1]
1:
proc 0 (:init:) terminates
1 process created
The entire example demonstrated that validating our Promela interpreter with Never
Claims is feasible, but not working totally satisfactory. This is due to the discrepant granularity between SPIN and the Promela interpreter. The solution we recommend for this
problem is to rewrite parts of the Promela interpreter and to adopt the same granularity
as SPIN. The granularity of each statement has to be found out with the help of many
simple, self-written Promela programs. Another solution would be to adapt the Never
Claim printer, but this would be an indirect fix and could not fully prove the correctness
of the Promela interpreter. Although we found out about different granularities, this does
not necessarily mean the interpreter is not working correctly, only that we cannot apply
an automatic technique to verify its correctness. The technique we can use to assume
correctness is comparing the verification and output results with SPIN. From another
vantage point, the higher granularity of the Promela interpreter could also be a desired
property. Running programs in the ProB animation mode, the user might appreciate the
execution of his code in small steps. We also think that debugging the interpreter is easier
with a high granularity, because it directly shows at which execution step a possible error
arose.
9
Optimization Strategies
There are various optimization strategies to speed up the model checking process or to
reduce memory requirements for validating extremely large models. We are going to
present three of them: Partial Evaluation, which we tried to apply to our Promela interpreter, Partial Order Reduction, and Bitstate Hashing, which are both used by the SPIN
model checker.
9.1
Partial Evaluation
Partial Evaluation is a program optimization technique, especially used in the fields of
compilation and compiler generation. The Partial Evaluation of an interpreter with respect to a source program will lead to a target program. The target program will be a
9
OPTIMIZATION STRATEGIES
35
specialized interpreter and should run more efficiently than the original one [JGS93]. Because we are regarding Partial Evaluation on interpreters, this special case is known as
the first Futamura projection, of which exist three:
1. Specializing an interpreter for a given source code, yielding an executable.
2. Specializing the specializer for the interpreter (as applied in 1.), yielding a compiler.
3. Specializing the specializer for itself (as applied in 2.), yielding a tool that can convert any interpreter to an equivalent compiler. [Fut]
Looking at Partial Evaluation at the level of function calls, a one-argument function can
be obtained from a two-argument function by specialization, which means "freezing" one
input to a fixed value. In mathematics, this kind of specialization is called "restriction" or
"projection" and in logic it is called "currying" [JGS93].
Figure 12: Partial Evaluator for Promela
Fig. 12 illustrates the functioning of a Partial Evaluator for our Promela interpreter. The
Partial Evaluator is given the Promela interpreter together with a Promela program as
input. It then creates a specialized Promela interpreter for that particular program. The
user input, in our case executed transitions in the Promela program, leads to the same
9
OPTIMIZATION STRATEGIES
36
visual output as if the Promela interpreter were run with both inputs (Promela program
and user input). During the runtime of a program, a dynamic Partial Evaluator specializes only the parts of the interpreter that are necessary for execution.
Carl Friedrich Bolz is currently developing a dynamic Partial Evaluator for interpreters
written in Prolog. The program is part of his master thesis [Bol08] and uses SWI-(SociaalWetenschappelijke Informatica) Prolog [Wie], which is an open-source implementation of
Prolog. The dynamic Partial Evaluator generates the code for the specialized interpreter,
with only the predicates necessary for the next execution step. During runtime we have
total knowledge of our environment, so that the generated code already contains every
value to each variable without performing a look-up in the environment. The specialized
interpreter will waste no time on backtracking which fails in the end, because its code
consists of just the right unification for the variables. The prize we pay for the Partial
Evaluation is to calculate the code of the specialized interpreter on each execution step.
We will get a payoff in execution time, if our program keeps on repeating similar code
fragments, such as loops or recursions. The dynamic Partial Evaluator will then remember program states it has already computed and can execute the code for the specialized
interpreter immediately.
For a test run of the Promela interpreter we substituted some calls to SICStus Prolog
built-ins with appropriate calls to the SWI-Prolog library. The AVL trees were replaced
by lists, because SWI-Prolog does not have an AVL library. This substitution will cause
no difference in runtime, because the specialized interpreter does not have to perform
look-ups.
The execution of the Partial Evaluator caused some unintended code, due to the stadium
of a beta release. It will still take some time before the program can be run in ProB and
the amount of performance improvement can be measured. Some minor errors were
caused by the Prolog if-then-else clauses and some problems arose with Prolog cuts. But
apart from these small errors, the result of the generated code was quite promising. The
producer-consumer example from Listing 1 produced a state graph where only the turn
variable changed from ’P’ to ’C’. That is exactly what a theoretical approach would result
in. The whole producer-consumer program consists of an infinite loop. That is, after
having executed all program states once, the specialized interpreter can switch states
without calculating. Its code always consists of just the appropriate next transition.
9.2
Partial Order Reduction
One of the most important optimization strategies performed by SPIN is called Partial
Order Reduction. This avoids creating states that cannot be affected by interleaving the
execution of different processes. The idea is to reveal sequences of transitions in the state
graph, which result in the same state when executed in different orders. This reduction
technique constructs a reduced state graph, in which the behaviors of the reduced graph
are a subset of the behaviors of the full state graph. The behaviors that are not present do
not add any information. Transition sequences, each representing a certain behavior, can
define an equivalence relation among behaviors, such that the checked property cannot
distinguish between equivalent behaviors [BA08], [EMCGP99, p. 141].
9
OPTIMIZATION STRATEGIES
37
We will examine the Partial Order Reduction with the help of a small example, taken
from [Hol06, p. 191] with the two finite state automata T1 and T2 from Fig. 13.
Figure 13: Finite State Automata T1 and T2
The automata share access to a common integer variable z, and each has access to a private variable x and y. The initial value of all variables is zero and the range of possible
values reaches from 0 to 4.
Running T1 and T2 as asynchronous processes, there are six possible different program
runs, which are listed in Fig. 14. An important note is that different interleavings of
x = 1 and y = 1 both lead to the same result, where x and y have the value 1. The two
interleavings of the statements z = z + 2 and z = z ∗ 2, on the other hand, lead to two
different values for z.
1:
2:
3:
4:
5:
6:
x = 1;
x = 1;
x = 1;
y = 1;
y = 1;
y = 1;
z = z + 2;
y = 1;
y = 1;
z = z ∗ 2;
x = 1;
x = 1;
y = 1;
z = z + 2;
z = z ∗ 2;
x = 1;
z = z ∗ 2;
z = z + 2;
z
z
z
z
z
z
= z ∗ 2;
= z ∗ 2;
= z + 2;
= z + 2;
= z + 2;
= z ∗ 2;
Figure 14: Possible system runs
The resulting state graph in Fig. 15 illustrates the different runs. A program state is
represented by the values of the triple (x,y,z).
We are able to see now that all different program runs result in only two different states,
(1,1,4) and (1,1,2). Thus, all runs which end up in (1,1,4) belong to the first equivalence
class and all runs ending up in (1,1,2) belong to the second one. We can randomly choose
one transition sequence out of each equivalence class for our new reduced graph to represent equivalent full system behavior [Hol06, p. 191]. There are three states less in the
reduced graph and only half the number of transitions. The graph in Fig. 16 has chosen
the runs 1 and 3 from Fig. 14 to create the new state graph.
9
OPTIMIZATION STRATEGIES
38
Figure 15: Product of T1 and T2
9.3
Bitstate Hashing
The SPIN model checker uses a depth-first search by default for visiting the states. Before
constructing a state, SPIN checks whether the state was previously visited or is new. The
decision is made with the help of a hash table, which maps the hash value of a state
(hash(S)) to the state S. If two different states compute the same hash value, a hash
collision is detected. This can be resolved in adding linked lists to the particular entry
in the hash table. Linked lists can store an arbitrary amount of states, but slow down
the hash table look-up, because of a linear look-up time. The performance of the model
checker is dependent on how fast it can compute states and how many look-ups it has to
perform in the hash table.
When enabling SPIN’s Bitstate Hashing algorithm, a hash table look-up time of O(1) can
be guaranteed by not resolving hash collisions. The idea is that hash collisions can be
made very improbable by using the right hash-function and a big hash table size. Instead
of storing states in the hash table, only one bit is used to indicate whether or not the state
that corresponds to the hash value, has been visited or not. Because we store so little
information, the hash table can be made bigger, thus reducing the propability of a hash
collusion even more. But, if a hash collusion occurs, we will get the false information
that the state has been visited and miss to compute the new state. Every other state that
is only accessible through this state will also be missed. Therefore, Bitstate Hashing must
be regarded as an approximation to the correctness question. It cannot lead to false error
10
PERFORMANCE ANALYSIS
39
Figure 16: Reduced product of T1 and T2
reports, but error-free models are only error-free with a certain, high probability [Hol06,
p.206-212].
10
Performance Analysis
Apart from correctness, scalability is another important property of an interpreter. In the
following, we are going to measure the performance of our Promela interpreter and SPIN
by means of two different examples. We expect SPIN to be in advantage, because it can
execute much more operations per second than our Promela interpreter. SPIN generates
a specialized model checker in C code first, which is then compiled into machine code,
whereas ProB executes an interpreter written in Prolog.
The default SPIN settings use Partial Order Reduction (see chapter 9.2), which we cannot
offer for our Prolog interpreter yet. The aim of the Partial Order Reduction algorithm is
to reduce the number of system states that needs to be visited and stored. An orthogonal strategy is to reduce the amount of memory that is required to store each system
10
PERFORMANCE ANALYSIS
40
state. This strategy is called Collapse Compression [Hol06, p.198] and can be enabled by
the SPIN compile-time option DCOLLAPSE. The Collapse Compression algorithm increases runtime, but can significantly reduce memory requirements. For unusually large
models, SPIN needs to increase the state vector, which is done with the compile-time
option DVECTORSZ=N (see Fig. 17). The SPIN Bitstate Hashing technique uses an alternative storage instead of the default exhaustive storage (see chapter 9.3). More options
that can be applied to the SPIN verifier are available in [Hol06, p. 527].
The Promela interpreter was run without any optimization strategy. The ProB Symmetry
Reduction technique (see [TB06] and chapter 12) used for models written in B cannot be
applied to Promela models yet and the combined depth-first, breadth-first search strategy
has no advantage when the complete state space has to be searched anyway.
The tests were all run on an Intel Core2 Duo CPU T5870 with 2.0 GHz and 4.0 GB RAM.
The SPIN version was 5.1.7 from December 23, 2008. Every test was run at least three
times, each measured time is the minimum of all runs.
We are going to compare the performance, verifiying the algorithms from Fig. 22 and Fig.
23 with SPIN and with our Promela interpreter. A correct verification should be specified
by no assertion violation, no error and no deadlock reports. Additionally, the algorithms
have to return the same results when running in SPIN or ProB animation mode.
When the ProB verifier is enabled with deadlock search, ProB reports deadlocks on valid
end-states. These pseudo-deadlocks can be distinguished from real deadlocks easily, because the last instruction called will be the destructor, and the number of active proctypes
will be zero. ProB then prints out the execution path to the deadlock state and the current
values of the variables.
10.1
The Sieve of Eratosthenes
The "Sieve of Eratosthenes" is an ancient algorithm developed by the Greek mathematician Eratosthenes for finding prime numbers. The algorithm returns a list of all prime
numbers up to a given highest number MAX.
The algorithm in Listing 22 is taken from [Hol06, p. 331] and represents a variant of the
Sieve of Eratosthenes. The idea of this algorithm is to count n up from 3 to MAX and test
if any of the prime numbers already found can divide n without a remainder. If none of
the prime numbers is a divisor of n, n will be added to the list of found prime numbers,
which are stored in the channel found. The initialization process already puts the first
prime number 2 into found, whereupon the algorithm can directly enter in its main doloop. When n reaches MAX, the algorithm terminates and holds all prime numbers in
the channel found.
The algorithm consists of exactly one valid end state which will be reported by ProB as a
deadlock, if deadlock search is enabled. From the channel found at this state, we will be
able to read out the desired prime numbers. In SPIN, the prime numbers can be seen in
the printouts in animation mode.
In the following, we are going to record different verification runs with changing values
for the variable MAX. We will measure the time needed, write down the number of states
10
PERFORMANCE ANALYSIS
41
computed and how many prime numbers were found. In SPIN we also give various
compilation flags to tune the model checker (see Fig. 17).
DBFS
DVECTORSZ=N
DCOLLAPSE
DBITSTATE
DNOREDUCE
Uses a breadth-first search instead of the default depth-first search.
Changes the maximum size for the state vector by substituting
a value for N. The default value for N is 1024.
Compresses the state descriptors using an indexing method,
which increases runtime but can significantly reduce the
memory requirements.
Uses the Bitstate Hashing algorithm instead of the default
exhaustive storage.
Disables the Partial Order Reduction algorithm and arranges for
the verifier to perform an exhaustive full state exploration,
without reductions.
Figure 17: SPIN Compile-Time Options
1
# d e f i n e MAX 1000
2
3
mtype = { number , e o f } ;
4
5
chan found = [MAX] o f { i n t } ;
6
7
a c t i v e proctype s i e v e ( ) {
8
9
10
11
int n = 3;
i n t prime = 2 ;
int i ;
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
found ! prime ;
p r i n t f ( "MSC: %d i s prime\n " , prime ) ;
do
: : n < MAX −>
i = l e n ( found ) ;
assert ( i > 0) ;
do
: : i > 0 −>
found ? prime ;
found ! prime ;
if
: : ( n%prime ) == 0 −>
break
: : e l s e −>
i −−
fi
: : e l s e −>
break
10
42
od ;
if
: : i == 0 −>
found ! n ;
p r i n t f ( "MSC: %d i s prime number %d\n " ,
n , l e n ( found ) )
: : else
fi ;
n++;
: : e l s e −>
break
od
31
32
33
34
35
36
37
38
39
40
41
42
43
PERFORMANCE ANALYSIS
}
Listing 22: The Sieve of Eratosthenes
MAX
ProB
200
400
500
600
800
1000
SPIN
200
400
500
600
800
1000
1000
Time
2000
2000
States
Prime Numbers found
6,4 sec
37,8 sec
46 sec
1:19 min
3:53 min
out of memory
13.000
40.800
59.800
81.900
135.086
46
78
95
109
139
0,24 sec
0,76 sec
1,4 sec
2,23 sec
4,81 sec
8,72 sec
1,2 sec
12.962
40.745
59.753
81.804
135.039
201.580
201.580
46
78
95
109
139
168
168
out of memory
6,46 sec
685.355
303
Parameters
DVECTORSZ=2048
DVECTORSZ=2048
DVECTORSZ=4096
DVECTORSZ=4096
DVECTORSZ=4096
DVECTORSZ=4096,
DCOLLAPSE
DVECTORSZ=8192
DVECTORSZ=8192,
DCOLLAPSE
Figure 18: Verification of the Sieve of Eratosthenes
ProB can compute prime numbers up to a MAX value of 800. The arithmetic average over
all measured times results in a speed-ratio of 38,63 between ProB and SPIN. That is, the
verification in ProB takes 38,63 times the computation time of SPIN in this example. The
graph in Fig. 19 supposes that the ratio might not be a constant factor, but should rather
be specified by a function. The range of MAX values we could measure is too small to
determine the exact ratio function type at this point.
The calculated number of states are nearly the same in ProB and SPIN, which indicates
similar computed state graphs from both model checkers. The number of prime numbers are identical as we expected. SPIN uses a default maximum search depth of 10.000
10
PERFORMANCE ANALYSIS
43
Figure 19: Speed measurement between ProB and SPIN
steps, which must be increased using the runtime option "-m" (for example -m100000 for
100.000 steps). For MAX values higher than 200, the maximum size for the state vector needs to be increased in SPIN with the compile-time option DVECTORSZ. If MAX >=
2000, we also need the compile-time option DCOLLAPSE to use an indexing method (see
Fig. 17) and to avoid an "out of memory" error. Surprisingly, with the indexing method
we can even further decrease the computation time.
10.2
Mergesort
The algorithm in Listing 23 is a mergesort implementation in Promela. The first step is to
choose N=4 random byte variables in the range from zero to MAXBYTE. The N variables
are split into two equally large sequences and sorted afterwards. In the end, Merge()
merges the sequences to one sorted sequence. Semaphores are used to assure that the
Merge() process does not start before the end of the Sort() processes. The "for() . . . rof()" construct is a self-defined macro for for-loops, which are implemented with the help of
do-loops.
The mergesort algorithm is fast when running with concrete input, but hard to verify
with random input variables. The input variables themselves span a state space of
(MAXBYTE+1)N states, which has to be multiplied by the residual number of states, to
figure out the number of states for the complete state graph. Unlike in "The Sieve of
Eratosthenes", this algorithm results in many valid end-states. Each randomly chosen
10
PERFORMANCE ANALYSIS
44
input sequence has a sorted output sequence, which accounts for an end-state. Thus, it
is important to disable the deadlock-search in ProB, because this would cause the model
checker to stop after the first end-state found and to report a deadlock.
1
2
3
4
/∗ mergesort . pml ∗/
/∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗/
/∗ Copyright (C) 2006 M. Ben−Ari . ∗/
/∗ modified by Dennis Winter , 2009 ∗/
5
6
7
# include " for . h"
# i n c l u d e " sem . h "
8
9
10
# d e f i n e MAXBYTE 1
# define N 4
11
12
13
byte a [N] , r e s u l t [N] ;
byte s [ 2 ] = 0 ;
14
15
16
17
18
19
20
21
22
23
24
25
26
/∗ I n s e r t i o n s o r t o f each h a l f o f t h e a r r a y ∗/
proctype S o r t ( byte sem ; byte low ; byte high ) {
byte max , temp ;
f o r ( I , low , high −1)
max = I ;
f o r ( J , I +1 , high )
i f : : a [ J ] < a [ max ] −> max = J : : e l s e f i ;
rof ( J ) ;
temp = a [ I ] ; a [ I ] = a [ max ] ; a [ max ] = temp ;
rof ( I ) ;
s i g n a l ( s [ sem ] )
}
27
28
29
30
31
32
i n l i n e Next ( index ) {
r e s u l t [ r ] = a [ index ] ;
r ++;
index ++;
}
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
proctype Merge ( ) {
wait ( s [ 0 ] ) ;
wait ( s [ 1 ] ) ;
byte f i r s t = 0 , second = N / 2 , r = 0 ;
do
: : ( f i r s t >= N/2) && ( second >= N) −> break
: : ( f i r s t >= N/2) && ( second < N) −> Next ( second )
: : ( f i r s t < N/2) && ( second >= N) −> Next ( f i r s t )
: : ( f i r s t < N/2) && ( second < N) −>
if
: : a [ f i r s t ] < a [ second ] −> Next ( f i r s t )
: : e l s e −> Next ( second )
fi
od ;
f o r (K, 0 , N−2)
10
45
a s s e r t ( r e s u l t [K] <= r e s u l t [K+ 1 ] ) ;
p r i n t f ( "MSC: %d\n " , r e s u l t [K ] )
r o f (K) ;
p r i n t f ( "MSC: %d\n " , r e s u l t [N−1])
49
50
51
52
53
PERFORMANCE ANALYSIS
}
54
55
56
57
58
59
60
61
i n l i n e Randnr ( ) {
do
: : a [ I ] < MAXBYTE −> a [ I ] + + ;
: : a [ I ] > 0 −> a [ I ]−−;
: : break
od ;
}
62
63
64
65
66
67
68
69
70
71
72
init {
atomic {
f o r ( I , 0 , N−1)
Randnr ( ) ;
rof ( I ) ;
run S o r t ( 0 , 0 , N/2 − 1 ) ;
run S o r t ( 1 , N/2 , N − 1 ) ;
run Merge ( ) ;
}
}
73
74
75
76
77
78
/∗ sem . h ∗/
/∗∗∗∗∗∗∗∗∗/
i n l i n e wait ( s ) {
atomic { s > 0 ; s−− }
}
79
80
81
82
inline signal ( s ) {
s++
}
83
84
85
86
87
/∗ f o r . h ∗/
/∗∗∗∗∗∗∗∗∗/
# d e f i n e f o r ( I , low , high ) byte I ; I = low ; do : : ( I > high ) −> break
: : e l s e −>
# d e f i n e r o f ( I ) ; I ++ od
Listing 23: Mergesort
The first verification runs lead to the results in Fig. 20. SPIN is not able to verify this
algorithm. The reason is in an infinite do-loop surrounded by an atomic statement. The
init proctype starts with the atomic keyword and then enters the Randnr() process.
The do-loop in the Randnr() proctype does not necessarily terminate, which causes the
SPIN model checker to compute indefinitely. The ProB model checker does not have this
problem within atomic statements and recognizes states it has already computed. In
SPIN, atomic and d_step statements must be chosen carefully (see also [Leu08]). For
the verification runs in Fig. 21, we eliminated the atomic statement in the init proctype
(line 64 and line 71).
10
PERFORMANCE ANALYSIS
MAXBYTE
ProB
1
2
3
4
SPIN
1
2
3
46
Time
States
12 sec
1:20 min
5:04 min
16:22 min
18.700
90.700
280.000
673.878
max search depth too small
max search depth too small
max search depth too small
Figure 20: Verification of Mergesort (atomic)
MAXBYTE
ProB
1
2
3
SPIN
1
1
2
2
3
3
24
24
24
25
255
Time
32 sec
3:41 min
14:48 min
0,01 sec
0,04 sec
0,04 sec
0,16 sec
0,11 sec
0,38 sec
1:31 min
2,61 sec
out of memory
out of memory
3,35 sec
States
Parameters
39.400
195.500
611.700
2.321
9.295
11.632
45.724
36.613
142.479
55.410.701
1.505.238
1.913.353
DNOREDUCE
DNOREDUCE
DNOREDUCE
DBITSTATE
DNOREDUCE
DBITSTATE
Figure 21: Verification of Mergesort (without atomic)
With the verification of the Mergesort example, SPIN can clearly show its advantages of
a specialized model checker written in C. It can check far more states per second than our
Promela interpreter and with the right compiling flags it can be challenged even further.
An important discovery is that the number of states computed from our Promela interpreter differs from SPIN and is also dependent in SPIN on compiling flags. The Promela
interpreter computes 17 times more states than SPIN because of the different granularity and no state-reducing-techniques applied by the model checker. One reason for the
state space explosion with the Promela interpreter could be the self-generated for-loops,
which instantiate new counting variables every time they are executed. The definition
and initialization of a new variable takes two execution steps in our Promela interpreter
and none in SPIN (see chapter 8.2). Another reason is in the generally applied Partial
Order Reduction technique of the SPIN model checker, which can be turned off by the
DNOREDUCE compiling flag, however.
11
RELATED WORK
47
SPIN is able to verify the complete state space up to a MAXBYTE value of 24. In order to
verify models with higher MAXBYTE values we have to apply Bitstate Hashing, which
cannot fully prove the correctness of the mergesort algorithm, however (see chapter 9.3).
Comparing the average duration of the verification runs between ProB (Fig. 20) and SPIN
(Fig. 21), we obtain a speed ratio of 1988. We can assume that the main speed advantage
of SPIN is due to the smart reduction of the state space. Even though we tested SPIN
with Partial Order Reduction turned off, it computes fewer states than ProB. It would
be interesting to apply the Partial Evaluation algorithm from chapter 12 to this example,
which will probably lead to a high reduction of the state space. This can be done as soon
as the Partial Evaluator reaches the next step of development.
When trying to verify huge models, where the complete state space cannot be computed,
the ProB as well as the SPIN model checker report found errors even though the search
has not been completed. If there is, for example, a deadlock state, which is easy to find
from the initial state, the user will certainly be informed about it. This piece of information might be helpful to the user, although he must be aware that further errors could be
existing in the model.
11
Related Work
Earlier work has made ProB a multifunctional tool in the field of formal method verification. The animation and model checking of CSP and B using Prolog technology was
shown by [LAB+ 01]. In 2007, ProB was extended in order to validate Z-Specifications.
The new tool was integrated into ProB by providing a translation of Z into B and by
extending the kernel of ProB to accommodate some new syntax and data types [PL07].
The generic, flexible LTL model checker was added to ProB in 2008. It can be applied to
high-level specifications such as B, Z, CSP, and others [PL08].
The Rodin platform was extended in 2007 by the ProB Disprover plugin. The plugin
utilizes the ProB animator and model checker to automatically find counterexamples for a
given problematic proof obligation. Using this plugin advances the debugging of EventB models in Rodin [LBL07].
The Primer and Reference Manual for SPIN is [Hol06], which formed the base for this
work. The Promela plugin we developed can be regarded as an alternative to the graphical front-ends XSPIN and JSPIN (see [BA]). Both tools execute SPIN commands in the
background in response to user actions. XSPIN (see Fig. 22) is written in Tcl/Tk and part
of the default SPIN Source Distribution, available at [SPI]. It provides a clear overview of
the many options in SPIN that are available for performing animations and verifications.
JSPIN (see Fig. 23) was built using the Java SWING library and consists of three adjustable panes, displaying text. The left one displays the Promela source files, the lower
one messages from SPIN and JSPIN and the right one is used to display the output of
printf statements and of data from animations [BA08].
ProB comes with its own model checker and is open to various languages that use formal
methods. The kind of guided animation that ProB offers is neither supported by XSPIN
nor by JSPIN.
12
CONCLUSIONS AND FUTURE WORK
48
Figure 22: Graphical User Interface of XSPIN
In [Leu08], the difference of model checking high-level versus low-level specifications is
examined. In particular, model checking of B-specifications in ProB is compared with
model checking of Promela specifications in SPIN. The work investigates different expressivity, granularity and search algorithms in both tools. An example is given, in which
the combined depth-first, breadth-first search strategy of ProB was the advantage over
the default depth-first search strategy of SPIN.
12
Conclusions and Future Work
This work deals with model checking as a method of formal verification. We showed
how to develop an interpreter and a compiler for Promela, and built a plugin for the ProB
model checker. Using Promela, this plugin extends ProB, which was originally developed
to animate and model check B-specifications. This increases the attractiveness of ProB as a
universal tool for formal methods. It was particularly important to support the complete
Promela language and to have the same semantics as the SPIN model checker. Therefore,
we compared our results with SPIN and specified a way to possibly verify a verification
run with the help of Never Claims. The results in chapter 10 showed that the SPIN model
checker scales so well that we cannot compete with it in verification runtime. But we still
consider our work to be a competitive alternative to SPIN, as we offer features such as
guided animation or source code highlighting which increase the comprehensibility of
the code.
Future work on the interpreter should try to improve scalability, because we think there
12
CONCLUSIONS AND FUTURE WORK
49
Figure 23: Graphical User Interface of JSPIN
is still potential with the help of the following techniques. The first is called Symmetry Reduction, a technique that is available in ProB for verifying models written in B [TB06].
With the help of Symmetry Reduction, duplicate symmetric components of the state
space can be made up and are not explored during the verification process. A smaller
state space would then lead to shorter verification time. To implement Symmetry Reduction for Promela models, the ProB model checker needs to get additional information
from the Promela interpreter. A second approach would be to implement the Partial Order Reduction strategy as described in chapter 9.2, such as it is used by the SPIN model
checker. This would require changes to the interpreter and the model checker. The third
and most advanced approach at present is the development of the Partial Evaluator (see
chapter 12). With this approach, the changes to the interpreter are minimal, because the
Partial Evaluator can work with all kinds of Prolog interpreters. The gain in verification
speed of Promela models should also be promising.
Another road to pursue is to verify the correctness of our Promela interpreter. We think
that Never Claims represent the solution to this problem as shown in chapter 8.2. The
next step in the development of the Promela interpreter would be to adopt the same
granularity as SPIN. The state spaces computed by the SPIN model checker and the ProB
model checker could then be compared automatically. Minor changes are also necessary
to the ProB platform and the Promela compiler when adding source code highlighting
for included files also. In chapter 5.4, we showed how to change the compiler in order
to support multiple files. With the intended movement of ProB to the IDE Eclipse, this
12
CONCLUSIONS AND FUTURE WORK
50
should be easy to implement.
Using the present work as a prototype, further langugages that use formal methods may
be implemented into ProB in the future.
A
A
PROMELA STATEMENTS - A COMPLETE LIST IN PROLOG SYNTAX
51
Promela Statements - A complete list in Prolog syntax
The following list contains Promela statements compiled into Prolog syntax. The Prolog
syntax constitutes the language understood by our Promela interpreter.
assert(expr(Name))
for stating simple safety properties
ProB will cause an assertion violation,
if assert evaluates to false.
assign(vt(Name,Type),
Value)
normal assign
Variable Name of type Type will be assigned to
Value.
assign(Name,
chan(N,TypeList))
assigns a channel to a variable
assign(array(Name,expr(K)), assigns a channel to a variable, which belongs
chan(N,TypeList))
to an array, for example: chan c[3] = [16] of { mtype }
assign(Name,
chan(0,TypeList))
assigns a rendezvous-channel to a variable
assign(array(Name,expr(K)), assigns a rendezvous-channel to a variable, which
chan(0,TypeList))
belongs to an array, for example:
chan c[3] = [0] of { mtype }
assign(chan(Name)
expr(chan(Name2)))
assigns a new channel-id to a channel
Important when channels are itself send over channels.
assign(array(chan(Name),
expr(K)),
expr(chan(Name2)))
assigns a new channel-id to a channel, which belongs
to an array of channels
break(X)
jumps to the end of the inner do loop
d_step_start(S)
starts a deterministic code fragment, that is
executed indivisible
d_step(S)
continues a deterministic code fragment
dec(vt(Name,Type))
decrements the variable Name
def(Type,Name)
defines the variable Name of type Type
destructor
call to the destructor, which removes channels and
A
PROMELA STATEMENTS - A COMPLETE LIST IN PROLOG SYNTAX
52
variables belonging to the proctype
do(Alt)
repitition construct
else
a system defined condition statement
expr(run(Proc,ArgList))
creates a new process
expr(Expr)
evaluates an expression into a constant
goto(X)
unconditional jump to a labeled statement
if(Alt)
selection construct
inc(vt(Name,Type))
increments the variable Name
label(L)
to identify a unique control state within a proctype
declaration
p_atomic_start(S)
starts a code fragment, that has to be executed
indivisible
p_atomic(S)
continues an atomic code fragment
poll(Chan,Msg)
receives a message from a channel
The message will not be removed from the channel.
printf(String)
prints a string
printf(String,ArgList)
prints arguments into a string using specific
conversion specifications
The conversions use the format/3 predicate from the
SICStus build-in [CS]. For example: %c prints
a single character, %d prints a decimal value.
printm(expr(Expr))
prints a mtpye string
For example: mtype x = pear; printm(x);
would print ’pear’.
provided(expr(Expr))
sets a global contraint on process execution
random_poll(Chan,Msg)
random poll operation
Like poll/2, but random_poll/2 is only executable if there
exists at least one message anywhere in the channel
that matches the pattern from the receive statement.
The first such message is then used.
A
PROMELA STATEMENTS - A COMPLETE LIST IN PROLOG SYNTAX
53
random_recv(Chan,Msg)
like random_poll/2, only it does a receive instead
of a poll.
recv(Chan,Msg)
receives a message from a channel
The message will be removed from the channel.
send(Chan,Msg)
sends a message to a channel
The message Msg will be appended in FIFO (first in,
first out) order to the channel Chan.
skip
a dummy statement
skip is always executable.
sorted_send(Chan,Msg)
sends a message to a channel
The message will be inserted in generic order, using the
samsort/2 predicate from the SICStus build-in [CS].
unless(Main,Escape)
similar to the repitition and selection constructs
The executability of all basic statements in the
Main sequence is constrained the non-executability
of all guard statements of the Escape sequence. If one
of the guard statements of the Escape sequence becomes
executable, execution proceeds with the remainder of the
Escape sequence and does not return to the Main
sequence [Hol06].
B
B
PROMELA GRAMMAR
54
Promela Grammar
/* Promela grammar, taken from Holzmann’s SPIN book */
/* modified by Dennis Winter, 2009 */
Package promela;
Helpers
// letters and digits
alpha = [[’a’ .. ’z’] + [’A’ .. ’Z’]];
digit = [’0’ .. ’9’] ;
byterange = [0 .. 255];
any_ascii_char = [[0 .. 0xffff] - ’"’]; /* THIS IS ACTUALLY ANY
UNICODE CHARACTER */
unicode_input_character = [0..0xffff];
ht = 0x0009;
lf = 0x000a;
ff = 0x000c;
cr = 0x000d;
sp = ’ ’;
line_terminator = lf | cr | cr lf;
input_character = [unicode_input_character - [cr + lf]];
not_star =
[input_character - ’*’] | line_terminator;
not_star_not_slash = [input_character - [’*’ + ’/’]] |
line_terminator;
Tokens
white_space = (sp | ht | ff | line_terminator)*;
traditional_comment = ’/*’ not_star* ’*’+
(not_star_not_slash not_star* ’*’+)* ’/’;
end_of_line_comment = ’//’ input_character* line_terminator?;
number = digit+;
bang = ’!’;
complement = ’~’;
multop = ’*’ | ’/’ | ’%’;
plus = ’+’;
minus = ’-’;
shiftop = ’<<’ | ’>>’;
eqop = ’==’ | ’!=’;
bitand = ’&’;
bitor = ’|’;
B
PROMELA GRAMMAR
bitxor = ’^’;
and = ’&&’;
or = ’||’;
lt = ’<’;
gt = ’>’;
relop = ’<=’ | ’>=’;
l_parenthese = ’(’;
r_parenthese = ’)’;
l_brace = ’{’;
r_brace = ’}’;
l_bracket = ’[’;
r_bracket = ’]’;
semicolon = ’;’;
number_sign = ’#’;
comma = ’,’;
dot = ’.’;
assign = ’=’;
colon_colon = ’::’;
colon = ’:’;
plus_plus = ’++’;
minus_minus = ’--’;
rightarrow = ’->’;
bang_bang = ’!!’;
query_query = ’??’;
query = ’?’;
underscore = ’_’;
quotes = ’"’;
at = ’@’;
apostrophe = ’’’;
backslash = ’\’;
activetok = ’active’;
assert = ’assert’;
atomic = ’atomic’;
bit = ’bit’;
bool = ’bool’;
break = ’break’;
byte = ’byte’;
chanop = ’full’ | ’empty’ | ’nfull’ | ’nempty’;
55
B
PROMELA GRAMMAR
56
chan = ’chan’;
d_step = ’d_step’;
do = ’do’;
else = ’else’;
enabled =’enabled’;
eval = ’eval’;
false = ’false’;
fi = ’fi’;
goto =’goto’;
hidden = ’hidden’;
if = ’if’;
inittok = ’init’;
int = ’int’;
len = ’len’;
line = ’line’;
mtypetok = ’mtype’;
nevertok = ’never’;
notrace = ’notrace’;
np = ’np_’;
od = ’od’;
of = ’of’;
pc_value = ’pc_value’;
pid = ’pid’;
printf = ’printf’;
printm = ’printm’;
prioritytok = ’priority’;
processid = ’_pid’;
nr_pr = ’_nr_pr’;
inlinetok = ’inline’;
proctypetok = ’proctype’;
provided = ’provided’;
run = ’run’;
short = ’short’;
show = ’show’;
skip = ’skip’;
timeout = ’timeout’;
tracetok = ’trace’;
true = ’true’;
typedef = ’typedef’;
unless = ’unless’;
unsigned = ’unsigned’;
xr = ’xr’;
xs = ’xs’;
name = alpha (alpha | digit | ’_’)*;
/* NEED TO CHECK THAT THIS
CORRECTLY DEFINES NAMES ADD _pid as special case */
B
PROMELA GRAMMAR
57
byteliteral = byterange;
string = ’"’ any_ascii_char* ’"’;
/*******************************************************************
* Ignored Tokens
*
*******************************************************************/
Ignored Tokens
white_space,
traditional_comment,
end_of_line_comment;
/*******************************************************************
* Productions
*
*******************************************************************/
Productions
spec =
module+ ;
module =
{cpp_comment} cpp_comment |
/* comments from the
C-preprocessor */
{anarres_cpp_comment} anarres_cpp_comment | /* comments from the
anarres C-preprocessor */
{utype} utype separator? |
/* user defined types */
{mtype} mtype separator? |
/* mtype declaration */
{varschans} one_decl separator? |
/* global vars, chans */
{inline} inline |
/* inline declaration */
{proctype} proctype |
/* proctype declaration */
{init} init |
/* init process - max 1 per model */
{never} never |
/* never claim - max 1 per model */
{trace} trace ;
/* event trace - max 1 per model */
cpp_comment =
{long} number_sign [first]:number string [second]:number |
{short} number_sign number string ;
anarres_cpp_comment =
number_sign line number string ;
inline =
inlinetok name l_parenthese arg_lst? r_parenthese
l_brace sequence r_brace ;
proctype =
active? proctypetok name l_parenthese decl_lst? r_parenthese
B
PROMELA GRAMMAR
priority? enabler? l_brace sequence r_brace ;
init =
inittok priority? l_brace sequence r_brace ;
never =
nevertok l_brace sequence r_brace ;
trace =
{trace} tracetok l_brace sequence r_brace |
{notrace} notrace l_brace sequence r_brace ;
utype =
typedef name l_brace udecl+ r_brace ;
udecl =
one_decl separator? ;
mtype =
mtypetok assign? l_brace name_lst r_brace ;
name_lst =
{one} name |
{many} name comma name_lst ;
decl_lst =
{one} one_decl |
{many} one_decl separator decl_lst ;
one_decl =
visible? typename ivar_lst ;
ivar_lst =
{one} ivar |
{many} ivar comma ivar_lst ;
typename =
{bit} bit |
{bool} bool |
{byte} byte |
{pid} pid |
{short} short |
{int} int |
{mtype} mtypetok |
{chan} chan |
{uname} name | /* user defined typenames (see utype) */
{unsigned} unsigned; /* unsigned is not in the SPIN book
58
B
PROMELA GRAMMAR
59
grammar */
typenamelst =
{one} typename |
{many} typename comma typenamelst ;
active =
{one} activetok |
{many} activetok l_bracket const r_bracket ; /* instantiation */
priority =
prioritytok const ; /* simulation only */
enabler =
provided l_parenthese expr r_parenthese ; /* constraint */
visible =
{hidden} hidden |
{show} show ;
sequence =
{null} |
{one} step |
{many} step separator sequence |
{atomic} atomic l_brace [main]:sequence r_brace
separator? [next]:sequence |
{dstep} d_step l_brace [main]:sequence r_brace
separator? [next]:sequence |
{braces} l_brace [main]:sequence r_brace
separator? [next]:sequence |
{label} name colon sequence |
{else} else separator? sequence ;
step =
{declaration} one_decl |
{stmnt} stmnt |
{unless} [main]:stmnt unless [escape]:stmnt |
{exreceive} xr varreflst |
{exsend} xs varreflst ;
varreflst =
{one} varref |
{many} varref comma varreflst ;
ivar =
{single} name width? ivarassignment? |
{array} name l_bracket const r_bracket ivarassignment? ;
B
PROMELA GRAMMAR
60
ivarassignment =
{variable} assign expr |
{channel} assign l_bracket const r_bracket of l_brace
typenamelst r_brace;
width =
colon const;
varref =
{single} name arrayref? |
{record} varref dot name arrayref? |
{string} [left]:apostrophe backslash? name [right]:apostrophe ;
arrayref =
l_bracket expr r_bracket ;
recordref =
dot varref ;
send =
{fifo} varref bang send_args |
{sorted} varref bang_bang send_args ;
/* fifo send */
/* sorted send */
receive =
{fifo} varref query recv_args |
{random} varref query_query recv_args |
{fifopoll} varref query lt recv_args gt |
{randompoll} varref query_query lt recv_args
/*
/*
/*
gt
fifo receive */
random receive */
poll */
; /* random
poll */
recv_poll =
{fifo} varref query l_bracket recv_args r_bracket |
{random} varref query_query l_bracket recv_args r_bracket ;
send_args =
{list} arg_lst |
{headedlist} expr l_parenthese arg_lst r_parenthese ;
arg_lst =
{one} expr |
{many} expr comma arg_lst ;
recv_args =
{one} recv_arg |
{many} recv_arg comma recv_args |
{manyheaded1} recv_arg l_parenthese recv_args r_parenthese |
B
PROMELA GRAMMAR
61
{manyheaded2} l_parenthese recv_args r_parenthese ;
recv_arg =
{var} varref |
{eval} eval l_parenthese expr r_parenthese |
{const} const |
{underscore} underscore ;
assignment =
{assignment} varref assign expr | /* assignment */
{increment} varref plus_plus | /* increment */
{decrement} varref minus_minus ; /* decrement */
stmnt =
{run_inline} name l_parenthese arg_lst? r_parenthese |
{if} if options fi | /* selection */
{do} do options od | /* iteration */
{send} send |
{receive} receive |
{assign} assignment |
{break} break | /* only inside loops */
{goto} goto name | /* anywhere */
{printm} printm l_parenthese expr r_parenthese |
{printf} printf l_parenthese string r_parenthese |
{printwithargs} printf l_parenthese string comma
arg_lst r_parenthese |
{assert} assert expr |
{expression} expr ; /* condition */
options =
colon_colon sequence options? ;
factor =
{parenthese} l_parenthese expr r_parenthese |
{length} len l_parenthese varref r_parenthese |
/* nr of messages
in chan */
{recv_poll} recv_poll |
{varref} varref |
{const} const |
{timeout} timeout | /* hang system state */
{nonprogress} np |
/* non-progress system state */
{enabled} enabled l_parenthese expr r_parenthese |
{pc_value} pc_value l_parenthese expr r_parenthese |
{remoteref} [process]:name l_bracket expr r_bracket
at [label]:name |
{run} run name l_parenthese arg_lst? r_parenthese priority? |
{chanop} chanop l_parenthese varref r_parenthese |
B
PROMELA GRAMMAR
{conditional} l_parenthese [if]:expr rightarrow [then]:expr
colon [else]:expr r_parenthese ;
un_expr =
{simple} factor |
{not} bang factor |
{complement} complement factor ;
mult_expr =
{simple} un_expr |
{compound} un_expr multop mult_expr ;
add_expr =
{simple} mult_expr |
{compoundplus} mult_expr plus add_expr |
{compoundminus} mult_expr minus add_expr ;
shift_expr =
{simple} add_expr |
{compound} add_expr shiftop shift_expr ;
rel_expr =
{simple} shift_expr |
{compoundrelop} shift_expr relop rel_expr |
{compoundgt} shift_expr gt rel_expr |
{compoundlt} shift_expr lt rel_expr ;
eq_expr =
{simple} rel_expr |
{compound} rel_expr eqop eq_expr ;
bitand_expr =
{simple} eq_expr |
{compound} eq_expr bitand bitand_expr ;
bitxor_expr =
{simple} bitand_expr |
{compound} bitand_expr bitxor bitxor_expr ;
bitor_expr =
{simple} bitxor_expr |
{compound} bitxor_expr bitor bitor_expr ;
and_expr =
{simple} bitor_expr |
{compound} bitor_expr and and_expr ;
62
B
PROMELA GRAMMAR
or_expr =
{simple} and_expr |
{compound} and_expr or or_expr ;
expr =
{simple} or_expr ;
const =
{true} true |
{false} false |
{skip} skip |
{number} minus? number |
{pid} processid |
{nr_pr} nr_pr ;
separator =
{semicolon} semicolon+ |
{rightarrow} rightarrow ;
// end of grammar
63
REFERENCES
64
References
[App02]
A PPEL, Andrew W.: Modern compiler implementation in Java. Cambridge
University Press, 2002. – ISBN 0–521–82060–X
[BA]
B EN -A RI, Moti: Tools for Teaching Concurrency with Spin. http://stwww.
weizmann.ac.il/g-cs/benari/jspin. – Online Resource, Retrieved
on March 18, 2009
[BA08]
B EN -A RI, Mordechai: Principles of the Spin Model Checker. Springer, 2008. –
ISBN 978–1–84628–769–5
[BCM+ 98]
B URCH, J.R. ; C LARKE, E.M. ; M C M ILLAN, K.L. ; D ILL, D.L. ; H WANG, L.J.:
Symbolic model checking: 1020 states and beyond. In: Information and Computation 2 (1998), p. 142–170
[Ben07]
B ENDISPOSTO, Jens M.: A Framework for Semantic-Aware Editors in
Eclipse. (2007). – Master thesis
[Bol08]
B OLZ, Carl F.: Automatic JIT Compiler Generation with Runtime Partial
Evaluation. (2008). – Master thesis
[Bry86]
B RYANT, Randal E.: Graph-Based Algorithms for Boolean Function Manipulation. In: IEEE Transactions on Computers 35 (1986), No. 8, p. 677–691
[But]
B UTLER, Michael: Event-B and the Rodin Platform. http://www.event-b.
org. – Online Resource, Retrieved on March 18, 2009
[Cla]
C LARKE, Edmund M.: Automatic verification of computer hardware and software. http://www.cs.cmu.edu/~emc. – Online Resource, Retrieved on
March 18, 2009
[CM03]
C LOCKSIN, William F. ; M ELLISH, Christopher S.: Programming in Prolog.
Springer, 2003. – ISBN 3540006788
[CS]
C OMPUTER S CIENCE, Swedish I.:
Documentation for SICStus Prolog
4. http://www.sics.se/sicstus/docs/latest4/html/sicstus.
html. – Online Resource, Retrieved on March 18, 2009
[Dep]
Deploy. http://www.deploy-project.eu. –
trieved on March 18, 2009
[DG05]
D ONALDSON, Alastair F. ; G AY, Simon J.: ETCH: An Enhanced Type
Checking Tool for Promela. In: 12th International SPIN Workshop on Model
Checking of Software 3639 (2005), p. 237–242. – Available at http://www.
allydonaldson.co.uk/etch
[EF]
E CLIPSE F OUNDATION, Inc.: Eclipse Homepage. http://www.eclipse.
org. – Online Resource, Retrieved on March 18, 2009
Online-Resource, Re-
[EMCGP99] E DMUND M. C LARKE, Jr. ; G RUMBERG, Orna ; P ELED, Doron A.: Model
Checking. The MIT Press, 1999
REFERENCES
65
[Fut]
Wikipedia: Partial evaluation.
http://en.wikipedia.org/wiki/
Partial_evaluation. – Online Resource, Retrieved on March 18, 2009
[GG07]
G ANAI, Malay ; G UPTA, Aarti: SAT-Based Scalable Formal Verification Solutions. Springer, 2007
[Hol06]
H OLZMANN, Gerard J.: The Spin model checker: primer and reference manual.
Addison-Wesley, 2006 (3). – ISBN 0–321–22862–8
[JGS93]
J ONES, Neil D. ; G OMARD, Carsten K. ; S ESTOFT, Peter: Partial Evaluation
and Automatic Program Generation. Prentice Hall International, 1993. – Available at http://www.itu.dk/people/sestoft/pebook. – ISBN 0–13–
020249–5
[LAB+ 01]
L EUSCHEL, Michael ; A DHIANTO, Laksono ; B UTLER, Michael ; F ERREIRA,
Carla ; M IKHAILOV, Leonid: Animation and Model Checking of CSP and B
using Prolog Technology. (2001), p. 97 ff
[LB03]
L EUSCHEL, Michael ; B UTLER, Michael: ProB: A Model Checker for B. In:
A RAKI, Keijiro (Hrsg.) ; G NESI, Stefania (Hrsg.) ; M ANDRIOLI, Dino (Hrsg.):
FME 2003: Formal Methods, Springer, 2003 (LNCS 2805). – ISBN 3–540–
40828–2, p. 855–874
[LBL07]
L IGOT, Olivier ; B ENDISPOSTO, Jens ; L EUSCHEL, Michael: Debugging
Event-B Models using the ProB Disprover Plug-in. In: Proceedings AFADL
’07 (2007)
[Leu08]
L EUSCHEL, Michael: The High Road to Formal Validation: Model Checking
High-Level versus Low-Level Specifications. (2008)
[LTL]
Wikipedia: Lineare temporale Logik. http://de.wikipedia.org/wiki/
Lineare_temporale_Logik. – Online Resource, Retrieved on March 18,
2009
[PL07]
P LAGGE, Daniel ; L EUSCHEL, Michael: Validating Z Specifications using
the ProB Animator and Model Checker. In: Integrated Formal Methods 4591
(2007), p. 480–500
[PL08]
P LAGGE, Daniel ; L EUSCHEL, Michael: Seven at one stroke: LTL model
checking for High-level Specifications in B, Z, CSP and more. In: STTT
(2008)
[Sab]
The SableCC Project. http://sablecc.org. – Online Resource, Retrieved
on March 18, 2009
[She]
S HEVEK: JCPP - A Java C Preprocessor. http://www.anarres.org/
projects/jcpp. – Online Resource, Retrieved on March 18, 2009
[SPI]
ON-THE-FLY, LTL MODEL CHECKING with SPIN. http://spinroot.
com/spin/whatispin.html. – Online Resource, Retrieved on March 18,
2009
REFERENCES
66
[TB06]
T URNER, Edd ; B UTLER, Michael: Symmetry Reduction in the ProB Model
Checker. In: FM2006 Doctoral Symposium (2006), p. 21–27
[THC01]
T HOMAS H. C ORMEN, Ronald L. R.: Introduction to Algorithms. B&T, 2001
(2). – ISBN 0–262–03293–3
[WHS06]
WANG, Chao ; H ACHTEL, Gary D. ; S OMENZI, Fabio: Abstraction Refinement
for Large Scale Model Checking. Springer, 2006
[Wie]
W IELEMAKER, Jan:
Documentation for SWI-Prolog.
http://www.
swi-prolog.org/. – Online Resource, Retrieved on March 18, 2009
LIST OF FIGURES
67
List of Figures
1
ProB GUI snapshot during Animation . . . . . . . . . . . . . . . . . . . . .
7
2
Snapshot of a State Graph computed by ProB . . . . . . . . . . . . . . . . .
8
3
Promela Plugin Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
4
Interpreter: System Variables . . . . . . . . . . . . . . . . . . . . . . . . . .
12
5
Variables, Proctypes and Channels . . . . . . . . . . . . . . . . . . . . . . .
13
6
A Promela State Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
7
The Visitor Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
8
Static variables of the PromelaVisitor class . . . . . . . . . . . . . . . . . . .
22
9
ProB Model Checking Options . . . . . . . . . . . . . . . . . . . . . . . . .
23
10
Semantics of LTL formulas . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
11
Control Flow Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
12
Partial Evaluator for Promela . . . . . . . . . . . . . . . . . . . . . . . . . .
35
13
Finite State Automata T1 and T2 . . . . . . . . . . . . . . . . . . . . . . . .
37
14
Possible system runs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
15
Product of T1 and T2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
16
Reduced product of T1 and T2 . . . . . . . . . . . . . . . . . . . . . . . . .
39
17
SPIN Compile-Time Options . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
18
Verification of the Sieve of Eratosthenes . . . . . . . . . . . . . . . . . . . .
42
19
Speed measurement between ProB and SPIN . . . . . . . . . . . . . . . . .
43
20
Verification of Mergesort (atomic) . . . . . . . . . . . . . . . . . . . . . . .
46
21
Verification of Mergesort (without atomic) . . . . . . . . . . . . . . . . . .
46
22
Graphical User Interface of XSPIN . . . . . . . . . . . . . . . . . . . . . . .
48
23
Graphical User Interface of JSPIN . . . . . . . . . . . . . . . . . . . . . . . .
49
Listings
1
Promela do-loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2
Promela interpreter: Initialization . . . . . . . . . . . . . . . . . . . . . . . .
11
3
User-defined structured data type (record) . . . . . . . . . . . . . . . . . .
14
4
Storage and look-up of a local array record variable . . . . . . . . . . . . .
14
5
The trans/3 predicate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
6
Interpreting a do construct . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
LISTINGS
68
7
Evaluation of X equals Y . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
8
Prolog syntax of the Promela do-loop . . . . . . . . . . . . . . . . . . . . .
19
9
Preprocessor generated comments . . . . . . . . . . . . . . . . . . . . . . .
21
10
File contents during LTL model checking . . . . . . . . . . . . . . . . . . .
25
11
Rendezvous Communication . . . . . . . . . . . . . . . . . . . . . . . . . .
25
12
Promela example 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
13
Promela example 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
14
Promela example 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
15
Promela interpreter: unless statement . . . . . . . . . . . . . . . . . . . .
26
16
Promela interpreter: rendezvous send . . . . . . . . . . . . . . . . . . . . .
27
17
Rules added to the BE4 parser . . . . . . . . . . . . . . . . . . . . . . . . . .
28
18
LTL formula translated into Never Claim . . . . . . . . . . . . . . . . . . .
30
19
Producer Consumer expressed in a Never Claim . . . . . . . . . . . . . . .
31
20
Granularity of Promela programs . . . . . . . . . . . . . . . . . . . . . . . .
32
21
State graph expressed in a Never Claim . . . . . . . . . . . . . . . . . . . .
33
22
The Sieve of Eratosthenes . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
23
Mergesort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
Download