Version 1.2 of ai05s/ai05-0051-1.txt

Unformatted version of ai05s/ai05-0051-1.txt version 1.2
Other versions for file ai05s/ai05-0051-1.txt

!standard 6.5(21/2)          07-10-30 AI05-0051-1/02
!class binding interpretation 07-05-04
!status work item 07-05-04
!status received 07-04-19
!priority Medium
!difficulty Easy
!qualifier Clarification
!subject Accessibility of dispatching function calls (aka another Baird question)
!summary
TBD
How do the various accessibility rules pertaining to function results apply in the case of a dispatching call to a function where the accessibility level of the function named in the call differs from the accessibility level of the function whose body is executed?
!recommendation
Scream, lest your head explode.
!wording
TBD
!discussion
In most cases, the dynamic semantics of access result types follow easily from an informal equivalence rule. The declaration
function Foo ( ...) return access T;
is pretty much equivalent to
type T_Ref is access all T; function Foo (...) return T_Ref;
, and that's that.
If the function in question overrides an inherited dispatching operation and if the controlling type of the function is declared in a more nested scope than its parent type, then this equivalence breaks down.
If the function returns an allocator, then what is the lifetime and accessibility level of the allocated object in the case where the function has been invoked via a dispatching call to the operation of the parent type?
Tuck has pointed out that this is an instance of a more general problem associated with uses of the accessibility level of a function body in the case where this level does not match the accessibility level of the function named in the (dispatching) call.
A function with an access result type may return a reference to an object whose accessibility level matches that of the function body:
X : aliased Integer; function F1 return access Integer is begin return X'access; end F1;
A function with a class-wide result type may return a result whose type's accessibility level matches that of the function body:
type Derived is new Some_Tagged_Type with null record; function F2 return Some_Tagged_Type'Class is begin return Derived'(...); end F2;
A function whose result type has anonymous access discriminants may return a result with a discriminant that references an object whose accessibility level matches that of the function body:
type Discriminated (Disc : access Integer) is limited null record; X : aliased Integer; function F3 return Discriminated is begin return (Disc => X'access); end F3;
Each of these three scenarios becomes problematic if the function overrides an inherited dispatching operation and the controlling type of the function is declared in a more nested scope than its parent type.
Consider:
declare type Tagged_Type is tagged null record; type Discriminated (D : access Integer) is tagged limited null record;
package Pkg_1 is type Parent_Type is abstract tagged null record; function F1 (X : Parent_Type) return access Integer is abstract; function F2 (X : Parent_Type) return Tagged_Type'Class is abstract; function F3 (X : Parent_Type) return Discriminated is abstract; function F4 (X : Parent_Type) return access Some_Designated_Type is abstract; end Pkg_1;
procedure Call_Functions (X : Pkg_1.Parent_Type'Class) is begin <Call X.F1, X.F2, and X.F3>
-- -- Each call should fail, right? -- Otherwise we've got the potential for dangling references. -- -- The situation with X.F4 is even less clear. end Call_Functions;
procedure Nested is package Pkg_2 is type Extension is new Pkg_1.Parent_Type with null record; function F1 (X : Extension) return access Integer; function F2 (X : Extension) return Tagged_Type'Class; function F3 (X : Extension) return Discriminated; function F4 (X : Extension) return access Some_Designated_Type; end Pkg_2;
package Other_Locals is I : aliased Integer; type Extension is new Tagged_Type with null record; end Locals;
package body Pkg_2 is function F1 (X : Extension) return access Integer is begin return Other_Locals.I'access; end F1;
function F2 (X : Extension) return Tagged_Type'Class is begin return Other_Locals.Extension'(null record); end F2;
function F3 (X : Extension) return Discriminated is begin return Discriminated'(D => Other_Locals.I'access); end F3;
function F4 (X : Extension) return access Some_Designated_Type is begin return new Some_Designated_Type'(...); end F4; end Pkg_2;
X : Pkg_2.Extension; begin Call_Functions (X); end Nested; begin Nested; end;
Tuck suggests an implementation model where these problematic dispatching operations of nested extensions expect an accessibility level to be passed in to indicate the accessibility level of the function declaration named in the call; that level is then used for anonymous-typed allocators at the point of return, and for accessibility checks.
This seems like a good approach, but it is not implicit in the current RM. Wording changes would be needed - a "clarification" would not suffice.
Furthermore, there are some aspects of this problem that might still need resolution even if this proposal were adopted:
1) In the case of a dispatching call to a function with an
access result type which returns an allocator, exactly when is the allocated object finalized? This remains a valid (albeit obscure) question even in the case where the parent type and the extension type have the same accessibility level. 7.6.1(11.2) refers to "the first freezing point of the ultimate ancestor type", but is this the freezing point of the access type associated with the function named in the call or with the function whose body is executed?
2) The problem of AI05-0075-1.
!ACATS test
!appendix

