diff mbox series

[Ada] Proof of Boolean'Image and Boolean'Value

Message ID 20211202162833.GA2156176@adacore.com
State New
Headers show
Series [Ada] Proof of Boolean'Image and Boolean'Value | expand

Commit Message

Pierre-Marie de Rodat Dec. 2, 2021, 4:28 p.m. UTC
This proves the functionality of the runtime support for these
attributes that print and parse a Boolean value.

As a side-effect of this proof, fix a possible range check failure in
System.Val_Bool.Value_Boolean and a possible overflow check failure in
System.Val_Util.Normalize_String.

GNATprove is run with switches --level=2.

Tested on x86_64-pc-linux-gnu, committed on trunk

gcc/ada/

	* libgnat/s-imgboo.adb: Mark in SPARK.
	* libgnat/s-imgboo.ads: Mark in SPARK. Change from Pure to
	Preelaborate unit in order to be able to depend on
	System.Val_Bool.
	(Image_Boolean): Functionally specify the result of the
	procedure by calling System.Val_Bool.Value_Boolean on the
	result.
	* libgnat/s-valboo.adb: Mark in SPARK.
	(First_Non_Space_Ghost): New ghost function.
	(Value_Boolean): Change type of L and F to avoid possible range
	check failure on empty Str.
	* libgnat/s-valboo.ads: Mark in SPARK. Duplicate with-clause
	from body in the spec to be able to call
	System.Val_Util.Only_Space_Ghost in the contract.
	(First_Non_Space_Ghost): New ghost function computing the first
	non-space character in a string.
	(Is_Boolean_Image_Ghost): New ghost function computing whether a
	string is the image of a boolean value.
	(Value_Boolean): Add in precondition the conditions to avoid
	raising Constraint_Error. This precondition is never executed,
	and only used in proof, thanks to the use of pragma
	Assertion_Policy. Given that precondition, the postcondition can
	simply check the first non-space character to decide whether
	True or False is read.
	* libgnat/s-valuti.adb: Mark in SPARK, but use SPARK_Mode Off on
	all subprograms not yet proved.
	(Bad_Value): Annotate expected exception.
	(Normalize_String): Rewrite to avoid possible overflow when
	incrementing F in the first loop. Add loop invariants.
	* libgnat/s-valuti.ads: Mark in SPARK.
	(Bad_Value): Add Depends contract to avoid warning on unused S.
	(Only_Space_Ghost): New ghost function to query if string has
	only space in the specified range.
	(Normalize_String): Add functional contract.
	(Scan_Exponent): Mark spec as not in SPARK as this function has
	side-effects.
diff mbox series

Patch

diff --git a/gcc/ada/libgnat/s-imgboo.adb b/gcc/ada/libgnat/s-imgboo.adb
--- a/gcc/ada/libgnat/s-imgboo.adb
+++ b/gcc/ada/libgnat/s-imgboo.adb
@@ -29,7 +29,17 @@ 
 --                                                                          --
 ------------------------------------------------------------------------------
 
-package body System.Img_Bool is
+--  Ghost code, loop invariants and assertions in this unit are meant for
+--  analysis only, not for run-time checking, as it would be too costly
+--  otherwise. This is enforced by setting the assertion policy to Ignore.
+
+pragma Assertion_Policy (Ghost          => Ignore,
+                         Loop_Invariant => Ignore,
+                         Assert         => Ignore);
+
+package body System.Img_Bool
+  with SPARK_Mode
+is
 
    -------------------
    -- Image_Boolean --


diff --git a/gcc/ada/libgnat/s-imgboo.ads b/gcc/ada/libgnat/s-imgboo.ads
--- a/gcc/ada/libgnat/s-imgboo.ads
+++ b/gcc/ada/libgnat/s-imgboo.ads
@@ -31,13 +31,33 @@ 
 
 --  Boolean'Image
 
