Version 1.3 of ai05s/ai05-0142-2.txt

Unformatted version of ai05s/ai05-0142-2.txt version 1.3
Other versions for file ai05s/ai05-0142-2.txt

!standard 3.10(6)          09-04-03 AI05-0142-2/01
!standard 3.10(12/2)
!standard 3.10.2(13.1/2)
!standard 3.10.2(18.1/2)
!class Amendment 09-04-04
!status No Action (9-0-0) 10-02-27
!status work item 09-04-04
!status received 09-03-19
!priority Medium
!difficulty Medium
!subject Limited access types
!summary
(See proposal.)
!problem
Modifying a portion of a larger opaque object (such as a container) is not well-supported in Ada. The Ada.Containers packages provide a procedure Replace_Element for this purpose. But this procedure requires copying the element (potentially in both directions). That could be very expensive if the element is large.
The Ada.Containers packages also provide an procedure Update_Element for this purpose; this provides a writable object as a parameter to a subprogram passed into the procedure. This procedure avoids the need to copy the element, but it is hardly convenient to define a procedure for every component of the element that needs changing. The extra syntax needed obscures the real meaning of the program.
An option that was rejected for the Ada.Containers packages was to return an access to the element type. However, this is problematic as it is difficult to control the accessibility and lifetime of the returned access. If the element is removed from the container, the returned access could become dangling; continued use of the access would make the program erroneous. Moreover, the accessibility of the returned object (and thus what could be done with it) would depend on the actual implementation of the container. Bounded containers would typically only return access values with a very short lifetime, while unbounded containers would typically return access values with a much longer lifetime. Converting from an unbounded to bounded form could thus introduce new runtime errors - a serious maintenance hazard.
!proposal
Add "limited" to anonymous access types. This would prevent the access value from being copied to another access type -- essentially, it can only be dereferenced.
We accomplish this by defining that limited anonymous access types have the same sort of accessibility as an anonymous access-to-subprogram parameter.
limited anonymous access types always have Storage_Size = 0.
Special case: The master of the parameters of a function with a limited access result would have be the same as the call as a whole. That would allow returning part of one of the parameters as the access result (without the parameter disappearing prematurely).
For the purposes of converting to a limited access type, accessibility would be checked as follows: [I don't know off-hand how to word this, the only existing cases that I know of allow anything to be converted to the type. - RLB]
* For a limited access parameter, any accessibility is allowed.
* For a limited access result, the converted access would have to have
a lifetime at least as long as the function call. This would include its parameters, but not any local object. Special case: A limited access parameter (or part thereof) can be returned as a limited access result. [This works because the parameter has to exist at least as long as the call does: recall the special master rule noted above. And it can't be assigned or converted beyond that.]
[RLB: We would like to allow a function with a limited access result to return the result of a call to another function with a limited access result. But that is not safe given the rules as given here. Imagine:
function B (P : access Something) return limited access Something is begin return P; end B;
function A return limited access Something is begin declare Local : aliased Something; begin return Local'access; -- Illegal, local object. return B (Local'access); -- Better not be legal! end; end A;
The problem here is B has "filed-off" the accessibility of the parameter, and we now have to assume the worst. Note that making P a limited access doesn't change anything. I don't see any solution to this problem, and since it is not critical to solving the original problem, I simply didn't try.]
* For a limited access type used as the type of a component or stand-alone object,
the converted access would have to have the same or greater lifetime than the object. Special case: For a limited access type used as a component of a return object, we use the accessibility that the object will have after the return (not the one that is current inside of the return statement). Otherwise, we'd again allow local objects. Note that this is the same rule as used for limited access results. [RLB: An alternative would be to recheck the accessibility when the accessibility of the return object changes. But that sounds like a pain to implement.] [RLB: Note that 3.10.2(10/2) means that we could initialize a stand-alone object with a function returning a limited access type. But that isn't allowed "naturally" because a limited access has an infinite accessibility. Do we need a special-case rule to handle that case?? OTOH, we surely do not want to allow any other initialization of a stand-alone object with a limited access.]
* For a limited access type used as the type of a formal object, the converted
access would have to have the same or greater lifetime than the instance. [Not sure if this is actually different than the above.]
* For a limited access type used as the type of an object renaming, the converted
access would have to have the same or greater lifetime than the renaming. In the case where a function call with a limited access result is being renamed, we would not be any accessibility check. (In this case, the master of the function call is that of the renames, so everything will be OK.)
* For a limited access type used as the type of an access discriminant, ????
[Do we need the entire mess of accessibility for access discriminants, or is the admittedly messy rules for limited access components already cover what we need to allow? That is, we "just" use the accessibility of the object. I lean toward unifying them, but I'm not certain. Note that no coextensions are allowed, because there aren't any allocators.]
!wording
Replace 3.10(6) by:
access_definition ::= [null_exclusion] [limited] access [constant] subtype_mark [null_exclusion] [limited] access [protected] procedure parameter_profile [null_exclusion] [limited] access [protected] function parameter_and_result_profile
Add after 3.10(12/2):
An access_definition that contains the reserved word *limited* is called a *limited access type*. In addition, the anonymous access subtype defined by the access_definition of a parameter is a limited access type if the access_definition defines an access-to-subprogram type.
[RLB: should this term include "anonymous" since we are only going to define these for anonymous access types? That is, "limited anonymous access type".]
The Storage_Size of a limited access type is defined to be zero. [RLB: This is intended to trigger 4.8(5.3/2). Note that we can't actually write the Storage_Size attribute for these types, so I can't steal the wording of E.2.2(17/2).]
Replace 3.10.2(13.1/2):
* The accessibility level of a limited access type is deeper than that of any master;
all limited access types have this same level.
[RLB: We probably ought change all of the other existing uses of "anonymous access type" in this list of bullets to say "non-limited anonymous access type" to avoid ambiguity, and also in 3.10.2(19/2). That's annoying.]
Replace 3.10.2(18.1/2):
* The accessibility level of a limited access type is statically deeper than that
of any master; all limited access types have this same level,
** Rest TBD **
!discussion
This proposal grew out of discussions about the problems noted in AI05-0142-1; what usually is wanted is an access value that can be dereferenced but not converted to some other type.
We only allow anonymous limited access types as it would be barely useful to allocate objects for a type that couldn't be copied. Moreover, the implicit conversions of anonymous access is exactly what is needed: conversions in are automatic, and conversions out are illegal anyway, so there is no problem that they are harder than usual to define.
We do include anonymous access-to-subprogram types, as it seems that they could be useful as well -- besides, we invented the idea for them originally.
Allocators are not allowed for limited anonymous access types. We could try to define their accessibility (it would be similar to what is allowed to be converted), but it doesn't seem very useful, as the object could only be dereferenced afterwards. Moreover, it seems difficult to avoid storage leaks. Finally, user experience with the existing allocators of anonymous access types is that they are usually exactly the wrong thing, so we don't see any compelling reason to make this problem worse with a new feature.
The intent of this feature is to use it in cases where it is desired to be able to return a writable reference to an object, but where creating a first-class access type is undesirable (because of lifetime issues, perhaps).
This feature directly cannot solve all dangling access issues, but it can be combined with other existing Ada features to do so.
For instance, in the containers libraries, it would be tempting to define:
function Modifiable_Element (Container : in out Vector; Position : in Cursor) return limited access Element_Type;
However, this would not be safe if the element was deleted from the container while the limited access object still exists. It might appear that this is unlikely, but it would be easy to rename the result of one of these calls or pass it's designated object as the parameter of a complex subprogram. Once the usage is sufficiently separated from the original call, it no longer would be unlikely for some modification to be performed to the container. (Recall that since we allow "sliding" of elements in vectors, deleting any element from a vector would change the element accessed, possibly leading to erroneous execution if a discriminant constraint had been applied somewhere.)
But we can combine this feature with finalization of objects to handle the problem properly (see the examples below).
!examples
In the existing containers libraries, we need to be able to treat the entire existence of a limited access to an element to be program text where tampering with elements is prohibited. The tricky part is getting a callback to clear the tampering state when the limited access no longer exists, since we don't have (non-trivial) finalization of access values.
We can accomplish this callback by wrapping the limited access in a controlled object. This requires two types in Ada as we currently have it defined, one for the private tampering mechnism, and one for the public limited access:
type I_Tamper_With_Elements is tagged limited private;
type Element_Accessor is limited new I_Tamper_With_Elements with record Element : limited access Element_Type; end record;
function Modifiable_Element (Container : in out Vector; Position : in Cursor) return Element_Accessor;
Note that the name "Element" has already been used by the containers libraries, and it is fact was used for a function with a type-conforming profile, so using that name would be inadvisable. Moreover, the fact that the container is "in out" when it can be modified (a standard design feature of the containers libraries) suggests that a different name be used so that it is still possible to read "in" parameter containers.
This could be used (based on a recent example from comp.lang.ada):
with Ada.Containers.Vectors; procedure Check is package Integer_Vectors is new Ada.Containers.Vectors (Index_Type => Natural, Element_Type => Integer);
package Nested_Vectors is new Ada.Containers.Vectors (Index_Type => Natural, Element_Type => Integer_Vectors.Vector, "=" => Integer_Vectors."=");
IV : Integer_Vectors.Vector; NV : Nested_Vectors.Vector; begin IV.Append(42); NV.Append(IV); NV.Modifiable_Element(0).Append(43); end Check;
The dereference does not need to be explicitly given in this case, and the fact that the object returned by Modifiable_Element is limited and (probably) controlled is not visible in the code.
You could also save the entire object as long as needed:
declare My_IV : Nested_Vectors.Element_Accessor := NV.Modifiable_Element(0); begin
(since My_NV would be built-in-place).
Note that this design still works as intended even if the user renames only the limited access value:
declare My_IV : limited access Integer_Vectors.Vector renames NV.Modifiable_Element(0).all; begin
Even in this case, the wrapping finalizable object still is protecting the container, because the master of the returned object is the master of the renames: it will stick around as long as the renames does.
Moreover, splitting the access from the object usually isn't possible:
declare My_IV : limited access Integer_Vectors.Vector := NV.Modifiable_Element(0).all; -- Illegal! begin
This is illegal because the accessibility check fails. (And that's intentional!)
It is OK to pass this element as a parameter, as again the wrapping object will live as long as the parameter:
Operate (NV.Modifiable_Element(0));
or
Munge (NV.Modifiable_Element(0).all);
Claw uses a very similar scheme to ensure that an object which has been returned cannot become dangling because of the actions of another task. Since Claw is written in Ada 95, it could not prevent the client from copying the access and defeating the purpose of the lock object. But the limited access type would prevent that problem.
Aside: It would be nice in this case if we could avoid the extra type in order to have a partially visible type. But I don't think this case is common enough to justify the violence to the language semantics that would be needed. Especially given the confusion that would ensue given that a partial view is of a private type, so some other description would have to be given to a partially private type.
!ACATS test
ACATS tests would be needed for this feature.
!appendix

From: Steve Baird
Sent: Wednesday, April  8, 2009  5:24 PM

> In the interests of putting my effort where my mouth is, here is one
> of my action items:
>
>> Randy: Create an AI-142 alternative using "limited access".
[This is AI05-142-2/01 - ED.]

To solve the problem that the AI is trying to address, we do not need limited
access types other than as parameter and function result types. I think there
may be complexity without corresponding benefit in allowing them in other cases.

For example, a limited discriminant type sounds like a mess (membership testing
implicitly involves equality testing).

Wouldn't a limited access non-discriminant component type require much the same
accessibility checks as an access discriminant?

What prevents a dangling reference in this case?

     type R is
        limited record
           F : limited access Some_Tagged_Type'Class;
         end R;

      type Ref is access R;
      Ptr : Ref;

      function Func (X : Some_Tagged_Type'Class) return
        limited access Some_Tagged_Type'Class is
      begin
          return X'Access;
      end Func;

      procedure P
          Y: Some_Extension;
      begin
          Ptr := new R'(F => Func (X => Y));
      end P;

Similar problems seem to exist if the result type of the function is R and the
function returns an aggregate.

What would be the benefit of allowing
      X : limited access T := ... ;
?
Why would anyone want to do this?

----

Do we really need limited access types as parameters?
What would be the consequences of taking a program which makes use of such
parameters and deleting the word "limited" from all of the parameter
declarations?

You mention in the !proposal section that a function with a limited access
result type wants to be able to return a limited access parameter, but this
could be accomplished by allowing it to return any access parameter, whether
limited or not. Thus, this does not provide a justification for limited access
parameters.

It seems that this new construct should be allowed only for function results.

----

Does the definition of "immutably limited" need updating?
Even if it doesn't strictly need it, would it still be a good idea?

Certainly "mode conformant" would need updating.
I understand that the wording section included "** Rest TBD **".
I'm just mentioning this because it crossed my mind.

----

Does the definition of a limited type need to be updated to include an anonymous
access to subprogram type? If would be odd if such a type were a "limited access
type" but not a limited type. In plain English, it would be obvious that a
limited access type is (one kind of) a limited type. Does this need to be stated
explicitly?

----

I agree with your concern about whether the word "anonymous" should somehow be
included. It is already somewhat confusing that a discriminant of a named access
type is not an access discriminant. I'd like to avoid adding another such
construct.

----

We are really interested in the "no assignment" aspect of limitedness here, as
opposed to the "no equality comparison" aspect. Should some mechanism be added
to at least allow testing to see whether a given value is null?

----

Would there be problems with ".all" renames of limited access function results?

      X : T renames F (<some aggregate>).all;

It seems like the finalization of the aggregate would have to be deferred to
match the lifetime of the rename. This could get messy, as in

      Y : T renames
            F (G (<some aggregate>).all.Aliased_Component'Access).all;

The same applies to generic in-out mode parameters (of course).

Would the possibility of an outstanding access value cause the finalization of
an aggregate object or a function result object to be deferred?

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

From: Randy Brukardt
Sent: Wednesday, April  8, 2009  6:40 PM

> To solve the problem that the AI is trying to address, we do not need
> limited access types other than as parameter and function result
> types.

I don't buy this at all. Returning a bare limited access value is still too
unsafe to use with Ada.Containers. The entire point of allowing them in other
places is so that we could return records with component(s) of limited access
types. I don't have any other need for them, but I don't see how to support that
case without supporting other ways to define objects.

> I think there may be complexity
> without corresponding benefit in allowing them in other cases.

That's true. I seriously considered *only* allowing them as record components
(and the two other cases), but it just seemed way to arbitrary. And there
doesn't seem to be any added (definitional) cost to the other cases; record
components is the messy one.

> For example, a limited discriminant type sounds like a mess
> (membership testing implicitly involves equality testing).

Not sure why. Semantically, I think they are identical to the non-limited case
except for the accessibility when reading them.

> Wouldn't a limited access non-discriminant component type require much
> the same accessibility checks as an access discriminant?

Yes. Indeed, I was thinking that the most appropriate set of rules was to use
*exactly* the same checks, so that the added implementation cost is small. (And
there are far fewer cases for a normal component.)

> What prevents a dangling reference in this case?
>
>      type R is
>         limited record
>            F : limited access Some_Tagged_Type'Class;
>          end R;
>
>       type Ref is access R;
>       Ptr : Ref;
>
>       function Func (X : Some_Tagged_Type'Class) return
>         limited access Some_Tagged_Type'Class is
>       begin
>           return X'Access;
>       end Func;
>
>       procedure P
>           Y: Some_Extension;
>       begin
>           Ptr := new R'(F => Func (X => Y));
>       end P;

The accessibility of the component F is that of the object Ptr, while Func
returns a limited access which has infinite accessibility when reading. Thus,
this clearly fails an accessibility check; the allocator is illegal.

As I noted in the AI, it is weird but intended that the result of a function
returning a limited access function cannot be converted to a different limited
access type (via return or initialization) other than a limited access parameter
(and that's because the parameter allows everything).

> Similar problems seem to exist if the result type of the function is R
> and the function returns an aggregate.

Same reason, this would fail an accessibility check. The intent is that a
limited access type cannot be converted to *any* other access type unless it
allows *any* accessibility (only the various forms of access parameters
qualify).

> What would be the benefit of allowing
>       X : limited access T := ... ;
> ?
> Why would anyone want to do this?

No reason, just that it seems weird to allow them in most contexts but not one
or two specific ones. If we decide to allow *only* access parameters, access
results, and record components, then it would not be so weird.

> ----
>
> Do we really need limited access types as parameters?
> What would be the consequences of taking a program which makes use of
> such parameters and deleting the word "limited"
> from all of the parameter declarations?

It eliminates the runtime checks that are necessary for regular anonymous access
parameters. Since accessibility checks always fail for such types, no overhead
for such checks is needed. They make sense if all you want to do with the access
is dereference it, or pass it to another access parameter.

It should be noted that I'm trying to get the benefits of my "specified
accessibility" proposal here; it's not that critical to have them.

> You mention in the !proposal section that a function with a limited
> access result type wants to be able to return a limited access
> parameter, but this could be accomplished by allowing it to return any
> access parameter, whether limited or not. Thus, this does not provide
> a justification for limited access parameters.

Correct. See above.

> It seems that this new construct should be allowed only for function
> results.

No point, because it would be just as unsafe as the original. I would never
agree to allow them in the Ada.Containers packages, and thus there would be no
reason for defining it. (Note that I consider this safety issue a dead-body,
bang-the-shoe-on-the-table critical. The argument that deleting a component is
unlikely doesn't hold any water [note that I wrote something different in
AI-142-1, but I've since seen new examples that show that it is way too
dangerous to allow], especially in that in cases like vectors, it is the
deletion of *any* component [not just the component that you are holding onto
with the access] that could cause problems.

You have to have a mechanism for a call-back in order to make them safe -- the
idea is that there is an associated controlled record that causes the call-back
when it is finalized. The limited access ensures that the access value cannot
exist longer than the controlled object, so using it is safe. (And, as the
examples I put in the AI shows, they can be quite simple to write.)

As a practical matter, we *only* need limited access components in types that
are returned from functions, but that seems just too silly of a restriction to
invent.

If you have a better idea of how to get the needed call-back (to end the
"anti-tampering" checks when the access value is destroyed), or a better way to
make containers usage safe, please suggest it.

If you want to see the details of what I'm talking about, please look at the
!examples section of the proposal.

> ----
>
> Does the definition of "immutably limited" need updating?
> Even if it doesn't strictly need it, would it still be a good idea?

Probably, but mainly because these "limited" access types are not limited in the
traditional sense. They still can be assigned (such as in function results);
they're definitely not built-in-place types. As you've noted, you could use them
in aggregates; it is the accessibility check that makes them illegal, not the
assignment.

Indeed, I did think about using a word other than "limited" ("restricted" comes
to mind) because it seems too overloaded here, but at this stage using a new
keyword is not appealing so I didn't do it.

> Certainly "mode conformant" would need updating.
> I understand that the wording section included "** Rest TBD **".
> I'm just mentioning this because it crossed my mind.

Why? Surely *static matching* and *static compatibility* would need updating,
but that seems like about it to me (everything else would follow from that).

> ----
>
> Does the definition of a limited type need to be updated to include an
> anonymous access to subprogram type?
> If would be odd if such a type were a "limited access type"
> but not a limited type. In plain English, it would be obvious that a
> limited access type is (one kind of) a limited type.
> Does this need to be stated explicitly?

I don't think these are limited types. We specifically wanted to avoid defining
limited elementary types, and I surely did not do so. They're passed by copy and
not built-in-place. I don't see how we can define pass-by-copy on a limited
type, and don't really want to think about it.

These are really "restricted" access types, not limited in any traditional
sense. We might want to disallow using them as components in non-limited
composite types, but only because the assignment operation between the
components would be defined to fail accessibility. (I seem to have forgotten
this detail in my write-up.)

> ----
>
> I agree with your concern about whether the word "anonymous"
> should somehow be included. It is already somewhat confusing that a
> discriminant of a named access type is not an access discriminant. I'd
> like to avoid adding another such construct.

Yes, I truly dislike calling parameters of an anonymous access type "access
parameters", because a parameter of a named access type is an access parameter
in causal talk. It's annoying when official language terminology is contrary to
causal discussion.

> ----
>
> We are really interested in the "no assignment" aspect of limitedness
> here, as opposed to the "no equality comparison" aspect.
> Should some mechanism be added to at least allow testing to see
> whether a given value is null?

Actually, we're not interested in either. We're interested it preventing
conversions to other types via accessibility checks. As I said, a limited access
is not (formally) a limited type in my view.

In any case, these are anonymous access types, and as such, they match the "="
for anonymous access types in Standard. So clearly there would be an "="
operator (there isn't an inherited one in any case for anonymous access types).

> ----
>
> Would there be problems with ".all" renames of limited access function
> results?
>
>       X : T renames F (<some aggregate>).all;
>
> It seems like the finalization of the aggregate would have to be
> deferred to match the lifetime of the rename.

Yes, that is already the rule that I was proposing. The language already
requires the function result to have the lifetime of the renames, and the
proposal expands that to include all of the parameters.

> This could get messy, as in
>
>       Y : T renames
>             F (G (<some aggregate>).all.Aliased_Component'Access).all;
>
> The same applies to generic in-out mode parameters (of course).

The parameter itself would *always* have same master as the result object. Parts
of the component expression might have shorter masters (if G returned something
other than a limited access, for instance).

> Would the possibility of an outstanding access value cause the
> finalization of an aggregate object or a function result object to be
> deferred?

As I noted, when you rename a function result, that already changes the master
to effectively defer the finalization. I would extend that to the parameters of
a function as well if the function result has a (visible) part that is of a
limited access type. I think that the rule would be straightforward to define;
whether the implementation would be straightforward is an open question, of
course. Note when I say "parameters", I mean the actual parameter expressions of
whatever types. I don't think there is any value to trying to decide between
things that might be returned vs. ones that will never be returned.

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

From: Steve Baird
Sent: Wednesday, April  8, 2009  7:40 PM

> Probably, but mainly because these "limited" access types are not limited in
> the traditional sense. They still can be assigned (such as in function
> results);

It looks like I was confused.

I thought you were very deliberately not modifying 7.5(3/2-4/2):

  A type is limited if it is a descendant of one of the following:
    - a type with the reserved word limited ... in its definition;

and that when you said "limited" you meant "limited".

I'll reread everything, substituting "funky" for "limited".

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

From: Randy Brukardt
Sent: Wednesday, April  8, 2009  8:24 PM

> I thought you were very deliberately not modifying 7.5(3/2-4/2):
>
>   A type is limited if it is a descendant of one of the following:
>     - a type with the reserved word limited ... in its definition;
>
> and that when you said "limited" you meant "limited".

I thought that I had to explicitly say that something was a limited type in
order for it to be so; I never actually verified that (obviously). Sorry about
the confusion.

I modeled these on anonymous access-to-subprogram parameters, which clearly are
not formally limited, even though they act that way in practice. That's what I
understood that we were talking about.

My intent was that it was fine to assign *into* a limited access object (subject
various accessibility checks), but assigning out of them is banned. That's not
quite the classical definition of limited.

> I'll reread everything, substituting "funky" for "limited".

Keep in mind a couple of things that I noted from your previous e-mail:

* There does need to be a ban on including "limited access" as components of
  non-limited composite types. That's because an assignment of such an object
  would necessarily include an assignment of a component that would necessarily
  fail an accessibility check. So we have to ban such assignments, and I think
  the easiest way is just to require limited types only. (Initialization is OK,
  I think.) [The intended use of limited access components is only in limited
  types anyway.]

* Your concerns about the complexity of limited access components and the like
  surely is separate from whether these are "limited" or "restricted" or
  "funky". Do read my answers there to clarify what I had in mind.

* You asked about "X : limited access T := ... ;" earlier. Since you can assign
  this object multiple times, there might in fact be a use for it. With a
  renames, you could only hold one object, and can't change it due to other
  computation. I would suggest using "limited" anytime that you hold onto (in a
  block, for example) a pointer for dereferencing purposes only (another example
  of the principle of least privilege). Still, this is a minor use at best.


I did create this proposal in record time (I was worried about having it done
before the next call), so I probably missed some points -- it is really easy to
get wrapped up in a particular vision without getting it all down on paper.

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

From: Tucker Taft
Sent: Wednesday, April  8, 2009  8:34 PM

All limited types can be "assigned" in Ada 2005, but they can't be used in an
assignment *statement*. I presume that would still be true here, namely they
could only be assigned as part of initialization, and then never "re"-assigned
to some other value, making them very much like "in" parameters and
discriminants.  But I haven't seen Randy's proposal, so I am only guessing.  Did
you send it to everyone, Randy?  Maybe it hit the "spam" filter...

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

From: Randy Brukardt
Sent: Wednesday, April  8, 2009 10:15 PM

I surely did send to everyone - perhaps you didn't notice it as it was in the
thread about "action items". I'll send you another copy. I really (as I
explained to Steve) was planning to allow assignment (and conversion) *into*
such objects, but not in the other direction. Truly limited access types had
made people uncomfortable in the past, and I didn't think we wanted to revisit
that -- the discussion on the conference call the other day was solely about
treating them like anonymous access-to-subprogram parameters and using
accessibility to prevent trouble rather than actual limitedness. I don't think I
made that clear enough in the write-up (at least Steve didn't understand that).

It should be noted that I suspect that making them "really" limited would
probably actually work to solve the critical problem; it's just that the idea
has been proposed many times in the past and the idea of immutably limited
elementary types scared everyone into not considering it. So I didn't even try
to think about it, especially given the discussion on the conference call.

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

From: Tucker Taft
Sent: Wednesday, April  8, 2009  11:10 PM

I don't remember that "truly" limited access types made folks uncomfortable, and
in any case, there has been a *whole* lot of water over the dam since we last
considered that.  I am personally made much *more* uncomfortable by allowing
assignment statements that assign to these guys.  I think that is really asking
for trouble.  I think allowing them to be assigned exactly once, when created,
is adequate, and anything more is going to be too complicated.  If we disallow
assignment statements, then limited-access components can use the rules for
access discriminants in most cases.

Do you have some examples where assignment statements are actually needed?  I
really worry about assigning to a limited-access component of an OUT parameter.

I think we need more examples, in general, to really know the best way for these
guys to work.  I'll admit I am wondering whether we could just use access
discriminants whenever a component would be needed, and then we only need to
allow "limited" on parameters and function results. A record type with a single
access discriminant and no other components would seem to be something that
could be stuck into the middle of anything and accomplish pretty much the same
thing as a limited-access component.

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

From: Randy Brukardt
Sent: Wednesday, April  8, 2009  11:40 PM

...
> Do you have some examples where assignment statements are actually
> needed?  I really worry about assigning to a limited-access component
> of an OUT parameter.

No, I don't have any such examples. But then I don't understand the worries
about accessibility during the conference call. Why would we care if you can't
assign them???

> I think we need more examples, in general, to really know the best way
> for these guys to work.  I'll admit I am wondering whether we could
> just use access discriminants whenever a component would be needed,
> and then we only need to allow "limited" on parameters and function
> results.
> A record type with a single access discriminant and no other
> components would seem to be something that could be stuck into the
> middle of anything and accomplish pretty much the same thing as a
> limited-access component.

If we can do that, then we don't need them at all (or any other solution for
that matter), because the only workable solution for the existing containers
involves returning a limited access component in a limited controlled object.
(At least so far as I can work out.) I have no reason to actually use limited
access parameters (other than the slightly cheaper implementation, but probably
not worth it alone) or limited access returns (they're not safe enough for use
with the predefined containers or any of my other designs). I could imagine some
confused users wanting to use limited access returns directly, but it surely
isn't worth the definitional overhead if we can't use them in the one case that
matters (the predefined containers).

But then I'm confused: what prevents assigning an access discriminant value into
some other object (of some other access type) that lives longer than the object
with the discriminant?? I suppose accessibility might do it in some cases (I
really have little idea on what the accessibility rules for access discriminants
are in detail), but I can't imagine that would *always* work. For instance, we
would want to prevent taking 'Access of any part of the designated object.

And even if it does, we no longer have a simple reason to change the master of
the parameters to live as long as the result. I don't think we'd want to make
that change on all function calls -- it would make some programs use quite a bit
more memory. (A significant change would be rare, but that almost makes it
worse.) That change is also required to make useful accessors for
Ada.Containers, presuming we want the accessors to work for the new bounded
containers as well as the existing unbounded containers.

So I still think we need some syntax to specify this case, even if the
*semantics* of an access discriminant works. (Indeed, I think it does for
inbound usages, which is what matters.)

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

From: Tucker Taft
Sent: Thursday, April  9, 2009 11:10 AM

Can you explain why you can't return a "bare" limited access value, but instead
need to wrap it in a record, to accomplish your goal of giving r/w access to a
component of a container?

By the way, we have plenty of examples in our home-grown container packages
where we return an access value, and then have a stern comment saying "never
store this access value."  That is where I think a limited access result type
would be ideal. I don't understand why these wouldn't work for the
language-defined containers.

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

From: Steve Baird
Sent: Thursday, April  9, 2009  11:44 AM

I think Randy wants to return a record so that he can update the tampering state
of the container when the record is finalized.

Randy - is this right?

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

From: Randy Brukardt
Sent: Thursday, April  9, 2009  1:11 PM

That's correct. I'm writing a longer explanation in a reply to another message.

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

From: Tucker Taft
Sent: Thursday, April  9, 2009  11:59 AM

Yes, I realized that after I wrote my answer.  I suppose the goal is to have a
controlled record wrapping a *visible* [limited] access value that can't be
changed or copied.  It certainly feels round-a-bout.

I *think* an access discriminant of a [limited] controlled object would already
do the trick.  The access discriminant can't be copied to a place that would
outlive the enclosing controlled object, so when the enclosing controlled object
gets finalized, it can safely know there are no remaining references, and
reenable tampering (or more precisely, decrement the "no tampering" count).

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

From: Steve Baird
Sent: Thursday, April  9, 2009 12:17 PM

> I *think* an access discriminant of a [limited] controlled object
> would already do the trick.  The access discriminant can't be copied
> to a place that would outlive the enclosing controlled object, so when
> the enclosing controlled object gets finalized, it can safely know
> there are no remaining references, and reenable tampering (or more
> precisely, decrement the "no tampering" count).

I was also thinking about that approach.

I think (Randy - correct me if I am misstating your position) the problem is
that you'd have to use Unchecked_Access to implement something like

     type Element_Handle (D : access Element) is tagged limited private;
     -- controlled

     function Handle (C : Container; I : Index) return Element_Handle is
     begin
         return Element_Handle (D => C.Elements(I)'Access, ... );
     end Handle;

Randy is trying to come up with a mechanism which will somehow allow a function
result to safely contain references to the actual parameters of the function.

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

From: Tucker Taft
Sent: Thursday, April  9, 2009 12:55 PM

My experience with containers is that they pretty much always involve a level of
indirection, so the actual elements are always stored in the heap, and your
'Access would succeed.  But if not, then I agree we need to add Randy's extra
"level" where we can take 'Access of a part of a parameter, and use it to
initialize an access discriminant of a build-in-place object.

Note that in your example, if there is no level of indirection in getting to
"C.Elements", either the parameter mode of the Container would need to be IN OUT
(;-), or be an access parameter, or the access discriminant would need to be
"access constant Element."

I realize I may be revisiting a heavily trodden path here, but I just want to be
sure that whatever we propose (or already have) really solves the problem.

Thanks for your indulgence,

P.S. By the way, I was contemplating whether the rules against multiple access
we are contemplating for function IN-OUT parameters, might have a role to play
here in preventing passing the container as an in-out parameter (allowing it to
be tampered), in the same "context" where we have another "live" reference to
some part of the container. (This is a *very* long shot, I suspect.) -TT

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

From: Steve Baird
Sent: Thursday, April  9, 2009  1:17 PM

> My experience with containers is that they pretty much always involve
> a level of indirection, so the actual elements are always stored in
> the heap, and your 'Access would succeed.

Agreed, except possibly for in the cases of the bounded containers.

> But if not,
> then I agree we need to add Randy's extra "level"
> where we can take 'Access of a part of a parameter, and use it to
> initialize an access discriminant of a build-in-place object.

If the existing language works in the cases we care
about (and bounded containers need to be discussed
here), then perhaps we don't need to do anything.

Randy - what say you?

> Note that in your example, if there is no level of
> indirection in getting to "C.Elements", either the
> parameter mode of the Container would need to be
> IN OUT (;-), or be an access parameter, or the
> access discriminant would need to be "access constant Element."
>

True.

> I realize I may be revisiting a heavily trodden path
> here, but I just want to be sure that whatever we
> propose (or already have) really solves the problem.
>

Agreed on all points.

> Thanks for your indulgence,
>
> P.S. By the way, I was contemplating whether the rules against
> multiple access we are contemplating for function IN-OUT
> parameters, might have a role to play here in preventing
> passing the container as an in-out parameter (allowing
> it to be tampered), in the same "context" where we have
> another "live" reference to some part of the container.
> (This is a *very* long shot, I suspect.) -TT

This issue will, I'm sure, be given the priority that it deserves.

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

From: Randy Brukardt
Sent: Thursday, April  9, 2009  1:50 PM

> My experience with containers is that they pretty much always involve
> a level of indirection, so the actual elements are always stored in
> the heap, and your 'Access would succeed.

That's definitely *not* the intended implementation model for the bounded
containers. And surely anything that we define has to work for the bounded
containers.

> But if not, then I agree we need to add Randy's extra "level"
> where we can take 'Access of a part of a parameter, and use it to
> initialize an access discriminant of a build-in-place object.

OK, good. But note that also implies changing the master of the parameters
(which matters if the parameter is a function result, for instance). And I don't
think that is something we want to do generally.

> Note that in your example, if there is no level of indirection in
> getting to "C.Elements", either the parameter mode of the Container
> would need to be IN OUT (;-), or be an access parameter, or the access
> discriminant would need to be "access constant Element."

Correct, the container parameters would have to be "in out" or "access". That's
already noted in the proposal.

> I realize I may be revisiting a heavily trodden path here, but I just
> want to be sure that whatever we propose (or already have) really
> solves the problem.

Correct.

> Thanks for your indulgence,

:-)

> P.S. By the way, I was contemplating whether the rules against
> multiple access we are contemplating for function IN-OUT parameters,
> might have a role to play here in preventing passing the container as
> an in-out parameter (allowing it to be tampered), in the same
> "context" where we have another "live" reference to some part of the
> container.
> (This is a *very* long shot, I suspect.) -TT

The answer is no. It wouldn't help with globally available containers.

In another message, Tucker wrote:

> By the way, we have plenty of examples in our home-grown container
> packages where we return an access value, and then have a stern
> comment saying "never store this access value."

That's insufficient to avoid problems (although it clearly reduces them).

Let me show you the worst possible case (at least that I've thought of to date)
that shows the need for a tampering check that is tightly associated with the
access value:

Assume that Ada.Containers.Vectors has an accessor function defined as follows:

function Accessor (C : in Vector; I : Index_Type) return limited access Element_Type;

[Drop the "limited" if you like; it helps but doesn't solve everything and has
nothing to do with this example.]

Now consider the following program fragments:

  package Data is
    type Node (D : Boolean := True) is record
          case D is
              when True =>
                  CT : Natural;
              when False =>
                  CF : Float;
          end case;
    end record;
    subtype True_Node  is Node (D => True);
    subtype False_Node is Node (D => False);

    package Node_Vector is new Ada.Containers.Vectors (Element_Type => Node,
           Index_Type => Positive);

    Node_List : Node_Vector.Vector;
  end Data;

  with Data;
  package Far_Away is
    procedure Some_Complex_Operation (...);
  end Far_Away;

  package body Far_Away is
    procedure Some_Complex_Operation (...) is
        Some_Index : Positive;
    begin
        ...
        Some_Index := <lengthy-computation-resulting-in-value-of-1>;
        ...
        Data.Node_List.Delete (Some_Index);
        ...
    end Some_Complex_Operation;
  end Far_Away;

  with Data;
  package Process is
    procedure Process_True (NT : in out Data.True_Node);
  end Process;

  with Far_Away;
  package body Process is
    procedure Process_True (NT : in out Data.True_Node) is
       Component : renames NT.CT; -- OK, NT is constrained.
    begin
       ...
       Far_Away.Some_Complex_Operation (...);
       if Component > 10 then -- ** Oh-oh!! Component has moved.
          ...
       end if;
    end Process_True;
  end Process;

  with Data, Process;
  procedure Main is
  begin
      Data.Node_List.Append (New_Item => (D => False, CF => 1.0)); -- Element 1.
      Data.Node_List.Append (New_Item => (D => True, CT => 4)); -- Element 2.
      Data.Node_List.Append (New_Item => (D => False, CF => 26.5)); -- Element 3.
      ...
      Process.Process_True (Data.Node_List.Accessor (2).all);
      ...
  end Main;

Now, in the canonical implementation for a vector, all of the data is stored in
an array of some size, and when a component is deleted, the rest of them slide
down. So, when the Far_Away operation (probably called through many levels of
calls) deletes the first element of the vector, all of the rest of them move.
That means that the second element passed to Process_True unexpectedly changes
its value to that which was in the third element. But that element doesn't match
the constraint and doesn't even have a CT component! So we get erroneous
behavior.

Keep in mind that no one has copied this access value at all. So a rule (or
"stern warning") preventing copying does no good. You can also get a similar
effect by renaming the access value itself and doing bad things while the
renames exists.

Note that the erroneousness can't happen if you use Update_Element instead,
because the tampering check will cause the deletion to raise Program_Error.
Similarly, cursors have rules allowing implementations to detect this sliding
and raise Program_Error. But this raw access can have no such check. Thus, I
conclude that a raw access is simply not enough here.

Of course, there are many lesser problems caused by similar scenarios; simply
having the element value change unexpectedly is unlikely to produce good
results.

This is surely a possible scenario. The record type is very similar to the one
used in the Janus/Ada symboltable, for instance (other than it is a lot
smaller!). I could easily imagine using a bounded multiway tree to hold such a
record, and having the other conditions hold as well.

Indeed, the bare access code that is actually used in Janus/Ada has run into
this exact scenario on a couple of occasions. It is incredibly hard to debug,
given the wide distances between the code that causes the trouble and the code
that fails. Indeed, we never figured out some of those bugs; we "solved" the
problem by eliminating the deletions of nodes completely once we moved to the
"massive" 256K memory of the original IBM PC, just marking them as inactive
instead. (We only had 48K for everything on the original CP/M compilers, so
keeping "dead" nodes was not an option.)

As I've said before, exactly how we solve this problem is not a big deal to me,
but I do think we need to solve it and we can't sacrifice safety to do it.

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

From: Randy Brukardt
Sent: Thursday, April  9, 2009  5:00 PM

In order to possibly make a second cut at this proposal, let me go over some
important areas that seem to have avoided discussion so far.

First, Tucker seems to be oblivious to the fact that "call-level" accessibility
as I originally defined it doesn't work. The problem is that the parameters of a
call in general have a different (shorter-living) master than the result of a
call. That means that returning part of a parameter can be a problem if the
parameter has been finalized.

For instance, consider this version of the example from the AI of a vector of
vectors:

     Obj : limited access Integer renames
           MV.Modifiable_Element(0).Modifiable_Element(0);

In this call, the master of the final return object (the limited access) is that
of the renames. But the master of the prefix MV.Modifiable_Element(0) is much
shorter; it will be finalized well before the renames is fully elaborated. That
would cause any tampering lock to be released prematurely.

The problem is worse if someone uses a read-only accessor (which could make
sense for large objects that you don't want to copy). If we have:

     function Constructor (...) return Big_Object;

     function Selector (O : in Big_Object) return limited access constant Small_Part;

where Selector returns part of Big_Object, then if we have:

     Small : Small_Part renames Constructor.Selector.all;

Constructor will be finalized before Small could even be referenced, meaning the
object would simply not exist. That is surely bad. While I think examples like
this latter one shouldn't be common in practice, the danger is too severe to
ignore.

This problem is easily solved by changing the master of the parameters for such
function calls to be the same as the return object. The problem is that if we
don't have a syntactic marker as to when to make such a change, we would have to
do it for some much less obvious scenario, such as for all functions, or maybe
for all functions returning an object with an access discriminant or an access
result. And then we also have to use call-level accessibility only for functions
that meet whatever the requirement is. That's clearly easier by allowing
call-level accessibility rules to only apply to limited access types (or
whatever we use in their place).

---

Upon further reflection, we surely do need special,
anonymous-access-to-subprogram-parameter-like accessibility for limited access
types if we in fact have them. Otherwise, I don't see how we could prevent
conversions to other types at the point of the call, or taking of 'Access for
aliased components. Limitedness would only prevent assignment, not conversions.

---

While I don't see any major problem with the semantics I originally defined, I
don't see any major need either. So I would be happy to make limited access
types limited. That would simplify the proposal somewhat.

---

There doesn't appear to be any semantic difference between a limited access
discriminant and a "regular" one, other than the different accessibility upon
reading. I'd expect to define it that way (if it is defined at all).

---

In view of these issues, I see three reasonable alternatives to write up.
Which should I follow:

(1) Allow limited access types anywhere that anonymous access types are
    (presuming that a limited type is OK in that context). This has the
    advantage of consistency. But many of the possible places aren't that
    useful.

(2) Allow limited access types only in access parameters, results, and in
    components.

(3) Allow limited access types only in access parameters, results, and in
    discriminants. This seems nice for some reason, probably because it pretty
    much matches what Ada 95 allowed for anonymous access types.

Well, of course there is a fourth idea, don't do anything at all. I hope we're
not to that point yet.

Thoughts??

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

From: Tucker Taft
Sent: Thursday, April  9, 2009  10:09 PM

...
> First, Tucker seems to be oblivious to the fact that "call-level"
> accessibility as I originally defined it doesn't work. The problem is
> that the parameters of a call in general have a different
> (shorter-living) master than the result of a call. That means that
> returning part of a parameter can be a problem if the parameter has been finalized.

Clearly we would have to fix this for cases where the result might designate
part of a parameter.

...
> This problem is easily solved by changing the master of the parameters
> for such function calls to be the same as the return object. The
> problem is that if we don't have a syntactic marker as to when to make
> such a change, we would have to do it for some much less obvious
> scenario, such as for all functions, or maybe for all functions
> returning an object with an access discriminant or an access result.
> And then we also have to use call-level accessibility only for functions that meet whatever the requirement is.
> That's clearly easier by allowing call-level accessibility rules to
> only apply to limited access types (or whatever we use in their place).

I see your point.  I don't see a major issue with having the finalization of
parameters with an aliased part or coextension deferred until the result object,
if there is a chance that some part or coextension of the result might designate
the aliased part/coextension.  My guess is that many compilers might do that
anyway. ;-)

...
> In view of these issues, I see three reasonable alternatives to write up.
> Which should I follow:
>
> (1) Allow limited access types anywhere that anonymous access types
> are (presuming that a limited type is OK in that context). This has
> the advantage of consistency. But many of the possible places aren't
> that useful.
>
> (2) Allow limited access types only in access parameters, results, and
> in components.
>
> (3) Allow limited access types only in access parameters, results, and
> in discriminants. This seems nice for some reason, probably because it
> pretty much matches what Ada 95 allowed for anonymous access types.
>
> Well, of course there is a fourth idea, don't do anything at all. I
> hope we're not to that point yet.
>
> Thoughts??

I think adding "call-level" accessibility is a smaller change, and is relatively
intuitive, so I would rather do that than add a whole "limited access type"
concept.

[Editor's note: This thread continues in AI05-0142-3, which uses some of these
ideas to come up with a smaller solution.]

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

Questions? Ask the ACAA Technical Agent