2520 lines
66 KiB
Plaintext
2520 lines
66 KiB
Plaintext
.HTML "Using SPIN
|
|
.\" runoff as:
|
|
.\" eqn file | tbl | troff -ms
|
|
.\"
|
|
.ds P \\s-1PROMELA\\s0
|
|
.ds V \\s-1SPIN\\s0
|
|
.ds C pcc
|
|
.\" .tr -\(hy
|
|
.EQ
|
|
delim $#
|
|
.EN
|
|
.TL
|
|
Using \*V
|
|
.AU
|
|
Gerard J. Holzmann
|
|
gerard@plan9.bell-labs.com
|
|
.AB
|
|
\*V can be used for proving or disproving logical properties
|
|
of concurrent systems.
|
|
To render the proofs, a concurrent system is first
|
|
modeled in a formal specification language called \*P.
|
|
The language allows one to specify the behaviors
|
|
of asynchronously executing
|
|
processes that may interact through synchronous
|
|
or asynchronous message passing, or through direct
|
|
access to shared variables.
|
|
.LP
|
|
System models specified in this way can be verified
|
|
for both safety and liveness properties. The specification
|
|
of general properties in linear time temporal logic is
|
|
also supported.
|
|
.LP
|
|
The first part of this manual
|
|
discusses the basic features of the specification language \*P.
|
|
The second part describes the verifier \*V.
|
|
.AE
|
|
.NH 1
|
|
The Language \*P
|
|
.LP
|
|
\*P is short for Protocol Meta Language [Ho91].
|
|
\*P is a \f2modeling\f1 language, not a programming language.
|
|
A formal model differs in two essential ways from an implementation.
|
|
First, a model is meant to be an abstraction of a design
|
|
that contains only those aspects of the design that are
|
|
directly relevant to the properties one is interested in proving.
|
|
Second, a formal model must contain things that are typically not part
|
|
of an implementation, such as worst-case assumptions about
|
|
the behavior of the environment that may interact with the
|
|
system being studied, and a formal statement of relevant correctness
|
|
properties. It is possible to mechanically extract abstract models
|
|
from implementation level code, as discussed, for instance in [HS99].
|
|
.LP
|
|
Verification with \*V is often performed in a series of steps,
|
|
with the construction of increasingly detailed models.
|
|
Each model can be verified under different types of
|
|
assumptions about the environment and for different
|
|
types of correctness properties.
|
|
If a property is not valid for the given assumptions about
|
|
system behavior, the verifier can produce a counter-example
|
|
that demonstrates how the property may be violated.
|
|
If a property is valid, it may be possible to simplify the
|
|
model based on that fact, and prove still other properties.
|
|
.LP
|
|
Section 1.1 covers the basic building blocks of the language.
|
|
Section 1.2 introduces the control flow structures.
|
|
Section 1.3 explains how correctness properties are specified.
|
|
Section 1.4 concludes the first part with a discussion of
|
|
special predefined variables and functions that can be used to
|
|
express some correctness properties.
|
|
.LP
|
|
Up to date manual pages for \*V can always be found online at:
|
|
.CW
|
|
http://cm.bell-labs.com/cm/cs/what/spin/Man/
|
|
.R
|
|
.NH 2
|
|
Basics
|
|
.LP
|
|
A \*P model can contain three different types of objects:
|
|
.IP
|
|
.RS
|
|
\(bu Processes (section 1.1.1),
|
|
.br
|
|
\(bu Variables (section 1.1.2),
|
|
.br
|
|
\(bu Message channels (section 1.1.3).
|
|
.RE
|
|
.LP
|
|
All processes are global objects.
|
|
For obvious reasons, a \*P model must contain at least one
|
|
process to be meaningful.
|
|
Since \*V is specifically meant to prove properties of
|
|
concurrent systems, a model typically contains more than
|
|
one process.
|
|
.LP
|
|
Message channels and variables, the two basic types of data objects,
|
|
can be declared with either a global scope or a local scope.
|
|
A data object with global scope can be referred to by all processes.
|
|
A data object with a local scope can be referred to by just a
|
|
single process: the process that declares and instantiates the object.
|
|
As usual, all objects must be declared in the specification
|
|
before they are referenced.
|
|
.NH 3
|
|
Processes
|
|
.LP
|
|
Here is a simple process that does nothing except print
|
|
a line of text:
|
|
.P1
|
|
init {
|
|
printf("it works\en")
|
|
}
|
|
.P2
|
|
There are a few things to note.
|
|
.CW Init
|
|
is a predefined keyword from the language.
|
|
It can be used to declare and instantiate
|
|
a single initial process in the model.
|
|
(It is comparable to the
|
|
.CW main
|
|
procedure of a C program.)
|
|
The
|
|
.CW init
|
|
process does not take arguments, but it can
|
|
start up (instantiate) other processes that do.
|
|
.CW Printf
|
|
is one of a few built-in procedures in the language.
|
|
It behaves the same as the C version.
|
|
Note, finally, that no semicolon follows the single
|
|
.CW printf
|
|
statement in the above example.
|
|
In \*P, semicolons are used as statement separators,
|
|
not statement terminators. (The \*V parser, however, is
|
|
lenient on this issue.)
|
|
.LP
|
|
Any process can start new processes by using another
|
|
built-in procedure called
|
|
.CW run .
|
|
For example,
|
|
.P1
|
|
proctype you_run(byte x)
|
|
{
|
|
printf("my x is: %d\en", x)
|
|
}
|
|
.P2
|
|
.P1
|
|
init {
|
|
run you_run(1);
|
|
run you_run(2)
|
|
}
|
|
.P2
|
|
The word
|
|
.CW proctype
|
|
is again a keyword that introduces the declaration
|
|
of a new type of process.
|
|
In this case, we have named that type
|
|
.CW you_run
|
|
and declared that all instantiations of processes
|
|
of this type will take one argument: a data object
|
|
of type
|
|
.CW byte ,
|
|
that can be referred to within this process by the name
|
|
.CW x .
|
|
Instances of a
|
|
.CW proctype
|
|
can be created with the predefined procedure
|
|
.CW run ,
|
|
as shown in the example.
|
|
When the
|
|
.CW run
|
|
statement completes, a copy of the process
|
|
has been started, and all its arguments have been
|
|
initialized with the arguments provided.
|
|
The process may, but need not, have performed
|
|
any statement executions at this point.
|
|
It is now part of the concurrent system,
|
|
and its execution can be interleaved arbitrarily with
|
|
those of the other, already executing processes.
|
|
(More about the semantics of execution follows shortly.)
|
|
.LP
|
|
In many cases, we are only interested in creating a
|
|
single instance of each process type that is declared,
|
|
and the processes require no arguments.
|
|
We can define this by prefixing the keyword
|
|
.CW proctype
|
|
from the process declaration with another keyword:
|
|
.CW active .
|
|
Instances of all active proctypes are created when the
|
|
system itself is initialized.
|
|
We could, for instance, have avoided the use of
|
|
.CW init
|
|
by declaring the corresponding process in the last example
|
|
as follows:
|
|
.P1
|
|
active proctype main() {
|
|
run you_run(1);
|
|
run you_run(2)
|
|
}
|
|
.P2
|
|
Note that there are no parameters to instantiate in this
|
|
case. Had they been declared, they would default to a
|
|
zero value, just like all other data objects
|
|
that are not explicitly instantiated.
|
|
.LP
|
|
Multiple copies of a process type can also be created in
|
|
this way. For example:
|
|
.P1
|
|
active [4] proctype try_me() {
|
|
printf("hi, i am process %d\en", _pid)
|
|
}
|
|
.P2
|
|
creates four processes.
|
|
A predefined variable
|
|
.CW _pid
|
|
is assigned to each running process, and holds
|
|
its unique process instantiation number.
|
|
In some cases, this number is needed when a reference
|
|
has to be made to a specific process.
|
|
.LP
|
|
Summarizing: process behavior is declared in
|
|
.CW proctype
|
|
definitions, and it is instantiated with either
|
|
.CW run
|
|
statements or with the prefix
|
|
.CW active .
|
|
Within a proctype declaration, statements are separated
|
|
(not terminated) by semicolons.
|
|
As we shall see in examples that follow, instead of the
|
|
semicolon, one can also use the alternative separator
|
|
.CW "->"
|
|
(arrow), wherever that may help to clarify the structure
|
|
of a \*P model.
|
|
.SH
|
|
Semantics of Execution
|
|
.LP
|
|
In \*P there is no difference between a condition or
|
|
expression and a statement.
|
|
Fundamental to the semantics of the language is the
|
|
notion of the \f2executability\f1 of statements.
|
|
Statements are either executable or blocked.
|
|
Executability is the basic means of enforcing
|
|
synchronization between the processes in a distributed system.
|
|
A process can wait for an event to happen by waiting
|
|
for a statement to become executable.
|
|
For instance, instead of writing a busy wait loop:
|
|
.P1
|
|
while (a != b) /* not valid Promela syntax */
|
|
skip; /* wait for a==b */
|
|
\&...
|
|
.P2
|
|
we achieve the same effect in \*P with the statement
|
|
.P1
|
|
(a == b);
|
|
\&...
|
|
.P2
|
|
Often we indicate that the continuation of an execution
|
|
is conditional on the truth of some expression by using
|
|
the alternate statement separator:
|
|
.P1
|
|
(a == b) -> \&...
|
|
.P2
|
|
Assignments and
|
|
.CW printf
|
|
statements are always executable in \*P.
|
|
A condition, however, can only be executed (passed) when it holds.
|
|
If the condition does not hold, execution blocks until it does.
|
|
There are similar rules for determining the executability
|
|
of all other primitive and compound statements in the
|
|
language.
|
|
The semantics of each statement is defined in terms of
|
|
rules for executability and effect.
|
|
The rules for executability set a precondition on the state
|
|
of the system in which a statement can be executed.
|
|
The effect defines how a statement will alter a
|
|
system state when executed.
|
|
.LP
|
|
\*P assumes that all individual statements are executed
|
|
atomically: that is, they model the smallest meaningful entities
|
|
of execution in the system being studied.
|
|
This means that \*P defines the standard asynchronous interleaving
|
|
model of execution, where a supposed scheduler is free at
|
|
each point in the execution to select any one of the processes
|
|
to proceed by executing a single primitive statement.
|
|
Synchronization constraints can be used to influence the
|
|
interleaving patterns. It is the purpose of a concurrent system's
|
|
design to constrain those patterns in such a way that no
|
|
correctness requirements can be violated, and all service
|
|
requirements are met. It is the purpose of the verifier
|
|
either to find counter-examples to a designer's claim that this
|
|
goal has been met, or to demonstrate that the claim is indeed valid.
|
|
.NH 3
|
|
Variables
|
|
.LP
|
|
The table summarizes the five basic data types used in \*P.
|
|
.CW Bit
|
|
and
|
|
.CW bool
|
|
are synonyms for a single bit of information.
|
|
The first three types can store only unsigned quantities.
|
|
The last two can hold either positive or negative values.
|
|
The precise value ranges of variables of types
|
|
.CW short
|
|
and
|
|
.CW int
|
|
is implementation dependent, and corresponds
|
|
to those of the same types in C programs
|
|
that are compiled for the same hardware.
|
|
The values given in the table are most common.
|
|
.KS
|
|
.TS
|
|
center;
|
|
l l
|
|
lw(10) lw(12).
|
|
=
|
|
\f3Type Range\f1
|
|
_
|
|
bit 0..1
|
|
bool 0..1
|
|
byte 0..255
|
|
short $-2 sup 15# .. $2 sup 15 -1#
|
|
int $-2 sup 31# .. $2 sup 31 -1#
|
|
_
|
|
.TE
|
|
.KE
|
|
.LP
|
|
The following example program declares a array of
|
|
two elements of type
|
|
.CW bool
|
|
and a scalar variable
|
|
.CW turn
|
|
of the same type.
|
|
Note that the example relies on the fact that
|
|
.CW _pid
|
|
is either 0 or 1 here.
|
|
.MT _sec5critical
|
|
.P1
|
|
/*
|
|
* Peterson's algorithm for enforcing
|
|
* mutual exclusion between two processes
|
|
* competing for access to a critical section
|
|
*/
|
|
bool turn, want[2];
|
|
|
|
active [2] proctype user()
|
|
{
|
|
again:
|
|
want[_pid] = 1; turn = _pid;
|
|
|
|
/* wait until this condition holds: */
|
|
(want[1 - _pid] == 0 || turn == 1 - _pid);
|
|
|
|
/* enter */
|
|
critical: skip;
|
|
/* leave */
|
|
|
|
want[_pid] = 0;
|
|
goto again
|
|
}
|
|
.P2
|
|
In the above case, all variables are initialized to zero.
|
|
The general syntax for declaring and instantiating a
|
|
variable, respectively for scalar and array variables, is:
|
|
.P1
|
|
type name = expression;
|
|
type name[constant] = expression
|
|
.P2
|
|
In the latter case, all elements of the array are initialized
|
|
to the value of the expression.
|
|
A missing initializer fields defaults to the value zero.
|
|
As usual, multiple variables of the same type can be grouped
|
|
behind a single type name, as in:
|
|
.P1
|
|
byte a, b[3], c = 4
|
|
.P2
|
|
In this example, the variable
|
|
.CW c
|
|
is initialized to the value 4; variable
|
|
.CW a
|
|
and the elements of array
|
|
.CW b
|
|
are all initialized to zero.
|
|
.LP
|
|
Variables can also be declared as structures.
|
|
For example:
|
|
.P1
|
|
typedef Field {
|
|
short f = 3;
|
|
byte g
|
|
};
|
|
|
|
typedef Msg {
|
|
byte a[3];
|
|
int fld1;
|
|
Field fld2;
|
|
chan p[3];
|
|
bit b
|
|
};
|
|
|
|
Msg foo;
|
|
.P2
|
|
introduces two user-defined data types, the first named
|
|
.CW Field
|
|
and the second named
|
|
.CW Msg .
|
|
A single variable named
|
|
.CW foo
|
|
of type
|
|
.CW Msg
|
|
is declared.
|
|
All fields of
|
|
.CW foo
|
|
that are not explicitly initialized (in the example, all fields except
|
|
.CW foo.fld2.f )
|
|
are initialized to zero.
|
|
References to the elements of a structure are written as:
|
|
.P1
|
|
foo.a[2] = foo.fld2.f + 12
|
|
.P2
|
|
A variable of a user-defined type can be passed as a single
|
|
argument to a new process in
|
|
.CW run
|
|
statements.
|
|
For instance,
|
|
.P1
|
|
proctype me(Msg z) {
|
|
z.a[2] = 12
|
|
}
|
|
init {
|
|
Msg foo;
|
|
run me(foo)
|
|
}
|
|
.P2
|
|
.LP
|
|
Note that even though \*P supports only one-dimensional arrays,
|
|
a two-dimensional array can be created indirectly with user-defined
|
|
structures, for instance as follows:
|
|
.P1
|
|
typedef Array {
|
|
byte el[4]
|
|
};
|
|
|
|
Array a[4];
|
|
.P2
|
|
This creates a data structure of 16 elements that can be
|
|
referenced, for instance, as
|
|
.CW a[i].el[j] .
|
|
.LP
|
|
As in C, the indices of an array of
|
|
.CW N
|
|
elements range from 0 to
|
|
.CW N-1 .
|
|
.SH
|
|
Expressions
|
|
.LP
|
|
Expressions must be side-effect free in \*P.
|
|
Specifically, this means that an expression cannot
|
|
contain assignments, or send and receive operations (see section 1.1.3).
|
|
.P1
|
|
c = c + 1; c = c - 1
|
|
.P2
|
|
and
|
|
.P1
|
|
c++; c--
|
|
.P2
|
|
are assignments in \*P, with the same effects.
|
|
But, unlike in C,
|
|
.P1
|
|
b = c++
|
|
.P2
|
|
is not a valid assignment, because the right-hand side
|
|
operand is not a valid expression in \*P (it is not side-effect free).
|
|
.LP
|
|
It is also possible to write a side-effect free conditional
|
|
expression, with the following syntax:
|
|
.P1
|
|
(expr1 -> expr2 : expr3)
|
|
.P2
|
|
The parentheses around the conditional expression are required to
|
|
avoid misinterpretation of the arrow.
|
|
The example expression has the value of \f(CWexpr2\f1 when \f(CWexpr1\f1
|
|
evaluates to a non-zero value, and the value of \f(CWexpr3\f1 otherwise.
|
|
.LP
|
|
In assignments like
|
|
.P1
|
|
variable = expression
|
|
.P2
|
|
the values of all operands used inside the expression are first cast to
|
|
signed integers before the operands are applied.
|
|
After the evaluation of the expression completes, the value produced
|
|
is cast to the type of the target variable before the assignment takes place.
|
|
.NH 3
|
|
Message Channels
|
|
.LP
|
|
Message channels are used to model the transfer of data
|
|
between processes.
|
|
They are declared either locally or globally,
|
|
for instance as follows:
|
|
.P1
|
|
chan qname = [16] of { short, byte }
|
|
.P2
|
|
The keyword
|
|
.CW chan
|
|
introduces a channel declaration.
|
|
In this case, the channel is named
|
|
.CW qname ,
|
|
and it is declared to be capable of storing up
|
|
to 16 messages.
|
|
Each message stored in the channel is declared here to
|
|
consist of two fields: one of type
|
|
.CW short
|
|
and one of type
|
|
.CW byte .
|
|
The fields of a message can be any one of the basic types
|
|
.CW bit ,
|
|
.CW bool ,
|
|
.CW byte ,
|
|
.CW short ,
|
|
.CW int ,
|
|
and
|
|
.CW chan ,
|
|
or any user-defined type.
|
|
Message fields cannot be declared as arrays.
|
|
.LP
|
|
A message field of type
|
|
.CW chan
|
|
can be used to pass a channel identifier
|
|
through a channel from one process to another.
|
|
.LP
|
|
The statement
|
|
.P1
|
|
qname!expr1,expr2
|
|
.P2
|
|
sends the values of expressions
|
|
.CW expr1
|
|
and
|
|
.CW expr2
|
|
to the channel that we just created. It appends
|
|
the message field created from the values of the two
|
|
expressions (and cast to the appropriate types of the
|
|
message fields declared for
|
|
.CW qname )
|
|
to the tail of the message buffer of 16 slots that belongs
|
|
to channel
|
|
.CW qname .
|
|
By default the send statement is only executable if the target
|
|
channel is non-full.
|
|
(This default semantics can be changed in the verifier into
|
|
one where the send statement is always executable, but the
|
|
message will be lost when an attempt is made to append it to
|
|
a full channel.)
|
|
.LP
|
|
The statement
|
|
.P1
|
|
qname?var1,var2
|
|
.P2
|
|
retrieves a message from the head of the same buffer,
|
|
and stores the two expressions in variables
|
|
.CW var1
|
|
and
|
|
.CW var2 .
|
|
.LP
|
|
The receive statement is executable only if the source channel
|
|
is non-empty.
|
|
.LP
|
|
If more parameters are sent per message than were declared
|
|
for the message channel, the redundant parameters are lost.
|
|
If fewer parameters are sent than declared,
|
|
the value of the remaining parameters is undefined.
|
|
Similarly, if the receive operation tries to retrieve more
|
|
parameters than available, the value of the extra parameters is
|
|
undefined; if it receives fewer than the number of parameters
|
|
sent, the extra information is lost.
|
|
.LP
|
|
An alternative, and equivalent, notation for the
|
|
send and receive operations is to structure the
|
|
message fields with parentheses, as follows:
|
|
.P1
|
|
qname!expr1(expr2,expr3)
|
|
qname?var1(var2,var3)
|
|
.P2
|
|
In the above case, we assume that
|
|
.CW qname
|
|
was declared to hold messages consisting of three fields.
|
|
.PP
|
|
Some or all of the arguments of the receive operation
|
|
can be given as constants instead of as variables:
|
|
.P1
|
|
qname?cons1,var2,cons2
|
|
.P2
|
|
In this case, an extra condition on the executability of the
|
|
receive operation is that the value of all message fields
|
|
specified as constants match the value of the corresponding
|
|
fields in the message that is to be received.
|
|
.LP
|
|
Here is an example that uses some of the mechanisms introduced
|
|
so far.
|
|
.P1
|
|
proctype A(chan q1)
|
|
{ chan q2;
|
|
q1?q2;
|
|
q2!123
|
|
}
|
|
.P2
|
|
.P1
|
|
proctype B(chan qforb)
|
|
{ int x;
|
|
qforb?x;
|
|
printf("x = %d\en", x)
|
|
}
|
|
.P2
|
|
.P1
|
|
init {
|
|
chan qname = [1] of { chan };
|
|
chan qforb = [1] of { int };
|
|
run A(qname);
|
|
run B(qforb);
|
|
qname!qforb
|
|
}
|
|
.P2
|
|
The value printed by the process of type
|
|
.CW B
|
|
will be
|
|
.CW 123 .
|
|
.LP
|
|
A predefined function
|
|
.CW len(qname)
|
|
returns the number of messages currently
|
|
stored in channel
|
|
.CW qname .
|
|
Two shorthands for the most common uses of this
|
|
function are
|
|
.CW empty(qname)
|
|
and
|
|
.CW full(qname) ,
|
|
with the obvious connotations.
|
|
.LP
|
|
Since all expressions must be side-effect free,
|
|
it is not valid to say:
|
|
.P1
|
|
(qname?var == 0)
|
|
.P2
|
|
or
|
|
.P1
|
|
(a > b && qname!123)
|
|
.P2
|
|
We could rewrite the second example (using an atomic sequence,
|
|
as explained further in section 1.2.1):
|
|
.P1
|
|
atomic { (a > b && !full(qname)) -> qname!123 }
|
|
.P2
|
|
The meaning of the first example is ambiguous. It could mean
|
|
that we want the condition to be true if the receive operation
|
|
is unexecutable. In that case, we can rewrite it without
|
|
side-effects as:
|
|
.P1
|
|
empty(qname)
|
|
.P2
|
|
It could also mean that we want the condition
|
|
to be true when the channel does contain a message with
|
|
value zero.
|
|
We can specify that as follows:
|
|
.P1
|
|
atomic { qname?[0] -> qname?var }
|
|
.P2
|
|
The first statement of this atomic sequence is
|
|
an expression without side-effects that
|
|
evaluates to a non-zero value only if the
|
|
receive operation
|
|
.P1
|
|
qname?0
|
|
.P2
|
|
would have been executable at that
|
|
point (i.e., channel
|
|
.CW qname
|
|
contains at least one message and the oldest
|
|
message stored consists of one message field
|
|
equal to zero).
|
|
Any receive statement can be turned into
|
|
a side-effect free expression by placing square
|
|
brackets around the list of all message parameters.
|
|
The channel contents remain undisturbed by the
|
|
evaluation of such expressions.
|
|
.LP
|
|
Note carefully, however, that in non-atomic sequences
|
|
of two statements such as
|
|
.P1
|
|
!full(qname) -> qname!msgtype
|
|
.P2
|
|
and
|
|
.P1
|
|
qname?[msgtype] -> qname?msgtype
|
|
.P2
|
|
the second statement is not necessarily executable
|
|
after the first one has been executed.
|
|
There may be race conditions when access to the channels
|
|
is shared between several processes.
|
|
Another process can send a message to the channel
|
|
just after this process determined that it was not full,
|
|
or another process can steal away the
|
|
message just after our process determined its presence.
|
|
.LP
|
|
Two other types of send and receive statements are used
|
|
less frequently: sorted send and random receive.
|
|
A sorted send operation is written with two, instead of one,
|
|
exclamation marks, as follows:
|
|
.P1
|
|
qname!!msg
|
|
.P2
|
|
A sorted send operation will insert a message into the channel's buffer
|
|
in numerical order, instead of in FIFO order.
|
|
The channel contents are scanned from the first message towards the
|
|
last, and the message is inserted immediately before the first message
|
|
that follows it in numerical order.
|
|
To determine the numerical order, all message fields are
|
|
taken into account.
|
|
.LP
|
|
The logical counterpart of the sorted send operation
|
|
is the random receive.
|
|
It is written with two, instead of one, question marks:
|
|
.P1
|
|
qname??msg
|
|
.P2
|
|
A random receive operation is executable if it is executable for \f2any\f1
|
|
message that is currently buffered in a message channel (instead of
|
|
only for the first message in the channel).
|
|
Normal send and receive operations can freely be combined with
|
|
sorted send and random receive operations.
|
|
.SH
|
|
Rendezvous Communication
|
|
.LP
|
|
So far we have talked about asynchronous communication between processes
|
|
via message channels, declared in statements such as
|
|
.P1
|
|
chan qname = [N] of { byte }
|
|
.P2
|
|
where
|
|
.CW N
|
|
is a positive constant that defines the buffer size.
|
|
A logical extension is to allow for the declaration
|
|
.P1
|
|
chan port = [0] of { byte }
|
|
.P2
|
|
to define a rendezvous port.
|
|
The channel size is zero, that is, the channel
|
|
.CW port
|
|
can pass, but cannot store, messages.
|
|
Message interactions via such rendezvous ports are
|
|
by definition synchronous.
|
|
Consider the following example:
|
|
.P1
|
|
#define msgtype 33
|
|
|
|
chan name = [0] of { byte, byte };
|
|
|
|
active proctype A()
|
|
{ name!msgtype(124);
|
|
name!msgtype(121)
|
|
}
|
|
.P2
|
|
.P1
|
|
active proctype B()
|
|
{ byte state;
|
|
name?msgtype(state)
|
|
}
|
|
.P2
|
|
Channel
|
|
.CW name
|
|
is a global rendezvous port.
|
|
The two processes will synchronously execute their first statement:
|
|
a handshake on message
|
|
.CW msgtype
|
|
and a transfer of the value 124 to local variable
|
|
.CW state .
|
|
The second statement in process
|
|
.CW A
|
|
will be unexecutable,
|
|
because there is no matching receive operation in process
|
|
.CW B .
|
|
.LP
|
|
If the channel
|
|
.CW name
|
|
is defined with a non-zero buffer capacity,
|
|
the behavior is different.
|
|
If the buffer size is at least 2, the process of type
|
|
.CW A
|
|
can complete its execution, before its peer even starts.
|
|
If the buffer size is 1, the sequence of events is as follows.
|
|
The process of type
|
|
.CW A
|
|
can complete its first send action, but it blocks on the
|
|
second, because the channel is now filled to capacity.
|
|
The process of type
|
|
.CW B
|
|
can then retrieve the first message and complete.
|
|
At this point
|
|
.CW A
|
|
becomes executable again and completes,
|
|
leaving its last message as a residual in the channel.
|
|
.LP
|
|
Rendezvous communication is binary: only two processes,
|
|
a sender and a receiver, can be synchronized in a
|
|
rendezvous handshake.
|
|
.LP
|
|
As the example shows, symbolic constants can be defined
|
|
with preprocessor macros using
|
|
.CW #define .
|
|
The source text of a \*P model is translated by the standard
|
|
C preprocessor.
|
|
The disadvantage of defining symbolic names in this way is,
|
|
however, that the \*P parser will only see the expanded text,
|
|
and cannot refer to the symbolic names themselves.
|
|
To prevent that, \*P also supports another way to define
|
|
symbolic names, which are preserved in error reports.
|
|
For instance, by including the declaration
|
|
.P1
|
|
mtype = { ack, msg, error, data };
|
|
.P2
|
|
at the top of a \*P model, the names provided between the
|
|
curly braces are equivalent to integers of type
|
|
.CW byte ,
|
|
but known by their symbolic names to the \*V parser and the
|
|
verifiers it generates.
|
|
The constant values assigned start at 1, and count up.
|
|
There can be only one
|
|
.CW mtype
|
|
declaration per model.
|
|
.NH 2
|
|
Control Flow
|
|
.LP
|
|
So far, we have seen only some of the basic statements
|
|
of \*P, and the way in which they can be combined to
|
|
model process behaviors.
|
|
The five types of statements we have mentioned are:
|
|
.CW printf ,
|
|
.CW assignment ,
|
|
.CW condition ,
|
|
.CW send ,
|
|
and
|
|
.CW receive .
|
|
.LP
|
|
The pseudo-statement
|
|
.CW skip
|
|
is syntactically and semantically equivalent to the
|
|
condition
|
|
.CW (1)
|
|
(i.e., to true), and is in fact quietly replaced with this
|
|
expression by the lexical analyzer of \*V.
|
|
.LP
|
|
There are also five types of compound statements.
|
|
.IP
|
|
.RS
|
|
\(bu
|
|
Atomic sequences (section 1.2.1),
|
|
.br
|
|
\(bu
|
|
Deterministic steps (section 1.2.2),
|
|
.br
|
|
\(bu
|
|
Selections (section 1.2.3),
|
|
.br
|
|
\(bu
|
|
Repetitions (section 1.2.4),
|
|
.br
|
|
\(bu
|
|
Escape sequences (section 1.2.5).
|
|
.RE
|
|
.LP
|
|
.NH 3
|
|
Atomic Sequences
|
|
.LP
|
|
The simplest compound statement is the
|
|
.CW atomic
|
|
sequence:
|
|
.P1
|
|
atomic { /* swap the values of a and b */
|
|
tmp = b;
|
|
b = a;
|
|
a = tmp
|
|
}
|
|
.P2
|
|
In the example, the values of two variables
|
|
.CW a
|
|
and
|
|
.CW b
|
|
are swapped in a sequence of statement executions
|
|
that is defined to be uninterruptable.
|
|
That is, in the interleaving of process executions, no
|
|
other process can execute statements from the moment that
|
|
the first statement of this sequence begins to execute until
|
|
the last one has completed.
|
|
.LP
|
|
It is often useful to use
|
|
.CW atomic
|
|
sequences to start a series of processes in such a
|
|
way that none of them can start executing statements
|
|
until all of them have been initialized:
|
|
.P1
|
|
init {
|
|
atomic {
|
|
run A(1,2);
|
|
run B(2,3);
|
|
run C(3,1)
|
|
}
|
|
}
|
|
.P2
|
|
.CW Atomic
|
|
sequences may be non-deterministic.
|
|
If any statement inside an
|
|
.CW atomic
|
|
sequence is found to be unexecutable, however,
|
|
the atomic chain is broken, and another process can take over
|
|
control.
|
|
When the blocking statement becomes executable later,
|
|
control can non-deterministically return to the process,
|
|
and the atomic execution of the sequence resumes as if
|
|
it had not been interrupted.
|
|
.NH 3
|
|
Deterministic Steps
|
|
.LP
|
|
Another way to define an indivisible sequence of actions
|
|
is to use the
|
|
.CW d_step
|
|
statement.
|
|
In the above case, for instance, we could also have written:
|
|
.P1
|
|
d_step { /* swap the values of a and b */
|
|
tmp = b;
|
|
b = a;
|
|
a = tmp
|
|
}
|
|
.P2
|
|
The difference between a
|
|
.CW d_step
|
|
sequence
|
|
and an
|
|
.CW atomic
|
|
sequence are:
|
|
.IP \(bu
|
|
A
|
|
.CW d_step
|
|
sequence must be completely deterministic.
|
|
(If non-determinism is nonetheless encountered,
|
|
it is always resolved in a fixed and deterministic
|
|
way: i.e., the first true guard in selection or
|
|
repetition structures is always selected.)
|
|
.IP \(bu
|
|
No
|
|
.CW goto
|
|
jumps into or out of a
|
|
.CW d_step
|
|
sequence are permitted.
|
|
.IP \(bu
|
|
The execution of a
|
|
.CW d_step
|
|
sequence cannot be interrupted when a
|
|
blocking statement is encountered.
|
|
It is an error if any statement other than
|
|
the first one in a
|
|
.CW d_step
|
|
sequence is found to be unexecutable.
|
|
.IP \(bu
|
|
A
|
|
.CW d_step
|
|
sequence is executed as one single statement.
|
|
In a way, it is a mechanism for adding new types
|
|
of statements to the language.
|
|
.LP
|
|
None of the items listed above apply to
|
|
.CW atomic
|
|
sequences.
|
|
This means that the keyword
|
|
.CW d_step
|
|
can always be replaced with the keyword
|
|
.CW atomic ,
|
|
but the reverse is not true.
|
|
(The main, perhaps the only, reason for using
|
|
.CW d_step
|
|
sequences is to improve the efficiency of
|
|
verifications.)
|
|
.NH 3
|
|
Selection Structures
|
|
.LP
|
|
A more interesting construct is the selection structure.
|
|
Using the relative values of two variables
|
|
.CW a
|
|
and
|
|
.CW b
|
|
to choose between two options, for instance, we can write:
|
|
.P1
|
|
if
|
|
:: (a != b) -> option1
|
|
:: (a == b) -> option2
|
|
fi
|
|
.P2
|
|
The selection structure above contains two execution sequences,
|
|
each preceded by a double colon.
|
|
Only one sequence from the list will be executed.
|
|
A sequence can be selected only if its first statement is executable.
|
|
The first statement is therefore called a \f2guard\f1.
|
|
.LP
|
|
In the above example the guards are mutually exclusive, but they
|
|
need not be.
|
|
If more than one guard is executable, one of the corresponding sequences
|
|
is selected nondeterministically.
|
|
If all guards are unexecutable the process will block until at least
|
|
one of them can be selected.
|
|
There is no restriction on the type of statements that can be used
|
|
as a guard: it may include sends or receives, assignments,
|
|
.CW printf ,
|
|
.CW skip ,
|
|
etc.
|
|
The rules of executability determine in each case what the semantics
|
|
of the complete selection structure will be.
|
|
The following example, for instance, uses receive statements
|
|
as guards in a selection.
|
|
.P1
|
|
mtype = { a, b };
|
|
|
|
chan ch = [1] of { byte };
|
|
|
|
active proctype A()
|
|
{ ch!a
|
|
}
|
|
.P2
|
|
.P1
|
|
active proctype B()
|
|
{ ch!b
|
|
}
|
|
.P2
|
|
.P1
|
|
active proctype C()
|
|
{ if
|
|
:: ch?a
|
|
:: ch?b
|
|
fi
|
|
}
|
|
.P2
|
|
The example defines three processes and one channel.
|
|
The first option in the selection structure of the process
|
|
of type
|
|
.CW C
|
|
is executable if the channel contains
|
|
a message named
|
|
.CW a ,
|
|
where
|
|
.CW a
|
|
is a symbolic constant defined in the
|
|
.CW mtype
|
|
declaration at the start of the program.
|
|
The second option is executable if it contains a message
|
|
.CW b ,
|
|
where, similarly,
|
|
.CW b
|
|
is a symbolic constant.
|
|
Which message will be available depends on the unknown
|
|
relative speeds of the processes.
|
|
.LP
|
|
A process of the following type will either increment
|
|
or decrement the value of variable
|
|
.CW count
|
|
once.
|
|
.P1
|
|
byte count;
|
|
|
|
active proctype counter()
|
|
{
|
|
if
|
|
:: count++
|
|
:: count--
|
|
fi
|
|
}
|
|
.P2
|
|
Assignments are always executable, so the choice made
|
|
here is truly a non-deterministic one that is independent
|
|
of the initial value of the variable (zero in this case).
|
|
.NH 3
|
|
Repetition Structures
|
|
.LP
|
|
We can modify the above program as follows, to obtain
|
|
a cyclic program that randomly changes the value of
|
|
the variable up or down, by replacing the selection
|
|
structure with a repetition.
|
|
.P1
|
|
byte count;
|
|
|
|
active proctype counter()
|
|
{
|
|
do
|
|
:: count++
|
|
:: count--
|
|
:: (count == 0) -> break
|
|
od
|
|
}
|
|
.P2
|
|
Only one option can be selected for execution at a time.
|
|
After the option completes, the execution of the structure
|
|
is repeated.
|
|
The normal way to terminate the repetition structure is
|
|
with a
|
|
.CW break
|
|
statement.
|
|
In the example, the loop can be
|
|
broken only when the count reaches zero.
|
|
Note, however, that it need not terminate since the other
|
|
two options remain executable.
|
|
To force termination we could modify the program as follows.
|
|
.P1
|
|
active proctype counter()
|
|
{
|
|
do
|
|
:: (count != 0) ->
|
|
if
|
|
:: count++
|
|
:: count--
|
|
fi
|
|
:: (count == 0) -> break
|
|
od
|
|
}
|
|
.P2
|
|
A special type of statement that is useful in selection
|
|
and repetition structures is the
|
|
.CW else
|
|
statement.
|
|
An
|
|
.CW else
|
|
statement becomes executable only if no other statement
|
|
within the same process, at the same control-flow point,
|
|
is executable.
|
|
We could try to use it in two places in the above example:
|
|
.P1
|
|
active proctype counter()
|
|
{
|
|
do
|
|
:: (count != 0) ->
|
|
if
|
|
:: count++
|
|
:: count--
|
|
:: else
|
|
fi
|
|
:: else -> break
|
|
od
|
|
}
|
|
.P2
|
|
The first
|
|
.CW else ,
|
|
inside the nested selection structure, can never become
|
|
executable though, and is therefore redundant (both alternative
|
|
guards of the selection are assignments, which are always
|
|
executable).
|
|
The second usage of the
|
|
.CW else ,
|
|
however, becomes executable exactly when
|
|
.CW "!(count != 0)"
|
|
or
|
|
.CW "(count == 0)" ,
|
|
and is therefore equivalent to the latter to break from the loop.
|
|
.LP
|
|
There is also an alternative way to exit the do-loop, without
|
|
using a
|
|
.CW break
|
|
statement: the infamous
|
|
.CW goto .
|
|
This is illustrated in the following implementation of
|
|
Euclid's algorithm for finding the greatest common divisor
|
|
of two non-zero, positive numbers:
|
|
.P1
|
|
proctype Euclid(int x, y)
|
|
{
|
|
do
|
|
:: (x > y) -> x = x - y
|
|
:: (x < y) -> y = y - x
|
|
:: (x == y) -> goto done
|
|
od;
|
|
done:
|
|
skip
|
|
}
|
|
.P2
|
|
.P1
|
|
init { run Euclid(36, 12) }
|
|
.P2
|
|
The
|
|
.CW goto
|
|
in this example jumps to a label named
|
|
.CW done .
|
|
Since a label can only appear before a statement,
|
|
we have added the dummy statement
|
|
.CW skip .
|
|
Like a
|
|
.CW skip ,
|
|
a
|
|
.CW goto
|
|
statement is always executable and has no other
|
|
effect than to change the control-flow point
|
|
of the process that executes it.
|
|
.LP
|
|
As a final example, consider the following implementation of
|
|
a Dijkstra semaphore, which is implemented with the help of
|
|
a synchronous channel.
|
|
.P1
|
|
#define p 0
|
|
#define v 1
|
|
|
|
chan sema = [0] of { bit };
|
|
.P2
|
|
.P1
|
|
active proctype Dijkstra()
|
|
{ byte count = 1;
|
|
|
|
do
|
|
:: (count == 1) ->
|
|
sema!p; count = 0
|
|
:: (count == 0) ->
|
|
sema?v; count = 1
|
|
od
|
|
}
|
|
.P2
|
|
.P1
|
|
active [3] proctype user()
|
|
{ do
|
|
:: sema?p;
|
|
/* critical section */
|
|
sema!v;
|
|
/* non-critical section */
|
|
od
|
|
}
|
|
.P2
|
|
The semaphore guarantees that only one of the three user processes
|
|
can enter its critical section at a time.
|
|
It does not necessarily prevent the monopolization of
|
|
the access to the critical section by one of the processes.
|
|
.LP
|
|
\*P does not have a mechanism for defining functions or
|
|
procedures. Where necessary, though, these may be
|
|
modeled with the help of additional processes.
|
|
The return value of a function, for instance, can be passed
|
|
back to the calling process via global variables or messages.
|
|
The following program illustrates this by recursively
|
|
calculating the factorial of a number
|
|
.CW n .
|
|
.P1
|
|
proctype fact(int n; chan p)
|
|
{ chan child = [1] of { int };
|
|
int result;
|
|
|
|
if
|
|
:: (n <= 1) -> p!1
|
|
:: (n >= 2) ->
|
|
run fact(n-1, child);
|
|
child?result;
|
|
p!n*result
|
|
fi
|
|
}
|
|
.P2
|
|
.P1
|
|
init
|
|
{ chan child = [1] of { int };
|
|
int result;
|
|
|
|
run fact(7, child);
|
|
child?result;
|
|
printf("result: %d\en", result)
|
|
}
|
|
.P2
|
|
Each process creates a private channel and uses it
|
|
to communicate with its direct descendant.
|
|
There are no input statements in \*P.
|
|
The reason is that models must always be complete to
|
|
allow for logical verifications, and input statements
|
|
would leave at least the source of some information unspecified.
|
|
A way to read input
|
|
would presuppose a source of information that is not
|
|
part of the model.
|
|
.LP
|
|
We have already discussed a few special types of statement:
|
|
.CW skip ,
|
|
.CW break ,
|
|
and
|
|
.CW else .
|
|
Another statement in this class is the
|
|
.CW timeout .
|
|
The
|
|
.CW timeout
|
|
is comparable to a system level
|
|
.CW else
|
|
statement: it becomes executable if and only if no other
|
|
statement in any of the processes is executable.
|
|
.CW Timeout
|
|
is a modeling feature that provides for an escape from a
|
|
potential deadlock state.
|
|
The
|
|
.CW timeout
|
|
takes no parameters, because the types of properties we
|
|
would like to prove for \*P models must be proven independent
|
|
of all absolute and relative timing considerations.
|
|
In particular, the relative speeds of processes can never be
|
|
known with certainty in an asynchronous system.
|
|
.NH 3
|
|
Escape Sequences
|
|
.LP
|
|
The last type of compound structure to be discussed is the
|
|
.CW unless
|
|
statement.
|
|
It is used as follows:
|
|
.MT _sec5unless
|
|
.P1
|
|
{ P } unless { E }
|
|
.P2
|
|
where the letters
|
|
.CW P
|
|
and
|
|
.CW E
|
|
represent arbitrary \*P fragments.
|
|
Execution of the
|
|
.CW unless
|
|
statement begins with the execution of statements from
|
|
.CW P .
|
|
Before each statement execution in
|
|
.CW P
|
|
the executability of the first statement of
|
|
.CW E
|
|
is checked, using the normal \*P semantics of executability.
|
|
Execution of statements from
|
|
.CW P
|
|
proceeds only while the first statement of
|
|
.CW E
|
|
remains unexecutable.
|
|
The first time that this `guard of the escape sequence'
|
|
is found to be executable, control changes to it,
|
|
and execution continues as defined for
|
|
.CW E .
|
|
Individual statement executions remain indivisible,
|
|
so control can only change from inside
|
|
.CW P
|
|
to the start of
|
|
.CW E
|
|
in between individual statement executions.
|
|
If the guard of the escape sequence
|
|
does not become executable during the
|
|
execution of
|
|
.CW P ,
|
|
then it is skipped entirely when
|
|
.CW P
|
|
terminates.
|
|
.LP
|
|
An example of the use of escape sequences is:
|
|
.P1
|
|
A;
|
|
do
|
|
:: b1 -> B1
|
|
:: b2 -> B2
|
|
\&...
|
|
od
|
|
unless { c -> C };
|
|
D
|
|
.P2
|
|
As shown in the example, the curly braces around the main sequence
|
|
(or the escape sequence) can be deleted if there can be no confusion
|
|
about which statements belong to those sequences.
|
|
In the example, condition
|
|
.CW c
|
|
acts as a watchdog on the repetition construct from the main sequence.
|
|
Note that this is not necessarily equivalent to the construct
|
|
.P1
|
|
A;
|
|
do
|
|
:: b1 -> B1
|
|
:: b2 -> B2
|
|
\&...
|
|
:: c -> break
|
|
od;
|
|
C; D
|
|
.P2
|
|
if
|
|
.CW B1
|
|
or
|
|
.CW B2
|
|
are non-empty.
|
|
In the first version of the example, execution of the iteration can
|
|
be interrupted at \f2any\f1 point inside each option sequence.
|
|
In the second version, execution can only be interrupted at the
|
|
start of the option sequences.
|
|
.NH 2
|
|
Correctness Properties
|
|
.LP
|
|
There are three ways to express correctness properties in \*P,
|
|
using:
|
|
.IP
|
|
.RS
|
|
.br
|
|
\(bu
|
|
Assertions (section 1.3.1),
|
|
.br
|
|
\(bu
|
|
Special labels (section 1.3.2),
|
|
.br
|
|
\(bu
|
|
.CW Never
|
|
claims (section 1.3.3).
|
|
.RE
|
|
.LP
|
|
.NH 3
|
|
Assertions
|
|
.LP
|
|
Statements of the form
|
|
.P1
|
|
assert(expression)
|
|
.P2
|
|
are always executable.
|
|
If the expression evaluates to a non-zero value (i.e., the
|
|
corresponding condition holds), the statement has no effect
|
|
when executed.
|
|
The correctness property expressed, though, is that it is
|
|
impossible for the expression to evaluate to zero (i.e., for
|
|
the condition to be false).
|
|
A failing assertion will cause execution to be aborted.
|
|
.NH 3
|
|
Special Labels
|
|
.LP
|
|
Labels in a \*P specification ordinarily serve as
|
|
targets for unconditional
|
|
.CW goto
|
|
jumps, as usual.
|
|
There are, however, also three types of labels that
|
|
have a special meaning to the verifier.
|
|
We discuss them in the next three subsections.
|
|
.NH 4
|
|
End-State Labels
|
|
.LP
|
|
When a \*P model is checked for reachable deadlock states
|
|
by the verifier, it must be able to distinguish valid \f2end state\f1s
|
|
from invalid ones.
|
|
By default, the only valid end states are those in which
|
|
every \*P process that was instantiated has reached the end of
|
|
its code.
|
|
Not all \*P processes, however, are meant to reach the
|
|
end of their code.
|
|
Some may very well linger in a known wait
|
|
state, or they may sit patiently in a loop
|
|
ready to spring into action when new input arrives.
|
|
.LP
|
|
To make it clear to the verifier that these alternate end states
|
|
are also valid, we can define special end-state labels.
|
|
We can do so, for instance, in the process type
|
|
.CW Dijkstra ,
|
|
from an earlier example:
|
|
.P1
|
|
proctype Dijkstra()
|
|
{ byte count = 1;
|
|
|
|
end: do
|
|
:: (count == 1) ->
|
|
sema!p; count = 0
|
|
:: (count == 0) ->
|
|
sema?v; count = 1
|
|
od
|
|
}
|
|
.P2
|
|
The label
|
|
.CW end
|
|
defines that it is not an error if, at the end of an
|
|
execution sequence, a process of this type
|
|
has not reached its closing curly brace, but waits at the label.
|
|
Of course, such a state could still be part of a deadlock state, but
|
|
if so, it is not caused by this particular process.
|
|
.LP
|
|
There may be more than one end-state label per \*P model.
|
|
If so, all labels that occur within the same process body must
|
|
be unique.
|
|
The rule is that every label name with the prefix
|
|
.CW end
|
|
is taken to be an end-state label.
|
|
.NH 4
|
|
Progress-State Labels
|
|
.LP
|
|
In the same spirit, \*P also allows for the definition of
|
|
.CW progress
|
|
labels.
|
|
Passing a progress label during an execution is interpreted
|
|
as a good thing: the process is not just idling while
|
|
waiting for things to happen elsewhere, but is making
|
|
effective progress in its execution.
|
|
The implicit correctness property expressed here is that any
|
|
infinite execution cycle allowed by the model that does not
|
|
pass through at least one of these progress labels is a
|
|
potential starvation loop.
|
|
In the
|
|
.CW Dijkstra
|
|
example, for instance, we can label the
|
|
successful passing of a semaphore test as progress and
|
|
ask a verifier to make sure that there is no cycle elsewhere
|
|
in the system.
|
|
.P1
|
|
proctype Dijkstra()
|
|
{ byte count = 1;
|
|
|
|
end: do
|
|
:: (count == 1) ->
|
|
progress: sema!p; count = 0
|
|
:: (count == 0) ->
|
|
sema?v; count = 1
|
|
od
|
|
}
|
|
.P2
|
|
If more than one state carries a progress label,
|
|
variations with a common prefix are again valid.
|
|
.NH 4
|
|
Accept-State Labels
|
|
.LP
|
|
The last type of label, the accept-state label, is used
|
|
primarily in combination with
|
|
.CW never
|
|
claims.
|
|
Briefly, by labeling a state with any label starting
|
|
with the prefix
|
|
.CW accept
|
|
we can ask the verifier to find all cycles that \f2do\f1
|
|
pass through at least one of those labels.
|
|
The implicit correctness claim is that this cannot happen.
|
|
The primary place where accept labels are used is inside
|
|
.CW never
|
|
claims.
|
|
We discuss
|
|
.CW never
|
|
claims next.
|
|
.NH 3
|
|
Never Claims
|
|
.LP
|
|
Up to this point we have talked about the specification
|
|
of correctness criteria with assertions
|
|
and with three special types of labels.
|
|
Powerful types of correctness criteria can already
|
|
be expressed with these tools, yet so far our only option is
|
|
to add them to individual
|
|
.CW proctype
|
|
declarations.
|
|
We can, for instance, express the claim ``every system state
|
|
in which property
|
|
.CW P
|
|
is true eventually leads to a system state in which property
|
|
.CW Q
|
|
is true,'' with an extra monitor process, such as:
|
|
.P1
|
|
active proctype monitor()
|
|
{
|
|
progress:
|
|
do
|
|
:: P -> Q
|
|
od
|
|
}
|
|
.P2
|
|
If we require that property
|
|
.CW P
|
|
must \f2remain\f1 true while we are waiting
|
|
.CW Q
|
|
to become true, we can try to change this to:
|
|
.P1
|
|
active proctype monitor()
|
|
{
|
|
progress:
|
|
do
|
|
:: P -> assert(P || Q)
|
|
od
|
|
}
|
|
.P2
|
|
but this does not quite do the job.
|
|
Note that we cannot make any assumptions about the
|
|
relative execution speeds of processes in a \*P model.
|
|
This means that if in the remainder of the system the
|
|
property
|
|
.CW P
|
|
becomes true, we can move to the state just before the
|
|
.CW assert ,
|
|
and wait there for an unknown amount of time (anything between
|
|
a zero delay and an infinite delay is possible here, since
|
|
no other synchronizations apply).
|
|
If
|
|
.CW Q
|
|
becomes true, we may pass the assertion, but we need not
|
|
do so.
|
|
Even if
|
|
.CW P
|
|
becomes false only \f2after\f1
|
|
.CW Q
|
|
has become true, we may still fail the assertion,
|
|
as long as there exists some later state where neither
|
|
.CW P
|
|
nor
|
|
.CW Q
|
|
is true.
|
|
This is clearly unsatisfactory, and we need another mechanism
|
|
to express these important types of liveness properties.
|
|
.SH
|
|
The Connection with Temporal Logic
|
|
.LP
|
|
A general way to express system properties of the type we
|
|
have just discussed is to use linear time temporal logic (LTL)
|
|
formulae.
|
|
Every \*P expression is automatically also a valid LTL formula.
|
|
An LTL formula can also contain the unary temporal operators □
|
|
(pronounced always), ◊ (pronounced eventually), and
|
|
two binary temporal operators
|
|
.CW U
|
|
(pronounced weak until) and
|
|
.BI U
|
|
(pronounced strong until).
|
|
.LP
|
|
Where the value of a \*P expression without temporal operators can be
|
|
defined uniquely for individual system states, without further context,
|
|
the truth value of an LTL formula is defined for sequences of states:
|
|
specifically, it is defined for the first state of a given infinite
|
|
sequence of system states (a trace).
|
|
Given, for instance, the sequence of system states:
|
|
.P1
|
|
s0;s1;s2;...
|
|
.P2
|
|
the LTL formula
|
|
.CW pUq ,
|
|
with
|
|
.CW p
|
|
and
|
|
.CW q
|
|
standard \*P expressions, is true for
|
|
.CW s0
|
|
either if
|
|
.CW q
|
|
is true in
|
|
.CW s0 ,
|
|
or if
|
|
.CW p
|
|
is true in
|
|
.CW s0
|
|
and
|
|
.CW pUq
|
|
holds for the remainder of the sequence after
|
|
.CW s0 .
|
|
.LP
|
|
Informally,
|
|
.CW pUq
|
|
says that
|
|
.CW p
|
|
is required to hold at least until
|
|
.CW q
|
|
becomes true.
|
|
If, instead, we would write \f(CWp\f(BIU\f(CWq\f1,
|
|
then we also require that there exists at least
|
|
one state in the sequence where
|
|
.CW q
|
|
does indeed become true.
|
|
.LP
|
|
The temporal operators □ and ◊
|
|
can be defined in terms of the strong until operator
|
|
.BI U ,
|
|
as follows.
|
|
.P1
|
|
□ p = !◊ !p = !(true \f(BIU\f(CW !p)
|
|
.P2
|
|
Informally, □
|
|
.CW p
|
|
says that property
|
|
.CW p
|
|
must hold in all states of a trace, and ◊
|
|
.CW p
|
|
says that
|
|
.CW p
|
|
holds in at least one state of the trace.
|
|
.LP
|
|
To express our original example requirement: ``every system state
|
|
in which property
|
|
.CW P
|
|
is true eventually leads to a system state in which property
|
|
.CW Q
|
|
is true,''
|
|
we can write the LTL formula:
|
|
.P1
|
|
□ (P -> ◊ Q)
|
|
.P2
|
|
where the logical implication symbol
|
|
.CW ->
|
|
is defined in the usual way as
|
|
.P1
|
|
P => Q means !P || Q
|
|
.P2
|
|
.SH
|
|
Mapping LTL Formulae onto Never Claims
|
|
.LP
|
|
\*P does not include syntax for specifying LTL formulae
|
|
directly, but it relies on the fact that every such
|
|
formula can be translated into a special type of
|
|
automaton, known as a Büchi automaton.
|
|
In the syntax of \*P this automaton is called a
|
|
.CW never
|
|
claim.
|
|
If you don't care too much about the details of
|
|
.CW never
|
|
claims, you can skip the remainder of this section and
|
|
simple remember that \*V can convert any LTL formula
|
|
automatically into the proper never claim syntax with
|
|
the command:
|
|
.P1
|
|
spin -f "...formula..."
|
|
.P2
|
|
Here are the details.
|
|
The syntax of a never claim is:
|
|
.P1
|
|
never {
|
|
\&...
|
|
}
|
|
.P2
|
|
where the dots can contain any \*P fragment, including
|
|
arbitrary repetition, selection, unless constructs,
|
|
jumps, etc.
|
|
.LP
|
|
There is an important difference in semantics between a
|
|
.CW proctype
|
|
declaration and a
|
|
.CW never
|
|
claim.
|
|
Every statement inside a
|
|
.CW never
|
|
claim is interpreted as a proposition, i.e., a condition.
|
|
A
|
|
.CW never
|
|
claim should therefore only contain expressions and never
|
|
statements that can have side-effects (assignments, sends or
|
|
receives, run-statements, etc.)
|
|
.LP
|
|
.CW Never
|
|
claims are used to express behaviors that are considered
|
|
undesirable or illegal.
|
|
We say that a
|
|
.CW never
|
|
claim is `matched' if the undesirable behavior can be realized,
|
|
contrary to the claim, and thus the correctness requirement violated.
|
|
The claims are evaluated over system executions, that is, the
|
|
propositions that are listed in the claim are evaluated over the
|
|
traces from the remainder of the system.
|
|
The claim, therefore, should not alter that behavior: it merely
|
|
monitors it.
|
|
Every time that the system reaches a new state, by asynchronously
|
|
executing statements from the model, the claim will evaluate the
|
|
appropriate propositions to determine if a counter-example can
|
|
be constructed to the implicit LTL formula that is specified.
|
|
.LP
|
|
Since LTL formulae are only defined for infinite executions,
|
|
the behavior of a
|
|
.CW never
|
|
claim can only be matched by an infinite system execution.
|
|
This by itself would restrict us to the use of progress labels
|
|
and accept labels as the only means we have discussed so far
|
|
for expressing properties of infinite behaviors.
|
|
To conform to standard omega automata theory, the behaviors of
|
|
.CW never
|
|
claims are expressed exclusively with
|
|
.CW accept
|
|
labels (never with
|
|
.CW progress
|
|
labels).
|
|
To match a claim, therefore, an infinite sequence of true propositions
|
|
must exist, at least one of which is labeled with an
|
|
.CW accept
|
|
label (inside the never claim).
|
|
.LP
|
|
Since \*P models can also express terminating system behaviors,
|
|
we have to define the semantics of the
|
|
.CW never
|
|
claims also for those behaviors.
|
|
To facilitate this, it is defined that a
|
|
.CW never
|
|
claim can also be matched when it reaches its closing curly brace
|
|
(i.e., when it appears to terminate).
|
|
This semantics is based on what is usually referred to as a `stuttering
|
|
semantics.'
|
|
With stuttering semantics, any terminating execution can be extended
|
|
into an equivalent infinite execution (for the purposes of evaluating
|
|
LTL properties) by repeating (stuttering) the final state infinitely often.
|
|
As a syntactical convenience, the final state of a
|
|
.CW never
|
|
claim is defined to be accepting, i.e., it could be replaced with
|
|
the explicit repetition construct:
|
|
.P1
|
|
accept: do :: skip od
|
|
.P2
|
|
Every process behavior, similarly, is (for the purposes of evaluating the
|
|
.CW never
|
|
claims) thought to be extended with a dummy self-loop on all final states:
|
|
.P1
|
|
do :: skip od
|
|
.P2
|
|
(Note the
|
|
.CW accept
|
|
labels only occur in the
|
|
.CW never
|
|
claim, not in the system.)
|
|
.SH
|
|
The Semantics of a Never Claim
|
|
.LP
|
|
.CW Never
|
|
claims are probably the hardest part of the language to understand,
|
|
so it is worth spending a few extra words on them.
|
|
On an initial reading, feel free to skip the remainder of this
|
|
section.
|
|
.LP
|
|
The difference between a
|
|
.CW never
|
|
claim and the remainder of a \*P system can be explained
|
|
as follows.
|
|
A \*P model defines an asynchronous interleaving product of the
|
|
behaviors of individual processes.
|
|
Given an arbitrary system state, its successor states are
|
|
conceptually obtained in two steps.
|
|
In a first step, all the executable statements in the
|
|
individual processes are identified.
|
|
In a second step, each one of these statements is executed,
|
|
each one producing one potential successor for the current state.
|
|
The complete system behavior is thus defined recursively and
|
|
represents all possible interleavings of the individual process behaviors.
|
|
It is this asynchronous product machine that we call the `global
|
|
system behavior'.
|
|
.LP
|
|
The addition of a
|
|
.CW never
|
|
claim defines a \f2synchronous\f1 product of the global system behavior
|
|
with the behavior expressed in the claim.
|
|
This synchronous product can be thought of as the construction of a
|
|
new global state machine, in which every state is defined as a pair
|
|
.CW (s,n)
|
|
with
|
|
.CW s
|
|
a state from the global system (the asynchronous product of processes), and
|
|
.CW n
|
|
a state from the claim.
|
|
Every transition in the new global machine is similarly defined by a pair
|
|
of transitions, with the first element a statement from the system, and the
|
|
second a proposition from the claim.
|
|
In other words, every transition in this final synchronous product is
|
|
defined as a joint transition of the system and the claim.
|
|
Of course, that transition can only occur if the proposition from the
|
|
second half of the transition pair evaluates to true in the current state
|
|
of the system (the first half of the state pair).
|
|
.SH
|
|
Examples
|
|
.LP
|
|
To manually translate an LTL formula into a
|
|
.CW never
|
|
claim (e.g. foregoing the builtin translation that \*V
|
|
offers), we must carefully consider whether the
|
|
formula expresses a positive or a negative property.
|
|
A positive property expresses a good behavior that we
|
|
would like our system to have.
|
|
A negative property expresses a bad behavior that we
|
|
claim the system does not have.
|
|
A
|
|
.CW never
|
|
claim can express only negative claims, not positive ones.
|
|
Fortunately, the two are exchangeable: if we want to express
|
|
that a good behavior is unavoidable, we can formalize all
|
|
ways in which the good behavior could be violated, and express
|
|
that in the
|
|
.CW never
|
|
claim.
|
|
.LP
|
|
Suppose that the LTL formula ◊□
|
|
.CW p ,
|
|
with
|
|
.CW p
|
|
a \*P expression, expresses a negative claim
|
|
(i.e., it is considered a correctness violation if
|
|
there exists any execution sequence in which
|
|
.CW p
|
|
can eventually remain true infinitely long).
|
|
This can be written in a
|
|
.CW never
|
|
claim as:
|
|
.P1
|
|
never { /* <>[]p */
|
|
do
|
|
:: skip /* after an arbitrarily long prefix */
|
|
:: p -> break /* p becomes true */
|
|
od;
|
|
accept: do
|
|
:: p /* and remains true forever after */
|
|
od
|
|
}
|
|
.P2
|
|
Note that in this case the claim does not terminate, and
|
|
also does not necessarily match all system behaviors.
|
|
It is sufficient if it precisely captures all violations
|
|
of our correctness requirement, and no more.
|
|
.LP
|
|
If the LTL formula expressed a positive property, we first
|
|
have to invert it to the corresponding negative property
|
|
.CW ◊!p
|
|
and translate that into a
|
|
.CW never
|
|
claim.
|
|
The requirement now says that it is a violation if
|
|
.CW p
|
|
does not hold infinitely long.
|
|
.P1
|
|
never { /* <>!p*/
|
|
do
|
|
:: skip
|
|
:: !p -> break
|
|
od
|
|
}
|
|
.P2
|
|
We have used the implicit match of a claim upon reaching the
|
|
closing terminating brace.
|
|
Since the first violation of the property suffices to disprove
|
|
it, we could also have written:
|
|
.P1
|
|
never { /* <>!p*/
|
|
do
|
|
:: p
|
|
:: !p -> break
|
|
od
|
|
}
|
|
.P2
|
|
or, if we abandon the connection with LTL for a moment,
|
|
even more tersely as:
|
|
.P1
|
|
never { do :: assert(p) od }
|
|
.P2
|
|
Suppose we wish to express that it is a violation of our
|
|
correctness requirements if there exists any execution in
|
|
the system where
|
|
.CW "□ (p -> ◊ q)"
|
|
is violated (i.e., the negation of this formula is satisfied).
|
|
The following
|
|
.CW never
|
|
claim expresses that property:
|
|
.P1
|
|
never {
|
|
do
|
|
:: skip
|
|
:: p && !q -> break
|
|
od;
|
|
accept:
|
|
do
|
|
:: !q
|
|
od
|
|
}
|
|
.P2
|
|
Note that using
|
|
.CW "(!p || q)"
|
|
instead of
|
|
.CW skip
|
|
in the first repetition construct would imply a check for just
|
|
the first occurrence of proposition
|
|
.CW p
|
|
becoming true in the execution sequence, while
|
|
.CW q
|
|
is false.
|
|
The above formalization checks for all occurrences, anywhere in a trace.
|
|
.LP
|
|
Finally, consider a formalization of the LTL property
|
|
.CW "□ (p -> (q U r))" .
|
|
The corresponding claim is:
|
|
.P1
|
|
never {
|
|
do
|
|
:: skip /* to match any occurrence */
|
|
:: p && q && !r -> break
|
|
:: p && !q && !r -> goto error
|
|
od;
|
|
do
|
|
:: q && !r
|
|
:: !q && !r -> break
|
|
od;
|
|
error: skip
|
|
}
|
|
.P2
|
|
Note again the use of
|
|
.CW skip
|
|
instead of
|
|
.CW "(!p || r)"
|
|
to avoid matching just the first occurrence of
|
|
.CW "(p && !r)"
|
|
in a trace.
|
|
.NH 2
|
|
Predefined Variables and Functions
|
|
.LP
|
|
The following predefined variables and functions
|
|
can be especially useful in
|
|
.CW never
|
|
claims.
|
|
.LP
|
|
The predefined variables are:
|
|
.CW _pid
|
|
and
|
|
.CW _last .
|
|
.LP
|
|
.CW _pid
|
|
is a predefined local variable in each process
|
|
that holds the unique instantiation number for
|
|
that process.
|
|
It is always a non-negative number.
|
|
.LP
|
|
.CW _last
|
|
is a predefined global variable that always holds the
|
|
instantiation number of the process that performed the last
|
|
step in the current execution sequence.
|
|
Its value is not part of the system state unless it is
|
|
explicitly used in a specification.
|
|
.P1
|
|
never {
|
|
/* it is not possible for the process with pid=1
|
|
* to execute precisely every other step forever
|
|
*/
|
|
accept:
|
|
do
|
|
:: _last != 1 -> _last == 1
|
|
od
|
|
}
|
|
.P2
|
|
The initial value of
|
|
.CW _last
|
|
is zero.
|
|
.LP
|
|
Three predefined functions are specifically intended to be used in
|
|
.CW never
|
|
claims, and may not be used elsewhere in a model:
|
|
.CW pc_value(pid) ,
|
|
.CW enabled(pid) ,
|
|
.CW procname[pid]@label .
|
|
.LP
|
|
The function
|
|
.CW pc_value(pid)
|
|
returns the current control state
|
|
of the process with instantiation number
|
|
.CW pid ,
|
|
or zero if no such process exists.
|
|
.LP
|
|
Example:
|
|
.P1
|
|
never {
|
|
/* Whimsical use: claim that it is impossible
|
|
* for process 1 to remain in the same control
|
|
* state as process 2, or one with smaller value.
|
|
*/
|
|
accept: do
|
|
:: pc_value(1) <= pc_value(2)
|
|
od
|
|
}
|
|
.P2
|
|
The function
|
|
.CW enabled(pid)
|
|
tells whether the process with instantiation number
|
|
.CW pid
|
|
has an executable statement that it can execute next.
|
|
.LP
|
|
Example:
|
|
.P1
|
|
never {
|
|
/* it is not possible for the process with pid=1
|
|
* to remain enabled without ever executing
|
|
*/
|
|
accept:
|
|
do
|
|
:: _last != 1 && enabled(1)
|
|
od
|
|
}
|
|
.P2
|
|
The last function
|
|
.CW procname[pid]@label
|
|
tells whether the process with instantiation number
|
|
.CW pid
|
|
is currently in the state labeled with
|
|
.CW label
|
|
in
|
|
.CW "proctype procname" .
|
|
It is an error if the process referred to is not an instantiation
|
|
of that proctype.
|
|
.NH 1
|
|
Verifications with \*V
|
|
.LP
|
|
The easiest way to use \*V is probably on a Windows terminal
|
|
with the Tcl/Tk implementation of \s-1XSPIN\s0.
|
|
All functionality of \*V, however, is accessible from
|
|
any plain ASCII terminal, and there is something to be
|
|
said for directly interacting with the tool itself.
|
|
.LP
|
|
The description in this paper gives a short walk-through of
|
|
a common mode of operation in using the verifier.
|
|
A more tutorial style description of the verification
|
|
process can be found in [Ho93].
|
|
More detail on the verification of large systems with the
|
|
help of \*V's supertrace (bitstate) verification algorithm
|
|
can be found in [Ho95].
|
|
.IP
|
|
.RS
|
|
.br
|
|
\(bu
|
|
Random and interactive simulations (section 2.1),
|
|
.br
|
|
\(bu
|
|
Generating a verifier (section 2.2),
|
|
.br
|
|
\(bu
|
|
Compilation for different types of searches (section 2.3),
|
|
.br
|
|
\(bu
|
|
Performing the verification (section 2.4),
|
|
.br
|
|
\(bu
|
|
Inspecting error traces produced by the verifier (section 2.5),
|
|
.br
|
|
\(bu
|
|
Exploiting partial order reductions (section 2.6).
|
|
.RE
|
|
.LP
|
|
.NH 2
|
|
Random and Interactive Simulations
|
|
.LP
|
|
Given a model in \*P, say stored in a file called
|
|
.CW spec ,
|
|
the easiest mode of operation is to perform a random simulation.
|
|
For instance,
|
|
.P1
|
|
spin -p spec
|
|
.P2
|
|
tells \*V to perform a random simulation, while printing the
|
|
process moves selected for execution at each step (by default
|
|
nothing is printed, other than explicit
|
|
.CW printf
|
|
statements that appear in the model itself).
|
|
A range of options exists to make the traces more verbose,
|
|
e.g., by adding printouts of local variables (add option
|
|
.CW -l ),
|
|
global variables (add option
|
|
.CW -g ),
|
|
send statements (add option
|
|
.CW -s ),
|
|
or receive statements (add option
|
|
.CW -r ).
|
|
Use option
|
|
.CW -n N
|
|
(with N any number) to fix the seed on \*V's internal
|
|
random number generator, and thus make the simulation runs
|
|
reproducible.
|
|
By default the current time is used to seed the random number
|
|
generator.
|
|
For instance:
|
|
.P1
|
|
spin -p -l -g -r -s -n1 spec
|
|
.P2
|
|
.LP
|
|
If you don't like the system randomly resolving non-deterministic
|
|
choices for you, you can select an interactive simulation:
|
|
.P1
|
|
spin -i -p spec
|
|
.P2
|
|
In this case you will be offered a menu with choices each time
|
|
the execution could proceed in more than one way.
|
|
.LP
|
|
Simulations, of course, are intended primarily for the
|
|
debugging of a model. They cannot prove anything about it.
|
|
Assertions will be evaluated during simulation runs, and
|
|
any violations that result will be reported, but none of
|
|
the other correctness requirements can be checked in this way.
|
|
.NH 2
|
|
Generating the Verifier
|
|
.LP
|
|
A model-specific verifier is generated as follows:
|
|
.P1
|
|
spin -a spec
|
|
.P2
|
|
This generates a C program in a number of files (with names
|
|
starting with
|
|
.CW pan ).
|
|
.NH 2
|
|
Compiling the Verifier
|
|
.LP
|
|
At this point it is good to know the physical limitations of
|
|
the computer system that you will run the verification on.
|
|
If you know how much physical (not virtual) memory your system
|
|
has, you can take advantage of that.
|
|
Initially, you can simply compile the verifier for a straight
|
|
exhaustive verification run (constituting the strongest type
|
|
of proof if it can be completed).
|
|
Compile as follows.
|
|
.P1
|
|
\*C -o pan pan.c # standard exhaustive search
|
|
.P2
|
|
If you know a memory bound that you want to restrict the run to
|
|
(e.g., to avoid paging), find the nearest power of 2 (e.g., 23
|
|
for the bound $2 sup 23# bytes) and compile as follows.
|
|
.P1
|
|
\*C '-DMEMCNT=23' -o pan pan.c
|
|
.P2
|
|
or equivalently in terms of MegaBytes:
|
|
.P1
|
|
\*C '-DMEMLIM=8' -o pan pan.c
|
|
.P2
|
|
If the verifier runs out of memory before completing its task,
|
|
you can decide to increase the bound or to switch to a frugal
|
|
supertrace verification. In the latter case, compile as follows.
|
|
.P1
|
|
\*C -DBITSTATE -o pan pan.c
|
|
.P2
|
|
.NH 2
|
|
Performing the Verification
|
|
.LP
|
|
There are three specific decisions to make to
|
|
perform verifications optimally: estimating the
|
|
size of the reachable state space (section 2.4.1),
|
|
estimating the maximum length of a unique execution
|
|
sequence (2.4.2), and selecting the type of correctness
|
|
property (2.4.3).
|
|
No great harm is done if the estimates from the first two
|
|
steps are off. The feedback from the verifier usually provides
|
|
enough clues to determine quickly what the optimal settings
|
|
for peak performance should be.
|
|
.NH 3
|
|
Reachable States
|
|
.LP
|
|
For a standard exhaustive run, you can override the default choice
|
|
for the size for the hash table ($2 sup 18# slots) with option
|
|
.CW -w .
|
|
For instance,
|
|
.P1
|
|
pan -w23
|
|
.P2
|
|
selects $2 sup 23# slots.
|
|
The hash table size should optimally be roughly equal to the number of
|
|
reachable states you expect (within say a factor of two or three).
|
|
Too large a number merely wastes memory, too low a number wastes
|
|
CPU time, but neither can affect the correctness of the result.
|
|
.sp
|
|
For a supertrace run, the hash table \f2is\f1 the memory arena, and
|
|
you can override the default of $2 sup 22# bits with any other number.
|
|
Set it to the maximum size of physical memory you can grab without
|
|
making the system page, again within a factor of say two or three.
|
|
Use, for instance
|
|
.CW -w23
|
|
if you expect 8 million reachable states and have access to at least
|
|
8 million ($2 sup 23#) bits of memory (i.e., $2 sup 20# or 1 Megabyte of RAM).
|
|
.NH 3
|
|
Search Depth
|
|
.LP
|
|
By default the analyzers have a search depth restriction of 10,000 steps.
|
|
If this isn't enough, the search will truncate at 9,999 steps (watch for
|
|
it in the printout).
|
|
Define a different search depth with the -m flag.
|
|
.P1
|
|
pan -m100000
|
|
.P2
|
|
If you exceed also this limit, it is probably good to take some
|
|
time to consider if the model you have specified is indeed finite.
|
|
Check, for instance, if no unbounded number of processes is created.
|
|
If satisfied that the model is finite, increase the search depth at
|
|
least as far as is required to avoid truncation completely.
|
|
.LP
|
|
If you find a particularly nasty error that takes a large number of steps
|
|
to hit, you may also set lower search depths to find the shortest variant
|
|
of an error sequence.
|
|
.P1
|
|
pan -m40
|
|
.P2
|
|
Go up or down by powers of two until you find the place where the
|
|
error first appears or disappears and then home in on the first
|
|
depth where the error becomes apparent, and use the error trail of
|
|
that verification run for guided simulation.
|
|
.sp
|
|
Note that if a run with a given search depth fails to find
|
|
an error, this does not necessarily mean that no violation of a
|
|
correctness requirement is possible within that number of steps.
|
|
The verifier performs its search for errors by using a standard
|
|
depth-first graph search. If the search is truncated at N steps,
|
|
and a state at level N-1 happens to be reachable also within fewer
|
|
steps from the initial state, the second time it is reached it
|
|
will not be explored again, and thus neither will its successors.
|
|
Those successors may contain errors states that are reachable within
|
|
N steps from the initial state.
|
|
Normally, the verification should be run in such a way that no
|
|
execution paths can be truncated, but to force the complete exploration
|
|
of also truncated searches one can override the defaults with a compile-time
|
|
flag
|
|
.CW -DREACH .
|
|
When the verifier is compiled with that additional directive, the depth at
|
|
which each state is visited is remembered, and a state is now considered
|
|
unvisited if it is revisited via a shorter path later in the search.
|
|
(This option cannot be used with a supertrace search.)
|
|
.NH 3
|
|
Liveness or Safety Verification
|
|
.LP
|
|
For the last, and perhaps the most critical, runtime decision:
|
|
it must be decided if the system is to be checked for safety
|
|
violations or for liveness violations.
|
|
.P1
|
|
pan -l # search for non-progress cycles
|
|
pan -a # search for acceptance cycles
|
|
.P2
|
|
(In the first case, though, you must compile pan.c with -DNP as an
|
|
additional directive. If you forget, the executable will remind you.)
|
|
If you don't use either of the above two options, the default types of
|
|
correctness properties are checked (assertion violations,
|
|
completeness, race conditions, etc.).
|
|
Note that the use of a
|
|
.CW never
|
|
claim that contains
|
|
.CW accept
|
|
labels requires the use of the
|
|
.CW -a
|
|
flag for complete verification.
|
|
.LP
|
|
Adding option
|
|
.CW -f
|
|
restricts the search for liveness properties further under
|
|
a standard \f2weak fairness\f1 constraint:
|
|
.P1
|
|
pan -f -l # search for weakly fair non-progress cycles
|
|
pan -f -a # search for weakly fair acceptance cycles
|
|
.P2
|
|
With this constraint, each process is required to appear
|
|
infinitely often in the infinite trace that constitutes
|
|
the violation of a liveness property (e.g., a non-progress cycle
|
|
or an acceptance cycle), unless it is permanently blocked
|
|
(i.e., has no executable statements after a certain point in
|
|
the trace is reached).
|
|
Adding the fairness constraint increases the time complexity
|
|
of the verification by a factor that is linear in the number
|
|
of active processes.
|
|
.LP
|
|
By default, the verifier will report on unreachable code in
|
|
the model only when a verification run is successfully
|
|
completed.
|
|
This default behavior can be turned off with the runtime option
|
|
.CW -n ,
|
|
as in:
|
|
.P1
|
|
pan -n -f -a
|
|
.P2
|
|
(The order in which the options such as these are listed is
|
|
always irrelevant.)
|
|
A brief explanation of these and other runtime options can
|
|
be determined by typing:
|
|
.P1
|
|
pan --
|
|
.P2
|
|
.NH 2
|
|
Inspecting Error Traces
|
|
.LP
|
|
If the verification run reports an error,
|
|
any error, \*V dumps an error trail into a file named
|
|
.CW spec.trail ,
|
|
where
|
|
.CW spec
|
|
is the name of your original \*P file.
|
|
To inspect the trail, and determine the cause of the error,
|
|
you must use the guided simulation option.
|
|
For instance:
|
|
.P1
|
|
spin -t -c spec
|
|
.P2
|
|
gives you a summary of message exchanges in the trail, or
|
|
.P1
|
|
spin -t -p spec
|
|
.P2
|
|
gives a printout of every single step executed.
|
|
Add as many extra or different options as you need to pin down the error:
|
|
.P1
|
|
spin -t -r -s -l -g spec
|
|
.P2
|
|
Make sure the file
|
|
.CW spec
|
|
didn't change since you generated the analyzer from it.
|
|
.sp
|
|
If you find non-progress cycles, add or delete progress labels
|
|
and repeat the verification until you are content that you have found what
|
|
you were looking for.
|
|
.sp
|
|
If you are not interested in the first error reported,
|
|
use pan option
|
|
.CW -c
|
|
to report on specific others:
|
|
.P1
|
|
pan -c3
|
|
.P2
|
|
ignores the first two errors and reports on the third one that
|
|
is discovered.
|
|
If you just want to count all errors and not see them, use
|
|
.P1
|
|
pan -c0
|
|
.P2
|
|
.SH
|
|
State Assignments
|
|
.LP
|
|
Internally, the verifiers produced by \*V deal with a formalization of
|
|
a \*P model in terms of extended finite state machines.
|
|
\*V therefore assigns state numbers to all statements in the model.
|
|
The state numbers are listed in all the relevant output to make it
|
|
completely unambiguous (source line references unfortunately do not
|
|
have that property).
|
|
To confirm the precise state assignments, there is a runtime option
|
|
to the analyzer generated:
|
|
.P1
|
|
pan -d # print state machines
|
|
.P2
|
|
which will print out a table with all state assignments for each
|
|
.CW proctype
|
|
in the model.
|
|
.NH 2
|
|
Exploiting Partial Order Reductions
|
|
.LP
|
|
The search algorithm used by \*V is optimized
|
|
according to the rules of a partial order theory explained in [HoPe94].
|
|
The effect of the reduction, however, can be increased considerably if the verifier
|
|
has extra information about the access of processes to global
|
|
message channels.
|
|
For this purpose, there are two keywords in the language that
|
|
allow one to assert that specific channels are used exclusively
|
|
by specific processes.
|
|
For example, the assertions
|
|
.P1
|
|
xr q1;
|
|
xs q2;
|
|
.P2
|
|
claim that the process that executes them is the \f2only\f1 process
|
|
that will receive messages from channel
|
|
.CW q1 ,
|
|
and the \f2only\f1 process that will send messages to channel
|
|
.CW q2 .
|
|
.LP
|
|
If an exclusive usage assertion turns out to be invalid, the
|
|
verifier will be able to detect this, and report it as a violation
|
|
of an implicit correctness requirement.
|
|
.LP
|
|
Every read or write access to a message channel can introduce
|
|
new dependencies that may diminish the maximum effect of the
|
|
partial order reduction strategies.
|
|
If, for instance, a process uses the
|
|
.CW len
|
|
function to check the number of messages stored in a channel,
|
|
this counts as a read access, which can in some cases invalidate
|
|
an exclusive access pattern that might otherwise exist.
|
|
There are two special functions that can be used to poll the
|
|
size of a channel in a safe way that is compatible with the
|
|
reduction strategy.
|
|
.LP
|
|
The expression
|
|
.CW nfull(qname)
|
|
returns true if channel
|
|
.CW qname
|
|
is not full, and
|
|
.CW nempty(qname)
|
|
returns true if channel
|
|
.CW qname
|
|
contains at least one message.
|
|
Note that the parser will not recognize the free form expressions
|
|
.CW !full(qname)
|
|
and
|
|
.CW !empty(qname)
|
|
as equally safe, and it will forbid constructions such as
|
|
.CW !nfull(qname)
|
|
or
|
|
.CW !nempty(qname) .
|
|
More detail on this aspect of the reduction algorithms can be
|
|
found in [HoPe94].
|
|
.SH
|
|
Keywords
|
|
.LP
|
|
For reference, the following table contains all the keywords,
|
|
predefined functions, predefined variables, and
|
|
special label-prefixes of the language \*P,
|
|
and refers to the section of this paper in
|
|
which they were discussed.
|
|
.KS
|
|
.TS
|
|
center;
|
|
l l l l.
|
|
_last (1.4) _pid (1.1.1) accept (1.3.2) active (1.1.1)
|
|
assert (1.3.1) atomic (1.2.1) bit (1.1.2) bool (1.1.2)
|
|
break (1.2.4) byte (1.1.2) chan (1.1.3) d_step (1.2.2)
|
|
do (1.2.4) else (1.2.4) empty (1.1.3) enabled (1.4)
|
|
end (1.3.2) fi (1.2.3) full (1.1.3) goto (1.2.2)
|
|
hidden (not discussed) if (1.2.3) init (1.1.1) int (1.1.2)
|
|
len (1.1.3) mtype (1.1.3) nempty (2.6) never (1.3.3)
|
|
nfull (2.6) od (1.2.4) of (1.1.3) pc_value (1.4)
|
|
printf (1.1.1) proctype (1.1.1) progress (1.3.2) run (1.1.1)
|
|
short (1.1.2) skip (1.2) timeout (1.2.4) typedef (1.1.2)
|
|
unless (1.2.5) xr (2.6) xs (2.6)
|
|
.TE
|
|
.KE
|
|
.SH
|
|
References
|
|
.LP
|
|
[Ho91]
|
|
G.J. Holzmann,
|
|
.I
|
|
Design and Validation of Computer Protocols,
|
|
.R
|
|
Prentice Hall, 1991.
|
|
.LP
|
|
[Ho93]
|
|
G.J. Holzmann, ``Tutorial: Design and Validation of Protocols,''
|
|
.I
|
|
Computer Networks and ISDN Systems,
|
|
.R
|
|
1993, Vol. 25, No. 9, pp. 981-1017.
|
|
.LP
|
|
[HoPe94]
|
|
G.J. Holzmann and D.A. Peled, ``An improvement in
|
|
formal verification,''
|
|
.I
|
|
Proc. 7th Int. Conf. on Formal Description Techniques,
|
|
.R
|
|
FORTE94, Berne, Switzerland. October 1994.
|
|
.LP
|
|
[Ho95]
|
|
G.J. Holzmann, ``An Analysis of Bitstate Hashing,''
|
|
technical report 2/95, available from author.
|
|
.LP
|
|
[HS99]
|
|
G.J. Holzmann, ``Software model checking: extracting
|
|
verification models from source code,''
|
|
.I
|
|
Proc. Formal Methods in Software Engineering and Distributed
|
|
Systems,
|
|
.R
|
|
PSTV/FORTE99, Beijng, China, Oct. 1999, Kluwer,pp. 481-497.
|