]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/ada/sem_spark.adb
[Ada] Minor reformattings
[thirdparty/gcc.git] / gcc / ada / sem_spark.adb
index c44fbc61c4f83e18acb8de3c83a315df688596cf..f99dced0da39ec353fcc9b3fe2e3abf8480264ca 100644 (file)
@@ -6,7 +6,7 @@
 --                                                                          --
 --                                 B o d y                                  --
 --                                                                          --
---          Copyright (C) 2017-2018, Free Software Foundation, Inc.         --
+--          Copyright (C) 2017-2019, 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- --
@@ -42,9 +42,9 @@ with GNAT.Dynamic_HTables; use GNAT.Dynamic_HTables;
 
 package body Sem_SPARK is
 
-   -------------------------------------------------
-   -- Handling of Permissions Associated to Paths --
-   -------------------------------------------------
+   ---------------------------------------------------
+   -- Handling of Permissions Associated with Paths --
+   ---------------------------------------------------
 
    package Permissions is
       Elaboration_Context_Max : constant := 1009;
@@ -54,11 +54,40 @@ package body Sem_SPARK is
 
       function Elaboration_Context_Hash
         (Key : Entity_Id) return Elaboration_Context_Index;
-      --  Function to hash any node of the AST
-
-      type Perm_Kind is (No_Access, Read_Only, Read_Write, Write_Only);
-      --  Permission type associated with paths
+      --  The hash function
+
+      --  Permission type associated with paths. These are related to but not
+      --  the same as the states associated with names used in SPARK RM 3.10:
+      --  Unrestricted, Observed, Borrowed, Moved. When ownership rules lead to
+      --  a state change for a name, this may correspond to multiple permission
+      --  changes for the paths corresponding to the name, its prefixes, and
+      --  its extensions. For example, when an object is assigned to, the
+      --  corresponding name gets into state Moved, while the path for the name
+      --  gets permission Write_Only as well as every prefix of the name, and
+      --  every suffix gets permission No_Access.
+
+      type Perm_Kind_Option is
+        (None,
+         --  Special value used when no permission is passed around
+
+         No_Access,
+         --  The path cannot be accessed for reading or writing. This is the
+         --  case for the path of a name in the Borrowed state.
+
+         Read_Only,
+         --  The path can only be accessed for reading. This is the case for
+         --  the path of a name in the Observed state.
+
+         Read_Write,
+         --  The path can be accessed for both reading and writing. This is the
+         --  case for the path of a name in the Unrestricted state.
+
+         Write_Only
+         --  The path can only be accessed for writing. This is the case for
+         --  the path of a name in the Moved state.
+        );
 
+      subtype Perm_Kind is Perm_Kind_Option range No_Access .. Write_Only;
       subtype Read_Perm is Perm_Kind range Read_Only .. Read_Write;
       subtype Write_Perm is Perm_Kind range Read_Write .. Write_Only;
 
@@ -82,58 +111,63 @@ package body Sem_SPARK is
 
       package Perm_Tree_Maps is new Simple_HTable
         (Header_Num => Elaboration_Context_Index,
-         Key        => Node_Id,
+         Key        => Entity_Id,
          Element    => Perm_Tree_Access,
          No_Element => null,
          Hash       => Elaboration_Context_Hash,
          Equal      => "=");
-      --  The instantation of a hash table, with keys being nodes and values
-      --  being pointers to trees. This is used to reference easily all
-      --  extensions of a Record_Component node (that can have name x, y, ...).
+      --  The instantation of a hash table, with keys being entities and values
+      --  being pointers to permission trees. This is used to define global
+      --  environment permissions (entities in that case are stand-alone
+      --  objects or formal parameters), as well as the permissions for the
+      --  extensions of a Record_Component node (entities in that case are
+      --  record components).
 
       --  The definition of permission trees. This is a tree, which has a
-      --  permission at each node, and depending on the type of the node,
-      --  can have zero, one, or more children pointed to by an access to tree.
+      --  permission at each node, and depending on the type of the node, can
+      --  have zero, one, or more children reached through an access to tree.
+
       type Perm_Tree (Kind : Path_Kind := Entire_Object) is record
          Permission : Perm_Kind;
          --  Permission at this level in the path
 
          Is_Node_Deep : Boolean;
-         --  Whether this node is of a deep type, to be used when moving the
-         --  path.
+         --  Whether this node is of a "deep" type, i.e. an access type or a
+         --  composite type containing access type subcomponents. This
+         --  corresponds to both "observing" and "owning" types in SPARK RM
+         --  3.10. To be used when moving the path.
 
-         case Kind is
+         Explanation : Node_Id;
+         --  Node that can be used in an explanation for a permission mismatch
 
+         case Kind is
             --  An entire object is either a leaf (an object which cannot be
             --  extended further in a path) or a subtree in folded form (which
             --  could later be unfolded further in another kind of node). The
             --  field Children_Permission specifies a permission for every
-            --  extension of that node if that permission is different from
-            --  the node's permission.
+            --  extension of that node if that permission is different from the
+            --  node's permission.
 
-            when Entire_Object    =>
+            when Entire_Object =>
                Children_Permission : Perm_Kind;
 
             --  Unfolded path of access type. The permission of the object
             --  pointed to is given in Get_All.
 
-            when Reference        =>
+            when Reference =>
                Get_All : Perm_Tree_Access;
 
-            --  Unfolded path of array type. The permission of the elements is
+            --  Unfolded path of array type. The permission of elements is
             --  given in Get_Elem.
 
-            when Array_Component  =>
+            when Array_Component =>
                Get_Elem : Perm_Tree_Access;
 
-            --  Unfolded path of record type. The permission of the regular
-            --  components is given in Component. The permission of unknown
-            --  components (for objects of tagged type) is given in
-            --  Other_Components.
+            --  Unfolded path of record type. The permission of the components
+            --  is given in Component.
 
             when Record_Component =>
                Component : Perm_Tree_Maps.Instance;
-               Other_Components : Perm_Tree_Access;
          end case;
       end record;
 
@@ -143,9 +177,8 @@ package body Sem_SPARK is
       --  We use this wrapper in order to have unconstrained discriminants
 
       type Perm_Env is new Perm_Tree_Maps.Instance;
-      --  The definition of a permission environment for the analysis. This
-      --  is just a hash table of permission trees, each of them rooted with
-      --  an Identifier/Expanded_Name.
+      --  The definition of a permission environment for the analysis. This is
+      --  just a hash table from entities to permission trees.
 
       type Perm_Env_Access is access Perm_Env;
       --  Access to permission environments
@@ -165,20 +198,17 @@ package body Sem_SPARK is
       --  The type defining the hash table saving the environments at the entry
       --  of each loop.
 
-      package Boolean_Variables_Maps is new Simple_HTable
+      package Variable_Maps is new Simple_HTable
         (Header_Num => Elaboration_Context_Index,
          Key        => Entity_Id,
-         Element    => Boolean,
-         No_Element => False,
+         Element    => Node_Id,
+         No_Element => Empty,
          Hash       => Elaboration_Context_Hash,
          Equal      => "=");
-      --  These maps allow tracking the variables that have been declared but
-      --  never used anywhere in the source code. Especially, we do not raise
-      --  an error if the variable stays write-only and is declared at package
-      --  level, because there is no risk that the variable has been moved,
-      --  because it has never been used.
 
-      type Initialization_Map is new Boolean_Variables_Maps.Instance;
+      type Variable_Mapping is new Variable_Maps.Instance;
+      --  Mapping from variables to nodes denoting paths that are observed or
+      --  borrowed by the variable.
 
       --------------------
       -- Simple Getters --
@@ -190,11 +220,11 @@ package body Sem_SPARK is
 
       function Children_Permission (T : Perm_Tree_Access) return Perm_Kind;
       function Component (T : Perm_Tree_Access) return Perm_Tree_Maps.Instance;
+      function Explanation (T : Perm_Tree_Access) return Node_Id;
       function Get_All (T : Perm_Tree_Access) return Perm_Tree_Access;
       function Get_Elem (T : Perm_Tree_Access) return Perm_Tree_Access;
       function Is_Node_Deep (T : Perm_Tree_Access) return Boolean;
       function Kind (T : Perm_Tree_Access) return Path_Kind;
-      function Other_Components (T : Perm_Tree_Access) return Perm_Tree_Access;
       function Permission (T : Perm_Tree_Access) return Perm_Kind;
 
       -----------------------
@@ -203,25 +233,24 @@ package body Sem_SPARK is
 
       procedure Copy_Env
         (From : Perm_Env;
-         To : in out Perm_Env);
+         To   : in out Perm_Env);
       --  Procedure to copy a permission environment
 
-      procedure Copy_Init_Map
-        (From : Initialization_Map;
-         To : in out Initialization_Map);
-      --  Procedure to copy an initialization map
+      procedure Move_Env (From, To : in out Perm_Env);
+      --  Procedure to move a permission environment. It frees To, moves From
+      --  in To and sets From to Nil.
+
+      procedure Move_Variable_Mapping (From, To : in out Variable_Mapping);
+      --  Move a variable mapping, freeing memory as needed and resetting the
+      --  source mapping.
 
-      procedure Copy_Tree
-        (From : Perm_Tree_Access;
-         To : Perm_Tree_Access);
+      procedure Copy_Tree (From, To : Perm_Tree_Access);
       --  Procedure to copy a permission tree
 
-      procedure Free_Env
-        (PE : in out Perm_Env);
+      procedure Free_Env (PE : in out Perm_Env);
       --  Procedure to free a permission environment
 
-      procedure Free_Perm_Tree
-        (PT : in out Perm_Tree_Access);
+      procedure Free_Tree (PT : in out Perm_Tree_Access);
       --  Procedure to free a permission tree
 
       --------------------
@@ -229,8 +258,11 @@ package body Sem_SPARK is
       --------------------
 
       procedure Perm_Mismatch
-        (Exp_Perm, Act_Perm : Perm_Kind;
-         N                   : Node_Id);
+        (N              : Node_Id;
+         Exp_Perm       : Perm_Kind;
+         Act_Perm       : Perm_Kind;
+         Expl           : Node_Id;
+         Forbidden_Perm : Boolean := False);
       --  Issues a continuation error message about a mismatch between a
       --  desired permission Exp_Perm and a permission obtained Act_Perm. N
       --  is the node on which the error is reported.
@@ -243,10 +275,7 @@ package body Sem_SPARK is
       -- Children_Permission --
       -------------------------
 
-      function Children_Permission
-        (T : Perm_Tree_Access)
-          return Perm_Kind
-      is
+      function Children_Permission (T : Perm_Tree_Access) return Perm_Kind is
       begin
          return T.all.Tree.Children_Permission;
       end Children_Permission;
@@ -255,9 +284,7 @@ package body Sem_SPARK is
       -- Component --
       ---------------
 
-      function Component
-        (T : Perm_Tree_Access)
-          return Perm_Tree_Maps.Instance
+      function Component (T : Perm_Tree_Access) return Perm_Tree_Maps.Instance
       is
       begin
          return T.all.Tree.Component;
@@ -267,16 +294,13 @@ package body Sem_SPARK is
       -- Copy_Env --
       --------------
 
-      procedure Copy_Env
-        (From : Perm_Env;
-         To : in out Perm_Env)
-      is
+      procedure Copy_Env (From : Perm_Env; To : in out Perm_Env) is
          Comp_From : Perm_Tree_Access;
-         Key_From : Perm_Tree_Maps.Key_Option;
-         Son : Perm_Tree_Access;
+         Key_From  : Perm_Tree_Maps.Key_Option;
+         Son       : Perm_Tree_Access;
 
       begin
-         Reset (To);
+         Free_Env (To);
          Key_From := Get_First_Key (From);
          while Key_From.Present loop
             Comp_From := Get (From, Key_From.K);
@@ -290,50 +314,28 @@ package body Sem_SPARK is
          end loop;
       end Copy_Env;
 
-      -------------------
-      -- Copy_Init_Map --
-      -------------------
-
-      procedure Copy_Init_Map
-        (From : Initialization_Map;
-         To : in out Initialization_Map)
-      is
-         Comp_From : Boolean;
-         Key_From : Boolean_Variables_Maps.Key_Option;
-
-      begin
-         Reset (To);
-         Key_From := Get_First_Key (From);
-         while Key_From.Present loop
-            Comp_From := Get (From, Key_From.K);
-            Set (To, Key_From.K, Comp_From);
-            Key_From := Get_Next_Key (From);
-         end loop;
-      end Copy_Init_Map;
-
       ---------------
       -- Copy_Tree --
       ---------------
 
-      procedure Copy_Tree
-        (From : Perm_Tree_Access;
-         To : Perm_Tree_Access)
-      is
+      procedure Copy_Tree (From, To : Perm_Tree_Access) is
       begin
+         --  Copy the direct components of the tree
+
          To.all := From.all;
 
+         --  Now reallocate access components for a deep copy of the tree
+
          case Kind (From) is
             when Entire_Object =>
                null;
 
             when Reference =>
                To.all.Tree.Get_All := new Perm_Tree_Wrapper;
-
                Copy_Tree (Get_All (From), Get_All (To));
 
             when Array_Component =>
                To.all.Tree.Get_Elem := new Perm_Tree_Wrapper;
-
                Copy_Tree (Get_Elem (From), Get_Elem (To));
 
             when Record_Component =>
@@ -343,29 +345,20 @@ package body Sem_SPARK is
                   Son : Perm_Tree_Access;
                   Hash_Table : Perm_Tree_Maps.Instance;
                begin
-               --  We put a new hash table, so that it gets dealiased from the
-               --  Component (From) hash table.
+                  --  We put a new hash table, so that it gets dealiased from
+                  --  the Component (From) hash table.
                   To.all.Tree.Component := Hash_Table;
-
-                  To.all.Tree.Other_Components :=
-                    new Perm_Tree_Wrapper'(Other_Components (From).all);
-
-                  Copy_Tree (Other_Components (From), Other_Components (To));
-
                   Key_From := Perm_Tree_Maps.Get_First_Key
                     (Component (From));
+
                   while Key_From.Present loop
                      Comp_From := Perm_Tree_Maps.Get
                        (Component (From), Key_From.K);
-
                      pragma Assert (Comp_From /= null);
                      Son := new Perm_Tree_Wrapper;
-
                      Copy_Tree (Comp_From, Son);
-
                      Perm_Tree_Maps.Set
                        (To.all.Tree.Component, Key_From.K, Son);
-
                      Key_From := Perm_Tree_Maps.Get_Next_Key
                        (Component (From));
                   end loop;
@@ -393,18 +386,18 @@ package body Sem_SPARK is
       begin
          CompO := Get_First (PE);
          while CompO /= null loop
-            Free_Perm_Tree (CompO);
+            Free_Tree (CompO);
             CompO := Get_Next (PE);
          end loop;
+
+         Reset (PE);
       end Free_Env;
 
-      --------------------
-      -- Free_Perm_Tree --
-      --------------------
+      ---------------
+      -- Free_Tree --
+      ---------------
 
-      procedure Free_Perm_Tree
-        (PT : in out Perm_Tree_Access)
-      is
+      procedure Free_Tree (PT : in out Perm_Tree_Access) is
          procedure Free_Perm_Tree_Dealloc is
            new Ada.Unchecked_Deallocation
              (Perm_Tree_Wrapper, Perm_Tree_Access);
@@ -413,41 +406,47 @@ package body Sem_SPARK is
       begin
          case Kind (PT) is
             when Entire_Object =>
-               Free_Perm_Tree_Dealloc (PT);
+               null;
 
             when Reference =>
-               Free_Perm_Tree (PT.all.Tree.Get_All);
-               Free_Perm_Tree_Dealloc (PT);
+               Free_Tree (PT.all.Tree.Get_All);
 
             when Array_Component =>
-               Free_Perm_Tree (PT.all.Tree.Get_Elem);
+               Free_Tree (PT.all.Tree.Get_Elem);
 
             when Record_Component =>
                declare
                   Comp : Perm_Tree_Access;
 
                begin
-                  Free_Perm_Tree (PT.all.Tree.Other_Components);
                   Comp := Perm_Tree_Maps.Get_First (Component (PT));
                   while Comp /= null loop
+
                      --  Free every Component subtree
 
-                     Free_Perm_Tree (Comp);
+                     Free_Tree (Comp);
                      Comp := Perm_Tree_Maps.Get_Next (Component (PT));
                   end loop;
                end;
-               Free_Perm_Tree_Dealloc (PT);
          end case;
-      end Free_Perm_Tree;
+
+         Free_Perm_Tree_Dealloc (PT);
+      end Free_Tree;
+
+      -----------------
+      -- Explanation --
+      -----------------
+
+      function Explanation (T : Perm_Tree_Access) return Node_Id is
+      begin
+         return T.all.Tree.Explanation;
+      end Explanation;
 
       -------------
       -- Get_All --
       -------------
 
-      function Get_All
-        (T : Perm_Tree_Access)
-          return Perm_Tree_Access
-      is
+      function Get_All (T : Perm_Tree_Access) return Perm_Tree_Access is
       begin
          return T.all.Tree.Get_All;
       end Get_All;
@@ -456,10 +455,7 @@ package body Sem_SPARK is
       -- Get_Elem --
       --------------
 
-      function Get_Elem
-        (T : Perm_Tree_Access)
-          return Perm_Tree_Access
-      is
+      function Get_Elem (T : Perm_Tree_Access) return Perm_Tree_Access is
       begin
          return T.all.Tree.Get_Elem;
       end Get_Elem;
@@ -468,10 +464,7 @@ package body Sem_SPARK is
       -- Is_Node_Deep --
       ------------------
 
-      function Is_Node_Deep
-        (T : Perm_Tree_Access)
-          return Boolean
-      is
+      function Is_Node_Deep (T : Perm_Tree_Access) return Boolean is
       begin
          return T.all.Tree.Is_Node_Deep;
       end Is_Node_Deep;
@@ -480,34 +473,38 @@ package body Sem_SPARK is
       -- Kind --
       ----------
 
-      function Kind
-        (T : Perm_Tree_Access)
-          return Path_Kind
-      is
+      function Kind (T : Perm_Tree_Access) return Path_Kind is
       begin
          return T.all.Tree.Kind;
       end Kind;
 
-      ----------------------
-      -- Other_Components --
-      ----------------------
+      --------------
+      -- Move_Env --
+      --------------
 
-      function Other_Components
-        (T : Perm_Tree_Access)
-          return Perm_Tree_Access
-      is
+      procedure Move_Env (From, To : in out Perm_Env) is
+      begin
+         Free_Env (To);
+         To   := From;
+         From := Perm_Env (Perm_Tree_Maps.Nil);
+      end Move_Env;
+
+      ---------------------------
+      -- Move_Variable_Mapping --
+      ---------------------------
+
+      procedure Move_Variable_Mapping (From, To : in out Variable_Mapping) is
       begin
-         return T.all.Tree.Other_Components;
-      end Other_Components;
+         Reset (To);
+         To   := From;
+         From := Variable_Mapping (Variable_Maps.Nil);
+      end Move_Variable_Mapping;
 
       ----------------
       -- Permission --
       ----------------
 
-      function Permission
-        (T : Perm_Tree_Access)
-          return Perm_Kind
-      is
+      function Permission (T : Perm_Tree_Access) return Perm_Kind is
       begin
          return T.all.Tree.Permission;
       end Permission;
@@ -517,13 +514,38 @@ package body Sem_SPARK is
       -------------------
 
       procedure Perm_Mismatch
-        (Exp_Perm, Act_Perm : Perm_Kind;
-         N                   : Node_Id)
+        (N              : Node_Id;
+         Exp_Perm       : Perm_Kind;
+         Act_Perm       : Perm_Kind;
+         Expl           : Node_Id;
+         Forbidden_Perm : Boolean := False)
       is
       begin
