Software Engineering · Requirements and Design

Exhaustive State Exploration and What a Bounded Model Proves

How model checking enumerates reachable states, why a bound is a parameter of the claim rather than a shortcut, and how to read a counterexample as a requirement defect.

A design document describes a transition relation and a property; a model checker answers whether any reachable state violates the property. The answer is exact for the model, which makes the technique valuable in a domain where every other check is a sample. It is also exact only for the model, which makes the technique dangerous when the model’s relationship to the implementation is left implicit.

The mechanism is a graph search. Start from the initial states, generate successors by applying every enabled transition, and continue until no new states appear. The visited set is the reachable state space; the property is checked at each state as it is discovered. For safety properties — those expressible as an assertion about individual states — that search is complete, and a property that survives it holds in every reachable state of the model.

The cost is the size of that graph. A model with $n$ boolean variables has $2^n$ states before any structure is considered, and concurrency multiplies rather than adds: $k$ processes with $m$ states each, interleaved freely, produce a reachable set whose size grows with the product of the individual state counts rather than their sum. This is the state explosion problem, and every practical technique in the field is an attempt to avoid enumerating that product.

The bound is part of the claim

Most real designs have unbounded state: a queue of arbitrary length, a counter that never wraps, a timestamp. A model checker needs a finite graph, so the model is bounded — the queue holds at most three messages, the counter goes to ten, the timestamp is one of four values. The resulting verdict is about the bounded model.

That is a weaker claim than it appears, and the weakness is not uniform. For safety properties of the form “no reachable state violates $I$”, a bounded search can find a violation but cannot establish the property for the unbounded system. What it establishes is that no violation occurs within the bound, which is exactly the claim a test suite makes, only exhaustively. The difference from testing is that the search covers every interleaving within the bound rather than a handful, so it finds violations that sampling reliably misses.

There are two legitimate ways to recover a general claim. The first is a small-model argument: prove that if a violation exists at all, one exists with at most $k$ messages in the queue, then check with the queue bounded at $k$. Such arguments exist for specific protocol shapes and are usually short, but they must be written and reviewed, because an incorrect small-model bound silently weakens every result.

The second is to make the bound part of the specification. “The system behaves correctly with at most $k$ outstanding messages, and rejects the $k+1$st” is a real requirement, and it is checkable without any argument about unbounded behaviour. Designs that intend to impose a capacity limit anyway should model the limit as a guard rather than as a checker parameter, which converts an approximation into a specified constraint. The distinction is visible in transition guards: a guard is part of the design, a bound is part of the tool.

Reduction without changing the answer

Three reductions are sound in the sense that they preserve the set of reachable states or the verdict, and they are the reason model checking is usable at all.

Partial-order reduction exploits independence. Two transitions that touch disjoint state and communicate with nothing else commute: applying both in either order reaches the same state. A search that explores only one order of each independent pair covers the same reachable set with far fewer steps. The saving is large when the concurrency is loose and small when every transition touches shared state, which is a useful diagnostic in itself: a design whose transitions are almost all dependent has little real concurrency and probably serialises under load.

Symmetry reduction exploits indistinguishability. If $k$ identical workers occupy the state space, permutations of them produce isomorphic states, and the search can keep one representative per orbit. The reduction factor is roughly $k!$, which is why it matters for pools and why it does not apply to processes with distinct identities.

Symbolic representation replaces explicit enumeration with a compact encoding of state sets, typically binary decision diagrams over the state bits. The graph is never built; the transition relation is applied to sets. This handles wide but regular state spaces — counters, arbiters, cache protocols — and struggles with irregular ones, which is the opposite of the profile that explicit enumeration handles well.

The choice among these is empirical, and the empirical fact worth knowing is that the difference between a model that finishes and one that does not is often a single irrelevant variable. Adding a field that no property mentions can multiply the state space by its range while adding no information. Trimming the model to the variables the property actually constrains is the highest-leverage optimisation, and it is a specification activity rather than a tool configuration.

Reading a counterexample

A failed check yields a trace: a sequence of states from an initial state to the violation. The trace is the useful output, and it is usually wrong in an instructive way.

Three interpretations recur. The first is that the design is wrong: the trace is a real execution the design permits and the requirement forbids. That is the case the technique exists for. The second is that the property is wrong: the trace is an execution nobody considered, and on inspection it is acceptable, which means the requirement was over-strong. The third, and the most common in practice, is that the model is wrong: the trace relies on a transition that the implementation cannot take, or on an interleaving the environment excludes.

The third case is where the fairness discussion in liveness and fairness becomes concrete. An explicit-state search explores every interleaving, including ones in which a process is never scheduled. A liveness violation found under such an interleaving is an artefact unless the design genuinely permits starvation. Adding a fairness constraint to the search, or a scheduling assumption to the model, changes the verdict — and the assumption is now a documented part of the claim.

The counterexample also needs shortening before it is useful to anyone else. A trace of forty steps where eight would do hides the causal chain. The standard repair is to re-run the search with a shorter bound, or to compute the shortest path to the violating state, which is what a breadth-first search gives for free. A minimal trace usually names one missing guard, and that guard is the requirement that was never written.

What the technique cannot see

Exhaustive search over a transition relation says nothing about whether the transition relation matches the code. That gap is the whole of the technique’s risk, and it does not close with more computing. Closing it requires either generating the model from the implementation, which is only feasible for restricted languages and narrow properties, or checking that each transition corresponds to a code path, which is a review activity.

The technique is also blind to properties that are not expressible over the model’s state. Latency, throughput, memory consumption, and the behaviour of the system under a partial network partition are outside the alphabet unless they are modelled explicitly, and modelling them explicitly enlarges the state space in exactly the way that defeats the search.

What remains is a narrow but real guarantee: for a stated model, with a stated bound, under stated assumptions, no reachable state violates the stated property. Each of those four qualifiers is a sentence in the design document, and a result reported without them is a result that cannot be reviewed. The same discipline applies to the weaker observational claim in refinement, where the qualifier is the abstraction function rather than the bound, and where a proof about the wrong abstraction is equally unreviewable. When the model is finite and the implementation is available, the bound can be turned into test cases instead of a proof, which is what conformance testing from a specification does with the same transition relation.

References

  1. Model Checking — Clarke, Grumberg, Peled, MIT Press, 1999
  2. The SPIN Model Checker: Primer and Reference Manual — Gerard Holzmann, Addison-Wesley, 2003
  3. Partial-Order Methods for the Verification of Concurrent Systems — Patrice Godefroid, Springer LNCS 1032, 1996