Since the representation of program state is a logical predicate, there is an alternative to keeping a complete representation of the state at
Chapter 8: Finite State Verification
8.7 Model Refinement
Because construction of finite state models requires a delicate balance between precision and efficiency, often the first model we construct will be unsatisfactory - either the
verification tool will produce reports of potential failures that are obviously impossible, or it will exhaust resources before producing any result at all. Minor differences in the model can have large effects on tractability of the verification procedure, so in practice finite state verification is often an iterative process of constructing a model, attempting verification, and then either abstracting the model further (if the verification exhausts computational resources or the user's patience before obtaining a conclusive result) or making the model more
precise to eliminate spurious results (i.e., a report of a potential error that cannot actually occur).
An iterative process of model refinement can be at least partly automated. We begin with a very coarse model that can be efficiently constructed and analyzed, and then we add detail specifically aimed at ruling out spurious error reports. There are two main approaches:
adding detail directly to the model, or adding premises to the property to be checked.
Initially, we try to verify that a very coarse model M1 satisfies property P:
However, M is only an approximation of the real system, and we find that the verification finds a violation of P because of some execution sequences that are possible in M1 but not in the real system. In the first approach, we examine the counter-example (an execution trace of M1 that violates P but is impossible in the real system) and create a new model M2 that is more precise in a way that will eliminate that particular execution trace (and many similar traces). We attempt verification again with the refined model:
If verification fails again, we repeat the process to obtain a new model M3, and so on, until verification succeeds with some "good enough" model Mk or we obtain a counter-example that corresponds to an execution of the actual program.
One kind of model that can be iteratively refined in this way is Boolean programs. The initial Boolean program model of an (ordinary) program omits all variables; branches (if, while, etc.) refer to a dummy Boolean variable whose value is unknown. Boolean programs are refined by adding variables, with assignments and tests - but only Boolean variables. For instance, if a counter-example produced by trying to verify a property of a pump controller shows that the waterLevel variable cannot be ignored, a Boolean program might be refined by adding a Boolean variable corresponding to a predicate in which waterLevel is tested (say, waterLevel < highLimit), rather than adding the variable waterLevel itself. For some kinds of interprocedural control flow analysis, it is possible to completely automate the step of choosing additional Boolean variables to refine Mi into Mi+1 and eliminate some spurious
executions.
In the second approach, M remains fixed,[4] but premises that constrain executions to be checked are added to the property P. When bogus behaviors of M violate P,we add a constraint C1 to rule them out and try the modified verification problem:
If the modified verification problem fails because of additional bogus behaviors, we try again with new constraints C2:
so on until verification either succeeds or produces a valid counter-example.
The FLAVERS finite state verification tool is an example of the second approach, adding constraints to refine a model of concurrent execution. A FLAVERS model approximates concurrent execution with a pairwise "may immediately precede" (MIP) relation among operations in different threads. Because MIP relates only pairs of individual process states, rather than k-tuples for a model with k processes, its size is only quadratic in the size of the state machine model, rather than exponential in the number of processes. Moreover, a reasonably good approximation of the MIP relation can be obtained in cubic time.[5]
If one thinks of each MIP edge in the program model as representing possible interruption of one thread and continuation of another, it is apparent that paths combining transitions within individual processes and MIP transitions between processes can represent all paths through the global state space. Many additional paths, which would not appear in a more precise global model of possible executions, are also represented. The overapproximation leads to spurious error reports involving impossible execution paths.
Additional spurious error reports result from eliding details of data variables. In the Boolean programs approach to model refinement, we would refine the model by expanding the finite state representation of the process. With FLAVERS, in contrast, information about the
variable value is represented in a separate constraint state machine, which may be provided by the user or extracted automatically from the program to be verified. Only violations of property P that satisfy all the constraints Ci are reported. The same approach of adding constraints is used to eliminate spurious error reports resulting from the MIP overestimation of possible concurrency.
[4]In practice the model M may be augmented slightly to facilitate observing significant events in the constraint, but the augmentation does not restrict or change the possible behaviors of the model M.
[5]Published algorithms for computing the "may immediately precede" relation, or the closely related "may happen in parallel" (MHP) relation, range from O(n3) to O(n6) where n is the sum of the sizes of the individual state machine models or control flow graphs. They differ
depending on the thread interactions under consideration (e.g., a MIP calculation for Ada tasks would use diffferent constraints than a MIP calculation for Java threads) as well as algorithmic approach.