-         Error_Msg_N ("\expected at least `"
-                      & Perm_Kind'Image (Exp_Perm) & "`, got `"
-                      & Perm_Kind'Image (Act_Perm) & "`", N);
+         Error_Msg_Sloc := Sloc (Expl);
+
+         if Forbidden_Perm then
+            if Exp_Perm = No_Access then
+               Error_Msg_N ("\object was moved #", N);
+            else
+               raise Program_Error;
+            end if;
+         else
+            case Exp_Perm is
+               when Write_Perm =>
+                  if Act_Perm = Read_Only then
+                     Error_Msg_N
+                       ("\object was declared as not writeable #", N);
+                  else
+                     Error_Msg_N ("\object was moved #", N);
+                  end if;
+
+               when Read_Only =>
+                  Error_Msg_N ("\object was moved #", N);
+
+               when No_Access =>
+                  raise Program_Error;
+            end case;
+         end if;
       end Perm_Mismatch;
 
    end Permissions;
@@ -534,45 +556,48 @@ package body Sem_SPARK is
    -- Analysis modes for AST traversal --
    --------------------------------------
 
-   --  The different modes for analysis. This allows to checking whether a path
-   --  found in the code should be moved, borrowed, or observed.
+   --  The different modes for analysis. This allows checking whether a path
+   --  has the right permission, and also updating permissions when a path is
+   --  moved, borrowed, or observed.
 
-   type Checking_Mode is
+   type Extended_Checking_Mode is
 
-     (Read,
-      --  Default mode. Checks that paths have Read_Perm permission.
+     (Read_Subexpr,
+      --  Special value used for assignment, to check that subexpressions of
+      --  the assigned path are readable.
+
+      Read,
+      --  Default mode
 
       Move,
-      --  Regular moving semantics (not under 'Access). Checks that paths have
-      --  Read_Write permission. After moving a path, its permission is set to
-      --  Write_Only and the permission of its extensions is set to No_Access.
+      --  Move semantics. Checks that paths have Read_Write permission. After
+      --  moving a path, its permission and the permission of its prefixes are
+      --  set to Write_Only, while the permission of its extensions is set to
+      --  No_Access.
 
       Assign,
       --  Used for the target of an assignment, or an actual parameter with
       --  mode OUT. Checks that paths have Write_Perm permission. After
-      --  assigning to a path, its permission is set to Read_Write.
-
-      Super_Move,
-      --  Enhanced moving semantics (under 'Access). Checks that paths have
-      --  Read_Write permission. After moving a path, its permission is set
-      --  to No_Access, as well as the permission of its extensions and the
-      --  permission of its prefixes up to the first Reference node.
+      --  assigning to a path, its permission and the permission of its
+      --  extensions are set to Read_Write. The permission of its prefixes may
+      --  be normalized from Write_Only to Read_Write depending on other
+      --  permissions in the tree (a prefix gets permission Read_Write when all
+      --  its extensions become Read_Write).
 
-      Borrow_Out,
-      --  Used for actual OUT parameters. Checks that paths have Write_Perm
-      --  permission. After checking a path, its permission is set to Read_Only
-      --  when of a by-copy type, to No_Access otherwise. After the call, its
-      --  permission is set to Read_Write.
+      Borrow,
+      --  Borrow semantics. Checks that paths have Read_Write permission. After
+      --  borrowing a path, its permission and the permission of its prefixes
+      --  and extensions are set to No_Access.
 
       Observe
-      --  Used for actual IN parameters of a scalar type. Checks that paths
-      --  have Read_Perm permission. After checking a path, its permission
-      --  is set to Read_Only.
-      --
-      --  Also used for formal IN parameters
+      --  Observe semantics. Checks that paths have Read_Perm permission. After
+      --  observing a path, its permission and the permission of its prefixes
+      --  and extensions are set to Read_Only.
      );
 
-   type Result_Kind is (Folded, Unfolded, Function_Call);
+   subtype Checking_Mode is Extended_Checking_Mode range Read .. Observe;
+
+   type Result_Kind is (Folded, Unfolded);
    --  The type declaration to discriminate in the Perm_Or_Tree type
 
    --  The result type of the function Get_Perm_Or_Tree. This returns either a
@@ -582,12 +607,16 @@ package body Sem_SPARK is
 
    type Perm_Or_Tree (R : Result_Kind) is record
       case R is
-         when Folded        => Found_Permission : Perm_Kind;
-         when Unfolded      => Tree_Access : Perm_Tree_Access;
-         when Function_Call => null;
+         when Folded   =>
+            Found_Permission : Perm_Kind;
+            Explanation      : Node_Id;
+         when Unfolded =>
+            Tree_Access      : Perm_Tree_Access;
       end case;
    end record;
 
+   type Error_Status is (OK, Error);
+
    -----------------------
    -- Local subprograms --
    -----------------------
@@ -597,182 +626,247 @@ package body Sem_SPARK is
    function Glb  (P1, P2 : Perm_Kind) return Perm_Kind;
    function Lub  (P1, P2 : Perm_Kind) return Perm_Kind;
 
-   --  Checking proceduress for safe pointer usage. These procedures traverse
-   --  the AST, check nodes for correct permissions according to SPARK RM
-   --  6.4.2, and update permissions depending on the node kind.
+   procedure Check_Assignment (Target : Node_Or_Entity_Id; Expr : Node_Id);
+   --  Handle assignment as part of an assignment statement or an object
+   --  declaration.
 
    procedure Check_Call_Statement (Call : Node_Id);
 
    procedure Check_Callable_Body (Body_N : Node_Id);
-   --  We are not in End_Of_Callee mode, hence we will save the environment
-   --  and start from a new one. We will add in the environment all formal
-   --  parameters as well as global used during the subprogram, with the
-   --  appropriate permissions (write-only for out, read-only for observed,
-   --  read-write for others).
-   --
-   --  After that we analyze the body of the function, and finaly, we check
-   --  that each borrowed parameter and global has read-write permission. We
-   --  then clean up the environment and put back the saved environment.
+   --  Entry point for the analysis of a subprogram or entry body
 
    procedure Check_Declaration (Decl : Node_Id);
 
-   procedure Check_Expression (Expr : Node_Id);
+   procedure Check_Declaration_Legality
+     (Decl  : Node_Id;
+      Force : Boolean;
+      Legal : in out Boolean);
+   --  Check the legality of declaration Decl regarding rules not related to
+   --  permissions. Update Legal to False if a rule is violated. Issue an
+   --  error message if Force is True and Emit_Messages returns True.
+
+   procedure Check_Expression (Expr : Node_Id; Mode : Extended_Checking_Mode);
+   pragma Precondition (Nkind_In (Expr, N_Index_Or_Discriminant_Constraint,
+                                        N_Range_Constraint,
+                                        N_Subtype_Indication,
+                                        N_Digits_Constraint,
+                                        N_Delta_Constraint)
+                        or else Nkind (Expr) in N_Subexpr);
+
+   procedure Check_Globals (Subp : Entity_Id);
+   --  This procedure takes a subprogram called and checks the permission of
+   --  globals.
 
-   procedure Check_Globals (N : Node_Id; Check_Mode : Checking_Mode);
-   --  This procedure takes a global pragma and checks depending on mode:
-   --  Mode Read: every in global is readable
-   --  Mode Observe: same as Check_Param_Observes but on globals
-   --  Mode Borrow_Out: Check_Param_Outs for globals
-   --  Mode Move: Check_Param for globals with mode Read
-   --  Mode Assign: Check_Param for globals with mode Assign
+   --  Checking proceduress for safe pointer usage. These procedures traverse
+   --  the AST, check nodes for correct permissions according to SPARK RM 3.10,
+   --  and update permissions depending on the node kind. The main procedure
+   --  Check_Node is mutually recursive with the specialized procedures that
+   --  follow.
 
    procedure Check_List (L : List_Id);
    --  Calls Check_Node on each element of the list
 
-   procedure Check_Loop_Statement (Loop_N : Node_Id);
+   procedure Check_Loop_Statement (Stmt : Node_Id);
 
    procedure Check_Node (N : Node_Id);
-   --  Main traversal procedure to check safe pointer usage. This procedure is
-   --  mutually recursive with the specialized procedures that follow.
+   --  Main traversal procedure to check safe pointer usage
+
+   procedure Check_Old_Loop_Entry (N : Node_Id);
+   --  Check SPARK RM 3.10(14) regarding 'Old and 'Loop_Entry
 
    procedure Check_Package_Body (Pack : Node_Id);
 
-   procedure Check_Param (Formal : Entity_Id; Actual : Node_Id);
-   --  This procedure takes a formal and an actual parameter and calls the
-   --  analyze node if the parameter is borrowed with mode in out, with the
-   --  appropriate Checking_Mode (Move).
+   procedure Check_Package_Spec (Pack : Node_Id);
 
-   procedure Check_Param_Observes (Formal : Entity_Id; Actual : Node_Id);
-   --  This procedure takes a formal and an actual parameter and calls
-   --  the analyze node if the parameter is observed, with the appropriate
-   --  Checking_Mode.
+   procedure Check_Parameter_Or_Global
+     (Expr       : Node_Id;
+      Typ        : Entity_Id;
+      Kind       : Formal_Kind;
+      Subp       : Entity_Id;
+      Global_Var : Boolean);
+   --  Check the permission of every actual parameter or global
 
-   procedure Check_Param_Outs (Formal : Entity_Id; Actual : Node_Id);
-   --  This procedure takes a formal and an actual parameter and calls the
-   --  analyze node if the parameter is of mode out, with the appropriate
-   --  Checking_Mode.
+   procedure Check_Pragma (Prag : Node_Id);
 
-   procedure Check_Param_Read (Formal : Entity_Id; Actual : Node_Id);
-   --  This procedure takes a formal and an actual parameter and checks the
-   --  readability of every in-mode parameter. This includes observed in, and
-   --  also borrowed in, that are then checked afterwards.
+   procedure Check_Source_Of_Borrow_Or_Observe
+     (Expr   : Node_Id;
+      Status : out Error_Status);
 
    procedure Check_Statement (Stmt : Node_Id);
 
-   function Get_Perm (N : Node_Id) return Perm_Kind;
-   --  The function that takes a name as input and returns a permission
-   --  associated to it.
+   procedure Check_Type_Legality
+     (Typ   : Entity_Id;
+      Force : Boolean;
+      Legal : in out Boolean);
+   --  Check that type Typ is either not deep, or that it is an observing or
+   --  owning type according to SPARK RM 3.10
 
-   function Get_Perm_Or_Tree (N : Node_Id) return Perm_Or_Tree;
-   --  This function gets a Node_Id and looks recursively to find the
-   --  appropriate subtree for that Node_Id. If the tree is folded on
-   --  that node, then it returns the permission given at the right level.
+   function Get_Expl (N : Node_Or_Entity_Id) return Node_Id;
+   --  The function that takes a name as input and returns an explanation node
+   --  for the permission associated with it.
 
-   function Get_Perm_Tree (N : Node_Id) return Perm_Tree_Access;
-   --  This function gets a Node_Id and looks recursively to find the
-   --  appropriate subtree for that Node_Id. If the tree is folded, then
-   --  it unrolls the tree up to the appropriate level.
+   function Get_Observed_Or_Borrowed_Expr (Expr : Node_Id) return Node_Id;
+   pragma Precondition (Is_Path_Expression (Expr));
+   --  Return the expression being borrowed/observed when borrowing or
+   --  observing Expr. If Expr is a call to a traversal function, this is
+   --  the first actual, otherwise it is Expr.
 
-   function Has_Alias
-     (N : Node_Id)
-       return Boolean;
-   --  Function that returns whether the path given as parameter contains an
-   --  extension that is declared as aliased.
+   function Get_Perm (N : Node_Or_Entity_Id) return Perm_Kind;
+   --  The function that takes a name as input and returns a permission
+   --  associated with it.
 
-   function Has_Array_Component (N : Node_Id) return Boolean;
-   --  This function gets a Node_Id and looks recursively to find if the given
-   --  path has any array component.
+   function Get_Perm_Or_Tree (N : Node_Id) return Perm_Or_Tree;
+   pragma Precondition (Is_Path_Expression (N));
+   --  This function gets a node and looks recursively to find the appropriate
+   --  subtree for that node. If the tree is folded on that node, then it
+   --  returns the permission given at the right level.
 
-   function Has_Function_Component (N : Node_Id) return Boolean;
-   --  This function gets a Node_Id and looks recursively to find if the given
-   --  path has any function component.
+   function Get_Perm_Tree (N : Node_Id) return Perm_Tree_Access;
+   pragma Precondition (Is_Path_Expression (N));
+   --  This function gets a node and looks recursively to find the appropriate
+   --  subtree for that node. If the tree is folded, then it unrolls the tree
+   --  up to the appropriate level.
+
+   function Get_Root_Object
+     (Expr              : Node_Id;
+      Through_Traversal : Boolean := True;
+      Is_Traversal      : Boolean := False) return Entity_Id;
+   --  Return the root of the path expression Expr, or Empty for an allocator,
+   --  NULL, or a function call. Through_Traversal is True if it should follow
+   --  through calls to traversal functions. Is_Traversal is True if this
+   --  corresponds to a value returned from a traversal function, which should
+   --  allow if-expressions and case-expressions that refer to the same root,
+   --  even if the paths are not the same in all branches.
+
+   generic
+      with procedure Handle_Parameter_Or_Global
+        (Expr       : Node_Id;
+         Formal_Typ : Entity_Id;
+         Param_Mode : Formal_Kind;
+         Subp       : Entity_Id;
+         Global_Var : Boolean);
+   procedure Handle_Globals (Subp : Entity_Id);
+   --  Handling of globals is factored in a generic instantiated below
+
+   function Has_Array_Component (Expr : Node_Id) return Boolean;
+   pragma Precondition (Is_Path_Expression (Expr));
+   --  This function gets a node and looks recursively to determine whether the
+   --  given path has any array component.
 
    procedure Hp (P : Perm_Env);
    --  A procedure that outputs the hash table. This function is used only in
    --  the debugger to look into a hash table.
    pragma Unreferenced (Hp);
 
-   procedure Illegal_Global_Usage (N : Node_Or_Entity_Id);
+   procedure Illegal_Global_Usage (N : Node_Or_Entity_Id; E : Entity_Id);
    pragma No_Return (Illegal_Global_Usage);
    --  A procedure that is called when deep globals or aliased globals are used
    --  without any global aspect.
 
-   function Is_Borrowed_In (E : Entity_Id) return Boolean;
-   --  Function that tells if the given entity is a borrowed in a formal
-   --  parameter, that is, if it is an access-to-variable type.
+   function Is_Path_Expression
+     (Expr         : Node_Id;
+      Is_Traversal : Boolean := False) return Boolean;
+   --  Return whether Expr corresponds to a path. Is_Traversal is True if this
+   --  corresponds to a value returned from a traversal function, which should
+   --  allow if-expressions and case-expressions.
 
-   function Is_Deep (E : Entity_Id) return Boolean;
-   --  A function that can tell if a type is deep or not. Returns true if the
-   --  type passed as argument is deep.
+   function Is_Subpath_Expression
+     (Expr         : Node_Id;
+      Is_Traversal : Boolean := False) return Boolean;
+   --  Return True if Expr can be part of a path expression. Is_Traversal is
+   --  True if this corresponds to a value returned from a traversal function,
+   --  which should allow if-expressions and case-expressions.
 
-   function Is_Shallow (E : Entity_Id) return Boolean;
-   --  A function that can tell if a type is shallow or not. Returns true if
-   --  the type passed as argument is shallow.
+   function Is_Prefix_Or_Almost (Pref, Expr : Node_Id) return Boolean;
+   --  Determine if the candidate Prefix is indeed a prefix of Expr, or almost
+   --  a prefix, in the sense that they could still refer to overlapping memory
+   --  locations.
+
+   function Is_Traversal_Function_Call (Expr : Node_Id) return Boolean;
 
    function Loop_Of_Exit (N : Node_Id) return Entity_Id;
    --  A function that takes an exit statement node and returns the entity of
    --  the loop that this statement is exiting from.
 
-   procedure Merge_Envs (Target : in out Perm_Env; Source : in out Perm_Env);
+   procedure Merge_Env (Source : in out Perm_Env; Target : in out Perm_Env);
    --  Merge Target and Source into Target, and then deallocate the Source
 
    procedure Perm_Error
-     (N : Node_Id;
-      Perm : Perm_Kind;
-      Found_Perm : Perm_Kind);
+     (N              : Node_Id;
+      Perm           : Perm_Kind;
+      Found_Perm     : Perm_Kind;
+      Expl           : Node_Id;
+      Forbidden_Perm : Boolean := False);
    --  A procedure that is called when the permissions found contradict the
-   --  rules established by the RM. This function is called with the node, its
-   --  entity and the permission that was expected, and adds an error message
-   --  with the appropriate values.
+   --  rules established by the RM. This function is called with the node and
+   --  the permission that was expected, and issues an error message with the
+   --  appropriate values.
 
    procedure Perm_Error_Subprogram_End
      (E          : Entity_Id;
       Subp       : Entity_Id;
       Perm       : Perm_Kind;
-      Found_Perm : Perm_Kind);
+      Found_Perm : Perm_Kind;
+      Expl       : Node_Id);
    --  A procedure that is called when the permissions found contradict the
-   --  rules established by the RM at the end of subprograms. This function
-   --  is called with the node, its entity, the node of the returning function
-   --  and the permission that was expected, and adds an error message with the
+   --  rules established by the RM at the end of subprograms. This function is
+   --  called with the node, the node of the returning function, and the
+   --  permission that was expected, and adds an error message with the
    --  appropriate values.
 
-   procedure Process_Path (N : Node_Id);
-
-   procedure Return_Declarations (L : List_Id);
-   --  Check correct permissions on every declared object at the end of a
-   --  callee. Used at the end of the body of a callable entity. Checks that
-   --  paths of all borrowed formal parameters and global have Read_Write
-   --  permission.
+   procedure Process_Path (Expr : Node_Id; Mode : Checking_Mode);
+   pragma Precondition (Is_Path_Expression (Expr));
+   --  Check correct permission for the path in the checking mode Mode, and
+   --  update permissions of the path and its prefixes/extensions.
 
    procedure Return_Globals (Subp : Entity_Id);
    --  Takes a subprogram as input, and checks that all borrowed global items
-   --  of the subprogram indeed have RW permission at the end of the subprogram
-   --  execution.
+   --  of the subprogram indeed have Read_Write permission at the end of the
+   --  subprogram execution.
 
    procedure Return_Parameter_Or_Global
-     (Id   : Entity_Id;
-      Mode : Formal_Kind;
-      Subp : Entity_Id);
+     (Id         : Entity_Id;
+      Typ        : Entity_Id;
+      Kind       : Formal_Kind;
+      Subp       : Entity_Id;
+      Global_Var : Boolean);
    --  Auxiliary procedure to Return_Parameters and Return_Globals
 
    procedure Return_Parameters (Subp : Entity_Id);
-   --  Takes a subprogram as input, and checks that all borrowed parameters of
-   --  the subprogram indeed have RW permission at the end of the subprogram
-   --  execution.
+   --  Takes a subprogram as input, and checks that all out parameters of the
+   --  subprogram indeed have Read_Write permission at the end of the
+   --  subprogram execution.
 
-   procedure Set_Perm_Extensions (T : Perm_Tree_Access; P : Perm_Kind);
+   procedure Set_Perm_Extensions
+     (T    : Perm_Tree_Access;
+      P    : Perm_Kind;
+      Expl : Node_Id);
    --  This procedure takes an access to a permission tree and modifies the
    --  tree so that any strict extensions of the given tree become of the
    --  access specified by parameter P.
 
-   procedure Set_Perm_Extensions_Move (T : Perm_Tree_Access; E : Entity_Id);
+   procedure Set_Perm_Extensions_Move
+     (T    : Perm_Tree_Access;
+      E    : Entity_Id;
+      Expl : Node_Id);
    --  Set permissions to
    --    No for any extension with more .all
    --    W for any deep extension with same number of .all
    --    RW for any shallow extension with same number of .all
 
-   function Set_Perm_Prefixes_Assign (N : Node_Id) return Perm_Tree_Access;
-   --  This function takes a name as an input and sets in the permission
+   function Set_Perm_Prefixes
+     (N    : Node_Id;
+      Perm : Perm_Kind_Option;
+      Expl : Node_Id) return Perm_Tree_Access;
+   pragma Precondition (Is_Path_Expression (N));
+   --  This function modifies the permissions of a given node in the permission
+   --  environment as well as all the prefixes of the path, to the new
+   --  permission Perm. The general rule here is that everybody updates the
+   --  permission of the subtree they are returning.
+
+   procedure Set_Perm_Prefixes_Assign (N : Node_Id);
+   pragma Precondition (Is_Path_Expression (N));
+   --  This procedure takes a name as an input and sets in the permission
    --  tree the given permission to the given name. The general rule here is
    --  that everybody updates the permission of the subtree it is returning.
    --  The permission of the assigned path has been set to RW by the caller.
@@ -782,45 +876,31 @@ package body Sem_SPARK is
    --  path, actualize its permissions, and then call the normalization on its
    --  parent.
    --
-   --  Example: We assign x.y and x.z then during Set_Perm_Prefixes_Move will
+   --  Example: We assign x.y and x.z, and then Set_Perm_Prefixes_Assign will
    --  change the permission of x to RW because all of its components have
-   --  permission have permission RW.
-
-   function Set_Perm_Prefixes_Borrow_Out (N : Node_Id) return Perm_Tree_Access;
-   --  This function modifies the permissions of a given node_id in the
-   --  permission environment as well as in all the prefixes of the path,
-   --  given that the path is borrowed with mode out.
-
-   function Set_Perm_Prefixes_Move
-     (N : Node_Id; Mode : Checking_Mode)
-      return Perm_Tree_Access;
-   pragma Precondition (Mode = Move or Mode = Super_Move);
-   --  Given a node and a mode (that can be either Move or Super_Move), this
-   --  function modifies the permissions of a given node_id in the permission
-   --  environment as well as all the prefixes of the path, given that the path
-   --  is moved with or without 'Access. The general rule here is everybody
-   --  updates the permission of the subtree they are returning.
-   --
-   --  This case describes a move either under 'Access or without 'Access.
-
-   function Set_Perm_Prefixes_Observe (N : Node_Id) return Perm_Tree_Access;
-   --  This function modifies the permissions of a given node_id in the
-   --  permission environment as well as all the prefixes of the path,
-   --  given that the path is observed.
+   --  permission RW.
 
    procedure Setup_Globals (Subp : Entity_Id);
    --  Takes a subprogram as input, and sets up the environment by adding
    --  global items with appropriate permissions.
 
    procedure Setup_Parameter_Or_Global
-     (Id   : Entity_Id;
-      Mode : Formal_Kind);
+     (Id         : Entity_Id;
+      Typ        : Entity_Id;
+      Kind       : Formal_Kind;
+      Subp       : Entity_Id;
+      Global_Var : Boolean;
+      Expl       : Node_Id);
    --  Auxiliary procedure to Setup_Parameters and Setup_Globals
 
    procedure Setup_Parameters (Subp : Entity_Id);
    --  Takes a subprogram as input, and sets up the environment by adding
    --  formal parameters with appropriate permissions.
 
+   procedure Setup_Protected_Components (Subp : Entity_Id);
+   --  Takes a protected operation as input, and sets up the environment by
+   --  adding protected components with appropriate permissions.
+
    ----------------------
    -- Global Variables --
    ----------------------
@@ -832,11 +912,13 @@ package body Sem_SPARK is
    --  scope. The analysis ensures at each point that this variables contains
    --  a valid permission environment with all bindings in scope.
 
-   Current_Checking_Mode : Checking_Mode := Read;
-   --  The current analysis mode. This global variable indicates at each point
-   --  of the analysis whether the node being analyzed is moved, borrowed,
-   --  assigned, read, ... The full list of possible values can be found in
-   --  the declaration of type Checking_Mode.
+   Inside_Procedure_Call : Boolean := False;
+   --  Set while checking the actual parameters of a procedure or entry call
+
+   Inside_Elaboration : Boolean := False;
+   --  Set during package spec/body elaboration, during which move and local
+   --  observe/borrow are not allowed. As a result, no other checking is needed
+   --  during elaboration.
 
    Current_Loops_Envs : Env_Backups;
    --  This variable contains saves of permission environments at each loop the
@@ -852,18 +934,107 @@ package body Sem_SPARK is
    --  restrictive than the saved environment at the beginning of the loop, and
    --  the permission environment after the loop is equal to the accumulator.
 
-   Current_Initialization_Map : Initialization_Map;
-   --  This variable contains a map that binds each variable of the analyzed
-   --  source code to a boolean that becomes true whenever the variable is used
-   --  after declaration. Hence we can exclude from analysis variables that
-   --  are just declared and never accessed, typically at package declaration.
+   Current_Borrowers : Variable_Mapping;
+   --  Mapping from borrowers to the path borrowed
+
+   Current_Observers : Variable_Mapping;
+   --  Mapping from observers to the path observed
+
+   --------------------
+   -- Handle_Globals --
+   --------------------
+
+   --  Generic procedure is defined first so that instantiations can be defined
+   --  anywhere afterwards.
+
+   procedure Handle_Globals (Subp : Entity_Id) is
+
+      --  Local subprograms
+
+      procedure Handle_Globals_From_List
+        (First_Item : Node_Id;
+         Kind       : Formal_Kind);
+      --  Handle global items from the list starting at Item
+
+      procedure Handle_Globals_Of_Mode (Global_Mode : Name_Id);
+      --  Handle global items for the mode Global_Mode
+
+      ------------------------------
+      -- Handle_Globals_From_List --
+      ------------------------------
+
+      procedure Handle_Globals_From_List
+        (First_Item : Node_Id;
+         Kind       : Formal_Kind)
+      is
+         Item : Node_Id := First_Item;
+         E    : Entity_Id;
+
+      begin
+         while Present (Item) loop
+            E := Entity (Item);
+
+            --  Ignore abstract states, which play no role in pointer aliasing
+
+            if Ekind (E) = E_Abstract_State then
+               null;
+            else
+               Handle_Parameter_Or_Global (Expr       => Item,
+                                           Formal_Typ => Retysp (Etype (Item)),
+                                           Param_Mode => Kind,
+                                           Subp       => Subp,
+                                           Global_Var => True);
+            end if;
+
+            Next_Global (Item);
+         end loop;
+      end Handle_Globals_From_List;
+
+      ----------------------------
+      -- Handle_Globals_Of_Mode --
+      ----------------------------
+
+      procedure Handle_Globals_Of_Mode (Global_Mode : Name_Id) is
+         Kind : Formal_Kind;
+
+      begin
+         case Global_Mode is
+            when Name_Input
+               | Name_Proof_In
+            =>
+               Kind := E_In_Parameter;
+
+            when Name_Output =>
+               Kind := E_Out_Parameter;
+
+            when Name_In_Out =>
+               Kind := E_In_Out_Parameter;
+
+            when others =>
+               raise Program_Error;
+         end case;
+
+         --  Check both global items from Global and Refined_Global pragmas
+
+         Handle_Globals_From_List (First_Global (Subp, Global_Mode), Kind);
+         Handle_Globals_From_List
+           (First_Global (Subp, Global_Mode, Refined => True), Kind);
+      end Handle_Globals_Of_Mode;
+
+   --  Start of processing for Handle_Globals
+
+   begin
+      Handle_Globals_Of_Mode (Name_Proof_In);
+      Handle_Globals_Of_Mode (Name_Input);
+      Handle_Globals_Of_Mode (Name_Output);
+      Handle_Globals_Of_Mode (Name_In_Out);
+   end Handle_Globals;
 
    ----------
    -- "<=" --
    ----------
 
-   function "<=" (P1, P2 : Perm_Kind) return Boolean
-   is
+   function "<=" (P1, P2 : Perm_Kind) return Boolean is
    begin
       return P2 >= P1;
    end "<=";
@@ -872,8 +1043,7 @@ package body Sem_SPARK is
    -- ">=" --
    ----------
 
-   function ">=" (P1, P2 : Perm_Kind) return Boolean
-   is
+   function ">=" (P1, P2 : Perm_Kind) return Boolean is
    begin
       case P2 is
          when No_Access  => return True;
@@ -883,222 +1053,425 @@ package body Sem_SPARK is
       end case;
    end ">=";
 
-   --------------------------
-   -- Check_Call_Statement --
-   --------------------------
-
-   procedure Check_Call_Statement (Call : Node_Id) is
-      Saved_Env : Perm_Env;
-
-      procedure Iterate_Call is new
-        Iterate_Call_Parameters (Check_Param);
-      procedure Iterate_Call_Observes is new
-        Iterate_Call_Parameters (Check_Param_Observes);
-      procedure Iterate_Call_Outs is new
-        Iterate_Call_Parameters (Check_Param_Outs);
-      procedure Iterate_Call_Read is new
-        Iterate_Call_Parameters (Check_Param_Read);
-
-   begin
-      --  Save environment, so that the modifications done by analyzing the
-      --  parameters are not kept at the end of the call.
-      Copy_Env (Current_Perm_Env,
-                Saved_Env);
+   ----------------------
+   -- Check_Assignment --
+   ----------------------
 
-      --  We first check the Read access on every in parameter
+   procedure Check_Assignment (Target : Node_Or_Entity_Id; Expr : Node_Id) is
 
-      Current_Checking_Mode := Read;
-      Iterate_Call_Read (Call);
-      Check_Globals (Get_Pragma
-                       (Get_Called_Entity (Call), Pragma_Global), Read);
+      --  Local subprograms
 
-      --  We first observe, then borrow with mode out, and then with other
-      --  modes. This ensures that we do not have to check for by-copy types
-      --  specially, because we read them before borrowing them.
+      procedure Handle_Borrow
+        (Var     : Entity_Id;
+         Expr    : Node_Id;
+         Is_Decl : Boolean);
+      --  Update map of current borrowers
 
-      Iterate_Call_Observes (Call);
-      Check_Globals (Get_Pragma
-                       (Get_Called_Entity (Call), Pragma_Global),
-                       Observe);
+      procedure Handle_Observe
+        (Var     : Entity_Id;
+         Expr    : Node_Id;
+         Is_Decl : Boolean);
+      --  Update map of current observers
 
-      Iterate_Call_Outs (Call);
-      Check_Globals (Get_Pragma
-                       (Get_Called_Entity (Call), Pragma_Global),
-                       Borrow_Out);
+      -------------------
+      -- Handle_Borrow --
+      -------------------
 
-      Iterate_Call (Call);
-      Check_Globals (Get_Pragma
-                       (Get_Called_Entity (Call), Pragma_Global), Move);
+      procedure Handle_Borrow
+        (Var     : Entity_Id;
+         Expr    : Node_Id;
+         Is_Decl : Boolean)
+      is
+         Borrowed : constant Node_Id := Get_Observed_Or_Borrowed_Expr (Expr);
 
-      --  Restore environment, because after borrowing/observing actual
-      --  parameters, they get their permission reverted to the ones before
-      --  the call.
+      begin
+         --  SPARK RM 3.10(8): If the type of the target is an anonymous
+         --  access-to-variable type (an owning access type), the source shall
+         --  be an owning access object [..] whose root object is the target
+         --  object itself.
 
-      Free_Env (Current_Perm_Env);
+         --  ??? In fact we could be slightly more permissive in allowing even
+         --  a call to a traversal function of the right form.
 
-      Copy_Env (Saved_Env,
-                Current_Perm_Env);
+         if not Is_Decl
+           and then (Is_Traversal_Function_Call (Expr)
+                      or else Get_Root_Object (Borrowed) /= Var)
+         then
+            if Emit_Messages then
+               Error_Msg_NE
+                 ("source of assignment must have & as root" &
+                    " (SPARK RM 3.10(8)))",
+                  Expr, Var);
+            end if;
+            return;
+         end if;
 
-      Free_Env (Saved_Env);
+         Set (Current_Borrowers, Var, Borrowed);
+      end Handle_Borrow;
 
-      --  We assign the out parameters (necessarily borrowed per RM)
-      Current_Checking_Mode := Assign;
-      Iterate_Call (Call);
-      Check_Globals (Get_Pragma
-                       (Get_Called_Entity (Call), Pragma_Global),
-                       Assign);
+      --------------------
+      -- Handle_Observe --
+      --------------------
 
-   end Check_Call_Statement;
+      procedure Handle_Observe
+        (Var     : Entity_Id;
+         Expr    : Node_Id;
+         Is_Decl : Boolean)
+      is
+         Observed : constant Node_Id := Get_Observed_Or_Borrowed_Expr (Expr);
 
-   -------------------------
-   -- Check_Callable_Body --
-   -------------------------
+      begin
+         --  ??? We are currently using the same restriction for observers
+         --  as for borrowers. To be seen if the SPARK RM current rule really
+         --  allows more uses.
 
-   procedure Check_Callable_Body (Body_N : Node_Id) is
+         if not Is_Decl
+           and then (Is_Traversal_Function_Call (Expr)
+                      or else Get_Root_Object (Observed) /= Var)
+         then
+            if Emit_Messages then
+               Error_Msg_NE
+                 ("source of assignment must have & as root" &
+                    " (SPARK RM 3.10(8)))",
+                  Expr, Var);
+            end if;
+            return;
+         end if;
 
-      Mode_Before : constant Checking_Mode := Current_Checking_Mode;
+         Set (Current_Observers, Var, Observed);
+      end Handle_Observe;
 
-      Saved_Env : Perm_Env;
-      Saved_Init_Map : Initialization_Map;
+      --  Local variables
 
-      New_Env : Perm_Env;
+      Target_Typ  : constant Node_Id := Etype (Target);
+      Is_Decl     : constant Boolean := Nkind (Target) = N_Defining_Identifier;
+      Target_Root : Entity_Id;
+      Expr_Root   : Entity_Id;
+      Perm        : Perm_Kind;
+      Status      : Error_Status;
+      Dummy       : Boolean := True;
 
-      Body_Id : constant Entity_Id := Defining_Entity (Body_N);
-      Spec_Id : constant Entity_Id := Unique_Entity (Body_Id);
+   --  Start of processing for Check_Assignment
 
    begin
-      --  Check if SPARK pragma is not set to Off
+      Check_Type_Legality (Target_Typ, Force => True, Legal => Dummy);
 
-      if Present (SPARK_Pragma (Defining_Entity (Body_N))) then
-         if Get_SPARK_Mode_From_Annotation
-           (SPARK_Pragma (Defining_Entity (Body_N, False))) /= Opt.On
-         then
+      if Is_Anonymous_Access_Type (Target_Typ) then
+         Check_Source_Of_Borrow_Or_Observe (Expr, Status);
+
+         if Status /= OK then
             return;
          end if;
-      else
-         return;
-      end if;
-
-      --  Save environment and put a new one in place
 
-      Copy_Env (Current_Perm_Env, Saved_Env);
+         if Is_Decl then
+            Target_Root := Target;
+         else
+            Target_Root := Get_Root_Object (Target);
+         end if;
 
-      --  Save initialization map
+         Expr_Root := Get_Root_Object (Expr);
 
-      Copy_Init_Map (Current_Initialization_Map, Saved_Init_Map);
+         --  SPARK RM 3.10(7): For an assignment statement where the target is
+         --  a stand-alone object of an anonymous access-to-object type.
 
-      Current_Checking_Mode := Read;
-      Current_Perm_Env := New_Env;
+         pragma Assert (Present (Target_Root));
 
-      --  Add formals and globals to the environment with adequate permissions
+         --  If the type of the target is an anonymous access-to-constant type
+         --  (an observing access type), the source shall be an owning access
+         --  object denoted by a name that is not in the Moved state, and whose
+         --  root object is not in the Moved state and is not declared at a
+         --  statically deeper accessibility level than that of the target
+         --  object.
 
-      if Is_Subprogram_Or_Entry (Spec_Id) then
-         Setup_Parameters (Spec_Id);
-         Setup_Globals (Spec_Id);
-      end if;
+         if Is_Access_Constant (Target_Typ) then
+            Perm := Get_Perm (Expr);
 
-      --  Analyze the body of the function
+            if Perm = No_Access then
+               Perm_Error (Expr, No_Access, No_Access,
+                           Expl => Get_Expl (Expr),
+                           Forbidden_Perm => True);
+               return;
+            end if;
 
-      Check_List (Declarations (Body_N));
-      Check_Node (Handled_Statement_Sequence (Body_N));
+            Perm := Get_Perm (Expr_Root);
 
-      --  Check the read-write permissions of borrowed parameters/globals
+            if Perm = No_Access then
+               Perm_Error (Expr, No_Access, No_Access,
+                           Expl => Get_Expl (Expr_Root),
+                           Forbidden_Perm => True);
+               return;
+            end if;
 
-      if Ekind_In (Spec_Id, E_Procedure, E_Entry)
-        and then not No_Return (Spec_Id)
-      then
-         Return_Parameters (Spec_Id);
-         Return_Globals (Spec_Id);
-      end if;
+            --  ??? check accessibility level
 
-      --  Free the environments
+            --  If the type of the target is an anonymous access-to-variable
+            --  type (an owning access type), the source shall be an owning
+            --  access object denoted by a name that is in the Unrestricted
+            --  state, and whose root object is the target object itself.
 
-      Free_Env (Current_Perm_Env);
+            Check_Expression (Expr, Observe);
+            Handle_Observe (Target_Root, Expr, Is_Decl);
 
-      Copy_Env (Saved_Env,
-                Current_Perm_Env);
+         else
+            Perm := Get_Perm (Expr);
 
-      Free_Env (Saved_Env);
+            if Perm /= Read_Write then
+               Perm_Error (Expr, Read_Write, Perm, Expl => Get_Expl (Expr));
+               return;
+            end if;
 
-      --  Restore initialization map
+            if not Is_Decl then
+               if not Is_Entity_Name (Target) then
+                  if Emit_Messages then
+                     Error_Msg_N
+                       ("target of borrow must be stand-alone variable",
+                        Target);
+                  end if;
+                  return;
 
-      Copy_Init_Map (Saved_Init_Map, Current_Initialization_Map);
+               elsif Target_Root /= Expr_Root then
+                  if Emit_Messages then
+                     Error_Msg_NE
+                       ("source of borrow must be variable &",
+                        Expr, Target);
+                  end if;
+                  return;
+               end if;
+            end if;
 
-      Reset (Saved_Init_Map);
+            Check_Expression (Expr, Borrow);
+            Handle_Borrow (Target_Root, Expr, Is_Decl);
+         end if;
 
-      --  The assignment of all out parameters will be done by caller
+      elsif Is_Deep (Target_Typ) then
 
-      Current_Checking_Mode := Mode_Before;
-   end Check_Callable_Body;
+         if Is_Path_Expression (Expr) then
+            Check_Expression (Expr, Move);
 
-   -----------------------
-   -- Check_Declaration --
-   -----------------------
+         else
+            if Emit_Messages then
+               Error_Msg_N ("expression not allowed as source of move", Expr);
+            end if;
+            return;
+         end if;
 
-   procedure Check_Declaration (Decl : Node_Id) is
-   begin
-      case N_Declaration'(Nkind (Decl)) is
-         when N_Full_Type_Declaration =>
-            --  Nothing to do here ??? NOT TRUE IF CONSTRAINT ON TYPE
-            null;
+      else
+         Check_Expression (Expr, Read);
+      end if;
+   end Check_Assignment;
 
-         when N_Object_Declaration =>
-            --  First move the right-hand side
-            Current_Checking_Mode := Move;
-            Check_Node (Expression (Decl));
+   --------------------------
+   -- Check_Call_Statement --
+   --------------------------
 
-            declare
-               Elem : Perm_Tree_Access;
+   procedure Check_Call_Statement (Call : Node_Id) is
 
-            begin
-               Elem := new Perm_Tree_Wrapper'
-                 (Tree =>
-                    (Kind                => Entire_Object,
-                     Is_Node_Deep        =>
-                       Is_Deep (Etype (Defining_Identifier (Decl))),
-                     Permission          => Read_Write,
-                     Children_Permission => Read_Write));
-
-               --  If unitialized declaration, then set to Write_Only. If a
-               --  pointer declaration, it has a null default initialization.
-               if Nkind (Expression (Decl)) = N_Empty
-                 and then not Has_Full_Default_Initialization
-                   (Etype (Defining_Identifier (Decl)))
-                 and then not Is_Access_Type
-                   (Etype (Defining_Identifier (Decl)))
-               then
-                  Elem.all.Tree.Permission := Write_Only;
-                  Elem.all.Tree.Children_Permission := Write_Only;
-               end if;
+      Subp : constant Entity_Id := Get_Called_Entity (Call);
+
+      --  Local subprograms
 
-               --  Create new tree for defining identifier
+      procedure Check_Param (Formal : Entity_Id; Actual : Node_Id);
+      --  Check the permission of every actual parameter
 
-               Set (Current_Perm_Env,
-                    Unique_Entity (Defining_Identifier (Decl)),
-                    Elem);
+      procedure Update_Param (Formal : Entity_Id; Actual : Node_Id);
+      --  Update the permission of OUT actual parameters
 
-               pragma Assert (Get_First (Current_Perm_Env)
-                              /= null);
+      -----------------
+      -- Check_Param --
+      -----------------
 
-            end;
+      procedure Check_Param (Formal : Entity_Id; Actual : Node_Id) is
+      begin
+         Check_Parameter_Or_Global
+           (Expr       => Actual,
+            Typ        => Retysp (Etype (Formal)),
+            Kind       => Ekind (Formal),
+            Subp       => Subp,
+            Global_Var => False);
+      end Check_Param;
+
+      ------------------
+      -- Update_Param --
+      ------------------
+
+      procedure Update_Param (Formal : Entity_Id; Actual : Node_Id) is
+         Mode : constant Entity_Kind := Ekind (Formal);
+
+      begin
+         case Formal_Kind'(Mode) is
+            when E_Out_Parameter =>
+               Check_Expression (Actual, Assign);
+
+            when others =>
+               null;
+         end case;
+      end Update_Param;
+
+      procedure Check_Params is new
+        Iterate_Call_Parameters (Check_Param);
+
+      procedure Update_Params is new
+        Iterate_Call_Parameters (Update_Param);
+
+   --  Start of processing for Check_Call_Statement
+
+   begin
+      Inside_Procedure_Call := True;
+      Check_Params (Call);
+      if Ekind (Get_Called_Entity (Call)) = E_Subprogram_Type then
+         if Emit_Messages then
+            Error_Msg_N
+              ("call through access to subprogram is not allowed in SPARK",
+               Call);
+         end if;
+      else
+         Check_Globals (Get_Called_Entity (Call));
+      end if;
+
+      Inside_Procedure_Call := False;
+      Update_Params (Call);
+   end Check_Call_Statement;
+
+   -------------------------
+   -- Check_Callable_Body --
+   -------------------------
+
+   procedure Check_Callable_Body (Body_N : Node_Id) is
+      Save_In_Elab    : constant Boolean := Inside_Elaboration;
+      Body_Id         : constant Entity_Id := Defining_Entity (Body_N);
+      Spec_Id         : constant Entity_Id := Unique_Entity (Body_Id);
+      Prag            : constant Node_Id := SPARK_Pragma (Body_Id);
+
+      Saved_Env       : Perm_Env;
+      Saved_Borrowers : Variable_Mapping;
+      Saved_Observers : Variable_Mapping;
+
+   begin
+      --  Only SPARK bodies are analyzed
+
+      if No (Prag)
+        or else Get_SPARK_Mode_From_Annotation (Prag) /= Opt.On
+      then
+         return;
+      end if;
+
+      Inside_Elaboration := False;
+
+      if Ekind (Spec_Id) = E_Function
+        and then Is_Anonymous_Access_Type (Etype (Spec_Id))
+        and then not Is_Traversal_Function (Spec_Id)
+      then
+         if Emit_Messages then
+            Error_Msg_N ("anonymous access type for result only allowed for "
+                         & "traversal functions", Spec_Id);
+         end if;
+         return;
+      end if;
+
+      --  Save environment and put a new one in place
+
+      Move_Env (Current_Perm_Env, Saved_Env);
+      Move_Variable_Mapping (Current_Borrowers, Saved_Borrowers);
+      Move_Variable_Mapping (Current_Observers, Saved_Observers);
+
+      --  Add formals and globals to the environment with adequate permissions
+
+      if Is_Subprogram_Or_Entry (Spec_Id) then
+         Setup_Parameters (Spec_Id);
+         Setup_Globals (Spec_Id);
+      end if;
+
+      --  For protected operations, add protected components to the environment
+      --  with adequate permissions.
+
+      if Is_Protected_Operation (Spec_Id) then
+         Setup_Protected_Components (Spec_Id);
+      end if;
+
+      --  Analyze the body of the subprogram
+
+      Check_List (Declarations (Body_N));
+      Check_Node (Handled_Statement_Sequence (Body_N));
+
+      --  Check the read-write permissions of borrowed parameters/globals
+
+      if Ekind_In (Spec_Id, E_Procedure, E_Entry)
+        and then not No_Return (Spec_Id)
+      then
+         Return_Parameters (Spec_Id);
+         Return_Globals (Spec_Id);
+      end if;
+
+      --  Restore the saved environment and free the current one
+
+      Move_Env (Saved_Env, Current_Perm_Env);
+      Move_Variable_Mapping (Saved_Borrowers, Current_Borrowers);
+      Move_Variable_Mapping (Saved_Observers, Current_Observers);
+
+      Inside_Elaboration := Save_In_Elab;
+   end Check_Callable_Body;
+
+   -----------------------
+   -- Check_Declaration --
+   -----------------------
+
+   procedure Check_Declaration (Decl : Node_Id) is
+      Target     : constant Entity_Id := Defining_Identifier (Decl);
+      Target_Typ : constant Node_Id := Etype (Target);
+      Expr       : Node_Id;
+      Dummy      : Boolean := True;
+
+   begin
+      --  Start with legality rules not related to permissions
+
+      Check_Declaration_Legality (Decl, Force => True, Legal => Dummy);
+
+      --  Now check permission-related legality rules
+
+      case N_Declaration'(Nkind (Decl)) is
+         when N_Full_Type_Declaration =>
+            null;
+
+            --  ??? What about component declarations with defaults.
 
          when N_Subtype_Declaration =>
-            Check_Node (Subtype_Indication (Decl));
+            Check_Expression (Subtype_Indication (Decl), Read);
+
+         when N_Object_Declaration =>
+            Expr := Expression (Decl);
+
+            if Present (Expr) then
+               Check_Assignment (Target => Target,
+                                 Expr   => Expr);
+            end if;
+
+            if Is_Deep (Target_Typ) then
+               declare
+                  Tree : constant Perm_Tree_Access :=
+                    new Perm_Tree_Wrapper'
+                      (Tree =>
+                         (Kind                => Entire_Object,
+                          Is_Node_Deep        => True,
+                          Explanation         => Decl,
+                          Permission          => Read_Write,
+                          Children_Permission => Read_Write));
+               begin
+                  Set (Current_Perm_Env, Target, Tree);
+               end;
+            end if;
 
          when N_Iterator_Specification =>
-            pragma Assert (Is_Shallow (Etype (Defining_Identifier (Decl))));
             null;
 
          when N_Loop_Parameter_Specification =>
-            pragma Assert (Is_Shallow (Etype (Defining_Identifier (Decl))));
             null;
 
          --  Checking should not be called directly on these nodes
 
-         when N_Component_Declaration
-            | N_Function_Specification
+         when N_Function_Specification
             | N_Entry_Declaration
             | N_Procedure_Specification
+            | N_Component_Declaration
          =>
             raise Program_Error;
 
@@ -1120,619 +1493,748 @@ package body Sem_SPARK is
       end case;
    end Check_Declaration;
 
-   ----------------------
-   -- Check_Expression --
-   ----------------------
+   --------------------------------
+   -- Check_Declaration_Legality --
+   --------------------------------
 
-   procedure Check_Expression (Expr : Node_Id) is
-      Mode_Before : constant Checking_Mode := Current_Checking_Mode;
-   begin
-      case N_Subexpr'(Nkind (Expr)) is
-         when N_Procedure_Call_Statement =>
-            Check_Call_Statement (Expr);
+   procedure Check_Declaration_Legality
+     (Decl  : Node_Id;
+      Force : Boolean;
+      Legal : in out Boolean)
+   is
+      function Original_Emit_Messages return Boolean renames Emit_Messages;
 
-         when N_Identifier
-            | N_Expanded_Name
-         =>
-            --  Check if identifier is pointing to nothing (On/Off/...)
-            if not Present (Entity (Expr)) then
-               return;
-            end if;
+      function Emit_Messages return Boolean;
+      --  Local wrapper around generic formal parameter Emit_Messages, to
+      --  check the value of parameter Force before calling the original
+      --  Emit_Messages, and setting Legal to False.
 
-            --  Do not analyze things that are not of object Kind
-            if Ekind (Entity (Expr)) not in Object_Kind then
-               return;
-            end if;
+      -------------------
+      -- Emit_Messages --
+      -------------------
 
-            --  Consider as ident
-            Process_Path (Expr);
+      function Emit_Messages return Boolean is
+      begin
+         Legal := False;
+         return Force and then Original_Emit_Messages;
+      end Emit_Messages;
 
-         --  Switch to read mode and then check the readability of each operand
+      --  Local variables
 
-         when N_Binary_Op =>
+      Target     : constant Entity_Id := Defining_Identifier (Decl);
+      Target_Typ : constant Node_Id := Etype (Target);
+      Expr       : Node_Id;
 
-            Current_Checking_Mode := Read;
-            Check_Node (Left_Opnd (Expr));
-            Check_Node (Right_Opnd (Expr));
+   --  Start of processing for Check_Declaration_Legality
 
-         --  Switch to read mode and then check the readability of each operand
+   begin
+      case N_Declaration'(Nkind (Decl)) is
+         when N_Full_Type_Declaration =>
+            Check_Type_Legality (Target, Force, Legal);
 
-         when N_Op_Abs
-            | N_Op_Minus
-            | N_Op_Not
-            | N_Op_Plus
-         =>
-            pragma Assert (Is_Shallow (Etype (Expr)));
-            Current_Checking_Mode := Read;
-            Check_Node (Right_Opnd (Expr));
+         when N_Subtype_Declaration =>
+            null;
 
-         --  Forbid all deep expressions for Attribute ???
+         when N_Object_Declaration =>
+            Expr := Expression (Decl);
 
-         when N_Attribute_Reference =>
-            case Attribute_Name (Expr) is
-               when Name_Access =>
-                  case Current_Checking_Mode is
-                     when Read =>
-                        Check_Node (Prefix (Expr));
+            Check_Type_Legality (Target_Typ, Force, Legal);
 
-                     when Move =>
-                        Current_Checking_Mode := Super_Move;
-                        Check_Node (Prefix (Expr));
+            --  A declaration of a stand-alone object of an anonymous access
+            --  type shall have an explicit initial value and shall occur
+            --  immediately within a subprogram body, an entry body, or a
+            --  block statement (SPARK RM 3.10(4)).
 
-                     --  Only assign names, not expressions
+            if Is_Anonymous_Access_Type (Target_Typ) then
+               declare
+                  Scop : constant Entity_Id := Scope (Target);
+               begin
+                  if not Is_Local_Context (Scop) then
+                     if Emit_Messages then
+                        Error_Msg_N
+                          ("object of anonymous access type must be declared "
+                           & "immediately within a subprogram, entry or block "
+                           & "(SPARK RM 3.10(4))", Decl);
+                     end if;
+                  end if;
+               end;
 
-                     when Assign =>
-                        raise Program_Error;
+               if No (Expr) then
+                  if Emit_Messages then
+                     Error_Msg_N ("object of anonymous access type must be "
+                                  & "initialized (SPARK RM 3.10(4))", Decl);
+                  end if;
+               end if;
+            end if;
 
-                     --  Prefix in Super_Move should be a name, error here
+         when N_Iterator_Specification =>
+            null;
 
-                     when Super_Move =>
-                        raise Program_Error;
+         when N_Loop_Parameter_Specification =>
+            null;
 
-                     --  Could only borrow names of mode out, not n'Access
+         --  Checking should not be called directly on these nodes
 
-                     when Borrow_Out =>
-                        raise Program_Error;
+         when N_Function_Specification
+            | N_Entry_Declaration
+            | N_Procedure_Specification
+            | N_Component_Declaration
+         =>
+            raise Program_Error;
 
-                     when Observe =>
-                        Check_Node (Prefix (Expr));
-                  end case;
+         --  Ignored constructs for pointer checking
 
-               when Name_Last
-                  | Name_First
-               =>
-                  Current_Checking_Mode := Read;
-                  Check_Node (Prefix (Expr));
+         when N_Formal_Object_Declaration
+            | N_Formal_Type_Declaration
+            | N_Incomplete_Type_Declaration
+            | N_Private_Extension_Declaration
+            | N_Private_Type_Declaration
+            | N_Protected_Type_Declaration
+         =>
+            null;
 
-               when Name_Min =>
-                  Current_Checking_Mode := Read;
-                  Check_Node (Prefix (Expr));
+         --  The following nodes are rewritten by semantic analysis
 
-               when Name_Image =>
-                  Check_Node (Prefix (Expr));
+         when N_Expression_Function =>
+            raise Program_Error;
+      end case;
+   end Check_Declaration_Legality;
 
-               when Name_SPARK_Mode =>
-                  null;
+   ----------------------
+   -- Check_Expression --
+   ----------------------
 
-               when Name_Value =>
-                  Current_Checking_Mode := Read;
-                  Check_Node (Prefix (Expr));
+   procedure Check_Expression
+     (Expr : Node_Id;
+      Mode : Extended_Checking_Mode)
+   is
+      --  Local subprograms
 
-               when Name_Update =>
-                  Check_List (Expressions (Expr));
-                  Check_Node (Prefix (Expr));
+      function Is_Type_Name (Expr : Node_Id) return Boolean;
+      --  Detect when a path expression is in fact a type name
 
-               when Name_Pred
-                   | Name_Succ
-               =>
-                  Check_List (Expressions (Expr));
-                  Check_Node (Prefix (Expr));
-
-               when Name_Length =>
-                  Current_Checking_Mode := Read;
-                  Check_Node (Prefix (Expr));
-
-               --  Any Attribute referring to the underlying memory is ignored
-               --  in the analysis. This means that taking the address of a
-               --  variable makes a silent alias that is not rejected by the
-               --  analysis.
-
-               when Name_Address
-                   | Name_Alignment
-                   | Name_Component_Size
-                   | Name_First_Bit
-                   | Name_Last_Bit
-                   | Name_Size
-                   | Name_Position
-               =>
-                  null;
+      procedure Move_Expression (Expr : Node_Id);
+      --  Some subexpressions are only analyzed in Move mode. This is a
+      --  specialized version of Check_Expression for that case.
 
-               --  Attributes referring to types (get values from types), hence
-               --  no need to check either for borrows or any loans.
+      procedure Move_Expression_List (L : List_Id);
+      --  Call Move_Expression on every expression in the list L
 
-               when Name_Base
-                  | Name_Val
-               =>
-                  null;
+      procedure Read_Expression (Expr : Node_Id);
+      --  Most subexpressions are only analyzed in Read mode. This is a
+      --  specialized version of Check_Expression for that case.
 
-               --  Other attributes that fall out of the scope of the analysis
+      procedure Read_Expression_List (L : List_Id);
+      --  Call Read_Expression on every expression in the list L
 
-               when others =>
-                  null;
-            end case;
+      procedure Read_Indexes (Expr : Node_Id);
+      --  When processing a path, the index expressions and function call
+      --  arguments occurring on the path should be analyzed in Read mode.
 
-         when N_In =>
-            Current_Checking_Mode := Read;
-            Check_Node (Left_Opnd (Expr));
-            Check_Node (Right_Opnd (Expr));
+      ------------------
+      -- Is_Type_Name --
+      ------------------
 
-         when N_Not_In =>
-            Current_Checking_Mode := Read;
-            Check_Node (Left_Opnd (Expr));
-            Check_Node (Right_Opnd (Expr));
+      function Is_Type_Name (Expr : Node_Id) return Boolean is
+      begin
+         return Nkind_In (Expr, N_Expanded_Name, N_Identifier)
+           and then Is_Type (Entity (Expr));
+      end Is_Type_Name;
 
-         --  Switch to read mode and then check the readability of each operand
+      ---------------------
+      -- Move_Expression --
+      ---------------------
 
-         when N_And_Then
-            | N_Or_Else
-         =>
-            pragma Assert (Is_Shallow (Etype (Expr)));
-            Current_Checking_Mode := Read;
-            Check_Node (Left_Opnd (Expr));
-            Check_Node (Right_Opnd (Expr));
+      --  Distinguish the case where the argument is a path expression that
+      --  needs explicit moving.
 
-         --  Check the arguments of the call
+      procedure Move_Expression (Expr : Node_Id) is
+      begin
+         if Is_Path_Expression (Expr) then
+            Check_Expression (Expr, Move);
+         else
+            Read_Expression (Expr);
+         end if;
+      end Move_Expression;
 
-         when N_Function_Call =>
-            Current_Checking_Mode := Read;
-            Check_List (Parameter_Associations (Expr));
+      --------------------------
+      -- Move_Expression_List --
+      --------------------------
 
-         when N_Explicit_Dereference =>
-            Process_Path (Expr);
+      procedure Move_Expression_List (L : List_Id) is
+         N : Node_Id;
+      begin
+         N := First (L);
+         while Present (N) loop
+            Move_Expression (N);
+            Next (N);
+         end loop;
+      end Move_Expression_List;
 
-         --  Copy environment, run on each branch, and then merge
+      ---------------------
+      -- Read_Expression --
+      ---------------------
 
-         when N_If_Expression =>
-            declare
-               Saved_Env : Perm_Env;
+      procedure Read_Expression (Expr : Node_Id) is
+      begin
+         Check_Expression (Expr, Read);
+      end Read_Expression;
 
-               --  Accumulator for the different branches
+      --------------------------
+      -- Read_Expression_List --
+      --------------------------
 
-               New_Env : Perm_Env;
+      procedure Read_Expression_List (L : List_Id) is
+         N : Node_Id;
+      begin
+         N := First (L);
+         while Present (N) loop
+            Read_Expression (N);
+            Next (N);
+         end loop;
+      end Read_Expression_List;
 
-               Elmt : Node_Id := First (Expressions (Expr));
+      ------------------
+      -- Read_Indexes --
+      ------------------
 
-            begin
-               Current_Checking_Mode := Read;
+      procedure Read_Indexes (Expr : Node_Id) is
 
-               Check_Node (Elmt);
+         --  Local subprograms
 
-               Current_Checking_Mode := Mode_Before;
+         function Is_Singleton_Choice (Choices : List_Id) return Boolean;
+         --  Return whether Choices is a singleton choice
 
-               --  Save environment
+         procedure Read_Param (Formal : Entity_Id; Actual : Node_Id);
+         --  Call Read_Expression on the actual
 
-               Copy_Env (Current_Perm_Env,
-                                 Saved_Env);
+         -------------------------
+         -- Is_Singleton_Choice --
+         -------------------------
 
-               --  Here we have the original env in saved, current with a fresh
-               --  copy, and new aliased.
+         function Is_Singleton_Choice (Choices : List_Id) return Boolean is
+            Choice : constant Node_Id := First (Choices);
+         begin
+            return List_Length (Choices) = 1
+              and then Nkind (Choice) /= N_Others_Choice
+              and then not Nkind_In (Choice, N_Subtype_Indication, N_Range)
+              and then not
+                (Nkind_In (Choice, N_Identifier, N_Expanded_Name)
+                  and then Is_Type (Entity (Choice)));
+         end Is_Singleton_Choice;
+
+         ----------------
+         -- Read_Param --
+         ----------------
+
+         procedure Read_Param (Formal : Entity_Id; Actual : Node_Id) is
+            pragma Unreferenced (Formal);
+         begin
+            Read_Expression (Actual);
+         end Read_Param;
 
-               --  THEN PART
+         procedure Read_Params is new Iterate_Call_Parameters (Read_Param);
 
-               Next (Elmt);
-               Check_Node (Elmt);
+      --  Start of processing for Read_Indexes
 
-               --  Here the new_environment contains curr env after then block
+      begin
+         if not Is_Subpath_Expression (Expr) then
+            if Emit_Messages then
+               Error_Msg_N
+                 ("name expected here for move/borrow/observe", Expr);
+            end if;
+            return;
+         end if;
 
-               --  ELSE part
+         case N_Subexpr'(Nkind (Expr)) is
+            when N_Identifier
+               | N_Expanded_Name
+               | N_Null
+            =>
+               null;
 
-               --  Restore environment before if
-               Copy_Env (Current_Perm_Env,
-                                 New_Env);
+            when N_Explicit_Dereference
+               | N_Selected_Component
+            =>
+               Read_Indexes (Prefix (Expr));
 
-               Free_Env (Current_Perm_Env);
+            when N_Indexed_Component =>
+               Read_Indexes (Prefix (Expr));
+               Read_Expression_List (Expressions (Expr));
 
-               Copy_Env (Saved_Env,
-                                 Current_Perm_Env);
+            when N_Slice =>
+               Read_Indexes (Prefix (Expr));
+               Read_Expression (Discrete_Range (Expr));
 
-               --  Here new environment contains the environment after then and
-               --  current the fresh copy of old one.
+            --  The argument of an allocator is moved as part of the implicit
+            --  assignment.
 
-               Next (Elmt);
-               Check_Node (Elmt);
+            when N_Allocator =>
+               Move_Expression (Expression (Expr));
 
-               Merge_Envs (New_Env,
-                                   Current_Perm_Env);
+            when N_Function_Call =>
+               Read_Params (Expr);
+               if Ekind (Get_Called_Entity (Expr)) = E_Subprogram_Type then
+                  if Emit_Messages then
+                     Error_Msg_N
+                       ("call through access to subprogram is not allowed in "
+                        & "SPARK", Expr);
+                  end if;
+               else
+                  Check_Globals (Get_Called_Entity (Expr));
+               end if;
 
-               --  CLEANUP
+            when N_Op_Concat =>
+               Read_Expression (Left_Opnd (Expr));
+               Read_Expression (Right_Opnd (Expr));
 
-               Copy_Env (New_Env,
-                                 Current_Perm_Env);
+            when N_Qualified_Expression
+               | N_Type_Conversion
+               | N_Unchecked_Type_Conversion
+            =>
+               Read_Indexes (Expression (Expr));
 
-               Free_Env (New_Env);
-               Free_Env (Saved_Env);
-            end;
+            when N_Aggregate =>
+               declare
+                  Assocs : constant List_Id := Component_Associations (Expr);
+                  CL     : List_Id;
+                  Assoc  : Node_Id := Nlists.First (Assocs);
+                  Choice : Node_Id;
 
-         when N_Indexed_Component =>
-            Process_Path (Expr);
+               begin
+                  --  The subexpressions of an aggregate are moved as part
+                  --  of the implicit assignments. Handle the positional
+                  --  components first.
 
-         --  Analyze the expression that is getting qualified
+                  Move_Expression_List (Expressions (Expr));
 
-         when N_Qualified_Expression =>
-            Check_Node (Expression (Expr));
+                  --  Handle the named components next
 
-         when N_Quantified_Expression =>
-            declare
-               Saved_Env : Perm_Env;
-            begin
-               Copy_Env (Current_Perm_Env, Saved_Env);
-               Current_Checking_Mode := Read;
-               Check_Node (Iterator_Specification (Expr));
-               Check_Node (Loop_Parameter_Specification (Expr));
+                  while Present (Assoc) loop
+                     CL := Choices (Assoc);
 
-               Check_Node (Condition (Expr));
-               Free_Env (Current_Perm_Env);
-               Copy_Env (Saved_Env, Current_Perm_Env);
-               Free_Env (Saved_Env);
-            end;
+                     --  For an array aggregate, we should also check that the
+                     --  expressions used in choices are readable.
 
-         --  Analyze the list of associations in the aggregate
+                     if Is_Array_Type (Etype (Expr)) then
+                        Choice := Nlists.First (CL);
+                        while Present (Choice) loop
+                           if Nkind (Choice) /= N_Others_Choice then
+                              Read_Expression (Choice);
+                           end if;
+                           Next (Choice);
+                        end loop;
+                     end if;
 
-         when N_Aggregate =>
-            Check_List (Expressions (Expr));
-            Check_List (Component_Associations (Expr));
+                     --  There can be only one element for a value of deep type
+                     --  in order to avoid aliasing.
+
+                     if not Box_Present (Assoc)
+                       and then Is_Deep (Etype (Expression (Assoc)))
+                       and then not Is_Singleton_Choice (CL)
+                       and then Emit_Messages
+                     then
+                        Error_Msg_F
+                          ("singleton choice required to prevent aliasing",
+                           First (CL));
+                     end if;
 
-         when N_Allocator =>
-            Check_Node (Expression (Expr));
+                     --  The subexpressions of an aggregate are moved as part
+                     --  of the implicit assignments.
 
-         when N_Case_Expression =>
-            declare
-               Saved_Env : Perm_Env;
-
-               --  Accumulator for the different branches
-
-               New_Env : Perm_Env;
-
-               Elmt : Node_Id := First (Alternatives (Expr));
-
-            begin
-               Current_Checking_Mode := Read;
-               Check_Node (Expression (Expr));
-
-               Current_Checking_Mode := Mode_Before;
-
-               --  Save environment
-
-               Copy_Env (Current_Perm_Env,
-                                 Saved_Env);
-
-               --  Here we have the original env in saved, current with a fresh
-               --  copy, and new aliased.
-
-               --  First alternative
-
-               Check_Node (Elmt);
-               Next (Elmt);
-
-               Copy_Env (Current_Perm_Env,
-                                 New_Env);
-
-               Free_Env (Current_Perm_Env);
-
-               --  Other alternatives
-
-               while Present (Elmt) loop
-                  --  Restore environment
-
-                  Copy_Env (Saved_Env,
-                                    Current_Perm_Env);
-
-                  Check_Node (Elmt);
-
-                  --  Merge Current_Perm_Env into New_Env
+                     if not Box_Present (Assoc) then
+                        Move_Expression (Expression (Assoc));
+                     end if;
 
-                  Merge_Envs (New_Env,
-                                      Current_Perm_Env);
+                     Next (Assoc);
+                  end loop;
+               end;
 
-                  Next (Elmt);
-               end loop;
+            when N_Extension_Aggregate =>
+               declare
+                  Exprs  : constant List_Id := Expressions (Expr);
+                  Assocs : constant List_Id := Component_Associations (Expr);
+                  CL     : List_Id;
+                  Assoc  : Node_Id := Nlists.First (Assocs);
 
-               --  CLEANUP
-               Copy_Env (New_Env,
-                                 Current_Perm_Env);
+               begin
+                  Move_Expression (Ancestor_Part (Expr));
 
-               Free_Env (New_Env);
-               Free_Env (Saved_Env);
-            end;
+                  --  No positional components allowed at this stage
 
-         --  Analyze the list of associates in the aggregate as well as the
-         --  ancestor part.
+                  if Present (Exprs) then
+                     raise Program_Error;
+                  end if;
 
-         when N_Extension_Aggregate =>
+                  while Present (Assoc) loop
+                     CL := Choices (Assoc);
 
-            Check_Node (Ancestor_Part (Expr));
-            Check_List (Expressions (Expr));
+                     --  Only singleton components allowed at this stage
 
-         when N_Range =>
-            Check_Node (Low_Bound (Expr));
-            Check_Node (High_Bound (Expr));
+                     if not Is_Singleton_Choice (CL) then
+                        raise Program_Error;
+                     end if;
 
-         --  We arrived at a path. Process it.
+                     --  The subexpressions of an aggregate are moved as part
+                     --  of the implicit assignments.
 
-         when N_Selected_Component =>
-            Process_Path (Expr);
+                     if not Box_Present (Assoc) then
+                        Move_Expression (Expression (Assoc));
+                     end if;
 
-         when N_Slice =>
-            Process_Path (Expr);
+                     Next (Assoc);
+                  end loop;
+               end;
 
-         when N_Type_Conversion =>
-            Check_Node (Expression (Expr));
+            when N_If_Expression =>
+               declare
+                  Cond      : constant Node_Id := First (Expressions (Expr));
+                  Then_Part : constant Node_Id := Next (Cond);
+                  Else_Part : constant Node_Id := Next (Then_Part);
+               begin
+                  Read_Expression (Cond);
+                  Read_Indexes (Then_Part);
+                  Read_Indexes (Else_Part);
+               end;
 
-         when N_Unchecked_Type_Conversion =>
-            Check_Node (Expression (Expr));
+            when N_Case_Expression =>
+               declare
+                  Cases    : constant List_Id := Alternatives (Expr);
+                  Cur_Case : Node_Id := First (Cases);
 
-         --  Checking should not be called directly on these nodes
+               begin
+                  Read_Expression (Expression (Expr));
 
-         when N_Target_Name =>
-            raise Program_Error;
+                  while Present (Cur_Case) loop
+                     Read_Indexes (Expression (Cur_Case));
+                     Next (Cur_Case);
+                  end loop;
+               end;
 
-         --  Unsupported constructs in SPARK
+            when N_Attribute_Reference =>
+               pragma Assert
+                 (Get_Attribute_Id (Attribute_Name (Expr)) =
+                    Attribute_Loop_Entry
+                  or else
+                  Get_Attribute_Id (Attribute_Name (Expr)) = Attribute_Update
+                  or else
+                  Get_Attribute_Id (Attribute_Name (Expr)) = Attribute_Image);
+
+               Read_Expression (Prefix (Expr));
+
+               if Get_Attribute_Id (Attribute_Name (Expr)) = Attribute_Update
+                 or else (Get_Attribute_Id (Attribute_Name (Expr)) =
+                            Attribute_Image
+                          and then Is_Type_Name (Prefix (Expr)))
+               then
+                  Read_Expression_List (Expressions (Expr));
+               end if;
 
-         when N_Delta_Aggregate =>
-            Error_Msg_N ("unsupported construct in SPARK", Expr);
+            when others =>
+               raise Program_Error;
+         end case;
+      end Read_Indexes;
 
-         --  Ignored constructs for pointer checking
+   --  Start of processing for Check_Expression
 
-         when N_Character_Literal
-            | N_Null
-            | N_Numeric_Or_String_Literal
-            | N_Operator_Symbol
-            | N_Raise_Expression
-            | N_Raise_xxx_Error
-         =>
-            null;
+   begin
+      if Is_Type_Name (Expr) then
+         return;
 
-         --  The following nodes are never generated in GNATprove mode
+      elsif Is_Path_Expression (Expr) then
+         if Mode /= Assign then
+            Read_Indexes (Expr);
+         end if;
 
-         when N_Expression_With_Actions
-            | N_Reference
-            | N_Unchecked_Expression
-         =>
-            raise Program_Error;
+         if Mode /= Read_Subexpr then
+            Process_Path (Expr, Mode);
+         end if;
 
-      end case;
-   end Check_Expression;
+         return;
+      end if;
 
-   -------------------
-   -- Check_Globals --
-   -------------------
+      --  Expressions that are not path expressions should only be analyzed in
+      --  Read mode.
 
-   procedure Check_Globals (N : Node_Id; Check_Mode : Checking_Mode) is
-   begin
-      if Nkind (N) = N_Empty then
+      if Mode /= Read then
+         if Emit_Messages then
+            Error_Msg_N ("name expected here for move/borrow/observe", Expr);
+         end if;
          return;
       end if;
 
-      declare
-         pragma Assert
-           (List_Length (Pragma_Argument_Associations (N)) = 1);
-
-         PAA      : constant Node_Id :=
-           First (Pragma_Argument_Associations (N));
-         pragma Assert (Nkind (PAA) = N_Pragma_Argument_Association);
+      --  Special handling for nodes that may contain evaluated expressions in
+      --  the form of constraints.
 
-         Row      : Node_Id;
-         The_Mode : Name_Id;
-         RHS      : Node_Id;
+      case Nkind (Expr) is
+         when N_Index_Or_Discriminant_Constraint =>
+            declare
+               Assn : Node_Id := First (Constraints (Expr));
+            begin
+               while Present (Assn) loop
+                  case Nkind (Assn) is
+                     when N_Discriminant_Association =>
+                        Read_Expression (Expression (Assn));
 
-         procedure Process (Mode   : Name_Id;
-                            The_Global : Entity_Id);
+                     when others =>
+                        Read_Expression (Assn);
+                  end case;
 
-         procedure Process (Mode   : Name_Id;
-                            The_Global : Node_Id)
-         is
-            Ident_Elt : constant Entity_Id :=
-              Unique_Entity (Entity (The_Global));
+                  Next (Assn);
+               end loop;
+            end;
+            return;
 
-            Mode_Before : constant Checking_Mode := Current_Checking_Mode;
+         when N_Range_Constraint =>
+            Read_Expression (Range_Expression (Expr));
+            return;
 
-         begin
-            if Ekind (Ident_Elt) = E_Abstract_State then
-               return;
+         when N_Subtype_Indication =>
+            if Present (Constraint (Expr)) then
+               Read_Expression (Constraint (Expr));
             end if;
+            return;
 
-            case Check_Mode is
-               when Read =>
-                  case Mode is
-                     when Name_Input
-                        | Name_Proof_In
-                     =>
-                        Check_Node (The_Global);
-
-                     when Name_Output
-                        | Name_In_Out
-                     =>
-                        null;
+         when N_Digits_Constraint =>
+            Read_Expression (Digits_Expression (Expr));
+            if Present (Range_Constraint (Expr)) then
+               Read_Expression (Range_Constraint (Expr));
+            end if;
+            return;
 
-                     when others =>
-                        raise Program_Error;
+         when N_Delta_Constraint =>
+            Read_Expression (Delta_Expression (Expr));
+            if Present (Range_Constraint (Expr)) then
+               Read_Expression (Range_Constraint (Expr));
+            end if;
+            return;
 
-                  end case;
+         when others =>
+            null;
+      end case;
 
-               when Observe =>
-                  case Mode is
-                     when Name_Input
-                        | Name_Proof_In
-                     =>
-                        if not Is_Borrowed_In (Ident_Elt) then
-                           --  Observed in
+      --  At this point Expr can only be a subexpression
 
-                           Current_Checking_Mode := Observe;
-                           Check_Node (The_Global);
-                        end if;
+      case N_Subexpr'(Nkind (Expr)) is
 
-                     when others =>
-                        null;
+         when N_Binary_Op
+            | N_Short_Circuit
+         =>
+            Read_Expression (Left_Opnd (Expr));
+            Read_Expression (Right_Opnd (Expr));
 
-                  end case;
+         when N_Membership_Test =>
+            Read_Expression (Left_Opnd (Expr));
+            if Present (Right_Opnd (Expr)) then
+               Read_Expression (Right_Opnd (Expr));
+            else
+               declare
+                  Cases    : constant List_Id := Alternatives (Expr);
+                  Cur_Case : Node_Id := First (Cases);
 
-               when Borrow_Out =>
+               begin
+                  while Present (Cur_Case) loop
+                     Read_Expression (Cur_Case);
+                     Next (Cur_Case);
+                  end loop;
+               end;
+            end if;
 
-                  case Mode is
-                     when Name_Output =>
-                        --  Borrowed out
-                        Current_Checking_Mode := Borrow_Out;
-                        Check_Node (The_Global);
+         when N_Unary_Op =>
+            Read_Expression (Right_Opnd (Expr));
 
-                     when others =>
-                        null;
+         when N_Attribute_Reference =>
+            declare
+               Aname   : constant Name_Id := Attribute_Name (Expr);
+               Attr_Id : constant Attribute_Id := Get_Attribute_Id (Aname);
+               Pref    : constant Node_Id := Prefix (Expr);
+               Args    : constant List_Id := Expressions (Expr);
 
-                  end case;
+            begin
+               case Attr_Id is
+
+                  --  The following attributes take either no arguments, or
+                  --  arguments that do not refer to evaluated expressions
+                  --  (like Length or Loop_Entry), hence only the prefix
+                  --  needs to be read.
+
+                  when Attribute_Address
+                     | Attribute_Alignment
+                     | Attribute_Callable
+                     | Attribute_Component_Size
+                     | Attribute_Constrained
+                     | Attribute_First
+                     | Attribute_First_Bit
+                     | Attribute_Last
+                     | Attribute_Last_Bit
+                     | Attribute_Length
+                     | Attribute_Loop_Entry
+                     | Attribute_Object_Size
+                     | Attribute_Position
+                     | Attribute_Size
+                     | Attribute_Terminated
+                     | Attribute_Valid
+                     | Attribute_Value_Size
+                  =>
+                     Read_Expression (Pref);
+
+                  --  The following attributes take a type name as prefix,
+                  --  hence only the arguments need to be read.
+
+                  when Attribute_Ceiling
+                     | Attribute_Floor
+                     | Attribute_Max
+                     | Attribute_Min
+                     | Attribute_Mod
+                     | Attribute_Pos
+                     | Attribute_Pred
+                     | Attribute_Remainder
+                     | Attribute_Rounding
+                     | Attribute_Succ
+                     | Attribute_Truncation
+                     | Attribute_Val
+                     | Attribute_Value
+                  =>
+                     Read_Expression_List (Args);
 
-               when Move =>
-                  case Mode is
-                     when Name_Input
-                        | Name_Proof_In
-                     =>
-                        if Is_Borrowed_In (Ident_Elt) then
-                           --  Borrowed in
+                  --  Attributes Image and Img either take a type name as
+                  --  prefix with an expression in argument, or the expression
+                  --  directly as prefix. Adapt to each case.
 
-                           Current_Checking_Mode := Move;
-                        else
-                           --  Observed
+                  when Attribute_Image
+                     | Attribute_Img
+                  =>
+                     if No (Args) then
+                        Read_Expression (Pref);
+                     else
+                        Read_Expression_List (Args);
+                     end if;
 
-                           return;
-                        end if;
+                  --  Attribute Update takes expressions as both prefix and
+                  --  arguments, so both need to be read.
 
-                     when Name_Output =>
-                        return;
+                  when Attribute_Update =>
+                     Read_Expression (Pref);
+                     Read_Expression_List (Args);
 
-                     when Name_In_Out =>
-                        --  Borrowed in out
+                  --  Attribute Modulus does not reference the evaluated
+                  --  expression, so it can be ignored for this analysis.
 
-                        Current_Checking_Mode := Move;
+                  when Attribute_Modulus =>
+                     null;
 
-                     when others =>
-                        raise Program_Error;
-                  end case;
+                  --  The following attributes apply to types; there are no
+                  --  expressions to read.
 
-                  Check_Node (The_Global);
-               when Assign =>
-                  case Mode is
-                     when Name_Input
-                        | Name_Proof_In
-                     =>
-                        null;
+                  when Attribute_Class
+                     | Attribute_Storage_Size
+                  =>
+                     null;
 
-                     when Name_Output
-                        | Name_In_Out
-                     =>
-                        --  Borrowed out or in out
+                  --  Postconditions should not be analyzed
 
-                        Process_Path (The_Global);
+                  when Attribute_Old
+                     | Attribute_Result
+                  =>
+                     raise Program_Error;
 
-                     when others =>
-                        raise Program_Error;
-                  end case;
+                  when others =>
+                     null;
+               end case;
+            end;
 
-               when others =>
-                  raise Program_Error;
-            end case;
+         when N_Range =>
+            Read_Expression (Low_Bound (Expr));
+            Read_Expression (High_Bound (Expr));
 
-            Current_Checking_Mode := Mode_Before;
-         end Process;
+         when N_If_Expression =>
+            Read_Expression_List (Expressions (Expr));
 
-      begin
-         if Nkind (Expression (PAA)) = N_Null then
-            --  global => null
-            --  No globals, nothing to do
-            return;
+         when N_Case_Expression =>
+            declare
+               Cases    : constant List_Id := Alternatives (Expr);
+               Cur_Case : Node_Id := First (Cases);
 
-         elsif Nkind_In (Expression (PAA), N_Identifier, N_Expanded_Name) then
-            --  global => foo
-            --  A single input
-            Process (Name_Input, Expression (PAA));
+            begin
+               while Present (Cur_Case) loop
+                  Read_Expression (Expression (Cur_Case));
+                  Next (Cur_Case);
+               end loop;
 
-         elsif Nkind (Expression (PAA)) = N_Aggregate
-           and then Expressions (Expression (PAA)) /= No_List
-         then
-            --  global => (foo, bar)
-            --  Inputs
-            RHS := First (Expressions (Expression (PAA)));
-            while Present (RHS) loop
-               case Nkind (RHS) is
-                  when N_Identifier
-                     | N_Expanded_Name
-                  =>
-                     Process (Name_Input, RHS);
+               Read_Expression (Expression (Expr));
+            end;
 
-                  when N_Numeric_Or_String_Literal =>
-                     Process (Name_Input, Original_Node (RHS));
+         when N_Qualified_Expression
+            | N_Type_Conversion
+            | N_Unchecked_Type_Conversion
+         =>
+            Read_Expression (Expression (Expr));
 
-                  when others =>
-                     raise Program_Error;
+         when N_Quantified_Expression =>
+            declare
+               For_In_Spec : constant Node_Id :=
+                 Loop_Parameter_Specification (Expr);
+               For_Of_Spec : constant Node_Id :=
+                 Iterator_Specification (Expr);
+               For_Of_Spec_Typ : Node_Id;
 
-               end case;
-               RHS := Next (RHS);
-            end loop;
+            begin
+               if Present (For_In_Spec) then
+                  Read_Expression (Discrete_Subtype_Definition (For_In_Spec));
+               else
+                  Read_Expression (Name (For_Of_Spec));
+                  For_Of_Spec_Typ := Subtype_Indication (For_Of_Spec);
+                  if Present (For_Of_Spec_Typ) then
+                     Read_Expression (For_Of_Spec_Typ);
+                  end if;
+               end if;
 
-         elsif Nkind (Expression (PAA)) = N_Aggregate
-           and then Component_Associations (Expression (PAA)) /= No_List
-         then
-            --  global => (mode => foo,
-            --             mode => (bar, baz))
-            --  A mixture of things
+               Read_Expression (Condition (Expr));
+            end;
 
-            declare
-               CA : constant List_Id :=
-                 Component_Associations (Expression (PAA));
-            begin
-               Row := First (CA);
-               while Present (Row) loop
-                  pragma Assert (List_Length (Choices (Row)) = 1);
-                  The_Mode := Chars (First (Choices (Row)));
-
-                  RHS := Expression (Row);
-                  case Nkind (RHS) is
-                     when N_Aggregate =>
-                        RHS := First (Expressions (RHS));
-                        while Present (RHS) loop
-                           case Nkind (RHS) is
-                              when N_Numeric_Or_String_Literal =>
-                                 Process (The_Mode, Original_Node (RHS));
-
-                              when others =>
-                                 Process (The_Mode, RHS);
-
-                           end case;
-                           RHS := Next (RHS);
-                        end loop;
+         when N_Character_Literal
+            | N_Numeric_Or_String_Literal
+            | N_Operator_Symbol
+            | N_Raise_Expression
+            | N_Raise_xxx_Error
+         =>
+            null;
 
-                     when N_Identifier
-                        | N_Expanded_Name
-                     =>
-                        Process (The_Mode, RHS);
+         when N_Delta_Aggregate
+            | N_Target_Name
+         =>
+            null;
 
-                     when N_Null =>
-                        null;
+         --  Procedure calls are handled in Check_Node
 
-                     when N_Numeric_Or_String_Literal =>
-                        Process (The_Mode, Original_Node (RHS));
+         when N_Procedure_Call_Statement =>
+            raise Program_Error;
 
-                     when others =>
-                        raise Program_Error;
+         --  Path expressions are handled before this point
 
-                  end case;
+         when N_Aggregate
+            | N_Allocator
+            | N_Expanded_Name
+            | N_Explicit_Dereference
+            | N_Extension_Aggregate
+            | N_Function_Call
+            | N_Identifier
+            | N_Indexed_Component
+            | N_Null
+            | N_Selected_Component
+            | N_Slice
+         =>
+            raise Program_Error;
 
-                  Row := Next (Row);
-               end loop;
-            end;
+         --  The following nodes are never generated in GNATprove mode
 
-         else
+         when N_Expression_With_Actions
+            | N_Reference
+            | N_Unchecked_Expression
+         =>
             raise Program_Error;
-         end if;
-      end;
-   end Check_Globals;
+      end case;
+   end Check_Expression;
 
    ----------------
    -- Check_List --
@@ -1752,7 +2254,7 @@ package body Sem_SPARK is
    -- Check_Loop_Statement --
    --------------------------
 
-   procedure Check_Loop_Statement (Loop_N : Node_Id) is
+   procedure Check_Loop_Statement (Stmt : Node_Id) is
 
       --  Local Subprograms
 
@@ -1773,11 +2275,12 @@ package body Sem_SPARK is
         (E          : Entity_Id;
          Loop_Id    : Node_Id;
          Perm       : Perm_Kind;
-         Found_Perm : Perm_Kind);
+         Found_Perm : Perm_Kind;
+         Expl       : Node_Id);
       --  A procedure that is called when the permissions found contradict
       --  the rules established by the RM at the exit of loops. This function
       --  is called with the entity, the node of the enclosing loop, the
-      --  permission that was expected and the permission found, and issues
+      --  permission that was expected, and the permission found, and issues
       --  an appropriate error message.
 
       -----------------------------------
@@ -1815,9 +2318,7 @@ package body Sem_SPARK is
          Orig_Tree : Perm_Tree_Access;
          E         : Entity_Id)
       is
-         -----------------------
-         -- Local Subprograms --
-         -----------------------
+         --  Local subprograms
 
          procedure Check_Is_Less_Restrictive_Tree_Than
            (Tree : Perm_Tree_Access;
@@ -1845,14 +2346,15 @@ package body Sem_SPARK is
          begin
             if not (Permission (Tree) >= Perm) then
                Perm_Error_Loop_Exit
-                 (E, Loop_N, Permission (Tree), Perm);
+                 (E, Stmt, Permission (Tree), Perm, Explanation (Tree));
             end if;
 
             case Kind (Tree) is
                when Entire_Object =>
                   if not (Children_Permission (Tree) >= Perm) then
                      Perm_Error_Loop_Exit
-                       (E, Loop_N, Children_Permission (Tree), Perm);
+                       (E, Stmt, Children_Permission (Tree), Perm,
+                        Explanation (Tree));
 
                   end if;
 
@@ -1874,9 +2376,6 @@ package body Sem_SPARK is
                         Comp :=
                           Perm_Tree_Maps.Get_Next (Component (Tree));
                      end loop;
-
-                     Check_Is_Less_Restrictive_Tree_Than
-                       (Other_Components (Tree), Perm, E);
                   end;
             end case;
          end Check_Is_Less_Restrictive_Tree_Than;
@@ -1893,14 +2392,15 @@ package body Sem_SPARK is
          begin
             if not (Perm >= Permission (Tree)) then
                Perm_Error_Loop_Exit
-                 (E, Loop_N, Permission (Tree), Perm);
+                 (E, Stmt, Permission (Tree), Perm, Explanation (Tree));
             end if;
 
             case Kind (Tree) is
                when Entire_Object =>
                   if not (Perm >= Children_Permission (Tree)) then
                      Perm_Error_Loop_Exit
-                       (E, Loop_N, Children_Permission (Tree), Perm);
+                       (E, Stmt, Children_Permission (Tree), Perm,
+                        Explanation (Tree));
                   end if;
 
                when Reference =>
@@ -1921,9 +2421,6 @@ package body Sem_SPARK is
                         Comp :=
                           Perm_Tree_Maps.Get_Next (Component (Tree));
                      end loop;
-
-                     Check_Is_More_Restrictive_Tree_Than
-                       (Other_Components (Tree), Perm, E);
                   end;
             end case;
          end Check_Is_More_Restrictive_Tree_Than;
@@ -1934,9 +2431,10 @@ package body Sem_SPARK is
          if not (Permission (New_Tree) <= Permission (Orig_Tree)) then
             Perm_Error_Loop_Exit
               (E          => E,
-               Loop_Id    => Loop_N,
+               Loop_Id    => Stmt,
                Perm       => Permission (New_Tree),
-               Found_Perm => Permission (Orig_Tree));
+               Found_Perm => Permission (Orig_Tree),
+               Expl       => Explanation (New_Tree));
          end if;
 
          case Kind (New_Tree) is
@@ -1954,9 +2452,10 @@ package body Sem_SPARK is
                           Children_Permission (Orig_Tree))
                   then
                      Perm_Error_Loop_Exit
