[Ada] Proof of System.Val_Util utilities for 'Value support

Message ID 20211202162835.GA2156201@adacore.com
State Committed
Commit 40b180995ab1b2b6748d5eb217d8fbfd21b4a51b
Headers
Series [Ada] Proof of System.Val_Util utilities for 'Value support |

Commit Message

Pierre-Marie de Rodat Dec. 2, 2021, 4:28 p.m. UTC
  This proves the functionality of the utility procedures supporting the
implementation of 'Value attributes.

As a side-effect of this proof, fix possible range check failures.

GNATprove is run with switch --level=3.

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

gcc/ada/

	* libgnat/s-valboo.adb (First_Non_Space_Ghost): Move to
	utilities.
	(Value_Boolean): Prefix call to First_Non_Space_Ghost.
	* libgnat/s-valboo.ads (First_Non_Space_Ghost): Move to
	utilities.
	(Is_Boolean_Image_Ghost, Value_Boolean): Prefix call to
	First_Non_Space_Ghost.
	* libgnat/s-valuer.adb (Scan_Raw_Real): Adapt to change of
	function Scan_Exponent to procedure.
	* libgnat/s-valueu.adb (Scan_Raw_Unsigned): Adapt to change of
	function Scan_Exponent to procedure.
	* libgnat/s-valuti.adb (First_Non_Space_Ghost): Function moved
	here.
	(Last_Number_Ghost): New ghost query function.
	(Scan_Exponent): Change function with side-effects into
	procedure, to mark in SPARK. Prove procedure wrt contract.
	Change type of local P to avoid possible range check failure (it
	is not known whether this can be activated by callers).
	(Scan_Plus_Sign, Scan_Sign): Change type of local P to avoid
	possible range check failure. Add loop invariants and assertions
	for proof.
	(Scan_Trailing_Blanks): Add loop invariant.
	(Scan_Underscore): Remove SPARK_Mode Off.
	* libgnat/s-valuti.ads (First_Non_Space_Ghost): Function moved
	here.
	(Last_Number_Ghost, Only_Number_Ghost, Is_Natural_Format_Ghost,
	Scan_Natural_Ghost): New ghost query functions.
	(Scan_Plus_Sign, Scan_Sign, Scan_Exponent, Scan_Trailing_Blanks,
	Scan_Underscore): Add functional contracts.
  

Patch

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
@@ -43,19 +43,6 @@  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 --
    -------------------
@@ -68,7 +55,7 @@  is
    begin
       Normalize_String (S, F, L);
 
