CSE 503 – Software Engineering Lecture 5: An introduction to the Spin model checker Rob DeLine 12 Apr 2004
Exploring state Using Alloy to explore state Model system state using sets and relations Define state changing operations declaratively as functions Use asserts to test properties of particular operation sequences Alloy tries to find states that break asserts Using Promela (language)/Spin (tool) to explore state Model system using processes with local variables and channels Define state changing operations imperatively as algorithms Use temporal logic formulae/asserts to specify states that we want/don’t want the system to reach Spin tries to find operation sequences that break formulae/asserts Today: Promela syntax and semantics, use of Spin Wed: Case studies
Promela data types Basic types (integers of various widths) bit, bool 1-bit, unsigned byte 8-bit, unsigned short 16-bit, signed int 32-bit, signed Arrays (index starts at zero) bit[8] Records typedef Header { bit flag0, flag1; byte dest; } Enumerated types (underlying type is byte) mtype = { ack, nack }
Variable declarations and assignments int x; // basic types initialize to zero by default byte b = 123; byte[3] array; byte[3] initArray = 1; // all elements initialized to 1 Header h; // apparently, no initializer allowed byte msgtype = ack; // mtypes are just bytes Assignment has C’s syntax x = 3; array[2] = y;
Expressions Promela has many expressions from C Arithmetic: a+b, a-b, a*b, a/b, a%b Comparisons: a==b, a!=b, a<b, a<=b, a>b, a>=b Logicals: a&&b, a||b,! a Bit-level: a&b, a|b, a^b, ~a, a<<b, a>>b Side-effecting: a++, a-- (postfix only) Plus other Promela-specific expressions (more later)
Process declarations Promela program consists of one or more processes Program begins with distinguished process “init” (no parameters) Start a process with “run” command, which returns a process id A process is defined with “proctype” proctype Hello () { printf(“hi\n”); } init { run Hello(); } Process != procedure No return value, no call stack if A “runs” B, then A and B execute concurrently However, process cannot end until all processes that it runs end
Control statements Conditionals (execute any branch whose condition is true) if :: condition0 -> statements0 :: conditionn -> statementsn fi Loops do od Unconditional jumps: break (leave nearest do); goto label assert(cond) means crash if cond isn’t true
Executability Expressions as statements Like C, an expression can be used as a statement Unlike C, an expression statement is blocked until it is true This is useful for making a process wait for a condition Conditionals and loops revisted General forms if :: statements0 :: ... :: statementsn fi do :: statements0 :: ... :: statementsn od S -> T is equivalent to S; T if/do nondeterministically choose any executable statement Timeout Special statement “timeout” is executable iff every process is blocked at a non-executable statement
What does this print? Two answers int i = 0; proctype A () { do :: i == 10 -> break; :: i % 2 == 0 -> printf(“%d\n”, i); i++; :: i % 2 == 1 -> i++; od } init { run A(); } Two answers 0, 2, 4, 6, 8 all even naturals
Concurrency Processes run concurrently Execution is interleaved on the statement level You can force multiple statements to be interleaved as one atomic { statements } Two forms of interaction between processes Global variables Shared communication channels
Producer/consumer using globals int buffer[10]; int i = 0; proctype Consumer () { int x; do :: i > 0 -> i--; x = buffer[i]; od } proctype Producer () { int item; do :: i < 10 -> buffer[i] = item; item++; i++; od init { run Consumer(); run Producer(); } Can any items get lost?
Oops! Race condition! initial Producer: buffer[i] = item; Producer: i++; Producer: buffer[i] = item; Producer: i++; Consumer: i--; Consumer: x = buffer[i]; Producer: buffer[i] = item; Consumer: i--; Consumer: x = buffer[i]; Producer: i++; Producer: buffer[i] = item; 1 1 1 1 2 2 3
Repaired producer/consumer int buffer[10]; int i = 0; proctype Consumer () { int x; do :: i > 0 -> atomic { i--; x = buffer[i]; Use(x); } od } proctype Producer () { int item; do :: i < 10 -> atomic { buffer[i] = item; item++; i++; } od init { run Consumer(); run Producer(); }
Interaction using channels A channel is a fixed-size buffer for communication Channels are named and contain typed data chan name = [size] of { type0, ..., typen } Process can read channel data, which blocks when channel empty name?var0,...,varn Process can write channel data, which blocks when channel full name!expr0,...,exprn Channels can be global or local Channels can be passed as process parameters
Producer/consumer with channels chan buffer = [10] of {int} proctype Consumer () { int x; do :: buffer?x -> Use(x); od } proctype Producer () { int item; do :: buffer!item -> item++; od init { run Consumer(); run Producer(); } Let’s see Spin in action!