1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518 |
- .\" 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.
|