-                       (E, Loop_N,
+                       (E, Stmt,
                         Children_Permission (New_Tree),
-                        Children_Permission (Orig_Tree));
+                        Children_Permission (Orig_Tree),
+                        Explanation (New_Tree));
                   end if;
 
                when Reference =>
@@ -1979,10 +2478,6 @@ package body Sem_SPARK is
                         Comp := Perm_Tree_Maps.Get_Next
                           (Component (Orig_Tree));
                      end loop;
-
-                     Check_Is_More_Restrictive_Tree_Than
-                       (Other_Components (Orig_Tree),
-                        Children_Permission (New_Tree), E);
                   end;
                end case;
 
@@ -2030,11 +2525,6 @@ package body Sem_SPARK is
                           Perm_Tree_Maps.Get_Next (Component (New_Tree));
                      end loop;
 
-                     Check_Is_Less_Restrictive_Tree_Than
-                       (Other_Components (New_Tree),
-                        Children_Permission (Orig_Tree),
-                        E);
-
                   when Record_Component =>
                      declare
 
@@ -2044,22 +2534,17 @@ package body Sem_SPARK is
                         KeyO := Perm_Tree_Maps.Get_First_Key
                           (Component (Orig_Tree));
                         while KeyO.Present loop
+                           CompN := Perm_Tree_Maps.Get
+                             (Component (New_Tree), KeyO.K);
+                           CompO := Perm_Tree_Maps.Get
+                             (Component (Orig_Tree), KeyO.K);
                            pragma Assert (CompO /= null);
 
                            Check_Is_Less_Restrictive_Tree (CompN, CompO, E);
 
                            KeyO := Perm_Tree_Maps.Get_Next_Key
                              (Component (Orig_Tree));
