Corina S. Pasareanu - Matthew B. Dwyer - Willem Visser
wait/notify).
The systems are: RAX (Remote Agent experiment), a Java version
of a component extracted from an
embedded spacecraft-control application,
Pipeline,
a generic framework for implementing multi-threaded
staged calculations, RWVSN, Lea's
generic readers-writers synchronization framework,
DEOS,
the scheduler from a real-time executive for avionics systems that
was translated from C++, BoundedBuffer, a
bounded buffer implementation in Java that is amenable to simultaneous use
by multiple threads, NestedMonitor, a version of the bounded buffer
implementation that uses semaphores instead of Java condition-based
synchronization, and ReplicatedWorkers,
a parameterizable parallel job scheduler.
Most of these programs use a rich set of Java features
and concurrency constructs, including abstract classes, inheritance,
and java.util.Vector.
The RAX, DEOS, BoundedBuffer and NestedMonitor
examples had known errors that
we checked for. For the Pipeline, RWVSN and
ReplicatedWorkers examples we seeded faults in the program.
For example, we
dropped a negation (!) in two programs and changed <=
into < (simulating an off-by-one error) in the other. It is interesting
to note that not all seeded faults could be detected given the
properties we checked for, so we altered the faults until we
generated a property violation. In our experiments, we encoded the properties
to be checked as assertions (rather than temporal logic formulae), in order
to use JPF's new heuristic-search capabilities.
Event.count field should be abstracted with signs.
Bandera's abstraction type inference determined that the
local count variables in the FirstTask.run() method
should also be abstracted, since there are two event objects
allocated this amounts to four abstracted variables in the system.
Running JPF (using depth-first search) on this abstracted system detects a
deadlock and produces a 103 step counter-example.
Analysis of this counter-example reveals that it is spurious.
After 35 steps in the counter-example the trace reaches the conditional
at line 15. In the real system, the branch condition
is false, but due to the nondeterminism of Signs.eq()
for positive parameters the abstract system enters the conditional.
JPF is able to find a 30 step choose-free counter-example.
Running JPF using the heuristic searches (i.e., both breadth-first search and
choose-free heuristic search) discover the same 30 step counter-example.We
ran JPF in simulation mode guided by the 30 step counter-example and it was
shown to be feasible.
It is clear that the presence of spurious counter-examples is
closely related to the property being checked, the program and
the abstraction's selected. We reran our model checks changing
the abstraction for Event.count field to record information
about the evenness or oddness of its values.
This produced a 72 step counter-example (using depth-first search) and a 30
step counter-example (using both heuristic searches), but
JPF was unable to find a choose-free counter-example.
At this point, we ran JPF in simulation
mode guided by the 72 step and the 30 step counter-examples and while these
counter-examples did contain nondeterministic choices, they were shown to be
feasible.
Pipeline class to manage execution of
a multi-threaded staged computation.
The application constructs and starts execution
of a pipeline, calls stop() to end execution of the pipeline,
and calls add() to provide input to the computation.
We model checked a precedence property for the Pipeline system
stating that ``no pipeline stage (i.e., thread) will terminate
until the stop method is called''.
We encoded this using a boolean variable, stopCalled, set to true
when the stop()
method had been called and embedded assert(stopCalled)
at the return point of the stage run methods.
This example was abstracted by identifying a loop index variable
that controlled the number of times the add() method was
called and abstracting it to signs. Type inference determined
that 5 additional fields and local variables also needed abstraction.
JPF found a choose-free counter-example that occurred on
the first iteration of an abstracted loop. We ran JPF in simulation mode to
analyze the counter-examples produced during depth-first search and the
heuristic searches, and they were shown to be feasible.
RWVSN class
to implement an object with a readers-writers synchronization policy.
That object is then shared by several threads that read and write
through the RWVSN interface.
We checked that access by a reader excluded access by a writer
by setting a boolean variable, in_writer, in the writer's
critical section and resetting it upon exit, and
embedding assert(!in_writer) in the reader's critical section.
Abstraction was applied to 3 integer fields of the RWVSN
class abstracting them to signs. JPF found a choose-free counter-example;
the counter-example produced during depth-first search was analyzed and found
not feasible, while the counter-examples produced during heuristic searches
were shown to be feasible.
SIZE defines the maximum number of
items that may be stored in the buffer.
Calls to put() items into the buffer are guarded by a check
for a full buffer using the Java conditional wait/notify idiom;
calls to get() items into the buffer are guarded similarly by a check
for an empty buffer.
We ignored the details of what items are stored in the buffer and how these
items are stored.
We analyzed an instance of the problem with two producer and two consumer
threads.
The abstraction of the program was driven by our selection that the
constant SIZE should be abstracted with signs. Bandera's
abstraction type inference determined that variable count, that
stores the number of items currently stored in the buffer, should also be
abstracted. Running JPF (using depth-first search) on this abstracted system
detects a deadlock and produces a counter-example; analysis of this
counter-example reveals that it is spurious. No choose-free counter-example
were found. Using the heuristic searches, JPF found counter-examples that
were shown to be feasible.
We reran our model checks changing the abstraction for SIZE with
range(0..1) abstraction, which tracks concrete
values 0 and 1, but abstracts the values less than 0 and greater than
1, by using the set of tokens { below0, zero, one, above1 }; this
produced a choose-free counter-example.
We ran JPF in simulation mode guided by the counter-example produced using
depth-first search and it was shown to be not feasible; the counter-examples
reported by the heuristic searches were found to be feasible.
SIZE (that
records the number of items that are stored in the buffer)
using signs. Checking the abstracted system yielded feasible
counter-examples with each of the techniques.
GLobalDone, set to true when a worker thread
signals termination.
To analyze the framework, additional code was written to simulate the
behavior of a driver that invokes the framework's operations and of stubs that
implement the work done by workers.
Similar to DEOS, we modeled the environment using explicit external
nondeterminism.
The signs abstraction
was applied to the variable that stores the number of
work items in the pool.
Checking the abstracted system yielded a choose-free counter-example;
during heuristic searches, JPF ran out of memory.