AI22-0076-1

!standard 3.10.2(12/2)                                     23-06-05  AI22-0076-1/01

!standard 4.6(48/3)

!class Amendment 23-06-05

!status work item 23-06-05

!status received 23-06-05

!priority Medium

!difficulty Medium

!subject Restricting Dynamic Accessibility Checks

!summary

We introduce the Restriction No_Dynamic_Accessibility_Checks which restricts features of Ada that lead to run-time checks on accessibility levels of access types. We do not propose here any restrictions on nested extensions of tagged types, which can require run-time checks on the accessibility of tags (tagged type descriptors).

!issue

Over the years, the complexity of the rules that govern accessibility in Ada, that is, what operations on pointers are allowed, has grown to a point where the rules are not fully understood by implementers and by users. In particular, a failure of a dynamic accessibility check is often difficult to debug and diagnose.

We propose a set of restrictions that attempt to eliminate dynamic accessibility checks on access types, while being compatible with, or requiring modest updates to, many existing Ada programs that make use of pointers.

The complexity of the rules that govern accessibility in Ada rest largely on the use of anonymous access types. These were [originally introduced in Ada 95] (https://www.adaic.org/resources/add_content/standards/95rat/rat95html/rat95-p2-3.html)

for access parameters and access discriminants, then [used more widely in Ada 2005] (https://www.adaic.org/resources/add_content/standards/05rat/html/Rat-3-3.html),

and [further refined in Ada 2012] (http://www.ada-auth.org/standards/12rat/html/Rat12-6-4.html) to reduce the likelihood of certain accessibility check failures.

!recommendation

We propose a set of restrictions that avoid run-time accessibility checks on access types.  These can be imposed by using the restriction No_Dynamic_Accessibility_Checks with the Restriction pragma.

We propose to distinguish the various different uses of anonymous access types.

Standalone objects

Var        : access T := ...
Var_To_Cst : access constant T := ...
Cst        : constant access T := ...
Cst_To_Cst : constant access constant T := ...

We propose to define the accessibility levels of standalone objects of anonymous access type (whether constants or variables) to be that of the level of their object declaration. This has the feature of allowing many common use-cases without the employment of Unchecked_Access, while still removing the need for dynamic checks.

The major benefit of this change would be compatibility with standard Ada rules.

For example, the following would be legal:

type T is null record;
type T_Ptr is access all T;
Anon  : access T := ...
Named : T_Ptr := Anon; -- Allowed

Subprogram parameters

procedure P (V : access T; X : access constant T);

We propose the following in terms of subprogram parameters:

When the type of a formal parameter is of anonymous access then, from the caller's perspective, its level is seen to be at least as deep as that of the type of the corresponding actual parameter (whatever that actual parameter might be) - meaning any actual can be used for an anonymous access parameter without the use of 'Unchecked_Access.

declare
   procedure Foo (Param : access Integer) is ...
   X : aliased Integer;
begin
   Foo (X'Access); -- Allowed
   Foo (X'Unchecked_Access); -- Not necessary
end;

From the callee's perspective, the level of anonymous access formal parameters would be

between the level of the subprogram and the level of the subprogram's locals. This has the effect of formal parameters being treated as local to the callee except in:

Note that with these more restricted rules we lose track of accessibility levels when assigned to local objects thus making (in the example below) the assignment to Node2.Link from Temp below compile-time illegal.

type Node is record
   Data : Integer;
   Link : access Node;
end record;

procedure Swap_Links (Node1, Node2 : in out Node) is
   Temp : constant access Integer := Node1.Link;
             -- We lose the "association" to Node1
begin
   Node1.Link := Node2.Link; -- Allowed
   Node2.Link := Temp; -- Not allowed
end;

function Identity (N : access Node) return access Node is
   Local : constant access Node := N;
begin
   if True then
      return N; -- Allowed
   else
      return Local; -- Not allowed
   end if;
end;

Function results

function Get (X : Rec) return access T;

We propose making the accessibility level of the result of a call to a function that has an anonymous access result type defined to be as whatever is deepest out of the following:

NOTE: We would need to include an additional item in the list if we were not to enforce the below restriction on tagged types:

Function result example:

declare
   type T is record
      Comp : aliased Integer;
   end record;

   function Identity (Param : access Integer) return access Integer is
   begin
      return Param; -- Legal
   end;

   function Identity_2 (Param : aliased Integer) return access Integer is
   begin
      return Param'Access; -- Legal
   end;

   X : access Integer;
begin
   X := Identity (X); -- Legal
   declare
      Y : access Integer;
      Z : aliased Integer;
   begin
      X := Identity (Y); -- Illegal since Y is too deep
      X := Identity_2 (Z); -- Illegal since Z is too deep
   end;
end;

 

However, an additional restriction that falls out of the above logic is that tagged type extensions *cannot* allow additional anonymous access discriminants in order to prevent upward conversions potentially making such "hidden" anonymous access discriminants visible and prone to memory leaks.

Here is an example of one such case of an upward conversion which would lead to a memory leak:

declare
   type T is tagged null record;
   type T2 (Disc : access Integer) is new T with null record;

      -- Must be illegal

   function Identity (Param : aliased T'Class) return access Integer is
   begin
      return T2 (T'Class (Param)).Disc;

         -- Here P gets effectively returned and set to X
   end;

   X : access Integer;
begin
   declare
      P : aliased Integer;
      Y : T2 (P'Access);
   begin
      X := Identity (T'Class (Y));

        -- Pass local variable P (via Y's discriminant),
        -- leading to a memory leak.
   end;
end;

-- Thus we need to make the following illegal to avoid such situations:

package Pkg1 is
   type T1 is tagged null record;
   function Func (X1 : T1) return access Integer is (null);
end;

package Pkg2 is
   type T2 (Ptr1, Ptr2 : access Integer) is new Pkg1.T1 with null record;

     -- Illegal
   ...
end;

 

In order to prevent upward conversions of anonymous function results (like below), we

also would need to ensure that the level of such a result (from the callee's perspective)

is statically deeper:

declare
   type Ref is access all Integer;
   Ptr : Ref;
   function Foo (Param : access Integer) return access Integer is
   begin
       return Result : access Integer := Param; do
          Ptr := Ref (Result); -- Not allowed
       end return;
   end;
begin
   declare
      Local : aliased Integer;
   begin
      Foo (Local'Access).all := 123;
   end;
end;

Discriminants and allocators

Access discriminants were introduced in Ada 95 as a way to tie accessibility levels to the scope where the object is declared, in order to allow code that would be otherwise illegal.

Any access discriminant will have the accessibility level of its enclosing object, identical to the standard Ada model therefore ensuring maximum compatibility. However, in the presence of the restriction No_Dynamic_Accessiblity_Checks, such access discriminants cannot be initialized directly via an allocator - eliminating the notion of coextensions.  They can be initialized by an allocator so long as it is qualified to be of a named access type, or is passed through a local constant with a well-defined (static) accessibility level.

procedure M is
  type T (X : access Integer) is null record;
  Disc  : access Integer := new Integer'(1);
  Obj_1 : T (new Integer'(1)); -- Illegal
  Obj_2 : T (Disc); -- Legal
begin
   null;
end;

!wording

**TBD

!discussion

(See !recommendation..)

!example

(See !recommendation.)

!ACATS test

**TBD

!appendix

[This is based on the RFC from https://github.com/AdaCore/ada-spark-rfcs/blob/master/prototyped/rfc-simpler-accessibility.md?plain=1]