-                           CompN := Perm_Tree_Maps.Get
-                             (Component (New_Tree), KeyO.K);
-                           CompO := Perm_Tree_Maps.Get
-                             (Component (Orig_Tree), KeyO.K);
                         end loop;
-
-                        Check_Is_Less_Restrictive_Tree
-                          (Other_Components (New_Tree),
-                           Other_Components (Orig_Tree),
-                           E);
                      end;
 
                   when others =>
@@ -2077,20 +2562,28 @@ package body Sem_SPARK is
         (E          : Entity_Id;
          Loop_Id    : Node_Id;
          Perm       : Perm_Kind;
-         Found_Perm : Perm_Kind)
+         Found_Perm : Perm_Kind;
+         Expl       : Node_Id)
       is
       begin
-         Error_Msg_Node_2 := Loop_Id;
-         Error_Msg_N ("insufficient permission for & when exiting loop &", E);
-         Perm_Mismatch (Exp_Perm => Perm,
-                        Act_Perm => Found_Perm,
-                        N        => Loop_Id);
+         if Emit_Messages then
+            Error_Msg_Node_2 := Loop_Id;
+            Error_Msg_N
+              ("insufficient permission for & when exiting loop &", E);
+            Perm_Mismatch (Exp_Perm => Perm,
+                           Act_Perm => Found_Perm,
+                           N        => Loop_Id,
+                           Expl     => Expl);
+         end if;
       end Perm_Error_Loop_Exit;
 
       --  Local variables
 
-      Loop_Name : constant Entity_Id := Entity (Identifier (Loop_N));
+      Loop_Name : constant Entity_Id := Entity (Identifier (Stmt));
       Loop_Env  : constant Perm_Env_Access := new Perm_Env;
+      Scheme    : constant Node_Id := Iteration_Scheme (Stmt);
+
+   --  Start of processing for Check_Loop_Statement
 
    begin
       --  Save environment prior to the loop
@@ -2107,9 +2600,10 @@ package body Sem_SPARK is
       --  Otherwise, the loop exit environment remains empty until it is
       --  populated by analyzing exit statements.
 
-      if Present (Iteration_Scheme (Loop_N)) then
+      if Present (Iteration_Scheme (Stmt)) then
          declare
-            Exit_Env  : constant Perm_Env_Access := new Perm_Env;
+            Exit_Env : constant Perm_Env_Access := new Perm_Env;
+
          begin
             Copy_Env (From => Current_Perm_Env, To => Exit_Env.all);
             Set (Current_Loops_Accumulators, Loop_Name, Exit_Env);
@@ -2118,8 +2612,35 @@ package body Sem_SPARK is
 
       --  Analyze loop
 
-      Check_Node (Iteration_Scheme (Loop_N));
-      Check_List (Statements (Loop_N));
+      if Present (Scheme) then
+
+         --  Case of a WHILE loop
+
+         if Present (Condition (Scheme)) then
+            Check_Expression (Condition (Scheme), Read);
+
+         --  Case of a FOR loop
+
+         else
+            declare
+               Param_Spec : constant Node_Id :=
+                 Loop_Parameter_Specification (Scheme);
+               Iter_Spec : constant Node_Id := Iterator_Specification (Scheme);
+            begin
+               if Present (Param_Spec) then
+                  Check_Expression
+                    (Discrete_Subtype_Definition (Param_Spec), Read);
+               else
+                  Check_Expression (Name (Iter_Spec), Read);
+                  if Present (Subtype_Indication (Iter_Spec)) then
+                     Check_Expression (Subtype_Indication (Iter_Spec), Read);
+                  end if;
+               end if;
+            end;
+         end if;
+      end if;
+
+      Check_List (Statements (Stmt));
 
       --  Check that environment gets less restrictive at end of loop
 
@@ -2145,12 +2666,12 @@ package body Sem_SPARK is
 
          if Exit_Env /= null then
             Copy_Env (From => Exit_Env.all, To => Current_Perm_Env);
+            Free_Env (Loop_Env.all);
+            Free_Env (Exit_Env.all);
          else
             Copy_Env (From => Loop_Env.all, To => Current_Perm_Env);
+            Free_Env (Loop_Env.all);
          end if;
-
-         Free_Env (Loop_Env.all);
-         Free_Env (Exit_Env.all);
       end;
    end Check_Loop_Statement;
 
@@ -2159,29 +2680,69 @@ package body Sem_SPARK is
    ----------------
 
    procedure Check_Node (N : Node_Id) is
-      Mode_Before : constant Checking_Mode := Current_Checking_Mode;
-   begin
-      case Nkind (N) is
-         when N_Declaration =>
-            Check_Declaration (N);
-
-         when N_Subexpr =>
-            Check_Expression (N);
 
-         when N_Subtype_Indication =>
-            Check_Node (Constraint (N));
+      procedure Check_Subprogram_Contract (N : Node_Id);
+      --  Check the postcondition-like contracts for use of 'Old
 
-         when N_Body_Stub =>
-            Check_Node (Get_Body_From_Stub (N));
+      -------------------------------
+      -- Check_Subprogram_Contract --
+      -------------------------------
 
-         when N_Statement_Other_Than_Procedure_Call =>
+      procedure Check_Subprogram_Contract (N : Node_Id) is
+      begin
+         if Nkind (N) = N_Subprogram_Declaration
+           or else Acts_As_Spec (N)
+         then
+            declare
+               E        : constant Entity_Id := Unique_Defining_Entity (N);
+               Post     : constant Node_Id :=
+                 Get_Pragma (E, Pragma_Postcondition);
+               Cases    : constant Node_Id :=
+                 Get_Pragma (E, Pragma_Contract_Cases);
+            begin
+               Check_Old_Loop_Entry (Post);
+               Check_Old_Loop_Entry (Cases);
+            end;
+
+         elsif Nkind (N) = N_Subprogram_Body then
+            declare
+               E        : constant Entity_Id := Defining_Entity (N);
+               Ref_Post : constant Node_Id :=
+                 Get_Pragma (E, Pragma_Refined_Post);
+            begin
+               Check_Old_Loop_Entry (Ref_Post);
+            end;
+         end if;
+      end Check_Subprogram_Contract;
+
+   --  Start of processing for Check_Node
+
+   begin
+      case Nkind (N) is
+         when N_Declaration =>
+            Check_Declaration (N);
+
+         when N_Body_Stub =>
+            Check_Node (Get_Body_From_Stub (N));
+
+         when N_Statement_Other_Than_Procedure_Call =>
             Check_Statement (N);
 
+         when N_Procedure_Call_Statement =>
+            Check_Call_Statement (N);
+
          when N_Package_Body =>
-            Check_Package_Body (N);
+            if not Is_Generic_Unit (Unique_Defining_Entity (N)) then
+               Check_Package_Body (N);
+            end if;
 
-         when N_Subprogram_Body
-            | N_Entry_Body
+         when N_Subprogram_Body =>
+            if not Is_Generic_Unit (Unique_Defining_Entity (N)) then
+               Check_Subprogram_Contract (N);
+               Check_Callable_Body (N);
+            end if;
+
+         when N_Entry_Body
             | N_Task_Body
          =>
             Check_Callable_Body (N);
@@ -2190,124 +2751,22 @@ package body Sem_SPARK is
             Check_List (Declarations (N));
 
          when N_Package_Declaration =>
-            declare
-               Spec : constant Node_Id := Specification (N);
-            begin
-               Current_Checking_Mode := Read;
-               Check_List (Visible_Declarations (Spec));
-               Check_List (Private_Declarations (Spec));
-
-               Return_Declarations (Visible_Declarations (Spec));
-               Return_Declarations (Private_Declarations (Spec));
-            end;
-
-         when N_Iteration_Scheme =>
-            Current_Checking_Mode := Read;
-            Check_Node (Condition (N));
-            Check_Node (Iterator_Specification (N));
-            Check_Node (Loop_Parameter_Specification (N));
-
-         when N_Case_Expression_Alternative =>
-            Current_Checking_Mode := Read;
-            Check_List (Discrete_Choices (N));
-            Current_Checking_Mode := Mode_Before;
-            Check_Node (Expression (N));
-
-         when N_Case_Statement_Alternative =>
-            Current_Checking_Mode := Read;
-            Check_List (Discrete_Choices (N));
-            Current_Checking_Mode := Mode_Before;
-            Check_List (Statements (N));
-
-         when N_Component_Association =>
-            Check_Node (Expression (N));
+            Check_Package_Spec (N);
 
          when N_Handled_Sequence_Of_Statements =>
             Check_List (Statements (N));
 
-         when N_Parameter_Association =>
-            Check_Node (Explicit_Actual_Parameter (N));
-
-         when N_Range_Constraint =>
-            Check_Node (Range_Expression (N));
-
-         when N_Index_Or_Discriminant_Constraint =>
-            Check_List (Constraints (N));
-
-         --  Checking should not be called directly on these nodes
+         when N_Pragma =>
+            Check_Pragma (N);
 
-         when N_Abortable_Part
-            | N_Accept_Alternative
-            | N_Access_Definition
-            | N_Access_Function_Definition
-            | N_Access_Procedure_Definition
-            | N_Access_To_Object_Definition
-            | N_Aspect_Specification
-            | N_Compilation_Unit
-            | N_Compilation_Unit_Aux
-            | N_Component_Clause
-            | N_Component_Definition
-            | N_Component_List
-            | N_Constrained_Array_Definition
-            | N_Contract
-            | N_Decimal_Fixed_Point_Definition
-            | N_Defining_Character_Literal
-            | N_Defining_Identifier
-            | N_Defining_Operator_Symbol
-            | N_Defining_Program_Unit_Name
-            | N_Delay_Alternative
-            | N_Derived_Type_Definition
-            | N_Designator
-            | N_Discriminant_Association
-            | N_Discriminant_Specification
-            | N_Elsif_Part
-            | N_Entry_Body_Formal_Part
-            | N_Enumeration_Type_Definition
-            | N_Entry_Call_Alternative
-            | N_Entry_Index_Specification
-            | N_Error
-            | N_Exception_Handler
-            | N_Floating_Point_Definition
-            | N_Formal_Decimal_Fixed_Point_Definition
-            | N_Formal_Derived_Type_Definition
-            | N_Formal_Discrete_Type_Definition
-            | N_Formal_Floating_Point_Definition
-            | N_Formal_Incomplete_Type_Definition
-            | N_Formal_Modular_Type_Definition
-            | N_Formal_Ordinary_Fixed_Point_Definition
-            | N_Formal_Private_Type_Definition
-            | N_Formal_Signed_Integer_Type_Definition
-            | N_Generic_Association
-            | N_Mod_Clause
-            | N_Modular_Type_Definition
-            | N_Ordinary_Fixed_Point_Definition
-            | N_Package_Specification
-            | N_Parameter_Specification
-            | N_Pragma_Argument_Association
-            | N_Protected_Definition
-            | N_Push_Pop_xxx_Label
-            | N_Real_Range_Specification
-            | N_Record_Definition
-            | N_SCIL_Dispatch_Table_Tag_Init
-            | N_SCIL_Dispatching_Call
-            | N_SCIL_Membership_Test
-            | N_Signed_Integer_Type_Definition
-            | N_Subunit
-            | N_Task_Definition
-            | N_Terminate_Alternative
-            | N_Triggering_Alternative
-            | N_Unconstrained_Array_Definition
-            | N_Unused_At_Start
-            | N_Unused_At_End
-            | N_Variant
-            | N_Variant_Part
-         =>
-            raise Program_Error;
+         when N_Subprogram_Declaration =>
+            Check_Subprogram_Contract (N);
 
-         --  Unsupported constructs in SPARK
+         --  Attribute references in statement position are not supported in
+         --  SPARK and will be rejected by GNATprove.
 
-         when N_Iterated_Component_Association =>
-            Error_Msg_N ("unsupported construct in SPARK", N);
+         when N_Attribute_Reference =>
+            null;
 
          --  Ignored constructs for pointer checking
 
@@ -2339,10 +2798,9 @@ package body Sem_SPARK is
             | N_Others_Choice
             | N_Package_Instantiation
             | N_Package_Renaming_Declaration
-            | N_Pragma
             | N_Procedure_Instantiation
+            | N_Raise_xxx_Error
             | N_Record_Representation_Clause
-            | N_Subprogram_Declaration
             | N_Subprogram_Renaming_Declaration
             | N_Task_Type_Declaration
             | N_Use_Package_Clause
@@ -2350,201 +2808,324 @@ package body Sem_SPARK is
             | N_Use_Type_Clause
             | N_Validate_Unchecked_Conversion
             | N_Variable_Reference_Marker
+            | N_Discriminant_Association
+
+            --  ??? check whether we should do something special for
+            --  N_Discriminant_Association, or maybe raise Program_Error.
          =>
             null;
 
-         --  The following nodes are rewritten by semantic analysis
+         --  Checking should not be called directly on these nodes
 
-         when N_Single_Protected_Declaration
-            | N_Single_Task_Declaration
-         =>
+         when others =>
             raise Program_Error;
       end case;
-
-      Current_Checking_Mode := Mode_Before;
    end Check_Node;
 
+   --------------------------
+   -- Check_Old_Loop_Entry --
+   --------------------------
+
+   procedure Check_Old_Loop_Entry (N : Node_Id) is
+
+      function Check_Attribute (N : Node_Id) return Traverse_Result;
+
+      ---------------------
+      -- Check_Attribute --
+      ---------------------
+
+      function Check_Attribute (N : Node_Id) return Traverse_Result is
+         Attr_Id : Attribute_Id;
+         Aname   : Name_Id;
+         Pref    : Node_Id;
+
+      begin
+         if Nkind (N) = N_Attribute_Reference then
+            Attr_Id := Get_Attribute_Id (Attribute_Name (N));
+            Aname   := Attribute_Name (N);
+
+            if Attr_Id = Attribute_Old
+              or else Attr_Id = Attribute_Loop_Entry
+            then
+               Pref := Prefix (N);
+
+               if Is_Deep (Etype (Pref)) then
+                  if Nkind (Pref) /= N_Function_Call then
+                     if Emit_Messages then
+                        Error_Msg_Name_1 := Aname;
+                        Error_Msg_N
+                          ("prefix of % attribute must be a function call "
+                           & "(SPARK RM 3.10(14))", Pref);
+                     end if;
+
+                  elsif Is_Traversal_Function_Call (Pref) then
+                     if Emit_Messages then
+                        Error_Msg_Name_1 := Aname;
+                        Error_Msg_N
+                          ("prefix of % attribute should not call a traversal "
+                           & "function (SPARK RM 3.10(14))", Pref);
+                     end if;
+                  end if;
+               end if;
+            end if;
+         end if;
+
+         return OK;
+      end Check_Attribute;
+
+      procedure Check_All is new Traverse_Proc (Check_Attribute);
+
+   --  Start of processing for Check_Old_Loop_Entry
+
+   begin
+      Check_All (N);
+   end Check_Old_Loop_Entry;
+
    ------------------------
    -- Check_Package_Body --
    ------------------------
 
    procedure Check_Package_Body (Pack : Node_Id) is
-      Saved_Env : Perm_Env;
-      CorSp : Node_Id;
+      Save_In_Elab : constant Boolean := Inside_Elaboration;
+      Spec         : constant Node_Id :=
+        Package_Specification (Corresponding_Spec (Pack));
+      Id           : constant Entity_Id := Defining_Entity (Pack);
+      Prag         : constant Node_Id := SPARK_Pragma (Id);
+      Aux_Prag     : constant Node_Id := SPARK_Aux_Pragma (Id);
+      Saved_Env    : Perm_Env;
 
    begin
-      if Present (SPARK_Pragma (Defining_Entity (Pack, False))) then
-         if Get_SPARK_Mode_From_Annotation
-           (SPARK_Pragma (Defining_Entity (Pack))) /= Opt.On
+      if Present (Prag)
+        and then Get_SPARK_Mode_From_Annotation (Prag) = Opt.On
+      then
+         Inside_Elaboration := True;
+
+         --  Save environment and put a new one in place
+
+         Move_Env (Current_Perm_Env, Saved_Env);
+
+         --  Reanalyze package spec to have its variables in the environment
+
+         Check_List (Visible_Declarations (Spec));
+         Check_List (Private_Declarations (Spec));
+
+         --  Check declarations and statements in the special mode for
+         --  elaboration.
+
+         Check_List (Declarations (Pack));
+
+         if Present (Aux_Prag)
+           and then Get_SPARK_Mode_From_Annotation (Aux_Prag) = Opt.On
          then
-            return;
+            Check_Node (Handled_Statement_Sequence (Pack));
          end if;
-      else
-         return;
-      end if;
-
-      CorSp := Parent (Corresponding_Spec (Pack));
-      while Nkind (CorSp) /= N_Package_Specification loop
-         CorSp := Parent (CorSp);
-      end loop;
 
-      Check_List (Visible_Declarations (CorSp));
+         --  Restore the saved environment and free the current one
 
-      --  Save environment
+         Move_Env (Saved_Env, Current_Perm_Env);
 
-      Copy_Env (Current_Perm_Env,
-                Saved_Env);
+         Inside_Elaboration := Save_In_Elab;
+      end if;
+   end Check_Package_Body;
 
-      Check_List (Private_Declarations (CorSp));
+   ------------------------
+   -- Check_Package_Spec --
+   ------------------------
 
-      --  Set mode to Read, and then analyze declarations and statements
+   procedure Check_Package_Spec (Pack : Node_Id) is
+      Save_In_Elab : constant Boolean := Inside_Elaboration;
+      Spec         : constant Node_Id := Specification (Pack);
+      Id           : constant Entity_Id := Defining_Entity (Pack);
+      Prag         : constant Node_Id := SPARK_Pragma (Id);
+      Aux_Prag     : constant Node_Id := SPARK_Aux_Pragma (Id);
+      Saved_Env    : Perm_Env;
 
-      Current_Checking_Mode := Read;
+   begin
+      if Present (Prag)
+        and then Get_SPARK_Mode_From_Annotation (Prag) = Opt.On
+      then
+         Inside_Elaboration := True;
 
-      Check_List (Declarations (Pack));
-      Check_Node (Handled_Statement_Sequence (Pack));
+         --  Save environment and put a new one in place
 
-      --  Check RW for every stateful variable (i.e. in declarations)
+         Move_Env (Current_Perm_Env, Saved_Env);
 
-      Return_Declarations (Private_Declarations (CorSp));
-      Return_Declarations (Visible_Declarations (CorSp));
-      Return_Declarations (Declarations (Pack));
+         --  Check declarations in the special mode for elaboration
 
-      --  Restore previous environment (i.e. delete every nonvisible
-      --  declaration) from environment.
+         Check_List (Visible_Declarations (Spec));
 
-      Free_Env (Current_Perm_Env);
-      Copy_Env (Saved_Env,
-                Current_Perm_Env);
-   end Check_Package_Body;
+         if Present (Aux_Prag)
+           and then Get_SPARK_Mode_From_Annotation (Aux_Prag) = Opt.On
+         then
+            Check_List (Private_Declarations (Spec));
+         end if;
 
-   -----------------
-   -- Check_Param --
-   -----------------
+         --  Restore the saved environment and free the current one. As part of
+         --  the restoration, the environment of the package spec is merged in
+         --  the enclosing environment, which may be an enclosing
+         --  package/subprogram spec or body which has access to the variables
+         --  of the package spec.
 
-   procedure Check_Param (Formal : Entity_Id; Actual : Node_Id) is
-      Mode : constant Entity_Kind := Ekind (Formal);
-      Mode_Before : constant Checking_Mode := Current_Checking_Mode;
+         Merge_Env (Saved_Env, Current_Perm_Env);
 
-   begin
-      case Current_Checking_Mode is
-         when Read =>
-            case Formal_Kind'(Mode) is
-               when E_In_Parameter =>
-                  if Is_Borrowed_In (Formal) then
-                     --  Borrowed in
+         Inside_Elaboration := Save_In_Elab;
+      end if;
+   end Check_Package_Spec;
 
-                     Current_Checking_Mode := Move;
-                  else
-                     --  Observed
+   -------------------------------
+   -- Check_Parameter_Or_Global --
+   -------------------------------
 
-                     return;
-                  end if;
+   procedure Check_Parameter_Or_Global
+     (Expr       : Node_Id;
+      Typ        : Entity_Id;
+      Kind       : Formal_Kind;
+      Subp       : Entity_Id;
+      Global_Var : Boolean)
+   is
+      Mode   : Checking_Mode;
+      Status : Error_Status;
 
-               when E_Out_Parameter =>
-                  return;
+   begin
+      if not Global_Var
+        and then Is_Anonymous_Access_Type (Typ)
+      then
+         Check_Source_Of_Borrow_Or_Observe (Expr, Status);
 
-               when E_In_Out_Parameter =>
-                  --  Borrowed in out
+         if Status /= OK then
+            return;
+         end if;
+      end if;
 
-                  Current_Checking_Mode := Move;
+      case Kind is
+         when E_In_Parameter =>
 
-            end case;
+            --  Inputs of functions have R permission only
 
-            Check_Node (Actual);
+            if Ekind (Subp) = E_Function then
+               Mode := Read;
 
-         when Assign =>
-            case Formal_Kind'(Mode) is
-               when E_In_Parameter =>
-                  null;
+            --  Input global variables have R permission only
 
-               when E_Out_Parameter
-                  | E_In_Out_Parameter
-               =>
-                  --  Borrowed out or in out
+            elsif Global_Var then
+               Mode := Read;
 
-                  Process_Path (Actual);
+            --  Anonymous access to constant is an observe
 
-            end case;
+            elsif Is_Anonymous_Access_Type (Typ)
+              and then Is_Access_Constant (Typ)
+            then
+               Mode := Observe;
 
-         when others =>
-            raise Program_Error;
+            --  Other access types are a borrow
 
-      end case;
-      Current_Checking_Mode := Mode_Before;
-   end Check_Param;
+            elsif Is_Access_Type (Typ) then
+               Mode := Borrow;
 
-   --------------------------
-   -- Check_Param_Observes --
-   --------------------------
+            --  Deep types other than access types define an observe
 
-   procedure Check_Param_Observes (Formal : Entity_Id; Actual : Node_Id) is
-      Mode : constant Entity_Kind := Ekind (Formal);
-      Mode_Before : constant Checking_Mode := Current_Checking_Mode;
+            elsif Is_Deep (Typ) then
+               Mode := Observe;
 
-   begin
-      case Mode is
-         when E_In_Parameter =>
-            if not Is_Borrowed_In (Formal) then
-               --  Observed in
+            --  Otherwise the variable is read
 
-               Current_Checking_Mode := Observe;
-               Check_Node (Actual);
+            else
+               Mode := Read;
             end if;
 
-         when others =>
-            null;
+         when E_Out_Parameter =>
+            Mode := Assign;
 
+         when E_In_Out_Parameter =>
+            Mode := Move;
       end case;
 
-      Current_Checking_Mode := Mode_Before;
-   end Check_Param_Observes;
+      if Mode = Assign then
+         Check_Expression (Expr, Read_Subexpr);
+      end if;
 
-   ----------------------
-   -- Check_Param_Outs --
-   ----------------------
+      Check_Expression (Expr, Mode);
+   end Check_Parameter_Or_Global;
 
-   procedure Check_Param_Outs (Formal : Entity_Id; Actual : Node_Id) is
-      Mode : constant Entity_Kind := Ekind (Formal);
-      Mode_Before : constant Checking_Mode := Current_Checking_Mode;
+   procedure Check_Globals_Inst is
+     new Handle_Globals (Check_Parameter_Or_Global);
 
-   begin
+   procedure Check_Globals (Subp : Entity_Id) renames Check_Globals_Inst;
 
-      case Mode is
-         when E_Out_Parameter =>
-            --  Borrowed out
-            Current_Checking_Mode := Borrow_Out;
-            Check_Node (Actual);
+   ------------------
+   -- Check_Pragma --
+   ------------------
 
-         when others =>
-            null;
+   procedure Check_Pragma (Prag : Node_Id) is
+      Prag_Id : constant Pragma_Id := Get_Pragma_Id (Prag);
+      Arg1    : constant Node_Id :=
+        First (Pragma_Argument_Associations (Prag));
+      Arg2    : Node_Id := Empty;
 
-      end case;
+   begin
+      if Present (Arg1) then
+         Arg2 := Next (Arg1);
+      end if;
 
-      Current_Checking_Mode := Mode_Before;
-   end Check_Param_Outs;
+      case Prag_Id is
+         when Pragma_Check =>
+            declare
+               Expr : constant Node_Id := Expression (Arg2);
+            begin
+               Check_Expression (Expr, Read);
 
-   ----------------------
-   -- Check_Param_Read --
-   ----------------------
+               --  Prefix of Loop_Entry should be checked inside any assertion
+               --  where it may appear.
 
-   procedure Check_Param_Read (Formal : Entity_Id; Actual : Node_Id) is
-      Mode : constant Entity_Kind := Ekind (Formal);
+               Check_Old_Loop_Entry (Expr);
+            end;
 
-   begin
-      pragma Assert (Current_Checking_Mode = Read);
+         --  Prefix of Loop_Entry should be checked inside a Loop_Variant
 
-      case Formal_Kind'(Mode) is
-         when E_In_Parameter =>
-            Check_Node (Actual);
+         when Pragma_Loop_Variant =>
+            Check_Old_Loop_Entry (Prag);
 
-         when E_Out_Parameter
-            | E_In_Out_Parameter
+         --  There is no need to check contracts, as these can only access
+         --  inputs and outputs of the subprogram. Inputs are checked
+         --  independently for R permission. Outputs are checked
+         --  independently to have RW permission on exit.
+
+         --  Postconditions are checked for correct use of 'Old, but starting
+         --  from the corresponding declaration, in order to avoid dealing with
+         --  with contracts on generic subprograms, which are not handled in
+         --  GNATprove.
+
+         when Pragma_Precondition
+            | Pragma_Postcondition
+            | Pragma_Contract_Cases
+            | Pragma_Refined_Post
          =>
             null;
 
+         --  The same holds for the initial condition after package
+         --  elaboration, for the different reason that library-level
+         --  variables can only be left in RW state after elaboration.
+
+         when Pragma_Initial_Condition =>
+            null;
+
+         --  These pragmas should have been rewritten and/or removed in
+         --  GNATprove mode.
+
+         when Pragma_Assert
+            | Pragma_Assert_And_Cut
+            | Pragma_Assume
+            | Pragma_Compile_Time_Error
+            | Pragma_Compile_Time_Warning
+            | Pragma_Debug
+            | Pragma_Loop_Invariant
+         =>
+            raise Program_Error;
+
+         when others =>
+            null;
       end case;
-   end Check_Param_Read;
+   end Check_Pragma;
 
    -------------------------
    -- Check_Safe_Pointers --
@@ -2583,14 +3164,8 @@ package body Sem_SPARK is
          Reset (Current_Loops_Envs);
          Reset (Current_Loops_Accumulators);
          Reset (Current_Perm_Env);
-         Reset (Current_Initialization_Map);
       end Initialize;
 
-      --  Local variables
-
-      Prag : Node_Id;
-      --  SPARK_Mode pragma in application
-
    --  Start of processing for Check_Safe_Pointers
 
    begin
@@ -2602,294 +3177,409 @@ package body Sem_SPARK is
 
          when N_Package_Body
             | N_Package_Declaration
+            | N_Subprogram_Declaration
             | N_Subprogram_Body
          =>
-            Prag := SPARK_Pragma (Defining_Entity (N));
-            if Present (Prag) then
-               if Get_SPARK_Mode_From_Annotation (Prag) = Opt.Off then
-                  return;
-               else
-                  Check_Node (N);
-               end if;
+            declare
+               E    : constant Entity_Id := Defining_Entity (N);
+               Prag : constant Node_Id := SPARK_Pragma (E);
+               --  SPARK_Mode pragma in application
+
+            begin
+               if Ekind (Unique_Entity (E)) in Generic_Unit_Kind then
+                  null;
 
-            elsif Nkind (N) = N_Package_Body then
-               Check_List (Declarations (N));
+               elsif Present (Prag) then
+                  if Get_SPARK_Mode_From_Annotation (Prag) = Opt.On then
+                     Check_Node (N);
+                  end if;
 
-            elsif Nkind (N) = N_Package_Declaration then
-               Check_List (Private_Declarations (Specification (N)));
-               Check_List (Visible_Declarations (Specification (N)));
-            end if;
+               elsif Nkind (N) = N_Package_Body then
+                  Check_List (Declarations (N));
+
+               elsif Nkind (N) = N_Package_Declaration then
+                  Check_List (Private_Declarations (Specification (N)));
+                  Check_List (Visible_Declarations (Specification (N)));
+               end if;
+            end;
 
          when others =>
             null;
       end case;
    end Check_Safe_Pointers;
 
+   ---------------------------------------
+   -- Check_Source_Of_Borrow_Or_Observe --
+   ---------------------------------------
+
+   procedure Check_Source_Of_Borrow_Or_Observe
+     (Expr   : Node_Id;
+      Status : out Error_Status)
+   is
+      Root : Entity_Id;
+
+   begin
+      if Is_Path_Expression (Expr) then
+         Root := Get_Root_Object (Expr);
+      else
+         Root := Empty;
+      end if;
+
+      Status := OK;
+
+      --  SPARK RM 3.10(3): If the target of an assignment operation is an
+      --  object of an anonymous access-to-object type (including copy-in for
+      --  a parameter), then the source shall be a name denoting a part of a
+      --  stand-alone object, a part of a parameter, or a call to a traversal
+      --  function.
+
+      if No (Root) then
+         if Emit_Messages then
+            if Nkind (Expr) = N_Function_Call then
+               Error_Msg_N
+                 ("incorrect borrow or observe (SPARK RM 3.10(3))", Expr);
+               Error_Msg_N
+                 ("\function called must be a traversal function", Expr);
+            else
+               Error_Msg_N
+                 ("incorrect borrow or observe (SPARK RM 3.10(3))", Expr);
+               Error_Msg_N
+                 ("\expression must be part of stand-alone object or " &
+                    "parameter",
+                  Expr);
+            end if;
+         end if;
+
+         Status := Error;
+      end if;
+   end Check_Source_Of_Borrow_Or_Observe;
+
    ---------------------
    -- Check_Statement --
    ---------------------
 
    procedure Check_Statement (Stmt : Node_Id) is
-      Mode_Before : constant Checking_Mode := Current_Checking_Mode;
    begin
       case N_Statement_Other_Than_Procedure_Call'(Nkind (Stmt)) is
+
+         --  An entry call is handled like other calls
+
          when N_Entry_Call_Statement =>
             Check_Call_Statement (Stmt);
 
-         --  Move right-hand side first, and then assign left-hand side
+         --  An assignment may correspond to a move, a borrow, or an observe
 
          when N_Assignment_Statement =>
-            if Is_Deep (Etype (Expression (Stmt))) then
-               Current_Checking_Mode := Move;
-            else
-               Current_Checking_Mode := Read;
-            end if;
-
-            Check_Node (Expression (Stmt));
-            Current_Checking_Mode := Assign;
-            Check_Node (Name (Stmt));
-
-         when N_Block_Statement =>
             declare
-               Saved_Env : Perm_Env;
+               Target : constant Node_Id := Name (Stmt);
 
             begin
-               --  Save environment
+               --  Start with checking that the subexpressions of the target
+               --  path are readable, before possibly updating the permission
+               --  of these subexpressions in Check_Assignment.
 
-               Copy_Env (Current_Perm_Env,
-                                 Saved_Env);
+               Check_Expression (Target, Read_Subexpr);
 
-               --  Analyze declarations and Handled_Statement_Sequences
+               Check_Assignment (Target => Target,
+                                 Expr   => Expression (Stmt));
 
-               Current_Checking_Mode := Read;
-               Check_List (Declarations (Stmt));
-               Check_Node (Handled_Statement_Sequence (Stmt));
+               --  ??? We need a rule that forbids targets of assignment for
+               --  which the path is not known, for example when there is a
+               --  function call involved (which includes calls to traversal
+               --  functions). Otherwise there is no way to update the
+               --  corresponding path permission.
 
-               --  Restore environment
+               if No (Get_Root_Object
+                       (Target, Through_Traversal => False))
+               then
+                  if Emit_Messages then
+                     Error_Msg_N ("illegal target for assignment", Target);
+                  end if;
+                  return;
+               end if;
 
-               Free_Env (Current_Perm_Env);
-               Copy_Env (Saved_Env,
-                                 Current_Perm_Env);
+               Check_Expression (Target, Assign);
             end;
 
-         when N_Case_Statement =>
-            declare
-               Saved_Env : Perm_Env;
+         when N_Block_Statement =>
+            Check_List (Declarations (Stmt));
+            Check_Node (Handled_Statement_Sequence (Stmt));
 
-               --  Accumulator for the different branches
+            --  Remove local borrowers and observers
+
+            declare
+               Decl : Node_Id := First (Declarations (Stmt));
+               Var  : Entity_Id;
+            begin
+               while Present (Decl) loop
+                  if Nkind (Decl) = N_Object_Declaration then
+                     Var := Defining_Identifier (Decl);
+                     Remove (Current_Borrowers, Var);
+                     Remove (Current_Observers, Var);
+                  end if;
 
-               New_Env : Perm_Env;
+                  Next (Decl);
+               end loop;
+            end;
 
-               Elmt : Node_Id := First (Alternatives (Stmt));
+         when N_Case_Statement =>
+            declare
+               Alt       : Node_Id;
+               Saved_Env : Perm_Env;
+               --  Copy of environment for analysis of the different cases
+               New_Env   : Perm_Env;
+               --  Accumulator for the different cases
 
             begin
-               Current_Checking_Mode := Read;
-               Check_Node (Expression (Stmt));
-               Current_Checking_Mode := Mode_Before;
+               Check_Expression (Expression (Stmt), Read);
 
                --  Save environment
 
-               Copy_Env (Current_Perm_Env,
-                                 Saved_Env);
-
-               --  Here we have the original env in saved, current with a fresh
-               --  copy, and new aliased.
+               Copy_Env (Current_Perm_Env, Saved_Env);
 
                --  First alternative
 
-               Check_Node (Elmt);
-               Next (Elmt);
+               Alt := First (Alternatives (Stmt));
+               Check_List (Statements (Alt));
+               Next (Alt);
 
-               Copy_Env (Current_Perm_Env,
-                                 New_Env);
-               Free_Env (Current_Perm_Env);
+               --  Cleanup
+
+               Move_Env (Current_Perm_Env, New_Env);
 
                --  Other alternatives
 
-               while Present (Elmt) loop
+               while Present (Alt) loop
+
                   --  Restore environment
 
-                  Copy_Env (Saved_Env,
-                                    Current_Perm_Env);
+                  Copy_Env (Saved_Env, Current_Perm_Env);
 
-                  Check_Node (Elmt);
+                  --  Next alternative
 
-                  --  Merge Current_Perm_Env into New_Env
+                  Check_List (Statements (Alt));
+                  Next (Alt);
 
-                  Merge_Envs (New_Env,
-                                      Current_Perm_Env);
+                  --  Merge Current_Perm_Env into New_Env
 
-                  Next (Elmt);
+                  Merge_Env (Source => Current_Perm_Env, Target => New_Env);
                end loop;
 
-               --  CLEANUP
-               Copy_Env (New_Env,
-                                 Current_Perm_Env);
-
-               Free_Env (New_Env);
+               Move_Env (New_Env, Current_Perm_Env);
                Free_Env (Saved_Env);
             end;
 
-         when N_Delay_Relative_Statement =>
-            Check_Node (Expression (Stmt));
-
-         when N_Delay_Until_Statement =>
-            Check_Node (Expression (Stmt));
+         when N_Delay_Relative_Statement
+            | N_Delay_Until_Statement
+         =>
+            Check_Expression (Expression (Stmt), Read);
 
          when N_Loop_Statement =>
             Check_Loop_Statement (Stmt);
 
-         --  If deep type expression, then move, else read
-
          when N_Simple_Return_Statement =>
