!standard 04.06 (12) 00-04-11 AI95-00168/07 !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 RM95 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 RM95 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 RM 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." (RM95 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. Aliased-ness 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 seems like the right solution, especially considering that constraints on access types are not a terribly useful feature. !corrigendum 3.07.01(7) @drepl A @fa is only allowed in a @fa whose @fa denotes either an unconstrained discriminated subtype, or an unconstrained access subtype whose designated subtype is an unconstrained discriminated subtype. @dby A @fa is only allowed in a @fa whose @fa 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 @fa is illegal if there is a place within the immediate scope of the designated subtype where its view is constrained. !corrigendum 4.06(11) @drepl @xbullet @dby @xbullet !corrigendum 4.06(12) @drepl @xbullet @dby @xbullet @xbullet !ACATS test B-Tests should be created for each rule. (There must be two, since the rules are in different sections.) !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: <> 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: <> 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? ****************************************************************