Version 1.16 of ais/ai-00168.txt

Unformatted version of ais/ai-00168.txt version 1.16
Other versions for file ais/ai-00168.txt

!standard 04.06 (12)          00-09-25 AI95-00168/09
!standard 03.07.01 (07)
!class binding interpretation 96-11-16
!status Corrigendum 2000 99-07-27
!status WG9 approved 99-06-12
!status ARG Approved 7-0-0 98-10-09
!status work item 98-04-04
!status received 96-11-16
!priority Medium
!difficulty Easy
!qualifier Error
!subject Aliased objects cannot have discriminants modified
!summary
A view conversion of an array object is illegal if the target subtype and the operand do not have both aliased components or both non-aliased components.
A discriminant constraint for a general access type is illegal if there are places where the designated subtype appears constrained and others where it appears unconstrained.
!question
Consider the following code fragment:
package P is pragma Elaborate_Body; type T is private; A : constant T; private type T (D : Integer := 0) is null record; type Ptr is access all T; A : constant T := (D => 1); end P;
with P; package Q is type A1 is array (1 .. 10) of aliased P.T; type A2 is array (1 .. 10) of P.T; X : A1; end Q;
with P, Q; procedure R is procedure S (Y : in out Q.A2) is begin Y (1) := P.A; end; begin S (Q.A2 (Q.X)); -- This call will change the discriminant of Q.X (1) end;
This example illustrates a case where it is possible to change the discriminant of an aliased component of an object, which is supposed to be forbidden.
!recommendation
(See wording.)
!wording
Add the following clause after 4.6(12):
In a view conversion for an array type, the target type and the operand type shall either both have aliased components, or both have non-aliased components.
Add the following sentence at the end of 3.7.1(7):
However, in the case of a general access subtype, a discriminant_constraint is illegal if there are places within the immediate scope of the designated subtype where it appears to be a constrained subtype.
!discussion
The problem (1) comes from the fact that it is possible to use a view conversion to convert an array object with aliased components to an array type with non-aliased components. Such a conversion must be disallowed.
The ARG also discussed the following example, which illustrates another case where the standard seems to allow a discriminant to be changed:
with Q; package body P is PT : Ptr (0) := Q.X (1)'access; begin Q.X := (others => (D => 2)); -- Changes the discriminant of Q.X (2) end P;
The root of problem (2) is that there are places (e.g., the visible part of P) where P.T is constrained, but other places (e.g., the private part and body of P) where P.T is unconstrained. This causes privacy problems when applying the following rule:
"if a component_definition contains the reserved word aliased and the type of the component is discriminated, then the nominal subtype of the component shall be constrained." (3.6(11))
Also note that the problem exists with non-private types, provided that the characteristic that the type is unconstrained is not visible everywhere:
package P.C is type NT is new T; private type Ptr is access all NT; -- Causes the same problems as P.Ptr. end P.C;
One way to fix this problem would be to require a component-by-component check on the assignment to Q.X, but that would be very expensive. Moreover, a compile-time check would clearly be better than a run-time check.
Aliasedness of the components is not really what is causing trouble, though. It is really the existence of a general access type, and in fact of a discriminant constraint on such an access type, which causes trouble. Thus, forbidding such a constraint is the chosen solution, especially considering that constraints on access types are not a terribly useful feature.
!corrigendum 3.07.01(7)
Replace the paragraph:
A discriminant_constraint is only allowed in a subtype_indication whose subtype_mark denotes either an unconstrained discriminated subtype, or an unconstrained access subtype whose designated subtype is an unconstrained discriminated subtype.
by:
A discriminant_constraint is only allowed in a subtype_indication whose subtype_mark denotes either an unconstrained discriminated subtype, or an unconstrained access subtype whose designated subtype is an unconstrained discriminated subtype. However, in the case of a general access subtype, a discriminant_constraint is illegal if there is a place within the immediate scope of the designated subtype where the designated subtype's view is constrained.
!corrigendum 4.06(11)
Replace the paragraph:
by:
!corrigendum 4.06(12)
Replace the paragraph:
by:
!ACATS test
Test B371001 checks that a discriminant_constraint of a general access type is illegal if there is a place within the immediate scope of the designated subtype where the designated subtype's view is constrained. (Test, 8-0-0, ARG Letter Ballot, February 2001).
Test B460005 checks that in a view conversion, the target and operand array's components may not have different aliasing (that is, if one has aliased components, the other must have aliased components, and vice-versa). (Test, 8-0-0, ARG Letter Ballot, February 2001).
!appendix