-            case Nkind (Expression (Stmt)) is
-               when N_Empty =>
+            declare
+               Subp : constant Entity_Id :=
+                 Return_Applies_To (Return_Statement_Entity (Stmt));
+               Expr : constant Node_Id := Expression (Stmt);
+            begin
+               if Present (Expression (Stmt)) then
                   declare
-                     --  ??? This does not take into account the fact that
-                     --  a simple return inside an extended return statement
-                     --  applies to the extended return statement.
-                     Subp : constant Entity_Id :=
-                       Return_Applies_To (Return_Statement_Entity (Stmt));
-                  begin
-                     Return_Parameters (Subp);
-                     Return_Globals (Subp);
-                  end;
+                     Return_Typ : constant Entity_Id :=
+                       Etype (Expression (Stmt));
 
-               when others =>
-                  if Is_Deep (Etype (Expression (Stmt))) then
-                     Current_Checking_Mode := Move;
-                  elsif Is_Shallow (Etype (Expression (Stmt))) then
-                     Current_Checking_Mode := Read;
-                  else
-                     raise Program_Error;
-                  end if;
+                  begin
+                     --  SPARK RM 3.10(5): A return statement that applies
+                     --  to a traversal function that has an anonymous
+                     --  access-to-constant (respectively, access-to-variable)
+                     --  result type, shall return either the literal null
+                     --  or an access object denoted by a direct or indirect
+                     --  observer (respectively, borrower) of the traversed
+                     --  parameter.
+
+                     if Is_Anonymous_Access_Type (Return_Typ) then
+                        pragma Assert (Is_Traversal_Function (Subp));
+
+                        if Nkind (Expr) /= N_Null then
+                           declare
+                              Expr_Root : constant Entity_Id :=
+                                Get_Root_Object (Expr, Is_Traversal => True);
+                              Param     : constant Entity_Id :=
+                                First_Formal (Subp);
+                           begin
+                              if Param /= Expr_Root and then Emit_Messages then
+                                 Error_Msg_NE
+                                   ("returned value must be rooted in "
+                                    & "traversed parameter & "
+                                    & "(SPARK RM 3.10(5))",
+                                    Stmt, Param);
+                              end if;
+                           end;
+                        end if;
 
-                  Check_Node (Expression (Stmt));
-            end case;
+                     --  Move expression to caller
 
-         when N_Extended_Return_Statement =>
-            Check_List (Return_Object_Declarations (Stmt));
-            Check_Node (Handled_Statement_Sequence (Stmt));
+                     elsif Is_Deep (Return_Typ) then
 
-            Return_Declarations (Return_Object_Declarations (Stmt));
+                        if Is_Path_Expression (Expr) then
+                           Check_Expression (Expr, Move);
 
-            declare
-               --  ??? This does not take into account the fact that a simple
-               --  return inside an extended return statement applies to the
-               --  extended return statement.
-               Subp : constant Entity_Id :=
+                        else
+                           if Emit_Messages then
+                              Error_Msg_N
+                                ("expression not allowed as source of move",
+                                 Expr);
+                           end if;
+                           return;
+                        end if;
+
+                     else
+                        Check_Expression (Expr, Read);
+                     end if;
+
+                     if Ekind_In (Subp, E_Procedure, E_Entry)
+                       and then not No_Return (Subp)
+                     then
+                        Return_Parameters (Subp);
+                        Return_Globals (Subp);
+                     end if;
+                  end;
+               end if;
+            end;
+
+         when N_Extended_Return_Statement =>
+            declare
+               Subp  : constant Entity_Id :=
                  Return_Applies_To (Return_Statement_Entity (Stmt));
+               Decls : constant List_Id := Return_Object_Declarations (Stmt);
+               Decl  : constant Node_Id := Last_Non_Pragma (Decls);
+               Obj   : constant Entity_Id := Defining_Identifier (Decl);
+               Perm  : Perm_Kind;
+
             begin
-               Return_Parameters (Subp);
-               Return_Globals (Subp);
+               --  SPARK RM 3.10(5): return statement of traversal function
+
+               if Is_Traversal_Function (Subp) and then Emit_Messages then
+                  Error_Msg_N
+                    ("extended return cannot apply to a traversal function",
+                     Stmt);
+               end if;
+
+               Check_List (Return_Object_Declarations (Stmt));
+               Check_Node (Handled_Statement_Sequence (Stmt));
+
+               if Is_Deep (Etype (Obj)) then
+                  Perm := Get_Perm (Obj);
+
+                  if Perm /= Read_Write then
+                     Perm_Error (Decl, Read_Write, Perm,
+                                 Expl => Get_Expl (Obj));
+                  end if;
+               end if;
+
+               if Ekind_In (Subp, E_Procedure, E_Entry)
+                 and then not No_Return (Subp)
+               then
+                  Return_Parameters (Subp);
+                  Return_Globals (Subp);
+               end if;
             end;
 
-         --  Merge the current_Perm_Env with the accumulator for the given loop
+         --  On loop exit, merge the current permission environment with the
+         --  accumulator for the given loop.
 
          when N_Exit_Statement =>
             declare
-               Loop_Name : constant Entity_Id := Loop_Of_Exit (Stmt);
-
+               Loop_Name         : constant Entity_Id := Loop_Of_Exit (Stmt);
                Saved_Accumulator : constant Perm_Env_Access :=
                  Get (Current_Loops_Accumulators, Loop_Name);
-
-               Environment_Copy : constant Perm_Env_Access :=
+               Environment_Copy  : constant Perm_Env_Access :=
                  new Perm_Env;
             begin
-
-               Copy_Env (Current_Perm_Env,
-                                 Environment_Copy.all);
+               Copy_Env (Current_Perm_Env, Environment_Copy.all);
 
                if Saved_Accumulator = null then
                   Set (Current_Loops_Accumulators,
                        Loop_Name, Environment_Copy);
                else
-                  Merge_Envs (Saved_Accumulator.all,
-                                      Environment_Copy.all);
+                  Merge_Env (Source => Environment_Copy.all,
+                             Target => Saved_Accumulator.all);
+                  --  ??? Either free Environment_Copy, or change the type
+                  --  of loop accumulators to directly store permission
+                  --  environments.
                end if;
             end;
 
-         --  Copy environment, run on each branch, and then merge
+         --  On branches, analyze each branch independently on a fresh copy of
+         --  the permission environment, then merge the resulting permission
+         --  environments.
 
          when N_If_Statement =>
             declare
                Saved_Env : Perm_Env;
-
+               New_Env   : Perm_Env;
                --  Accumulator for the different branches
 
-               New_Env : Perm_Env;
-
             begin
-
-               Check_Node (Condition (Stmt));
+               Check_Expression (Condition (Stmt), Read);
 
                --  Save environment
 
-               Copy_Env (Current_Perm_Env,
-                                 Saved_Env);
-
-               --  Here we have the original env in saved, current with a fresh
-               --  copy.
+               Copy_Env (Current_Perm_Env, Saved_Env);
 
-               --  THEN PART
+               --  THEN branch
 
                Check_List (Then_Statements (Stmt));
+               Move_Env (Current_Perm_Env, New_Env);
 
-               Copy_Env (Current_Perm_Env,
-                                 New_Env);
+               --  ELSIF branches
 
-               Free_Env (Current_Perm_Env);
-
-               --  Here the new_environment contains curr env after then block
-
-               --  ELSIF part
                declare
-                  Elmt : Node_Id;
-
+                  Branch : Node_Id;
                begin
-                  Elmt := First (Elsif_Parts (Stmt));
-                  while Present (Elmt) loop
-                     --  Transfer into accumulator, and restore from save
-
-                     Copy_Env (Saved_Env,
-                                       Current_Perm_Env);
+                  Branch := First (Elsif_Parts (Stmt));
+                  while Present (Branch) loop
 
-                     Check_Node (Condition (Elmt));
-                     Check_List (Then_Statements (Stmt));
+                     --  Restore current permission environment
 
-                     --  Merge Current_Perm_Env into New_Env
+                     Copy_Env (Saved_Env, Current_Perm_Env);
+                     Check_Expression (Condition (Branch), Read);
+                     Check_List (Then_Statements (Branch));
 
-                     Merge_Envs (New_Env,
-                                         Current_Perm_Env);
+                     --  Merge current permission environment
 
-                     Next (Elmt);
+                     Merge_Env (Source => Current_Perm_Env, Target => New_Env);
+                     Next (Branch);
                   end loop;
                end;
 
-               --  ELSE part
+               --  ELSE branch
 
-               --  Restore environment before if
-
-               Copy_Env (Saved_Env,
-                                 Current_Perm_Env);
-
-               --  Here new environment contains the environment after then and
-               --  current the fresh copy of old one.
+               --  Restore current permission environment
 
+               Copy_Env (Saved_Env, Current_Perm_Env);
                Check_List (Else_Statements (Stmt));
 
-               Merge_Envs (New_Env,
-                                   Current_Perm_Env);
+               --  Merge current permission environment
 
-               --  CLEANUP
+               Merge_Env (Source => Current_Perm_Env, Target => New_Env);
 
-               Copy_Env (New_Env,
-                                 Current_Perm_Env);
+               --  Cleanup
 
-               Free_Env (New_Env);
+               Move_Env (New_Env, Current_Perm_Env);
                Free_Env (Saved_Env);
             end;
 
+         --  We should ideally ignore the branch after raising an exception,
+         --  so that it is not taken into account in merges. For now, just
+         --  propagate the current environment.
+
+         when N_Raise_Statement =>
+            null;
+
+         when N_Null_Statement =>
+            null;
+
          --  Unsupported constructs in SPARK
 
          when N_Abort_Statement
@@ -2901,13 +3591,6 @@ package body Sem_SPARK is
             | N_Requeue_Statement
             | N_Selective_Accept
             | N_Timed_Entry_Call
-         =>
-            Error_Msg_N ("unsupported construct in SPARK", Stmt);
-
-         --  Ignored constructs for pointer checking
-
-         when N_Null_Statement
-            | N_Raise_Statement
          =>
             null;
 
@@ -2920,225 +3603,334 @@ package body Sem_SPARK is
       end case;
    end Check_Statement;
 
-   --------------
-   -- Get_Perm --
-   --------------
+   -------------------------
+   -- Check_Type_Legality --
+   -------------------------
+
+   procedure Check_Type_Legality
+     (Typ   : Entity_Id;
+      Force : Boolean;
+      Legal : in out Boolean)
+   is
+      function Original_Emit_Messages return Boolean renames Emit_Messages;
+
+      function Emit_Messages return Boolean;
+      --  Local wrapper around generic formal parameter Emit_Messages, to
+      --  check the value of parameter Force before calling the original
+      --  Emit_Messages, and setting Legal to False.
+
+      -------------------
+      -- Emit_Messages --
+      -------------------
+
+      function Emit_Messages return Boolean is
+      begin
+         Legal := False;
+         return Force and then Original_Emit_Messages;
+      end Emit_Messages;
+
+      --  Local variables
+
+      Check_Typ : constant Entity_Id := Retysp (Typ);
 
-   function Get_Perm (N : Node_Id) return Perm_Kind is
-      Tree_Or_Perm : constant Perm_Or_Tree := Get_Perm_Or_Tree (N);
+   --  Start of processing for Check_Type_Legality
 
    begin
-      case Tree_Or_Perm.R is
-         when Folded =>
-            return Tree_Or_Perm.Found_Permission;
+      case Type_Kind'(Ekind (Check_Typ)) is
+         when Access_Kind =>
+            case Access_Kind'(Ekind (Check_Typ)) is
+               when E_Access_Type
+                  | E_Anonymous_Access_Type
+               =>
+                  null;
+               when E_Access_Subtype =>
+                  Check_Type_Legality (Base_Type (Check_Typ), Force, Legal);
+               when E_Access_Attribute_Type =>
+                  if Emit_Messages then
+                     Error_Msg_N ("access attribute not allowed in SPARK",
+                                  Check_Typ);
+                  end if;
+               when E_Allocator_Type =>
+                  if Emit_Messages then
+                     Error_Msg_N ("missing type resolution", Check_Typ);
+                  end if;
+               when E_General_Access_Type =>
+                  if Emit_Messages then
+                     Error_Msg_NE
+                       ("general access type & not allowed in SPARK",
+                        Check_Typ, Check_Typ);
+                  end if;
+               when Access_Subprogram_Kind =>
+                  if Emit_Messages then
+                     Error_Msg_NE
+                       ("access to subprogram type & not allowed in SPARK",
+                        Check_Typ, Check_Typ);
+                  end if;
+            end case;
 
-         when Unfolded =>
-            pragma Assert (Tree_Or_Perm.Tree_Access /= null);
-            return Permission (Tree_Or_Perm.Tree_Access);
+         when E_Array_Type
+            | E_Array_Subtype
+         =>
+            Check_Type_Legality (Component_Type (Check_Typ), Force, Legal);
 
-         --  We encoutered a function call, hence the memory area is fresh,
-         --  which means that the association permission is RW.
+         when Record_Kind =>
+            if Is_Deep (Check_Typ)
+              and then (Is_Tagged_Type (Check_Typ)
+                        or else Is_Class_Wide_Type (Check_Typ))
+            then
+               if Emit_Messages then
+                  Error_Msg_NE
+                    ("tagged type & cannot be owning in SPARK",
+                     Check_Typ, Check_Typ);
+               end if;
 
-         when Function_Call =>
-            return Read_Write;
+            else
+               declare
+                  Comp : Entity_Id;
+               begin
+                  Comp := First_Component_Or_Discriminant (Check_Typ);
+                  while Present (Comp) loop
+
+                     --  Ignore components which are not visible in SPARK
+
+                     if Component_Is_Visible_In_SPARK (Comp) then
+                        Check_Type_Legality (Etype (Comp), Force, Legal);
+                     end if;
+                     Next_Component_Or_Discriminant (Comp);
+                  end loop;
+               end;
+            end if;
+
+         when Scalar_Kind
+            | E_String_Literal_Subtype
+            | Protected_Kind
+            | Task_Kind
+            | Incomplete_Kind
+            | E_Exception_Type
+            | E_Subprogram_Type
+         =>
+            null;
+
+         --  Do not check type whose full view is not SPARK
 
+         when E_Private_Type
+            | E_Private_Subtype
+            | E_Limited_Private_Type
+            | E_Limited_Private_Subtype
+         =>
+            null;
       end case;
-   end Get_Perm;
+   end Check_Type_Legality;
 
-   ----------------------
-   -- Get_Perm_Or_Tree --
-   ----------------------
+   --------------
+   -- Get_Expl --
+   --------------
 
-   function Get_Perm_Or_Tree (N : Node_Id) return Perm_Or_Tree is
+   function Get_Expl (N : Node_Or_Entity_Id) return Node_Id is
    begin
-      case Nkind (N) is
+      --  Special case for the object declared in an extended return statement
 
-         --  Base identifier. Normally those are the roots of the trees stored
-         --  in the permission environment.
+      if Nkind (N) = N_Defining_Identifier then
+         declare
+            C : constant Perm_Tree_Access :=
+              Get (Current_Perm_Env, Unique_Entity (N));
+         begin
+            pragma Assert (C /= null);
+            return Explanation (C);
+         end;
 
-         when N_Defining_Identifier =>
-            raise Program_Error;
+      --  The expression is a call to a traversal function
 
-         when N_Identifier
-            | N_Expanded_Name
-         =>
-            declare
-               P : constant Entity_Id := Entity (N);
+      elsif Is_Traversal_Function_Call (N) then
+         return N;
 
-               C : constant Perm_Tree_Access :=
-                 Get (Current_Perm_Env, Unique_Entity (P));
+      --  The expression is directly rooted in an object
 
-            begin
-               --  Setting the initialization map to True, so that this
-               --  variable cannot be ignored anymore when looking at end
-               --  of elaboration of package.
+      elsif Present (Get_Root_Object (N, Through_Traversal => False)) then
+         declare
+            Tree_Or_Perm : constant Perm_Or_Tree := Get_Perm_Or_Tree (N);
+         begin
+            case Tree_Or_Perm.R is
+               when Folded =>
+                  return Tree_Or_Perm.Explanation;
 
-               Set (Current_Initialization_Map, Unique_Entity (P), True);
+               when Unfolded =>
+                  pragma Assert (Tree_Or_Perm.Tree_Access /= null);
+                  return Explanation (Tree_Or_Perm.Tree_Access);
+            end case;
+         end;
 
-               if C = null then
-                  --  No null possible here, there are no parents for the path.
-                  --  This means we are using a global variable without adding
-                  --  it in environment with a global aspect.
+      --  The expression is a function call, an allocation, or null
 
-                  Illegal_Global_Usage (N);
-               else
-                  return (R => Unfolded, Tree_Access => C);
-               end if;
-            end;
+      else
+         return N;
+      end if;
+   end Get_Expl;
 
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
-         =>
-            return Get_Perm_Or_Tree (Expression (N));
+   -----------------------------------
+   -- Get_Observed_Or_Borrowed_Expr --
+   -----------------------------------
+
+   function Get_Observed_Or_Borrowed_Expr (Expr : Node_Id) return Node_Id is
+   begin
+      if Is_Traversal_Function_Call (Expr) then
+         return First_Actual (Expr);
+      else
+         return Expr;
+      end if;
+   end Get_Observed_Or_Borrowed_Expr;
 
-         --  Happening when we try to get the permission of a variable that
-         --  is a formal parameter. We get instead the defining identifier
-         --  associated with the parameter (which is the one that has been
-         --  stored for indexing).
+   --------------
+   -- Get_Perm --
+   --------------
 
-         when N_Parameter_Specification =>
-            return Get_Perm_Or_Tree (Defining_Identifier (N));
+   function Get_Perm (N : Node_Or_Entity_Id) return Perm_Kind is
+   begin
+      --  Special case for the object declared in an extended return statement
 
-         --  We get the permission tree of its prefix, and then get either the
-         --  subtree associated with that specific selection, or if we have a
-         --  leaf that folds its children, we take the children's permission
-         --  and return it using the discriminant Folded.
+      if Nkind (N) = N_Defining_Identifier then
+         declare
+            C : constant Perm_Tree_Access :=
+              Get (Current_Perm_Env, Unique_Entity (N));
+         begin
+            pragma Assert (C /= null);
+            return Permission (C);
+         end;
 
-         when N_Selected_Component =>
-            declare
-               C : constant Perm_Or_Tree :=
-                 Get_Perm_Or_Tree (Prefix (N));
+      --  The expression is a call to a traversal function
 
-            begin
-               case C.R is
-                  when Folded
-                     | Function_Call
-                  =>
-                     return C;
+      elsif Is_Traversal_Function_Call (N) then
+         declare
+            Callee : constant Entity_Id := Get_Called_Entity (N);
+         begin
+            if Is_Access_Constant (Etype (Callee)) then
+               return Read_Only;
+            else
+               return Read_Write;
+            end if;
+         end;
 
-                  when Unfolded =>
-                     pragma Assert (C.Tree_Access /= null);
-
-                     pragma Assert (Kind (C.Tree_Access) = Entire_Object
-                                    or else
-                                    Kind (C.Tree_Access) = Record_Component);
-
-                     if Kind (C.Tree_Access) = Record_Component then
-                        declare
-                           Selected_Component : constant Entity_Id :=
-                             Entity (Selector_Name (N));
-
-                           Selected_C : constant Perm_Tree_Access :=
-                             Perm_Tree_Maps.Get
-                               (Component (C.Tree_Access), Selected_Component);
-
-                        begin
-                           if Selected_C = null then
-                              return (R => Unfolded,
-                                      Tree_Access =>
-                                        Other_Components (C.Tree_Access));
-                           else
-                              return (R => Unfolded,
-                                      Tree_Access => Selected_C);
-                           end if;
-                        end;
-                     elsif Kind (C.Tree_Access) = Entire_Object then
-                        return (R => Folded, Found_Permission =>
-                                  Children_Permission (C.Tree_Access));
-                     else
-                        raise Program_Error;
-                     end if;
-               end case;
-            end;
+      --  The expression is directly rooted in an object
 
-         --  We get the permission tree of its prefix, and then get either the
-         --  subtree associated with that specific selection, or if we have a
-         --  leaf that folds its children, we take the children's permission
-         --  and return it using the discriminant Folded.
+      elsif Present (Get_Root_Object (N, Through_Traversal => False)) then
+         declare
+            Tree_Or_Perm : constant Perm_Or_Tree := Get_Perm_Or_Tree (N);
+         begin
+            case Tree_Or_Perm.R is
+               when Folded =>
+                  return Tree_Or_Perm.Found_Permission;
 
-         when N_Indexed_Component
-            | N_Slice
-         =>
-            declare
-               C : constant Perm_Or_Tree :=
-                 Get_Perm_Or_Tree (Prefix (N));
+               when Unfolded =>
+                  pragma Assert (Tree_Or_Perm.Tree_Access /= null);
+                  return Permission (Tree_Or_Perm.Tree_Access);
+            end case;
+         end;
 
-            begin
-               case C.R is
-                  when Folded
-                     | Function_Call
-                  =>
-                     return C;
+      --  The expression is a function call, an allocation, or null
 
-                  when Unfolded =>
-                     pragma Assert (C.Tree_Access /= null);
+      else
+         return Read_Write;
+      end if;
+   end Get_Perm;
+
+   ----------------------
+   -- Get_Perm_Or_Tree --
+   ----------------------
 
-                     pragma Assert (Kind (C.Tree_Access) = Entire_Object
-                                    or else
-                                    Kind (C.Tree_Access) = Array_Component);
+   function Get_Perm_Or_Tree (N : Node_Id) return Perm_Or_Tree is
+   begin
+      case Nkind (N) is
 
-                     if Kind (C.Tree_Access) = Array_Component then
-                        pragma Assert (Get_Elem (C.Tree_Access) /= null);
+         when N_Expanded_Name
+            | N_Identifier
+         =>
+            declare
+               C : constant Perm_Tree_Access :=
+                 Get (Current_Perm_Env, Unique_Entity (Entity (N)));
+            begin
+               --  Except during elaboration, the root object should have been
+               --  declared and entered into the current permission
+               --  environment.
 
-                        return (R => Unfolded,
-                                Tree_Access => Get_Elem (C.Tree_Access));
-                     elsif Kind (C.Tree_Access) = Entire_Object then
-                        return (R => Folded, Found_Permission =>
-                                  Children_Permission (C.Tree_Access));
-                     else
-                        raise Program_Error;
-                     end if;
-               end case;
+               if not Inside_Elaboration
+                 and then C = null
+               then
+                  Illegal_Global_Usage (N, N);
+               end if;
+
+               return (R => Unfolded, Tree_Access => C);
             end;
 
-         --  We get the permission tree of its prefix, and then get either the
-         --  subtree associated with that specific selection, or if we have a
-         --  leaf that folds its children, we take the children's permission
-         --  and return it using the discriminant Folded.
+         --  For a nonterminal path, we get the permission tree of its
+         --  prefix, and then get the subtree associated with the extension,
+         --  if unfolded. If folded, we return the permission associated with
+         --  children.
 
-         when N_Explicit_Dereference =>
+         when N_Explicit_Dereference
+            | N_Indexed_Component
+            | N_Selected_Component
+            | N_Slice
+         =>
             declare
-               C : constant Perm_Or_Tree :=
-                 Get_Perm_Or_Tree (Prefix (N));
-
+               C : constant Perm_Or_Tree := Get_Perm_Or_Tree (Prefix (N));
             begin
                case C.R is
-                  when Folded
-                     | Function_Call
-                  =>
-                     return C;
 
-                  when Unfolded =>
-                     pragma Assert (C.Tree_Access /= null);
+                  --  Some earlier prefix was already folded, return the
+                  --  permission found.
 
-                     pragma Assert (Kind (C.Tree_Access) = Entire_Object
-                                    or else
-                                    Kind (C.Tree_Access) = Reference);
+                  when Folded =>
+                     return C;
 
-                     if Kind (C.Tree_Access) = Reference then
-                        if Get_All (C.Tree_Access) = null then
-                           --  Hash_Table_Error
-                           raise Program_Error;
-                        else
-                           return
-                             (R => Unfolded,
-                              Tree_Access => Get_All (C.Tree_Access));
-                        end if;
-                     elsif Kind (C.Tree_Access) = Entire_Object then
-                        return (R => Folded, Found_Permission =>
-                                  Children_Permission (C.Tree_Access));
-                     else
-                        raise Program_Error;
-                     end if;
+                  when Unfolded =>
+                     case Kind (C.Tree_Access) is
+
+                        --  If the prefix tree is already folded, return the
+                        --  children permission.
+
+                        when Entire_Object =>
+                           return (R                => Folded,
+                                   Found_Permission =>
+                                      Children_Permission (C.Tree_Access),
+                                   Explanation      =>
+                                      Explanation (C.Tree_Access));
+
+                        when Reference =>
+                           pragma Assert (Nkind (N) = N_Explicit_Dereference);
+                           return (R           => Unfolded,
+                                   Tree_Access => Get_All (C.Tree_Access));
+
+                        when Record_Component =>
+                           pragma Assert (Nkind (N) = N_Selected_Component);
+                           declare
+                              Comp : constant Entity_Id :=
+                                Original_Record_Component
+                                  (Entity (Selector_Name (N)));
+                              D : constant Perm_Tree_Access :=
+                                Perm_Tree_Maps.Get
+                                  (Component (C.Tree_Access), Comp);
+                           begin
+                              pragma Assert (D /= null);
+                              return (R           => Unfolded,
+                                      Tree_Access => D);
+                           end;
+
+                        when Array_Component =>
+                           pragma Assert (Nkind (N) = N_Indexed_Component
+                                            or else
+                                          Nkind (N) = N_Slice);
+                           pragma Assert (Get_Elem (C.Tree_Access) /= null);
+                           return (R => Unfolded,
+                                   Tree_Access => Get_Elem (C.Tree_Access));
+                     end case;
                end case;
             end;
 
-         --  The name contains a function call, hence the given path is always
-         --  new. We do not have to check for anything.
-
-         when N_Function_Call =>
-            return (R => Function_Call);
+         when N_Qualified_Expression
+            | N_Type_Conversion
+            | N_Unchecked_Type_Conversion
+         =>
+            return Get_Perm_Or_Tree (Expression (N));
 
          when others =>
             raise Program_Error;
@@ -3149,295 +3941,169 @@ package body Sem_SPARK is
    -- Get_Perm_Tree --
    -------------------
 
-   function Get_Perm_Tree
-     (N : Node_Id)
-       return Perm_Tree_Access
-   is
+   function Get_Perm_Tree (N : Node_Id) return Perm_Tree_Access is
    begin
-      case Nkind (N) is
-
-         --  Base identifier. Normally those are the roots of the trees stored
-         --  in the permission environment.
+      return Set_Perm_Prefixes (N, None, Empty);
+   end Get_Perm_Tree;
 
-         when N_Defining_Identifier =>
-            raise Program_Error;
+   ---------------------
+   -- Get_Root_Object --
+   ---------------------
 
-         when N_Identifier
-            | N_Expanded_Name
-         =>
-            declare
-               P : constant Node_Id := Entity (N);
+   function Get_Root_Object
+     (Expr              : Node_Id;
+      Through_Traversal : Boolean := True;
+      Is_Traversal      : Boolean := False) return Entity_Id
+   is
+      function GRO (Expr : Node_Id) return Entity_Id;
+      --  Local wrapper on the actual function, to propagate the values of
+      --  optional parameters.
 
-               C : constant Perm_Tree_Access :=
-                 Get (Current_Perm_Env, Unique_Entity (P));
+      ---------
+      -- GRO --
+      ---------
 
-            begin
-               --  Setting the initialization map to True, so that this
-               --  variable cannot be ignored anymore when looking at end
-               --  of elaboration of package.
+      function GRO (Expr : Node_Id) return Entity_Id is
+      begin
+         return Get_Root_Object (Expr, Through_Traversal, Is_Traversal);
+      end GRO;
 
-               Set (Current_Initialization_Map, Unique_Entity (P), True);
+      Get_Root_Object : Boolean;
+      pragma Unmodified (Get_Root_Object);
+      --  Local variable to mask the name of function Get_Root_Object, to
+      --  prevent direct call. Instead GRO wrapper should be called.
 
-               if C = null then
-                  --  No null possible here, there are no parents for the path.
-                  --  This means we are using a global variable without adding
-                  --  it in environment with a global aspect.
+   --  Start of processing for Get_Root_Object
 
-                  Illegal_Global_Usage (N);
-               else
-                  return C;
-               end if;
-            end;
+   begin
+      if not Is_Subpath_Expression (Expr, Is_Traversal) then
+         if Emit_Messages then
+            Error_Msg_N ("name expected here for path", Expr);
+         end if;
+         return Empty;
+      end if;
 
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
+      case Nkind (Expr) is
+         when N_Expanded_Name
+            | N_Identifier
          =>
-            return Get_Perm_Tree (Expression (N));
+            return Entity (Expr);
 
-         when N_Parameter_Specification =>
-            return Get_Perm_Tree (Defining_Identifier (N));
-
-         --  We get the permission tree of its prefix, and then get either the
-         --  subtree associated with that specific selection, or if we have a
-         --  leaf that folds its children, we unroll it in one step.
+         when N_Explicit_Dereference
+            | N_Indexed_Component
+            | N_Selected_Component
+            | N_Slice
+         =>
+            return GRO (Prefix (Expr));
 
-         when N_Selected_Component =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Get_Perm_Tree (Prefix (N));
+         --  There is no root object for an (extension) aggregate, allocator,
+         --  concat, or NULL.
 
-            begin
-               if C = null then
-                  --  If null then it means we went through a function call
+         when N_Aggregate
+            | N_Allocator
+            | N_Extension_Aggregate
+            | N_Null
+            | N_Op_Concat
+         =>
+            return Empty;
 
-                  return null;
-               end if;
+         --  In the case of a call to a traversal function, the root object is
+         --  the root of the traversed parameter. Otherwise there is no root
+         --  object.
 
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Record_Component);
+         when N_Function_Call =>
+            if Through_Traversal
+              and then Is_Traversal_Function_Call (Expr)
+            then
+               return GRO (First_Actual (Expr));
+            else
+               return Empty;
+            end if;
 
-               if Kind (C) = Record_Component then
-                  --  The tree is unfolded. We just return the subtree.
+         when N_Qualified_Expression
+            | N_Type_Conversion
+            | N_Unchecked_Type_Conversion
+         =>
+            return GRO (Expression (Expr));
 
-                  declare
-                     Selected_Component : constant Entity_Id :=
-                       Entity (Selector_Name (N));
-                     Selected_C : constant Perm_Tree_Access :=
-                       Perm_Tree_Maps.Get
-                         (Component (C), Selected_Component);
+         when N_Attribute_Reference =>
+            pragma Assert
+              (Get_Attribute_Id (Attribute_Name (Expr)) =
+                 Attribute_Loop_Entry
+               or else
+               Get_Attribute_Id (Attribute_Name (Expr)) =
+                 Attribute_Update
+               or else Get_Attribute_Id (Attribute_Name (Expr)) =
+                 Attribute_Image);
+            return Empty;
 
-                  begin
-                     if Selected_C = null then
-                        return Other_Components (C);
+         when N_If_Expression =>
+            if Is_Traversal then
+               declare
+                  Cond      : constant Node_Id := First (Expressions (Expr));
+                  Then_Part : constant Node_Id := Next (Cond);
+                  Else_Part : constant Node_Id := Next (Then_Part);
+                  Then_Root : constant Entity_Id := GRO (Then_Part);
+                  Else_Root : constant Entity_Id := GRO (Else_Part);
+               begin
+                  if Nkind (Then_Part) = N_Null then
+                     return Else_Root;
+                  elsif Nkind (Else_Part) = N_Null then
+                     return Then_Part;
+                  elsif Then_Root = Else_Root then
+                     return Then_Root;
+                  else
+                     if Emit_Messages then
+                        Error_Msg_N
+                          ("same name expected here in each branch", Expr);
                      end if;
-
-                     return Selected_C;
-                  end;
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with
-                     --  Record_Component.
-
-                     Elem : Node_Id;
-
-                     --  Create the unrolled nodes
-
-                     Son : Perm_Tree_Access;
-
-                     Child_Perm : constant Perm_Kind :=
-                       Children_Permission (C);
-
-                  begin
-
-                     --  We change the current node from Entire_Object to
-                     --  Record_Component with same permission and an empty
-                     --  hash table as component list.
-
-                     C.all.Tree :=
-                       (Kind             => Record_Component,
-                        Is_Node_Deep     => Is_Node_Deep (C),
-                        Permission       => Permission (C),
-                        Component        => Perm_Tree_Maps.Nil,
-                        Other_Components =>
-                           new Perm_Tree_Wrapper'
-                          (Tree =>
-                               (Kind                => Entire_Object,
-                                --  Is_Node_Deep is true, to be conservative
-                                Is_Node_Deep        => True,
-                                Permission          => Child_Perm,
-                                Children_Permission => Child_Perm)
-                          )
-                       );
-
-                     --  We fill the hash table with all sons of the record,
-                     --  with basic Entire_Objects nodes.
-                     Elem := First_Component_Or_Discriminant
-                       (Etype (Prefix (N)));
-
-                     while Present (Elem) loop
-                        Son := new Perm_Tree_Wrapper'
-                          (Tree =>
-                             (Kind                => Entire_Object,
-                              Is_Node_Deep        => Is_Deep (Etype (Elem)),
-                              Permission          => Child_Perm,
-                              Children_Permission => Child_Perm));
-
-                        Perm_Tree_Maps.Set
-                          (C.all.Tree.Component, Elem, Son);
-
-                        Next_Component_Or_Discriminant (Elem);
-                     end loop;
-
-                     --  we return the tree to the sons, so that the recursion
-                     --  can continue.
-
-                     declare
-                        Selected_Component : constant Entity_Id :=
-                          Entity (Selector_Name (N));
-
-                        Selected_C : constant Perm_Tree_Access :=
-                          Perm_Tree_Maps.Get
-                            (Component (C), Selected_Component);
-
-                     begin
-                        pragma Assert (Selected_C /= null);
-
-                        return Selected_C;
-                     end;
-
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
-
-         --  We set the permission tree of its prefix, and then we extract from
-         --  the returned pointer the subtree. If folded, we unroll the tree at
-         --  one step.
-
-         when N_Indexed_Component
-            | N_Slice
-         =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Get_Perm_Tree (Prefix (N));
-
-            begin
-               if C = null then
-                  --  If null then we went through a function call
-
-                  return null;
-               end if;
-
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Array_Component);
-
-               if Kind (C) = Array_Component then
-                  --  The tree is unfolded. We just return the elem subtree
-
-                  pragma Assert (Get_Elem (C) = null);
-
-                  return Get_Elem (C);
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace node with Array_Component.
-
-                     Son : Perm_Tree_Access;
-
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Node_Deep (C),
-                           Permission          => Children_Permission (C),
-                           Children_Permission => Children_Permission (C)));
-
-                     --  We change the current node from Entire_Object
-                     --  to Array_Component with same permission and the
-                     --  previously defined son.
-
-                     C.all.Tree := (Kind         => Array_Component,
-                                    Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => Permission (C),
-                                    Get_Elem     => Son);
-
-                     return Get_Elem (C);
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
-
-         --  We get the permission tree of its prefix, and then get either the
-         --  subtree associated with that specific selection, or if we have a
-         --  leaf that folds its children, we unroll the tree.
-
-         when N_Explicit_Dereference =>
-            declare
-               C : Perm_Tree_Access;
-
-            begin
-               C := Get_Perm_Tree (Prefix (N));
-
-               if C = null then
-                  --  If null, we went through a function call
-
-                  return null;
-               end if;
-
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Reference);
-
-               if Kind (C) = Reference then
-                  --  The tree is unfolded. We return the elem subtree
-
-                  if Get_All (C) = null then
-                     --  Hash_Table_Error
-                     raise Program_Error;
+                     return Empty;
                   end if;
+               end;
+            else
+               if Emit_Messages then
+                  Error_Msg_N ("name expected here for path", Expr);
+               end if;
+               return Empty;
+            end if;
 
-                  return Get_All (C);
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with Reference.
-
-                     Son : Perm_Tree_Access;
-
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Deep (Etype (N)),
-                           Permission          => Children_Permission (C),
-                           Children_Permission => Children_Permission (C)));
-
-                     --  We change the current node from Entire_Object to
-                     --  Reference with same permission and the previous son.
-
-                     pragma Assert (Is_Node_Deep (C));
+         when N_Case_Expression =>
+            if Is_Traversal then
+               declare
+                  Cases       : constant List_Id := Alternatives (Expr);
+                  Cur_Case    : Node_Id := First (Cases);
+                  Cur_Root    : Entity_Id;
+                  Common_Root : Entity_Id := Empty;
 
