Search code examples
concurrencyformal-verificationmodel-checkingpromelaspin

correct use of ``progress`` label


According to the man pages,

Progress labels are used to define correctness claims. A progress label states the requirement that the labeled global state must be visited infinitely often in any infinite system execution. Any violation of this requirement can be reported by the verifier as a non-progress cycle.

and

Spin has a special mode to prove absence of non-progress cycles. It does so with the predefined LTL formula:

(<>[] np_)

which formalizes non-progress as a standard Buchi acceptance property.

But let's take a look at the very primitive promela specification

bool initialised = 0;

init{
progress:
    initialised++;
    assert(initialised == 1);
}

In my understanding, the assert should hold but verification fail because initialised++ is executed exactly once whereas the progress label claims it should be possible to execute it arbitrarily often.

However, even with the above LTL formula, this verifies just fine in ispin (see below).

How do I correctly test whether a statement can be executed arbitrarily often (e.g. for a locking scheme)?

(Spin Version 6.4.7 -- 19 August 2017)

               + Partial Order Reduction

Full statespace search for:

never claim           + (:np_:)
assertion violations  + (if within scope of claim)
non-progress cycles   + (fairness disabled)
invalid end states    - (disabled by never claim)

State-vector 28 byte, depth reached 7, errors: 0

   6 states, stored (8 visited)
   3 states, matched
   11 transitions (= visited+matched)
   0 atomic steps

hash conflicts: 0 (resolved)

Stats on memory usage (in Megabytes):

0.000 equivalent memory usage for states (stored*(State-vector + overhead))
0.293 actual memory usage for states
64.000    memory used for hash table (-w24)
 0.343    memory used for DFS stack (-m10000)
64.539    total actual memory usage

unreached in init

   (0 of 3 states)

pan: elapsed time 0.001 seconds

No errors found -- did you verify all claims?

UPDATE

Still not sure how to use this ...

bool initialised = 0;

init{
    initialised = 1;
}

active [2] proctype reader()
{
assert(_pid >= 1);
(initialised == 1)
do
:: else ->
progress_reader:
    assert(true);
od
}

active [2] proctype writer()
{
assert(_pid >= 1);
(initialised == 1)
do
:: else -> 
    (initialised == 0)
progress_writer:
    assert(true);
od
}

And let's select testing for non-progress cycles. Then ispin runs this as

spin -a  test.pml
gcc -DMEMLIM=1024 -O2 -DXUSAFE -DNP -DNOCLAIM -w -o pan pan.c
./pan -m10000  -l

Which verifies without error.

So let's instead try this with ltl properties ...

/*pid: 0 = init, 1-2 = reader, 3-4 =  writer*/

ltl progress_reader1{ []<> reader[1]@progress_reader }
ltl progress_reader2{ []<> reader[2]@progress_reader }
ltl progress_writer1{ []<> writer[3]@progress_writer }
ltl progress_writer2{ []<> writer[4]@progress_writer }

bool initialised = 0;



init{
    initialised = 1;
}

active [2] proctype reader()
{
assert(_pid >= 1);
(initialised == 1)
do
:: else ->
progress_reader:
    assert(true);
od
}

active [2] proctype writer()
{
assert(_pid >= 1);
(initialised == 1)
do
:: else -> 
    (initialised == 0)
progress_writer:
    assert(true);
od
}

Now, first of all,

  the model contains 4 never claims: progress_writer2, progress_writer1, progress_reader2, progress_reader1
  only one claim is used in a verification run
  choose which one with ./pan -a -N name (defaults to -N progress_reader1)
  or use e.g.: spin -search -ltl progress_reader1 test.pml

Fine, I don't care, I just want this to finally run, so let's just keep progress_writer1 and worry about how to stitch it all together later:

/*pid: 0 = init, 1-2 = reader, 3-4 =  writer*/
/*ltl progress_reader1{ []<> reader[1]@progress_reader }*/
/*ltl progress_reader2{ []<> reader[2]@progress_reader }*/
ltl progress_writer1{ []<> writer[3]@progress_writer }
/*ltl progress_writer2{ []<> writer[4]@progress_writer }*/
bool initialised = 0;
init{
    initialised = 1;
}
active [2] proctype reader()
{
assert(_pid >= 1);
(initialised == 1)
do
:: else ->
progress_reader:
    assert(true);
od
}
active [2] proctype writer()
{
assert(_pid >= 1);
(initialised == 1)
do
:: else -> 
    (initialised == 0)
progress_writer:
    assert(true);
od
}