!section 4.6(00)
!subject Aliased objects can have discriminants modified
!reference RM95-4.6
!reference RM95-6.4.1 (16,17)
!from Stephen Michell 96-10-12
!keywords constrained object aliased change of discriminat
!reference 96-5720.a Steve Michell 96-10-12>>
!discussion

Consider the following code fragment

       procedure acc_cvt2 is
         package P is
           type T is private;
           a: constant T;
           b: constant T;
         private
           type T( X: integer := 0 ) is null record;
           a: constant T := ( X => 1 );
           b: constant T := ( X => 2 );
         end P;
         type A is array( 1 .. 10) of aliased P.T;
         type B is array( 1 .. 10) of P.T;
         X : A := (1..10 => P.a);
         procedure Q( Y : in out B ) is
         begin
           Y(1) := P.B;
         end Q;
       begin
         Q(B(X));
       end ACC_CVT2;

Object X is constrained because it is an array of aliased records, even
though unaliased objects of such a type would be unconstrained. The call
Q(B(X)) is a view conversion by 4.6(5). The remaining rules in 4.6
do not appear to cover the case shown above, but it appears that a view
conversion between an unconstrained and constrained view of an object
should be illegal.

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

From the editor:

While reviewing the corrigendum wording at the September 1999 meeting,
Robert Eachus pointed out the problem of 3.7.1(7) can also happen for derived
types where additional visibility appears at a later point:

   package P is
     type T is private
   private
     type T (D: integer := _) is
   end P;

   package P.Child is
      type NT is new P.T;
      type ANT is access all NT;
   private
      subtype SANT is ANT (1); -- Illegal, we hope!
   end P.Child;

The wording and AI will need to be updated to reflect this.

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

From: Tucker Taft
Sent: Wednesday, October 6, 1999

I have been going back and forth in my mind whether
an aliased object whose type is private without discriminants,
but whose full view has defaulted discriminants, is constrained
by its initial value.  E.g.:

   procedure test is
       package p is
           type T is private;
           T_True : constant T;
       private
           type T(D : Boolean := False) is record
               case D is
                   when True => String(1..10);
                   when False => null;
               end case;
           end record;
           T_True : constant T := (D => True, (others => 'x'));
       end;
       X : aliased P.T;  -- Default initialized with D => False;
                         -- is it constrained?
   begin
       X := T_True;  -- Constraint_Error?
   end;

3.3.1(9) indicates that an object is constrained by its initial value
if it is aliased and has an unconstrained nominal subtype.
X has a constrained nominal subtype, since T has no discriminants
at the point where X is declared.  So are we allowed to change its
discriminant by a whole-object assignment?

Note that this is related to the following question:

Is the object created by an allocator for an access-to-T type
constrained by its initial value?

The "aliased" keyword didn't exist in Ada 83, but clearly what we
now call "aliased" objects did, namely those objects created by
allocators.  The reason for forcing objects in the heap with discriminants
to be constrained is the possibility of access subtypes, where
a value might be set to point to the object when the discriminant
has one value, and then, if unconstrained, might have its discriminant
changed in a way inconsistent with the access subtype.

Note that (Ada95) AI-168 deals with a similar case, but makes
the presumption that even if the partial view lacks discriminants,
an aliased object is constrained by its initial value, but an
aliased component might nevertheless have its discriminant changed
by an assignment to the enclosing object.  We seem to be presuming
that a direct assignment to the component would raise Constraint_Error,
even though the private type makes no indication of any discriminants.

So based on the AI-168 writeup, I guess the answer to my
original question is that the object "X" above is constrained,
and constraint_error should be raised.  This seems to mean
that implementing a private type with a type with defaulted
discriminants is dangerous, if it is going to be used in the
heap or for an aliased object, since the discriminants
are going to suddenly become constrained in an annoying way.
This also probably means that the language-defined private types in various
RTS packages must not be implemented with full views that have
defaulted discriminants.   For example, the full view of
Unbounded_String should not have defaulted discriminants.

Is that right?  It seems like a serious breach of privateness,
and an undesired pitfall in the language.

Unfortunately, trying to work out the solution is pretty nasty.
It basically means that aliased variables of a type with a partial view that
lacks discriminants should *not* be constrained by their initial
value, including when created by an allocator.  Furthermore, the
prohibition of access subtypes would need to extend to pool-specific
access types, since they can point to objects created by allocators
with changeable discriminants.  That would be a (very minor?) upward
incompatibility with Ada 83.

Whatever we decide, I think 3.3.1(9) and perhaps elsewhere needs
to make it clear what happens when the partial view has no
discriminants, but the full view has defaulted ones.

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

From: Robert I. Eachus
Sent: Thursday, October 07, 1999 2:24 PM