-                     C.all.Tree := (Kind         => Reference,
-                                    Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => Permission (C),
-                                    Get_All      => Son);
+               begin
+                  while Present (Cur_Case) loop
+                     Cur_Root := GRO (Expression (Cur_Case));
+
+                     if Common_Root = Empty then
+                        Common_Root := Cur_Root;
+                     elsif Common_Root /= Cur_Root then
+                        if Emit_Messages then
+                           Error_Msg_N
+                             ("same name expected here in each branch", Expr);
+                        end if;
+                        return Empty;
+                     end if;
+                     Next (Cur_Case);
+                  end loop;
 
-                     return Get_All (C);
-                  end;
-               else
-                  raise Program_Error;
+                  return Common_Root;
+               end;
+            else
+               if Emit_Messages then
+                  Error_Msg_N ("name expected here for path", Expr);
                end if;
-            end;
-
-         --  No permission tree for function calls
-
-         when N_Function_Call =>
-            return null;
+               return Empty;
+            end if;
 
          when others =>
             raise Program_Error;
       end case;
-   end Get_Perm_Tree;
+   end Get_Root_Object;
 
    ---------
    -- Glb --
@@ -3477,206 +4143,44 @@ package body Sem_SPARK is
       end case;
    end Glb;
 
-   ---------------
-   -- Has_Alias --
-   ---------------
-
-   function Has_Alias
-     (N : Node_Id)
-       return Boolean
-   is
-      function Has_Alias_Deep (Typ : Entity_Id) return Boolean;
-      function Has_Alias_Deep (Typ : Entity_Id) return Boolean
-      is
-         Comp : Node_Id;
-      begin
-
-         if Is_Array_Type (Typ)
-           and then Has_Aliased_Components (Typ)
-         then
-            return True;
-
-            --  Note: Has_Aliased_Components applies only to arrays
-
-         elsif Is_Record_Type (Typ) then
-            --  It is possible to have an aliased discriminant, so they must be
-            --  checked along with normal components.
-
-            Comp := First_Component_Or_Discriminant (Typ);
-            while Present (Comp) loop
-               if Is_Aliased (Comp)
-                 or else Is_Aliased (Etype (Comp))
-               then
-                  return True;
-               end if;
-
-               if Has_Alias_Deep (Etype (Comp)) then
-                  return True;
-               end if;
-
-               Next_Component_Or_Discriminant (Comp);
-            end loop;
-            return False;
-         else
-            return Is_Aliased (Typ);
-         end if;
-      end Has_Alias_Deep;
-
-   begin
-      case Nkind (N) is
-
-         when N_Identifier
-            | N_Expanded_Name
-         =>
-            return Has_Alias_Deep (Etype (N));
-
-         when N_Defining_Identifier =>
-            return Has_Alias_Deep (Etype (N));
-
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
-         =>
-            return Has_Alias (Expression (N));
-
-         when N_Parameter_Specification =>
-            return Has_Alias (Defining_Identifier (N));
-
-         when N_Selected_Component =>
-            case Nkind (Selector_Name (N)) is
-               when N_Identifier =>
-                  if Is_Aliased (Entity (Selector_Name (N))) then
-                     return True;
-                  end if;
-
-               when others => null;
-
-            end case;
-
-            return Has_Alias (Prefix (N));
-
-         when N_Indexed_Component
-            | N_Slice
-         =>
-            return Has_Alias (Prefix (N));
-
-         when N_Explicit_Dereference =>
-            return True;
-
-         when N_Function_Call =>
-            return False;
-
-         when N_Attribute_Reference =>
-            if Is_Deep (Etype (Prefix (N))) then
-               raise Program_Error;
-            end if;
-            return False;
-
-         when others =>
-            return False;
-      end case;
-   end Has_Alias;
-
    -------------------------
    -- Has_Array_Component --
    -------------------------
 
-   function Has_Array_Component (N : Node_Id) return Boolean is
+   function Has_Array_Component (Expr : Node_Id) return Boolean is
    begin
-      case Nkind (N) is
-         --  Base identifier. There is no array component here.
-
-         when N_Identifier
-            | N_Expanded_Name
-            | N_Defining_Identifier
+      case Nkind (Expr) is
+         when N_Expanded_Name
+            | N_Identifier
          =>
             return False;
 
-         --  We check if the expression inside the conversion has an array
-         --  component.
-
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
+         when N_Explicit_Dereference
+            | N_Selected_Component
          =>
-            return Has_Array_Component (Expression (N));
-
-         --  We check if the prefix has an array component
-
-         when N_Selected_Component =>
-            return Has_Array_Component (Prefix (N));
-
-         --  We found the array component, return True
+            return Has_Array_Component (Prefix (Expr));
 
          when N_Indexed_Component
             | N_Slice
          =>
             return True;
 
-         --  We check if the prefix has an array component
-
-         when N_Explicit_Dereference =>
-            return Has_Array_Component (Prefix (N));
-
-         when N_Function_Call =>
-            return False;
-
-         when others =>
-            raise Program_Error;
-      end case;
-   end Has_Array_Component;
-
-   ----------------------------
-   -- Has_Function_Component --
-   ----------------------------
-
-   function Has_Function_Component (N : Node_Id) return Boolean is
-   begin
-      case Nkind (N) is
-         --  Base identifier. There is no function component here.
-
-         when N_Identifier
-            | N_Expanded_Name
-            | N_Defining_Identifier
+         when N_Allocator
+            | N_Null
+            | N_Function_Call
          =>
             return False;
 
-         --  We check if the expression inside the conversion has a function
-         --  component.
-
-         when N_Type_Conversion
+         when N_Qualified_Expression
+            | N_Type_Conversion
             | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
-         =>
-            return Has_Function_Component (Expression (N));
-
-         --  We check if the prefix has a function component
-
-         when N_Selected_Component =>
-            return Has_Function_Component (Prefix (N));
-
-         --  We check if the prefix has a function component
-
-         when N_Indexed_Component
-            | N_Slice
          =>
-            return Has_Function_Component (Prefix (N));
-
-         --  We check if the prefix has a function component
-
-         when N_Explicit_Dereference =>
-            return Has_Function_Component (Prefix (N));
-
-         --  We found the function component, return True
-
-         when N_Function_Call =>
-            return True;
+            return Has_Array_Component (Expression (Expr));
 
          when others =>
             raise Program_Error;
-
       end case;
-   end Has_Function_Component;
+   end Has_Array_Component;
 
    --------
    -- Hp --
@@ -3697,146 +4201,381 @@ package body Sem_SPARK is
    -- Illegal_Global_Usage --
    --------------------------
 
-   procedure Illegal_Global_Usage (N : Node_Or_Entity_Id)  is
+   procedure Illegal_Global_Usage (N : Node_Or_Entity_Id; E : Entity_Id)
+   is
    begin
-      Error_Msg_NE ("cannot use global variable & of deep type", N, N);
+      Error_Msg_NE ("cannot use global variable & of deep type", N, E);
       Error_Msg_N ("\without prior declaration in a Global aspect", N);
-
       Errout.Finalize (Last_Call => True);
       Errout.Output_Messages;
       Exit_Program (E_Errors);
    end Illegal_Global_Usage;
 
-   --------------------
-   -- Is_Borrowed_In --
-   --------------------
-
-   function Is_Borrowed_In (E : Entity_Id) return Boolean is
-   begin
-      return Is_Access_Type (Etype (E))
-        and then not Is_Access_Constant (Etype (E));
-   end Is_Borrowed_In;
-
    -------------
    -- Is_Deep --
    -------------
 
-   function Is_Deep (E : Entity_Id) return Boolean is
-      function Is_Private_Entity_Mode_Off (E : Entity_Id) return Boolean;
-
-      function Is_Private_Entity_Mode_Off (E : Entity_Id) return Boolean is
-         Decl : Node_Id;
-         Pack_Decl : Node_Id;
-
-      begin
-         if Is_Itype (E) then
-            Decl := Associated_Node_For_Itype (E);
-         else
-            Decl := Parent (E);
-         end if;
-
-         Pack_Decl := Parent (Parent (Decl));
-
-         if Nkind (Pack_Decl) /= N_Package_Declaration then
-            return False;
-         end if;
-
-         return
-           Present (SPARK_Aux_Pragma (Defining_Entity (Pack_Decl)))
-           and then Get_SPARK_Mode_From_Annotation
-             (SPARK_Aux_Pragma (Defining_Entity (Pack_Decl))) = Off;
-      end Is_Private_Entity_Mode_Off;
+   function Is_Deep (Typ : Entity_Id) return Boolean is
    begin
-      pragma Assert (Is_Type (E));
-
-      case Ekind (E) is
-         when Scalar_Kind =>
-            return False;
-
+      case Type_Kind'(Ekind (Retysp (Typ))) is
          when Access_Kind =>
             return True;
 
-         --  Just check the depth of its component type
-
          when E_Array_Type
             | E_Array_Subtype
          =>
-            return Is_Deep (Component_Type (E));
+            return Is_Deep (Component_Type (Retysp (Typ)));
 
-         when E_String_Literal_Subtype =>
-            return False;
-
-         --  Per RM 8.11 for class-wide types
-
-         when E_Class_Wide_Subtype
-            | E_Class_Wide_Type
-         =>
-            return True;
-
-         --  ??? What about hidden components
-
-         when E_Record_Type
-            | E_Record_Subtype
-            =>
+         when Record_Kind =>
             declare
-               Elmt : Entity_Id;
-
+               Comp : Entity_Id;
             begin
-               Elmt := First_Component_Or_Discriminant (E);
-               while Present (Elmt) loop
-                  if Is_Deep (Etype (Elmt)) then
+               Comp := First_Component_Or_Discriminant (Retysp (Typ));
+               while Present (Comp) loop
+
+                  --  Ignore components not visible in SPARK
+
+                  if Component_Is_Visible_In_SPARK (Comp)
+                    and then Is_Deep (Etype (Comp))
+                  then
                      return True;
-                  else
-                     Next_Component_Or_Discriminant (Elmt);
                   end if;
+                  Next_Component_Or_Discriminant (Comp);
                end loop;
-
-               return False;
             end;
+            return False;
 
-         when Private_Kind =>
-            if Is_Private_Entity_Mode_Off (E) then
-               return False;
-            else
-               if Present (Full_View (E)) then
-                  return Is_Deep (Full_View (E));
-               else
-                  return True;
-               end if;
-            end if;
+         when Scalar_Kind
+            | E_String_Literal_Subtype
+            | Protected_Kind
+            | Task_Kind
+            | Incomplete_Kind
+            | E_Exception_Type
+            | E_Subprogram_Type
+         =>
+            return False;
+
+         --  Ignore full view of types if it is not in SPARK
+
+         when E_Private_Type
+            | E_Private_Subtype
+            | E_Limited_Private_Type
+            | E_Limited_Private_Subtype
+         =>
+            return False;
+      end case;
+   end Is_Deep;
+
+   --------------
+   -- Is_Legal --
+   --------------
+
+   function Is_Legal (N : Node_Id) return Boolean is
+      Legal : Boolean := True;
+
+   begin
+      case Nkind (N) is
+         when N_Declaration =>
+            Check_Declaration_Legality (N, Force => False, Legal => Legal);
+         when others =>
+            null;
+      end case;
+
+      return Legal;
+   end Is_Legal;
+
+   ----------------------
+   -- Is_Local_Context --
+   ----------------------
+
+   function Is_Local_Context (Scop : Entity_Id) return Boolean is
+   begin
+      return Is_Subprogram_Or_Entry (Scop)
+        or else Ekind (Scop) = E_Block;
+   end Is_Local_Context;
+
+   ------------------------
+   -- Is_Path_Expression --
+   ------------------------
+
+   function Is_Path_Expression
+     (Expr         : Node_Id;
+      Is_Traversal : Boolean := False) return Boolean
+   is
+      function IPE (Expr : Node_Id) return Boolean;
+      --  Local wrapper on the actual function, to propagate the values of
+      --  optional parameter Is_Traversal.
+
+      ---------
+      -- IPE --
+      ---------
+
+      function IPE (Expr : Node_Id) return Boolean is
+      begin
+         return Is_Path_Expression (Expr, Is_Traversal);
+      end IPE;
+
+      Is_Path_Expression : Boolean;
+      pragma Unmodified (Is_Path_Expression);
+      --  Local variable to mask the name of function Is_Path_Expression, to
+      --  prevent direct call. Instead IPE wrapper should be called.
 
-         when E_Incomplete_Type =>
+   --  Start of processing for Is_Path_Expression
+
+   begin
+      case Nkind (Expr) is
+         when N_Expanded_Name
+            | N_Explicit_Dereference
+            | N_Identifier
+            | N_Indexed_Component
+            | N_Selected_Component
+            | N_Slice
+         =>
             return True;
 
-         when E_Incomplete_Subtype =>
+         --  Special value NULL corresponds to an empty path
+
+         when N_Null =>
             return True;
 
-         --  No problem with synchronized types
+         --  Object returned by an (extension) aggregate, an allocator, or
+         --  a function call corresponds to a path.
 
-         when E_Protected_Type
-            | E_Protected_Subtype
-            | E_Task_Subtype
-            | E_Task_Type
-          =>
-            return False;
+         when N_Aggregate
+            | N_Allocator
+            | N_Extension_Aggregate
+            | N_Function_Call
+         =>
+            return True;
 
-         when E_Exception_Type =>
-            return False;
+         when N_Qualified_Expression
+            | N_Type_Conversion
+            | N_Unchecked_Type_Conversion
+         =>
+            return IPE (Expression (Expr));
+
+         --  When returning from a traversal function, consider an
+         --  if-expression as a possible path expression.
+
+         when N_If_Expression =>
+            if Is_Traversal then
+               declare
+                  Cond      : constant Node_Id := First (Expressions (Expr));
+                  Then_Part : constant Node_Id := Next (Cond);
+                  Else_Part : constant Node_Id := Next (Then_Part);
+               begin
+                  return IPE (Then_Part)
+                    and then IPE (Else_Part);
+               end;
+            else
+               return False;
+            end if;
+
+         --  When returning from a traversal function, consider
+         --  a case-expression as a possible path expression.
+
+         when N_Case_Expression =>
+            if Is_Traversal then
+               declare
+                  Cases    : constant List_Id := Alternatives (Expr);
+                  Cur_Case : Node_Id := First (Cases);
+
+               begin
+                  while Present (Cur_Case) loop
+                     if not IPE (Expression (Cur_Case)) then
+                        return False;
+                     end if;
+                     Next (Cur_Case);
+                  end loop;
+
+                  return True;
+               end;
+            else
+               return False;
+            end if;
 
          when others =>
-            raise Program_Error;
+            return False;
       end case;
-   end Is_Deep;
+   end Is_Path_Expression;
 
-   ----------------
-   -- Is_Shallow --
-   ----------------
+   -------------------------
+   -- Is_Prefix_Or_Almost --
+   -------------------------
+
+   function Is_Prefix_Or_Almost (Pref, Expr : Node_Id) return Boolean is
+
+      type Expr_Array is array (Positive range <>) of Node_Id;
+      --  Sequence of expressions that make up a path
+
+      function Get_Expr_Array (Expr : Node_Id) return Expr_Array;
+      pragma Precondition (Is_Path_Expression (Expr));
+      --  Return the sequence of expressions that make up a path
+
+      --------------------
+      -- Get_Expr_Array --
+      --------------------
+
+      function Get_Expr_Array (Expr : Node_Id) return Expr_Array is
+      begin
+         case Nkind (Expr) is
+            when N_Expanded_Name
+               | N_Identifier
+            =>
+               return Expr_Array'(1 => Expr);
+
+            when N_Explicit_Dereference
+               | N_Indexed_Component
+               | N_Selected_Component
+               | N_Slice
+            =>
+               return Get_Expr_Array (Prefix (Expr)) & Expr;
+
+            when N_Qualified_Expression
+               | N_Type_Conversion
+               | N_Unchecked_Type_Conversion
+            =>
+               return Get_Expr_Array (Expression (Expr));
+
+            when others =>
+               raise Program_Error;
+         end case;
+      end Get_Expr_Array;
+
+      --  Local variables
+
+      Prefix_Path : constant Expr_Array := Get_Expr_Array (Pref);
+      Expr_Path   : constant Expr_Array := Get_Expr_Array (Expr);
+
+      Prefix_Root : constant Node_Id := Prefix_Path (1);
+      Expr_Root   : constant Node_Id := Expr_Path (1);
+
+      Common_Len  : constant Positive :=
+        Positive'Min (Prefix_Path'Length, Expr_Path'Length);
+
+   --  Start of processing for Is_Prefix_Or_Almost
+
+   begin
+      if Entity (Prefix_Root) /= Entity (Expr_Root) then
+         return False;
+      end if;
+
+      for J in 2 .. Common_Len loop
+         declare
+            Prefix_Elt : constant Node_Id := Prefix_Path (J);
+            Expr_Elt   : constant Node_Id := Expr_Path (J);
+         begin
+            case Nkind (Prefix_Elt) is
+               when N_Explicit_Dereference =>
+                  if Nkind (Expr_Elt) /= N_Explicit_Dereference then
+                     return False;
+                  end if;
+
+               when N_Selected_Component =>
+                  if Nkind (Expr_Elt) /= N_Selected_Component
+                    or else Original_Record_Component
+                              (Entity (Selector_Name (Prefix_Elt)))
+                         /= Original_Record_Component
+                              (Entity (Selector_Name (Expr_Elt)))
+                  then
+                     return False;
+                  end if;
+
+               when N_Indexed_Component
+                  | N_Slice
+               =>
+                  if not Nkind_In (Expr_Elt, N_Indexed_Component, N_Slice) then
+                     return False;
+                  end if;
+
+               when others =>
+                  raise Program_Error;
+            end case;
+         end;
+      end loop;
+
+      --  If the expression path is longer than the prefix one, then at this
+      --  point the prefix property holds.
+
+      if Expr_Path'Length > Prefix_Path'Length then
+         return True;
 
-   function Is_Shallow (E : Entity_Id) return Boolean is
+      --  Otherwise check if none of the remaining path elements in the
+      --  candidate prefix involve a dereference.
+
+      else
+         for J in Common_Len + 1 .. Prefix_Path'Length loop
+            if Nkind (Prefix_Path (J)) = N_Explicit_Dereference then
+               return False;
+            end if;
+         end loop;
+
+         return True;
+      end if;
+   end Is_Prefix_Or_Almost;
+
+   ---------------------------
+   -- Is_Subpath_Expression --
+   ---------------------------
+
+   function Is_Subpath_Expression
+     (Expr         : Node_Id;
+      Is_Traversal : Boolean := False) return Boolean
+   is
+   begin
+      return Is_Path_Expression (Expr, Is_Traversal)
+
+        or else (Nkind_In (Expr, N_Qualified_Expression,
+                                 N_Type_Conversion,
+                                 N_Unchecked_Type_Conversion)
+                  and then Is_Subpath_Expression (Expression (Expr)))
+
+        or else (Nkind (Expr) = N_Attribute_Reference
+                  and then
+                    (Get_Attribute_Id (Attribute_Name (Expr)) =
+                       Attribute_Update
+                     or else
+                     Get_Attribute_Id (Attribute_Name (Expr)) =
+                       Attribute_Loop_Entry
+                     or else
+                     Get_Attribute_Id (Attribute_Name (Expr)) =
+                       Attribute_Image))
+
+        or else Nkind (Expr) = N_Op_Concat;
+   end Is_Subpath_Expression;
+
+   ---------------------------
+   -- Is_Traversal_Function --
+   ---------------------------
+
+   function Is_Traversal_Function (E : Entity_Id) return Boolean is
+   begin
+      return Ekind (E) = E_Function
+
+        --  A function is said to be a traversal function if the result type of
+        --  the function is an anonymous access-to-object type,
+
+        and then Is_Anonymous_Access_Type (Etype (E))
+
+        --  the function has at least one formal parameter,
+
+        and then Present (First_Formal (E))
+
+        --  and the function's first parameter is of an access type.
+
+        and then Is_Access_Type (Retysp (Etype (First_Formal (E))));
+   end Is_Traversal_Function;
+
+   --------------------------------
+   -- Is_Traversal_Function_Call --
+   --------------------------------
+
+   function Is_Traversal_Function_Call (Expr : Node_Id) return Boolean is
    begin
-      pragma Assert (Is_Type (E));
-      return not Is_Deep (E);
-   end Is_Shallow;
+      return Nkind (Expr) = N_Function_Call
+        and then Present (Get_Called_Entity (Expr))
+        and then Is_Traversal_Function (Get_Called_Entity (Expr));
+   end Is_Traversal_Function_Call;
 
    ------------------
    -- Loop_Of_Exit --
@@ -3857,12 +4596,12 @@ package body Sem_SPARK is
       end if;
       return Entity (Nam);
    end Loop_Of_Exit;
+
    ---------
    -- Lub --
    ---------
 
-   function Lub (P1, P2 : Perm_Kind) return Perm_Kind
-   is
+   function Lub (P1, P2 : Perm_Kind) return Perm_Kind is
    begin
       case P1 is
          when No_Access =>
@@ -3895,59 +4634,65 @@ package body Sem_SPARK is
       end case;
    end Lub;
 
-   ----------------
-   -- Merge_Envs --
-   ----------------
+   ---------------
+   -- Merge_Env --
+   ---------------
+
+   procedure Merge_Env (Source : in out Perm_Env; Target : in out Perm_Env) is
+
+      --  Local subprograms
+
+      procedure Apply_Glb_Tree
+        (A : Perm_Tree_Access;
+         P : Perm_Kind);
 
-   procedure Merge_Envs
-     (Target : in out Perm_Env;
-      Source : in out Perm_Env)
-   is
       procedure Merge_Trees
         (Target : Perm_Tree_Access;
          Source : Perm_Tree_Access);
 
-      procedure Merge_Trees
-        (Target : Perm_Tree_Access;
-         Source : Perm_Tree_Access)
-      is
-         procedure Apply_Glb_Tree
-           (A : Perm_Tree_Access;
-            P : Perm_Kind);
+      --------------------
+      -- Apply_Glb_Tree --
+      --------------------
 
-         procedure Apply_Glb_Tree
-           (A : Perm_Tree_Access;
-            P : Perm_Kind)
-         is
-         begin
-            A.all.Tree.Permission := Glb (Permission (A), P);
+      procedure Apply_Glb_Tree
+        (A : Perm_Tree_Access;
+         P : Perm_Kind)
+      is
+      begin
+         A.all.Tree.Permission := Glb (Permission (A), P);
 
-            case Kind (A) is
-               when Entire_Object =>
-                  A.all.Tree.Children_Permission :=
-                    Glb (Children_Permission (A), P);
+         case Kind (A) is
+            when Entire_Object =>
+               A.all.Tree.Children_Permission :=
+                 Glb (Children_Permission (A), P);
 
-               when Reference =>
-                  Apply_Glb_Tree (Get_All (A), P);
+            when Reference =>
+               Apply_Glb_Tree (Get_All (A), P);
 
-               when Array_Component =>
-                  Apply_Glb_Tree (Get_Elem (A), P);
+            when Array_Component =>
+               Apply_Glb_Tree (Get_Elem (A), P);
 
-               when Record_Component =>
-                  declare
-                     Comp : Perm_Tree_Access;
-                  begin
-                     Comp := Perm_Tree_Maps.Get_First (Component (A));
-                     while Comp /= null loop
-                        Apply_Glb_Tree (Comp, P);
-                        Comp := Perm_Tree_Maps.Get_Next (Component (A));
-                     end loop;
+            when Record_Component =>
+               declare
+                  Comp : Perm_Tree_Access;
+               begin
+                  Comp := Perm_Tree_Maps.Get_First (Component (A));
+                  while Comp /= null loop
+                     Apply_Glb_Tree (Comp, P);
+                     Comp := Perm_Tree_Maps.Get_Next (Component (A));
+                  end loop;
+               end;
+         end case;
+      end Apply_Glb_Tree;
 
-                     Apply_Glb_Tree (Other_Components (A), P);
-                  end;
-            end case;
-         end Apply_Glb_Tree;
+      -----------------
+      -- Merge_Trees --
+      -----------------
 
+      procedure Merge_Trees
+        (Target : Perm_Tree_Access;
+         Source : Perm_Tree_Access)
+      is
          Perm : constant Perm_Kind :=
            Glb (Permission (Target), Permission (Source));
 
@@ -3994,10 +4739,9 @@ package body Sem_SPARK is
                              (Component (Target));
                         end loop;
                      end;
-                     Apply_Glb_Tree (Other_Components (Target), Child_Perm);
-
                   end case;
                end;
+
             when Reference =>
                case Kind (Source) is
                when Entire_Object =>
@@ -4045,7 +4789,6 @@ package body Sem_SPARK is
                         Comp :=
                           Perm_Tree_Maps.Get_Next (Component (Target));
                      end loop;
-                     Apply_Glb_Tree (Other_Components (Target), Child_Perm);
                   end;
 
                when Record_Component =>
@@ -4070,22 +4813,22 @@ package body Sem_SPARK is
                         Key_Source := Perm_Tree_Maps.Get_Next_Key
                           (Component (Source));
                      end loop;
-
-                     Merge_Trees (Other_Components (Target),
-                                  Other_Components (Source));
                   end;
 
                when others =>
                   raise Program_Error;
-
                end case;
          end case;
       end Merge_Trees;
 
+      --  Local variables
+
       CompTarget : Perm_Tree_Access;
       CompSource : Perm_Tree_Access;
       KeyTarget : Perm_Tree_Maps.Key_Option;
 
+   --  Start of processing for Merge_Env
+
    begin
       KeyTarget := Get_First_Key (Target);
       --  Iterate over every tree of the environment in the target, and merge
@@ -4131,16 +4874,18 @@ package body Sem_SPARK is
       end;
 
       Free_Env (Source);
-   end Merge_Envs;
+   end Merge_Env;
 
    ----------------
    -- Perm_Error --
    ----------------
 
    procedure Perm_Error
-     (N : Node_Id;
-      Perm : Perm_Kind;
-      Found_Perm : Perm_Kind)
+     (N              : Node_Id;
+      Perm           : Perm_Kind;
+      Found_Perm     : Perm_Kind;
+      Expl           : Node_Id;
+      Forbidden_Perm : Boolean := False)
    is
       procedure Set_Root_Object
         (Path  : Node_Id;
@@ -4186,7 +4931,6 @@ package body Sem_SPARK is
                raise Program_Error;
          end case;
       end Set_Root_Object;
-
       --  Local variables
 
       Root : Entity_Id;
@@ -4197,14 +4941,16 @@ package body Sem_SPARK is
    begin
       Set_Root_Object (N, Root, Is_Deref);
 
-      if Is_Deref then
-         Error_Msg_NE
-           ("insufficient permission on dereference from &", N, Root);
-      else
-         Error_Msg_NE ("insufficient permission for &", N, Root);
-      end if;
+      if Emit_Messages then
+         if Is_Deref then
+            Error_Msg_NE
+              ("insufficient permission on dereference from &", N, Root);
+         else
+            Error_Msg_NE ("insufficient permission for &", N, Root);
+         end if;
 
-      Perm_Mismatch (Perm, Found_Perm, N);
+         Perm_Mismatch (N, Perm, Found_Perm, Expl, Forbidden_Perm);
+      end if;
    end Perm_Error;
 
    -------------------------------
@@ -4215,364 +4961,397 @@ package body Sem_SPARK is
      (E          : Entity_Id;
       Subp       : Entity_Id;
       Perm       : Perm_Kind;
-      Found_Perm : Perm_Kind)
+      Found_Perm : Perm_Kind;
+      Expl       : Node_Id)
    is
    begin
-      Error_Msg_Node_2 := Subp;
-      Error_Msg_NE ("insufficient permission for & when returning from &",
-                    Subp, E);
-      Perm_Mismatch (Perm, Found_Perm, Subp);
+      if Emit_Messages then
+         Error_Msg_Node_2 := Subp;
+         Error_Msg_NE ("insufficient permission for & when returning from &",
+                       Subp, E);
+         Perm_Mismatch (Subp, Perm, Found_Perm, Expl);
+      end if;
    end Perm_Error_Subprogram_End;
 
    ------------------
    -- Process_Path --
    ------------------
 
-   procedure Process_Path (N : Node_Id) is
-      Root : constant Entity_Id := Get_Enclosing_Object (N);
-   begin
-      --  We ignore if yielding to synchronized
-
-      if Present (Root)
-        and then Is_Synchronized_Object (Root)
-      then
-         return;
-      end if;
+   procedure Process_Path (Expr : Node_Id; Mode : Checking_Mode) is
 
-      --  We ignore shallow unaliased. They are checked in flow analysis,
-      --  allowing backward compatibility.
+      procedure Check_Not_Borrowed (Expr : Node_Id; Root : Entity_Id);
+      --  Check expression Expr originating in Root was not borrowed
 
-      if not Has_Alias (N)
-        and then Is_Shallow (Etype (N))
-      then
-         return;
-      end if;
+      procedure Check_Not_Observed (Expr : Node_Id; Root : Entity_Id);
+      --  Check expression Expr originating in Root was not observed
 
-      declare
-         Perm_N : constant Perm_Kind := Get_Perm (N);
+      ------------------------
+      -- Check_Not_Borrowed --
+      ------------------------
 
+      procedure Check_Not_Borrowed (Expr : Node_Id; Root : Entity_Id) is
       begin
+         --  An expression without root object cannot be borrowed
 
-         case Current_Checking_Mode is
-            --  Check permission R, do nothing
+         if No (Root) then
+            return;
+         end if;
 
-            when Read =>
-               if Perm_N not in Read_Perm then
-                  Perm_Error (N, Read_Only, Perm_N);
-               end if;
+         --  Otherwise, try to match the expression with one of the borrowed
+         --  expressions.
 
-            --  If shallow type no need for RW, only R
+         declare
+            Key      : Variable_Maps.Key_Option :=
+              Get_First_Key (Current_Borrowers);
+            Var      : Entity_Id;
+            Borrowed : Node_Id;
+            B_Pledge : Entity_Id := Empty;
 
-            when Move =>
-               if Is_Shallow (Etype (N)) then
-                  if Perm_N not in Read_Perm then
-                     Perm_Error (N, Read_Only, Perm_N);
-                  end if;
-               else
-                  --  Check permission RW if deep
+         begin
+            --  Search for a call to a pledge function or a global pragma in
+            --  the parents of Expr.
 
-                  if Perm_N /= Read_Write then
-                     Perm_Error (N, Read_Write, Perm_N);
+            declare
+               Call : Node_Id := Expr;
+            begin
+               while Present (Call)
+                 and then
+                   (Nkind (Call) /= N_Function_Call
+                     or else not Is_Pledge_Function (Get_Called_Entity (Call)))
+               loop
+                  --  Do not check for borrowed objects in global contracts
+                  --  ??? However we should keep these objects in the borrowed
+                  --  state when verifying the subprogram so that we can make
+                  --  sure that they are only read inside pledges.
+                  --  ??? There is probably a better way to disable checking of
+                  --  borrows inside global contracts.
+
+                  if Nkind (Call) = N_Pragma
+                    and then Get_Pragma_Id (Pragma_Name (Call)) = Pragma_Global
+                  then
+                     return;
                   end if;
 
-                  declare
-                     --  Set permission to W to the path and any of its prefix
-
-                     Tree : constant Perm_Tree_Access :=
-                       Set_Perm_Prefixes_Move (N, Move);
-
-                  begin
-                     if Tree = null then
-                        --  We went through a function call, no permission to
-                        --  modify.
+                  Call := Parent (Call);
+               end loop;
 
-                        return;
-                     end if;
+               if Present (Call)
+                 and then Nkind (First_Actual (Call)) in N_Has_Entity
+               then
+                  B_Pledge := Entity (First_Actual (Call));
+               end if;
+            end;
 
-                     --  Set permissions to
-                     --  No for any extension with more .all
-                     --  W for any deep extension with same number of .all
-                     --  RW for any shallow extension with same number of .all
+            while Key.Present loop
+               Var := Key.K;
+               Borrowed := Get (Current_Borrowers, Var);
 
-                     Set_Perm_Extensions_Move (Tree, Etype (N));
-                  end;
+               if Is_Prefix_Or_Almost (Pref => Borrowed, Expr => Expr)
+                 and then Var /= B_Pledge
+                 and then Emit_Messages
+               then
+                  Error_Msg_Sloc := Sloc (Borrowed);
+                  Error_Msg_N ("object was borrowed #", Expr);
                end if;
 
-            --  Check permission RW
+               Key := Get_Next_Key (Current_Borrowers);
+            end loop;
+         end;
+      end Check_Not_Borrowed;
 
-            when Super_Move =>
-               if Perm_N /= Read_Write then
-                  Perm_Error (N, Read_Write, Perm_N);
-               end if;
+      ------------------------
+      -- Check_Not_Observed --
+      ------------------------
 
-               declare
-                  --  Set permission to No to the path and any of its prefix up
-                  --  to the first .all and then W.
+      procedure Check_Not_Observed (Expr : Node_Id; Root : Entity_Id) is
+      begin
+         --  An expression without root object cannot be observed
 
-                  Tree : constant Perm_Tree_Access :=
-                    Set_Perm_Prefixes_Move (N, Super_Move);
+         if No (Root) then
+            return;
+         end if;
 
-               begin
-                  if Tree = null then
-                     --  We went through a function call, no permission to
-                     --  modify.
+         --  Otherwise, try to match the expression with one of the observed
+         --  expressions.
 
-                     return;
-                  end if;
-
-                  --  Set permissions to No on any strict extension of the path
-
-                  Set_Perm_Extensions (Tree, No_Access);
-               end;
+         declare
+            Key      : Variable_Maps.Key_Option :=
+              Get_First_Key (Current_Observers);
+            Var      : Entity_Id;
+            Observed : Node_Id;
 
-            --  Check permission W
+         begin
+            while Key.Present loop
+               Var := Key.K;
+               Observed := Get (Current_Observers, Var);
 
-            when Assign =>
-               if Perm_N not in Write_Perm then
-                  Perm_Error (N, Write_Only, Perm_N);
+               if Is_Prefix_Or_Almost (Pref => Observed, Expr => Expr)
+                 and then Emit_Messages
+               then
+                  Error_Msg_Sloc := Sloc (Observed);
+                  Error_Msg_N ("object was observed #", Expr);
                end if;
 
-               --  If the tree has an array component, then the permissions do
-               --  not get modified by the assignment.
-
-               if Has_Array_Component (N) then
-                  return;
-               end if;
+               Key := Get_Next_Key (Current_Observers);
+            end loop;
+         end;
+      end Check_Not_Observed;
 
-               --  Same if has function component
+      --  Local variables
 
-               if Has_Function_Component (N) then
-                  return;
-               end if;
+      Expr_Type : constant Entity_Id := Etype (Expr);
+      Root      : Entity_Id := Get_Root_Object (Expr);
+      Perm      : Perm_Kind_Option;
 
-               declare
-                  --  Get the permission tree for the path
+   --  Start of processing for Process_Path
 
-                  Tree : constant Perm_Tree_Access :=
-                    Get_Perm_Tree (N);
+   begin
+      --  Nothing to do if the root type is not deep, or the path is not rooted
+      --  in an object.
 
-                  Dummy : Perm_Tree_Access;
+      if not Present (Root)
+        or else not Is_Deep (Etype (Root))
+      then
+         return;
+      end if;
 
-               begin
-                  if Tree = null then
-                     --  We went through a function call, no permission to
-                     --  modify.
+      --  Identify the root type for the path
 
-                     return;
-                  end if;
+      Root := Unique_Entity (Root);
 
-                  --  Set permission RW for it and all of its extensions
+      --  Except during elaboration, the root object should have been declared
+      --  and entered into the current permission environment.
 
-                  Tree.all.Tree.Permission := Read_Write;
+      if not Inside_Elaboration
+        and then Get (Current_Perm_Env, Root) = null
+      then
+         Illegal_Global_Usage (Expr, Root);
+      end if;
 
-                  Set_Perm_Extensions (Tree, Read_Write);
+      --  During elaboration, only the validity of operations is checked, no
+      --  need to compute the permission of Expr.
 
-                  --  Normalize the permission tree
+      if Inside_Elaboration then
+         Perm := None;
+      else
+         Perm := Get_Perm (Expr);
+      end if;
 
-                  Dummy := Set_Perm_Prefixes_Assign (N);
-               end;
+      --  Check permissions
 
