Article Directory
idiocy
-- divmod.adb
package body DivMod with SPARK_Mode is
procedure DivMod(X : Positive; N : Positive; K : out Natural; Remainder : out Natural)
is
Y : Natural := X;
begin
K := 0;
while Y >= N loop
Y := Y - N;
K := K + 1;
end loop;
Remainder := Y;
end DivMod;
end DivMod;
--divmod.ads
package DivMod with SPARK_Mode is
procedure DivMod(X : in Positive; N : in Positive; K : out Natural;
Remainder : out Natural);
end DivMod;
--main.adb
with Ada.Integer_Text_IO;
use Ada.Integer_Text_IO;
with Ada.Text_IO;
use Ada.Text_IO;
with DivMod;
procedure Main with SPARK_Mode is
K : Natural;
R : Natural;
X : Integer;
N : Integer;
begin
Put_Line("Compute the result and remainder of X/N, via repeated subtraction.");
Put_Line("The result and remainder are numbers K and R where X = K * N + R and R < N");
Put("Enter a positive integer for X: ");
Get(X);
Put("Enter a positive integer for N: ");
Get(N);
if (X > 0 and N > 0) then
DivMod.DivMod(X,N,K,R);
Put("K: "); Put(K); New_Line;
Put("R: "); Put(R); New_Line;
-- assert that the result is correct
pragma Assert (X = K * N + R and R < N);
-- SPARK needs some help to conclude that X/N = K.
-- We derive that final result step-by-step
-- firstly K is not greater than X/N
pragma Assert (K * N <= X);
pragma Assert (X/N >= K);
-- secondly K is not lower than X/N
pragma Assert (if K < Integer'Last and then Integer'Last / N > K + 1 then
N * (K + 1) > X);
pragma Assert (X/N <= K);
-- therefore K is exactly equal to X/N
pragma Assert (X/N = K);
else
Put_Line("X and N must both be positive. Exiting.");
end if;
end Main;
问题:Now run the SPARK prover:
SPARK → Prove All
. You will see that the providedmain.adb
contains a number ofpragma Assert
statements. These are assertions that the SPARK prover tries to prove always hold.
You will see that theassertion X = K * N + R and R < N
inmain.adb
cannot be proved. (You
may also see potential problems reported in theDivMod
package, but we will come to those later.)
This is because theDivMod
procedurehas no contract (pre/postcondition annotations)
, so the SPARK prover cannot tell anything about K and R after it is called.
问题:Add a postcondition annotation to theDivMod
procedure indivmod.ads
to allow the failing assert to be proved. Hint: this postcondition should state what is true aboutK
andR
in terms ofX
andN
, afterDivMod
returns.
- The reason for the above problem is: we
main.adb
used it inassert
, but sincedivmod.ads
we didn’t usepost
to publishX = K * N + Remainder
this relationship to the program in , we can’t satisfy it when passingmain.adb
in , so we only need to add this inassert
divmod.adb
post condition
package DivMod with SPARK_Mode is
procedure DivMod(X : in Positive; N : in Positive; K : out Natural;
Remainder : out Natural) with
Post=> (X = K * N + Remainder);
end DivMod;
-
The above problem is solved
-
You can think
post condition
of the role of as: explicitly declare the execution result of a certainprocedure
or to the programfunction
so that SPARK can check it
问题: Now run the
SPARK Prover
again. Now theassertions
inmain.adb
should be able to be proved, using the contract onDivMod
. However, the SPARK prover cannot actually prove that the contract holds.
It also cannot prove that the loop in DivMod won’t cause integer overflow.
To help it prove these, we need to add a suitable
loop invariant
annotation for thewhile-loop
inDivMod
. To work out what the invariant should say, you can add print statements to this loop to get it to print out the values ofY
andK
each time through the loop. Then look for a relationship that always holds betweenY, K, N
andX
.
Once you have figured out the invariant, add an appropriate annotation to thewhile-loop
:pragma Loop Invariant (. . . your invariant goes here . . .);
package body DivMod with SPARK_Mode is
procedure DivMod(X : Positive; N : Positive; K : out Natural; Remainder : out Natural)
is
Y : Natural := X;
begin
K := 0;
while Y >= N loop
Y := Y - N;
K := K + 1;
pragma Loop_Invariant (Y <= X);
pragma Loop_Invariant (Y + N * K = X);
end loop;
Remainder := Y;
end DivMod;
end DivMod;
Loop invariants vs postconditions
Both loop invariants and postconditions are key tools for verifying program correctness, but they have some differences in their specific uses.
- Loop invariant: This is a condition that remains true at the beginning and end of each iteration of the loop. A loop invariant is a condition or attribute that is kept constantly during the loop, which can help us understand the behavior of the loop and ensure the correctness of the loop. Loop invariants are usually designed to capture some key information about the ongoing computation.
- Postcondition (postcondition): This is a condition that must be satisfied at the end of a procedure or function. It describes the expected state of the program after execution. Postconditions are often used together with preconditions (the state before the program starts) and the actual operation of the program to prove the correctness of the program.
In a way, you can think of a loop invariant as analogous to a postcondition in the context of a loop, in that it describes the expected state at the end of each loop iteration. However, they are semantically different: postconditions describe the state of the program at the end, while loop invariants describe each iteration of the loop.
In formal methods and program verification, it is common to use both loop invariants and pre/postconditions to help ensure program correctness.
Now re-run the SPARK prover. If your invariant is correct, you should find that the SPARK prover does not report any problems. You have proved the correctness of your first program. Congratulations!
Extended Thinking
问题: If you have time: Look at the assert statements in
main.adb
more closely. The final one asserts thatX / N = K
, i.e. thatK
does in fact hold the result of performing integer division onX
byN
.
Try commenting out each of the assert statements above and re-running the SPARK prover for each. You should find that when one of these assertions is commented out, one of the following assertions cannot be proved.
This means that, to prove that following assertion, the SPARK prover first needs to know that the preceding one holds, i.e. it cannot derive the following assertion in one go but it needs some help: we first have to tell it to derive the intermediate assertion and, only then, can it derive the subsequent one. This can sometimes happen with automated provers like the SPARK prover. Using intermediate assertions like this can be a useful way, therefore, helping to derive extra facts that cannot be inferred automatically.