From: Stephen W. Baird
Sent: Thursday, April 19, 2007  12:04 PM

!question

How do the various accessibility rules pertaining to function results
apply in the case of a dispatching call to a function where the accessibility
level of the function named in the call differs from the accessibility
level of the function whose body is executed?

!discussion

In most cases, the dynamic semantics of access result types follow easily
from an informal equivalence rule. The declaration

    function Foo ( ...) return access T;

is pretty much equivalent to

    type T_Ref is access all T;
    function Foo (...) return T_Ref;

, and that's that.

If the function in question overrides an inherited dispatching operation
and if the controlling type of the function is declared in a more nested
scope than its parent type, then this equivalence breaks down.

If the function returns an allocator, then what is the lifetime and
accessibility level of the allocated object in the case where the
function has been invoked via a dispatching call to the operation of
the parent type?

Tuck has pointed out that this is an instance of a more general problem
associated with uses of the accessibility level of a function body
in the case where this level does not match the accessibility level
of the function named in the (dispatching) call.

A function with an access result type may return a reference to an object
whose accessibility level matches that of the function body:

    X : aliased Integer;
    function F1 return access Integer is
    begin
      return X'Access;
    end F1;
    
A function with a class-wide result type may return a
result whose type's accessibility level matches that of
the function body:

    type Derived is new Some_Tagged_Type with null record;
    function F2 return Some_Tagged_Type'Class is
    begin
      return Derived'(...);
    end F2;

A function whose result type has anonymous access discriminants
may return a result with a discriminant that references an
object whose accessibility level matches that of the function body:

    type Discriminated (Disc : access Integer) is limited null record;
    X : aliased Integer;
    function F3 return Discriminated is
    begin
       return (Disc => X'Access);
    end F3;

Each of these three scenarios becomes problematic if the function
overrides an inherited dispatching operation and the controlling
type of the function is declared in a more nested scope than its
parent type.

Consider:

    declare
      type Tagged_Type is tagged null record;
      type Discriminated (D : access Integer)
        is tagged limited null record;
          
      package Pkg_1 is
        type Parent_Type is abstract tagged null record;
        function F1 (X : Parent_Type) return access Integer is abstract;
        function F2 (X : Parent_Type) return Tagged_Type'Class is abstract;
        function F3 (X : Parent_Type) return Discriminated is abstract;
        function F4 (X : Parent_Type)
                 return access Some_Designated_Type is abstract;
      end Pkg_1;
      
      procedure Call_Functions
                 (X : Pkg_1.Parent_Type'Class) is
      begin
        <Call X.F1, X.F2, and X.F3>
        
        --
        -- Each call should fail, right?
        -- Otherwise we've got the potential for dangling references.
        --
        -- The situation with X.F4 is even less clear.
       end Call_Functions;
      
      procedure Nested is
        package Pkg_2 is
          type Extension is new Pkg_1.Parent_Type with null record;
          function F1 (X : Extension) return access Integer;
          function F2 (X : Extension) return Tagged_Type'Class;
          function F3 (X : Extension) return Discriminated;
          function F4 (X : Extension) return access Some_Designated_Type;
        end Pkg_2;
     
        package Other_Locals is
          I : aliased Integer;
          type Extension is new Tagged_Type with null record;
        end Locals;
          
        package body Pkg_2 is
          function F1 (X : Extension) return access Integer
            is begin return Other_Locals.I'access; end F1;
            
          function F2 (X : Extension) return Tagged_Type'Class
            is begin return Other_Locals.Extension'(null record); end F2;
 
          function F3 (X : Extension) return Discriminated
            is begin return Discriminated'(D => Other_Locals.I'Access); end F3;

          function F4 (X : Extension) return access Some_Designated_Type
            is begin return new Some_Designated_Type'(...); end F4;
         end Pkg_2;
                    
        X : Pkg_2.Extension;       
      begin
        Call_Functions (X);
      end Nested;    
    begin
      Nested;
    end;
    
Tuck suggests an implementation model where these problematic dispatching
operations of nested extensions expect an accessibility level to be
passed in to indicate the accessibility level of the function declaration
named in the call; that level is then used for anonymous-typed allocators
at the point of return, and for accessibility checks.

This seems like a good approach, but it is not implicit in the current RM.
Wording changes would be needed - a "clarification" would not suffice.

Furthermore, there are some aspects of this problem that might still
need resolution even if this proposal were adopted:

1) In the case of a dispatching call to a function with an
   access result type which returns an allocator, exactly when is the
   allocated object finalized? This remains a valid (albeit obscure)
   question even in the case where the parent type and the extension
   type have the same accessibility level. 7.6.1(11.2) refers to
   "the first freezing point of the ultimate ancestor type", but
   is this the freezing point of the access type associated with the
   function named in the call or with the function whose body is executed?