-            --  Check permission W
+      case Mode is
 
-            when Borrow_Out =>
-               if Perm_N not in Write_Perm then
-                  Perm_Error (N, Write_Only, Perm_N);
-               end if;
+         when Read =>
 
-               declare
-                  --  Set permission to No to the path and any of its prefixes
+            --  No checking needed during elaboration
 
-                  Tree : constant Perm_Tree_Access :=
-                    Set_Perm_Prefixes_Borrow_Out (N);
+            if Inside_Elaboration then
+               return;
+            end if;
 
-               begin
-                  if Tree = null then
-                     --  We went through a function call, no permission to
-                     --  modify.
+            --  Check path is readable
 
-                     return;
-                  end if;
+            if Perm not in Read_Perm then
+               Perm_Error (Expr, Read_Only, Perm, Expl => Get_Expl (Expr));
+               return;
+            end if;
 
-                  --  Set permissions to No on any strict extension of the path
+         when Move =>
 
-                  Set_Perm_Extensions (Tree, No_Access);
-               end;
+            --  Forbidden on deep path during elaboration, otherwise no
+            --  checking needed.
 
-            when Observe =>
-               if Perm_N not in Read_Perm then
-                  Perm_Error (N, Read_Only, Perm_N);
+            if Inside_Elaboration then
+               if Is_Deep (Expr_Type)
+                 and then not Inside_Procedure_Call
+                 and then Present (Get_Root_Object (Expr))
+                 and then Emit_Messages
+               then
+                  Error_Msg_N ("illegal move during elaboration", Expr);
                end if;
 
-               if Is_By_Copy_Type (Etype (N)) then
-                  return;
+               return;
+            end if;
+
+            --  For deep path, check RW permission, otherwise R permission
+
+            if not Is_Deep (Expr_Type) then
+               if Perm not in Read_Perm then
+                  Perm_Error (Expr, Read_Only, Perm, Expl => Get_Expl (Expr));
                end if;
+               return;
+            end if;
 
-               declare
-                  --  Set permission to No on the path and any of its prefixes
+            --  SPARK RM 3.10(1): At the point of a move operation the state of
+            --  the source object (if any) shall be Unrestricted.
 
-                  Tree : constant Perm_Tree_Access :=
-                    Set_Perm_Prefixes_Observe (N);
+            if Perm /= Read_Write then
+               Perm_Error (Expr, Read_Write, Perm, Expl => Get_Expl (Expr));
+               return;
+            end if;
 
-               begin
-                  if Tree = null then
-                     --  We went through a function call, no permission to
-                     --  modify.
+         when Assign =>
 
-                     return;
-                  end if;
+            --  No checking needed during elaboration
 
-                  --  Set permissions to No on any strict extension of the path
+            if Inside_Elaboration then
+               return;
+            end if;
 
-                  Set_Perm_Extensions (Tree, Read_Only);
-               end;
-         end case;
-      end;
-   end Process_Path;
+            --  For assignment, check W permission
 
-   -------------------------
-   -- Return_Declarations --
-   -------------------------
+            if Perm not in Write_Perm then
+               Perm_Error (Expr, Write_Only, Perm, Expl => Get_Expl (Expr));
+               return;
+            end if;
 
-   procedure Return_Declarations (L : List_Id) is
+         when Borrow =>
 
-      procedure Return_Declaration (Decl : Node_Id);
-      --  Check correct permissions for every declared object
+            --  Forbidden during elaboration, an error is already issued in
+            --  Check_Declaration, just return.
 
-      ------------------------
-      -- Return_Declaration --
-      ------------------------
+            if Inside_Elaboration then
+               return;
+            end if;
 
-      procedure Return_Declaration (Decl : Node_Id) is
-      begin
-         if Nkind (Decl) = N_Object_Declaration then
-            --  Check RW for object declared, unless the object has never been
-            --  initialized.
+            --  For borrowing, check RW permission
 
-            if Get (Current_Initialization_Map,
-                    Unique_Entity (Defining_Identifier (Decl))) = False
-            then
+            if Perm /= Read_Write then
+               Perm_Error (Expr, Read_Write, Perm, Expl => Get_Expl (Expr));
                return;
             end if;
 
-            --  We ignore shallow unaliased. They are checked in flow analysis,
-            --  allowing backward compatibility.
+         when Observe =>
 
-            if not Has_Alias (Defining_Identifier (Decl))
-              and then Is_Shallow (Etype (Defining_Identifier (Decl)))
-            then
+            --  Forbidden during elaboration, an error is already issued in
+            --  Check_Declaration, just return.
+
+            if Inside_Elaboration then
                return;
             end if;
 
-            declare
-               Elem : constant Perm_Tree_Access :=
-                 Get (Current_Perm_Env,
-                      Unique_Entity (Defining_Identifier (Decl)));
+            --  For borrowing, check R permission
 
-            begin
-               if Elem = null then
-                  --  Here we are on a declaration. Hence it should have been
-                  --  added in the environment when analyzing this node with
-                  --  mode Read. Hence it is not possible to find a null
-                  --  pointer here.
+            if Perm not in Read_Perm then
+               Perm_Error (Expr, Read_Only, Perm, Expl => Get_Expl (Expr));
+               return;
+            end if;
+      end case;
 
-                  --  Hash_Table_Error
-                  raise Program_Error;
-               end if;
+      --  Check path was not borrowed
 
-               if Permission (Elem) /= Read_Write then
-                  Perm_Error (Decl, Read_Write, Permission (Elem));
-               end if;
-            end;
-         end if;
-      end Return_Declaration;
+      Check_Not_Borrowed (Expr, Root);
 
-      --  Local Variables
+      --  For modes that require W permission, check path was not observed
 
-      N : Node_Id;
+      case Mode is
+         when Read | Observe =>
+            null;
+         when Assign | Move | Borrow =>
+            Check_Not_Observed (Expr, Root);
+      end case;
 
-   --  Start of processing for Return_Declarations
+      --  Do not update permission environment when handling calls
 
-   begin
-      N := First (L);
-      while Present (N) loop
-         Return_Declaration (N);
-         Next (N);
-      end loop;
-   end Return_Declarations;
+      if Inside_Procedure_Call then
+         return;
+      end if;
 
-   --------------------
-   -- Return_Globals --
-   --------------------
+      --  Update the permissions
 
-   procedure Return_Globals (Subp : Entity_Id) is
+      case Mode is
 
-      procedure Return_Globals_From_List
-        (First_Item : Node_Id;
-         Kind       : Formal_Kind);
-      --  Return global items from the list starting at Item
+         when Read =>
+            null;
 
-      procedure Return_Globals_Of_Mode (Global_Mode : Name_Id);
-      --  Return global items for the mode Global_Mode
+         when Move =>
 
-      ------------------------------
-      -- Return_Globals_From_List --
-      ------------------------------
+            --  SPARK RM 3.10(1): After a move operation, the state of the
+            --  source object (if any) becomes Moved.
 
-      procedure Return_Globals_From_List
-        (First_Item : Node_Id;
-         Kind       : Formal_Kind)
-      is
-         Item : Node_Id := First_Item;
-         E    : Entity_Id;
+            if Present (Get_Root_Object (Expr)) then
+               declare
+                  Tree : constant Perm_Tree_Access :=
+                    Set_Perm_Prefixes (Expr, Write_Only, Expl => Expr);
+               begin
+                  pragma Assert (Tree /= null);
+                  Set_Perm_Extensions_Move (Tree, Etype (Expr), Expl => Expr);
+               end;
+            end if;
 
-      begin
-         while Present (Item) loop
-            E := Entity (Item);
+         when Assign =>
 
-            --  Ignore abstract states, which play no role in pointer aliasing
+            --  If there is no root object, or the tree has an array component,
+            --  then the permissions do not get modified by the assignment.
 
-            if Ekind (E) = E_Abstract_State then
-               null;
-            else
-               Return_Parameter_Or_Global (E, Kind, Subp);
+            if No (Get_Root_Object (Expr))
+              or else Has_Array_Component (Expr)
+            then
+               return;
             end if;
-            Next_Global (Item);
-         end loop;
-      end Return_Globals_From_List;
 
-      ----------------------------
-      -- Return_Globals_Of_Mode --
-      ----------------------------
+            --  Set permission RW for the path and its extensions
 
-      procedure Return_Globals_Of_Mode (Global_Mode : Name_Id) is
-         Kind : Formal_Kind;
+            declare
+               Tree : constant Perm_Tree_Access := Get_Perm_Tree (Expr);
+            begin
+               Tree.all.Tree.Permission := Read_Write;
+               Set_Perm_Extensions (Tree, Read_Write, Expl => Expr);
 
-      begin
-         case Global_Mode is
-            when Name_Input | Name_Proof_In =>
-               Kind := E_In_Parameter;
-            when Name_Output =>
-               Kind := E_Out_Parameter;
-            when Name_In_Out =>
-               Kind := E_In_Out_Parameter;
-            when others =>
-               raise Program_Error;
-         end case;
+               --  Normalize the permission tree
+
+               Set_Perm_Prefixes_Assign (Expr);
+            end;
 
-         --  Return both global items from Global and Refined_Global pragmas
+         --  Borrowing and observing of paths is handled by the variables
+         --  Current_Borrowers and Current_Observers.
 
-         Return_Globals_From_List (First_Global (Subp, Global_Mode), Kind);
-         Return_Globals_From_List
-           (First_Global (Subp, Global_Mode, Refined => True), Kind);
-      end Return_Globals_Of_Mode;
+         when Borrow | Observe =>
+            null;
+      end case;
+   end Process_Path;
+
+   --------------------
+   -- Return_Globals --
+   --------------------
+
+   procedure Return_Globals (Subp : Entity_Id) is
+
+      procedure Return_Global
+        (Expr       : Node_Id;
+         Typ        : Entity_Id;
+         Kind       : Formal_Kind;
+         Subp       : Entity_Id;
+         Global_Var : Boolean);
+      --  Proxy procedure to return globals, to adjust for the type of first
+      --  parameter expected by Return_Parameter_Or_Global.
+
+      -------------------
+      -- Return_Global --
+      -------------------
+
+      procedure Return_Global
+        (Expr       : Node_Id;
+         Typ        : Entity_Id;
+         Kind       : Formal_Kind;
+         Subp       : Entity_Id;
+         Global_Var : Boolean)
+      is
+      begin
+         Return_Parameter_Or_Global
+           (Id         => Entity (Expr),
+            Typ        => Typ,
+            Kind       => Kind,
+            Subp       => Subp,
+            Global_Var => Global_Var);
+      end Return_Global;
+
+      procedure Return_Globals_Inst is new Handle_Globals (Return_Global);
 
    --  Start of processing for Return_Globals
 
    begin
-      Return_Globals_Of_Mode (Name_Proof_In);
-      Return_Globals_Of_Mode (Name_Input);
-      Return_Globals_Of_Mode (Name_Output);
-      Return_Globals_Of_Mode (Name_In_Out);
+      Return_Globals_Inst (Subp);
    end Return_Globals;
 
    --------------------------------
@@ -4580,38 +5359,54 @@ package body Sem_SPARK is
    --------------------------------
 
    procedure Return_Parameter_Or_Global
-     (Id   : Entity_Id;
-      Mode : Formal_Kind;
-      Subp : Entity_Id)
+     (Id         : Entity_Id;
+      Typ        : Entity_Id;
+      Kind       : Formal_Kind;
+      Subp       : Entity_Id;
+      Global_Var : Boolean)
    is
-      Elem : constant Perm_Tree_Access := Get (Current_Perm_Env, Id);
-      pragma Assert (Elem /= null);
-
    begin
-      --  Shallow unaliased parameters and globals cannot introduce pointer
-      --  aliasing.
+      --  Shallow parameters and globals need not be considered
+
+      if not Is_Deep (Typ) then
+         return;
+
+      elsif Kind = E_In_Parameter then
+
+         --  Input global variables are observed only
+
+         if Global_Var then
+            return;
+
+         --  Anonymous access to constant is an observe
 
-      if not Has_Alias (Id) and then Is_Shallow (Etype (Id)) then
-         null;
+         elsif Is_Anonymous_Access_Type (Typ)
+           and then Is_Access_Constant (Typ)
+         then
+            return;
 
-      --  Observed IN parameters and globals need not return a permission to
-      --  the caller.
+         --  Deep types other than access types define an observe
 
-      elsif Mode = E_In_Parameter and then not Is_Borrowed_In (Id) then
-         null;
+         elsif not Is_Access_Type (Typ) then
+            return;
+         end if;
+      end if;
 
       --  All other parameters and globals should return with mode RW to the
       --  caller.
 
-      else
-         if Permission (Elem) /= Read_Write then
+      declare
+         Tree : constant Perm_Tree_Access := Get (Current_Perm_Env, Id);
+      begin
+         if Permission (Tree) /= Read_Write then
             Perm_Error_Subprogram_End
               (E          => Id,
                Subp       => Subp,
                Perm       => Read_Write,
-               Found_Perm => Permission (Elem));
+               Found_Perm => Permission (Tree),
+               Expl       => Explanation (Tree));
          end if;
-      end if;
+      end;
    end Return_Parameter_Or_Global;
 
    -----------------------
@@ -4620,11 +5415,15 @@ package body Sem_SPARK is
 
    procedure Return_Parameters (Subp : Entity_Id) is
       Formal : Entity_Id;
-
    begin
       Formal := First_Formal (Subp);
       while Present (Formal) loop
-         Return_Parameter_Or_Global (Formal, Ekind (Formal), Subp);
+         Return_Parameter_Or_Global
+           (Id         => Formal,
+            Typ        => Retysp (Etype (Formal)),
+            Kind       => Ekind (Formal),
+            Subp       => Subp,
+            Global_Var => False);
          Next_Formal (Formal);
       end loop;
    end Return_Parameters;
@@ -4634,52 +5433,55 @@ package body Sem_SPARK is
    -------------------------
 
    procedure Set_Perm_Extensions
-     (T : Perm_Tree_Access;
-      P : Perm_Kind)
-   is
+     (T    : Perm_Tree_Access;
+      P    : Perm_Kind;
+      Expl : Node_Id) is
+
       procedure Free_Perm_Tree_Children (T : Perm_Tree_Access);
+      --  Free the permission tree of children if any, prio to replacing T
 
-      procedure Free_Perm_Tree_Children (T : Perm_Tree_Access)
-      is
+      -----------------------------
+      -- Free_Perm_Tree_Children --
+      -----------------------------
+
+      procedure Free_Perm_Tree_Children (T : Perm_Tree_Access) is
       begin
          case Kind (T) is
             when Entire_Object =>
                null;
 
             when Reference =>
-               Free_Perm_Tree (T.all.Tree.Get_All);
+               Free_Tree (T.all.Tree.Get_All);
 
             when Array_Component =>
-               Free_Perm_Tree (T.all.Tree.Get_Elem);
-
-            --  Free every Component subtree
+               Free_Tree (T.all.Tree.Get_Elem);
 
             when Record_Component =>
                declare
-                  Comp : Perm_Tree_Access;
+                  Hashtbl : Perm_Tree_Maps.Instance := Component (T);
+                  Comp    : Perm_Tree_Access;
 
                begin
-                  Comp := Perm_Tree_Maps.Get_First (Component (T));
+                  Comp := Perm_Tree_Maps.Get_First (Hashtbl);
                   while Comp /= null loop
-                     Free_Perm_Tree (Comp);
-                     Comp := Perm_Tree_Maps.Get_Next (Component (T));
+                     Free_Tree (Comp);
+                     Comp := Perm_Tree_Maps.Get_Next (Hashtbl);
                   end loop;
 
-                  Free_Perm_Tree (T.all.Tree.Other_Components);
+                  Perm_Tree_Maps.Reset (Hashtbl);
                end;
          end case;
       end Free_Perm_Tree_Children;
 
-      Son : constant Perm_Tree :=
-        Perm_Tree'
-          (Kind                => Entire_Object,
-           Is_Node_Deep        => Is_Node_Deep (T),
-           Permission          => Permission (T),
-           Children_Permission => P);
+   --  Start of processing for Set_Perm_Extensions
 
    begin
       Free_Perm_Tree_Children (T);
-      T.all.Tree := Son;
+      T.all.Tree := Perm_Tree'(Kind                => Entire_Object,
+                               Is_Node_Deep        => Is_Node_Deep (T),
+                               Explanation         => Expl,
+                               Permission          => Permission (T),
+                               Children_Permission => P);
    end Set_Perm_Extensions;
 
    ------------------------------
@@ -4687,1365 +5489,453 @@ package body Sem_SPARK is
    ------------------------------
 
    procedure Set_Perm_Extensions_Move
-     (T : Perm_Tree_Access;
-      E : Entity_Id)
+     (T    : Perm_Tree_Access;
+      E    : Entity_Id;
+      Expl : Node_Id)
    is
+      Check_Ty : constant Entity_Id := Retysp (E);
    begin
-      if not Is_Node_Deep (T) then
-         --  We are a shallow extension with same number of .all
+      --  Shallow extensions are set to RW
 
-         Set_Perm_Extensions (T, Read_Write);
+      if not Is_Node_Deep (T) then
+         Set_Perm_Extensions (T, Read_Write, Expl => Expl);
          return;
       end if;
 
-      --  We are a deep extension here (or the moved deep path)
+      --  Deep extensions are set to W before .all and NO afterwards
 
       T.all.Tree.Permission := Write_Only;
 
       case T.all.Tree.Kind is
-         --  Unroll the tree depending on the type
-
-         when Entire_Object =>
-            case Ekind (E) is
-               when Scalar_Kind
-                  | E_String_Literal_Subtype
-               =>
-                  Set_Perm_Extensions (T, No_Access);
-
-               --  No need to unroll here, directly put sons to No_Access
-
-               when Access_Kind =>
-                  if Ekind (E) in Access_Subprogram_Kind then
-                     null;
-                  else
-                     Set_Perm_Extensions (T, No_Access);
-                  end if;
-
-               --  No unrolling done, too complicated
 
-               when E_Class_Wide_Subtype
-                  | E_Class_Wide_Type
-                  | E_Incomplete_Type
-                  | E_Incomplete_Subtype
-                  | E_Exception_Type
-                  | E_Task_Type
-                  | E_Task_Subtype
-               =>
-                  Set_Perm_Extensions (T, No_Access);
-
-               --  Expand the tree. Replace the node with Array component.
+         --  For a folded tree of composite type, unfold the tree for better
+         --  precision.
 
+         when Entire_Object =>
+            case Ekind (Check_Ty) is
                when E_Array_Type
-                  | E_Array_Subtype =>
+                  | E_Array_Subtype
+               =>
                   declare
-                     Son : Perm_Tree_Access;
-
+                     C : constant Perm_Tree_Access :=
+                       new Perm_Tree_Wrapper'
+                         (Tree =>
+                            (Kind                => Entire_Object,
+                             Is_Node_Deep        => Is_Node_Deep (T),
+                             Explanation         => Expl,
+                             Permission          => Read_Write,
+                             Children_Permission => Read_Write));
                   begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Node_Deep (T),
-                           Permission          => Read_Write,
-                           Children_Permission => Read_Write));
-
-                     Set_Perm_Extensions_Move (Son, Component_Type (E));
-
-                     --  We change the current node from Entire_Object to
-                     --  Reference with Write_Only and the previous son.
-
-                     pragma Assert (Is_Node_Deep (T));
-
+                     Set_Perm_Extensions_Move
+                       (C, Component_Type (Check_Ty), Expl);
                      T.all.Tree := (Kind         => Array_Component,
                                     Is_Node_Deep => Is_Node_Deep (T),
+                                    Explanation  => Expl,
                                     Permission   => Write_Only,
-                                    Get_Elem     => Son);
+                                    Get_Elem     => C);
                   end;
 
-               --  Unroll, and set permission extensions with component type
-
-               when E_Record_Type
-                  | E_Record_Subtype
-                  | E_Record_Type_With_Private
-                  | E_Record_Subtype_With_Private
-                  | E_Protected_Type
-                  | E_Protected_Subtype
-               =>
+               when Record_Kind =>
                   declare
-                     --  Expand the tree. Replace the node with
-                     --  Record_Component.
+                     C       : Perm_Tree_Access;
+                     Comp    : Entity_Id;
+                     Hashtbl : Perm_Tree_Maps.Instance;
 
-                     Elem : Node_Id;
+                  begin
+                     Comp := First_Component_Or_Discriminant (Check_Ty);
+                     while Present (Comp) loop
 
-                     Son : Perm_Tree_Access;
+                        --  Unfold components which are visible in SPARK
 
-                  begin
-                     --  We change the current node from Entire_Object to
-                     --  Record_Component with same permission and an empty
-                     --  hash table as component list.
+                        if Component_Is_Visible_In_SPARK (Comp) then
+                           C := new Perm_Tree_Wrapper'
+                             (Tree =>
+                                (Kind                => Entire_Object,
+                                 Is_Node_Deep        => Is_Deep (Etype (Comp)),
+                                 Explanation         => Expl,
+                                 Permission          => Read_Write,
+                                 Children_Permission => Read_Write));
+                           Set_Perm_Extensions_Move (C, Etype (Comp), Expl);
+
+                        --  Hidden components are never deep
+
+                        else
+                           C := new Perm_Tree_Wrapper'
+                             (Tree =>
+                                (Kind                => Entire_Object,
+                                 Is_Node_Deep        => False,
+                                 Explanation         => Expl,
+                                 Permission          => Read_Write,
+                                 Children_Permission => Read_Write));
+                           Set_Perm_Extensions (C, Read_Write, Expl => Expl);
+                        end if;
 
-                     pragma Assert (Is_Node_Deep (T));
+                        Perm_Tree_Maps.Set
+                          (Hashtbl, Original_Record_Component (Comp), C);
+                        Next_Component_Or_Discriminant (Comp);
+                     end loop;
 
                      T.all.Tree :=
                        (Kind             => Record_Component,
                         Is_Node_Deep     => Is_Node_Deep (T),
+                        Explanation      => Expl,
                         Permission       => Write_Only,
-                        Component        => Perm_Tree_Maps.Nil,
-                        Other_Components =>
-                           new Perm_Tree_Wrapper'
-                          (Tree =>
-                               (Kind                => Entire_Object,
-                                Is_Node_Deep        => True,
-                                Permission          => Read_Write,
-                                Children_Permission => Read_Write)
-                          )
-                       );
-
-                     --  We fill the hash table with all sons of the record,
-                     --  with basic Entire_Objects nodes.
-                     Elem := First_Component_Or_Discriminant (E);
-                     while Present (Elem) loop
-                        Son := new Perm_Tree_Wrapper'
-                          (Tree =>
-                             (Kind                => Entire_Object,
-                              Is_Node_Deep        => Is_Deep (Etype (Elem)),
-                              Permission          => Read_Write,
-                              Children_Permission => Read_Write));
-
-                        Set_Perm_Extensions_Move (Son, Etype (Elem));
-
-                        Perm_Tree_Maps.Set
-                          (T.all.Tree.Component, Elem, Son);
-
-                        Next_Component_Or_Discriminant (Elem);
-                     end loop;
+                        Component        => Hashtbl);
                   end;
 
-               when E_Private_Type
-                  | E_Private_Subtype
-                  | E_Limited_Private_Type
-                  | E_Limited_Private_Subtype
-               =>
-                  Set_Perm_Extensions_Move (T, Underlying_Type (E));
+               --  Otherwise, extensions are set to NO
 
                when others =>
-                  raise Program_Error;
+                  Set_Perm_Extensions (T, No_Access, Expl);
             end case;
 
          when Reference =>
-            --  Now the son does not have the same number of .all
-            Set_Perm_Extensions (T, No_Access);
+            Set_Perm_Extensions (T, No_Access, Expl);
 
          when Array_Component =>
-            Set_Perm_Extensions_Move (Get_Elem (T), Component_Type (E));
+            Set_Perm_Extensions_Move
+              (Get_Elem (T), Component_Type (Check_Ty), Expl);
 
          when Record_Component =>
             declare
-               Comp : Perm_Tree_Access;
-               It : Node_Id;
+               C    : Perm_Tree_Access;
+               Comp : Entity_Id;
 
             begin
-               It := First_Component_Or_Discriminant (E);
-               while It /= Empty loop
-                  Comp := Perm_Tree_Maps.Get (Component (T), It);
-                  pragma Assert (Comp /= null);
-                  Set_Perm_Extensions_Move (Comp, It);
-                  It := Next_Component_Or_Discriminant (E);
-               end loop;
-
-               Set_Perm_Extensions (Other_Components (T), No_Access);
-            end;
-      end case;
-   end Set_Perm_Extensions_Move;
+               Comp := First_Component_Or_Discriminant (Check_Ty);
+               while Present (Comp) loop
+                  C := Perm_Tree_Maps.Get
+                    (Component (T), Original_Record_Component (Comp));
+                  pragma Assert (C /= null);
 
-   ------------------------------
-   -- Set_Perm_Prefixes_Assign --
-   ------------------------------
-
-   function Set_Perm_Prefixes_Assign
-     (N : Node_Id)
-       return Perm_Tree_Access
-   is
-      C : constant Perm_Tree_Access := Get_Perm_Tree (N);
-
-   begin
-      pragma Assert (Current_Checking_Mode = Assign);
+                  --  Move visible components
 
-      --  The function should not be called if has_function_component
-
-      pragma Assert (C /= null);
-
-      case Kind (C) is
-         when Entire_Object =>
-            pragma Assert (Children_Permission (C) = Read_Write);
-            C.all.Tree.Permission := Read_Write;
-
-         when Reference =>
-            pragma Assert (Get_All (C) /= null);
-
-            C.all.Tree.Permission :=
-              Lub (Permission (C), Permission (Get_All (C)));
-
-         when Array_Component =>
-            pragma Assert (C.all.Tree.Get_Elem /= null);
-
-            --  Given that it is not possible to know which element has been
-            --  assigned, then the permissions do not get changed in case of
-            --  Array_Component.
-
-            null;
-
-         when Record_Component =>
-            declare
-               Perm : Perm_Kind := Read_Write;
-
-               Comp : Perm_Tree_Access;
-
-            begin
-               --  We take the Glb of all the descendants, and then update the
-               --  permission of the node with it.
-               Comp := Perm_Tree_Maps.Get_First (Component (C));
-               while Comp /= null loop
-                  Perm := Glb (Perm, Permission (Comp));
-                  Comp := Perm_Tree_Maps.Get_Next (Component (C));
-               end loop;
-
-               Perm := Glb (Perm, Permission (Other_Components (C)));
-
-               C.all.Tree.Permission := Lub (Permission (C), Perm);
-            end;
-      end case;
-
-      case Nkind (N) is
-         --  Base identifier. End recursion here.
-
-         when N_Identifier
-            | N_Expanded_Name
-            | N_Defining_Identifier
-         =>
-            return null;
-
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
-         =>
-            return Set_Perm_Prefixes_Assign (Expression (N));
-
-         when N_Parameter_Specification =>
-            raise Program_Error;
-
-         --  Continue recursion on prefix
-
-         when N_Selected_Component =>
-            return Set_Perm_Prefixes_Assign (Prefix (N));
-
-         --  Continue recursion on prefix
-
-         when N_Indexed_Component
-            | N_Slice
-         =>
-            return Set_Perm_Prefixes_Assign (Prefix (N));
-
-         --  Continue recursion on prefix
-
-         when N_Explicit_Dereference =>
-            return Set_Perm_Prefixes_Assign (Prefix (N));
-
-         when N_Function_Call =>
-            raise Program_Error;
-
-         when others =>
-            raise Program_Error;
-
-      end case;
-   end Set_Perm_Prefixes_Assign;
-
-   ----------------------------------
-   -- Set_Perm_Prefixes_Borrow_Out --
-   ----------------------------------
-
-   function Set_Perm_Prefixes_Borrow_Out
-     (N : Node_Id)
-       return Perm_Tree_Access
-   is
-   begin
-      pragma Assert (Current_Checking_Mode = Borrow_Out);
-
-      case Nkind (N) is
-         --  Base identifier. Set permission to No.
-
-         when N_Identifier
-            | N_Expanded_Name
-         =>
-            declare
-               P : constant Node_Id := Entity (N);
-
-               C : constant Perm_Tree_Access :=
-                 Get (Current_Perm_Env, Unique_Entity (P));
-
-               pragma Assert (C /= null);
-
-            begin
-               --  Setting the initialization map to True, so that this
-               --  variable cannot be ignored anymore when looking at end
-               --  of elaboration of package.
-
-               Set (Current_Initialization_Map, Unique_Entity (P), True);
-
-               C.all.Tree.Permission := No_Access;
-               return C;
-            end;
-
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
-         =>
-            return Set_Perm_Prefixes_Borrow_Out (Expression (N));
-
-         when N_Parameter_Specification
-            | N_Defining_Identifier
-         =>
-            raise Program_Error;
-
-            --  We set the permission tree of its prefix, and then we extract
-            --  our subtree from the returned pointer and assign an adequate
-            --  permission to it, if unfolded. If folded, we unroll the tree
-            --  in one step.
-
-         when N_Selected_Component =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Borrow_Out (Prefix (N));
-
-            begin
-               if C = null then
-                  --  We went through a function call, do nothing
-
-                  return null;
-               end if;
-
-               --  The permission of the returned node should be No
-
-               pragma Assert (Permission (C) = No_Access);
-
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Record_Component);
-
-               if Kind (C) = Record_Component then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the record subtree.
-
-                  declare
-                     Selected_Component : constant Entity_Id :=
-                       Entity (Selector_Name (N));
-
-                     Selected_C : Perm_Tree_Access :=
-                       Perm_Tree_Maps.Get
-                         (Component (C), Selected_Component);
-
-                  begin
-                     if Selected_C = null then
-                        Selected_C := Other_Components (C);
-                     end if;
-
-                     pragma Assert (Selected_C /= null);
-
-                     Selected_C.all.Tree.Permission := No_Access;
-
-                     return Selected_C;
-                  end;
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with
-                     --  Record_Component.
-
-                     Elem : Node_Id;
-
-                     --  Create an empty hash table
-
-                     Hashtbl : Perm_Tree_Maps.Instance;
-
-                     --  We create the unrolled nodes, that will all have same
-                     --  permission than parent.
-
-                     Son : Perm_Tree_Access;
-
-                     ChildrenPerm : constant Perm_Kind :=
-                       Children_Permission (C);
-
-                  begin
-                     --  We change the current node from Entire_Object to
-                     --  Record_Component with same permission and an empty
-                     --  hash table as component list.
-
-                     C.all.Tree :=
-                       (Kind         => Record_Component,
-                        Is_Node_Deep => Is_Node_Deep (C),
-                        Permission   => Permission (C),
-                        Component    => Hashtbl,
-                        Other_Components =>
-                           new Perm_Tree_Wrapper'
-                          (Tree =>
-                               (Kind                => Entire_Object,
-                                Is_Node_Deep        => True,
-                                Permission          => ChildrenPerm,
-                                Children_Permission => ChildrenPerm)
-                          ));
-
-                     --  We fill the hash table with all sons of the record,
-                     --  with basic Entire_Objects nodes.
-                     Elem := First_Component_Or_Discriminant
-                       (Etype (Prefix (N)));
-
-                     while Present (Elem) loop
-                        Son := new Perm_Tree_Wrapper'
-                          (Tree =>
-                             (Kind                => Entire_Object,
-                              Is_Node_Deep        => Is_Deep (Etype (Elem)),
-                              Permission          => ChildrenPerm,
-                              Children_Permission => ChildrenPerm));
-
-                        Perm_Tree_Maps.Set
-                          (C.all.Tree.Component, Elem, Son);
-
-                        Next_Component_Or_Discriminant (Elem);
-                     end loop;
-
-                     --  Now we set the right field to No_Access, and then we
-                     --  return the tree to the sons, so that the recursion can
-                     --  continue.
-
-                     declare
-                        Selected_Component : constant Entity_Id :=
-                          Entity (Selector_Name (N));
-
-                        Selected_C : Perm_Tree_Access :=
-                          Perm_Tree_Maps.Get
-                            (Component (C), Selected_Component);
-
-                     begin
-                        if Selected_C = null then
-                           Selected_C := Other_Components (C);
-                        end if;
-
-                        pragma Assert (Selected_C /= null);
-
-                        Selected_C.all.Tree.Permission := No_Access;
-
-                        return Selected_C;
-                     end;
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
-
-            --  We set the permission tree of its prefix, and then we extract
-            --  from the returned pointer the subtree and assign an adequate
-            --  permission to it, if unfolded. If folded, we unroll the tree in
-            --  one step.
-
-         when N_Indexed_Component
-            | N_Slice
-         =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Borrow_Out (Prefix (N));
-
-            begin
-               if C = null then
-                  --  We went through a function call, do nothing
-
-                  return null;
-               end if;
-
-               --  The permission of the returned node should be either W
-               --  (because the recursive call sets <= Write_Only) or No
-               --  (if another path has been moved with 'Access).
-
-               pragma Assert (Permission (C) = No_Access);
-
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Array_Component);
-
-               if Kind (C) = Array_Component then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the elem subtree.
-
-                  pragma Assert (Get_Elem (C) /= null);
-
-                  C.all.Tree.Get_Elem.all.Tree.Permission := No_Access;
-
-                  return Get_Elem (C);
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace node with Array_Component.
-
-                     Son : Perm_Tree_Access;
-
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Node_Deep (C),
-                           Permission          => No_Access,
-                           Children_Permission => Children_Permission (C)));
-
-                     --  We change the current node from Entire_Object
-                     --  to Array_Component with same permission and the
-                     --  previously defined son.
-
-                     C.all.Tree := (Kind         => Array_Component,
-                                    Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => No_Access,
-                                    Get_Elem     => Son);
-
-                     return Get_Elem (C);
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
-
-            --  We set the permission tree of its prefix, and then we extract
-            --  from the returned pointer the subtree and assign an adequate
-            --  permission to it, if unfolded. If folded, we unroll the tree
-            --  at one step.
-
-         when N_Explicit_Dereference =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Borrow_Out (Prefix (N));
-
-            begin
-               if C = null then
-                  --  We went through a function call. Do nothing.
-
-                  return null;
-               end if;
-
-               --  The permission of the returned node should be No
-
-               pragma Assert (Permission (C) = No_Access);
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Reference);
-
-               if Kind (C) = Reference then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the elem subtree.
-
-                  pragma Assert (Get_All (C) /= null);
-
-                  C.all.Tree.Get_All.all.Tree.Permission := No_Access;
-
-                  return Get_All (C);
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with Reference.
-
-                     Son : Perm_Tree_Access;
-
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Deep (Etype (N)),
-                           Permission          => No_Access,
-                           Children_Permission => Children_Permission (C)));
-
-                     --  We change the current node from Entire_Object to
-                     --  Reference with No_Access and the previous son.
-
-                     pragma Assert (Is_Node_Deep (C));
-
-                     C.all.Tree := (Kind         => Reference,
-                                    Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => No_Access,
-                                    Get_All      => Son);
-
-                     return Get_All (C);
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
-
-         when N_Function_Call =>
-            return null;
-
-         when others =>
-            raise Program_Error;
-      end case;
-   end Set_Perm_Prefixes_Borrow_Out;
-
-   ----------------------------
-   -- Set_Perm_Prefixes_Move --
-   ----------------------------
-
-   function Set_Perm_Prefixes_Move
-     (N : Node_Id; Mode : Checking_Mode)
-       return Perm_Tree_Access
-   is
-   begin
-      case Nkind (N) is
-
-         --  Base identifier. Set permission to W or No depending on Mode.
-
-         when N_Identifier
-            | N_Expanded_Name
-         =>
-            declare
-               P : constant Node_Id := Entity (N);
-               C : constant Perm_Tree_Access :=
-                     Get (Current_Perm_Env, Unique_Entity (P));
-
-            begin
-               --  The base tree can be RW (first move from this base path) or
-               --  W (already some extensions values moved), or even No_Access
-               --  (extensions moved with 'Access). But it cannot be Read_Only
-               --  (we get an error).
-
-               if Permission (C) = Read_Only then
-                  raise Unrecoverable_Error;
-               end if;
-
-               --  Setting the initialization map to True, so that this
-               --  variable cannot be ignored anymore when looking at end
-               --  of elaboration of package.
-
-               Set (Current_Initialization_Map, Unique_Entity (P), True);
-
-               if C = null then
-                  --  No null possible here, there are no parents for the path.
-                  --  This means we are using a global variable without adding
-                  --  it in environment with a global aspect.
-
-                  Illegal_Global_Usage (N);
-               end if;
-
-               if Mode = Super_Move then
-                  C.all.Tree.Permission := No_Access;
-               else
-                  C.all.Tree.Permission := Glb (Write_Only, Permission (C));
-               end if;
-
-               return C;
-            end;
-
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
-         =>
-            return Set_Perm_Prefixes_Move (Expression (N), Mode);
-
-         when N_Parameter_Specification
-            | N_Defining_Identifier
-         =>
-            raise Program_Error;
-
-            --  We set the permission tree of its prefix, and then we extract
-            --  from the returned pointer our subtree and assign an adequate
-            --  permission to it, if unfolded. If folded, we unroll the tree
-            --  at one step.
-
-         when N_Selected_Component =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Move (Prefix (N), Mode);
-
-            begin
-               if C = null then
-                  --  We went through a function call, do nothing
-
-                  return null;
-               end if;
-
-               --  The permission of the returned node should be either W
-               --  (because the recursive call sets <= Write_Only) or No
-               --  (if another path has been moved with 'Access).
-
-               pragma Assert (Permission (C) = No_Access
-                              or else Permission (C) = Write_Only);
-
-               if Mode = Super_Move then
-                  --  The permission of the returned node should be No (thanks
-                  --  to the recursion).
-
-                  pragma Assert (Permission (C) = No_Access);
-                  null;
-               end if;
-
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Record_Component);
-
-               if Kind (C) = Record_Component then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the record subtree.
-
-                  declare
-                     Selected_Component : constant Entity_Id :=
-                       Entity (Selector_Name (N));
-
-                     Selected_C : Perm_Tree_Access :=
-                       Perm_Tree_Maps.Get
-                         (Component (C), Selected_Component);
-
-                  begin
-                     if Selected_C = null then
-                        --  If the hash table returns no element, then we fall
-                        --  into the part of Other_Components.
-                        pragma Assert (Is_Tagged_Type (Etype (Prefix (N))));
-
-                        Selected_C := Other_Components (C);
-                     end if;
-
-                     pragma Assert (Selected_C /= null);
-
-                     --  The Selected_C can have permissions:
-                     --  RW : first move in this path
-                     --  W  : Already other moves in this path
-                     --  No : Already other moves with 'Access
-
-                     pragma Assert (Permission (Selected_C) /= Read_Only);
-                     if Mode = Super_Move then
-                        Selected_C.all.Tree.Permission := No_Access;
-                     else
-                        Selected_C.all.Tree.Permission :=
-                          Glb (Write_Only, Permission (Selected_C));
-
-                     end if;
-
-                     return Selected_C;
-                  end;
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with
-                     --  Record_Component.
-
-                     Elem : Node_Id;
-
-                     --  Create an empty hash table
-
-                     Hashtbl : Perm_Tree_Maps.Instance;
-
-                     --  We are in Move or Super_Move mode, hence we can assume
-                     --  that the Children_permission is RW, given that there
-                     --  are no other paths that could have been moved.
-
-                     pragma Assert (Children_Permission (C) = Read_Write);
-
-                     --  We create the unrolled nodes, that will all have RW
-                     --  permission given that we are in move mode. We will
-                     --  then set the right node to W.
-
-                     Son : Perm_Tree_Access;
-
-                  begin
-                     --  We change the current node from Entire_Object to
-                     --  Record_Component with same permission and an empty
-                     --  hash table as component list.
-
-                     C.all.Tree :=
-                       (Kind             => Record_Component,
-                        Is_Node_Deep     => Is_Node_Deep (C),
-                        Permission       => Permission (C),
-                        Component        => Hashtbl,
-                        Other_Components =>
-                           new Perm_Tree_Wrapper'
-                          (Tree =>
-                               (Kind                => Entire_Object,
-                                Is_Node_Deep        => True,
-                                Permission          => Read_Write,
-                                Children_Permission => Read_Write)
-                          ));
-
-                     --  We fill the hash table with all sons of the record,
-                     --  with basic Entire_Objects nodes.
-                     Elem := First_Component_Or_Discriminant
-                       (Etype (Prefix (N)));
-
-                     while Present (Elem) loop
-                        Son := new Perm_Tree_Wrapper'
-                          (Tree =>
-                             (Kind                => Entire_Object,
-                              Is_Node_Deep        => Is_Deep (Etype (Elem)),
-                              Permission          => Read_Write,
-                              Children_Permission => Read_Write));
-
-                        Perm_Tree_Maps.Set
-                          (C.all.Tree.Component, Elem, Son);
-
-                        Next_Component_Or_Discriminant (Elem);
-                     end loop;
-
-                     --  Now we set the right field to Write_Only or No_Access
-                     --  depending on mode, and then we return the tree to the
-                     --  sons, so that the recursion can continue.
-
-                     declare
-                        Selected_Component : constant Entity_Id :=
-                          Entity (Selector_Name (N));
-
-                        Selected_C : Perm_Tree_Access :=
-                          Perm_Tree_Maps.Get
-                            (Component (C), Selected_Component);
-
-                     begin
-                        if Selected_C = null then
-                           Selected_C := Other_Components (C);
-                        end if;
-
-                        pragma Assert (Selected_C /= null);
-
-                        --  Given that this is a newly created Select_C, we can
-                        --  safely assume that its permission is Read_Write.
-
-                        pragma Assert (Permission (Selected_C) =
-                                         Read_Write);
-
-                        if Mode = Super_Move then
-                           Selected_C.all.Tree.Permission := No_Access;
-                        else
-                           Selected_C.all.Tree.Permission := Write_Only;
-                        end if;
-
-                        return Selected_C;
-                     end;
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
-
-            --  We set the permission tree of its prefix, and then we extract
-            --  from the returned pointer the subtree and assign an adequate
-            --  permission to it, if unfolded. If folded, we unroll the tree
-            --  at one step.
-
-         when N_Indexed_Component
-            | N_Slice
-         =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Move (Prefix (N), Mode);
-
-            begin
-               if C = null then
-                  --  We went through a function call, do nothing
-
-                  return null;
-               end if;
-
-               --  The permission of the returned node should be either
-               --  W (because the recursive call sets <= Write_Only)
-               --  or No (if another path has been moved with 'Access)
-
-               if Mode = Super_Move then
-                  pragma Assert (Permission (C) = No_Access);
-                  null;
-               else
-                  pragma Assert (Permission (C) = Write_Only
-                                 or else Permission (C) = No_Access);
-                  null;
-               end if;
-
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Array_Component);
-
-               if Kind (C) = Array_Component then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the elem subtree.
-
-                  if Get_Elem (C) = null then
-                     --  Hash_Table_Error
-                     raise Program_Error;
-                  end if;
-
-                  --  The Get_Elem can have permissions :
-                  --  RW : first move in this path
-                  --  W  : Already other moves in this path
-                  --  No : Already other moves with 'Access
-
-                  pragma Assert (Permission (Get_Elem (C)) /= Read_Only);
-
-                  if Mode = Super_Move then
-                     C.all.Tree.Get_Elem.all.Tree.Permission := No_Access;
-                  else
-                     C.all.Tree.Get_Elem.all.Tree.Permission :=
-                       Glb (Write_Only, Permission (Get_Elem (C)));
-                  end if;
-
-                  return Get_Elem (C);
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace node with Array_Component.
-
-                     --  We are in move mode, hence we can assume that the
-                     --  Children_permission is RW.
-
-                     pragma Assert (Children_Permission (C) = Read_Write);
-
-                     Son : Perm_Tree_Access;
-
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Node_Deep (C),
-                           Permission          => Read_Write,
-                           Children_Permission => Read_Write));
-
-                     if Mode = Super_Move then
-                        Son.all.Tree.Permission := No_Access;
-                     else
-                        Son.all.Tree.Permission := Write_Only;
-                     end if;
-
-                     --  We change the current node from Entire_Object
-                     --  to Array_Component with same permission and the
-                     --  previously defined son.
-
-                     C.all.Tree := (Kind         => Array_Component,
-                                    Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => Permission (C),
-                                    Get_Elem     => Son);
-
-                     return Get_Elem (C);
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
-
-            --  We set the permission tree of its prefix, and then we extract
-            --  from the returned pointer the subtree and assign an adequate
-            --  permission to it, if unfolded. If folded, we unroll the tree
-            --  at one step.
-
-         when N_Explicit_Dereference =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Move (Prefix (N), Move);
-
-            begin
-               if C = null then
-                  --  We went through a function call: do nothing
-
-                  return null;
-               end if;
-
-               --  The permission of the returned node should be only
-               --  W (because the recursive call sets <= Write_Only)
-               --  No is NOT POSSIBLE here
-
-               pragma Assert (Permission (C) = Write_Only);
-
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Reference);
-
-               if Kind (C) = Reference then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the elem subtree.
-
-                  if Get_All (C) = null then
-                     --  Hash_Table_Error
-                     raise Program_Error;
-                  end if;
-
-                  --  The Get_All can have permissions :
-                  --  RW : first move in this path
-                  --  W  : Already other moves in this path
-                  --  No : Already other moves with 'Access
-
-                  pragma Assert (Permission (Get_All (C)) /= Read_Only);
-
-                  if Mode = Super_Move then
-                     C.all.Tree.Get_All.all.Tree.Permission := No_Access;
-                  else
-                     Get_All (C).all.Tree.Permission :=
-                       Glb (Write_Only, Permission (Get_All (C)));
-                  end if;
-                  return Get_All (C);
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with Reference.
-
-                     --  We are in Move or Super_Move mode, hence we can assume
-                     --  that the Children_permission is RW.
-
-                     pragma Assert (Children_Permission (C) = Read_Write);
-
-                     Son : Perm_Tree_Access;
-
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Deep (Etype (N)),
-                           Permission          => Read_Write,
-                           Children_Permission => Read_Write));
-
-                     if Mode = Super_Move then
-                        Son.all.Tree.Permission := No_Access;
-                     else
-                        Son.all.Tree.Permission := Write_Only;
-                     end if;
-
-                     --  We change the current node from Entire_Object to
-                     --  Reference with Write_Only and the previous son.
-
-                     pragma Assert (Is_Node_Deep (C));
-
-                     C.all.Tree := (Kind         => Reference,
-                                    Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => Write_Only,
-                                    --  Write_only is equal to C.Permission
-                                    Get_All      => Son);
-
-                     return Get_All (C);
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
+                  if Component_Is_Visible_In_SPARK (Comp) then
+                     Set_Perm_Extensions_Move (C, Etype (Comp), Expl);
 
