Flow Analysis

In this section we present the flow analysis capability provided by the GNATprove tool, a critical tool for using SPARK.

What does flow analysis do?

Flow analysis concentrates primarily on variables. It models how information flows through them during a subprogram's execution, connecting the final values of variables to their initial values. It analyzes global variables declared at library level, local variables, and formal parameters of subprograms.

Nesting of subprograms creates what we call scope variables: variables declared locally to an enclosing unit. From the perspective of a nested subprogram, scope variables look very much like global variables

Flow analysis is usually fast, roughly as fast as compilation. It detects various types of errors and finds violations of some SPARK legality rules, such as the absence of aliasing and freedom of expressions from side-effects. We discussed these rules in the SPARK Overview.

Flow analysis is sound: if it doesn't detect any errors of a type it's supposed to detect, we know for sure there are no such errors.

Errors Detected

Uninitialized Variables

We now present each class of errors detected by flow analysis. The first is the reading of an uninitialized variable. This is nearly always an error: it introduces non-determinism and breaks the type system because the value of an uninitialized variable may be outside the range of its subtype. For these reasons, SPARK requires every variable to be initialized before being read.

Flow analysis is responsible for ensuring that SPARK code always fulfills this requirement. For example, in the function Max_Array shown below, we've neglected to initialize the value of Max prior to entering the loop. As a consequence, the value read by the condition of the if statement may be uninitialized. Flow analysis detects and reports this error.

package Show_Uninitialized is type Array_Of_Naturals is array (Integer range <>) of Natural; function Max_Array (A : Array_Of_Naturals) return Natural; end Show_Uninitialized;
package body Show_Uninitialized is function Max_Array (A : Array_Of_Naturals) return Natural is Max : Natural; begin for I in A'Range loop if A (I) > Max then -- Here Max may not be initialized Max := A (I); end if; end loop; return Max; end Max_Array; end Show_Uninitialized;

Ineffective Statements

Ineffective statements are different than dead code: they're executed, and often even modify the value of variables, but have no effect on any of the subprogram's visible outputs: parameters, global variables or the function result. Ineffective statements should be avoided because they make the code less readable and more difficult to maintain.

More importantly, they're often caused by errors in the program: the statement may have been written for some purpose, but isn't accomplishing that purpose. These kinds of errors can be difficult to detect in other ways.

For example, the subprograms Swap1 and Swap2 shown below don't properly swap their two parameters X and Y. This error caused a statement to be ineffective. That ineffective statement is not an error in itself, but flow analysis produces a warning since it can be indicative of an error, as it is here.

package Show_Ineffective_Statements is type T is new Integer; procedure Swap1 (X, Y : in out T); procedure Swap2 (X, Y : in out T); end Show_Ineffective_Statements;
package body Show_Ineffective_Statements is procedure Swap1 (X, Y : in out T) is Tmp : T; begin Tmp := X; -- This statement is ineffective X := Y; Y := X; end Swap1; Tmp : T := 0; procedure Swap2 (X, Y : in out T) is Temp : T := X; -- This variable is unused begin X := Y; Y := Tmp; end Swap2; end Show_Ineffective_Statements;

So far, we've seen examples where flow analysis warns about ineffective statements and unused variables.

Incorrect Parameter Mode

Parameter modes are an important part of documenting the usage of a subprogram and affect the code generated for that subprogram. Flow analysis checks that each specified parameter mode corresponds to the usage of that parameter in the subprogram's body. It checks that an in parameter is never modified, either directly or through a subprogram call, checks that the initial value of an out parameter is never read in the subprogram (since it may not be defined on subprogram entry), and warn whens an in out parameter isn't modified or when its initial value isn't used. All of these may be signs of an error.

We see an example below. The subprogram Swap is incorrect and GNATprove warns about an input which isn't read:

package Show_Incorrect_Param_Mode is type T is new Integer; procedure Swap (X, Y : in out T); end Show_Incorrect_Param_Mode;
package body Show_Incorrect_Param_Mode is procedure Swap (X, Y : in out T) is Tmp : T := X; begin Y := X; -- The initial value of Y is not used X := Tmp; -- Y is computed to be an out parameter end Swap; end Show_Incorrect_Param_Mode;