At 06:52 PM 10/6/1999 -0400, Tucker Taft wrote:
>Unfortunately, trying to work out the solution is pretty nasty.
>It basically means that aliased variables of a type with a partial view that
>lacks discriminants should *not* be constrained by their initial
>value, including when created by an allocator.  Furthermore, the
>prohibition of access subtypes would need to extend to pool-specific
>access types, since they can point to objects created by allocators
>with changeable discriminants.  That would be a (very minor?) upward
>incompatibility with Ada 83.

>Whatever we decide, I think 3.3.1(9) and perhaps elsewhere needs
>to make it clear what happens when the partial view has no
>discriminants, but the full view has defaulted ones.

   I think I now understand the issue, and I think the case of pool-specific
access subtypes is a red herring. The only place such a subtype could be
specified is where the full type of the designated object is known.  If this
means that more cases than in Ada 83 have to have "assume the largest"
semantics so be it.

   To me it is much more important for the implementor of a package to do the
best he can without worrying about things that may never happen.  In most
cases of record types with components that depend on a discriminant, the size of
the objects won't vary that much.  The exceptions are mostly records containing
strings which should be using bounded or unbounded strings anyway.

   I may not be saying this as well as I could.  I think that the feature of
being able to export a mutable type as a private abstraction is important.
Adding this major a restriction to make access subtypes work in pathological
cases is silly.

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

From: Robert A Duff
Sent: Friday, October 08, 1999 10:02 AM

Tuck says:

> I have been going back and forth in my mind whether
> an aliased object whose type is private without discriminants,
> but whose full view has defaulted discriminants, is constrained
> by its initial value.

