0

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?

  • I would have assumed that Standard_Input was declared to be readable. External files are tricky, as your program isn't the only one affecting them. – Jacob Sparre Andersen Nov 17 '17 at 06:14
  • Yeah, I don't understand how SPARK expects to be able to reason about external files. Would it be better to have all Text_IO be in full Ada? – LT 'syreal' Jones Nov 17 '17 at 23:00
  • I haven't seriously tried to combine SPARK 2014 with I/O (or database access), but it is my impression that it requires you to think of different solutions than with Ada. – Jacob Sparre Andersen Nov 18 '17 at 07:02

1 Answers1

2

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.

  • I tried adding the Assert( Is_Readable( Standard_Input ) ) and then SPARK complained about Standard_Input not being in the Initializes aspect of package test. I tried doing just that (though that sounded wrong to me, why would some random function in a program initialize Standard_Input??) but SPARK complained about Standard_Input needing to be in the Initializes aspect of a visible declaration of the package. I know some of those words, but unsure how to satiate SPARK, so I abandoned that track and put Is_Readable(Standard_Input) next to not End_of_File in the if check of the body. – LT 'syreal' Jones Nov 17 '17 at 21:27
  • SPARK now warns that that If statement has no effect even though it makes the medium level error go away. Weird. And thanks for the pro bono code help. I need all that I can get! – LT 'syreal' Jones Nov 17 '17 at 21:28
  • @LukeJones I'm surprised someone more knowledgeable hasn't responded. You might try asking on comp.lang.ada, where there are several active SPARK users. It's possible that your precondition was only failing because of End_Of_File and you don't have to worry about Is_Readable at all. What happens if you remove it from the if? – Jeffrey R. Carter Nov 21 '17 at 10:39