------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--              L I B . X R E F . S P A R K _ S P E C I F I C               --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 2011-2016, Free Software Foundation, Inc.         --
--                                                                          --
-- GNAT is free software;  you can  redistribute it  and/or modify it under --
-- terms of the  GNU General Public License as published  by the Free Soft- --
-- ware  Foundation;  either version 3,  or (at your option) any later ver- --
-- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
-- for  more details.  You should have  received  a copy of the GNU General --
-- Public License  distributed with GNAT; see file COPYING3.  If not, go to --
-- http://www.gnu.org/licenses for a complete copy of the license.          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

with Einfo;       use Einfo;
with Nmake;       use Nmake;
with SPARK_Xrefs; use SPARK_Xrefs;

with GNAT.HTable;

separate (Lib.Xref)
package body SPARK_Specific is

   ---------------------
   -- Local Constants --
   ---------------------

   --  Table of SPARK_Entities, True for each entity kind used in SPARK

   SPARK_Entities : constant array (Entity_Kind) of Boolean :=
     (E_Constant         => True,
      E_Entry            => True,
      E_Function         => True,
      E_In_Out_Parameter => True,
      E_In_Parameter     => True,
      E_Loop_Parameter   => True,
      E_Operator         => True,
      E_Out_Parameter    => True,
      E_Procedure        => True,
      E_Variable         => True,
      others             => False);

   --  True for each reference type used in SPARK

   SPARK_References : constant array (Character) of Boolean :=
     ('m'    => True,
      'r'    => True,
      's'    => True,
      others => False);

   type Entity_Hashed_Range is range 0 .. 255;
   --  Size of hash table headers

   ---------------------
   -- Local Variables --
   ---------------------

   Heap : Entity_Id := Empty;
   --  A special entity which denotes the heap object

   package Drefs is new Table.Table (
     Table_Component_Type => Xref_Entry,
     Table_Index_Type     => Xref_Entry_Number,
     Table_Low_Bound      => 1,
     Table_Initial        => Alloc.Drefs_Initial,
     Table_Increment      => Alloc.Drefs_Increment,
     Table_Name           => "Drefs");
   --  Table of cross-references for reads and writes through explicit
   --  dereferences, that are output as reads/writes to the special variable
   --  "Heap". These references are added to the regular references when
   --  computing SPARK cross-references.

   -----------------------
   -- Local Subprograms --
   -----------------------

   procedure Add_SPARK_File (Uspec, Ubody : Unit_Number_Type; Dspec : Nat);
   --  Add file and corresponding scopes for unit to the tables
   --  SPARK_File_Table and SPARK_Scope_Table. When two units are present
   --  for the same compilation unit, as it happens for library-level
   --  instantiations of generics, then Ubody is the number of the body
   --  unit; otherwise it is No_Unit.

   procedure Add_SPARK_Xrefs;
   --  Filter table Xrefs to add all references used in SPARK to the table
   --  SPARK_Xref_Table.

   function Entity_Hash (E : Entity_Id) return Entity_Hashed_Range;
   --  Hash function for hash table

   --------------------
   -- Add_SPARK_File --
   --------------------

   procedure Add_SPARK_File (Uspec, Ubody : Unit_Number_Type; Dspec : Nat) is
      File : constant Source_File_Index := Source_Index (Uspec);
      From : constant Scope_Index       := SPARK_Scope_Table.Last + 1;

      Scope_Id : Pos := 1;

      procedure Add_SPARK_Scope (N : Node_Id);
      --  Add scope N to the table SPARK_Scope_Table

      procedure Detect_And_Add_SPARK_Scope (N : Node_Id);
      --  Call Add_SPARK_Scope on scopes

      ---------------------
      -- Add_SPARK_Scope --
      ---------------------

      procedure Add_SPARK_Scope (N : Node_Id) is
         E   : constant Entity_Id  := Defining_Entity (N);
         Loc : constant Source_Ptr := Sloc (E);

         --  The character describing the kind of scope is chosen to be the
         --  same as the one describing the corresponding entity in cross
         --  references, see Xref_Entity_Letters in lib-xrefs.ads

         Typ : Character;

      begin
         --  Ignore scopes without a proper location

         if Sloc (N) = No_Location then
            return;
         end if;

         case Ekind (E) is
            when E_Entry
               | E_Entry_Family
               | E_Generic_Function
               | E_Generic_Package
               | E_Generic_Procedure
               | E_Package
               | E_Protected_Type
               | E_Task_Type
            =>
               Typ := Xref_Entity_Letters (Ekind (E));

            when E_Function
               | E_Procedure
            =>
               --  In SPARK we need to distinguish protected functions and
               --  procedures from ordinary subprograms, but there are no
               --  special Xref letters for them. Since this distiction is
               --  only needed to detect protected calls, we pretend that
               --  such calls are entry calls.

               if Ekind (Scope (E)) = E_Protected_Type then
                  Typ := Xref_Entity_Letters (E_Entry);
               else
                  Typ := Xref_Entity_Letters (Ekind (E));
               end if;

            when E_Package_Body
               | E_Protected_Body
               | E_Subprogram_Body
               | E_Task_Body
            =>
               Typ := Xref_Entity_Letters (Ekind (Unique_Entity (E)));

            when E_Void =>

               --  Compilation of prj-attr.adb with -gnatn creates a node with
               --  entity E_Void for the package defined at a-charac.ads16:13.
               --  ??? TBD

               return;

            when others =>
               raise Program_Error;
         end case;

         --  File_Num and Scope_Num are filled later. From_Xref and To_Xref
         --  are filled even later, but are initialized to represent an empty
         --  range.

         SPARK_Scope_Table.Append
           ((Scope_Name     => new String'(Unique_Name (E)),
             File_Num       => Dspec,
             Scope_Num      => Scope_Id,
             Spec_File_Num  => 0,
             Spec_Scope_Num => 0,
             Line           => Nat (Get_Logical_Line_Number (Loc)),
             Stype          => Typ,
             Col            => Nat (Get_Column_Number (Loc)),
             From_Xref      => 1,
             To_Xref        => 0,
             Scope_Entity   => E));

         Scope_Id := Scope_Id + 1;
      end Add_SPARK_Scope;

      --------------------------------
      -- Detect_And_Add_SPARK_Scope --
      --------------------------------

      procedure Detect_And_Add_SPARK_Scope (N : Node_Id) is
      begin
         --  Entries

         if Nkind_In (N, N_Entry_Body, N_Entry_Declaration)

           --  Packages

           or else Nkind_In (N, N_Package_Body,
                                N_Package_Body_Stub,
                                N_Package_Declaration)
           --  Protected units

           or else Nkind_In (N, N_Protected_Body,
                                N_Protected_Body_Stub,
                                N_Protected_Type_Declaration)

           --  Subprograms

           or else Nkind_In (N, N_Subprogram_Body,
                                N_Subprogram_Body_Stub,
                                N_Subprogram_Declaration)

           --  Task units

           or else Nkind_In (N, N_Task_Body,
                                N_Task_Body_Stub,
                                N_Task_Type_Declaration)
         then
            Add_SPARK_Scope (N);
         end if;
      end Detect_And_Add_SPARK_Scope;

      procedure Traverse_Scopes is new
        Traverse_Compilation_Unit (Detect_And_Add_SPARK_Scope);

      --  Local variables

      File_Name      : String_Ptr;
      Unit_File_Name : String_Ptr;

   --  Start of processing for Add_SPARK_File

   begin
      --  Source file could be inexistant as a result of an error, if option
      --  gnatQ is used.

      if File = No_Source_File then
         return;
      end if;

      --  Subunits are traversed as part of the top-level unit to which they
      --  belong.

      if Nkind (Unit (Cunit (Uspec))) = N_Subunit then
         return;
      end if;

      Traverse_Scopes (CU => Cunit (Uspec), Inside_Stubs => True);

      --  When two units are present for the same compilation unit, as it
      --  happens for library-level instantiations of generics, then add all
      --  scopes to the same SPARK file.

      if Ubody /= No_Unit then
         Traverse_Scopes (CU => Cunit (Ubody), Inside_Stubs => True);
      end if;

      --  Make entry for new file in file table

      Get_Name_String (Reference_Name (File));
      File_Name := new String'(Name_Buffer (1 .. Name_Len));

      --  For subunits, also retrieve the file name of the unit. Only do so if
      --  unit has an associated compilation unit.

      if Present (Cunit (Unit (File)))
        and then Nkind (Unit (Cunit (Unit (File)))) = N_Subunit
      then
         Get_Name_String (Reference_Name (Main_Source_File));
         Unit_File_Name := new String'(Name_Buffer (1 .. Name_Len));
      else
         Unit_File_Name := null;
      end if;

      SPARK_File_Table.Append (
        (File_Name      => File_Name,
         Unit_File_Name => Unit_File_Name,
         File_Num       => Dspec,
         From_Scope     => From,
         To_Scope       => SPARK_Scope_Table.Last));
   end Add_SPARK_File;

   ---------------------
   -- Add_SPARK_Xrefs --
   ---------------------

   procedure Add_SPARK_Xrefs is
      function Entity_Of_Scope (S : Scope_Index) return Entity_Id;
      --  Return the entity which maps to the input scope index

      function Get_Entity_Type (E : Entity_Id) return Character;
      --  Return a character representing the type of entity

      function Get_Scope_Num (N : Entity_Id) return Nat;
      --  Return the scope number associated to entity N

      function Is_Constant_Object_Without_Variable_Input
        (E : Entity_Id) return Boolean;
      --  Return True if E is known to have no variable input, as defined in
      --  SPARK RM.

      function Is_Future_Scope_Entity
        (E : Entity_Id;
         S : Scope_Index) return Boolean;
      --  Check whether entity E is in SPARK_Scope_Table at index S or higher

      function Is_SPARK_Reference
        (E   : Entity_Id;
         Typ : Character) return Boolean;
      --  Return whether entity reference E meets SPARK requirements. Typ is
      --  the reference type.

      function Is_SPARK_Scope (E : Entity_Id) return Boolean;
      --  Return whether the entity or reference scope meets requirements for
      --  being a SPARK scope.

      function Lt (Op1 : Natural; Op2 : Natural) return Boolean;
      --  Comparison function for Sort call

      procedure Move (From : Natural; To : Natural);
      --  Move procedure for Sort call

      procedure Set_Scope_Num (N : Entity_Id; Num : Nat);
      --  Associate entity N to scope number Num

      procedure Update_Scope_Range
        (S    : Scope_Index;
         From : Xref_Index;
         To   : Xref_Index);
      --  Update the scope which maps to S with the new range From .. To

      package Sorting is new GNAT.Heap_Sort_G (Move, Lt);

      No_Scope : constant Nat := 0;
      --  Initial scope counter

      type Scope_Rec is record
         Num    : Nat;
         Entity : Entity_Id;
      end record;
      --  Type used to relate an entity and a scope number

      package Scopes is new GNAT.HTable.Simple_HTable
        (Header_Num => Entity_Hashed_Range,
         Element    => Scope_Rec,
         No_Element => (Num => No_Scope, Entity => Empty),
         Key        => Entity_Id,
         Hash       => Entity_Hash,
         Equal      => "=");
      --  Package used to build a correspondence between entities and scope
      --  numbers used in SPARK cross references.

      Nrefs : Nat := Xrefs.Last;
      --  Number of references in table. This value may get reset (reduced)
      --  when we eliminate duplicate reference entries as well as references
      --  not suitable for local cross-references.

      Nrefs_Add : constant Nat := Drefs.Last;
      --  Number of additional references which correspond to dereferences in
      --  the source code.

      Rnums : array (0 .. Nrefs + Nrefs_Add) of Nat;
      --  This array contains numbers of references in the Xrefs table. This
      --  list is sorted in output order. The extra 0'th entry is convenient
      --  for the call to sort. When we sort the table, we move the indices in
      --  Rnums around, but we do not move the original table entries.

      ---------------------
      -- Entity_Of_Scope --
      ---------------------

      function Entity_Of_Scope (S : Scope_Index) return Entity_Id is
      begin
         return SPARK_Scope_Table.Table (S).Scope_Entity;
      end Entity_Of_Scope;

      ---------------------
      -- Get_Entity_Type --
      ---------------------

      function Get_Entity_Type (E : Entity_Id) return Character is
      begin
         case Ekind (E) is
            when E_Out_Parameter    => return '<';
            when E_In_Out_Parameter => return '=';
            when E_In_Parameter     => return '>';
            when others             => return '*';
         end case;
      end Get_Entity_Type;

      -------------------
      -- Get_Scope_Num --
      -------------------

      function Get_Scope_Num (N : Entity_Id) return Nat is
      begin
         return Scopes.Get (N).Num;
      end Get_Scope_Num;

      -----------------------------------------------
      -- Is_Constant_Object_Without_Variable_Input --
      -----------------------------------------------

      function Is_Constant_Object_Without_Variable_Input
        (E : Entity_Id) return Boolean
      is
         Result : Boolean;

      begin
         case Ekind (E) is

            --  A constant is known to have no variable input if its
            --  initializing expression is static (a value which is
            --  compile-time-known is not guaranteed to have no variable input
            --  as defined in the SPARK RM). Otherwise, the constant may or not
            --  have variable input.

            when E_Constant =>
               declare
                  Decl : Node_Id;
               begin
                  if Present (Full_View (E)) then
                     Decl := Parent (Full_View (E));
                  else
                     Decl := Parent (E);
                  end if;

                  if Is_Imported (E) then
                     Result := False;
                  else
                     pragma Assert (Present (Expression (Decl)));
                     Result := Is_Static_Expression (Expression (Decl));
                  end if;
               end;

            when E_In_Parameter
               | E_Loop_Parameter
            =>
               Result := True;

            when others =>
               Result := False;
         end case;

         return Result;
      end Is_Constant_Object_Without_Variable_Input;

      ----------------------------
      -- Is_Future_Scope_Entity --
      ----------------------------

      function Is_Future_Scope_Entity
        (E : Entity_Id;
         S : Scope_Index) return Boolean
      is
         function Is_Past_Scope_Entity return Boolean;
         --  Check whether entity E is in SPARK_Scope_Table at index strictly
         --  lower than S.

         --------------------------
         -- Is_Past_Scope_Entity --
         --------------------------

         function Is_Past_Scope_Entity return Boolean is
         begin
            for Index in SPARK_Scope_Table.First .. S - 1 loop
               if SPARK_Scope_Table.Table (Index).Scope_Entity = E then
                  return True;
               end if;
            end loop;

            return False;
         end Is_Past_Scope_Entity;

      --  Start of processing for Is_Future_Scope_Entity

      begin
         for Index in S .. SPARK_Scope_Table.Last loop
            if SPARK_Scope_Table.Table (Index).Scope_Entity = E then
               return True;
            end if;
         end loop;

         --  If this assertion fails, this means that the scope which we are
         --  looking for has been treated already, which reveals a problem in
         --  the order of cross-references.

         pragma Assert (not Is_Past_Scope_Entity);

         return False;
      end Is_Future_Scope_Entity;

      ------------------------
      -- Is_SPARK_Reference --
      ------------------------

      function Is_SPARK_Reference
        (E   : Entity_Id;
         Typ : Character) return Boolean
      is
      begin
         --  The only references of interest on callable entities are calls. On
         --  uncallable entities, the only references of interest are reads and
         --  writes.

         if Ekind (E) in Overloadable_Kind then
            return Typ = 's';

         --  In all other cases, result is true for reference/modify cases,
         --  and false for all other cases.

         else
            return Typ = 'r' or else Typ = 'm';
         end if;
      end Is_SPARK_Reference;

      --------------------
      -- Is_SPARK_Scope --
      --------------------

      function Is_SPARK_Scope (E : Entity_Id) return Boolean is
      begin
         return Present (E)
           and then not Is_Generic_Unit (E)
           and then Renamed_Entity (E) = Empty
           and then Get_Scope_Num (E) /= No_Scope;
      end Is_SPARK_Scope;

      --------
      -- Lt --
      --------

      function Lt (Op1 : Natural; Op2 : Natural) return Boolean is
         T1 : constant Xref_Entry := Xrefs.Table (Rnums (Nat (Op1)));
         T2 : constant Xref_Entry := Xrefs.Table (Rnums (Nat (Op2)));

      begin
         --  First test: if entity is in different unit, sort by unit. Note:
         --  that we use Ent_Scope_File rather than Eun, as Eun may refer to
         --  the file where the generic scope is defined, which may differ from
         --  the file where the enclosing scope is defined. It is the latter
         --  which matters for a correct order here.

         if T1.Ent_Scope_File /= T2.Ent_Scope_File then
            return Dependency_Num (T1.Ent_Scope_File) <
                   Dependency_Num (T2.Ent_Scope_File);

         --  Second test: within same unit, sort by location of the scope of
         --  the entity definition.

         elsif Get_Scope_Num (T1.Key.Ent_Scope) /=
               Get_Scope_Num (T2.Key.Ent_Scope)
         then
            return Get_Scope_Num (T1.Key.Ent_Scope) <
                   Get_Scope_Num (T2.Key.Ent_Scope);

         --  Third test: within same unit and scope, sort by location of
         --  entity definition.

         elsif T1.Def /= T2.Def then
            return T1.Def < T2.Def;

         else
            --  Both entities must be equal at this point

            pragma Assert (T1.Key.Ent = T2.Key.Ent);
            pragma Assert (T1.Key.Ent_Scope = T2.Key.Ent_Scope);
            pragma Assert (T1.Ent_Scope_File = T2.Ent_Scope_File);

            --  Fourth test: if reference is in same unit as entity definition,
            --  sort first.

            if T1.Key.Lun /= T2.Key.Lun
              and then T1.Ent_Scope_File = T1.Key.Lun
            then
               return True;

            elsif T1.Key.Lun /= T2.Key.Lun
              and then T2.Ent_Scope_File = T2.Key.Lun
            then
               return False;

            --  Fifth test: if reference is in same unit and same scope as
            --  entity definition, sort first.

            elsif T1.Ent_Scope_File = T1.Key.Lun
              and then T1.Key.Ref_Scope /= T2.Key.Ref_Scope
              and then T1.Key.Ent_Scope = T1.Key.Ref_Scope
            then
               return True;

            elsif T2.Ent_Scope_File = T2.Key.Lun
              and then T1.Key.Ref_Scope /= T2.Key.Ref_Scope
              and then T2.Key.Ent_Scope = T2.Key.Ref_Scope
            then
               return False;

            --  Sixth test: for same entity, sort by reference location unit

            elsif T1.Key.Lun /= T2.Key.Lun then
               return Dependency_Num (T1.Key.Lun) <
                      Dependency_Num (T2.Key.Lun);

            --  Seventh test: for same entity, sort by reference location scope

            elsif Get_Scope_Num (T1.Key.Ref_Scope) /=
                  Get_Scope_Num (T2.Key.Ref_Scope)
            then
               return Get_Scope_Num (T1.Key.Ref_Scope) <
                      Get_Scope_Num (T2.Key.Ref_Scope);

            --  Eighth test: order of location within referencing unit

            elsif T1.Key.Loc /= T2.Key.Loc then
               return T1.Key.Loc < T2.Key.Loc;

            --  Finally, for two locations at the same address prefer the one
            --  that does NOT have the type 'r', so that a modification or
            --  extension takes preference, when there are more than one
            --  reference at the same location. As a result, in the case of
            --  entities that are in-out actuals, the read reference follows
            --  the modify reference.

            else
               return T2.Key.Typ = 'r';
            end if;
         end if;
      end Lt;

      ----------
      -- Move --
      ----------

      procedure Move (From : Natural; To : Natural) is
      begin
         Rnums (Nat (To)) := Rnums (Nat (From));
      end Move;

      -------------------
      -- Set_Scope_Num --
      -------------------

      procedure Set_Scope_Num (N : Entity_Id; Num : Nat) is
      begin
         Scopes.Set (K => N, E => Scope_Rec'(Num => Num, Entity => N));
      end Set_Scope_Num;

      ------------------------
      -- Update_Scope_Range --
      ------------------------

      procedure Update_Scope_Range
        (S    : Scope_Index;
         From : Xref_Index;
         To   : Xref_Index)
      is
      begin
         SPARK_Scope_Table.Table (S).From_Xref := From;
         SPARK_Scope_Table.Table (S).To_Xref := To;
      end Update_Scope_Range;

      --  Local variables

      Col        : Nat;
      From_Index : Xref_Index;
      Line       : Nat;
      Prev_Loc   : Source_Ptr;
      Prev_Typ   : Character;
      Ref_Count  : Nat;
      Ref_Id     : Entity_Id;
      Ref_Name   : String_Ptr;
      Scope_Id   : Scope_Index;

   --  Start of processing for Add_SPARK_Xrefs

   begin
      for Index in SPARK_Scope_Table.First .. SPARK_Scope_Table.Last loop
         declare
            S : SPARK_Scope_Record renames SPARK_Scope_Table.Table (Index);
         begin
            Set_Scope_Num (S.Scope_Entity, S.Scope_Num);
         end;
      end loop;

      declare
         Drefs_Table : Drefs.Table_Type
                         renames Drefs.Table (Drefs.First .. Drefs.Last);
      begin
         Xrefs.Append_All (Xrefs.Table_Type (Drefs_Table));
         Nrefs := Nrefs + Drefs_Table'Length;
      end;

      --  Capture the definition Sloc values. As in the case of normal cross
      --  references, we have to wait until now to get the correct value.

      for Index in 1 .. Nrefs loop
         Xrefs.Table (Index).Def := Sloc (Xrefs.Table (Index).Key.Ent);
      end loop;

      --  Eliminate entries not appropriate for SPARK. Done prior to sorting
      --  cross-references, as it discards useless references which do not have
      --  a proper format for the comparison function (like no location).

      Ref_Count := Nrefs;
      Nrefs     := 0;

      for Index in 1 .. Ref_Count loop
         declare
            Ref : Xref_Key renames Xrefs.Table (Index).Key;

         begin
            if SPARK_Entities (Ekind (Ref.Ent))
              and then SPARK_References (Ref.Typ)
              and then Is_SPARK_Scope (Ref.Ent_Scope)
              and then Is_SPARK_Scope (Ref.Ref_Scope)
              and then Is_SPARK_Reference (Ref.Ent, Ref.Typ)

              --  Discard references from unknown scopes, e.g. generic scopes

              and then Get_Scope_Num (Ref.Ent_Scope) /= No_Scope
              and then Get_Scope_Num (Ref.Ref_Scope) /= No_Scope
            then
               Nrefs         := Nrefs + 1;
               Rnums (Nrefs) := Index;
            end if;
         end;
      end loop;

      --  Sort the references

      Sorting.Sort (Integer (Nrefs));

      --  Eliminate duplicate entries

      --  We need this test for Ref_Count because if we force ALI file
      --  generation in case of errors detected, it may be the case that
      --  Nrefs is 0, so we should not reset it here.

      if Nrefs >= 2 then
         Ref_Count := Nrefs;
         Nrefs     := 1;

         for Index in 2 .. Ref_Count loop
            if Xrefs.Table (Rnums (Index)) /= Xrefs.Table (Rnums (Nrefs)) then
               Nrefs := Nrefs + 1;
               Rnums (Nrefs) := Rnums (Index);
            end if;
         end loop;
      end if;

      --  Eliminate the reference if it is at the same location as the previous
      --  one, unless it is a read-reference indicating that the entity is an
      --  in-out actual in a call.

      Ref_Count := Nrefs;
      Nrefs     := 0;
      Prev_Loc  := No_Location;
      Prev_Typ  := 'm';

      for Index in 1 .. Ref_Count loop
         declare
            Ref : Xref_Key renames Xrefs.Table (Rnums (Index)).Key;

         begin
            if Ref.Loc /= Prev_Loc
              or else (Prev_Typ = 'm' and then Ref.Typ = 'r')
            then
               Prev_Loc      := Ref.Loc;
               Prev_Typ      := Ref.Typ;
               Nrefs         := Nrefs + 1;
               Rnums (Nrefs) := Rnums (Index);
            end if;
         end;
      end loop;

      --  The two steps have eliminated all references, nothing to do

      if SPARK_Scope_Table.Last = 0 then
         return;
      end if;

      Ref_Id     := Empty;
      Scope_Id   := 1;
      From_Index := 1;

      --  Loop to output references

      for Refno in 1 .. Nrefs loop
         declare
            Ref_Entry : Xref_Entry renames Xrefs.Table (Rnums (Refno));
            Ref       : Xref_Key   renames Ref_Entry.Key;
            Typ       : Character;

         begin
            --  If this assertion fails, the scope which we are looking for is
            --  not in SPARK scope table, which reveals either a problem in the
            --  construction of the scope table, or an erroneous scope for the
            --  current cross-reference.

            pragma Assert (Is_Future_Scope_Entity (Ref.Ent_Scope, Scope_Id));

            --  Update the range of cross references to which the current scope
            --  refers to. This may be the empty range only for the first scope
            --  considered.

            if Ref.Ent_Scope /= Entity_Of_Scope (Scope_Id) then
               Update_Scope_Range
                 (S    => Scope_Id,
                  From => From_Index,
                  To   => SPARK_Xref_Table.Last);

               From_Index := SPARK_Xref_Table.Last + 1;
            end if;

            while Ref.Ent_Scope /= Entity_Of_Scope (Scope_Id) loop
               Scope_Id := Scope_Id + 1;
               pragma Assert (Scope_Id <= SPARK_Scope_Table.Last);
            end loop;

            if Ref.Ent /= Ref_Id then
               Ref_Name := new String'(Unique_Name (Ref.Ent));
            end if;

            if Ref.Ent = Heap then
               Line := 0;
               Col  := 0;
            else
               Line := Nat (Get_Logical_Line_Number (Ref_Entry.Def));
               Col  := Nat (Get_Column_Number (Ref_Entry.Def));
            end if;

            --  References to constant objects without variable inputs (see
            --  SPARK RM 3.3.1) are considered specially in SPARK section,
            --  because these will be translated as constants in the
            --  intermediate language for formal verification, and should
            --  therefore never appear in frame conditions. Other constants may
            --  later be treated the same, up to GNATprove to decide based on
            --  its flow analysis.

            if Is_Constant_Object_Without_Variable_Input (Ref.Ent) then
               Typ := 'c';
            else
               Typ := Ref.Typ;
            end if;

            SPARK_Xref_Table.Append (
              (Entity_Name => Ref_Name,
               Entity_Line => Line,
               Etype       => Get_Entity_Type (Ref.Ent),
               Entity_Col  => Col,
               File_Num    => Dependency_Num (Ref.Lun),
               Scope_Num   => Get_Scope_Num (Ref.Ref_Scope),
               Line        => Nat (Get_Logical_Line_Number (Ref.Loc)),
               Rtype       => Typ,
               Col         => Nat (Get_Column_Number (Ref.Loc))));
         end;
      end loop;

      --  Update the range of cross references to which the scope refers to

      Update_Scope_Range
        (S    => Scope_Id,
         From => From_Index,
         To   => SPARK_Xref_Table.Last);
   end Add_SPARK_Xrefs;

   -------------------------
   -- Collect_SPARK_Xrefs --
   -------------------------

   procedure Collect_SPARK_Xrefs
     (Sdep_Table : Unit_Ref_Table;
      Num_Sdep   : Nat)
   is
      Sdep      : Pos;
      Sdep_Next : Pos;
      --  Index of the current and next source dependency

      Sdep_File : Pos;
      --  Index of the file to which the scopes need to be assigned; for
      --  library-level instances of generic units this points to the unit
      --  of the body, because this is where references are assigned to.

      Ubody : Unit_Number_Type;
      Uspec : Unit_Number_Type;
      --  Unit numbers for the dependency spec and possibly its body (only in
      --  the case of library-level instance of a generic package).

   begin
      --  Cross-references should have been computed first

      pragma Assert (Xrefs.Last /= 0);

      Initialize_SPARK_Tables;

      --  Generate file and scope SPARK cross-reference information

      Sdep := 1;
      while Sdep <= Num_Sdep loop

         --  Skip dependencies with no entity node, e.g. configuration files
         --  with pragmas (.adc) or target description (.atp), since they
         --  present no interest for SPARK cross references.

         if No (Cunit_Entity (Sdep_Table (Sdep))) then
            Sdep_Next := Sdep + 1;

         --  For library-level instantiation of a generic, two consecutive
         --  units refer to the same compilation unit node and entity (one to
         --  body, one to spec). In that case, treat them as a single unit for
         --  the sake of SPARK cross references by passing to Add_SPARK_File.

         else
            if Sdep < Num_Sdep
              and then Cunit_Entity (Sdep_Table (Sdep)) =
                       Cunit_Entity (Sdep_Table (Sdep + 1))
            then
               declare
                  Cunit1 : Node_Id renames Cunit (Sdep_Table (Sdep));
                  Cunit2 : Node_Id renames Cunit (Sdep_Table (Sdep + 1));

               begin
                  --  Both Cunits point to compilation unit nodes

                  pragma Assert
                    (Nkind (Cunit1) = N_Compilation_Unit
                      and then Nkind (Cunit2) = N_Compilation_Unit);

                  --  Do not depend on the sorting order, which is based on
                  --  Unit_Name, and for library-level instances of nested
                  --  generic packages they are equal.

                  --  If declaration comes before the body

                  if Nkind (Unit (Cunit1)) = N_Package_Declaration
                    and then Nkind (Unit (Cunit2)) = N_Package_Body
                  then
                     Uspec := Sdep_Table (Sdep);
                     Ubody := Sdep_Table (Sdep + 1);

                     Sdep_File := Sdep + 1;

                  --  If body comes before declaration

                  elsif Nkind (Unit (Cunit1)) = N_Package_Body
                    and then Nkind (Unit (Cunit2)) = N_Package_Declaration
                  then
                     Uspec := Sdep_Table (Sdep + 1);
                     Ubody := Sdep_Table (Sdep);

                     Sdep_File := Sdep;

                  --  Otherwise it is an error

                  else
                     raise Program_Error;
                  end if;

                  Sdep_Next := Sdep + 2;
               end;

            --  ??? otherwise?

            else
               Uspec := Sdep_Table (Sdep);
               Ubody := No_Unit;

               Sdep_File := Sdep;
               Sdep_Next := Sdep + 1;
            end if;

            Add_SPARK_File
              (Uspec => Uspec,
               Ubody => Ubody,
               Dspec => Sdep_File);
         end if;

         Sdep := Sdep_Next;
      end loop;

      --  Fill in the spec information when relevant

      declare
         package Entity_Hash_Table is new
           GNAT.HTable.Simple_HTable
             (Header_Num => Entity_Hashed_Range,
              Element    => Scope_Index,
              No_Element => 0,
              Key        => Entity_Id,
              Hash       => Entity_Hash,
              Equal      => "=");

      begin
         --  Fill in the hash-table

         for S in SPARK_Scope_Table.First .. SPARK_Scope_Table.Last loop
            declare
               Srec : SPARK_Scope_Record renames SPARK_Scope_Table.Table (S);
            begin
               Entity_Hash_Table.Set (Srec.Scope_Entity, S);
            end;
         end loop;

         --  Use the hash-table to locate spec entities

         for S in SPARK_Scope_Table.First .. SPARK_Scope_Table.Last loop
            declare
               Srec : SPARK_Scope_Record renames SPARK_Scope_Table.Table (S);

               Spec_Entity : constant Entity_Id :=
                               Unique_Entity (Srec.Scope_Entity);
               Spec_Scope  : constant Scope_Index :=
                               Entity_Hash_Table.Get (Spec_Entity);

            begin
               --  Generic spec may be missing in which case Spec_Scope is zero

               if Spec_Entity /= Srec.Scope_Entity
                 and then Spec_Scope /= 0
               then
                  Srec.Spec_File_Num :=
                    SPARK_Scope_Table.Table (Spec_Scope).File_Num;
                  Srec.Spec_Scope_Num :=
                    SPARK_Scope_Table.Table (Spec_Scope).Scope_Num;
               end if;
            end;
         end loop;
      end;

      --  Generate SPARK cross-reference information

      Add_SPARK_Xrefs;
   end Collect_SPARK_Xrefs;

   -------------------------------------
   -- Enclosing_Subprogram_Or_Package --
   -------------------------------------

   function Enclosing_Subprogram_Or_Library_Package
     (N : Node_Id) return Entity_Id
   is
      Context : Entity_Id;

   begin
      --  If N is the defining identifier for a subprogram, then return the
      --  enclosing subprogram or package, not this subprogram.

      if Nkind_In (N, N_Defining_Identifier, N_Defining_Operator_Symbol)
        and then (Ekind (N) in Entry_Kind
                   or else Ekind (N) = E_Subprogram_Body
                   or else Ekind (N) in Generic_Subprogram_Kind
                   or else Ekind (N) in Subprogram_Kind)
      then
         Context := Parent (Unit_Declaration_Node (N));

         --  If this was a library-level subprogram then replace Context with
         --  its Unit, which points to N_Subprogram_* node.

         if Nkind (Context) = N_Compilation_Unit then
            Context := Unit (Context);
         end if;
      else
         Context := N;
      end if;

      while Present (Context) loop
         case Nkind (Context) is
            when N_Package_Body
               | N_Package_Specification
            =>
               --  Only return a library-level package

               if Is_Library_Level_Entity (Defining_Entity (Context)) then
                  Context := Defining_Entity (Context);
                  exit;
               else
                  Context := Parent (Context);
               end if;

            when N_Pragma =>

               --  The enclosing subprogram for a precondition, postcondition,
               --  or contract case should be the declaration preceding the
               --  pragma (skipping any other pragmas between this pragma and
               --  this declaration.

               while Nkind (Context) = N_Pragma
                 and then Is_List_Member (Context)
                 and then Present (Prev (Context))
               loop
                  Context := Prev (Context);
               end loop;

               if Nkind (Context) = N_Pragma then
                  Context := Parent (Context);
               end if;

            when N_Entry_Body
               | N_Entry_Declaration
               | N_Protected_Type_Declaration
               | N_Subprogram_Body
               | N_Subprogram_Declaration
               | N_Subprogram_Specification
               | N_Task_Body
               | N_Task_Type_Declaration
            =>
               Context := Defining_Entity (Context);
               exit;

            when others =>
               Context := Parent (Context);
         end case;
      end loop;

      if Nkind (Context) = N_Defining_Program_Unit_Name then
         Context := Defining_Identifier (Context);
      end if;

      --  Do not return a scope without a proper location

      if Present (Context)
        and then Sloc (Context) = No_Location
      then
         return Empty;
      end if;

      return Context;
   end Enclosing_Subprogram_Or_Library_Package;

   -----------------
   -- Entity_Hash --
   -----------------

   function Entity_Hash (E : Entity_Id) return Entity_Hashed_Range is
   begin
      return
        Entity_Hashed_Range (E mod (Entity_Id (Entity_Hashed_Range'Last) + 1));
   end Entity_Hash;

   --------------------------
   -- Generate_Dereference --
   --------------------------

   procedure Generate_Dereference
     (N   : Node_Id;
      Typ : Character := 'r')
   is
      procedure Create_Heap;
      --  Create and decorate the special entity which denotes the heap

      -----------------
      -- Create_Heap --
      -----------------

      procedure Create_Heap is
      begin
         Name_Len := Name_Of_Heap_Variable'Length;
         Name_Buffer (1 .. Name_Len) := Name_Of_Heap_Variable;

         Heap := Make_Defining_Identifier (Standard_Location, Name_Enter);

         Set_Ekind       (Heap, E_Variable);
         Set_Is_Internal (Heap, True);
         Set_Has_Fully_Qualified_Name (Heap);
      end Create_Heap;

      --  Local variables

      Loc : constant Source_Ptr := Sloc (N);

   --  Start of processing for Generate_Dereference

   begin
      if Loc > No_Location then
         Drefs.Increment_Last;

         declare
            Deref_Entry : Xref_Entry renames Drefs.Table (Drefs.Last);
            Deref       : Xref_Key   renames Deref_Entry.Key;

         begin
            if No (Heap) then
               Create_Heap;
            end if;

            Deref.Ent := Heap;
            Deref.Loc := Loc;
            Deref.Typ := Typ;

            --  It is as if the special "Heap" was defined in the main unit,
            --  in the scope of the entity for the main unit. This single
            --  definition point is required to ensure that sorting cross
            --  references works for "Heap" references as well.

            Deref.Eun := Main_Unit;
            Deref.Lun := Get_Top_Level_Code_Unit (Loc);

            Deref.Ref_Scope := Enclosing_Subprogram_Or_Library_Package (N);
            Deref.Ent_Scope := Cunit_Entity (Main_Unit);

            Deref_Entry.Def := No_Location;

            Deref_Entry.Ent_Scope_File := Main_Unit;
         end;
      end if;
   end Generate_Dereference;

   -------------------------------
   -- Traverse_Compilation_Unit --
   -------------------------------

   procedure Traverse_Compilation_Unit
     (CU           : Node_Id;
      Inside_Stubs : Boolean)
   is
      procedure Traverse_Block                      (N : Node_Id);
      procedure Traverse_Declaration_Or_Statement   (N : Node_Id);
      procedure Traverse_Declarations_And_HSS       (N : Node_Id);
      procedure Traverse_Declarations_Or_Statements (L : List_Id);
      procedure Traverse_Handled_Statement_Sequence (N : Node_Id);
      procedure Traverse_Package_Body               (N : Node_Id);
      procedure Traverse_Visible_And_Private_Parts  (N : Node_Id);
      procedure Traverse_Protected_Body             (N : Node_Id);
      procedure Traverse_Subprogram_Body            (N : Node_Id);
      procedure Traverse_Task_Body                  (N : Node_Id);

      --  Traverse corresponding construct, calling Process on all declarations

      --------------------
      -- Traverse_Block --
      --------------------

      procedure Traverse_Block (N : Node_Id) renames
        Traverse_Declarations_And_HSS;

      ---------------------------------------
      -- Traverse_Declaration_Or_Statement --
      ---------------------------------------

      procedure Traverse_Declaration_Or_Statement (N : Node_Id) is
         function Traverse_Stub (N : Node_Id) return Boolean;
         --  Returns True iff stub N should be traversed

         function Traverse_Stub (N : Node_Id) return Boolean is
         begin
            pragma Assert (Nkind_In (N, N_Package_Body_Stub,
                                        N_Protected_Body_Stub,
                                        N_Subprogram_Body_Stub,
                                        N_Task_Body_Stub));

            return Inside_Stubs and then Present (Library_Unit (N));
         end Traverse_Stub;

      --  Start of processing for Traverse_Declaration_Or_Statement

      begin
         case Nkind (N) is
            when N_Package_Declaration =>
               Traverse_Visible_And_Private_Parts (Specification (N));

            when N_Package_Body =>
               Traverse_Package_Body (N);

            when N_Package_Body_Stub =>
               if Traverse_Stub (N) then
                  Traverse_Package_Body (Get_Body_From_Stub (N));
               end if;

            when N_Subprogram_Body =>
               Traverse_Subprogram_Body (N);

            when N_Entry_Body =>
               Traverse_Subprogram_Body (N);

            when N_Subprogram_Body_Stub =>
               if Traverse_Stub (N) then
                  Traverse_Subprogram_Body (Get_Body_From_Stub (N));
               end if;

            when N_Protected_Body =>
               Traverse_Protected_Body (N);

            when N_Protected_Body_Stub =>
               if Traverse_Stub (N) then
                  Traverse_Protected_Body (Get_Body_From_Stub (N));
               end if;

            when N_Protected_Type_Declaration =>
               Traverse_Visible_And_Private_Parts (Protected_Definition (N));

            when N_Task_Definition =>
               Traverse_Visible_And_Private_Parts (N);

            when N_Task_Body =>
               Traverse_Task_Body (N);

            when N_Task_Body_Stub =>
               if Traverse_Stub (N) then
                  Traverse_Task_Body (Get_Body_From_Stub (N));
               end if;

            when N_Block_Statement =>
               Traverse_Block (N);

            when N_If_Statement =>

               --  Traverse the statements in the THEN part

               Traverse_Declarations_Or_Statements (Then_Statements (N));

               --  Loop through ELSIF parts if present

               if Present (Elsif_Parts (N)) then
                  declare
                     Elif : Node_Id := First (Elsif_Parts (N));

                  begin
                     while Present (Elif) loop
                        Traverse_Declarations_Or_Statements
                          (Then_Statements (Elif));
                        Next (Elif);
                     end loop;
                  end;
               end if;

               --  Finally traverse the ELSE statements if present

               Traverse_Declarations_Or_Statements (Else_Statements (N));

            when N_Case_Statement =>

               --  Process case branches

               declare
                  Alt : Node_Id := First (Alternatives (N));
               begin
                  loop
                     Traverse_Declarations_Or_Statements (Statements (Alt));
                     Next (Alt);
                     exit when No (Alt);
                  end loop;
               end;

            when N_Extended_Return_Statement =>
               Traverse_Handled_Statement_Sequence
                 (Handled_Statement_Sequence (N));

            when N_Loop_Statement =>
               Traverse_Declarations_Or_Statements (Statements (N));

               --  Generic declarations are ignored

            when others =>
               null;
         end case;
      end Traverse_Declaration_Or_Statement;

      -----------------------------------
      -- Traverse_Declarations_And_HSS --
      -----------------------------------

      procedure Traverse_Declarations_And_HSS (N : Node_Id) is
      begin
         Traverse_Declarations_Or_Statements (Declarations (N));
         Traverse_Handled_Statement_Sequence (Handled_Statement_Sequence (N));
      end Traverse_Declarations_And_HSS;

      -----------------------------------------
      -- Traverse_Declarations_Or_Statements --
      -----------------------------------------

      procedure Traverse_Declarations_Or_Statements (L : List_Id) is
         N : Node_Id;

      begin
         --  Loop through statements or declarations

         N := First (L);
         while Present (N) loop

            --  Call Process on all declarations

            if Nkind (N) in N_Declaration
              or else Nkind (N) in N_Later_Decl_Item
              or else Nkind (N) = N_Entry_Body
            then
               Process (N);
            end if;

            Traverse_Declaration_Or_Statement (N);

            Next (N);
         end loop;
      end Traverse_Declarations_Or_Statements;

      -----------------------------------------
      -- Traverse_Handled_Statement_Sequence --
      -----------------------------------------

      procedure Traverse_Handled_Statement_Sequence (N : Node_Id) is
         Handler : Node_Id;

      begin
         if Present (N) then
            Traverse_Declarations_Or_Statements (Statements (N));

            if Present (Exception_Handlers (N)) then
               Handler := First (Exception_Handlers (N));
               while Present (Handler) loop
                  Traverse_Declarations_Or_Statements (Statements (Handler));
                  Next (Handler);
               end loop;
            end if;
         end if;
      end Traverse_Handled_Statement_Sequence;

      ---------------------------
      -- Traverse_Package_Body --
      ---------------------------

      procedure Traverse_Package_Body (N : Node_Id) is
         Spec_E : constant Entity_Id := Unique_Defining_Entity (N);

      begin
         case Ekind (Spec_E) is
            when E_Package =>
               Traverse_Declarations_And_HSS (N);

            when E_Generic_Package =>
               null;

            when others =>
               raise Program_Error;
         end case;
      end Traverse_Package_Body;

      -----------------------------
      -- Traverse_Protected_Body --
      -----------------------------

      procedure Traverse_Protected_Body (N : Node_Id) is
      begin
         Traverse_Declarations_Or_Statements (Declarations (N));
      end Traverse_Protected_Body;

      ------------------------------
      -- Traverse_Subprogram_Body --
      ------------------------------

      procedure Traverse_Subprogram_Body (N : Node_Id) is
         Spec_E : constant Entity_Id := Unique_Defining_Entity (N);

      begin
         case Ekind (Spec_E) is
            when Entry_Kind
               | E_Function
               | E_Procedure
            =>
               Traverse_Declarations_And_HSS (N);

            when Generic_Subprogram_Kind =>
               null;

            when others =>
               raise Program_Error;
         end case;
      end Traverse_Subprogram_Body;

      ------------------------
      -- Traverse_Task_Body --
      ------------------------

      procedure Traverse_Task_Body (N : Node_Id) renames
        Traverse_Declarations_And_HSS;

      ----------------------------------------
      -- Traverse_Visible_And_Private_Parts --
      ----------------------------------------

      procedure Traverse_Visible_And_Private_Parts (N : Node_Id) is
      begin
         Traverse_Declarations_Or_Statements (Visible_Declarations (N));
         Traverse_Declarations_Or_Statements (Private_Declarations (N));
      end Traverse_Visible_And_Private_Parts;

      --  Local variables

      Lu : Node_Id;

   --  Start of processing for Traverse_Compilation_Unit

   begin
      --  Get Unit (checking case of subunit)

      Lu := Unit (CU);

      if Nkind (Lu) = N_Subunit then
         Lu := Proper_Body (Lu);
      end if;

      --  Do not add scopes for generic units

      if Nkind (Lu) = N_Package_Body
        and then Ekind (Corresponding_Spec (Lu)) in Generic_Unit_Kind
      then
         return;
      end if;

      --  Call Process on all declarations

      if Nkind (Lu) in N_Declaration
        or else Nkind (Lu) in N_Later_Decl_Item
      then
         Process (Lu);
      end if;

      --  Traverse the unit

      Traverse_Declaration_Or_Statement (Lu);
   end Traverse_Compilation_Unit;

end SPARK_Specific;