-package System.Img_Bool is
-   pragma Pure;
+--  Preconditions in this unit are meant for analysis only, not for run-time
+--  checking, so that the expected exceptions are raised. This is enforced by
+--  setting the corresponding assertion policy to Ignore. Postconditions and
+--  contract cases should not be executed at runtime as well, in order not to
+--  slow down the execution of these functions.
+
+pragma Assertion_Policy (Pre            => Ignore,
+                         Post           => Ignore,
+                         Contract_Cases => Ignore,
+                         Ghost          => Ignore);
+
+with System.Val_Bool;
+
+package System.Img_Bool
+  with SPARK_Mode, Preelaborate
+is
 
    procedure Image_Boolean
      (V : Boolean;
       S : in out String;
-      P : out Natural);
+      P : out Natural)
+   with
+     Pre  => S'First = 1
+       and then (if V then S'Length >= 4 else S'Length >= 5),
+     Post => (if V then P = 4 else P = 5)
+       and then System.Val_Bool.Is_Boolean_Image_Ghost (S (1 .. P))
+       and then System.Val_Bool.Value_Boolean (S (1 .. P)) = V;
    --  Computes Boolean'Image (V) and stores the result in S (1 .. P)
    --  setting the resulting value of P. The caller guarantees that S
    --  is long enough to hold the result, and that S'First is 1.


diff --git a/gcc/ada/libgnat/s-valboo.adb b/gcc/ada/libgnat/s-valboo.adb
--- a/gcc/ada/libgnat/s-valboo.adb
+++ b/gcc/ada/libgnat/s-valboo.adb
@@ -29,22 +29,47 @@ 
 --                                                                          --
 ------------------------------------------------------------------------------
 
+--  Ghost code, loop invariants and assertions in this unit are meant for
+--  analysis only, not for run-time checking, as it would be too costly
+--  otherwise. This is enforced by setting the assertion policy to Ignore.
+
+pragma Assertion_Policy (Ghost          => Ignore,
+                         Loop_Invariant => Ignore,
+                         Assert         => Ignore);
+
 with System.Val_Util; use System.Val_Util;
 
