Search code examples
adaproofspark-2014

How to prove a SPARK.Text_IO procedure precondition will hold


I'm using SPARK.Text_IO from the spark_io example in SPARK Discovery 2017.

My problem is that many of the SPARK.Text_IO procedures have a precondition that I do not know how to begin to try to prove namely that the standard input is readable and we're not at end of file. My attempt, as displayed in the code below, was to add the precondition of the SPARK.Text_IO procedure (Get_Immediate in this case) to the precondition of the calling procedure, thinking that maybe that would guarantee to the prover that that precondition would be true. It didn't work. Here's an example of what I'm talking about:

Test spec:

with SPARK.Ada.Text_IO; use SPARK.Ada.Text_IO;

package test with SPARK_Mode
is

   continue_messages_key : Character := ' ';

   procedure User_Wait_For_Continue_Messages_Key
   with Global => (In_Out => Standard_Input,
                   Input => continue_messages_key),
        Pre => Is_Readable (Standard_Input) and then
                    not End_Of_File; 

end test;

Test body:

pragma SPARK_Mode(On);

package body test is

   procedure User_Wait_For_Continue_Messages_Key
   is
      IR : Immediate_Result;
      Avail : Boolean;
   begin
      loop
         Get_Immediate(IR, Avail);
         if Avail then
            if IR.Status = Success then
               if IR.Available = True then
                  if IR.Item = continue_messages_key then
                     return;
                  end if;
               end if;
            end if;
         end if;
      end loop;
   end User_Wait_For_Continue_Messages_Key;

end test;

The error the prover gives is on the Get_Immediate line "medium: precondition might fail" The prototype and contract of the Get_Immediate procedure is below:

procedure Get_Immediate (Item      :    out Character_Result)
     with Global => (In_Out => Standard_Input),
          Pre    => Is_Readable (Standard_Input) and then
                    not End_Of_File,
          Post   => Is_Readable (Standard_Input) and
                    Name (Standard_Input) = Name (Standard_Input)'Old and
                    Form (Standard_Input) = Form (Standard_Input)'Old and
                    Is_Standard_Input (Standard_Input);

How do you prove to SPARK that Standard_Input is readable and that it's not end of file?


Solution

  • Let me start by saying I haven't used SPARK since the days of special comments, so my answer may not reflect current usage.

    One way to look at SPARK is that it makes you think about every thing that your program might encounter. What should your program do if the precondition is False? You have to show that you've considered this possibility.

    Presuming that all SPARK.Ada.Text_IO operations on Standard_Input have a postcondition including Is_Readable (Standard_Input) as Get_Immediate does, then it should suffice to put something like

    pragma Assert (Is_Readable (Standard_Input) );
    

    at the beginning of your program and add this to the postcondition of your procedure (and any other subprograms you have that read Standard_Input). Then you should be sure that that part of the precondition holds throughout your program. (The assertion may not be needed if SPARK initially guarantees that Standard_Input is readable.)

    not End_Of_File is a bit more complicated. It's possible for it to be False, at least on some platforms. Linux, for example, treats Ctrl-D as EOF when entered as the first thing on a line. There's also the cases where you're reading from a pipe or input redirection. Your procedure may call Get_Immediate when End_Of_File is True if the user enters EOF during the loop, so it's not surprising SPARK can't prove otherwise. Probably you need to take this part off the precondition of your procedure and change your body to something like

    All_Chars : loop
       if not End_Of_File then
          Get_Immediate (IR, Avail);
    
          exit All_Chars when Avail               and then
                              IR.Status = Success and then
                              IR.Available        and then
                              IR.Item = Continue_Messages_Key;
       end if;
    end loop All_Chars;
    

    Then you'll be sure the precondition of Get_Immediate is satisfied and have the apparently desired behavior (infinite loop) if End_Of_File becomes True (presuming that some field of IR fails one of your checks in that case).

    There are some puzzling things about your code. First is the global variable. Global variables are the Root of All Evil, or at least guarantee unreadable code. Then there's the specificity of the procedure. Wouldn't something more general, like

    procedure Wait_For_Key (Key : in Character);
    

    be just as easy to write and prove, and more useful? Then there's the string of nested if statements, which I find harder to read than the equivalent conditions connected with and then. Finally, there's comparing a Boolean to True. Since "=" returns Boolean, doesn't that indicate that what's needed is a Boolean, which is what is already on the left side of "="?

    Perhaps it means that the result of "=" must also be compared to True. Then the result of that "=" must also be compared to True. This might be better, since it ensures that whoever is writing this will never finish.