I agree with Tucker (ie I'm going back and forth).

On the one hand, clients certainly shouldn't have to trip over
mysteriously-constrained heap objects.  So the best answer is that these
things are *not* constrained.

On the other hand, implementers have long depended on the fact that heap
objects with discrims are always constrained -- is it a big burden to
change that fact?

I guess I lean toward the user, rather than the implementer -- that is,
make these things unconstrained, and disallow access subtypes in the
troublesome cases.  But it's pretty weird to have unconstrained heap
objects in just this case -- I would be happier if the original
designers of Ada 83 had allowed unconstrained heap objects.  I remember
that being a surprise to me, when I first learned Preliminary Ada (or
was called Green then?) -- coming from a Pascal background, where
variant records in the heap are just like ones on the stack --
unconstrained.

Robert Eachus says:

>      I think I now understand the issue, and I think the case of
> pool-specific access subtypes is a red herring.  The only place such a
> subtype could be specified is where the full type of the designated
> object is known.  If this means that more cases than in Ada 83 have to
> have "assume the largest" semantics so be it.

I don't think this helps.  There could still be places where you can see
both the access subtype, and the unconstrained object, so that the
object can change colors out from under you.  If we allow you to create
an unconstrained heap object, then I think we need to disallow
constrained access subtypes.

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

From: Norman Cohen
Sent: Friday, October 08, 1999 10:35 AM

Robert Duff writes:

   <<On the other hand, implementers have long depended on the fact that heap
   objects with discrims are always constrained -- is it a big burden to change
   that fact?>>

It would certainly not have been a burden to write compilers to support
unconstrained heap objects in the first place  (provided--see below--that access
subtypes were also removed from the language).  After all, the compilers could
implement the usual user workaround:

   type Wrapper is
      record
         Value: Discriminated_Type := Initial_Value;
      end record;

   type Pointer is access Wrapper;

Whether it is a big burden to change existing compilers in this way is, of
course, a very different question.  I suspect that it would be very difficult to
find all the places in which compilers have implicitly depended on unconstrained
heap objects being disallowed.

Bob continues:

   <<I guess I lean toward the user, rather than the implementer -- that is,
   make these things unconstrained, and disallow access subtypes in the
   troublesome cases.  But it's pretty weird to have unconstrained heap objects
   in just this case -- I would be happier if the original designers of Ada 83
   had allowed unconstrained heap objects.  I remember that being a surprise to
   me, when I first learned Preliminary Ada (or was called Green then?) --
   coming from a Pascal background, where variant records in the heap are just
   like ones on the stack -- unconstrained.>>

The Ada-83 rule was necessary to support access subtypes.  (I think we could
have done without access subtypes in Ada 83, but once we had them, there was no
choice about unconstrained heap objects.)  Here is the problem:

      type Pointer is access Discriminated_Type;
      subtype Constrained_Pointer is access Pointer (Discrim_Value_1);
   begin
      Constrained: Constrained_Pointer := new Discriminated_Type(Discrim_Value_1);
      Unconstrained: Pointer := Constrained;
      Unconstrained.all := Discriminated_Type'(Discrim_Value_2,...);

Now Constrained designates an object that violates the discriminant constraint
on the access subtype.

Notice how this problem cannot arise when the user workaraound is used, because
the designated type of the access type is no longer discriminated, i.e., the
access type has no nontrivial subtypes.

-- Norman

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

From: Robert I. Eachus
Sent: Friday, October 08, 1999 11:30 AM

Robert A Duff wrote:

>On the other hand, implementers have long depended on the fact that heap
>objects with discrims are always constrained -- is it a big burden to
>change that fact?

   As Norm pointed out, there have always been objects on the heap with
unconstrained components.  With aliased components in Ada 95, we can now have
pointers to the heap (not created by allocaters) that designate unconstrained
components.  Right?  So I see all these problems coming up with assignment to
objects with aliased components in any case.

>I don't think this helps.  There could still be places where you can see
>both the access subtype, and the unconstrained object, so that the
>object can change colors out from under you.  If we allow you to create
>an unconstrained heap object, then I think we need to disallow
>constrained access subtypes.

    See above.  For the case we have been discussing, I think we are clean.  The
problem seems to occur where there are two access values designating the same
object, but with two different views.  So we may have to bite the bullet and
eliminate access subtypes, but I think the better fix is to define a check on
the discriminant that the implementation is allowed to optimize away if it can
figure how.

                                        Robert I. Eachus

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

From: Robert A Duff
Sent: Friday, October 08, 1999 12:50 PM

>    As Norm pointed out, there have always been objects on the heap with
> unconstrained components.  With aliased components in Ada 95, we can now
> have pointers to the heap (not created by allocaters) that designate
> unconstrained components.  Right?

No, aliased components are constrained by their initial value, for the
same reason as heap objects -- access subtypes.

- Bob

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

From: Robert I. Eachus
Sent: Friday, October 08, 1999 2:17 PM

    Is there an AI that changes 3.9.3(9), or there a different rule that covers
that?

   RM 3.9.3(9):  "A view of an object is defined to be aliased if it is defined
by an object_declaration or component_definition with the reserved word aliased,
or by a renaming of an aliased view. In addition, the dereference of an
access-to-object value denotes an aliased view, as does a view conversion (see
4.6) of an aliased view. Finally, the current instance of a limited type, and a
formal parameter or generic formal object of a tagged type are defined to be
aliased. Aliased views are the ones that can be designated by an access value.
If the view defined by an object_declaration is aliased, and the type of the
object has discriminants, then the object is constrained; if its nominal subtype
is unconstrained, then the object is constrained by its initial value.
Similarly, if the object created by an allocator has discriminants, the object
is constrained, either by the designated subtype, or by its initial value."

    The next to the last sentence says that an aliased object defined by is
constrained, but says nothing about an object defined by a component_definition.
(See first sentence.)

                                        Robert I. Eachus

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

From: Randy Brukardt
Sent: Friday, October 08, 1999 3:18 PM

Robert E. gave the wrong section reference: it is actually 3.10(9). But there
isn't any AI that changes 3.10(9); the only AIs on section 3.10 are in the
Corrigendum. The AARM doesn't shed any light, either.

Unless someone else can point out another rule, I agree with Robert E. that
there is a problem here.

				Randy.

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

From: Robert A Duff
Sent: Monday, October 11, 1999 10:43 AM

>     Is there an AI that changes 3.9.3(9), or there a different rule that covers
> that?

You mean 3.10(9).

The rule for components is actually in 3.6(11).  I think there's an AI
related to that.

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

From: Robert I. Eachus
Sent: Tuesday, October 12, 1999 11:36 AM

Does it fix the index?  I didn't think to look under arrays for aliased record
component legality rules. ;-)

But 3.6(10) is a legality rule, and doesn't seem to cover this case.  I can have
a variant record with a constrained aliased component that depends on the
discriminant, and if the discriminant has defaults, there is still a problem:

    type Problem (B: Boolean := False) is record
      case B is
        when True => S: aliased String(1..10);
        when False => F: Float;
      end case;
    end record;

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

From: Robert A Duff
Sent: Thursday, October 14, 1999 10:18 AM

>     Does it fix the index?  I didn't think to look under arrays for
> aliased record component legality rules. ;-)

It took me a while to track it down.  ;-)

The reason it's under arrays is that arrays and records share the syntax
for component_definition, and arrays come first, so the syntax is there,
and the rule is directly referring to the syntactic category, so it's in
the same section as the syntax rule.  That wasn't very helpful, was it?

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


Questions? Ask the ACAA Technical Agent