In SPARK, unlike Ada, you should declare an out parameter to be in out if it's not modified on every path, in which case its value may depend on its initial value. SPARK is stricter than Ada to allow more static detection of errors. This table summarizes SPARK's valid parameter modes as a function of whether reads and writes are done to the parameter.

Initial value read Written on some path Written on every path Parameter mode
X     in
X X   in out
X   X in out
  X   in out
    X out

Additional Verifications

Global Contracts

So far, none of the verifications we've seen require you to write any additional annotations. However, flow analysis also checks flow annotations that you write. In SPARK, you can specify the set of global and scoped variables accessed or modified by a subprogram. You do this using a contract named Global.

When you specify a Global contract for a subprogram, flow analysis checks that it's both correct and complete, meaning that no variables other than those stated in the contract are accessed or modified, either directly or through a subprogram call, and that all those listed are accessed or modified. For example, we may want to specify that the function Get_Value_Of_X reads the value of the global variable X and doesn't access any other global variable. If we do this through a comment, as is usually done in other languages, GNATprove can't verify that the code complies with this specification:

package Show_Global_Contracts is

   X : Natural := 0;

   function Get_Value_Of_X return Natural;
   --  Get_Value_Of_X reads the value of the global variable X

end Show_Global_Contracts;

You write global contracts as part of the subprogram specification. In addition to their value in flow analysis, they also provide useful information to users of a subprogram. The value you specify for the Global aspect is an aggregate-like list of global variable names, grouped together according to their mode.

In the example below, the procedure Set_X_To_Y_Plus_Z reads both Y and Z. We indicate this by specifying them as the value for Input. It also writes X, which we specify using Output. Since Set_X_To_X_Plus_Y both writes X and reads its initial value, X's mode is In_Out. Like parameters, if no mode is specified in a Global aspect, the default is Input. We see this in the case of the declaration of Get_Value_Of_X. Finally, if a subprogram, such as Incr_Parameter_X, doesn't reference any global variables, you set the value of the global contract to null.

package Show_Global_Contracts is X, Y, Z : Natural := 0; procedure Set_X_To_Y_Plus_Z with Global => (Input => (Y, Z), -- reads values of Y and Z Output => X); -- modifies value of X procedure Set_X_To_X_Plus_Y with Global => (Input => Y, -- reads value of Y In_Out => X); -- modifies value of X and -- also reads its initial value function Get_Value_Of_X return Natural with Global => X; -- reads the value of the global variable X procedure Incr_Parameter_X (X : in out Natural) with Global => null; -- do not reference any global variable end Show_Global_Contracts;

Depends Contracts

You may also supply a Depends contract for a subprogram to specify dependencies between its inputs and outputs. These dependencies include not only global variables but also parameters and the function's result. When you supply a Depends contract for a subprogram, flow analysis checks that it's correct and complete, that is, for each dependency you list, the variable depends on those listed and on no others.

For example, you may want to say that the new value of each parameter of Swap, shown below, depends only on the initial value of the other parameter and that the value of X after the return of Set_X_To_Zero doesn't depend on any global variables. If you indicate this through a comment, as you often do in other languages, GNATprove can't verify that this is actually the case.

package Show_Depends_Contracts is

   type T is new Integer;

   procedure Swap (X, Y : in out T);
   --  The value of X (resp. Y) after the call depends only
   --  on the value of Y (resp. X) before the call

   X : Natural;
   procedure Set_X_To_Zero;
   --  The value of X after the call depends on no input

end Show_Depends_Contracts;

Like Global contracts, you specify a Depends contract in subprogram declarations using an aspect. Its value is a list of one or more dependency relations between the outputs and inputs of the subprogram. Each relation is represented as two lists of variable names separated by an arrow. On the left of each arrow are variables whose final value depends on the initial value of the variables you list on the right.

For example, here we indicate that the final value of each parameter of Swap depends only on the initial value of the other parameter. If the subprogram is a function, we list its result as an output, using the Result attribute, as we do for Get_Value_Of_X below.