-         when N_Function_Call =>
-            return null;
+                  --  Hidden components are never deep
 
-         when others =>
-            raise Program_Error;
-      end case;
+                  else
+                     Set_Perm_Extensions (C, Read_Write, Expl => Expl);
+                  end if;
 
-   end Set_Perm_Prefixes_Move;
+                  Next_Component_Or_Discriminant (Comp);
+               end loop;
+            end;
+      end case;
+   end Set_Perm_Extensions_Move;
 
-   -------------------------------
-   -- Set_Perm_Prefixes_Observe --
-   -------------------------------
+   -----------------------
+   -- Set_Perm_Prefixes --
+   -----------------------
 
-   function Set_Perm_Prefixes_Observe
-     (N : Node_Id)
-      return Perm_Tree_Access
+   function Set_Perm_Prefixes
+     (N    : Node_Id;
+      Perm : Perm_Kind_Option;
+      Expl : Node_Id) return Perm_Tree_Access
    is
    begin
-      pragma Assert (Current_Checking_Mode = Observe);
-
       case Nkind (N) is
-         --  Base identifier. Set permission to R.
-
-         when N_Identifier
-            | N_Expanded_Name
-            | N_Defining_Identifier
+         when N_Expanded_Name
+            | N_Identifier
          =>
             declare
-               P : Node_Id;
-               C : Perm_Tree_Access;
+               E : constant Entity_Id := Unique_Entity (Entity (N));
+               C : constant Perm_Tree_Access := Get (Current_Perm_Env, E);
+               pragma Assert (C /= null);
 
             begin
-               if Nkind (N) = N_Defining_Identifier then
-                  P := N;
-               else
-                  P := Entity (N);
+               if Perm /= None then
+                  C.all.Tree.Permission := Glb (Perm, Permission (C));
                end if;
 
-               C := Get (Current_Perm_Env, Unique_Entity (P));
-               --  Setting the initialization map to True, so that this
-               --  variable cannot be ignored anymore when looking at end
-               --  of elaboration of package.
+               return C;
+            end;
 
-               Set (Current_Initialization_Map, Unique_Entity (P), True);
+         --  For a nonterminal path, we set the permission tree of its prefix,
+         --  and then we extract from the returned pointer the subtree and
+         --  assign an adequate permission to it, if unfolded. If folded,
+         --  we unroll the tree one level.
 
-               if C = null then
-                  --  No null possible here, there are no parents for the path.
-                  --  This means we are using a global variable without adding
-                  --  it in environment with a global aspect.
+         when N_Explicit_Dereference =>
+            declare
+               C : constant Perm_Tree_Access :=
+                 Set_Perm_Prefixes (Prefix (N), Perm, Expl);
+               pragma Assert (C /= null);
+               pragma Assert (Kind (C) = Entire_Object
+                              or else Kind (C) = Reference);
+            begin
+               --  The tree is already unfolded. Replace the permission of the
+               --  dereference.
 
-                  Illegal_Global_Usage (N);
-               end if;
+               if Kind (C) = Reference then
+                  declare
+                     D : constant Perm_Tree_Access := Get_All (C);
+                     pragma Assert (D /= null);
 
-               C.all.Tree.Permission := Glb (Read_Only, Permission (C));
+                  begin
+                     if Perm /= None then
+                        D.all.Tree.Permission := Glb (Perm, Permission (D));
+                     end if;
 
-               return C;
-            end;
+                     return D;
+                  end;
 
-         when N_Type_Conversion
-            | N_Unchecked_Type_Conversion
-            | N_Qualified_Expression
-         =>
-            return Set_Perm_Prefixes_Observe (Expression (N));
+               --  The tree is folded. Expand it.
 
-         when N_Parameter_Specification =>
-            raise Program_Error;
+               else
+                  declare
+                     pragma Assert (Kind (C) = Entire_Object);
+
+                     Child_P : constant Perm_Kind := Children_Permission (C);
+                     D : constant Perm_Tree_Access :=
+                       new Perm_Tree_Wrapper'
+                         (Tree =>
+                            (Kind                => Entire_Object,
+                             Is_Node_Deep        => Is_Deep (Etype (N)),
+                             Explanation         => Expl,
+                             Permission          => Child_P,
+                             Children_Permission => Child_P));
+                  begin
+                     if Perm /= None then
+                        D.all.Tree.Permission := Perm;
+                     end if;
 
-            --  We set the permission tree of its prefix, and then we extract
-            --  from the returned pointer our subtree and assign an adequate
-            --  permission to it, if unfolded. If folded, we unroll the tree
-            --  at one step.
+                     C.all.Tree := (Kind         => Reference,
+                                    Is_Node_Deep => Is_Node_Deep (C),
+                                    Explanation  => Expl,
+                                    Permission   => Permission (C),
+                                    Get_All      => D);
+                     return D;
+                  end;
+               end if;
+            end;
 
          when N_Selected_Component =>
             declare
                C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Observe (Prefix (N));
-
-            begin
-               if C = null then
-                  --  We went through a function call, do nothing
-
-                  return null;
-               end if;
-
+                 Set_Perm_Prefixes (Prefix (N), Perm, Expl);
+               pragma Assert (C /= null);
                pragma Assert (Kind (C) = Entire_Object
                               or else Kind (C) = Record_Component);
+            begin
+               --  The tree is already unfolded. Replace the permission of the
+               --  component.
 
                if Kind (C) = Record_Component then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the record subtree. We put the permission to the
-                  --  glb of read_only and its current permission, to consider
-                  --  the case of observing x.y while x.z has been moved. Then
-                  --  x should be No_Access.
-
                   declare
-                     Selected_Component : constant Entity_Id :=
-                       Entity (Selector_Name (N));
-
-                     Selected_C : Perm_Tree_Access :=
-                       Perm_Tree_Maps.Get
-                         (Component (C), Selected_Component);
+                     Comp : constant Entity_Id :=
+                       Original_Record_Component
+                         (Entity (Selector_Name (N)));
+                     D : constant Perm_Tree_Access :=
+                       Perm_Tree_Maps.Get (Component (C), Comp);
+                     pragma Assert (D /= null);
 
                   begin
-                     if Selected_C = null then
-                        Selected_C := Other_Components (C);
+                     if Perm /= None then
+                        D.all.Tree.Permission := Glb (Perm, Permission (D));
                      end if;
 
-                     pragma Assert (Selected_C /= null);
-
-                     Selected_C.all.Tree.Permission :=
-                       Glb (Read_Only, Permission (Selected_C));
-
-                     return Selected_C;
+                     return D;
                   end;
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with
-                     --  Record_Component.
 
-                     Elem : Node_Id;
+               --  The tree is folded. Expand it.
 
-                     --  Create an empty hash table
+               else
+                  declare
+                     pragma Assert (Kind (C) = Entire_Object);
 
+                     D       : Perm_Tree_Access;
+                     D_This  : Perm_Tree_Access;
+                     Comp    : Node_Id;
+                     P       : Perm_Kind;
+                     Child_P : constant Perm_Kind := Children_Permission (C);
                      Hashtbl : Perm_Tree_Maps.Instance;
-
-                     --  We create the unrolled nodes, that will all have RW
-                     --  permission given that we are in move mode. We will
-                     --  then set the right node to W.
-
-                     Son : Perm_Tree_Access;
-
-                     Child_Perm : constant Perm_Kind :=
-                       Children_Permission (C);
+                     --  Create an empty hash table
 
                   begin
-                     --  We change the current node from Entire_Object to
-                     --  Record_Component with same permission and an empty
-                     --  hash table as component list.
+                     Comp :=
+                       First_Component_Or_Discriminant
+                         (Retysp (Etype (Prefix (N))));
+
+                     while Present (Comp) loop
+                        if Perm /= None
+                          and then Original_Record_Component (Comp) =
+                          Original_Record_Component
+                            (Entity (Selector_Name (N)))
+                        then
+                           P := Perm;
+                        else
+                           P := Child_P;
+                        end if;
 
-                     C.all.Tree :=
-                       (Kind             => Record_Component,
-                        Is_Node_Deep     => Is_Node_Deep (C),
-                        Permission       => Permission (C),
-                        Component        => Hashtbl,
-                        Other_Components =>
-                           new Perm_Tree_Wrapper'
-                          (Tree =>
-                               (Kind                => Entire_Object,
-                                Is_Node_Deep        => True,
-                                Permission          => Child_Perm,
-                                Children_Permission => Child_Perm)
-                          ));
-
-                     --  We fill the hash table with all sons of the record,
-                     --  with basic Entire_Objects nodes.
-                     Elem := First_Component_Or_Discriminant
-                       (Etype (Prefix (N)));
-
-                     while Present (Elem) loop
-                        Son := new Perm_Tree_Wrapper'
+                        D := new Perm_Tree_Wrapper'
                           (Tree =>
                              (Kind                => Entire_Object,
-                              Is_Node_Deep        => Is_Deep (Etype (Elem)),
-                              Permission          => Child_Perm,
-                              Children_Permission => Child_Perm));
-
+                              Is_Node_Deep        =>
+                                --  Hidden components are never deep
+                                Component_Is_Visible_In_SPARK (Comp)
+                                  and then Is_Deep (Etype (Comp)),
+                              Explanation         => Expl,
+                              Permission          => P,
+                              Children_Permission => Child_P));
                         Perm_Tree_Maps.Set
-                          (C.all.Tree.Component, Elem, Son);
-
-                        Next_Component_Or_Discriminant (Elem);
-                     end loop;
+                          (Hashtbl, Original_Record_Component (Comp), D);
 
-                     --  Now we set the right field to Read_Only. and then we
-                     --  return the tree to the sons, so that the recursion can
-                     --  continue.
+                        --  Store the tree to return for this component
 
-                     declare
-                        Selected_Component : constant Entity_Id :=
-                          Entity (Selector_Name (N));
-
-                        Selected_C : Perm_Tree_Access :=
-                          Perm_Tree_Maps.Get
-                            (Component (C), Selected_Component);
-
-                     begin
-                        if Selected_C = null then
-                           Selected_C := Other_Components (C);
+                        if Original_Record_Component (Comp) =
+                          Original_Record_Component
+                            (Entity (Selector_Name (N)))
+                        then
+                           D_This := D;
                         end if;
 
-                        pragma Assert (Selected_C /= null);
-
-                        Selected_C.all.Tree.Permission :=
-                          Glb (Read_Only, Child_Perm);
+                        Next_Component_Or_Discriminant (Comp);
+                     end loop;
 
-                        return Selected_C;
-                     end;
+                     C.all.Tree := (Kind         => Record_Component,
+                                    Is_Node_Deep => Is_Node_Deep (C),
+                                    Explanation  => Expl,
+                                    Permission   => Permission (C),
+                                    Component    => Hashtbl);
+                     return D_This;
                   end;
-               else
-                  raise Program_Error;
                end if;
             end;
 
-         --  We set the permission tree of its prefix, and then we extract from
-         --  the returned pointer the subtree and assign an adequate permission
-         --  to it, if unfolded. If folded, we unroll the tree at one step.
-
          when N_Indexed_Component
             | N_Slice
          =>
             declare
                C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Observe (Prefix (N));
-
-            begin
-               if C = null then
-                  --  We went through a function call, do nothing
-
-                  return null;
-               end if;
-
+                 Set_Perm_Prefixes (Prefix (N), Perm, Expl);
+               pragma Assert (C /= null);
                pragma Assert (Kind (C) = Entire_Object
                               or else Kind (C) = Array_Component);
+            begin
+               --  The tree is already unfolded. Replace the permission of the
+               --  component.
 
                if Kind (C) = Array_Component then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the elem subtree.
-
-                  pragma Assert (Get_Elem (C) /= null);
-
-                  C.all.Tree.Get_Elem.all.Tree.Permission :=
-                    Glb (Read_Only, Permission (Get_Elem (C)));
-
-                  return Get_Elem (C);
-               elsif Kind (C) = Entire_Object then
                   declare
-                     --  Expand the tree. Replace node with Array_Component.
+                     D : constant Perm_Tree_Access := Get_Elem (C);
+                     pragma Assert (D /= null);
 
-                     Son : Perm_Tree_Access;
+                  begin
+                     if Perm /= None then
+                        D.all.Tree.Permission := Glb (Perm, Permission (D));
+                     end if;
 
-                     Child_Perm : constant Perm_Kind :=
-                       Glb (Read_Only, Children_Permission (C));
+                     return D;
+                  end;
 
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Node_Deep (C),
-                           Permission          => Child_Perm,
-                           Children_Permission => Child_Perm));
+               --  The tree is folded. Expand it.
 
-                     --  We change the current node from Entire_Object
-                     --  to Array_Component with same permission and the
-                     --  previously defined son.
+               else
+                  declare
+                     pragma Assert (Kind (C) = Entire_Object);
+
+                     Child_P : constant Perm_Kind := Children_Permission (C);
+                     D : constant Perm_Tree_Access :=
+                       new Perm_Tree_Wrapper'
+                         (Tree =>
+                            (Kind                => Entire_Object,
+                             Is_Node_Deep        => Is_Node_Deep (C),
+                             Explanation         => Expl,
+                             Permission          => Child_P,
+                             Children_Permission => Child_P));
+                  begin
+                     if Perm /= None then
+                        D.all.Tree.Permission := Perm;
+                     end if;
 
                      C.all.Tree := (Kind         => Array_Component,
                                     Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => Child_Perm,
-                                    Get_Elem     => Son);
-
-                     return Get_Elem (C);
+                                    Explanation  => Expl,
+                                    Permission   => Permission (C),
+                                    Get_Elem     => D);
+                     return D;
                   end;
-
-               else
-                  raise Program_Error;
                end if;
             end;
 
-         --  We set the permission tree of its prefix, and then we extract from
-         --  the returned pointer the subtree and assign an adequate permission
-         --  to it, if unfolded. If folded, we unroll the tree at one step.
-
-         when N_Explicit_Dereference =>
-            declare
-               C : constant Perm_Tree_Access :=
-                 Set_Perm_Prefixes_Observe (Prefix (N));
+         when N_Qualified_Expression
+            | N_Type_Conversion
+            | N_Unchecked_Type_Conversion
+         =>
+            return Set_Perm_Prefixes (Expression (N), Perm, Expl);
 
-            begin
-               if C = null then
-                  --  We went through a function call, do nothing
+         when others =>
+            raise Program_Error;
+      end case;
+   end Set_Perm_Prefixes;
 
-                  return null;
-               end if;
+   ------------------------------
+   -- Set_Perm_Prefixes_Assign --
+   ------------------------------
 
-               pragma Assert (Kind (C) = Entire_Object
-                              or else Kind (C) = Reference);
+   procedure Set_Perm_Prefixes_Assign (N : Node_Id) is
+      C : constant Perm_Tree_Access := Get_Perm_Tree (N);
 
-               if Kind (C) = Reference then
-                  --  The tree is unfolded. We just modify the permission and
-                  --  return the elem subtree.
+   begin
+      case Kind (C) is
+         when Entire_Object =>
+            pragma Assert (Children_Permission (C) = Read_Write);
+            C.all.Tree.Permission := Read_Write;
 
-                  pragma Assert (Get_All (C) /= null);
+         when Reference =>
+            C.all.Tree.Permission :=
+              Lub (Permission (C), Permission (Get_All (C)));
 
-                  C.all.Tree.Get_All.all.Tree.Permission :=
-                    Glb (Read_Only, Permission (Get_All (C)));
+         when Array_Component =>
+            null;
 
-                  return Get_All (C);
-               elsif Kind (C) = Entire_Object then
-                  declare
-                     --  Expand the tree. Replace the node with Reference.
+         when Record_Component =>
+            declare
+               Comp : Perm_Tree_Access;
+               Perm : Perm_Kind := Read_Write;
 
-                     Son : Perm_Tree_Access;
+            begin
+               --  We take the Glb of all the descendants, and then update the
+               --  permission of the node with it.
 
-                     Child_Perm : constant Perm_Kind :=
-                       Glb (Read_Only, Children_Permission (C));
+               Comp := Perm_Tree_Maps.Get_First (Component (C));
+               while Comp /= null loop
+                  Perm := Glb (Perm, Permission (Comp));
+                  Comp := Perm_Tree_Maps.Get_Next (Component (C));
+               end loop;
 
-                  begin
-                     Son := new Perm_Tree_Wrapper'
-                       (Tree =>
-                          (Kind                => Entire_Object,
-                           Is_Node_Deep        => Is_Deep (Etype (N)),
-                           Permission          => Child_Perm,
-                           Children_Permission => Child_Perm));
+               C.all.Tree.Permission := Lub (Permission (C), Perm);
+            end;
+      end case;
 
-                     --  We change the current node from Entire_Object to
-                     --  Reference with Write_Only and the previous son.
+      case Nkind (N) is
 
-                     pragma Assert (Is_Node_Deep (C));
+         --  Base identifier end recursion
 
-                     C.all.Tree := (Kind         => Reference,
-                                    Is_Node_Deep => Is_Node_Deep (C),
-                                    Permission   => Child_Perm,
-                                    Get_All      => Son);
+         when N_Expanded_Name
+            | N_Identifier
+         =>
+            null;
 
-                     return Get_All (C);
-                  end;
-               else
-                  raise Program_Error;
-               end if;
-            end;
+         when N_Explicit_Dereference
+            | N_Indexed_Component
+            | N_Selected_Component
+            | N_Slice
+         =>
+            Set_Perm_Prefixes_Assign (Prefix (N));
 
-         when N_Function_Call =>
-            return null;
+         when N_Qualified_Expression
+            | N_Type_Conversion
+            | N_Unchecked_Type_Conversion
+         =>
+            Set_Perm_Prefixes_Assign (Expression (N));
 
          when others =>
             raise Program_Error;
-
       end case;
-   end Set_Perm_Prefixes_Observe;
+   end Set_Perm_Prefixes_Assign;
 
    -------------------
    -- Setup_Globals --
@@ -6053,73 +5943,42 @@ package body Sem_SPARK is
 
    procedure Setup_Globals (Subp : Entity_Id) is
 
-      procedure Setup_Globals_From_List
-        (First_Item : Node_Id;
-         Kind       : Formal_Kind);
-      --  Set up global items from the list starting at Item
-
-      procedure Setup_Globals_Of_Mode (Global_Mode : Name_Id);
-      --  Set up global items for the mode Global_Mode
+      procedure Setup_Global
+        (Expr       : Node_Id;
+         Typ        : Entity_Id;
+         Kind       : Formal_Kind;
+         Subp       : Entity_Id;
+         Global_Var : Boolean);
+      --  Proxy procedure to set up globals, to adjust for the type of first
+      --  parameter expected by Setup_Parameter_Or_Global.
 
-      -----------------------------
-      -- Setup_Globals_From_List --
-      -----------------------------
+      ------------------
+      -- Setup_Global --
+      ------------------
 
-      procedure Setup_Globals_From_List
-        (First_Item : Node_Id;
-         Kind       : Formal_Kind)
+      procedure Setup_Global
+        (Expr       : Node_Id;
+         Typ        : Entity_Id;
+         Kind       : Formal_Kind;
+         Subp       : Entity_Id;
+         Global_Var : Boolean)
       is
-         Item : Node_Id := First_Item;
-         E    : Entity_Id;
-
-      begin
-         while Present (Item) loop
-            E := Entity (Item);
-
-            --  Ignore abstract states, which play no role in pointer aliasing
-
-            if Ekind (E) = E_Abstract_State then
-               null;
-            else
-               Setup_Parameter_Or_Global (E, Kind);
-            end if;
-            Next_Global (Item);
-         end loop;
-      end Setup_Globals_From_List;
-
-      ---------------------------
-      -- Setup_Globals_Of_Mode --
-      ---------------------------
-
-      procedure Setup_Globals_Of_Mode (Global_Mode : Name_Id) is
-         Kind : Formal_Kind;
-
       begin
-         case Global_Mode is
-            when Name_Input | Name_Proof_In =>
-               Kind := E_In_Parameter;
-            when Name_Output =>
-               Kind := E_Out_Parameter;
-            when Name_In_Out =>
-               Kind := E_In_Out_Parameter;
-            when others =>
-               raise Program_Error;
-         end case;
+         Setup_Parameter_Or_Global
+           (Id         => Entity (Expr),
+            Typ        => Typ,
+            Kind       => Kind,
+            Subp       => Subp,
+            Global_Var => Global_Var,
+            Expl       => Expr);
+      end Setup_Global;
 
-         --  Set up both global items from Global and Refined_Global pragmas
-
-         Setup_Globals_From_List (First_Global (Subp, Global_Mode), Kind);
-         Setup_Globals_From_List
-           (First_Global (Subp, Global_Mode, Refined => True), Kind);
-      end Setup_Globals_Of_Mode;
+      procedure Setup_Globals_Inst is new Handle_Globals (Setup_Global);
 
    --  Start of processing for Setup_Globals
 
    begin
-      Setup_Globals_Of_Mode (Name_Proof_In);
-      Setup_Globals_Of_Mode (Name_Input);
-      Setup_Globals_Of_Mode (Name_Output);
-      Setup_Globals_Of_Mode (Name_In_Out);
+      Setup_Globals_Inst (Subp);
    end Setup_Globals;
 
    -------------------------------
@@ -6127,49 +5986,86 @@ package body Sem_SPARK is
    -------------------------------
 
    procedure Setup_Parameter_Or_Global
-     (Id   : Entity_Id;
-      Mode : Formal_Kind)
+     (Id         : Entity_Id;
+      Typ        : Entity_Id;
+      Kind       : Formal_Kind;
+      Subp       : Entity_Id;
+      Global_Var : Boolean;
+      Expl       : Node_Id)
    is
-      Elem : Perm_Tree_Access;
+      Perm : Perm_Kind_Option;
 
    begin
-      Elem := new Perm_Tree_Wrapper'
-        (Tree =>
-           (Kind                => Entire_Object,
-            Is_Node_Deep        => Is_Deep (Etype (Id)),
-            Permission          => Read_Write,
-            Children_Permission => Read_Write));
-
-      case Mode is
+      case Kind is
          when E_In_Parameter =>
 
-            --  Borrowed IN: RW for everybody
+            --  Shallow parameters and globals need not be considered
+
+            if not Is_Deep (Typ) then
+               Perm := None;
+
+            --  Inputs of functions have R permission only
+
+            elsif Ekind (Subp) = E_Function then
+               Perm := Read_Only;
+
+            --  Input global variables have R permission only
+
+            elsif Global_Var then
+               Perm := Read_Only;
 
-            if Is_Borrowed_In (Id) then
-               Elem.all.Tree.Permission := Read_Write;
-               Elem.all.Tree.Children_Permission := Read_Write;
+            --  Anonymous access to constant is an observe
 
-            --  Observed IN: R for everybody
+            elsif Is_Anonymous_Access_Type (Typ)
+              and then Is_Access_Constant (Typ)
+            then
+               Perm := Read_Only;
+
+            --  Other access types are a borrow
+
+            elsif Is_Access_Type (Typ) then
+               Perm := Read_Write;
+
+            --  Deep types other than access types define an observe
 
             else
-               Elem.all.Tree.Permission := Read_Only;
-               Elem.all.Tree.Children_Permission := Read_Only;
+               Perm := Read_Only;
             end if;
 
-         --  OUT: borrow, but callee has W only
+         when E_Out_Parameter
+            | E_In_Out_Parameter
+         =>
+            --  Shallow parameters and globals need not be considered
 
-         when E_Out_Parameter =>
-            Elem.all.Tree.Permission := Write_Only;
-            Elem.all.Tree.Children_Permission := Write_Only;
+            if not Is_Deep (Typ) then
+               Perm := None;
 
-         --  IN OUT: borrow and callee has RW
+            --  Functions cannot have outputs in SPARK
 
-         when E_In_Out_Parameter =>
-            Elem.all.Tree.Permission := Read_Write;
-            Elem.all.Tree.Children_Permission := Read_Write;
+            elsif Ekind (Subp) = E_Function then
+               return;
+
+            --  Deep types define a borrow or a move
+
+            else
+               Perm := Read_Write;
+            end if;
       end case;
 
-      Set (Current_Perm_Env, Id, Elem);
+      if Perm /= None then
+         declare
+            Tree : constant Perm_Tree_Access :=
+              new Perm_Tree_Wrapper'
+                (Tree =>
+                   (Kind                => Entire_Object,
+                    Is_Node_Deep        => Is_Deep (Etype (Id)),
+                    Explanation         => Expl,
+                    Permission          => Perm,
+                    Children_Permission => Perm));
+         begin
+            Set (Current_Perm_Env, Id, Tree);
+         end;
+      end if;
    end Setup_Parameter_Or_Global;
 
    ----------------------
@@ -6178,13 +6074,52 @@ package body Sem_SPARK is
 
    procedure Setup_Parameters (Subp : Entity_Id) is
       Formal : Entity_Id;
-
    begin
       Formal := First_Formal (Subp);
       while Present (Formal) loop
-         Setup_Parameter_Or_Global (Formal, Ekind (Formal));
+         Setup_Parameter_Or_Global
+           (Id         => Formal,
+            Typ        => Retysp (Etype (Formal)),
+            Kind       => Ekind (Formal),
+            Subp       => Subp,
+            Global_Var => False,
+            Expl       => Formal);
          Next_Formal (Formal);
       end loop;
    end Setup_Parameters;
 
+   --------------------------------
+   -- Setup_Protected_Components --
+   --------------------------------
+
+   procedure Setup_Protected_Components (Subp : Entity_Id) is
+      Typ  : constant Entity_Id := Scope (Subp);
+      Comp : Entity_Id;
+      Kind : Formal_Kind;
+
+   begin
+      Comp := First_Component_Or_Discriminant (Typ);
+
+      --  The protected object is an implicit input of protected functions, and
+      --  an implicit input-output of protected procedures and entries.
+
+      if Ekind (Subp) = E_Function then
+         Kind := E_In_Parameter;
+      else
+         Kind := E_In_Out_Parameter;
+      end if;
+
+      while Present (Comp) loop
+         Setup_Parameter_Or_Global
+           (Id         => Comp,
+            Typ        => Retysp (Etype (Comp)),
+            Kind       => Kind,
+            Subp       => Subp,
+            Global_Var => False,
+            Expl       => Comp);
+
+         Next_Component_Or_Discriminant (Comp);
+      end loop;
+   end Setup_Protected_Components;
+
 end Sem_SPARK;