-package body System.Val_Bool is
+package body System.Val_Bool
+  with SPARK_Mode
+is
+
+   function First_Non_Space_Ghost (S : String) return Positive is
+   begin
+      for J in S'Range loop
+         if S (J) /= ' ' then
+            return J;
+         end if;
+
+         pragma Loop_Invariant (for all K in S'First .. J => S (K) = ' ');
+      end loop;
+
+      raise Program_Error;
+   end First_Non_Space_Ghost;
 
    -------------------
    -- Value_Boolean --
    -------------------
 
    function Value_Boolean (Str : String) return Boolean is
-      F : Natural;
-      L : Natural;
+      F : Integer;
+      L : Integer;
       S : String (Str'Range) := Str;
 
    begin
       Normalize_String (S, F, L);
 
+      pragma Assert (F = First_Non_Space_Ghost (S));
+
       if S (F .. L) = "TRUE" then
          return True;
 


diff --git a/gcc/ada/libgnat/s-valboo.ads b/gcc/ada/libgnat/s-valboo.ads
--- a/gcc/ada/libgnat/s-valboo.ads
+++ b/gcc/ada/libgnat/s-valboo.ads
@@ -29,10 +29,70 @@ 
 --                                                                          --
 ------------------------------------------------------------------------------
 
-package System.Val_Bool is
+--  Preconditions in this unit are meant for analysis only, not for run-time
+--  checking, so that the expected exceptions are raised. This is enforced by
+--  setting the corresponding assertion policy to Ignore. Postconditions and
+--  contract cases should not be executed at runtime as well, in order not to
+--  slow down the execution of these functions.
+
+pragma Assertion_Policy (Pre            => Ignore,
+                         Post           => Ignore,
+                         Contract_Cases => Ignore,
+                         Ghost          => Ignore);
+
+with System.Val_Util;
+
+package System.Val_Bool
+  with SPARK_Mode
+is
    pragma Preelaborate;
 
-   function Value_Boolean (Str : String) return Boolean;
+   function First_Non_Space_Ghost (S : String) return Positive
+   with
+     Ghost,
+     Pre  => not System.Val_Util.Only_Space_Ghost (S, S'First, S'Last),
+     Post => First_Non_Space_Ghost'Result in S'Range
+       and then S (First_Non_Space_Ghost'Result) /= ' '
+       and then System.Val_Util.Only_Space_Ghost
+         (S, S'First, First_Non_Space_Ghost'Result - 1);
+   --  Ghost function that returns the index of the first non-space character
+   --  in S, which necessarily exists given the precondition on S.
+
+   function Is_Boolean_Image_Ghost (Str : String) return Boolean is
+     (not System.Val_Util.Only_Space_Ghost (Str, Str'First, Str'Last)
+        and then
+      (declare
+         F : constant Positive := First_Non_Space_Ghost (Str);
+       begin
+         (F <= Str'Last - 3
+          and then Str (F)     in 't' | 'T'
+          and then Str (F + 1) in 'r' | 'R'
+          and then Str (F + 2) in 'u' | 'U'
+          and then Str (F + 3) in 'e' | 'E'
+          and then
+            (if F + 3 < Str'Last then
+               System.Val_Util.Only_Space_Ghost (Str, F + 4, Str'Last)))
+           or else
+         (F <= Str'Last - 4
+          and then Str (F)     in 'f' | 'F'
+          and then Str (F + 1) in 'a' | 'A'
+          and then Str (F + 2) in 'l' | 'L'
+          and then Str (F + 3) in 's' | 'S'
+          and then Str (F + 4) in 'e' | 'E'
+          and then
+            (if F + 4 < Str'Last then
+               System.Val_Util.Only_Space_Ghost (Str, F + 5, Str'Last)))))
+   with
+     Ghost;
+   --  Ghost function that returns True iff Str is the image of a boolean, that
+   --  is "true" or "false" in any capitalization, possibly surounded by space
+   --  characters.
+
+   function Value_Boolean (Str : String) return Boolean
+   with
+     Pre  => Is_Boolean_Image_Ghost (Str),
+     Post =>
+       Value_Boolean'Result = (Str (First_Non_Space_Ghost (Str)) in 't' | 'T');
    --  Computes Boolean'Value (Str)
 
 end System.Val_Bool;


diff --git a/gcc/ada/libgnat/s-valuti.adb b/gcc/ada/libgnat/s-valuti.adb
--- a/gcc/ada/libgnat/s-valuti.adb
+++ b/gcc/ada/libgnat/s-valuti.adb
@@ -29,15 +29,27 @@ 
 --                                                                          --
 ------------------------------------------------------------------------------
 
+--  Ghost code, loop invariants and assertions in this unit are meant for
+--  analysis only, not for run-time checking, as it would be too costly
+--  otherwise. This is enforced by setting the assertion policy to Ignore.
+
+pragma Assertion_Policy (Ghost          => Ignore,
+                         Loop_Invariant => Ignore,
+                         Assert         => Ignore);
+
 with System.Case_Util; use System.Case_Util;
 
-package body System.Val_Util is
+package body System.Val_Util
+  with SPARK_Mode
+is
 
    ---------------
    -- Bad_Value --
    ---------------
 
    procedure Bad_Value (S : String) is
+      pragma Annotate (GNATprove, Intentional, "exception might be raised",
+                       "Intentional exception from Bad_Value");
    begin
       --  Bad_Value might be called with very long strings allocated on the
       --  heap. Limit the size of the message so that we avoid creating a
@@ -62,21 +74,33 @@  package body System.Val_Util is
       F := S'First;
       L := S'Last;
 
+      --  Case of empty string
+
+      if F > L then
+         return;
+      end if;
+
       --  Scan for leading spaces
 
-      while F <= L and then S (F) = ' ' loop
+      while F < L and then S (F) = ' ' loop
+         pragma Loop_Invariant (F in S'First .. L - 1);
+         pragma Loop_Invariant (for all J in S'First .. F => S (J) = ' ');
          F := F + 1;
       end loop;
 
-      --  Case of no nonspace characters found
+      --  Case of no nonspace characters found. Decrease L to ensure L < F
+      --  without risking an overflow if F is Integer'Last.
 
-      if F > L then
+      if S (F) = ' ' then
+         L := L - 1;
          return;
       end if;
 
       --  Scan for trailing spaces
 
       while S (L) = ' ' loop
+         pragma Loop_Invariant (L in F + 1 .. S'Last);
+         pragma Loop_Invariant (for all J in L .. S'Last => S (J) = ' ');
          L := L - 1;
       end loop;
 
@@ -85,6 +109,8 @@  package body System.Val_Util is
       if S (F) /= ''' then
          for J in F .. L loop
             S (J) := To_Upper (S (J));
+            pragma Loop_Invariant
+              (for all K in F .. J => S (K) = To_Upper (S'Loop_Entry (K)));
          end loop;
       end if;
    end Normalize_String;
@@ -98,6 +124,8 @@  package body System.Val_Util is
       Ptr  : not null access Integer;
       Max  : Integer;
       Real : Boolean := False) return Integer
+   with
+     SPARK_Mode => Off  --  Function with side-effect through Ptr
    is
       P : Natural := Ptr.all;
       M : Boolean;
@@ -181,6 +209,8 @@  package body System.Val_Util is
       Ptr   : not null access Integer;
       Max   : Integer;
       Start : out Positive)
+   with
+     SPARK_Mode => Off  --  Not proved yet
    is
       P : Natural := Ptr.all;
 
@@ -226,6 +256,8 @@  package body System.Val_Util is
       Max   : Integer;
       Minus : out Boolean;
       Start : out Positive)
+   with
+     SPARK_Mode => Off  --  Not proved yet
    is
       P : Natural := Ptr.all;
 
@@ -283,7 +315,10 @@  package body System.Val_Util is
    -- Scan_Trailing_Blanks --
    --------------------------
 
-   procedure Scan_Trailing_Blanks (Str : String; P : Positive) is
+   procedure Scan_Trailing_Blanks (Str : String; P : Positive)
+   with
+     SPARK_Mode => Off  --  Not proved yet
+   is
    begin
       for J in P .. Str'Last loop
          if Str (J) /= ' ' then
@@ -302,6 +337,8 @@  package body System.Val_Util is
       Ptr : not null access Integer;
       Max : Integer;
       Ext : Boolean)
+   with
+     SPARK_Mode => Off  --  Not proved yet
    is
       C : Character;
 


diff --git a/gcc/ada/libgnat/s-valuti.ads b/gcc/ada/libgnat/s-valuti.ads
--- a/gcc/ada/libgnat/s-valuti.ads
+++ b/gcc/ada/libgnat/s-valuti.ads
@@ -31,16 +31,60 @@ 
 
 --  This package provides some common utilities used by the s-valxxx files
 
-package System.Val_Util is
-   pragma Pure;
+--  Preconditions in this unit are meant for analysis only, not for run-time
+--  checking, so that the expected exceptions are raised. This is enforced by
+--  setting the corresponding assertion policy to Ignore. Postconditions and
+--  contract cases should not be executed at runtime as well, in order not to
+--  slow down the execution of these functions.
 
-   procedure Bad_Value (S : String);
+pragma Assertion_Policy (Pre            => Ignore,
+                         Post           => Ignore,
+                         Contract_Cases => Ignore,
+                         Ghost          => Ignore);
+
+with System.Case_Util;
+
+package System.Val_Util
+  with SPARK_Mode, Pure
+is
+
+   procedure Bad_Value (S : String)
+   with
+     Depends => (null => S);
    pragma No_Return (Bad_Value);
    --  Raises constraint error with message: bad input for 'Value: "xxx"
 
+   function Only_Space_Ghost (S : String; From, To : Integer) return Boolean is
+      (for all J in From .. To => S (J) = ' ')
+   with
+     Ghost,
+     Pre => From > To or else (From >= S'First and then To <= S'Last);
+   --  Ghost function that returns True if S has only space characters from
+   --  index From to index To.
+
    procedure Normalize_String
      (S    : in out String;
-      F, L : out Integer);
+      F, L : out Integer)
+   with
+     Post => (if Only_Space_Ghost (S'Old, S'First, S'Last) then
+                F > L
+              else
+                F >= S'First
+                  and then L <= S'Last
+                  and then F <= L
+                  and then Only_Space_Ghost (S'Old, S'First, F - 1)
+                  and then S'Old (F) /= ' '
+                  and then S'Old (L) /= ' '
+                  and then
+                    (if L < S'Last then
+                      Only_Space_Ghost (S'Old, L + 1, S'Last))
+                  and then
+                    (if S'Old (F) /= ''' then
+                      (for all J in S'Range =>
+                        (if J in F .. L then
+                           S (J) = System.Case_Util.To_Upper (S'Old (J))
+                         else
+                           S (J) = S'Old (J)))));
    --  This procedure scans the string S setting F to be the index of the first
    --  non-blank character of S and L to be the index of the last non-blank
    --  character of S. Any lower case characters present in S will be folded to
@@ -85,7 +129,9 @@  package System.Val_Util is
      (Str  : String;
       Ptr  : not null access Integer;
       Max  : Integer;
-      Real : Boolean := False) return Integer;
+      Real : Boolean := False) return Integer
+   with
+     SPARK_Mode => Off;  --  Function with side-effect through Ptr
    --  Called to scan a possible exponent. Str, Ptr, Max are as described above
    --  for Scan_Sign. If Ptr.all < Max and Str (Ptr.all) = 'E' or 'e', then an
    --  exponent is scanned out, with the exponent value returned in Exp, and