-      pragma Assert (F = First_Non_Space_Ghost (S));
+      pragma Assert (F = System.Val_Util.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
@@ -47,22 +47,11 @@  package System.Val_Bool
 is
    pragma Preelaborate;
 
-   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);
+         F : constant Positive := System.Val_Util.First_Non_Space_Ghost (Str);
        begin
          (F <= Str'Last - 3
           and then Str (F)     in 't' | 'T'
@@ -92,7 +81,8 @@  is
    with
      Pre  => Is_Boolean_Image_Ghost (Str),
      Post =>
-       Value_Boolean'Result = (Str (First_Non_Space_Ghost (Str)) in 't' | 'T');
+       Value_Boolean'Result =
+         (Str (System.Val_Util.First_Non_Space_Ghost (Str)) in 't' | 'T');
    --  Computes Boolean'Value (Str)
 
 end System.Val_Bool;


diff --git a/gcc/ada/libgnat/s-valuer.adb b/gcc/ada/libgnat/s-valuer.adb
--- a/gcc/ada/libgnat/s-valuer.adb
+++ b/gcc/ada/libgnat/s-valuer.adb
@@ -511,6 +511,8 @@  package body System.Value_R is
       Value : Uns;
       --  Mantissa as an Integer
 
+      Expon : Integer;
+
    begin
       --  The default base is 10
 
@@ -643,7 +645,8 @@  package body System.Value_R is
       --  Update pointer and scan exponent
 
       Ptr.all := Index;
-      Scale := Scale + Scan_Exponent (Str, Ptr, Max, Real => True);
+      Scan_Exponent (Str, Ptr, Max, Expon, Real => True);
+      Scale := Scale + Expon;
 
       --  Here is where we check for a bad based number
 


diff --git a/gcc/ada/libgnat/s-valueu.adb b/gcc/ada/libgnat/s-valueu.adb
--- a/gcc/ada/libgnat/s-valueu.adb
+++ b/gcc/ada/libgnat/s-valueu.adb
@@ -234,7 +234,7 @@  package body System.Value_U is
       --  Come here with scanned unsigned value in Uval. The only remaining
       --  required step is to deal with exponent if one is present.
 
-      Expon := Scan_Exponent (Str, Ptr, Max);
+      Scan_Exponent (Str, Ptr, Max, Expon);
 
       if Expon /= 0 and then Uval /= 0 then
 


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
@@ -62,6 +62,41 @@  is
       end if;
    end Bad_Value;
 
+   ---------------------------
+   -- First_Non_Space_Ghost --
+   ---------------------------
+
+   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;
+
+   -----------------------
+   -- Last_Number_Ghost --
+   -----------------------
+
+   function Last_Number_Ghost (Str : String) return Positive is
+   begin
+      for J in Str'Range loop
+         if Str (J) not in '0' .. '9' | '_' then
+            return J - 1;
+         end if;
+
+         pragma Loop_Invariant
+           (for all K in Str'First .. J => Str (K) in '0' .. '9' | '_');
+      end loop;
+
+      return Str'Last;
+   end Last_Number_Ghost;
+
    ----------------------
    -- Normalize_String --
    ----------------------
@@ -119,15 +154,14 @@  is
    -- Scan_Exponent --
    -------------------
 
-   function Scan_Exponent
+   procedure Scan_Exponent
      (Str  : String;
       Ptr  : not null access Integer;
       Max  : Integer;
-      Real : Boolean := False) return Integer
-   with
-     SPARK_Mode => Off  --  Function with side-effect through Ptr
+      Exp  : out Integer;
+      Real : Boolean := False)
    is
-      P : Natural := Ptr.all;
+      P : Integer := Ptr.all;
       M : Boolean;
       X : Integer;
 
@@ -135,7 +169,8 @@  is
       if P >= Max
         or else (Str (P) /= 'E' and then Str (P) /= 'e')
       then
-         return 0;
+         Exp := 0;
+         return;
       end if;
 
       --  We have an E/e, see if sign follows
@@ -146,7 +181,8 @@  is
          P := P + 1;
 
          if P > Max then
-            return 0;
+            Exp := 0;
+            return;
          else
             M := False;
          end if;
@@ -155,7 +191,8 @@  is
          P := P + 1;
 
          if P > Max or else not Real then
-            return 0;
+            Exp := 0;
+            return;
          else
             M := True;
          end if;
@@ -165,7 +202,8 @@  is
       end if;
 
       if Str (P) not in '0' .. '9' then
-         return 0;
+         Exp := 0;
+         return;
       end if;
 
       --  Scan out the exponent value as an unsigned integer. Values larger
@@ -176,28 +214,52 @@  is
 
       X := 0;
 
-      loop
-         if X < (Integer'Last / 10) then
-            X := X * 10 + (Character'Pos (Str (P)) - Character'Pos ('0'));
-         end if;
+      declare
+         Rest : constant String := Str (P .. Max) with Ghost;
+         Last : constant Natural := Last_Number_Ghost (Rest) with Ghost;
 
-         P := P + 1;
+      begin
+         pragma Assert (Is_Natural_Format_Ghost (Rest));
 
-         exit when P > Max;
+         loop
+            pragma Assert (Str (P) = Rest (P));
+            pragma Assert (Str (P) in '0' .. '9');
 
-         if Str (P) = '_' then
-            Scan_Underscore (Str, P, Ptr, Max, False);
-         else
-            exit when Str (P) not in '0' .. '9';
-         end if;
-      end loop;
+            if X < (Integer'Last / 10) then
+               X := X * 10 + (Character'Pos (Str (P)) - Character'Pos ('0'));
+            end if;
+
+            pragma Loop_Invariant (X >= 0);
+            pragma Loop_Invariant (P in P'Loop_Entry .. Last);
+            pragma Loop_Invariant (Str (P) in '0' .. '9');
+            pragma Loop_Invariant
+              (Scan_Natural_Ghost (Rest, P'Loop_Entry, 0)
+               = (if P = Max
+                    or else Rest (P + 1) not in '0' .. '9' | '_'
+                    or else X >= Integer'Last / 10
+                  then
+                    X
+                  else
+                    Scan_Natural_Ghost (Rest, P + 1, X)));
+
+            P := P + 1;
+
+            exit when P > Max;
+
+            if Str (P) = '_' then
+               Scan_Underscore (Str, P, Ptr, Max, False);
+            else
+               exit when Str (P) not in '0' .. '9';
+            end if;
+         end loop;
+      end;
 
       if M then
          X := -X;
       end if;
 
       Ptr.all := P;
-      return X;
+      Exp := X;
    end Scan_Exponent;
 
    --------------------
@@ -209,10 +271,8 @@  is
       Ptr   : not null access Integer;
       Max   : Integer;
       Start : out Positive)
-   with
-     SPARK_Mode => Off  --  Not proved yet
    is
-      P : Natural := Ptr.all;
+      P : Integer := Ptr.all;
 
    begin
       if P > Max then
@@ -224,6 +284,12 @@  is
       while Str (P) = ' ' loop
          P := P + 1;
 
+         pragma Loop_Invariant (Ptr.all = Ptr.all'Loop_Entry);
+         pragma Loop_Invariant (P in Ptr.all .. Max);
+         pragma Loop_Invariant (for some J in P .. Max => Str (J) /= ' ');
+         pragma Loop_Invariant
+           (for all J in Ptr.all .. P - 1 => Str (J) = ' ');
+
          if P > Max then
             Ptr.all := P;
             Bad_Value (Str);
@@ -232,6 +298,8 @@  is
 
       Start := P;
 
+      pragma Assert (Start = First_Non_Space_Ghost (Str (Ptr.all .. Max)));
+
       --  Skip past an initial plus sign
 
       if Str (P) = '+' then
@@ -256,10 +324,8 @@  is
       Max   : Integer;
       Minus : out Boolean;
       Start : out Positive)
-   with
-     SPARK_Mode => Off  --  Not proved yet
    is
-      P : Natural := Ptr.all;
+      P : Integer := Ptr.all;
 
    begin
       --  Deal with case of null string (all blanks). As per spec, we raise
@@ -274,6 +340,12 @@  is
       while Str (P) = ' ' loop
          P := P + 1;
 
+         pragma Loop_Invariant (Ptr.all = Ptr.all'Loop_Entry);
+         pragma Loop_Invariant (P in Ptr.all .. Max);
+         pragma Loop_Invariant (for some J in P .. Max => Str (J) /= ' ');
+         pragma Loop_Invariant
+           (for all J in Ptr.all .. P - 1 => Str (J) = ' ');
+
          if P > Max then
             Ptr.all := P;
             Bad_Value (Str);
@@ -282,6 +354,8 @@  is
 
       Start := P;
 
+      pragma Assert (Start = First_Non_Space_Ghost (Str (Ptr.all .. Max)));
+
       --  Remember an initial minus sign
 
       if Str (P) = '-' then
@@ -315,15 +389,14 @@  is
    -- Scan_Trailing_Blanks --
    --------------------------
 
-   procedure Scan_Trailing_Blanks (Str : String; P : Positive)
-   with
-     SPARK_Mode => Off  --  Not proved yet
-   is
+   procedure Scan_Trailing_Blanks (Str : String; P : Positive) is
    begin
       for J in P .. Str'Last loop
          if Str (J) /= ' ' then
             Bad_Value (Str);
          end if;
+
+         pragma Loop_Invariant (for all K in P .. J => Str (K) = ' ');
       end loop;
    end Scan_Trailing_Blanks;
 
@@ -337,8 +410,6 @@  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
@@ -47,6 +47,7 @@  with System.Case_Util;
 package System.Val_Util
   with SPARK_Mode, Pure
 is
+   pragma Unevaluated_Use_Of_Old (Allow);
 
    procedure Bad_Value (S : String)
    with
@@ -62,6 +63,17 @@  is
    --  Ghost function that returns True if S has only space characters from
    --  index From to index To.
 
+   function First_Non_Space_Ghost (S : String) return Positive
+   with
+     Ghost,
+     Pre  => not 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 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.
+
    procedure Normalize_String
      (S    : in out String;
       F, L : out Integer)
@@ -96,7 +108,27 @@  is
       Ptr   : not null access Integer;
       Max   : Integer;
       Minus : out Boolean;
-      Start : out Positive);
+      Start : out Positive)
+   with
+     Pre  =>
+       --  Ptr.all .. Max is either an empty range, or a valid range in Str
+       (Ptr.all > Max or else (Ptr.all >= Str'First and then Max <= Str'Last))
+       and then not Only_Space_Ghost (Str, Ptr.all, Max)
+       and then
+         (declare
+            F : constant Positive :=
+              First_Non_Space_Ghost (Str (Ptr.all .. Max));
+          begin
+            (if Str (F) in '+' | '-' then
+               F <= Max - 1 and then Str (F + 1) /= ' ')),
+     Post =>
+       (declare
+          F : constant Positive :=
+            First_Non_Space_Ghost (Str (Ptr.all'Old .. Max));
+        begin
+          Minus = (Str (F) = '-')
+            and then Ptr.all = (if Str (F) in '+' | '-' then F + 1 else F)
+            and then Start = F);
    --  The Str, Ptr, Max parameters are as for the scan routines (Str is the
    --  string to be scanned starting at Ptr.all, and Max is the index of the
    --  last character in the string). Scan_Sign first scans out any initial
@@ -121,17 +153,150 @@  is
      (Str   : String;
       Ptr   : not null access Integer;
       Max   : Integer;
-      Start : out Positive);
+      Start : out Positive)
+   with
+     Pre  =>
+       --  Ptr.all .. Max is either an empty range, or a valid range in Str
+       (Ptr.all > Max or else (Ptr.all >= Str'First and then Max <= Str'Last))
+       and then not Only_Space_Ghost (Str, Ptr.all, Max)
+       and then
+         (declare
+            F : constant Positive :=
+              First_Non_Space_Ghost (Str (Ptr.all .. Max));
+          begin
+            (if Str (F) = '+' then
+               F <= Max - 1 and then Str (F + 1) /= ' ')),
+     Post =>
+       (declare
+          F : constant Positive :=
+            First_Non_Space_Ghost (Str (Ptr.all'Old .. Max));
+        begin
+          Ptr.all = (if Str (F) = '+' then F + 1 else F)
+            and then Start = F);
    --  Same as Scan_Sign, but allows only plus, not minus. This is used for
    --  modular types.
 
-   function Scan_Exponent
+   function Only_Number_Ghost (Str : String; From, To : Integer) return Boolean
+   is
+      (for all J in From .. To => Str (J) in '0' .. '9' | '_')
+   with
+     Ghost,
+     Pre => From > To or else (From >= Str'First and then To <= Str'Last);
+   --  Ghost function that returns True if S has only number characters from
+   --  index From to index To.
+
+   function Last_Number_Ghost (Str : String) return Positive
+   with
+     Ghost,
+     Pre  => Str /= "" and then Str (Str'First) in '0' .. '9',
+     Post => Last_Number_Ghost'Result in Str'Range
+       and then (if Last_Number_Ghost'Result < Str'Last then
+                   Str (Last_Number_Ghost'Result + 1) not in '0' .. '9' | '_')
+       and then Only_Number_Ghost (Str, Str'First, Last_Number_Ghost'Result);
+   --  Ghost function that returns the index of the last character in S that
+   --  is either a figure or underscore, which necessarily exists given the
+   --  precondition on S.
+
+   function Is_Natural_Format_Ghost (Str : String) return Boolean is
+     (Str /= ""
+        and then Str (Str'First) in '0' .. '9'
+        and then
+        (declare
+           L : constant Positive := Last_Number_Ghost (Str);
+         begin
+           Str (L) in '0' .. '9'
+             and then (for all J in Str'First .. L =>
+                         (if Str (J) = '_' then Str (J + 1) /= '_'))))
+   with
+     Ghost;
+   --  Ghost function that determines if Str has the correct format for a
+   --  natural number, consisting in a sequence of figures possibly separated
+   --  by single underscores. It may be followed by other characters.
+
+   function Scan_Natural_Ghost
+     (Str : String;
+      P   : Natural;
+      Acc : Natural)
+      return Natural
+   is
+     (if Str (P) = '_' then
+        Scan_Natural_Ghost (Str, P + 1, Acc)
+      else
+        (declare
+           Shift_Acc : constant Natural :=
+             Acc * 10 + (Character'Pos (Str (P)) - Character'Pos ('0'));
+         begin
+           (if P = Str'Last
+              or else Str (P + 1) not in '0' .. '9' | '_'
+              or else Shift_Acc >= Integer'Last / 10
+            then
+              Shift_Acc
+            else
+              Scan_Natural_Ghost (Str, P + 1, Shift_Acc))))
+   with
+     Ghost,
+     Subprogram_Variant => (Increases => P),
+     Pre => Is_Natural_Format_Ghost (Str)
+       and then P in Str'First .. Last_Number_Ghost (Str)
+       and then Acc < Integer'Last / 10;
+   --  Ghost function that recursively computes the natural number in Str, up
+   --  to the first number greater or equal to Natural'Last / 10, assuming Acc
+   --  has been scanned already and scanning continues at index P.
+
+   procedure Scan_Exponent
      (Str  : String;
       Ptr  : not null access Integer;
       Max  : Integer;
-      Real : Boolean := False) return Integer
+      Exp  : out Integer;
+      Real : Boolean := False)
    with
-     SPARK_Mode => Off;  --  Function with side-effect through Ptr
+     Pre =>
+       --  Ptr.all .. Max is either an empty range, or a valid range in Str
+       (Ptr.all > Max or else (Ptr.all >= Str'First and then Max <= Str'Last))
+         and then
+       Max < Natural'Last
+         and then
+       (if Ptr.all < Max and then Str (Ptr.all) in 'E' | 'e' then
+          (declare
+             Plus_Sign  : constant Boolean := Str (Ptr.all + 1) = '+';
+             Minus_Sign : constant Boolean := Str (Ptr.all + 1) = '-';
+             Sign       : constant Boolean := Plus_Sign or Minus_Sign;
+           begin
+             (if Minus_Sign and not Real then True
+              elsif Sign
+                and then (Ptr.all > Max - 2
+                            or else Str (Ptr.all + 2) not in '0' .. '9')
+              then True
+              else
+                (declare
+                   Start : constant Natural :=
+                     (if Sign then Ptr.all + 2 else Ptr.all + 1);
+                 begin
+                   Is_Natural_Format_Ghost (Str (Start .. Max)))))),
+     Post =>
+       (if Ptr.all'Old < Max and then Str (Ptr.all'Old) in 'E' | 'e' then
+          (declare
+             Plus_Sign  : constant Boolean := Str (Ptr.all'Old + 1) = '+';
+             Minus_Sign : constant Boolean := Str (Ptr.all'Old + 1) = '-';
+             Sign       : constant Boolean := Plus_Sign or Minus_Sign;
+             Unchanged  : constant Boolean :=
+               Exp = 0 and Ptr.all = Ptr.all'Old;
+           begin
+             (if Minus_Sign and not Real then Unchanged
+              elsif Sign
+                and then (Ptr.all'Old > Max - 2
+                            or else Str (Ptr.all'Old + 2) not in '0' .. '9')
+              then Unchanged
+              else
+                (declare
+                   Start : constant Natural :=
+                     (if Sign then Ptr.all'Old + 2 else Ptr.all'Old + 1);
+                   Value : constant Natural :=
+                     Scan_Natural_Ghost (Str (Start .. Max), Start, 0);
+                 begin
+                   Exp = (if Minus_Sign then -Value else Value))))
+        else
+          Exp = 0 and Ptr.all = Ptr.all'Old);
    --  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
@@ -146,18 +311,37 @@  is
    --  This routine must not be called with Str'Last = Positive'Last. There is
    --  no check for this case, the caller must ensure this condition is met.
 
-   procedure Scan_Trailing_Blanks (Str : String; P : Positive);
+   procedure Scan_Trailing_Blanks (Str : String; P : Positive)
+   with
+     Pre => P >= Str'First
+       and then Only_Space_Ghost (Str, P, Str'Last);
    --  Checks that the remainder of the field Str (P .. Str'Last) is all
    --  blanks. Raises Constraint_Error if a non-blank character is found.
 
+   pragma Warnings
+     (GNATprove, Off, """Ptr"" is not modified",
+      Reason => "Ptr is actually modified when raising an exception");
    procedure Scan_Underscore
      (Str : String;
       P   : in out Natural;
       Ptr : not null access Integer;
       Max : Integer;
-      Ext : Boolean);
+      Ext : Boolean)
+   with
+     Pre  => P in Str'Range
+       and then Str (P) = '_'
+       and then Max in Str'Range
+       and then P < Max
+       and then
+         (if Ext then
+            Str (P + 1) in '0' .. '9' | 'A' .. 'F' | 'a' .. 'f'
+          else
+            Str (P + 1) in '0' .. '9'),
+     Post =>
+       P = P'Old + 1
+         and then Ptr.all = Ptr.all;
    --  Called if an underscore is encountered while scanning digits. Str (P)
-   --  contains the underscore. Ptr it the pointer to be returned to the
+   --  contains the underscore. Ptr is the pointer to be returned to the
    --  ultimate caller of the scan routine, Max is the maximum subscript in
    --  Str, and Ext indicates if extended digits are allowed. In the case
    --  where the underscore is invalid, Constraint_Error is raised with Ptr
@@ -166,5 +350,6 @@  is
    --
    --  This routine must not be called with Str'Last = Positive'Last. There is
    --  no check for this case, the caller must ensure this condition is met.
+   pragma Warnings (GNATprove, On, """Ptr"" is not modified");
 
 end System.Val_Util;