ispin runs this with

spin -a  test.pml
ltl progress_writer1: [] (<> ((writer[3]@progress_writer)))
gcc -DMEMLIM=1024 -O2 -DXUSAFE -DSAFETY -DNOCLAIM -w -o pan pan.c
./pan -m10000 

Which does not yield an error, but instead reports

unreached in claim progress_writer1
    _spin_nvr.tmp:3, state 5, "(!((writer[3]._p==progress_writer)))"
    _spin_nvr.tmp:3, state 5, "(1)"
    _spin_nvr.tmp:8, state 10, "(!((writer[3]._p==progress_writer)))"
    _spin_nvr.tmp:10, state 13, "-end-"
    (3 of 13 states)    

Yeah? Splendid! I have absolutely no idea what to do about this.

How do I get this to run?


Solution

  • The problem with your code example is that it does not have any infinite system execution.

    Progress labels are used to define correctness claims. A progress label states the requirement that the labeled global state must be visited infinitely often in any infinite system execution. Any violation of this requirement can be reported by the verifier as a non-progress cycle.


    Try this example instead:

    short val = 0;
    
    init {
        do
            :: val == 0 ->
               val = 1;
               // ...
               val = 0;
    
            :: else ->
    progress:
               // super-important progress state
               printf("progress-state\n");
               assert(val != 0);
        od;
    };
    

    A normal check does not find any error:

    ~$ spin -search test.pml 
    
    (Spin Version 6.4.3 -- 16 December 2014)
        + Partial Order Reduction
    
    Full statespace search for:
        never claim             - (none specified)
        assertion violations    +
        cycle checks            - (disabled by -DSAFETY)
        invalid end states      +
    
    State-vector 12 byte, depth reached 2, errors: 0
            3 states, stored
            1 states, matched
            4 transitions (= stored+matched)
            0 atomic steps
    hash conflicts:         0 (resolved)
    
    Stats on memory usage (in Megabytes):
        0.000   equivalent memory usage for states (stored*(State-vector + overhead))
        0.292   actual memory usage for states
      128.000   memory used for hash table (-w24)
        0.534   memory used for DFS stack (-m10000)
      128.730   total actual memory usage
    
    
    unreached in init
        test.pml:12, state 5, "printf('progress-state\n')"
        test.pml:13, state 6, "assert((val!=0))"
        test.pml:15, state 10, "-end-"
        (3 of 10 states)
    
    pan: elapsed time 0 seconds
    

    whereas, checking for progress yields the error:

    ~$ spin -search -l test.pml 
    
    pan:1: non-progress cycle (at depth 2)
    pan: wrote test.pml.trail
    
    (Spin Version 6.4.3 -- 16 December 2014)
    Warning: Search not completed
        + Partial Order Reduction
    
    Full statespace search for:
        never claim             + (:np_:)
        assertion violations    + (if within scope of claim)
        non-progress cycles     + (fairness disabled)
        invalid end states      - (disabled by never claim)
    
    State-vector 20 byte, depth reached 7, errors: 1
            4 states, stored
            0 states, matched
            4 transitions (= stored+matched)
            0 atomic steps
    hash conflicts:         0 (resolved)
    
    Stats on memory usage (in Megabytes):
        0.000   equivalent memory usage for states (stored*(State-vector + overhead))
        0.292   actual memory usage for states
      128.000   memory used for hash table (-w24)
        0.534   memory used for DFS stack (-m10000)
      128.730   total actual memory usage
    
    
    
    pan: elapsed time 0 seconds
    

    WARNING: ensure to write -l after option -search, otherwise it is not handed over to the verifier.


    You ask:

    How do I correctly test whether a statement can be executed arbitrarily often (e.g. for a locking scheme)?

    Simply write a liveness property:

    ltl prop { [] <> proc[0]@label };
    

    This checks that process with name proc and pid 0 executes infinitely often the statement corresponding to label.