AdaCore Blog

SPARK 2014 Rationale: Data Dependencies

by Florian Schanda

I claim that aspect Global is extremely easy to use. In order to figure out whether I am right or wrong, lets conduct an experiment. Without having explained anything about how aspect Global works I will give you the specifications of three procedures and also the code of the Main that calls them. You will then have to guess if the Main will behave predictably or not. If you guess correctly, then my initial statement was correct (otherwise, ... oh well tounge_smile.gif). So, here we go:

package Guess_The_Order is
   Global_Variable : Integer;

   procedure Print with
     Global => (Input  => Global_Variable);

   procedure Sub (X, Y : in Integer) with
     Global => (In_Out => Global_Variable);

   procedure Add (X, Y : in Integer) with
     Global => (Output => Global_Variable);
end Guess_The_Order;

with Guess_The_Order;
procedure Main is
begin
   Sub (6, 1);
   Add (2, 3);
   Print;
end Main;

The main program will produce unpredictable results because procedure Sub tries to read Global_Var before it has been initialized! The correct order would be, call Add first, Sub second and Print last. The Global aspect on procedure Add says that Global_Variable will be set by the subprogram. Procedure Sub's contract says that Global_Variable will be both read (which is what causes the problem) and written. Procedure Print only reads Global_Variable and presumably does some kind of printing.

Regardless of whether you guessed correctly or not, if you want to learn some more about how aspect Global works, read on! regular_smile.gif

The Global aspect helps us in two different ways. It ensures that:

  • ALL global variables mentioned by the aspect, and ONLY them, will be used by the subprogram and
  • global variables that are meant as inputs are not updated and global variables that are meant as outputs are NOT read before they are set.

The following four modes inform us of the way in which the subprogram interacts with its global variables:

  • Input: A global variable of this mode can NOT have its value updated. The global variable can and HAS TO be read. This means that there has to be at least one execution path of the subprogram where the global is read. In essence, if we say that something is an Input, then it is an Input!
  • Output: Global variables of this mode can NOT have their values read before the subprogram has set them. Furthermore, their values have to be set in ALL execution paths of the subprogram. This means that by the end of the subprogram the value of the global will always be set to something.
  • In_Out: There has to be at least one execution path that reads the initial value of the global variable and one path that updates it.
  • Proof_In: The value of the global variable can only be mentioned inside proof related annotations. Consequently, no outputs of the subprogram can ever be affected by a such a global variable. This mode allows us to use globals to check that certain properties are true for our subprogram but it also ensures that we did NOT accidentally affect the outputs of the subprogram while doing our checks.

When a mode is not specified, the tools assume a default mode of "Input". Conceptually, the first 3 modes are very similar to modes "in", "out" and "in out" of formal parameters.

Enforcing that NO global variables are used

When a subprogram is annotated with a "Global => null" contract, the tools will ensure that NO global variables are used by the subprogram. Be careful, having no Global contract is not the same as having a null Global contract (i.e. Global => null). In the absence of a Globalcontract, the tools will generate an implicit Global contract based on the subprogram's body, they will assume that to be the correct contract and will propagate it to all callers of the subprogram.

Correct Examples

package Correct_Globals
  with SPARK_Mode
is
   Everything_OK     : Boolean := True;
   Global_Var        : Natural := 1;
   Backup_Global_Var : Natural := 1;

   procedure Increase_1 (X : in out Natural) with
     Global => (Input => Global_Var);
   --  Same as: "with Global => Global_Var;"

   procedure Increase_2 (X : in out Natural) with
     Global => null;
   --  Cannot use any global variables here.

   procedure Increase_Global_Var (X : Natural) with
     Global => (In_Out   => Global_Var,
                Output   => Backup_Global_Var,
                Proof_In => Everything_OK);
   --  Everything_OK can only appear in proof-related annotations.

end Correct_Globals;
package body Correct_Globals
  with SPARK_Mode
