In this section, we investigate the semantics and the verification of the program for computing factorial numbers already presented in Section 3.1:
Right-clicking the item fac in the “Symbols” tree and selecting the menu entry “Show Semantics” lets the RISC ProgramExplorer switch to the “Semantics” view and display the information shown in Figure 4.1.
The view is split into several parts:
In the pane “Bodsy/Statement Knowledge”, the following information is displayed for the selected method/command (which is also shown at the bottom of the pane):
By default, all formulas are shown after extensive logical simplification. By selecting the link “Show Original Formulas” the original formulas as constructed by the formal calculus are displayed (these are potentially huge but may be of interest if it is unclear how the simplified formula was derived).
For example, for the body of the method fac, the following information is derived:
This is a copy of the method precondition.
The method ends with a return statement and does not modify any global variable (all modified variables are local to the method).
The core of this relation is the existential formula from which the formula value@next = factorial(old n) can be derived, i.e., the result of the function is the factorial of parameter n (the term value@next denotes the returned value in the post-state of the method execution). The remaining information drawn from the relation is that the original value of n must not be negative (old n ≥ 0), that the factorial of this value must not exceed the limit of type int (factorial(old n) ≤ Base.MAXINT), and that the command always terminates with the execution of a return statement (returns@next).
The formula states that the method body terminates, if the original value of n is not negative (or that a predecessor command prevents the execution of the command, i.e., the pre-state of the command execution is not an “executing” one such that executes@now does not hold). This information is deduced from the fact that the value of the loop’s termination term must be initially non-negative.
By selecting the body of the while loop, the following information is displayed:
We see from the pre-state knowledge that the body is only executed for 1 ≤ old i ≤ old n and that old p = factorial(old i-1); from the effects and the transition relation we can determine that the only effect of the body is to increment i and multiply p with (the old value of) i.
Selecting the whole loop, gives the following information:
We see from the pre-state knowledge that the loop is executed with old n ≥ 0 and old i = 1; from this and the termination condition old n-old i ≥-1, the termination of the loop can be established. The transition relation also tells us that var i = old n+1, i.e., that the loop terminates with the value of i being n+1 (since from the effects we know that n is not changed); from this and var p = factorial(var i-1), we know that the loop terminates with p being factorial(n).We may also the investigate the effect of imposing additional constraints on certain states. For example, we may select with the radio button the body of the while loop and enter into the textbox as a “Prestate” condition
After pressing the “Submit” button, we get the view depicted in Figure 4.2.
The green marker indicates for which statement the condition has been fixed that is indicated at the bottom of the left pane. By moving the mouse over the markers we may investigate the effects of this constraint for selected statement. For instance, when selecting
we get the information
When selecting
we get
i.e., we know that after the execution of the loop body p is even and i equals 3.
After the investigation of the method semantics (which has not given any indication of a problem so far), we may turn to the actual verification of the method. We return to the “Analysis” view and investigate the task tree of the method which is displayed in Figure 4.3 (some of the tasks labeled in violet may also be labeled red, if the corresponding proof has not yet been performed). Open tasks are indicated by red task labels.
The solution of a task proceeds by
By right-clicking the symbol of an open task, a menu appears that allows to print the corresponding task translations in a linear format (this is mainly useful to investigate how the final proof problem was generated from the original task).
For solving the proof problem associated to an open task, there exist three options:
In the last two cases, a proof is generated and stored on disk. If the RISC ProgramExplorer is terminated and restarted, the labels of the corresponding tasks are displayed in violet color. On performing “Execute Task”, the stored proof is replayed and the label turns blue again.
In detail, the following tasks need to be performed:
No attempt is made to apply the validity checker; there is always a proof requested.
The tasks are partially interdependent, e.g., the proof of partial correctness (performed in task “postcondition”) depends of the fulfillment of tasks “preconditions” and of the “loops”. However, the tasks can be performed in any order, e.g. it is recommended to first solve the “postcondition” task (under the assumption that the invariants hold) and only afterward solve “loops” (which are typically more difficult). In this way, no time is wasted on proving the correctness of an invariant that is not strong enough to show the partial correctness of a method.
In the following, we investigate the tasks for the method fac in some more detail.
Postcondition For a method with precondition p and state relation r, to show that the postcondition q is satisfied amounts to proving
p∧r ⇒ q
In our case, we have to prove
where the first line corresponds to p, the next two lines correspond to r, and the last line corresponds to q. The proof succeeds by the automatic scatter strategy.
Termination The proof that the “method body terminates” is of form
p∧¬d ⇒ t
In our case, the termination condition is essentially old n ≥ 0 which is part of the pre-condition; the corresponding task is solved by the validity checker.
Preconditions For a command with pre-state knowledge k and pre-condition c, to show that the pre-condition is met amounts to proving
k ⇒ c
In our example, there are three precondition tasks, one for the loop (to show that the loop invariant initially holds), the other two for the assignments and in the loop body (to show that no arithmetic overflows occur). The first proof of
is (after triggering the proof) solved automatically. The second proof of
(which correspods to the assignment p = p*i) requires some arithmetic reasoning with the help of additional lemmas about the factorial function specified in theory Math; we omit it here. The third proof of
(which corresponds to the assignment i = i+1) is simpler; it only depends on the theorem ∀n ∈ NAT : n > 2 ⇒ factorial(n) > n; the corresponding proof tree is
Loops For a while loop with invariant i and a body with state relation r, the proof of the correctness of the invariant amounts to proving a formula of form
i′∧e∧r ⇒ i′′
In our example, this amounts to proving (after some simplification) the formula
This corresponding proof tree is
i.e., the proof requires the heuristic instantiation of formula “1yj” which represents the factorial axiom ∀n ∈ NAT : factorial(n+1) = (n+1)⋅factorial(n).
The proof that the loop “body terminates” is essentially to show
i′∧e ⇒ b
where i′ and e are as indicated above and b is the condition for the termination of the loop body.
In our case, b is true such that the corresponding task is not generated.
The proof that the loop “measure is well-formed” is essentially to show
i′∧e∧r ⇒ t′≥ 0
where i′, e, and r are as indicated in the paragraph “Invariants” above and t′ represents the value of the termination term after the loop iteration.
In our case, t′ is denoted by old n-var i+1 where e represents old i ≤ old n and r implies var i = old i+1; the corresponding task can be solved by the validity checker.
The proof that the loop “measure is decreased” is essentially to show
i′∧e∧r ⇒ t > t′
where i′, e, r, and t′ are indicated as above and t represents the value of the termination term before the iteration of the loop.
In our case, t is denoted by old n- old i+1 with the other definitions given above; the corresponding task can be solved by the validity checker.
Type Checking Conditions The type checking conditions all result from the fact that in the method specification respectively loop invariant terms like factorial(var n) respectively factorial(var i-1) occur; for each such occurrence it has to be shown that the argument of factorial : NAT → NAT (which is by static type checking known to be an integer number) is a natural number, i.e., not negative. All the corresponding tasks can be automatically solved by the validity checker.
Specification Validation The validation of a method specification with precondition p and postcondition q amounts to proving
∀x : p(x) ⇒∃y : q(x,y)
∀x : p(x) ⇒∃y : ¬q(x,y)
In case of the method fac, this amounts to proving the following:
The corresponding proof trees are as follows:
The proof for satisfiability amounts to instantiating, for given method argument n0, the existential formula with a poststate derived from the prestate “now_” by setting the return value to factorial(n0); in the proof of non-triviality, the return value is set to -1 (a state, i.e. a value of type “STATE”, is a record whose field val denotes the state’s return value, if there is any).