Version 1.5 of ai05s/ai05-0024-1.txt

Unformatted version of ai05s/ai05-0024-1.txt version 1.5
Other versions for file ai05s/ai05-0024-1.txt

!standard 4.8(10/1)          06-11-13 AI05-0024-1/01
!standard 3.10.2(14-14.3/2)
!class binding interpretation 06-11-13
!status work item 06-11-13
!status received 06-08-29
!priority High
!difficulty Hard
!qualifier Error
!subject Run-time accessibility checks
!summary
TBD.
!question
(1) In Ada 95, accessibility checks for access parameters could be implemented by passing as implicit parameter which was a small integer representing the static nesting level of the actual parameter. This model is explained in an Implementation Note in AARM 3.10.2(22.u/2-22.tt).
In Ada 2005 however, it seems that things are more complicated and that a static nesting level is not enough. Apparently you need to pass an indication of dynamic nesting, roughly equivalent to a stack nesting depth. I suppose that a frame pointer corresponding to the master where the actual parameter was created could be used for that purpose, although the check seems rather nasty in the presence of tasking and multiple stacks.
For instance, consider:
declare type T1 is tagged null record;
task T is entry E (X : T1'Class); end T;
task body T is type Ref2 is access T1'Class; R2 : Ref2; begin accept E (X : T1'Class) do R2 := new T1'Class'(X); end; -- Delay for a while and then do nasty things with R2. end;
procedure Proc is type T2 is new T1 with null record; X2 : T2; begin T.E (X2); end;
begin Proc; end;
This piece of code is squirreling away an object of type T2 into a task that outlasts the type. We don't want this. The problem appears to be that the accessibility levels of Ref2 and T2 are incomparable. Statically, the nesting levels are both 2, so the check of 4.8(10.1/2) succeeds. Dynamically, the situation is muddled because "run-time nesting of masters" doesn't seem to apply here: both T and Proc are (dynamically) nested in the outer block, but it's not like one of them is (dynamically) into the other.
---
(2)
The accessibility rules for objects created by allocators in 3.10.2(14-14.2) don't seem to cover derived types:
type T (D : access Integer) is null record; type D is new T (new Integer'(3));
The allocator here is not quite used to define the constraint in a subtype_declaration so 3.10.2(14.1/2) doesn't apply.
There also appears to be a rule missing for the following case:
type A is access T; X : A (new Integer'(4));
It seems clear that the allocator is not being used to define the discriminant of an object, so the wording of 3.10.2(14.3/2) does not apply.
!recommendation
(See Summary.)
!wording
TBD.
!discussion
--!corrigendum A.18.2(239/2)
!ACATS test
!appendix

From: Stephen W. Baird
Date: Tuesday, August 29, 2006  2:51 PM

Given two masters neither of which is statically nested within the
other, the language does not define whether the accessibility level of
one is deeper than that of the other. One implementation might assign
a deeper accessibility level to the first master while another might
not. This does not introduce portability problems as long as these
values are never compared as part of a run-time accessibility check.

Unfortunately, it appears that such a comparison is possible.

RM 4.8(10.1/2) states:
  For any allocator, if the designated type of the type of the allocator
  is class-wide, then a check is made that the accessibility level of the
  type determined by the subtype_indication, or by the tag of the value
  of the  qualified_expression, is not deeper than that of the type of
  the allocator. 

Consider the following example:

  declare
    type T1 is tagged null record;

    procedure Proc_1 (P : access T1'Class)  is
      type Ref is access T1'Class;
      X : Ref := new T1'Class'(P.all); 
    begin
      null;
    end; 

    procedure Proc_2 is
      type T2 is new T1 with null record;
      X2 : aliased T2;
    begin
      Proc_1 (X2'access);
 
      declare
        type T3 is new T1 with null record;
        X3 :  aliased T3;
      begin
        Proc_1 (X3'Access);
      end;
    end;
  begin
    Proc_2;
  end;
 
There is an accessibility check associated with the evaluation of
the allocator. Am I right in thinking that this check might either
pass or fail the first time Proc_1 is called and, if the check
passes the first time, it might pass or fail the second time Proc_1
is called? If so, then is this a problem?

This check was introduced in order to prevent an allocated object
from outliving its type. Note that there is no danger of that
happening in this case.

Similarly, RM 6.5(8/2) states:
  If the result type is class-wide, the tag of the return object is that
  of the value of the expression. A check is made that the accessibility
  level of the type identified by the tag of the result is not deeper
  than that of the master that elaborated the function body.
 
We have essentially the same situation here, as illustrated by the
following example:

  declare
    type T1 is tagged null record;

    function Func_1 (P : access T1'Class) return T1'Class is
    begin
      return P.all;
    end; 

    procedure Proc_3 is
      type T2 is new T1 with null record;
      X2 : aliased T2;
      Y2 : T1'Class := Func_1 (X2'Access);
    begin
      declare
        type T3 is new T1 with null record;
        X3 : aliased T3
        Y3 : T1'Class := Func_1 (X3'Access);
      begin
        null;
      end;
    end;
  begin
    Proc_3;
  end;
 
The check was introduced in order to prevent a function from returning
a value of a locally declared (specific) type. Again, there is no
danger of that happening in this case.

Is this just a case where the language is "insufficiently broken", or
is some corrective action needed here?

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

From: Tucker Taft
Date: Tuesday, August 29, 2006  5:38 PM

> Given two masters neither of which is statically nested within the
> other, the language does not define whether the accessibility level of
> one is deeper than that of the other.

I haven't investigated your example in detail, but the above
statement is false, I believe.  The language defines run-time
accessibility checks in terms of *run-time* (dynamic) nesting,
not static nesting.  There are compile-time accessibility
checks which are defined in terms of static nesting, but
these are always backed up with run-time accessibility checks
as well.

> ...
> ... One implementation might assign
> a deeper accessibility level to the first master while another might
> not. This does not introduce portability problems as long as these
> values are never compared as part of a run-time accessibility check.

From the language point of view, accessibility level checks are
always related to dynamic nesting level.  If implementations choose
to use static nesting levels to simplify the checking, that
is their business, but if true dynamic nesting levels need to
be maintained, that is not a problem from the language
correctness point of view (though admittedly it could be
a performance issue).  In at least one case in Ada 95, we
already know some additional tricks need to be played to
properly implement the run-time accessibility checks associated
with access parameters using static nesting levels.
See 3.10.2(22.dd/2).

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

From: Tucker Taft
Date: Tuesday, August 29, 2006  7:45 PM

In looking at your examples in more detail, the first example
should never fail, and the second example should always
fail.  The access type of Proc_1's allocator in the first
example is necessarily deeper (dynamically) than
any type identified by the tag of its actual parameter.
In the second example, it doesn't seem to matter whether
you consider static or dynamic levels, since T2 and T3
are clearly deeper by both measures than Func_1.

Unless I am missing something... (which is always
possible when it comes to accessibility checks!).

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

From: Brad Moore
Date: Friday, September 8, 2006  10:43 AM

I have tried compiling and running the code in the original example and
have noted an unexpected behavior.

In the first example, the behavior is as was suggested.

That is, the first call to Proc_1 does indeed does not fail, while the
second nested call to Proc_1 does.

In the second example using functions, the first call to Func_1 fails
the accessibility check and generates a program_error.

This was unexpected, as I don't see a significant difference here from
the first example. Should this call have failed?

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

From: Pascal Leroy
Date: Monday, September 11, 2006 4:29 AM

As Tuck pointed out, both calls to Proc_1 should succeed, so the behavior
of the first example is in fact incorrect.

Note that the accessibility checks in question are new to Ada 2005, and
that this is a very complicated part of the language, and one that we kept
changing fairly late in the game.  So I would not be surprised if there
were still wrinkles to iron out in compilers in this area.  What you
report just looks like a bug to me, and your vendor will probably be
interested to hear about it.  It doesn't invalidate Tuck's analysis.

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

From: Brad Moore
Date: Monday, September 11, 2006  1:41 AM

Thanks for the explanation. I see now why the function returns failed
in the second example.
 
As to why the second nested call to Proc_1 in the first example might
have failed, I note the following;
 
RM 4.8 (5.1/2) states
          {AI95-00344-01} If the designated type of the type of the allocator
          is class-wide, the accessibility level of the type determined by
          the subtype_indication or qualified_expression shall not be 
          statically deeper than that of the type of the allocator.
 
Type T3 would be statically deeper than the Type Ref referenced in Proc_1, so
according to this check, it seems the second call should fail.
Type T2 however, would be statically just as deep as the Ref Type,
(both are defined in procedures declared in the same block, even though 
hey are different procedures) so that test passes.
 
Presumably the check described in 
RM 4.8(10.1/2), namely;
   For any allocator, if the designated type of the type of the allocator
   is class-wide, then a check is made that the accessibility level of  the
   type determined by the subtype_indication, or by the tag of the
  value of the  qualified_expression, is not deeper than that of the type
  of  the allocator.

is a dynamic accessibility check.
 
It seems that a conservative approach is applied here and that both static
and dynamic checks need to pass for such allocators.
 
Does this make sense, or am I missing something?

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

From: Gary Dismukes
Date: Tuesday, September 12, 2006  4:24 PM

You're missing something.

Your statement:

> It seems that a conservative approach is applied here and that both
> static and dynamic checks need to pass for such allocators.

is correct, that both static and dynamic checks must pass, but your
interpretation of how those checks are applied is flawed.

Here's how these rules work.  The rule 4.8(5.1/2) is a compile-time rule,
and it applies to the types as named or resolved in the allocator, not to
any types determined at run-time by the argument passed into Proc_1.
In this case we have an initialized allocator (new T1'Class'(P.all)),
where the type of P.all is T1'Class, and the accessibility of T1'Class
of course matches that of the designated type since they're the same type.

For the run-time check, what matters is the accessibility level of the type
determined by the tag of the run-time value P.all, which in the example
is either type T2 or type T3.  Now accessibility levels are defined
according to run-time "nesting", not static nesting.  Therefore the
level of the type of the allocator (Proc_1.Ref) is necessarily deeper
than that of a type declared in any caller of Proc_1, so the run-time
accessibility check also passes.

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

From: Brad Moore
Date: Wednesday, September 13, 2006  9:04 AM

Thanks again for the further clarification.
I think I understand the rule now based on what you've said.
RM 4.8 (5.1/2) is a subtle rule.

Note, I think your explanation was on track but not quite accurate.

> In this case we have an initialized allocator (new T1'Class'(P.all)),
> where the type of P.all is T1'Class, and the accessibility of T1'Class
> of course matches that of the designated type since they're the same type.

The type of the left hand side really does not match the type as the
right hand side of the assignment.
I believe the types we are comparing is Ref vs T1'Class.
My understanding now is that what we are comparing statically is the
accessibility of the type of Ref vs the accessibility of the type of T1'Class


To illustrate the subtlety consider a slight modification to example 1:
In this case, the first allocation allocates a T2'Class and assigns to a T1'Class.
The type T2 is still a statically a deeper type than T1, but the test passes.
If I understand things correctly now, we still expect the assignment to compile
and pass at run time because what we are really checking is that the accessibility
of type T2'Class is not statically deeper than type Ref, which it isn't.

The second allocation does not compile because the accessibility of T2'Class
is statically deeper than the type XRef.

Hopefully I've got it right now.
Thanks again to all for looking at this.


   Example_3_Block : declare

      type T1 is tagged null record;
      type XRef is access T1'Class;
     
      Y1 : XRef := null;

   begin -- Example_3_Block

      Inner_Block : declare

         type T2 is new T1 with null record;

         procedure Proc_1 (P : access T2'Class) is
            -- Note P is now of type access to T2'Class instead of T1'Class
            --
            type Ref is access T1'Class;
            X : Ref := new T2'Class'(P.all);  -- Passes at run time in my
                                              -- compiler, which is OK I believe.
         begin -- Proc_1
            Text_IO.Put_Line ("Doing Proc_1");
            Y1 := new T2'Class'(P.all);        -- Does not compile
         end Proc_1;

         procedure Proc_2 is
            X2 : aliased T2;
         begin -- Proc_2
            Proc_1 (X2'Access);
         end Proc_2;
      begin -- Inner_Block
         Proc_2;
      end Inner_Block;

   end Example_3_Block;

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

From: Pascal Leroy
Date: Wednesday, September 13, 2006  9:20 AM

> The type of the left hand side really does not match the type as
> the right hand side of the assignment.
> I believe the types we are comparing is Ref vs T1'Class.
> My understanding now is that what we are comparing statically
> is the accessibility of the type of Ref vs the accessibility of
> the type of T1'Class 

Yes, that's correct.  The rules are fairly complicated, but their
purpose is quite simple, and is explained in the AARM.  For instance,
AARM 4.8(5.1/2): "This prevents the allocated object from outliving its type".
If T1'Class were deeper than Ref, an object in the storage pool
associated with Ref could stay around while it type would have
"disappeared" and that would cause all sorts of anomalies.

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

From: Gary Dismukes
Date: wednesday, September 13, 2006  12:59 PM

> The type of the left hand side really does not match the type as the
> right hand side of the assignment.
> I believe the types we are comparing is Ref vs T1'Class
> My understanding now is that what we are comparing statically is the
> accessibility of the type of Ref vs the accessibility of the type of
> T1'Class

You're correct, a slip in my description, the type that matters of course is
the type of the allocator, not its designated type.

> To illustrate the subtlety consider a slight modification to example 1:
> In this case, the first allocation allocates a T2'Class and assigns to a
> T1'Class.
> The type T2 is still a statically a deeper type than T1, but the test
> passes.
> If I understand things correctly now, we still expect the assignment to
> compile and pass at run time because what
> we are really checking is that the accessibility of type T2'Class is not
> statically deeper than type Ref, which it isn't.

Almost right.  The compile-time check is satisfied based on Ref vs. T2'Class,
but the run-time check is that the accessibility level of the type denoted by
P.all's tag is not deeper than Ref.  In your latest test case the run-time
succeeds for the call with X2'Access, but note that the value of P.all could
originate from some type T3 declared at a deeper level than Ref, in which case
the check would fail.

> The second allocation does not compile because the accessibility of
> T2'Class is statically deeper than the type XRef.

Right, in that case the static check fails.

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

From: Pascal Leroy
Date: Tuesday, September 19, 2006  3:40 AM

> I haven't investigated your example in detail, but the above 
> statement is false, I believe.  The language defines run-time 
> accessibility checks in terms of *run-time* (dynamic) 
> nesting, not static nesting.  There are compile-time 
> accessibility checks which are defined in terms of static 
> nesting, but these are always backed up with run-time 
> accessibility checks as well.

I agree that this is how the language has to work, but it has consequences
that I, for one, had not noticed when we were designing these things.

In Ada 95, accessibility checks for access parameters could be implemented
by passing as implicit parameter which was a small integer representing
the static nesting level of the actual parameter.  This model is explained
in an Implementation Note in AARM 3.10.2(22.u/2-22.tt).  We were certainly
using this model in our Ada 95 implementation.

In Ada 2005 however, it seems that things are more complicated
(surprised?) and that a static nesting level is not enough.  Apparently
you need to pass an indication of dynamic nesting, roughly equivalent to a
stack nesting depth.  I suppose that a frame pointer corresponding to the
master where the actual parameter was created could be used for that
purpose, although the check seems rather nasty in the presence of tasking
and multiple stacks.

It may be that this was obvious for everyone else, but I had not realized
(1) that implementations needed to change in this area or that (2) the
dynamic accessibility checks might be significantly more expensive in Ada
2005 than they were in Ada 95.  Apparently the Editor didn't realize this
either, because the above-mentioned implementation note should have been
deleted.

Am I confused or what?

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

From: Tucker Taft
Date: Tuesday, September 19, 2006  8:31 AM

...
> In Ada 2005 however, it seems that things are more complicated
> (surprised?) and that a static nesting level is not enough.  Apparently
> you need to pass an indication of dynamic nesting, roughly equivalent to a
> stack nesting depth. 

You might be right, though as I always say, it takes me a while
to get my head around any question that relates to accessibility.

Can you illustrate a case where you need to do something more
than what we already need to do for access parameters, where
there is a subtle adjustment made in the value passed at certain
points (as explained in AARM 3.10.2 (22.dd/2))?

I didn't think that Steve's example created such a situation.
This presumes that the implementation doesn't consider the level passed
in with an access parameter if static nesting already ensures the check
couldn't fail, as suggested in AARM 3.10.2(22.ee/2).

> ...
> I suppose that a frame pointer corresponding to the
> master where the actual parameter was created could be used for that
> purpose, although the check seems rather nasty in the presence of tasking
> and multiple stacks.
> 
> It may be that this was obvious for everyone else, but I had not realized
> (1) that implementations needed to change in this area or that (2) the
> dynamic accessibility checks might be significantly more expensive in Ada
> 2005 than they were in Ada 95.  Apparently the Editor didn't realize this
> either, because the above-mentioned implementation note should have been
> deleted.
> 
> Am I confused or what?

I think (hope?) you might be confused.  A couple of specific examples
could determine it one way or the other.  Do you have some?
I'll set aside some quiet hours to ponder them if you think you do...

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

From: Robert A. Duff
Date: Wednesday, September 20, 2006  7:17 AM

>...  I suppose that a frame pointer corresponding to the
> master where the actual parameter was created could be used for that
> purpose, although the check seems rather nasty in the presence of tasking
> and multiple stacks.

That way lies madness.  If the RM requires that, then the RM is wrong.
As you say, multiple task stacks make it "rather nasty" -- somewhat
of an understatement, I'd say.  ;-)

I remember discussing such an implementation model with Tucker during the Ada
9X project.  We both agreed that the task issue ruled it out.

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

From: Pascal Leroy
Date: Thursday, September 21, 2006  7:51 AM

> I think (hope?) you might be confused.  A couple of specific 
> examples could determine it one way or the other.  Do you 
> have some? I'll set aside some quiet hours to ponder them if 
> you think you do...

I do hope that I am confused, but while trying to build various examples
involving accessibility checks, I ran into the following:

  declare
    type T1 is tagged null record;

    task T is
      entry E (X : T1'Class);
    end T;

    task body T is
      type Ref2 is access T1'Class;
      R2 : Ref2;
    begin
      accept E (X : T1'Class) do
        R2 := new T1'Class'(X);
      end;
      -- Delay for a while and then do nasty things with R2.
    end;

    procedure Proc is
      type T2 is new T1 with null record;
      X2 : T2;
    begin
      T.E (X2);
    end;

  begin
    Proc;
  end;

This piece of code is squirreling away an object of type T2 into a task
that outlasts the type.  We don't want this.  The problem appears to be
that the accessibility levels of Ref2 and T2 are incomparable.
Statically, the nesting levels are both 2, so the check of 4.8(10.1/2)
succeeds.  Dynamically, the situation is muddled because "run-time nesting
of masters" doesn't seem to apply here: both T and Proc are (dynamically)
nested in the outer block, but it's not like one of them is (dynamically)
into the other.

Interestingly enough, this situation doesn't arise with access parameters
(no such parameters for task entries) or for function returns (entries are
not functions).  So allocators seem to be the only problematic case.

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

From: Tucker Taft
Date: Thursday, September 21, 2006  9:08 AM

We went out of our way to disallow access parameters in task entries,
specifically because of this problem.  But I wonder whether some kind of
"trick" like that described in 3.10.2(22.dd/2) might have solved the task
entry problem, and might also work here.  Actually, the adjustment
suggested in 3.10.2(22.dd/2) is to solve a different kind of problem,
where an access parameter looks like it is too *deeply* nested.
But perhaps some adjustment would accomplish the goal.

The important thing is that a static accessibility level passed to an entry
must make sense in the context of the entry.  The only real problem
is with levels associated with scopes inside the task body but outside
the accept statement.  I think a caller from outside the task body
has to adjust the static accessibility level to be essentially
infinitely deep if it doesn't outlive the task object whose entry
is being called.  The level isn't looked at at all on conversion
to a type local to the accept statement, and the infinite level
will clearly fail any accessibility check on conversion to
a type local to the task body, but outside the accept statement.
If the call comes from a task nested *inside* the task body,
and it refers to a scope of the enclosing task body, then the
"normal" static level can be passed.  If it refers to a level
inside the nested task, then it should be passed as infinity.

Another way to put this is that if the level passed in refers
to a scope shared with the called task, then no adjustment is
necessary.  If it refers to a scope that is not shared with
the called task, then it should be set to infinity.

Now how does this translate to passing objects of type blah'class?
Normally I would think that we store an accessibility level
inside the object itself.  However, it seems that when calling
a task entry, we will have to pass a separate accessibility
level along with every class-wide parameter.  Alternatively,
we could move the check to the point of the entry call,
so you wouldn't be allowed to pass an object of a type
that doesn't outlive the task.  That is probably preferable,
since the caller is much more likely to know the level of the
actual parameter passed in to an entry, and it avoids having
to add implicit parameters to task entries.

Of course it clearly requires a "binding interpretation,"
since we are adding a new place for an accessibility check.
Also, it may interact badly with synchronized interfaces
with procedures implemented as entries.  We can put whatever
adjustment is required into the compiler-generated wrapper,
but it would be weird to insert an accessibility check
into the compiler-generated wrapper, or to move the check
to all calls on procedures of synchronized interfaces.

Hmmmmm...

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

From: Pascal Leroy
Date: Thursday, September 21, 2006  9:49 AM

> Also, it may interact badly with synchronized interfaces with 
> procedures implemented as entries.  We can put whatever 
> adjustment is required into the compiler-generated wrapper, 
> but it would be weird to insert an accessibility check into 
> the compiler-generated wrapper, or to move the check to all 
> calls on procedures of synchronized interfaces.

That crossed my mind, and my brain started to ache.

Since the problem is with allocators, I would try to fix it on the
allocator, not on the subprogram call.  In other words, complement the
first sentence of 4.8(10.1/2) by something like:

"Furthermore, if the allocator appears in a task that is deeper than the
class-wide type, a check is made that the task is also deeper than the
type determined by the subtype_indication, or by the tag of the value of
the qualified_expression."

In other words, the object created by the allocator must be of a type that
outlives the task.  This is a bit pessimistic, but what the heck!  It's
not worse than making access parameters illegal for entries.

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

From: Tucker Taft
Date: Thursday, September 21, 2006  11:05 AM

Can't we limit this rule to allocators inside of accept statements
where the initializing value is a formal parameter of an enclosing
accept statement?

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

From: Tucker Taft
Date: Thursday, September 21, 2006  12:56 PM

I can answer my own question: probably not, since
you could pass the formal parameter to a subprogram
nested inside the task.  But that leads me to worry
about generics.  Suppose you instantiate a nice
generic package inside a task body, and it declares
extensions of types declared outside the task body.
We don't want the generic to become unusable because
of these required checks.  I also think we can't
ignore the function return case, since there could
be a function called from the accept statement
which turns around and returns the passed in
class-wide object.

My preference at this point would be to leave the
rules as is, and let implementors experiment with
solutions that get the *right* answer.  This may
mean that the run-time accessibility check associated
with allocating and returning class-wide objects
*is* more complex than a simple level comparison.
Perhaps whenever a nested type extension is
elaborated, some additional work needs to be
performed to create a relative *dynamic* accessibility
level (relative to the ultimate ancestor).
For types that are at the same level as their ultimate
ancestor, this level would always be zero.

Even with this extra implementation work, I wonder
whether the RM rules correctly cover the case of
objects passed to task entries.  Are even the
*dynamic* accessibility levels comparable?
Perhaps this notion of "infinitely deep" needs
to be enshrined in RM words as well.  Perhaps
something analogous to what we do for anonymous
access-to-subprogram should apply when passing
an object of a nested type extension to a task
entry if the type is declared outside of any scope that
dynamically encloses the accept statement.

Something like:

     When the accessibility level of an entity is determined
     from that of a master M1, then the level is considered deeper
     than that of any other master M2 that is not dynamically enclosed
     by M1, and whose enclosing task does not depend, directly or
     indirectly, on M1.

   AARM NOTE: This handles the case when M1 and M2 are in different
     "branches" of the overall task/master tree.  Essentially
     if two masters are "incomparable" then the entities of one master
     are considered deeper than the other master, and vice-versa,
     so accessibility checks associated with allocators and function
     returns will fail.  This is important because either master
     might outlive the other.

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

From: Randy Brukardt
Date: Thursday, September 21, 2006  1:36 PM

> My preference at this point would be to leave the
> rules as is, and let implementors experiment with
> solutions that get the *right* answer.  This may
> mean that the run-time accessibility check associated
> with allocating and returning class-wide objects
> *is* more complex than a simple level comparison.
> Perhaps whenever a nested type extension is
> elaborated, some additional work needs to be
> performed to create a relative *dynamic* accessibility
> level (relative to the ultimate ancestor).
> For types that are at the same level as their ultimate
> ancestor, this level would always be zero.

While it might be the only answer, it certainly strikes me as yucky.

> Even with this extra implementation work, I wonder
> whether the RM rules correctly cover the case of
> objects passed to task entries.  Are even the
> *dynamic* accessibility levels comparable?

No, of course not. We're talking two different trees in this case.

> Perhaps this notion of "infinitely deep" needs
> to be enshrined in RM words as well.  Perhaps
> something analogous to what we do for anonymous
> access-to-subprogram should apply when passing
> an object of a nested type extension to a task
> entry if the type is declared outside of any scope that
> dynamically encloses the accept statement.
>
> Something like:
>
>      When the accessibility level of an entity is determined
>      from that of a master M1, then the level is considered deeper
>      than that of any other master M2 that is not dynamically enclosed
>      by M1, and whose enclosing task does not depend, directly or
>      indirectly, on M1.
>
>    AARM NOTE: This handles the case when M1 and M2 are in different
>      "branches" of the overall task/master tree.  Essentially
>      if two masters are "incomparable" then the entities of one master
>      are considered deeper than the other master, and vice-versa,
>      so accessibility checks associated with allocators and function
>      returns will fail.  This is important because either master
>      might outlive the other.

Something like this seems to be necessary, but it really gives me
indigestion. It means that you need to call the task supervisor in order to
make an accessibility check (since that is where the relationships between
task masters lives).

Humm, I'm not sure it is right anyway. Do you really want this to work in
nested tasks? It would be easier (both to describe and implement) if we
simply defined the accessibility of masters that belong to different tasks
to be incomparable, and that checks on incomparable items always fail. That
would keep the task supervisor (or equivalent) out of it.

(Saying M1 > M2 and M2 > M1 violates basic mathematics, and this stuff is
hard enough to understand as it is without being "tricky")

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

From: Tucker Taft
Date: Thursday, September 21, 2006  2:04 PM

...
> Humm, I'm not sure it is right anyway. Do you really want this to work in
> nested tasks? It would be easier (both to describe and implement) if we
> simply defined the accessibility of masters that belong to different tasks
> to be incomparable, and that checks on incomparable items always fail. That
> would keep the task supervisor (or equivalent) out of it.

This is incompatible on face value, since the library level objects
and types are all associated with the environment task, and we don't want
to make the environment task's outermost master incomparable with
every other task.  But something like this might be possible.

I think we only need to be talking about function return
and allocators and nested type extensions.  Perhaps nested type
extensions of a given task can only be allocated for an access
type declared in the same task.  No, that doesn't really work.
Suppose you have a generic containing a task body that declares
an access-to-classwide type.  It surely should be able to do
allocators for its own local access type, even if the specific
type is declared outside the task body.

> (Saying M1 > M2 and M2 > M1 violates basic mathematics, and this stuff is
> hard enough to understand as it is without being "tricky")

I tried to be careful to say that the entities of one master are
deeper than the other master, but I agree it is not obvious what
is the best way to get what we want.

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

From: Pascal Leroy
Date: Thursday, September 21, 2006  3:21 PM

> My preference at this point would be to leave the
> rules as is, and let implementers experiment with
> solutions that get the *right* answer.

Surely you are kidding.  It's already hard enough to implement the
language when it's correctly defined, we cannot expect implementer to
design the language themselves.  Observe that it's not only a matter of
choosing a more-or-less efficient implementation strategy: it's really
unclear what the "right" answer ought to be, how pessimistic we should be,
how we can avoid incompatibilities.

We have established that we have a nasty hole here, and that it seems to
pervade the language (because as you point out pretty much anything can
show up in a task body).  We cannot just throw up our hands in despair: we
have to do serious design work.  Expect some headaches during the Atlanta
meeting.

> Even with this extra implementation work, I wonder
> whether the RM rules correctly cover the case of
> objects passed to task entries.  Are even the
> *dynamic* accessibility levels comparable?

No, I was trying to point this out in my original message: the masters
form a tree and a tree doesn't give you a total ordering.

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

From: Tucker Taft
Date: Thursday, September 21, 2006  3:55 PM

>> My preference at this point would be to leave the
>> rules as is, and let implementers experiment with
>> solutions that get the *right* answer.
> 
> Surely you are kidding.  ...

I originally thought you were pointing out an
implementation problem only, namely that
simple static accessibility levels weren't enough.
It wasn't until later in writing my response that
I realized there was a language hole.  I agree that
we don't want implementors to fix a hole by trial
and error.  What I meant was that if we had what
we believe to be the "right" rules, then I am
reluctant to try to change them if we don't know
for sure we have an implementation problem.

> We have established that we have a nasty hole here, and that it seems to
> pervade the language (because as you point out pretty much anything can
> show up in a task body).  We cannot just throw up our hands in despair: we
> have to do serious design work.  Expect some headaches during the Atlanta
> meeting.
> 
>> Even with this extra implementation work, I wonder
>> whether the RM rules correctly cover the case of
>> objects passed to task entries.  Are even the
>> *dynamic* accessibility levels comparable?
> 
> No, I was trying to point this out in my original message: the masters
> form a tree and a tree doesn't give you a total ordering.

Sorry, I didn't pick up on that initially.  Yes
I agree we need to fix this hole.  I think it only
affects the function return and allocator checks,
and only on nested type extensions.  Although
I don't love the wording I suggested, we all seem
to agree that these checks need to fail if they
correspond to masters in separate branches
of the task/master tree.  I would hope if either
master is "above" the other, then the checks should
work the normal way, even if the masters happen
to be in separate tasks.

The implementation of the above rules are left as a detail
to be worked out by the reader...  ;-)

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

From: Tucker Taft
Date: Thursday, September 21, 2006  5:00 PM

If we take a cue from what we did with 'Class'Input and 'Class'Output,
then a simple rule would be that you can't pass to a
task entry with a class-wide parameter, an object whose
tag identifies a type more nested than that of the task
type.  I think this may be roughly what Randy and/or Pascal
have already suggested.

This would have to be a run-time check when calling
through a synchronized interface implemented by
a task type (and it wouldn't apply to any "controlling"
parameters since they can't fail).  Presumably we would
also define a legality rule when calling an entry
"directly" when the actual is not of the same type as
the formal.

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

From: Pascal Leroy
Date: Friday, September 22, 2006  10:36 AM

> This would have to be a run-time check when calling
> through a synchronized interface implemented by
> a task type (and it wouldn't apply to any "controlling" 
> parameters since they can't fail).

But then the check would have to happen in the wrapper, right?  We
certainly don't want a distributed overhead on all calls through limited
interfaces.

Probably a dumb idea but... how about a post-compilation rule: if T'Class
is used as a parameter of a task entry, there shall be no descendant of T
more nested than the task unit.  Not exactly a pretty rule, and probably a
bit awkward for implementations, but then I would argue that a
post-compilation check is more user-friendly than a run-time check that
fails once in a blue moon.

Also note that we probably have a similar problem for (named) access types
designating T'Class: they too could be used to squirrel away an object
that would outlive its type.

Finally, am I right in believing that there no reason for disallowing
access parameters of an access-to-subprogram type in entries, other than
the fact that we forgot to relax this restriction?

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

From: Tucker Taft
Date: Friday, September 22, 2006  11:47 AM

>> This would have to be a run-time check when calling
>> through a synchronized interface implemented by
>> a task type (and it wouldn't apply to any "controlling" 
>> parameters since they can't fail).
> 
> But then the check would have to happen in the wrapper, right?  We
> certainly don't want a distributed overhead on all calls through limited
> interfaces.

Hmmm, good point.  I didn't think it was so bad to impose it
on all calls through synchronized interfaces, but I agree it
is overkill to impose it on all calls through limited interfaces.
So I guess it would need to be in the wrapper, which I admit
isn't desirable.

> Probably a dumb idea but... how about a post-compilation rule: if T'Class
> is used as a parameter of a task entry, there shall be no descendant of T
> more nested than the task unit.  Not exactly a pretty rule, and probably a
> bit awkward for implementations, but then I would argue that a
> post-compilation check is more user-friendly than a run-time check that
> fails once in a blue moon.

I would give this a triple "g" disgggusting.

How about the following: perform an accessibility
check when passing/copying a class-wide object whose root type
is declared outside the task body, from within
an accept statement to anything outside the accept
statement.  Require that the tag identify a type
declared outside the task body.  This check can
use a static accessibility level.

(By "copying" a task-wide object I mean using it as
the initial value in an allocator.)

> Also note that we probably have a similar problem for (named) access types
> designating T'Class: they too could be used to squirrel away an object
> that would outlive its type.

Can you elaborate?  The existing rules are designed to ensure
that a named access type never outlives an object designated
by one of its values.  That's the point of the check on
allocators.

> Finally, am I right in believing that there no reason for disallowing
> access parameters of an access-to-subprogram type in entries, other than
> the fact that we forgot to relax this restriction?

Yes, I think you are right; we could relax the restriction.
But we might want to allow access parameters in general,
if we end up solving the class-wide object problem, by
imposing a similar limitation on converting/passing access
parameters from inside an accept statement to something
declared outside the accept statement.

In both cases, we want to "carve out" the scopes within
the task body, and require that once an object that might
have come from the entry caller "escapes" from the
static scope of the accept statement, it is guaranteed
to live at least as long as the task type.

I think this rule would be relatively straightforward
to implement, and would almost certainly impose no
significant limitation on the user, given the tendency
to have very simple parameters to an accept statement.

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

From: Pascal Leroy
Date: Tuesday, September 26, 2006  6:34 AM

> > Probably a dumb idea but... how about a post-compilation rule: if 
> > T'Class is used as a parameter of a task entry, there shall be no 
> > descendant of T more nested than the task unit.  
> 
> I would give this a triple "g" disgggusting.

I knew you would say this, but then I'm not in love with dynamic
accessibility checks.  They have the potential to fail infrequently, and
they are not something that users are aware of.  So they are not ideal for
testability.

> How about the following: perform an accessibility
> check when passing/copying a class-wide object whose root 
> type is declared outside the task body, from within an accept 
> statement to anything outside the accept statement.  Require 
> that the tag identify a type declared outside the task body.  
> This check can use a static accessibility level.

Sounds reasonable.  Of course, it's not really "within an accept
statement", it's within anything that can be called from an accept
statement, and that can see an access-to-T'Class declared in the task
body.  So pretty much any code in the task, as soon as the task plays
games with class-wide types.

By the way, don't we have a similar problem with coextensions?  I believe
that the following is legal with the RM as written, and that it can also
cause trouble:

	type T1 is tagged null record;

	task type T (D : access T1'Class);
	type A is access T;

	procedure P is
	    type T2 is new T1 with null record;
	    X : A := new T (new T2));
	begin
	   null;
	end;

It appears that D.all can outlive T2.

> > Also note that we probably have a similar problem for (named) access 
> > types designating T'Class: they too could be used to squirrel away an 
> > object that would outlive its type.
> 
> Can you elaborate?

I was confused.

> Yes, I think you are right; we could relax the restriction.
> But we might want to allow access parameters in general,
> if we end up solving the class-wide object problem, by
> imposing a similar limitation on converting/passing access 
> parameters from inside an accept statement to something 
> declared outside the accept statement.

That's an interesting idea, although it looks like a big change now that
we are in Corrigendum mode.  On the other hand, it would be silly to force
implementers to perform checks for class-wide parameters without taking
advantage of these checks for access parameters.

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

From: Tucker Taft
Date: Tuesday, September 26, 2006  9:35 AM

> ...
>> How about the following: perform an accessibility
>> check when passing/copying a class-wide object whose root 
>> type is declared outside the task body, from within an accept 
>> statement to anything outside the accept statement.  Require 
>> that the tag identify a type declared outside the task body.  
>> This check can use a static accessibility level.
> 
> Sounds reasonable.  Of course, it's not really "within an accept
> statement", it's within anything that can be called from an accept
> statement, and that can see an access-to-T'Class declared in the task
> body.  So pretty much any code in the task, as soon as the task plays
> games with class-wide types.

I actually meant that the check would occur when calling a subprogram
or allocating for an access type declared lexically outside of
the accept statement.  So I really meant "within an accept statement."
Once you got lexically outside the accept statement, I didn't
want any special rules to apply.  Accept statements tend to be
relatively short, and it seemed OK to impose fairly severe
restrictions on what they could do with classwide parameters.

> By the way, don't we have a similar problem with coextensions?  I believe
> that the following is legal with the RM as written, and that it can also
> cause trouble:
> 
> 	type T1 is tagged null record;
> 
> 	task type T (D : access T1'Class);
> 	type A is access T;
> 
> 	procedure P is
> 	    type T2 is new T1 with null record;
> 	    X : A := new T (new T2));
> 	begin
> 	   null;
> 	end;
> 
> It appears that D.all can outlive T2.

I'm not sure why you think it is legal.  It seems to be
a relatively clear (as these things go ;-) violation of
4.8(5.1):

   If the designated type of the type of the allocator is
   class-wide, the accessibility level of the type determined
   by the subtype_indication or qualified_expression shall
   not be statically deeper than that of the type of the allocator.

In this case, "new T2" violates this rule, because its
access type has the accessibility of the object allocated
by the "new T" (per 3.10(14.3/2)), which in turn has the accessibility
of "A" (per 3.10(14/2)), and T2 is statically deeper than A.

...
>> Yes, I think you are right; we could relax the restriction.
>> But we might want to allow access parameters in general,
>> if we end up solving the class-wide object problem, by
>> imposing a similar limitation on converting/passing access 
>> parameters from inside an accept statement to something 
>> declared outside the accept statement.
> 
> That's an interesting idea, although it looks like a big change now that
> we are in Corrigendum mode.  On the other hand, it would be silly to force
> implementers to perform checks for class-wide parameters without taking
> advantage of these checks for access parameters.

That was my feeling.  Access parameters and nested extensions
should sink or swim together.

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

From: Pascal Leroy
Date: Tuesday, September 26, 2006  11:15 AM

> I actually meant that the check would occur when calling a 
> subprogram or allocating for an access type declared 
> lexically outside of the accept statement.  So I really meant 
> "within an accept statement." Once you got lexically outside 
> the accept statement, I didn't want any special rules to 
> apply.  Accept statements tend to be relatively short, and it 
> seemed OK to impose fairly severe restrictions on what they 
> could do with classwide parameters.

Hmm, but then these are indeed pretty severe restrictions.  Preventing
users from calling a (local) subprogram from within an accept statement on
the odd chance that this subprogram would try to squirrel away the
parameter of the entry is unappealing, to say the least.  The only
work-around would be for the user to replicate the code of the subprogram
in the accept statement.  Yuck.  I absolutely hate it when we make
subprograms unusable.  After all subprograms are one of the most useful
constructs in programming languages.

So I'd rather perform the check at the point of the allocator, always,
even if that means imposing a distributed overhead outside of accept
statements.  The overhead is not so bad because the checks are only needed
if (1) the task has an entry with a parameter of type T'Class and (2) the
allocator is for an access-to-T'Class type declared in the task body.

> I'm not sure why you think it is legal.

Because my favorite compiler thought it was legal, maybe?  ;-)

I agree that it's illegal, and my favorite compiler has now changed its
mind.

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

From: Randy Brukardt
Date: Tuesday, September 26, 2006  12:01 PM

> Hmm, but then these are indeed pretty severe restrictions.  Preventing
> users from calling a (local) subprogram from within an accept statement on
> the odd chance that this subprogram would try to squirrel away the
> parameter of the entry is unappealing, to say the least.  The only
> work-around would be for the user to replicate the code of the subprogram
> in the accept statement.  Yuck.  I absolutely hate it when we make
> subprograms unusable.  After all subprograms are one of the most useful
> constructs in programming languages.
>
> So I'd rather perform the check at the point of the allocator, always,
> even if that means imposing a distributed overhead outside of accept
> statements.  The overhead is not so bad because the checks are only needed
> if (1) the task has an entry with a parameter of type T'Class and (2) the
> allocator is for an access-to-T'Class type declared in the task body.

Now I'm confused. What's "the task" here? You can't mean to require full
program analysis, and this problem occurs for any classwide type. Besides, I
have a hard time believing that there is anything special about a parameter
of a classwide type: typically, it is a completely normal parameter -- I
have to think that there are other ways to create this problem.

Beyond that, I would be strongly opposed to any requirement that depended on
the sort of parameters in otherwise unrelated subprograms/entries -- we have
no such rules now, and for good reason: changing an unrelated parameter
could suddenly make your program illegal, and far away from the parameter.
So any rule would have to simply be an accessibility rule -- which is where
we started this discussion. I think we need to fix the accessibility and let
the chips fall where they may; we're going to have to fix the accessibility
sooner or later, and random "patches" hardly ever work.

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

From: Tucker Taft
Date: Tuesday, September 26, 2006  1:01 PM

> Now I'm confused. What's "the task" here? You can't mean to require full
> program analysis, and this problem occurs for any classwide type. Besides, I
> have a hard time believing that there is anything special about a parameter
> of a classwide type: typically, it is a completely normal parameter -- I
> have to think that there are other ways to create this problem.

I think what Pascal is suggesting is that we leave the rules
roughly as is, but clarify that in a rendezvous,
all of the masters dynamically enclosing the caller
that do not also dynamically enclose the task type
are considered deeper than the accept statement.

I think there is still the issue of nested tasks, and
whether they should have different rules.

> Beyond that, I would be strongly opposed to any requirement that depended on
> the sort of parameters in otherwise unrelated subprograms/entries -- we have
> no such rules now, and for good reason: changing an unrelated parameter
> could suddenly make your program illegal, and far away from the parameter.
> So any rule would have to simply be an accessibility rule -- which is where
> we started this discussion. I think we need to fix the accessibility and let
> the chips fall where they may; we're going to have to fix the accessibility
> sooner or later, and random "patches" hardly ever work.

I think the "requirement" is simply on implementations.  It doesn't
change the rules, but it says that this more complicated check
(from an implementation point of view) is only necessary under
the circumstances Pascal identified.  The allocator where the
check would be needed must be statically enclosed within the task,
so no "full program analysis" is required.

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

From: Randy Brukardt
Date: Tuesday, September 26, 2006  3:40 PM

I don't see this. If the nested extension "belongs" to the task (and the
class-wide type does not), it would seem that the check is needed (as the
dynamic nesting would be incompatible). That could be the case in an
instance, for example (with the generic unit being separately compiled). So,
it seems to be a general check. (And I don't see how you would write the
rules such that it would only apply to a class-wide object passed as a
parameter to an entry, but I think that is less important.)

I can understand your "draconian" rules (they can be limited to the task
body only), but I don't see how Pascal's could be (so it's equivalent to the
original problem).

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

From: Tucker Taft
Date: Tuesday, September 26, 2006  4:22 PM

I am afraid I have lost track of what you mean by a
"general check".  Can you illustrate with one or more specific
examples?

I believe we are focusing on the check associated
with an initialized allocator for an access-to-classwide type,
where the initial value potentially comes from the caller
in a rendezvous, and we might get the wrong answer if
we did things the "normal" way (which I presumes involves using
a static accessibility level stored in a place accessible from the
tag(s) of the initial value).

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

From: Randy Brukardt
Date: Tuesday, September 26, 2006  5:11 PM

Sure, but I'm just asking why that check doesn't apply to the same example
wrapped in a generic package and instantiated in the right place. The
accessibility check in the generic body is a dynamic one (else nothing at
all would be allowed), and it would seem to have the same problems as the
"in-place" allocator you are worried about -- except that it is separately
compiled. (Note that the type extension itself does not need to be in the
generic body for this to happen -- it would be illegal for the type
extension to be in the generic body, but it is OK in the spec.)

I can write out the example if you insist, but I'd rather do real work.

Or maybe thinking about accessibility has driven me to insanity...

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

From: Randy Brukardt
Date: Tuesday, September 26, 2006  5:21 PM

> I can write out the example if you insist, but I'd rather do real work.

Having said this, I figured I better actually write it:

type T is tagged ...

generic
package Gen is
   procedure Operation (Obj : in out T'Class);
private
   type Acc_T_Class is access T'Class;
end Gen;

package body Gen is
   procedure Operation (Obj : in out T'Class) is
      P : Acc_T_Class := new T'Class'(Obj);
   begin
   end Operation;
end Gen;

Now, if you have an accept statement:
    accept E (O : in out T'Class) do
       declare
           package OK is new Gen;
       begin
           OK.Operation (O);
       end;
    end E;

you seem to have the same problem as before, except now it is separately
compiled (discounting generic macro expansion effects, which we don't have
of course). So any sort of static check seems to be out, and a dynamic check
means distributed overhead...

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

From: Tucker Taft
Date: Tuesday, September 26, 2006  7:13 PM

I understand your point, though the example you show
I don't believe needs any special handling.  I believe
the special handling is only needed when dealing with
types that are declared inside the task body but outside
the accept statement.

In any case, it does seem to be a particular
challenge for shared generics.  I would envisage
that each non-limited tagged type might need
an implicit dispatching operation that does the
accessibility checks associated with allocators and
function return.  A type that is not
a nested extension could do the straightforward
thing.  A type that is a nested extension
would have to worry about the case of a type
declared in a task body that has accept statements.
These types could presumably be specially marked
when declared, and checking against them would
force the more complex check.

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

From: Pascal Leroy
Date: Wednesday, September 27, 2006  1:23 AM

> Besides, I have a hard time believing 
> that there is anything special about a parameter of a 
> classwide type: typically, it is a completely normal 
> parameter -- I have to think that there are other ways to 
> create this problem.

Please show an example.  "I have a hard time" is not a valid argument.
Class-wide types are special for allocators and function returns.  Access
parameters are special for task entries.  I don't find it too surprising
that class-wide parameters are special for task entries.  So far no other
issue has been identified.

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

From: Pascal Leroy
Date: Wednesday, September 27, 2006  1:57 AM

>     accept E (O : in out T'Class) do
>        declare
>            package OK is new Gen;
>        begin
>            OK.Operation (O);
>        end;
>     end E;
> 
> you seem to have the same problem as before, except now it is 
> separately compiled (discounting generic macro expansion 
> effects, which we don't have of course).

(As Tuck mentioned, you want the generic instantiation to be in the task
body but outside of the accept statement; in the code above the access
type goes away at the end of the rendezvous so there is no problem.)

My view is that this case should be covered by the general rule in
4.8(5.1).  It is not at the moment because are missing a rule that would
say that the type identified by the tag of O has an accessibility level
incomparable with that of the access type.  If we add this rule, the
allocator will fail the check and everything will be fine.

> So any sort of 
> static check seems to be out, and a dynamic check means 
> distributed overhead...

Well, since the check depends on the parameter, it has to be a runtime
check.  What I was trying to point out is that in many circumstances you
can statically eliminate it: it's only needed if there are both class-wide
parameters and local access-to-class-wide types in sight.  In particular
if you replicate generics, it's only ever needed inside the task.

If you are using nested type extensions and you share generics, yes, there
is some distributed overhead, but there is one anyway for the
allocators/function returns check, independently of tasking (remember that
you cannot determine on the generic if a formal type will be class-wide).
My view is that the language should not make an universally shared
implementation of generics infeasible, but it the rules require passing
extra thunks, so be it.

At any rate, I don't see the need for "full program analysis".

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

From: Randy Brukardt
Date: wednesday, September 27, 2006  12:42 PM

...
> > So any sort of
> > static check seems to be out, and a dynamic check means
> > distributed overhead...
>
> Well, since the check depends on the parameter, it has to be a runtime
> check.  What I was trying to point out is that in many circumstances you
> can statically eliminate it: it's only needed if there are both class-wide
> parameters and local access-to-class-wide types in sight.  In particular
> if you replicate generics, it's only ever needed inside the task.

OK, but that's not what you wrote. Since you were responding to Tucker's
"special check" idea, it appeared that you were proposing a different
"special check". I think any "special check" is silly here; we just have to
get the accessibility right and be done with it.

I don't much care (in terms of defining the correct language rules) whether
or not it can be statically eliminated if you have a 20 pass compiler and
the moon is full. :-) That's just an implementation detail (and one that is
impractical in many implementations, I suspect), and mentioning it confuses
the issue more than it helps.

> If you are using nested type extensions and you share generics, yes, there
> is some distributed overhead, but there is one anyway for the
> allocators/function returns check, independently of tasking (remember that
> you cannot determine on the generic if a formal type will be class-wide).
> My view is that the language should not make an universally shared
> implementation of generics infeasible, but it the rules require passing
> extra thunks, so be it.
>
> At any rate, I don't see the need for "full program analysis".

The "special check" you seemed to be proposing did require it; but since you
say that isn't what you meant, then I agree. You'd need a multi-pass
compiler and full program analysis (or macro substitution for all generics
and stubs) to take advantage of the static elimination that you suggest
(making it impractical, I think), but since the whole thing is irrelevant to
the proper rules, it doesn't matter.

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

From: Randy Brukardt
Date: Wednesday, September 27, 2006  12:46 PM

> Please show an example.  "I have a hard time" is not a valid argument.
> Class-wide types are special for allocators and function returns.  Access
> parameters are special for task entries.  I don't find it too surprising
> that class-wide parameters are special for task entries.  So far no other
> issue has been identified.

Classwide types are just a normal kind of type, and they can be used in all
of the normal ways. That means that there must be other ways to get
class-wide objects into an entry. Perhaps they're all illegal for some other
reason (I realize components are), but I'm dubious. No, I haven't found an
example -- that's Steve's job. ;-)

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

From: Brad Moore
Date: Thursday, September 28, 2006  2:12 PM

> Please show an example.  "I have a hard time" is not a valid argument.
> Class-wide types are special for allocators and function returns.  Access
> parameters are special for task entries.  I don't find it too surprising
> that class-wide parameters are special for task entries.  So far no other
> issue has been identified.

I am wondering if there might be a problem with protected objects as well.
The following is very similar to the original example, except it involves
a protected entry instead of a task. My compiler compiler currently crashes
if the allocator isn't commented out, so I do not know if the compiler
would have viewed this as legal or not.
 

 
   Protected_Example : declare

      type T1 is tagged null record;
 
      protected type P is
         procedure S (X : T1'Class);
      private
         R2 : access T1'Class;
      end P;
 
      protected body P is
         procedure S (X : T1'Class) is
         begin
            R2 := new T1'Class'(X);  -- Compiler crashes if uncommented
            null;
         end S;
      end P;
 
      Prot : P;
 
      procedure Proc is
         type T2 is new T1 with null record;
         X2 : T2;
      begin
         Prot.S (X2);
      end Proc;
 
   begin -- Protected_Example
      Proc;
   end Protected_Example;

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

From: Brad Moore
Date: Sunday, October 1, 2006  11:48 PM

> I am wondering if there might be a problem with protected types as well.


This is just an update regarding whether the accessibility check problem
discussed in this thread also applies to protected types in addition to
task types. 

The compiler issue I was having was quickly fixed by the vendor.  Once
this fix was in place, it was confirmed that the behavior is similar to
the case for tasking. 

That is, using protected types, it is possible to squirrel way an access
to a type that outlives the lifetime of the type being referenced.


I have a rather light-hearted and somewhat graphic program below that also
illustrates the problem. The program implements the same interface for a
regular package, a tasking implementation, and a protected type
implementation. The regular package implementation works as expected and
prevents zombie squirrels, while the tasking and protected type implementations
do not.

This also demonstrate dispatching to a tagged type after the type's lifetime
has expired, which is something that shouldn't be allowed to happen.


Note: I would normally try to create a minimal example that strictly
demonstrates the issue, and I admit I got carried away a bit here. Pascal's
usage of the term “squirreling away” in his original example inspired me,
and I guess I was having fun with the squirrel concept.


My apologies to squirrel lovers everywhere.


A further note, this does somewhat hit close to home, as in the mid 1960's
a couple of non-native gray squirrels escaped from the local zoo, and now
there are literally thousands of them all over our city.

with Ada.Text_IO; use Ada.Text_IO;
procedure test_accessibility_checks is

   package Chaos is

      type Critter is interface;
      function Species (Object : Critter) return String is abstract;

      type Cage is limited interface;
      procedure Put_In_Kennel
        (X : in out Cage;
         Y : in Critter'Class)  is abstract;
      procedure Roll_Call (X : in out Cage) is abstract;
      function Cage_Type (X : in Cage) return String is abstract;

   end Chaos;

   ----------------------------------------------------------

   package Edible_Straw_Cage is

      -- Squirrels have chewed a hole
      task type Straw_Cage is new Chaos.Cage with
         entry Put_In_Kennel (Y : in Chaos.Critter'Class);
         entry Roll_Call;
      end Straw_Cage;

      function Cage_Type (X : in Straw_Cage) return String;

   end Edible_Straw_Cage;

   package body Edible_Straw_Cage is

      task body Straw_Cage is
         R2 : access Chaos.Critter'Class;  -- Squirrelable
      begin
         accept Put_In_Kennel (Y : in Chaos.Critter'Class) do
            R2 := new Chaos.Critter'Class'(Y);  -- Compiler Run time
                                                -- accessibility check passes
         end Put_In_Kennel;
         accept Roll_Call do
            Put (R2.Species);
         end Roll_Call;
      end Straw_Cage;

      function Cage_Type (X : in Straw_Cage) return String is
      begin -- Cage_Type
         return "Straw (Tasking)";
      end Cage_Type;

   end Edible_Straw_Cage;

   ----------------------------------------------------------

   package Safe_Metal_Cage is  -- Squirrel proof cage

      type Metal_Cage is new Chaos.Cage with private;

      overriding procedure Put_In_Kennel
        (X : in out Metal_Cage;
         Y : Chaos.Critter'Class);
      overriding procedure Roll_Call (X : in out Metal_Cage);

      overriding function Cage_Type (X : in Metal_Cage) return String;

   private
      type Metal_Cage is new Chaos.Cage with record
         R1 : access Chaos.Critter'Class;  -- Squirrel proof
      end record;
   end Safe_Metal_Cage;

   package body Safe_Metal_Cage is

      procedure Put_In_Kennel
        (X : in out Metal_Cage;
         Y : Chaos.Critter'Class)
      is
      begin -- Put_In_Kennel
         X.R1 := new Chaos.Critter'Class'(Y);
         -- Compiler Run time accessibility check fails

      end Put_In_Kennel;

      procedure Roll_Call (X : in out Metal_Cage) is
      begin -- Roll_Call
         if X.R1 /= null then
            Put (X.R1.Species);  -- Won't get called
         end if;
      end Roll_Call;

      function Cage_Type (X : in Metal_Cage) return String is
      begin -- Cage_Type
         return "Metal (Regular Package)";
      end Cage_Type;

   end Safe_Metal_Cage;

   ----------------------------------------------------------

   package Chewable_Plastic_Cage is

      -- Squirrels have chewed a hole
      protected type Plastic_Cage is new Chaos.Cage with
         procedure Put_In_Kennel (Y : Chaos.Critter'Class);
         procedure Roll_Call;
         function Cage_Type return String;
      private
         R3 : access Chaos.Critter'Class;  -- Squirrelable
      end Plastic_Cage;

   end Chewable_Plastic_Cage;

   package body Chewable_Plastic_Cage is
      protected body Plastic_Cage is

         procedure Put_In_Kennel (Y : Chaos.Critter'Class) is
         begin
            R3 := new Chaos.Critter'Class'(Y);  -- Run time accessibility check does not fail
            null;  -- Compiler problems uncommenting out preceding line
         end Put_In_Kennel;

         procedure Roll_Call is
         begin -- Roll_Call
            if R3 /= null then  -- Compiler Crash if uncommented
                Put (R3.Species);
             end if;
            null; -- Compiler problems commenting out preceding if statement

         end Roll_Call;

         function Cage_Type return String is
         begin
            return "Plastic (Protected Type)";
         end Cage_Type;

      end Plastic_Cage;
   end Chewable_Plastic_Cage;

   ----------------------------------------------------------

   procedure Test_Cage (Cage : in out Chaos.Cage'Class) is

      package Nasty is
         type Squirrel is new Chaos.Critter with null record;
         overriding function Species (Object : Squirrel) return String;
      end Nasty;

      package body Nasty is
         function Species (Object : Squirrel) return String is
         begin
            return "Zombie Squirrels";
         end Species;
      end Nasty;

      Rocky_The_Flying_Squirrel : Nasty.Squirrel;

   begin -- Test_Cage
      Cage.Put_In_Kennel (Rocky_The_Flying_Squirrel);    
      -- Dispatch on Cage Type
      Put_Line
        ("Squirrel from " & Cage.Cage_Type & " cage has been granted immortality!");

   exception
      when others =>
         Put_Line
           ("Squirrel from " & Cage.Cage_Type & " cage has been caught!");
   end Test_Cage;

   Metal_Cage   : Safe_Metal_Cage.Metal_Cage;
   Plastic_Cage : Chewable_Plastic_Cage.Plastic_Cage;
   Straw_Cage   : Edible_Straw_Cage.Straw_Cage;

begin -- test_accessibility_checks

   -- Dispatch some calls to see if any squirrels are still alive.
   -- They shouldn't be, because the Nasty Squirrel package is out of scope.

   Test_Cage (Straw_Cage);
   Straw_Cage.Roll_Call; -- By now, the squirrels lifetime should be over.

-- Note: I think I should have been able to use the object prefix notation here.
-- I suspect this is a compiler bug.
   Put_Line
     (" from " & Edible_Straw_Cage.Cage_Type (X => Straw_Cage) &
      " cage are running amok!");

   Test_Cage (Metal_Cage);
   Metal_Cage.Roll_Call;
   Put_Line
     ("No squirrels running loose from " & Metal_Cage.Cage_Type & " cage.");

   Test_Cage (Plastic_Cage);
   Plastic_Cage.Roll_Call; -- By now, the squirrels lifetime should be over.
   Put_Line ("from " & Plastic_Cage.Cage_Type & " cage have returned!");

end test_accessibility_checks;


--------------------------------------------------------------------------

The output is:

Squirrel from Straw (Tasking) cage has been granted immortality!
Zombie Squirrels from Straw (Tasking) cage are running amok!
Squirrel from Metal (Regular Package) cage has been caught!
No squirrels running loose from Metal (Regular Package) cage.
Squirrel from Plastic (Protected Type) cage has been granted immortality!
Zombie Squirrels from Plastic (Protected Type) cage have returned!

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

From: Pascal Leroy
Date: Monday, October 2, 2006  5:37 AM

> Note: I would normally try to create a minimal example that
> strictly demonstrates the issue, and I admit I got carried away 
> a bit here. Pascal's usage of the term “squirreling away” in his
> original example inspired me, and I guess I was having fun with
> the squirrel concept. 

"To squirrel away" used to be a technical term in Ada 95: look it
up in the index. Apparently a squirrel-hater has removed it in
Ada 2005, although the term is still used in the AARM.

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

From: Randy Brukardt
Date: Tuesday, October 3, 2006  2:21 PM

I just looked in my original Ada 95 manual, and I can't find 
squirrel away" in the index. It wouldn't have surprised me if it
had been indexed, but I can't find any evidence that it was. So I don't
think that there was a "squirrel-hater".

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

From: Robert A. Duff
Date: Tuesday, October 3, 2006  3:52 PM

During Ada 9X, all silly jokes were relegated to the Annotated version of the
reference manual.

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

From: Pascal Leroy
Date: Thursday, October 5, 2006  8:22 AM

I am looking at the accessibility rules for objects created by allocators
in 3.10.2(14-14.2).  These rules don't seem to cover derived types:

	type T (D : access Integer) is null record;
	type D is new T (new Integer'(3));

The allocator here is not quite used to define the constraint in a
subtype_declaration so 3.10.2(14.1/2) doesn't apply.

There also appears to be a rule missing for the following case:

	type A is access T;
	X : A (new Integer'(4));

It seems clear that the allocator is *not* being used to define the
discriminant of an object, so the wording of 3.10.2(14.3/2) does not
apply.

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

From: Gary Dismukes
Date: Sunday, November 12, 2006  4:57 PM

The discussion we had a while back about accessibility checks (initiated
by Steve Baird's posting) led me to start thinking about cases that could
pose problems for using static levels to implement the checks for allocators.
I think there was general agreement that the intent is certainly that it
should be possible to use static levels, and that if dynamic levels must be
maintained this could lead to real difficulties (for example because of cases
involving multiple task stacks).  That segued into a discussion of problems
related to accessibility checking within task entries with class-wide
parameters, and how we might restrict those (discussion still not fully
resolved AFAIK, to be continued I believe at the ARG meeting).  Here I'm
looking at a different sort of case, with no tasks involved.

Consider this example that uses an anonymous access-to-subprogram type.

procedure Test is

   type T1 is tagged null record;

   procedure Proc_1 (AP : access procedure (T1C : T1'Class)) is

      type Ref is access all T1'Class;

      type NT1 is new T1 with null record;
      XNT1 : NT1;

   begin
      AP.all (XNT1);
   end Proc_1; 

   procedure Proc_2 is

      type Ref is access all T1'Class;

      X : Ref;

      procedure Indirect_Proc (T1C : T1'Class) is
      begin
         X := new T1'Class'(T1C);  -- Should raise C_E if T1C'Tag = NT1'Tag
      end Indirect_Proc;

   begin
      Proc_1 (Indirect_Proc'access);
      --  Do something with X.all...
   end Proc_2;

begin
   Proc_2;
end;


What's going on here is that a tagged object of a type (NT1) declared
within procedure Proc_1 is passed back out of its scope in a call to
Indirect_Proc, which then executes a class-wide allocator initialized
to the passed NT1 object.  The allocator is for an access type declared
at a shallower level than type NT1.  This is required to raise an exception,
since it would create a reference to an object of a "dangling" tagged type.
But how can we implement that check if we only use static levels?  The
static levels of types NT1 and Ref match.  (For that matter, the passed
procedure and access type could be declared at an even deeper static level.)

Putting aside the question of how to cheaply determine a dynamic level
(using a frame pointer being one possibility), there needs to be a way
to associate the dynamic level with the object passed to Indirect_Proc.
It seems that the level either needs to be provided as an extra parameter
with class-wide parameters, as is done for access parameters, or else
perhaps contained within the object itself (or obtained via the object's
tag).  This seems rather unpleasant, especially the need at all for dynamic
levels, but offhand I don't see how it can be avoided for case like this.

Note that I'm not pointing out any semantic problem with the RM rules here.
This case is semantically well-defined.  I'm just worried about how to
go about implementing it.  Also, as I mentioned above, there were concerns
raised that if dynamic levels are needed then this create cause real problems
for cases involving multiple task stacks.

The question is, in view of this sort of example, is it still possible to
retain an implementation model based on static levels?  Maybe the above
case could be handled by using a special "infinite" level value to avoid
the need for using fully dynamic levels, but it still seems necessary to
associate the level with the parameter, and that would appear to entail
a possible distributed overhead, which seems unfortunate.

Unless anyone can suggest an easy and efficient way to deal with cases of
this kind, I'd like to see this issue added to the discussion of accessibility
in task entries at the upcoming ARG meeting (assuming that's going to be on
the agenda).

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

From: Randy Brukardt
Date: Monday, November 13, 2006  2:15 AM

Gary gives a thought-provoking example, and then poses the question:

> The question is, in view of this sort of example, is it still possible to
> retain an implementation model based on static levels?

No. The static level implementation required some bizarre fix-ups in Ada 95
to work, and it doesn't surprise me in the least that we managed to break
those when we relaxed various restrictions.

But it probably is possible to retain that model in most cases. (Details
left to the implementor.)

> Maybe the above
> case could be handled by using a special "infinite" level value to avoid
> the need for using fully dynamic levels, but it still seems necessary to
> associate the level with the parameter, and that would appear to entail
> a possible distributed overhead, which seems unfortunate.

The accessibility level of the type should be associated with the tag. A tag
for a nested extension already has a dynamic part, so I don't see why there
would be any significant hardship with having a dynamic accessibility level.
The nasty thing would be figuring out when they are incomparable, but that
probably could be done in a library routine.

> Unless anyone can suggest an easy and efficient way to deal with cases of
> this kind, I'd like to see this issue added to the discussion of accessibility
> in task entries at the upcoming ARG meeting (assuming that's going to be on
> the agenda).

The idea that there is anything easy about accessibility checking makes me
laugh uncontrollably! ;-)

I do want to say that I'm not trying to think too deeply about this (it's 2
am, after all), so I might have missed something. But I don't see that we're
going to say "gee, the accessibility checks are harder to do. Let's repeal
the ability to have nested type extensions!!" And eliminating cases like the
above some other way would probably make class-wide parameters (or anonymous
access-to-subprogram types) unusable. So, I think implementers are just
going to have to suck it up on this one. (Maybe I'll feel differently
tomorrow...)

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

From: Robert A. Duff
Date: Monday, November 13, 2006  8:59 AM

While sleepwalking at 2:00 am, "Randy Brukardt" <randy@rrsoftware.com> writes:

> The accessibility level of the type should be associated with the tag.

How about storing two things in the type descriptor (what the tag points at):

    - the frame pointer of the innermost enclosing master
    - a unique id for the current task

Then check that the task id's are equal, and the frame pointers <=, and raise
C_E otherwise.  Does that make sense?

It requires a contiguous stack.  :-(

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

From: Pascal Leroy
Date: Monday, November 13, 2006  10:26 AM

> Then check that the task id's are equal, and the frame 
> pointers <=, and raise C_E otherwise.  Does that make sense?

I don't know much about the implementation of tasking, but I am under the
impression that querying the current task id is an expensive operation if
you are running on top of a thread library.

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

From: Randy Brukardt
Date: Monday, November 13, 2006  5:26 PM

Possibly true, but since the accessibility checks are comparing the levels
of two tagged types, you would only need to get the task id when you are
creating the tag (and only for nested extensions). It doesn't seem likely
that the cost of creating tags is frequent enough to matter significantly.
(The most likely case would be in containers instances, but those are
relatively expensive anyway.)

The actual accessibility checks would be fairly cheap, especially as you
could include a marker for extensions that are not nested (they cannot fail
these nested extension checks). So I think Bob's strategy would work well
for nested extension accessibility checks (and would implement
"incomparibility" easily).

I haven't thought about the access type checks, but it seems to me that it
would always be possible to cache the task id/stack frame at the point of
the type declaration, and thus prevent needing to get the task id
frequently. So this seems like a viable approach to investigate further. (I
won't go so far as to say that it will have to work, because nothing with
accessibility is ever as it seems...)

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

From: Robert A. Duff
Date: Monday, November 13, 2006  6:54 PM

> I don't know much about the implementation of tasking, but I am under the
> impression that querying the current task id is an expensive operation if
> you are running on top of a thread library.

Good point.

It is certainly possible (and desirable!) that current-task-id be efficient.
But I have seen threading systems that get in the way of that.

The other question is whether this scheme does what we want and/or what the RM
currently requires.  I'm not sure.

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

From: Tucker Taft
Date: Monday, November 13, 2006  7:21 PM

I think what Gary showed is that the challenge
is not restricted to inter-task calls.  The fundamental
problem is when you need to communicate an accessibility
level at run-time to a subprogram that is not within
the scope of the caller.  We see this problem in Ada 95 in
3.10.2(22.dd), where an outer subprogram passes along its access
parameter to a local nested subprogram.  This nested subprogram
was not in the scope of the original caller, and as such
the static accessibility level from the original caller is not
particularly meaningful.  In specific, if the actual is more nested
than the outer called subprogram, 3.10.2(22.dd) suggests its
accessibility level should be adjusted to match that of the
outer called subprogram's outermost scope.  This makes it
meaningful to the nested routine being called.

Unfortunately, unless we want all tagged types to be treated
like access parameters, we cannot easily adjust an accessibility
level as part of parameter passing.  Whatever accessibility
level information we have for a tagged type, it is presumably
set when the tagged type is elaborated, and readily accessible
from every object of the type.  The most fundamental
question is whether the tagged type is more nested than some
ancestor.  If not more nested, then of course no accessibility
check associated with nested extensions can fail.

And in the opposite case, where the type is more nested than
some ancestor (we will call this a "nested extension"), if
we are checking against the level of an access type or
function declared at the library level, then
the check is certain to fail.  These two cases probably will
account for 99% of all checks, and the remaining case where
we have a nested extension, and the access type or function
against which the check is performed is not declared at library level,
can probably incur a fair amount of overhead without slowing down
the program significantly.

Let us presume that in any scope in which one or more nested
extensions are declared, or an access type or function is declared
against which a tagged-type-accessibility check might be performed,
we create an accessibility "anchor" of some sort with a unique ID
(such as its address), and arrange that each nested extension refers
to this anchor.  We then simplify another portion of the checks,
because if the anchor ID matches that of the function or access type
against we are checking, we know the check passes.

We are now down to the cases where the anchor ID doesn't match that
of the access type/function, and we need to check that the anchor ID
of the nested extension outlives that of the access-type/function.
If we presume that anchors are linked to the next longer-lived anchor,
we can simply walk the chain of anchors starting at the anchor
of the access-type/function, and if we don't run into the anchor referenced
by the nested extension before we reach library level, then we raise
an exception.  Of course there is no real need for a library-level anchor;
it can be represented by a null pointer.

Note that these anchors approximate the "master records" that some
run-time systems create on the stack to link frames containing task
objects and/or finalizable objects.  Since nested extensions may
have finalization actions, and access-to-class-wide types generally
have finalization actions as well (since the designated objects might
have controlled parts), it might be natural to use a master record
as the accessibility anchor.  The only clearly new need for such a master
record would be for a scope declaring one or more functions that
return class-wide types.  Creating the master record could be
deferred until it is determined that one or more of the functions
contain a return statement that needs to perform a dynamic
tagged-type accessibility check.

So to summarize the possible approach -- when entering a scope containing
an access-to-class-wide, function-returning-classwide, or nested extension,
an accessibility "anchor" or master record must be allocated on the
stack, and initialized to point to the enclosing anchor/master record.
When it is time to do a check, presuming the special cases mentioned
above don't apply, we walk the anchor/master chain starting at that
of the access-type/function until we find that of the nested extension
(check then passes), or reach the library level (check then fails).

It just might work...

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

From: Tucker Taft
Date: Monday, November 13, 2006  7:44 PM

In a separate response, I proposed using an accessibility
"anchor" analogous to the "master" record used by some
run-time systems to link together stack frames that contain
task objects or finalizable objects.  It seems like it
might be useful to take Gary's example and try to annotate
it with where these "anchors" would be created, and how
they could be used to do the checking.

See below...

Gary Dismukes wrote:
> The discussion we had a while back about accessibility checks (initiated
> by Steve Baird's posting) led me to start thinking about cases that could
> pose problems for using static levels to implement the checks for allocators.
> I think there was general agreement that the intent is certainly that it
> should be possible to use static levels, and that if dynamic levels must be
> maintained this could lead to real difficulties (for example because of cases
> involving multiple task stacks).  That segued into a discussion of problems
> related to accessibility checking within task entries with class-wide
> parameters, and how we might restrict those (discussion still not fully
> resolved AFAIK, to be continued I believe at the ARG meeting).  Here I'm
> looking at a different sort of case, with no tasks involved.
> 
> Consider this example that uses an anonymous access-to-subprogram type.
> 
> procedure Test is
> 
>    type T1 is tagged null record;
> 
>    procedure Proc_1 (AP : access procedure (T1C : T1'Class)) is
> 
>       type Ref is access all T1'Class;
> 
>       type NT1 is new T1 with null record;
>       XNT1 : NT1;

--> anchor(#1) created in this stack frame because we
     have access-to-class-wide as well as a nested extension.
     XNT1 (and other objects of type NT1) will refer to this anchor.
     Remember that there is a new anchor created every time
     Proc_1 is called.
> 
>    begin
>       AP.all (XNT1);
>    end Proc_1; 
> 
>    procedure Proc_2 is
> 
>       type Ref is access all T1'Class;

--> anchor(#2) created in this stack frame because we have an
     access-to-class-wide type.
> 
>       X : Ref;
> 
>       procedure Indirect_Proc (T1C : T1'Class) is
>       begin
>          X := new T1'Class'(T1C);  -- Should raise C_E if T1C'Tag = NT1'Tag

--> at this point, we look at the anchor associated with the frame
     where the access type is declared (anchor#2), and we start following
     it up toward the library level looking for the anchor associated
     with T1C.  If we find it, the check passes.  If we don't, the
     check fails.  In this case, following the chain from anchor#2
     will not find the anchor associated with the T1C passed by the
     indirect "AP.all(XNT1)" call above.  Instead, anchor#1 points
     "up" at anchor#2, not vice-versa.

>       end Indirect_Proc;
> 
>    begin
>       Proc_1 (Indirect_Proc'access);

--> Proc_2 calls Proc_1, so anchor#1 points "up" to anchor#2

>       --  Do something with X.all...
>    end Proc_2;
> 
> begin
>    Proc_2;
> end;

In this example, the technique seems to produce the correct
answer, because anchor#1 will *not* be found on the chain
starting at anchor#2 and going up toward library level.

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


Questions? Ask the ACAA Technical Agent