is
   procedure Increase_1 (X : in out Natural) is
   begin
      X := X + Global_Var;
   end Increase_1;

   procedure Increase_2 (X : in out Natural) is
   begin
      X := X + 1;
   end Increase_2;

   procedure Increase_Global_Var (X : Natural) is
   begin
      pragma Assert (Everything_OK);  --  Some assertion...

      Backup_Global_Var := Global_Var;
      Global_Var := Global_Var + X;
   end Increase_Global_Var;
end Correct_Globals;

Incorrect Examples

package Incorrect_Globals
  with SPARK_Mode
is
   Global_Var : Integer;

   procedure Read_Global_Variable (X : out Integer) with
     Global => Global_Var;

   procedure Update_Global_Var (X : Integer) with
     Global => (Output => Global_Var);

   function Increase (X : Integer) return Integer with
     Global => null;
end Incorrect_Globals;
package body Incorrect_Globals
  with SPARK_Mode
is
   procedure Read_Global_Variable (X : out Integer) is
   begin
      X := Global_Var;
      Global_Var := Global_Var + 1;  --  Global_Var is of mode "Input"
      --  The error message generated by the tools is:
      --    "Global_Var" must be a global output of "Read_Global_Variable"
   end Read_Global_Variable;

   procedure Update_Global_Var (X : Integer) is
   begin
      if X < 0 then
         Global_Var := -1 * X;
      elsif X > 0 then
         Global_Var := X;
      end if;
      --  When X = 0, Global_Var is not being set. So it's Global mode
      --  should have been In_Out. Another way to look at it is as follows.
      --  A mode of "Output" means that the global variable is ALWAYS set
      --  while a mode of "In_Out" means that the variable is set
      --  SOMETIMES BUT NOT ALWAYS. The warnigs printed by the tools here is:
      --    warning: "Global_Var" might not be initialized
   end Update_Global_Var;

   function Increase (X : Integer) return Integer is
      (if X < 0
       then X + 1
       else X + Global_Var);
   --  The error message generated by the tools is:
   --    "Global_Var" must be listed in the Global aspect of "Increase"
end Incorrect_Globals;

Generated Globals

When a user-provided contract is available, the tools will attempt to verify its validity and report back in case of a contradiction. As said before, in the absence of a user-provided contract, the tools generate a contract based on the body of the subprogram, they then consider this to be the "correct" contract and use it to verify any user-provided contracts.

package Generated_Globals
  with SPARK_Mode
is
   Global_Var : Integer := 1;

   procedure Without_Contract (X : out Integer);
   --  Based on the body, the tools will compute a global contract that will
   --  say that Global_Var is a global input.

   procedure With_Correct_Contract (X : out Integer) with
     Global => Global_Var;

   procedure With_Incorrect_Contract (X : out Integer) with
     Global => null;
end Generated_Globals;
package body Generated_Globals
  with SPARK_Mode
is
   procedure Without_Contract (X : out Integer) is
   begin
      X := Global_Var;
   end Without_Contract;

   procedure With_Correct_Contract (X : out Integer) is
   begin
      Without_Contract (X);
      --  The computed Global contract verifies the validity of
      --  the user-provided contract of procedure With_Contract.
   end With_Correct_Contract;

   procedure With_Incorrect_Contract (X : out Integer) is
   begin
      Without_Contract (X);
      --  The computed Global contract contradicts the user-provided contract
      --  of procedure With_Contract. Due to the mismatch, the following
      --  error is generated:
      --    "Global_Var" must be listed in the Global aspect of
      --    "With_Incorrect_Contract"
   end With_Incorrect_Contract;
end Generated_Globals;

Conclusion

In his book "Better Embedded Systems SW" Phil Koopman says: "The main problem with using global variables is that they create implicit couplings among various pieces of the program (various routines might set or modify a variable, while several more routines might read it). Those couplings are not well represented in the software design, and are not explicitly represented in the implementation language. This type of opaque data coupling among modules results in difficult to find and hard to understand bugs." We don't have this in SPARK, thanks to the Global aspect!

Posted in #Language    #Formal Verification    #SPARK