Version 1.1 of ais/ai-00363.txt

Unformatted version of ais/ai-00363.txt version 1.1
Other versions for file ais/ai-00363.txt

!standard xx.xx(xx)          03-11-26 AI95-00363/01
!class amendment 03-11-26
!status work item 03-11-26
!status received 03-11-15
!priority High
!difficulty Hard
!subject Eliminating access subtype problems
!summary
Those ^&**^$% access subtypes gotta go.
!problem
[Editor's note: this is not a full write-up.]
!proposal
Disallow the discriminant constraint on a general access type if the discriminants have defaults on the designated type, recheck in an instance and presume the worst in a generic body.
We must disallow constraints on all access types declared outside the package defining the private type (presuming the private view has no visible discriminants), allocating space for unconstrained objects in the heap even when constraints are given in the allocator for such an access type, and setting the 'Constrained attribute False when dereferencing values of such an access type.
If the access type is a general access type, then this "new" semantics for allocators should apply to access types declared inside the package as well, presuming the designated subtype is not constrained, since subtypes would be disallowed on the "inside" general access types as well. Furthermore, there would need to be rules disallowing a 'Access that delivers a value of such a type being applied to a constrained variable (3.7.2(27)), and disallowing conversion from some other access type that had constrained designated objects (4.6(16)). Both 3.7.2(27) and 4.6(16) would then say "discriminated and indefinite" rather than "discriminated and unconstrained."
!wording
!discussion
!example
--!corrigendum
!ACATS test
ACATS test(s) should be constructed to check these changes.
!appendix

From: Tucker Taft
Sent: Saturday, November 15, 2003  5:30 PM

Along with Pascal, I was assigned homework to study the problem of access
subtypes.  Here are my thoughts:

First, let's identify the basic problem and sequence of
piecemeal solutions:

-------------------------------------------------------
---- the problem and current solution attempts --------
-------------------------------------------------------

Normally when a discriminated type has defaults, objects of
the unconstrained subtype allow their discriminants to
change as part of a whole-object assignment.

Unfortunately, the *possibility* of constrained access subtypes
means that objects that might be pointed-to by a value of
such a subtype must be constrained, since the constraint check
associated with such a subtype is performed only on assignment
and parameter passing, not on use.

When we added general access types and objects explicitly declared "aliased"
in Ada 95, we permitted access subtypes on these types as well, and
generalized what was originally a requirement only on objects created by
allocators, to apply to all aliased objects, whether declared or
the result of an allocator, as well as all aliased components.

This has created a stream of headaches, particularly related to
aliased components, because composite assignments and view
conversions generally do no per-component constraint checks, so
extra care needs to be used to prevent the possibility of
discriminants changing due to operations on the enclosing
object.  This has led to 3.6(11) which requires aliased components
of non-limited types to be constrained.  However, if the type
is private and its discriminants are only visible in the private part,
then 3.6(11) provides no protection, so 3.7.1(7) was created to
disallow general access subtypes of types whose partial view
lacks discriminants, while the full view has defaulted discriminants.

Furthermore, view conversions of limited array types could
create a problem, if the target and source type differed
in whether the components were aliased, since in one view
the discriminants were mutable, and in the other they weren't.
This led to AI-168 and changes to 4.6(12).

Generic formal (limited) array types create similar problems,
if the actual and formal differ on component aliasing, for essentially
the same reason.  12.5.3(8) worries about the case in one direction,
and AI-275 was created to worry about the case in the other direction.

It was also noticed that 3.6(11) does not provide protection for
generic formal private types, since the actual might have
defaulted discriminants, so AI-295 was created to deal with this
situation, essentially requiring 3.6(11) to be enforced in the
private part of the instance, and enforced in the body of the
instance by a (pseudo) run-time check.

Another somewhat unrelated problem, which was exacerbated by
an Ada 95 change, is that if a private type is implemented
with a full type with defaulted discriminants, then the
implementation might work great in tests that don't put
such objects in the heap, but would suddenly start failing
if a user of the private type decided to allocate objects
of the type in the heap.  This was exacerbated in Ada 95
because we made it easier for a private type with no
visible discriminant part to be implemented with a
discriminant-with-defaults full type.  This was possible
in Ada 83, but required jumping through a few extra hoops.

I know about this problem because a few years ago it hit
some of our customers, because we had implemented Unbounded_Strings
with a variant record, one variant for "short" strings, and
one variant for longer strings.  It is easy enough to solve
by wrapping the full type in a record, but it seems a clear
violation of the "spirit" of privateness, namely that objects
of a private type with no visible discriminants should suddenly
behave differently when clients of the type happen to allocate
them in the heap.

It seems clear that fixing this problem would require allowing
unconstrained heap objects to be created, at least when using
an access type declared outside the scope of the full type,
and disallowing access subtypes of such an access type, even
if the type is pool-specific.  Of course these access subtypes
would be a bit weird, since they would have to be declared
somewhere within the scope of the full type to be able to
see the discriminants, even though the main access type were
declared outside the scope of the full type.

------------------------------------------
---- So where do we go from here? --------
------------------------------------------

The "fix" in 3.7.1(7) which disallows general access subtypes
in certain cases is a bit frustrating, in that it is really just
an attempt to patch a hole in 3.6(11), and doesn't really help
with the problem I described above about private types that stop
working for heap objects.  Unless you know that there are *no*
access subtypes, whether pool-specific or general, can you
start expecting an allocator for a private type with no visible
discriminants to create an object that "works" the same way
as a declared object or component having the same type.
So personally, I would put some priority on finding a
solution that fixes this privacy-breaking situation.
(I'll come back to this later -- see below.)

In addition, the language rules are excessively complicated due to the
possibility of general access subtypes designating types, private or
not, that have defaulted discriminants.  The rules would be significantly
simplified if declared objects and components behave the same whether
aliased or not, as far as constrained-ness.  For these cases,
we are only worried about general access subtypes, since only
general access types can designate (aliased) declared objects or components.

In the discussion of AI-295, one of the alternatives considered was
disallowing creating access subtypes of general access types, perhaps
only if the designated type has defaulted discriminants.  However, this was
felt to be a contract model problem, since a formal *pool-specific*
access type can be associated with an actual *general* access type.
A related problem is that a formal type without defaults can
be associated with an actual type that has defaults.
Let's look at this problem in more detail:

The contemplated rule (in place of the existing rule in 3.7.1(7)) is:
  A discriminant constraint is not permitted on a general access type
  if the designated type has defaulted discriminants.

To enforce this rule in the presence of generics, we would clearly
need to check it in instances, including in the private part.
In the body, we would have to disallow a constraint on any access type
that *might* have this property, or do it as a run-time check.
None of these rules seem too bad, since the likelihood of declaring
access subtypes inside a generic body are quite slim, and if
subtypes of some access type are really going to be used widely, it may be
reasonable to omit the defaults on the discriminants to begin with.

One other alternative to consider is to allow such access subtypes
(perhaps only in generic bodies), but define their semantics to
be that the checks are, as now, made on assignment and parameter passing,
but that, in addition, discriminant checks are also required on use.  On use,
the implementation would be permitted to check for an exact
match to the associated discriminant constraint, or to perform
just the "normal" discriminant checks associated with referencing
discriminant-dependent components.

In fact, probably no wording changes related to discriminant checks
would necessarily be required to adopt this "alternative" semantic model
(other than perhaps to permit the "fiercer" check), since discriminant
checks are defined to occur on every use already, and it is just
logical inference which allows compilers to currently omit the checks
when dereferencing a value of a constrained access subtype.
This logical inference would no longer be true in all cases, if
we permit aliased declared objects and components to have mutable
discriminants.

Clearly disallowing the access subtypes themselves is preferable
from the point of view of doing as much checking as possible at
compile-time, but the "altered" semantic model would actually be a
bit more upward compatible, since existing exception-free code
would remain legal and exception-free.  Code that is currently
raising exceptions might raise them at different places, namely
on use of an access value, rather than on an attempt to change
the discriminants via assignment.

My recommendation?

Disallow the discriminant constraint on a general
access type if the discriminants have defaults on the
designated type, recheck in an instance and presume the worst
in a generic body.  I think this is quite reasonable since
declaring access subtypes is rare to begin with, even rarer when
the designated type's discriminants have defaults, rarer still
in a package body, and probably exceedingly rare in a generic package body.
(If by chance some upward incompatilibility does arise, two "workarounds"
are to eliminate the defaults on the discriminants (if not really needed),
or eliminate the constraints on the declaration of the access subtypes.
Exception-free code should continue to run exception-free.)

This change would allow us to eliminate 3.6(11), and the various
attempts to "patch" it via AI-168 for 4.6(12), AI-275 for 12.5.3(8),
and AI-295 for generics.  Aliased components would no longer
have to be constrained; we could independently decide whether
standalone aliased objects declared with defaulted discriminants would
be constrained by their initial value, though there doesn't seem much
remaining justification for making them so.

-----------------------------------------------------
---- fixing privacy-breaking constrainedness --------
-----------------------------------------------------

As mentioned above, an existing error-prone situation
is that a private type that works perfectly well for
non-aliased objects, starts failing on objects declared
aliased or allocated in the heap.  The "aliased" part could
be fixed by the above change, but the heap-allocated problem is
not fixed by disallowing constraints on certain general access subtypes.

To fix this problem, we must disallow constraints on *all* access types
declared outside the package defining the private type (presuming the
private view has no visible discriminants), allocating
space for unconstrained objects in the heap even when
constraints are given in the allocator for such an access type,
and setting the 'Constrained attribute False when dereferencing
values of such an access type.  Essentially it makes an allocator
for such an access type equivalent to declaring a variable
of the (private) type, which is always going to have mutable
(non-visible) discriminants.

In fact, I believe that constraints are *already* disallowed on
such an access type, even when inside the scope of the full
type of the designated type, because according to 3.10(14), such
an access type is constrained from the get-go.  This is because
at the point of its definition, and throughout its immediate scope,
its designated type never has any discriminants, so there is
nothing to constrain.  As usual, even if some client
knows more than what is known where the access type is declared,
it can't take advantage of that.

So the real change is to make the semantics of allocators (see 4.8(6)) match
this model as well.  Allocators for such an access type should
create unconstrained objects, even if the place where the allocator
takes place might "know" that the designated type has discriminants,
and might use a constrained subtype in the allocator.

If the access type is a general access type, and we presume the "other"
change proposed above is adopted, then this "new" semantics for allocators
should probably apply to access types declared inside the package
as well, presuming the designated subtype is not constrained,
since subtypes would be disallowed on the "inside" general access types
as well.  Furthermore, there would need to be rules disallowing
a 'Access that delivers a value of such a type being applied to a
constrained variable (3.7.2(27)), and disallowing conversion from some other
access type that had constrained designated objects (4.6(16)).
Both 3.7.2(27) and 4.6(16) would then say "discriminated and indefinite"
rather than "discriminated and unconstrained."

I think this further change would be a "good" thing, as it provides
significant improvements to the privacy model, and it complements
the rule suggested which applies only to general access types.

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

From: Pascal Leroy
Sent: Monday, November 24, 2003  9:17 AM

Thank you for writing this up, and sorry for the late answer.

I very much like your proposal, in particular the notion of fixing the
privacy-breaking oddity where objects of a private type work differently
depending on whether they are allocated on the heap on declared as normal
variables/constants.

I think that change #1 (discriminant constraints for general access types) is
fairly uncontroversial as this area of the language is quite muddled, so it's
only going to break code which was only working by a fluke.  And the effect of
the change will be to make existing code illegal.

On the other hand change #2 (the privacy-breaking problem) is more dicey as it
introduces a performance incompatibility: imagine a unit that was allocating
1,000,000 objects of 10 bytes, but would occasionally allocate an object of
1,000,000 bytes.  Suddenly it's going to allocate 1,000,000 objects of
1,000,000 bytes.

I am still willing to swallow this incompatibility given the headaches that
it's going to solve, but it's definitely going to be a contentious proposal.

I will include a summary of your proposal in my next WG9 report.  In all
likelihood it will go back to the ARG for further analysis.

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

From: Tucker Taft
Sent: Monday, November 24, 2003  1:41 PM

> ...
>
> I think that change #1 (discriminant constraints for general access types) is
> fairly uncontroversial as this area of the language is quite muddled, so it's
> only going to break code which was only working by a fluke.  And the effect
> of the change will be to make existing code illegal.

Once I sat down and began to piece together the set of patches we made to
try to fix the problems with 3.6(11), I was somewhat appalled.  I hope
we can at least get consensus on this part of the proposal.

>
> On the other hand change #2 (the privacy-breaking problem) is more dicey as
> it introduces a performance incompatibility: imagine a unit that was
> allocating 1,000,000 objects of 10 bytes, but would occasionally allocate an
> object of 1,000,000 bytes.  Suddenly it's going to allocate 1,000,000 objects
> of 1,000,000 bytes.

Yes, that is an issue.

I see this problem as relatively rare, since from the outside view, this type
only comes in one size, and all local or global variables are going to be the
"max" size (for most run-time models).  There are also a couple of potential
workarounds:

  1) Declare the (full and private) type limited -- I would presume only
     nonlimited types would have unconstrained instances in the heap.
  2) Put discriminants, known or unknown, on the private type declaration.

> I am still willing to swallow this incompatibility given the headaches that
> it's going to solve, but it's definitely going to be a contentious proposal.

Probably...

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

Questions? Ask the ACAA Technical Agent