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