package Show_Depends_Contracts is type T is new Integer; X, Y, Z : T := 0; procedure Swap (X, Y : in out T) with Depends => (X => Y, -- X depends on the initial value of Y Y => X); -- Y depends on the initial value of X function Get_Value_Of_X return T with Depends => (Get_Value_Of_X'Result => X); -- result depends on the initial value of X procedure Set_X_To_Y_Plus_Z with Depends => (X => (Y, Z)); -- X depends on the initial values of Y and Z procedure Set_X_To_X_Plus_Y with Depends => (X =>+ Y); -- X depends on Y and X's initial value procedure Do_Nothing (X : T) with Depends => (null => X); -- no output is affected by X procedure Set_X_To_Zero with Depends => (X => null); -- X depends on no input end Show_Depends_Contracts;

Often, the final value of a variable depends on its own initial value. You can specify this in a concise way using the + character, as we did in the specification of Set_X_To_X_Plus_Y above. If there's more than one variable on the left of the arrow, a + means each variables depends on itself, not that they all depend on each other. You can write the corresponding dependency with (=> +) or without (=>+) whitespace.

If you have a program where an input isn't used to compute the final value of any output, you express that by writting null on the left of the dependency relation, as we did for the Do_Nothing subprogram above. You can only write one such dependency relation, which lists all unused inputs of the subprogram, and it must be written last. Such an annotation also silences flow analysis' warning about unused parameters. You can also write null on the right of a dependency relation to indicate that an output doesn't depend on any input. We do that above for the procedure Set_X_To_Zero.

Shortcomings

Modularity

Flow analysis is sound, meaning that if it doesn't output a message on some analyzed SPARK code, you can be assured that none of the errors it tests for can occur in that code. On the other hand, flow analysis often issues messages when there are, in fact, no errors. The first, and probably most common reason for this relates to modularity.

To scale flow analysis to large projects, verifications are usually done on a per-subprogram basis, including detection of uninitialized variables. To analyze this modularly, flow analysis needs to assume the initialization of inputs on subprogram entry and modification of outputs during subprogram execution. Therefore, each time a subprogram is called, flow analysis checks that global and parameter inputs are initialized and each time a subprogram returns, it checks that global and parameter outputs were modified.

This can produce error messages on perfectly correct subprograms . An example is Set_X_To_Y_Plus_Z below, which only sets its out parameter X when Overflow is False.

procedure Set_X_To_Y_Plus_Z (Y, Z : Natural; X : out Natural; Overflow : out Boolean) is begin if Natural'Last - Z < Y then Overflow := True; -- X should be initialized on every path else Overflow := False; X := Y + Z; end if; end Set_X_To_Y_Plus_Z;

The message means that flow analysis wasn't able to verify that the program didn't read an uninitialized variable. To solve this problem, you can either set X to a dummy value when there's an overflow or manually verify that X is never used after a call to Set_X_To_Y_Plus_Z that returned True as the value of Overflow.

Composite Types

Another common cause of false alarms is caused by the way flow analysis handles composite types. Let's start with arrays.

Flow analysis treats an entire array as single object instead of one object per element, so it considers modifying a single element to be a modification of the array as a whole. Obviously, this makes reasoning about which global variables are accessed less precise and hence the dependencies of those variables are also less precise. This also affects the ability to accurately detect reads of uninitialized data.

It's often impossible for flow analysis to determine if an entire array object has been initialized, even in very simple cases. For example, after we write a loop to initialize every element of an unconstrained array A, we may still receive a message from flow analysis claiming that the array isn't initialized. To resolve this issue, you can either use an aggregate assignment, or, if that's not possible, verify initialization of the object manually.

package Show_Composite_Types_Shortcoming is type T is array (Natural range <>) of Integer; procedure Init_Loop (A : out T); procedure Init_Aggregate (A : out T); end Show_Composite_Types_Shortcoming;
package body Show_Composite_Types_Shortcoming is procedure Init_Loop (A : out T) is begin for I in A'Range loop A (I) := 0; end loop; -- flow analysis doesn't know that A is initialized end Init_Loop; procedure Init_Aggregate (A : out T) is begin A := (others => 0); -- flow analysis knows that A is initialized end Init_Aggregate; end Show_Composite_Types_Shortcoming;

Flow analysis is more precise on record objects because it tracks the value of each component of a record separately within a single subprogram. So when a record object is initialized by successive assignments of its components, flow analysis knows that the entire object is initialized. However, record objects are still treated as single objects when analyzed as an input or output of a subprogram.

package Show_Record_Flow_Analysis is type Rec is record F1 : Natural; F2 : Natural; end record; procedure Init (R : out Rec); end Show_Record_Flow_Analysis;
package body Show_Record_Flow_Analysis is procedure Init (R : out Rec) is begin R.F1 := 0; R.F2 := 0; -- R is initialized end Init; end Show_Record_Flow_Analysis;

Flow analysis complains when a procedure call initializes only some components of a record object. It'll notify you of uninitialized components, as we see in subprogram Init_F2 below.

package Show_Record_Flow_Analysis is type Rec is record F1 : Natural; F2 : Natural; end record; procedure Init (R : out Rec); procedure Init_F2 (R : in out Rec); end Show_Record_Flow_Analysis;
package body Show_Record_Flow_Analysis is procedure Init_F2 (R : in out Rec) is begin R.F2 := 0; end Init_F2; procedure Init (R : out Rec) is begin R.F1 := 0; Init_F2 (R); -- R should be initialized before this call end Init; end Show_Record_Flow_Analysis;

Value Dependency

Flow analysis is not value-dependent: it never reasons about the values of expressions, only whether they have been set to some value or not. As a consequence, if some execution path in a subprogram is impossible, but the impossibility can only be determined by looking at the values of expressions, flow analysis still considers that path feasible and may emit messages based on it believing that execution along such a path is possible.

For example, in the version of Absolute_Value below, flow analysis computes that R is uninitialized on a path that enters neither of the two conditional statements. Because it doesn't consider values of expressions, it can't know that such a path is impossible.

procedure Absolute_Value (X : Integer; R : out Natural) is begin if X < 0 then R := -X; end if; if X >= 0 then R := X; end if; -- flow analysis doesn't know that R is initialized end Absolute_Value;

To avoid this problem, you should make the control flow explicit, as in this second version of Absolute_Value:

procedure Absolute_Value (X : Integer; R : out Natural) is begin if X < 0 then R := -X; else R := X; end if; -- flow analysis knows that R is initialized end Absolute_Value;

Contract Computation

The final cause of unexpected flow messages that we'll discuss also comes from inaccuracy in computations of contracts. As we explained earlier, both Global and Depends contracts are optional, but GNATprove uses their data for some of its analysis.

For example, flow analysis can't detect reads from uninitialized variables without knowing the set of variables accessed. It needs to analyze and check both the Depends contracts you wrote for a subprogram and those you wrote for callers of that subprogram. Since each flow contract on a subprogram depends on the flow contracts of all the subprograms called inside its body, this computation can often be quite time-consuming. Therefore, flow analysis sometimes trades-off the precision of this computation against the time a more precise computation would take.

This is the case for Depends contracts, where flow analysis simply assumes the worst, that each subprogram's output depends on all of that subprogram's inputs. To avoid this assumption, all you have to do is supply contracts when default ones are not precise enough. You may also want to supply Global contracts to further speed up flow analysis on larger programs.

Code Examples / Pitfalls

Example #1

The procedure Search_Array searches for an occurrence of element E in an array A. If it finds one, it stores the index of the element in Result. Otherwise, it sets Found to False.

package Show_Search_Array is type Array_Of_Positives is array (Natural range <>) of Positive; procedure Search_Array (A : Array_Of_Positives; E : Positive; Result : out Integer; Found : out Boolean); end Show_Search_Array;
package body Show_Search_Array is procedure Search_Array (A : Array_Of_Positives; E : Positive; Result : out Integer; Found : out Boolean) is begin for I in A'Range loop if A (I) = E then Result := I; Found := True; return; end if; end loop; Found := False; end Search_Array; end Show_Search_Array;

GNATprove produces a message saying that Result is possibly uninitialized on return. There are perfectly legal uses of the function Search_Array, but flow analysis detects that Result is not initialized on the path that falls through from the loop. Even though this program is correct, you shouldn't ignore the message: it means flow analysis cannot guarantee that Result is always initialized at the call site and so assumes any read of Result at the call site will read initialized data. Therefore, you should either initialize Result when Found is false, which silences flow analysis, or verify this assumption at each call site by other means.

Example #2

To avoid the message previously issued by GNATprove, we modify Search_Array to raise an exception when E isn't found in A:

package Show_Search_Array is type Array_Of_Positives is array (Natural range <>) of Positive; Not_Found : exception; procedure Search_Array (A : Array_Of_Positives; E : Positive; Result : out Integer); end Show_Search_Array;
package body Show_Search_Array is procedure Search_Array (A : Array_Of_Positives; E : Positive; Result : out Integer) is begin for I in A'Range loop if A (I) = E then Result := I; return; end if; end loop; raise Not_Found; end Search_Array; end Show_Search_Array;

Flow analysis doesn't emit any messages in this case, meaning it can verify that Result can't be read in SPARK code while uninitialized. But why is that, since Result is still not initialized when E is not in A? This is because the exception, Not_Found, can never be caught within SPARK code (SPAK doesn't allow exception handlers). However, the GNATprove tool also tries to ensure the absence of runtime errors in SPARK code, so tries to prove that Not_Found is never raised. When it can't do that here, it produces a different message.

Example #3

In this example, we're using a discriminated record for the result of Search_Array instead of conditionally raising an exception. By using such a structure, the place to store the index at which E was found exists only when E was indeed found. So if it wasn't found, there's nothing to be initialized.

package Show_Search_Array is type Array_Of_Positives is array (Natural range <>) of Positive; type Search_Result (Found : Boolean := False) is record case Found is when True => Content : Integer; when False => null; end case; end record; procedure Search_Array (A : Array_Of_Positives; E : Positive; Result : out Search_Result) with Pre => not Result'Constrained; end Show_Search_Array;
package body Show_Search_Array is procedure Search_Array (A : Array_Of_Positives; E : Positive; Result : out Search_Result) is begin for I in A'Range loop if A (I) = E then Result := (Found => True, Content => I); return; end if; end loop; Result := (Found => False); end Search_Array; end Show_Search_Array;

This example is correct and flow analysis doesn't issue any message: it can verify both that no uninitialized variables are read in Search_Array's body, and that all its outputs are set on return. We've used the attribute Constrained in the precondition of Search_Array to indicate that the value of the Result in argument can be set to any variant of the record type Search_Result, specifically to either the variant where E was found and where it wasn't.

Example #4

The function Size_Of_Biggest_Increasing_Sequence is supposed to find all sequences within its parameter A that contain elements with increasing values and returns the length of the longest one. To do this, it calls a nested procedure Test_Index iteratively on all the elements of A. Test_Index checks if the sequence is still increasing. If so, it updates the largest value seen so far in this sequence. If not, it means it's found the end of a sequence, so it computes the size of that sequence and stores it in Size_Of_Seq.

package Show_Biggest_Increasing_Sequence is type Array_Of_Positives is array (Integer range <>) of Positive; function Size_Of_Biggest_Increasing_Sequence (A : Array_Of_Positives) return Natural; end Show_Biggest_Increasing_Sequence;
package body Show_Biggest_Increasing_Sequence is function Size_Of_Biggest_Increasing_Sequence (A : Array_Of_Positives) return Natural is Max : Natural; End_Of_Seq : Boolean; Size_Of_Seq : Natural; Beginning : Integer; procedure Test_Index (Current_Index : Integer) is begin if A (Current_Index) >= Max then Max := A (Current_Index); End_Of_Seq := False; else Max := 0; End_Of_Seq := True; Size_Of_Seq := Current_Index - Beginning; Beginning := Current_Index; end if; end Test_Index; Biggest_Seq : Natural := 0; begin for I in A'Range loop Test_Index (I); if End_Of_Seq then Biggest_Seq := Natural'Max (Size_Of_Seq, Biggest_Seq); end if; end loop; return Biggest_Seq; end Size_Of_Biggest_Increasing_Sequence; end Show_Biggest_Increasing_Sequence;

However, this example is not correct. Flow analysis emits messages for Test_Index stating that Max, Beginning, and Size_Of_Seq should be initialized before being read. Indeed, when you look carefully, you see that both Max and Beginning are missing initializations because they are read in Test_Index before being written. As for Size_Of_Seq, we only read its value when End_Of_Seq is true, so it actually can't be read before being written, but flow analysis isn't able to verify its initialization by using just flow information.

The call to Test_Index is automatically inlined by GNATprove, which leads to another messages above. If GNATprove couldn't inline the call to Test_Index, for example if it was defined in another unit, the same messages would be issued on the call to Test_Index.

Example #5

In the following example, we model permutations as arrays where the element at index I is the position of the I'th element in the permutation. The procedure Init initializes a permutation to the identity, where the I'th elements is at the I'th position. Cyclic_Permutation calls Init and then swaps elements to construct a cyclic permutation.

package Show_Permutation is type Permutation is array (Positive range <>) of Positive; procedure Swap (A : in out Permutation; I, J : Positive); procedure Init (A : out Permutation); function Cyclic_Permutation (N : Natural) return Permutation; end Show_Permutation;
package body Show_Permutation is procedure Swap (A : in out Permutation; I, J : Positive) is Tmp : Positive := A (I); begin A (I) := A (J); A (J) := Tmp; end Swap; procedure Init (A : out Permutation) is begin for I in A'Range loop A (I) := I; end loop; end Init; function Cyclic_Permutation (N : Natural) return Permutation is A : Permutation (1 .. N); begin Init (A); for I in A'First .. A'Last - 1 loop Swap (A, I, I + 1); end loop; return A; end Cyclic_Permutation; end Show_Permutation;

This program is correct. However, flow analysis will nevertheless still emit messages because it can't verify that every element of A is initialized by the loop in Init. This message is a false alarm. You can either ignore it or justify it safely.

Example #6

This program is the same as the previous one except that we've changed the mode of A in the specification of Init to in out to avoid the message from flow analysis on array assignment.

package Show_Permutation is type Permutation is array (Positive range <>) of Positive; procedure Swap (A : in out Permutation; I, J : Positive); procedure Init (A : in out Permutation); function Cyclic_Permutation (N : Natural) return Permutation; end Show_Permutation;
package body Show_Permutation is procedure Swap (A : in out Permutation; I, J : Positive) is Tmp : Positive := A (I); begin A (I) := A (J); A (J) := Tmp; end Swap; procedure Init (A : in out Permutation) is begin for I in A'Range loop A (I) := I; end loop; end Init; function Cyclic_Permutation (N : Natural) return Permutation is A : Permutation (1 .. N); begin Init (A); for I in A'First .. A'Last - 1 loop Swap (A, I, I + 1); end loop; return A; end Cyclic_Permutation; end Show_Permutation;

This program is not correct. Changing the mode of a parameter that should really be out to in out to silence a false alarm is not a good idea. Not only does this obfuscate the specification of Init, but flow analysis emits a message on the procedure where A is not initialized, as shown by the message in Cyclic_Permutation.

Example #7

Incr_Step_Function takes an array A as an argument and iterates through A to increment every element by the value of Increment, saturating at a specified threshold value. We specified a Global contract for Incr_Until_Threshold.

package Show_Increments is type Array_Of_Positives is array (Natural range <>) of Positive; Increment : constant Natural := 10; procedure Incr_Step_Function (A : in out Array_Of_Positives); end Show_Increments;
package body Show_Increments is procedure Incr_Step_Function (A : in out Array_Of_Positives) is Threshold : Positive := Positive'Last; procedure Incr_Until_Threshold (I : Integer) with Global => (Input => Threshold, In_Out => A); procedure Incr_Until_Threshold (I : Integer) is begin if Threshold - Increment <= A (I) then A (I) := Threshold; else A (I) := A (I) + Increment; end if; end Incr_Until_Threshold; begin for I in A'Range loop if I > A'First then Threshold := A (I - 1); end if; Incr_Until_Threshold (I); end loop; end Incr_Step_Function; end Show_Increments;

Everything is fine here. Specifically, the Global contract is correct. It mentions both Threshold, which is read but not written in the procedure, and A, which is both read and written. The fact that A is a parameter of an enclosing unit doesn't prevent us from using it inside the Global contract; it really is global to Incr_Until_Threshold. We didn't mention Increment since it's a static constant.

Example #8

We now go back to the procedure Test_Index from Example #4 and correct the missing initializations. We want to know if the Global contract of Test_Index is correct.

package Show_Biggest_Increasing_Sequence is type Array_Of_Positives is array (Integer range <>) of Positive; function Size_Of_Biggest_Increasing_Sequence (A : Array_Of_Positives) return Natural; end Show_Biggest_Increasing_Sequence;
package body Show_Biggest_Increasing_Sequence is function Size_Of_Biggest_Increasing_Sequence (A : Array_Of_Positives) return Natural is Max : Natural := 0; End_Of_Seq : Boolean; Size_Of_Seq : Natural := 0; Beginning : Integer := A'First - 1; procedure Test_Index (Current_Index : Integer) with Global => (In_Out => (Beginning, Max, Size_Of_Seq), Output => End_Of_Seq, Input => Current_Index) is begin if A (Current_Index) >= Max then Max := A (Current_Index); End_Of_Seq := False; else Max := 0; End_Of_Seq := True; Size_Of_Seq := Current_Index - Beginning; Beginning := Current_Index; end if; end Test_Index; Biggest_Seq : Natural := 0; begin for I in A'Range loop Test_Index (I); if End_Of_Seq then Biggest_Seq := Natural'Max (Size_Of_Seq, Biggest_Seq); end if; end loop; return Biggest_Seq; end Size_Of_Biggest_Increasing_Sequence; end Show_Biggest_Increasing_Sequence;

The contract in this example is not correct: Current_Index is a parameter of Test_Index, so we shouldn't reference it as a global variable. Also, we should have listed variable A from the outer scope as an Input in the Global contract.

Example #9

Next, we change the Global contract of Test_Index into a Depends contract. In general, we don't need both contracts because the set of global variables accessed can be deduced from the Depends contract.

package Show_Biggest_Increasing_Sequence is type Array_Of_Positives is array (Integer range <>) of Positive; function Size_Of_Biggest_Increasing_Sequence (A : Array_Of_Positives) return Natural; end Show_Biggest_Increasing_Sequence;
package body Show_Biggest_Increasing_Sequence is function Size_Of_Biggest_Increasing_Sequence (A : Array_Of_Positives) return Natural is Max : Natural := 0; End_Of_Seq : Boolean; Size_Of_Seq : Natural := 0; Beginning : Integer := A'First - 1; procedure Test_Index (Current_Index : Integer) with Depends => ((Max, End_Of_Seq) => (A, Current_Index, Max), (Size_Of_Seq, Beginning) => + (A, Current_Index, Max, Beginning)) is begin if A (Current_Index) >= Max then Max := A (Current_Index); End_Of_Seq := False; else Max := 0; End_Of_Seq := True; Size_Of_Seq := Current_Index - Beginning; Beginning := Current_Index; end if; end Test_Index; Biggest_Seq : Natural := 0; begin for I in A'Range loop Test_Index (I); if End_Of_Seq then Biggest_Seq := Natural'Max (Size_Of_Seq, Biggest_Seq); end if; end loop; return Biggest_Seq; end Size_Of_Biggest_Increasing_Sequence; end Show_Biggest_Increasing_Sequence;

This example is correct. Some of the dependencies, such as Size_Of_Seq depending on Beginning, come directly from the assignments in the subprogram. Since the control flow influences the final value of all of the outputs, the variables that are being read, A, Current_Index, and Max, are present in every dependency relation. Finally, the dependencies of Size_Of_Eq and Beginning on themselves are because they may not be modified by the subprogram execution.

Example #10

The subprogram Identity swaps the value of its parameter two times. Its Depends contract says that the final value of X only depends on its initial value and likewise for Y.

package Show_Swap is procedure Swap (X, Y : in out Positive); procedure Identity (X, Y : in out Positive) with Depends => (X => X, Y => Y); end Show_Swap;
package body Show_Swap is procedure Swap (X, Y : in out Positive) is Tmp : constant Positive := X; begin X := Y; Y := Tmp; end Swap; procedure Identity (X, Y : in out Positive) is begin Swap (X, Y); Swap (Y, X); end Identity; end Show_Swap;

This code is correct, but flow analysis can't verify the Depends contract of Identity because we didn't supply a Depends contract for Swap. Therefore, flow analysis assumes that all outputs of Swap, X and Y, depend on all its inputs, both X and Y's initial values. To prevent this, we should manually specify a Depends contract for Swap.