1

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?

User1291
  • 7,664
  • 8
  • 51
  • 108

2 Answers2

2

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.

Patrick Trentin
  • 7,126
  • 3
  • 23
  • 40
  • Thank you, but I'm afraid I still don't get how to "simply write a liveness property" (see edit above). You think you could help me out? – User1291 Nov 02 '17 at 11:40
  • @User1291 you might want to give a quick read to [these slides](http://www.patricktrentin.com/pages/fm2017.html). – Patrick Trentin Nov 02 '17 at 13:08
1

Since your edit substantially changes the question, I write a new answer to avoid confusion. This answer addresses only the new content. Next time, consider creating a new, separate, question instead.


This is one of those cases in which paying attention to the unreached in ... warning message is really important, because it affects the outcome of the verification process.

The warning message:

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)   

relates to the content of file _spin_nvr.tmp that is created during the compilation process:

...
never progress_writer1 {    /* !([] (<> ((writer[3]@progress_writer)))) */
T0_init:
    do
    :: (! (((writer[3]@progress_writer)))) -> goto accept_S4    // state 5
    :: (1) -> goto T0_init
    od;
accept_S4:
    do
    :: (! (((writer[3]@progress_writer)))) -> goto accept_S4    // state 10
    od;
}                                                               // state 13 '-end-'
...

Roughly speaking, you can view this as the specification of a Buchi Automaton which accepts executions of your writer process with _pid equal to 3 in which it does not reach the statement with label progress_writer infinitely often, i.e. it does so only a finite number of times.

To understand this you should know that, to verify an ltl property φ, spin builds an automaton containing all paths in the original Promela model that do not satisfy φ. This is done by computing the synchronous product of the automaton modeling the original system with the automaton representing the negation of the property φ you want to verify. In your example, the negation of φ is given by the excerpt of code above taken from _spin_nvr.tmp and labeled with never progress_writer1. Then, Spin checks if there is any possible execution of this automaton:

  • if there is, then property φ is violated and such execution trace is a witness (aka counter-example) of your property
  • otherwise, property φ is verified.

The warning tells you that in the resulting synchronous product none of those states is ever reachable. Why is this the case?

Consider this:

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

At line 2:, you check that initialised == 1. This statement forces writer to block at line 2: until when initialised is set to 1. Luckily, this is done by the init process.

At line 5:, you check that initialised == 0. This statement forces writer to block at line 5: until when initialised is set to 0. However, no process ever sets initialised to 0 anywhere in the code. Therefore, the line of code labeled with progress_writer: is effectively unreachable.

See the documentation:

(1)       /* always executable               */
(0)       /* never executable                */
skip      /* always executable, same as (1)  */
true      /* always executable, same as skip */
false     /* always blocks, same as (0)      */
a == b    /* executable only when a equals b */

A condition statement can only be executed (passed) if it holds. [...]

Patrick Trentin
  • 7,126
  • 3
  • 23
  • 40
  • But I also get the ``unreached`` statements when I remove the ``(initialised==0)``? – User1291 Nov 02 '17 at 13:08
  • @User1291 I get `errors: 1` when I remove `(initialised == 0)` with `Spin 6.4.3`. Ensure you have latest version of `Spin`, then execute `~$ spin -search -a -ltl progress_writer1 file_name.pml`. – Patrick Trentin Nov 02 '17 at 13:11
  • Yup, finally got that error (had to switch to "acceptance cycle" testing, in ispin ... but I don't get how. After removing ``initialised == 0``, the writer is -- same as the reader -- in a ``while(true)`` loop, executing nothing but ``assert(true)``. I cannot see how the claim that it will "always eventually execute ``assert(true)`` could possibly be false. – User1291 Nov 02 '17 at 13:36
  • 1
    @User1291 this is due to the fact that in an **unfair** execution, the *scheduler* can at some point select for execution only processes with `_pid` equal to `1`, `2` or `4`, thus preventing `progress_writer:` to be reached by `writer` `3`. You should add some fairness constraints to your property that ensure every process gets the chance to execute infinitely often. If you try the same property when there is only one instance of `writer` and no `reader`, then it holds. – Patrick Trentin Nov 02 '17 at 13:55
  • I must confess, it hadn't occured to me that spin would be so annoying as to assume a non-blocked process could be starved by not being scheduled. I mean, seriously, who the heck CARES about the scheduler if he isn't in the process of building a scheduler? None, because you can't do anything about it anyway. And quite frankly, a process that is not blocked WILL (in the absence of hardware errors) get scheduled EVENTUALLY, otherwise, the scheduler is quite simply faulty. Anyway, thanks a lot for finally getting this to run. – User1291 Nov 02 '17 at 14:09
  • @User1291 take a look at the documentation of `enabled()` and `_last` to write *fairness constraints*. Also, use `~$ spin -l -g -p -t file_name` to examine counter-examples generated by falsified properties. You are very welcome, I dusted off quite a few concepts today. – Patrick Trentin Nov 02 '17 at 14:12