2

this is my first Q on Stack Exchange so if there's anything that's breaking guidelines please let me know.

I have a program written in Promela for a college OS and concurrent systems class. There are two processes running that increment a variable n. Our task is to write the processes and then use the verification tool in Spin to prove that it is possible for n to take on the value 4. I've had a read through all of the command line arguments and messed around but nothing strikes out to me as the "insert this modifier followed by a variable name to check all it's possible values."

byte n;

proctype p()
{
    byte countp = 0;
    byte temp;
    do
    :: countp != 2 -> temp = n; temp = temp + 1; n = temp; countp = countp + 1;
    :: countp >= 2 -> break;
    od
}

proctype q()
{

    byte countq = 0;

    do
    :: countq != 2 -> n = n + 1; countq = countq + 1;
    :: countq >= 2 -> break;
    od
}

init
{
    run p();
    run q();
}
Patrick Trentin
  • 7,126
  • 3
  • 23
  • 40
Jack Cassidy
  • 159
  • 2
  • 14

1 Answers1

2

There are several ways to do that, though as a teacher I would prefer the ltl based one, since at least it shows you understand how to use it.


Monitor Process.

This is by far the simplest, conceptually: you simply add a process that asserts that n != 4 at any time, and then check whether this assertion eventually fails or not.

byte n;

active proctype p()
{
    byte countp = 0;
    byte temp;
    do
    :: countp != 2 -> temp = n; temp = temp + 1; n = temp; countp = countp + 1;
    :: countp >= 2 -> break;
    od
}

active proctype q()
{

    byte countq = 0;

    do
    :: countq != 2 -> n = n + 1; countq = countq + 1;
    :: countq >= 2 -> break;
    od
}

active proctype monitor()
{
    do
        :: true -> assert(n != 4);
    od;
}

Note: the loop in the monitor process is totally unnecessary, but it makes its purpose more clear to beginners.

You can verify this program with the following one liner:

~$ spin -search -bfs buggy_01.pml

Spin finds a counter-example in zero-time:

Depth=10 States=56 Transitions=84 Memory=128.195    
pan:1: assertion violated (n!=4) (at depth 19)
pan: wrote buggy_01.pml.trail

(Spin Version 6.4.3 -- 16 December 2014)
Warning: Search not completed
    + Breadth-First Search
    + Partial Order Reduction

Full statespace search for:
    never claim             - (none specified)
    assertion violations    +
    cycle checks            - (disabled by -DSAFETY)
    invalid end states      +

State-vector 28 byte, depth reached 19, errors: 1
      215 states, stored
             215 nominal states (stored-atomic)
      181 states, matched
      396 transitions (= stored+matched)
        0 atomic steps
hash conflicts:         0 (resolved)

Stats on memory usage (in Megabytes):
    0.011       equivalent memory usage for states (stored*(State-vector + overhead))
    0.290       actual memory usage for states
  128.000       memory used for hash table (-w24)
  128.195       total actual memory usage

pan: elapsed time 0 seconds

You can actually check the execution trace that violates the assertion with:

~$ spin -t -p -g -l buggy_01.pml

The options have the following meaning:

  • -t: replay the .trail counter-example generated by spin
  • -p: print all statements
  • -g: print all global variables
  • -l: print all local variables

This is the output:

using statement merging
  1:    proc  2 (monitor:1) buggy_01.pml:27 (state 1)   [(1)]
  2:    proc  1 (q:1) buggy_01.pml:19 (state 1) [((countq!=2))]
  3:    proc  0 (p:1) buggy_01.pml:8 (state 1)  [((countp!=2))]
  4:    proc  1 (q:1) buggy_01.pml:19 (state 2) [n = (n+1)]
        n = 1
  5:    proc  1 (q:1) buggy_01.pml:19 (state 3) [countq = (countq+1)]
        q(1):countq = 1
  6:    proc  1 (q:1) buggy_01.pml:19 (state 1) [((countq!=2))]
  7:    proc  1 (q:1) buggy_01.pml:19 (state 2) [n = (n+1)]
        n = 2
  8:    proc  1 (q:1) buggy_01.pml:19 (state 3) [countq = (countq+1)]
        q(1):countq = 2
  9:    proc  1 (q:1) buggy_01.pml:20 (state 4) [((countq>=2))]
 10:    proc  0 (p:1) buggy_01.pml:8 (state 2)  [temp = n]
        p(0):temp = 2
 11:    proc  0 (p:1) buggy_01.pml:8 (state 3)  [temp = (temp+1)]
        p(0):temp = 3
 12:    proc  0 (p:1) buggy_01.pml:8 (state 4)  [n = temp]
        n = 3
 13:    proc  0 (p:1) buggy_01.pml:8 (state 5)  [countp = (countp+1)]
        p(0):countp = 1
 14:    proc  0 (p:1) buggy_01.pml:8 (state 1)  [((countp!=2))]
 15:    proc  0 (p:1) buggy_01.pml:8 (state 2)  [temp = n]
        p(0):temp = 3
 16:    proc  0 (p:1) buggy_01.pml:8 (state 3)  [temp = (temp+1)]
        p(0):temp = 4
 17:    proc  0 (p:1) buggy_01.pml:8 (state 4)  [n = temp]
        n = 4
 18:    proc  0 (p:1) buggy_01.pml:8 (state 5)  [countp = (countp+1)]
        p(0):countp = 2
 19:    proc  0 (p:1) buggy_01.pml:9 (state 6)  [((countp>=2))]
spin: buggy_01.pml:27, Error: assertion violated
spin: text of failed assertion: assert((n!=4))
 20:    proc  2 (monitor:1) buggy_01.pml:27 (state 2)   [assert((n!=4))]
spin: trail ends after 20 steps
#processes: 3
        n = 4
 20:    proc  2 (monitor:1) buggy_01.pml:26 (state 3)
 20:    proc  1 (q:1) buggy_01.pml:22 (state 9) <valid end state>
 20:    proc  0 (p:1) buggy_01.pml:11 (state 11) <valid end state>
3 processes created

as you can see, it reports (one) of the possible execution traces that leads to the assertion violation.


LTL.

One can think of several ltl properties that can help to verify whether n will eventually be equal to 4. One of such properties, is [] (n != 4), which reads:

starting from the initial state, in every reachable state along all outgoing paths it is always true that n is different from 4.

The new model looks like this:

byte n;

active proctype p()
{
    byte countp = 0;
    byte temp;
    do
    :: countp != 2 -> temp = n; temp = temp + 1; n = temp; countp = countp + 1;
    :: countp >= 2 -> break;
    od
}

active proctype q()
{

    byte countq = 0;

    do
    :: countq != 2 -> n = n + 1; countq = countq + 1;
    :: countq >= 2 -> break;
    od
}

ltl p0 { [] (n != 4) }

You verify this property pretty much in the same way as you would with the assertion. To keep this answer short, I won't copy-and-paste the whole output here, and just list the commands used:

~$ spin -search -bfs buggy_02.pml
ltl p0: [] ((n!=4))
Depth=10 States=40 Transitions=40 Memory=128.195    
pan:1: assertion violated  !( !((n!=4))) (at depth 15)
pan: wrote buggy_02.pml.trail

...

Full statespace search for:
    never claim             + (p0)
    ...
State-vector 28 byte, depth reached 15, errors: 1
...

~$ spin -t -p -g -l buggy_02.pml
...

If you want to guarantee that n is always eventually equal to 4, then you should look into some mutual exclusion approach to protect your critical section or, alternatively, check the documentation of d_step {}.

Patrick Trentin
  • 7,126
  • 3
  • 23
  • 40
  • Thanks a lot, that's a great answer. Exactly what I was looking for. So are LTL's used to prove or disprove certain things in a process? Was there a specific reason for calling the ltl p0? – Jack Cassidy Mar 09 '17 at 12:00
  • 1
    @JackCassidy You can use any name, `p0` stands for `property 0`, and it's just my personal naming convention for *'silly'* properties. Names are used in the case in which you have multiple properties, because with *spin* you check 1 property at a time. *LTL* is used to state properties that you want to verify for being *true* or *false* on your program. Which way you encode a property depends on whether you want a *counter-example* or not: if the property is true, you are not given one. – Patrick Trentin Mar 09 '17 at 12:04