Ada Tutorial (3) SPARK2 - Post condition + Loop Invariant Post condition + loop invariant

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 provided main.adb contains a number of pragma Assert statements. These are assertions that the SPARK prover tries to prove always hold.
You will see that the assertion X = K * N + R and R < N in main.adb cannot be proved. (You
may also see potential problems reported in the DivMod package, but we will come to those later.)
This is because the DivMod procedure has no contract (pre/postcondition annotations), so the SPARK prover cannot tell anything about K and R after it is called.
insert image description here
问题:Add a postcondition annotation to the DivMod procedure in divmod.ads to allow the failing assert to be proved. Hint: this postcondition should state what is true about K and R in terms of X and N, after DivMod returns.

  • The reason for the above problem is: we main.adbused it in assert, but since divmod.adswe didn’t use postto publish X = K * N + Remainderthis relationship to the program in , we can’t satisfy it when passing main.adbin , so we only need to add this inassertdivmod.adbpost 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;

insert image description here

  • The above problem is solved

  • You can think post conditionof the role of as: explicitly declare the execution result of a certain procedureor to the program functionso that SPARK can check it

问题: Now run the SPARK Prover again. Now the assertions in main.adb should be able to be proved, using the contract on DivMod. 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.
insert image description here

To help it prove these, we need to add a suitable loop invariant annotation for the while-loop in DivMod. To work out what the invariant should say, you can add print statements to this loop to get it to print out the values of Y and K each time through the loop. Then look for a relationship that always holds between Y, K, N and X.
Once you have figured out the invariant, add an appropriate annotation to the while-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 that X / N = K, i.e. that K does in fact hold the result of performing integer division on X by N.
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.

Guess you like

Origin blog.csdn.net/qq_42902997/article/details/131169244