2) A wording change in 6.5(21/2) seems to be needed; in the case of
   a class-wide result type, the specified check may need to be performed
   even if the result type of the function has no access discriminants.

   Consider the following example:

     declare
       type Root is tagged limited null record;
       type Ext (D : access Integer) is new Root with null record;

       function F return Root'Class is
         Local_Var : aliased Integer;

         function Local_Func return Ext is
         begin
           return (D => Local_Var'Access);
         end Local_Func;
       begin
         return Local_Func;
       end F;
    
       procedure Do_Something (X : access Integer) is ... end;
     begin
       Do_Something (Ext (F).D);
     end;

   If this test executes without raising any exception, then a
   dangling reference will be passed to Do_Something. It seems
   that the check described in 6.5(21/2) needs to be performed
   as part of returning from F, but the result subtype of F
   lacks unconstrained access discriminants.
   
   This will introduce distributed overhead in the sense that
   a function which returns a limited classwide result may have
   to check for the possibility that its result has a "bad"
   access discriminant values even if there are no access
   discriminants anywhere in the program.
   
   We probably don't want two active AIs which both modify
   6.5(21/2), but this issue might be split off into a separate
   AI if that doesn't appear to be a problem.


From: Tucker Taft
Sent: March 27, 2007
      Sent to Steve Baird, in response to (roughly) the question
      about allocators that begins the !discussion section above.

Very nasty.  It seems like we might have the same
problem with functions returning class-wide types,
and perhaps with functions returning objects with
access discriminants.  In all three cases, there
is an accessibility check at the point of the return
statement, and in each case the check is against
the accessibility level of the function body itself.
But if the caller has reached the function body via
a dispatching call, the caller may be presuming
an accessibility level associated with the dispatching
operation identified at compile-time, rather than the body
determined at runtime.

There seem various possible solutions, none particularly
pleasant.  I think my favorite would be essentially
a "wrapper" model, where dispatching operations of
nested extensions expect an accessibility level passed
*in* to indicate the accessibility level of the function
declaration named in the call, and that level is used
for anonymous allocators at the point of return, and
for accessibility checks.  The "wrapper" aspect is that
to avoid distributed overhead, the extra implicit parameter
would be provided by a "wrapper" rather than by the
caller, so if calling operations of non-nested extensions,
there is no additional overhead.  Note that any function
that might *become* a dispatching operation via renaming
would have to assume the worst, and allow for this
implicit parameter.  Some kind of a wrapper may be needed
for these kinds of dispatching calls to deal with the
static link, so adding another implicit parameter might
be relatively straightforward.

This model also seems consistent with the wording in 3.10.2(10.1/2)
where we indicate that the accessibility of a return object
goes from being that of the return statement, to being that
determined by the point of call, with no interim "stop"
at the level of the function body.

I think passing *in* an accessibility level is preferable to
passing one *out* because we already have situations where
we pass one in (i.e. with access parameters, and probably also
with the new limited return types), and so we have
already figured out what that entails as far as passing
static vs. dynamic levels.  On the other hand, the level we
pass in for access parameters is never used for the *target*
of a conversion, only for the *operand*, so the same logic
might not work...  Hmmm...

****************************************************************

Questions? Ask the ACAA Technical Agent