Gem #41: Accessibility Checks (Part II: Ada2005)

by Ramón Fernández —AdaCore

Let's get started…

Ada 2005 allows the use of anonymous access types in a more general manner, adding considerable power to the object-oriented programming features of the language. The accessibility rules have been correspondingly augmented to ensure safety by preventing the possibility of dangling references. The new rules have been designed with programming flexibility in mind, as well as to allow the compiler to enforce checks statically.

The accessibility levels in the new contexts for anonymous access types are generally determined by the scope where they are declared. This makes it possible to perform compile-time accessibility checks.

Another rule that allows for static accessibility checks relates to derived types: a type derivation does not create new accessibility level for the derived type, but just takes that of the parent type:

     procedure Example_1 is
        type Node is record
           N : access Integer;
        end record;
        List : Node

        procedure P is
           type Other_Node is new Node;
              L : aliased Integer := 1;
              Data : Other_Node := Other_Node'(N => L'Access);
              --  L'Access is illegal!
              List := Node (Data);
        end P;

     end Example_1;

In the above example, we don't need to worry about expensive run-time checks on assignment or return of an object of type Other_Node; we know it has the same accessibility level as type Node, making the Access attribute illegal. If this were not prevented, after returning from P, List.N would be a dangling reference.

Ada 2005 also allows functions to return objects of anonymous access types. In this case, the accessibility level of the object is statically determined by the scope of the function declaration. Consider the following example:

     procedure Example_2 is
        type Rec is record
           V : access Integer;
        end record;

        Global : aliased Integer := 1;

        function F1 (X : Boolean) return Rec is
           Local : aliased Integer := 2;

           --  Nested function returns anonymous access values
           --  with different nesting depths

           function F2 (Y : Boolean) return Access Integer is
              if Y then
                 return Global'Access;
                 return Local'Access;
              end if;
           end F2;

           return (V => F2 (X), ...); -- Illegal
        end F1;

        Data : Rec;
        Data := F1 (True);
     end Example_2;

In this example, applying the aforementioned rule, the compiler statically determines that this accessibility level is the scope where F2 is declared, which is deeper than the accessibility level of Rec. So even though the call F1 (True) would provide a valid value for V, the code is illegal. The accessibility restriction is conservative, to keep the rules simple, and so that the compiler is not required to perform data flow analysis to determine legality (not to mention that in general the legality would be undecidable).

The new rules also take into account discriminants of an anonymous access type (which are technically referred to as access discriminants). In Ada 2005, access discriminants are now permitted for nonlimited types. Consequently, it's necessary to disallow defaults for access discriminants of nonlimited types. Thus, the following declaration is illegal:

     Default : aliased Integer := ...
     type Rec (D : access Integer := Default'Access) is record

This restriction is needed to prevent the discriminant from creating a dangling reference due to an assignment of the record object; it ensures that the object and the discriminant are bound together for their lifetime.

Special care must be taken when types with access discriminants are used with allocators and return statements. The accessibility rules require the compiler to perform static checks when new objects containing access discriminatns are created or returned. Consider the following example:

    procedure Example_3 is
       type Node (D : access Integer) is record
          V : Integer;
       end record;
       type Ptr is access all Node;

       Global_Value : aliased Integer := 1;
       Other_Data   : Integer := 2;
       procedure P is
          Local : aliased Integer := 3;
          R1 : Ptr;
          R2 : Ptr;
          R1 := new Node'(D => Global_Value'Access, V => Other_Data);
          --  This is legal

          R2 := new Node'(D => Local_Value'Access, V => Other_Data);
          --  This is illegal
       end P;
    end Example_3;

The allocator for R1 is legal, since the accessibility level of Global'Access is the same as the accessibility level of D. However the allocator for R2 is illegal, because the accessibility level of Local'Access is deeper than the accessibility level of D, and assigning R2 to an object outside P could lead to a dangling reference.

In summary, these rules forbid the creation of an object in a storage pool that contains an access discriminant pointing to some area of memory, be it a part of the stack or some other storage pool, with a shorter lifetime, thus preventing the discriminant from pointing to a nonexistent object.

About the Author

Ramón Fernández is a senior software engineer at AdaCore. Since joining the company in 2001 he has been involved in a wide variety of areas including customer support, GNAT Pro front-end and run-time development, real-time systems, quality assurance, embedded systems, production infrastructure, and IT management. Ramón has an MSc in Computer Science from New York University, and a Telecommunications Engineering degree from ETSIT-UPM (Technical University of Madrid, Spain).