------------------------------------------------------------------------------
--                                                                          --
--                         GNAT LIBRARY COMPONENTS                          --
--                                                                          --
--               A D A . C O N T A I N E R S . H E L P E R S                --
--                                                                          --
--                                 S p e c                                  --
--                                                                          --
--             Copyright (C) 2015-2018, 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.                                     --
--                                                                          --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception,   --
-- version 3.1, as published by the Free Software Foundation.               --
--                                                                          --
-- You should have received a copy of the GNU General Public License and    --
-- a copy of the GCC Runtime Library Exception along with this program;     --
-- see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see    --
-- <http://www.gnu.org/licenses/>.                                          --
------------------------------------------------------------------------------

with Ada.Finalization;
with System.Atomic_Counters;

package Ada.Containers.Helpers is
   pragma Annotate (CodePeer, Skip_Analysis);
   pragma Pure;

   --  Miscellaneous helpers shared among various containers

   package SAC renames System.Atomic_Counters;

   Count_Type_Last : constant := Count_Type'Last;
   --  Count_Type'Last as a universal_integer, so we can compare Index_Type
   --  values against this without type conversions that might overflow.

   type Tamper_Counts is record
      Busy : aliased SAC.Atomic_Unsigned := 0;
      Lock : aliased SAC.Atomic_Unsigned := 0;
   end record;

   --  Busy is positive when tampering with cursors is prohibited. Busy and
   --  Lock are both positive when tampering with elements is prohibited.

   type Tamper_Counts_Access is access all Tamper_Counts;
   for Tamper_Counts_Access'Storage_Size use 0;

   generic
   package Generic_Implementation is

      --  Generic package used in the implementation of containers.

      --  This needs to be generic so that the 'Enabled attribute will return
      --  the value that is relevant at the point where a container generic is
      --  instantiated. For example:
      --
      --     pragma Suppress (Container_Checks);
      --     package My_Vectors is new Ada.Containers.Vectors (...);
      --
      --  should suppress all container-related checks within the instance
      --  My_Vectors.

      --  Shorthands for "checks enabled" and "tampering checks enabled". Note
      --  that suppressing either Container_Checks or Tampering_Check disables
      --  tampering checks. Note that this code needs to be in a generic
      --  package, because we want to take account of check suppressions at the
      --  instance. We use these flags, along with pragma Inline, to ensure
      --  that the compiler can optimize away the checks, as well as the
      --  tampering check machinery, when checks are suppressed.

      Checks : constant Boolean := Container_Checks'Enabled;
      T_Check : constant Boolean :=
        Container_Checks'Enabled and Tampering_Check'Enabled;

      --  Reference_Control_Type is used as a component of reference types, to
      --  prohibit tampering with elements so long as references exist.

      type Reference_Control_Type is
         new Finalization.Controlled with record
            T_Counts : Tamper_Counts_Access;
         end record
           with Disable_Controlled => not T_Check;

      overriding procedure Adjust (Control : in out Reference_Control_Type);
      pragma Inline (Adjust);

      overriding procedure Finalize (Control : in out Reference_Control_Type);
      pragma Inline (Finalize);

      procedure Zero_Counts (T_Counts : out Tamper_Counts);
      pragma Inline (Zero_Counts);
      --  Set Busy and Lock to zero

      procedure Busy (T_Counts : in out Tamper_Counts);
      pragma Inline (Busy);
      --  Prohibit tampering with cursors

      procedure Unbusy (T_Counts : in out Tamper_Counts);
      pragma Inline (Unbusy);
      --  Allow tampering with cursors

      procedure Lock (T_Counts : in out Tamper_Counts);
      pragma Inline (Lock);
      --  Prohibit tampering with elements

      procedure Unlock (T_Counts : in out Tamper_Counts);
      pragma Inline (Unlock);
      --  Allow tampering with elements

      procedure TC_Check (T_Counts : Tamper_Counts);
      pragma Inline (TC_Check);
      --  Tampering-with-cursors check

      procedure TE_Check (T_Counts : Tamper_Counts);
      pragma Inline (TE_Check);
      --  Tampering-with-elements check

      -----------------
      --  RAII Types --
      -----------------

      --  Initialize of With_Busy increments the Busy count, and Finalize
      --  decrements it. Thus, to prohibit tampering with elements within a
      --  given scope, declare an object of type With_Busy. The Busy count
      --  will be correctly decremented in case of exception or abort.

      --  With_Lock is the same as With_Busy, except it increments/decrements
      --  BOTH Busy and Lock, thus prohibiting tampering with cursors.

      type With_Busy (T_Counts : not null access Tamper_Counts) is
        new Finalization.Limited_Controlled with null record
          with Disable_Controlled => not T_Check;
      overriding procedure Initialize (Busy : in out With_Busy);
      overriding procedure Finalize (Busy : in out With_Busy);

      type With_Lock (T_Counts : not null access Tamper_Counts) is
        new Finalization.Limited_Controlled with null record
          with Disable_Controlled => not T_Check;
      overriding procedure Initialize (Lock : in out With_Lock);
      overriding procedure Finalize (Lock : in out With_Lock);

      --  Variables of type With_Busy and With_Lock are declared only for the
      --  effects of Initialize and Finalize, so they are not referenced;
      --  disable warnings about that. Note that all variables of these types
      --  have names starting with "Busy" or "Lock". These pragmas need to be
      --  present wherever these types are used.

      pragma Warnings (Off, "variable ""Busy*"" is not referenced");
      pragma Warnings (Off, "variable ""Lock*"" is not referenced");

   end Generic_Implementation;

end Ada.Containers.Helpers;
