CS 3110 Lecture 14
We will use the term
to refer to a process that generates high assurance that code works on all
inputs and in all environments. Testing is a good, cost-effective way of getting assurance, but it is not a
verification process in this sense because there is no guarantee that the coverage of the tests is sufficient for
all uses of the code. Verification generates a proof (sometimes only implicitly) that all inputs will result in
outputs that conform to the specification. In this lecture, we look at verification based on explicitly but
informally proving correctness of the code. Later we'll see a more formal approach to proving correctness.
Verification tends to be expensive and to require thinking carefully about and deeply understanding the code
to be verified. In practice, it tends to be applied to code that is important and relatively short. Verification is
particularly valuable for critical systems where testing is less effective. Because their execution is not
determistic, concurrent programs are hard to test and sometimes subtle bugs can only be found by attempting
to verify the code formally. In fact, tools to help prove programs correct have been getting increasingly
effective and some large systems have been fully verified, including compilers, processors and processor
emulators, and key pieces of operating systems.
Another benefit to studying verification is that when you understand what it takes to prove code correct, it
will help you reason about your own code (or others') and to write code that is correct more often, based on
specs that are more precise and useful.
In recent years, techniques have been developed that combine ideas from verification and testing have been
developed that can sometimes give the best of both worlds. These ideas,
, can give the same level of assurance as formal verification at lower cost, or more assurance
than testing at similar cost. However, in the next couple of lectures, we'll look at verification in the classic
A simple example
Let's prove a short piece of code correct, in a slightly informal but hopefully convincing way. Here is a
slightly odd implementation of the
function on integers, using
, whose spec is also given:
(* Returns: max x y
is the maximum of x and y.
((max x y = x) or (max x y = y)) and
(max x y >= x) and (max x y >= y)
* Requires: Both x and y are between min_int/2 and max_int/2
let max x y = (x + y + abs(y-x))/2
(* Returns: abs x
is x if x >= 0, -x otherwise. *)
val abs : int -> int
Because this implementation doesn't use
doesn't), it's not inconceivable that this could be
faster than the obvious implementation!
To verify a function like this