!standard 3.10.2(14/2) 09-06-01 AI05-0051-1/08 !standard 3.10.2(14.4/2) !standard 3.10.2(19/2) !standard 6.5(21/2) !standard 7.6.1(11/2) !standard 13.11(25.2/2) !class binding interpretation 07-05-04 !status work item 07-05-04 !status received 07-04-19 !priority Medium !difficulty Hard !qualifier Clarification !subject Accessibility checks for class-wide types and return from nested-extension primitives !summary (See recommendation.) !question How do the various accessibility rules pertaining to function results apply in the case of a dispatching call to a function where the accessibility level of the function named in the call differs from the accessibility level of the function whose body is executed? (The caller passes in an accessibility level to be used for dynamic checks.) Similarly, what storage pool should be used by an anonymous allocator returned by a function with an access result, if the function is reached by a call that dispatches to a nested type extension? (Use a pool identified by the caller.) In the case where the result returned by a function has one or more access discriminants, should the runtime check of 6.5(21/2) be performed even if the function result type is class-wide and does not have the same discriminants as the function result? (Yes.) Should similar checks be performed for class-wide allocators? (Yes.) Should similar checks be performed for part of return objects that have access discriminants? (Yes.) Should compile-time checks analogous to the existing checks of 4.8(5.2/2) and 6.5(5.6/2) also be performed? (Yes.) !recommendation We propose that where accessibility checks are currently required in return statements for access discrminants or access results, the dynamic check be based on an accessibility level passed in from the caller, while the static check would still be based on the accessibility level of the master that elaborated the function body. Similarly, the storage pool to use for a function access result would be determined by information passed in by the caller. This eliminates the problem with dispatching calls, since the passed-in level is unaffected by whether the call is a dispatching call to a primitive of a type declared at a more nested level than the function named in the call. Currently, almost all cases where such an accessibility check might be performed as part of a return statement, the caller already needs to pass in information because the returned object, or objects designated by its access discriminants, might need to be created in a place or storage pool determined by the context of the call, or might need finalization at a point determined by the caller. The one place where the current rules do not require some caller context is where a function has an access result. However, the current rule for handling access results causes an anonymous allocator to almost always lead to a storage leak. We propose to change the rules for what storage pool should be used in such an anonymous allocator, to minimize the likelihood of creating a storage leak. As a side-effect, we also solve the problem with dispatching calls to functions with access results, and make all of these cases more consistent with one another. Essentially 3.10.2(14-14.3) will be adjusted so that anonymous allocators in return expressions are treated as they would be if they were substituted into the call site. Note that we define the static accessibility check to remain based on the master that elaborated the function body, which preserves the same legality rules already present in Ada 2005. Only the dynamic checks are changed to overcome the problem with dispatching calls to overridings of nested extensions. In answer to the questions about access discriminants, we propose that all accessibility checks related to access discriminants be performed based on the actual object, rather than on the expected type, in the case where the expected type or deignated type is class-wide. !wording Modify 3.10.2(14/2) as follows: The accessibility level of an object created by an allocator is the same as that of the access type, except for an allocator of an anonymous access type {(an "anonymous allocator") in certain contexts, as follows:} [that defines the value of an access parameter or an access discriminant]. {For an anonymous allocator that defines the result of a function with an access result, the accessibility level is determined as though the allocator were in place of the call of the function; in the special case of a call that is the operand of a type conversion, the level is that of the target access type of the conversion.} For an {anonymous} allocator defining the value of an access parameter, the accessibility level is that of the innermost master of the call. For one defining an access discriminant, the accessibility level is determined as follows: Add after 3.10.2(14.4/2): * Within a return statement, the accessibility level of the anonymous access type of an access result is determined by the point of call. If the call is the operand of an explicit type conversion, the accessibility level is that of the target access type of the conversion. If the call is an actual parameter of another call or the prefix of a name, the accessibility level is that of the innermost master of the call. If the call defines an access discriminant, the level is the same as that given above for an object created by an anonymous allocator that defines an access discriminant (even if the access result is of an access-to- subprogram type). If the call itself defines the result of a function with an access result, this rule is applied recursively. Add after 3.10.2(19/2): * For determining whether a level is statically deeper than the level of the anonymous access type of an access result of a function, when within a return statement that applies to the function, the level determined by the point of call is presumed to be the same as that of the level of the master that elaborated the function body. Replace 4.8(5.2/2): If the designated subtype of the type of the allocator has one or more unconstrained access discriminants, then the accessibility level of the anonymous access type of each access discriminant, as determined by the subtype_indication or qualified_expression of the allocator, shall not be statically deeper than that of the type of the allocator (see 3.10.2). with: If the subtype determined by the subtype_indication or qualified_expression of the allocator has one or more access discriminants, then the accessibility level of the anonymous access type of each access discriminant shall not be statically deeper than that of the type of the allocator (see 3.10.2). Modify 4.8(10.1/2) as follows: For any allocator, if the designated type of the type of the allocator is class-wide, then a check is made that the accessibility level of the type determined by the subtype_indication, or by the tag of the value of the qualified_expression, is not deeper than that of the type of the allocator. If the [designated subtype] {subtype determined by the subtype_indication or qualified_ expression} of the allocator has one or more [unconstrained] access discriminants, then a check is made that the accessibility level of the anonymous access type of each access discriminant is not deeper than that of the type of the allocator. Program_Error is raised if either such check fails. In 6.5(5.6/2) replace - If the result subtype of the function is class-wide, the accessibility level of the type of the expression of the return statement shall not be statically deeper than that of the master that elaborated the function body. If the result subtype has one or more unconstrained access discriminants, the accessibility level of the anonymous access type of each access discriminant, as determined by the expression of the simple_return_statement or the return_subtype_indication, shall not be statically deeper than that of the master that elaborated the function body. with two separate bulleted items - If the result subtype of the function is class-wide, the accessibility level of the type of the expression of the return statement (if any) shall not be statically deeper than that of the master that elaborated the function body. - If the subtype determined by the expression of the simple_return_statement or by the return_subtype_indication has one or more access discriminants, the accessibility level of the anonymous access type of each access discriminant shall not be statically deeper than that of the master that elaborated the function body. Modify 6.5(21/2) as follows: If [the result subtype]{any part of the return object or coextension thereof} of a function has one or more [unconstrained] access discriminants {whose value is not constrained by the result subtype of the function}, a check is made that the accessibility level of the anonymous access type of each access discriminant, as determined by the expression or the return_subtype_indication of the function, is not deeper than [that of the master that elaborated the function body] {the level of the return object as determined by the point of call (see 3.10.2)}. If this check fails, Program_Error is raised. Modify 7.6.1(11/2) as follows: The order in which the finalization of a master performs finalization of objects is as follows: Objects created by declarations in the master are finalized in the reverse order of their creation. For objects that were created by allocators for [an] {a named} access type whose ultimate ancestor is declared in the master, this rule is applied as though each such object that still exists had been created in an arbitrary order at the first freezing point (see 13.14) of the ultimate ancestor type; the finalization of these objects is called the finalization of the collection. {Objects created by allocators for an anonymous access type that are not coextensions of some other object, are finalized in an arbitrary order during the finalization of their associated master.} After the finalization of a master is complete, the objects finalized as part of its finalization cease to exist, as do any types and subtypes defined and created within the master. Add the following after 13.11(25.2/2): * If the allocator defines the result of a function with an access result, the storage pool is determined as though the allocator were in place of the call of the function. If the call is the operand of a type conversion, the storage pool is that of the target access type of the conversion. If the call is itself defining the result of a function with an access result, this rule is applied recursively. !discussion Discussion relating to class-wide result types or designated types: A wording change in 6.5(21/2) seems to be needed; in the case of a class-wide result type, the specified check may need to be performed even if the result type of the function has no access discriminants. Consider the following example: declare type Root is tagged null record; type Ext (D : access Integer) is new Root with null record; function F return Root'Class is Local_Var : aliased Integer; function Local_Func return Ext is begin return (D => Local_Var'Access); end Local_Func; begin return Local_Func; end F; procedure Do_Something (X : access Integer) is ... end; begin Do_Something (Ext (F).D); end; If this test executes without raising any exception, then a dangling reference will be passed to Do_Something. It seems that the check described in 6.5(21/2) needs to be performed as part of returning from F, but the result subtype of F lacks unconstrained access discriminants. This will introduce distributed overhead in the sense that a function which returns a classwide result may have to check for the possibility that its result has a "bad" access discriminant values even if there are no access discriminants anywhere in the program. For some implementations, this might involve querying a "has anonymous access discriminants" flag in the descriptor associated with a specific tagged type. There is an analogous problem for allocators. Consider the following variation on the preceding example: declare type Root is tagged null record; type Ext (D : access Integer) is new Root with null record; type Ref is access Root'Class; function F return Ref is Local_Var : aliased Integer; function Local_Func return Ext is begin return (D => Local_Var'access); end Local_Func; begin return new Ext'(Local_Func); end F; procedure Do_Something (X : access Integer) is ... end; begin Do_Something (Ext (F.all).D); end; At a minimum, additional runtime checks are needed to cover these cases (see 4.8(10.1/2), 6.5(21/2)). Just adding runtime checks would be sloppy. Additional static checks are also needed (see 4.8(5.2/2) and 6.5(5.6/2)). So that's what's been done. The preceding two examples illustrate the need for runtime checks in the cases of function results and allocators. The following variations on those examples illustrate the need for static checking for function results and for allocators: declare type Root is tagged null record; type Ext (D : access Integer) is new Root with null record; function F return Root'Class is Local_Var : aliased Integer; Result : Ext (Local_Var'Access); begin return Result; -- should be rejected end F; procedure Do_Something (X : access Integer) is ... end; begin Do_Something (Ext (F).D); end; declare type Root is tagged null record; type Ext (D : access Integer) is new Root with null record; type Ref is access Root'Class; function F return Ref is Local_Var : aliased Integer; subtype Designated is Ext (D => Local_Var'Access); begin return new Designated; -- should be rejected end F; procedure Do_Something (X : access Integer) is ... end; begin Do_Something (Ext (F.all).D); end; Note that AI05-0032, not this AI, contains the wording changes needed to ensure the static rejection of cases like declare type Root is tagged null record; function F return Root'Class is type Local_Extension is new Root with null record; begin return X : Local_Extension; -- should be rejected end F; begin null; end; Does the proposed wording correctly handle the case where no static check is possible because no information is known about the discriminant values? Consider the first two examples (which are intended to illustrate the need for runtime checks; they are not intended to be rejected at compile-time). Does the proposed wording for static checks correctly let these examples through without any definitional problems? In particular, is the test that "the accessibility level of the anonymous access type of each access discriminant shall not be statically deeper than that of the master that elaborated the function body" well-defined in these cases? When we refer to the subtype "determined by the expression of the simple_return_statement", is this well-defined? Suppose, for example, that the expression is an aggregate. Do we need to generalize the definition of "nominal subtype" so that it is defined for all expressions, not just names? --- Similarly, we may need to make these checks for components or coextensions of the return object. Consider: procedure Defaulted_Access_Discrim is X : aliased Integer; type Discriminated (D : access Integer := X'Access) is limited null record; type Undiscriminated is limited record Comp : Discriminated; end record; type Ref is access Undiscriminated; Ptr : Ref; procedure P is Y : aliased Integer := 0; function F return Undiscriminated is begin return Z : Undiscriminated := (Comp => (D => Y'Access)); end F; begin Ptr := new Undiscriminated'(F); end P; begin P; -- if we reach this point, Ptr.all.Comp.D is dangling end Defaulted_Access_Discrim; 6.5(21) needs to cover this case as well. ------ Discussion related to dispatching calls reaching primitive functions of a nested type extension: In many cases, the original formulation of the semantics rules for access result types followed from an informal equivalence rule. The declaration function Foo ( ...) return access T; was considered roughly equivalent to: type T_Ref is access all T; function Foo (...) return T_Ref; If the function in question overrides an inherited dispatching operation and if the controlling type of the function is declared in a more nested scope than its parent type, then this equivalence would break down because the caller and the callee see different access types, at different accessibility levels. Also, if such a function returns an allocator, then what should be the lifetime and accessibility level of the allocated object, both in the normal case, and in the case where the function has been invoked via a dispatching call to the operation of the parent type? It is important that the object live at least as long as that implied by the accessibility level associated with the function result. We would also prefer that the storage be allocated from a pool that will minimize storage leaks. This AI proposes that we use a simple substitution rule to determine both the accessibility level and the best storage pool to use. The accessibility issue for a function with an access result is an instance of a more general problem associated with uses of the accessibility level of a function body in the case where this level does not match the accessibility level of the function named in the (dispatching) call. A function with an access result type may return a reference to an object whose accessibility level matches that of the function body: X : aliased Integer; function F1 return access Integer is begin return X'Access; end F1; A function with a class-wide result type may return a result whose type's accessibility level matches that of the function body: type Derived is new Some_Tagged_Type with null record; function F2 return Some_Tagged_Type'Class is begin return Derived'(...); end F2; A function whose result type has anonymous access discriminants may return a result with a discriminant that references an object whose accessibility level matches that of the function body: type Discriminated (Disc : access Integer) is limited null record; X : aliased Integer; function F3 return Discriminated is begin return (Disc => X'Access); end F3; And then we have the function with an access result returning an anonymous allocator: function F4 return access Some_Designated_Type is begin return new Some_Designated_Type'(...); end F4; Each of these four scenarios becomes more interesting if the function overrides an inherited dispatching operation and the controlling type of the function is declared in a more nested scope than its parent type. Consider: declare type Tagged_Type is tagged null record; type TT_Ptr is access Tagged_Type'Class; type Discriminated (D : access Integer) is tagged limited null record; type D_Ptr is access Discriminated; package Pkg_1 is type Parent_Type is abstract tagged null record; function F1 (X : Parent_Type) return access Integer is abstract; function F2 (X : Parent_Type) return Tagged_Type'Class is abstract; function F3 (X : Parent_Type) return Discriminated is abstract; function F4 (X : Parent_Type) return access Some_Designated_Type is abstract; end Pkg_1; procedure Call_Functions (X : Pkg_1.Parent_Type'Class) is -- Presume this contains calls on X.F1, X.F2, X.F3, and X.F4. -- See below for definitions of F1, F2, F3, and F4 -- for a nested type extension Extension_A. -- Presuming that X denotes an object of type Extension_A, -- consider the following calls: Z : Tagged_Type'Class := X.F2; -- equiv to F2(X); -- NOTE: We want this to succeed. In this -- case there is no special check required. -- The "normal" tag accessibility checks at -- function return are adequate. Q : TT_Ptr := new Tagged_Type'Class(X.F2); -- NOTE: We want this to fail (the allocator has -- [almost] library-level accessibility). -- NOTE2: In this case, the allocator will make the -- tag accessibility check, so again, there is no problem -- in this case. The problem occurs only for access -- discriminants. If Extension_B in Other_Locals (below) -- had an access discriminant, then we'd have the problem. -- That's the case for F3. Z3 : Discriminated := X.F3; -- NOTE: Should succeed, because Z3 will not live as long -- as the local discriminant. Q3 : D_Ptr := new X.F3; -- NOTE: This should fail, heap object lives longer than -- the discriminant. begin -- Each call returns a reference to an object or a type that -- is more nested than Pkg_1. Hence, it is important that -- the caller and callee agree about what accessibility level -- to presume for the returned object or designated object, -- so we don't end up with a dangling reference. -- -- For X.F4, we also need to determine the storage pool for the -- anonymous allocator in the return statement. For F2 and -- F3, if the calling context is an initialized allocator, we -- can avoid copying if we have the return object allocated -- directly in the desired storage pool. This is required for -- F3 because it is limited, and is allowed for F2 for efficiency. -- -- If such an initialized allocator is for an access type -- declared outside Call_Functions, we need to be sure that -- some accessibility check prevents the allocator from succeeding. -- -- The result of calls on F1 or F4 might be converted to an access -- type declared outside of Call_Functions. Again, some accessiblity -- check must prevent this. -- -- By passing in an accessibility level in all these cases, -- as well as a storage pool or equivalent, the called function -- can do the appropriate accessibility check, and allocate -- from the appropriate storage pool to minimize the chance -- of a storage leak. end Call_Functions; procedure Nested is package Pkg_2 is type Extension_A is new Pkg_1.Parent_Type with null record; function F1 (X : Extension_A) return access Integer; function F2 (X : Extension_A) return Tagged_Type'Class; function F3 (X : Extension_A) return Discriminated; function F4 (X : Extension_A) return access Some_Designated_Type; end Pkg_2; package Other_Locals is I : aliased Integer; type Extension_B is new Tagged_Type with null record; end Locals; package body Pkg_2 is function F1 (X : Extension_A) return access Integer is begin return Other_Locals.I'access; end F1; -- Return address of local object function F2 (X : Extension_A) return Tagged_Type'Class is begin return Other_Locals.Extension_B'(null record); end F2; -- Return instance of local extension function F3 (X : Extension_A) return Discriminated is begin return Discriminated'(D => Other_Locals.I'Access); end F3; -- Return object with acc. discrim designating a local function F4 (X : Extension_A) return access Some_Designated_Type is begin return new Some_Designated_Type'(...); end F4; -- Return anonymous allocator end Pkg_2; X : Pkg_2.Extension_A; begin Call_Functions (X); end Nested; begin Nested; end; Note that part of the wording change in 6.5(21/2) recognizes that even if the result subtype has no visible access discriminants, nevertheless the returned object might have access discriminants, if the result type is class-wide. Hence the rule needs to be written in terms of the returned object rather than the result subtype. On a separate but related issue, this AI also recommends we relax the rules for order of finalization of objects (other than coextensions) created by anonymous allocators, since tying them to a particular freezing point is tricky, and doesn't seem to be of great value to the user. All that really matters is that they get finalized in the master determined by their accessibility level, and that is already specified by 7.6.1(4). For named access types, there is the concern that their collections be finalized before any corresponding storage pool object gets finalized, but there is no similar consideration for non-coextension anonymous allocations, since they always use the "default" storage pool. IMPLEMENTATION MODEL To find an implementation model for the dynamic accessibility check for a return statement as proposed by this AI, the best analogy is the approach used for access parameters, where the caller identifies the level of the actual parameter with a static accessibility level. Inside the "callee" subprogram, the level is only of interest when converting the access parameter to a type declared outside the callee. Levels that are the same or deeper than the locals of the callee are all equivalent for this purpose, and could be represented by any value greater than or equal to the level of the callee. When we carry this analogy over to the cases covered by this AI, the caller must pass in some kind of static accessibility level to be used for any accessibility check at function return. It is needed any time the function has an access result, or has a result subtype where return objects might have access discriminants that are not constrained by the result subtype. This includes all functions whose result type is class-wide, since access discriminants can be added in a type extension. At the return statement, the accessibility level of the object to be designated by an access discriminant of the return object would be checked against this level. Similarly, if the function has an access result, the level of the object to be designated by the access result would be checked against this level. In both cases, for the dynamic accessibility check to succeed, the object to be designated by the access discriminant or access result must be no deeper than the level passed in by the caller. We presume that we would not even be performing this check if the static accessibility check failed, because such a return statement is already illegal. If we want to be sure the dynamic check will succeed, we can pass in a very large value (e.g. Integer'Last). We could use such a level if the result of the function call is used to initialize a local object, or is to be allocated out of a local storage pool, since we know that these cases are always safe (presuming the static accessibility legality check has already passed). On the other hand, if the result of the function is used in an initialized allocator for an access type declared outside the caller, or in the case of an access result, is converted to such an access type, the caller would pass in the static level of that non-local access type. If the callee function is declared at that level or below, then this passed-in level is a meaningful level for it to use in its dynamic check in its return statement. On the other hand, if the callee function is not as deeply nested as the target access type, then the dynamic check will always succeed, which is as it should be. BACKGROUND ON THIS APPROACH We had earlier considered a rule where the accessibility level of the controlling tag used in the call would be used at the call site to determine the actual level of the returned object. However, after trying to work out the details for that, we gave up. Furthermore, it implied a check both in the return statement and then another check after return on use. That seemed inefficient. Finally, we realized that in essentially all of these cases, we already needed to pass in some kind of caller context because the return object might not allow copying or might require finalization, and the caller is determining where the return object is to be located and/or what master it is to be associated with for finalization purposes. Hence the proposed solution is now to change the dynamic accessibility-level check at the return statement to be based on an accessibility level passed in from the caller, rather than basing it on the level of the master that elaborated the function body. Using the level of the master is too optimistic for nested overrides, and is too pessimistic in many other cases. We chose to leave the static legality checks the same. It turns out no additional rules are needed for the "class-wide" tag-accessibility checks, because the caller necessarily has access to the type that is being returned if it is at the same level as the function, since they are able to call the function. And presuming the caller makes no assumptions about the tag of an object returned from a function with a class-wide result, then they will check it again on any further function return or class-wide allocator. After solving this problem for returning objects with access discriminants, we go on to propose essentially a similar solution for functions with access results. Namely, the "dynamic" accessibility level for access results would be determined by the caller rather than being determined by the master that elaborated the function body. This solves the problem with nested overrides, makes access results work more consistently with access discriminants and functions that return limited types, and it minimizes storage leaks. It effectively means that whether an initialized allocator is at the call site using the result of the function as the initial value, or the initialized allocator is at the return statement of a function with an access result, and the caller merely converts it to the desired type, the net effect is the same. EXAMPLES Here is an example. In the following, the initialization of X and the initialization of Y produce essentially the same thing, namely a pointer to a heap object allocated from the storage pool associated with Lim_T_Ptr: function Blurfo return Lim_T is ... function Blah return access Lim_T is begin return new Lim_T'(Blurfo); end Blah; type Lim_T_Ptr is access Lim_T; X : Lim_T_Ptr := new Lim_T'(Blurfo); Y : Lim_T_Ptr := Lim_T_Ptr(Blah); Another useful feature of having the caller specify the dynamic accessibility level to check occurs when the function returns a reference to a component of an object denoted by an access parameter. E.g.: type Rec is record Comp : aliased Comp_Type; end record; function Get_Comp(R : access Rec) return access Comp_Type is begin return R.Comp'Access; -- (1) -- Run-time check based on level passed in from caller end Get_Comp; procedure Process(G : access Comp_Type) is ... procedure Test is Local : aliased Rec; begin Process(Get_Comp(Local'Access)); end Test; The above is perfectly safe and reasonable, but would fail if the dynamic check at (1) were against the level of the master that elaborated Get_Comp. !ACATS test B-Tests and C-Tests need to be constructed to check all of these new legality rules and run-time checks. !appendix From: Stephen W. Baird Sent: Thursday, April 19, 2007 12:04 PM !question How do the various accessibility rules pertaining to function results apply in the case of a dispatching call to a function where the accessibility level of the function named in the call differs from the accessibility level of the function whose body is executed? !discussion In most cases, the dynamic semantics of access result types follow easily from an informal equivalence rule. The declaration function Foo ( ...) return access T; is pretty much equivalent to type T_Ref is access all T; function Foo (...) return T_Ref; , and that's that. If the function in question overrides an inherited dispatching operation and if the controlling type of the function is declared in a more nested scope than its parent type, then this equivalence breaks down. If the function returns an allocator, then what is the lifetime and accessibility level of the allocated object in the case where the function has been invoked via a dispatching call to the operation of the parent type? Tuck has pointed out that this is an instance of a more general problem associated with uses of the accessibility level of a function body in the case where this level does not match the accessibility level of the function named in the (dispatching) call. A function with an access result type may return a reference to an object whose accessibility level matches that of the function body: X : aliased Integer; function F1 return access Integer is begin return X'Access; end F1; A function with a class-wide result type may return a result whose type's accessibility level matches that of the function body: type Derived is new Some_Tagged_Type with null record; function F2 return Some_Tagged_Type'Class is begin return Derived'(...); end F2; A function whose result type has anonymous access discriminants may return a result with a discriminant that references an object whose accessibility level matches that of the function body: type Discriminated (Disc : access Integer) is limited null record; X : aliased Integer; function F3 return Discriminated is begin return (Disc => X'Access); end F3; Each of these three scenarios becomes problematic if the function overrides an inherited dispatching operation and the controlling type of the function is declared in a more nested scope than its parent type. Consider: declare type Tagged_Type is tagged null record; type Discriminated (D : access Integer) is tagged limited null record; package Pkg_1 is type Parent_Type is abstract tagged null record; function F1 (X : Parent_Type) return access Integer is abstract; function F2 (X : Parent_Type) return Tagged_Type'Class is abstract; function F3 (X : Parent_Type) return Discriminated is abstract; function F4 (X : Parent_Type) return access Some_Designated_Type is abstract; end Pkg_1; procedure Call_Functions (X : Pkg_1.Parent_Type'Class) is begin -- -- Each call should fail, right? -- Otherwise we've got the potential for dangling references. -- -- The situation with X.F4 is even less clear. end Call_Functions; procedure Nested is package Pkg_2 is type Extension is new Pkg_1.Parent_Type with null record; function F1 (X : Extension) return access Integer; function F2 (X : Extension) return Tagged_Type'Class; function F3 (X : Extension) return Discriminated; function F4 (X : Extension) return access Some_Designated_Type; end Pkg_2; package Other_Locals is I : aliased Integer; type Extension is new Tagged_Type with null record; end Locals; package body Pkg_2 is function F1 (X : Extension) return access Integer is begin return Other_Locals.I'access; end F1; function F2 (X : Extension) return Tagged_Type'Class is begin return Other_Locals.Extension'(null record); end F2; function F3 (X : Extension) return Discriminated is begin return Discriminated'(D => Other_Locals.I'Access); end F3; function F4 (X : Extension) return access Some_Designated_Type is begin return new Some_Designated_Type'(...); end F4; end Pkg_2; X : Pkg_2.Extension; begin Call_Functions (X); end Nested; begin Nested; end; Tuck suggests an implementation model where these problematic dispatching operations of nested extensions expect an accessibility level to be passed in to indicate the accessibility level of the function declaration named in the call; that level is then used for anonymous-typed allocators at the point of return, and for accessibility checks. This seems like a good approach, but it is not implicit in the current RM. Wording changes would be needed - a "clarification" would not suffice. Furthermore, there are some aspects of this problem that might still need resolution even if this proposal were adopted: 1) In the case of a dispatching call to a function with an access result type which returns an allocator, exactly when is the allocated object finalized? This remains a valid (albeit obscure) question even in the case where the parent type and the extension type have the same accessibility level. 7.6.1(11.2) refers to "the first freezing point of the ultimate ancestor type", but is this the freezing point of the access type associated with the function named in the call or with the function whose body is executed? 2) A wording change in 6.5(21/2) seems to be needed; in the case of a class-wide result type, the specified check may need to be performed even if the result type of the function has no access discriminants. Consider the following example: declare type Root is tagged limited null record; type Ext (D : access Integer) is new Root with null record; function F return Root'Class is Local_Var : aliased Integer; function Local_Func return Ext is begin return (D => Local_Var'Access); end Local_Func; begin return Local_Func; end F; procedure Do_Something (X : access Integer) is ... end; begin Do_Something (Ext (F).D); end; If this test executes without raising any exception, then a dangling reference will be passed to Do_Something. It seems that the check described in 6.5(21/2) needs to be performed as part of returning from F, but the result subtype of F lacks unconstrained access discriminants. This will introduce distributed overhead in the sense that a function which returns a limited classwide result may have to check for the possibility that its result has a "bad" access discriminant values even if there are no access discriminants anywhere in the program. We probably don't want two active AIs which both modify 6.5(21/2), but this issue might be split off into a separate AI if that doesn't appear to be a problem. From: Tucker Taft Sent: March 27, 2007 Sent to Steve Baird, in response to (roughly) the question about allocators that begins the !discussion section above. Very nasty. It seems like we might have the same problem with functions returning class-wide types, and perhaps with functions returning objects with access discriminants. In all three cases, there is an accessibility check at the point of the return statement, and in each case the check is against the accessibility level of the function body itself. But if the caller has reached the function body via a dispatching call, the caller may be presuming an accessibility level associated with the dispatching operation identified at compile-time, rather than the body determined at runtime. There seem various possible solutions, none particularly pleasant. I think my favorite would be essentially a "wrapper" model, where dispatching operations of nested extensions expect an accessibility level passed *in* to indicate the accessibility level of the function declaration named in the call, and that level is used for anonymous allocators at the point of return, and for accessibility checks. The "wrapper" aspect is that to avoid distributed overhead, the extra implicit parameter would be provided by a "wrapper" rather than by the caller, so if calling operations of non-nested extensions, there is no additional overhead. Note that any function that might *become* a dispatching operation via renaming would have to assume the worst, and allow for this implicit parameter. Some kind of a wrapper may be needed for these kinds of dispatching calls to deal with the static link, so adding another implicit parameter might be relatively straightforward. This model also seems consistent with the wording in 3.10.2(10.1/2) where we indicate that the accessibility of a return object goes from being that of the return statement, to being that determined by the point of call, with no interim "stop" at the level of the function body. I think passing *in* an accessibility level is preferable to passing one *out* because we already have situations where we pass one in (i.e. with access parameters, and probably also with the new limited return types), and so we have already figured out what that entails as far as passing static vs. dynamic levels. On the other hand, the level we pass in for access parameters is never used for the *target* of a conversion, only for the *operand*, so the same logic might not work... Hmmm... **************************************************************** From: Tucker Taft Sent: Tuesday, October 30, 2007 9:02 PM You use the phrase "This condition shall also hold ...". That's a new one, as far as I can remember, for the RM. I would suggest spelling it out, perhaps introduced by: Similarly, if the designated subtype of the type of the allocator is class-wide ... then the accessibility level ... shall not be statically deeper ... Alternatively, in this case, couldn't we combine the rules into: If the type determined by the subtype_indication or the qualified_expression of the allocator has one or more access discriminants, then the accessibility level of the anonymous access type of each access discriminant, as determined by the subtype_indication or qualified_expression of the allocator, shall not be statically deeper than that of the type of the allocator (see 3.10.2). **************************************************************** Summary of a private thread about this AI: The AI has: A couple of other changes that really should have gone into AI05-0032 are also included to handle static rejection of cases like function Foo return T'Class is type Local_Extension is new T with null record; begin return X : Local_Extension; end Foo; --- But this case is statically illegal, because "Local_Extension" does not have the same type as "T'Class" -- and 6.5(5.2/2) requires that for extended return statements (for functions with ordinary subtype returns -- there are different rules for access results). --- But Steve is talking about AI05-0032, which allows the return_subtype_indication to determine any type that is *covered* by the result type of the function. --- Why? That AI has never been approved. (And thus isn't in *my* AARM.) If it needs additional rules in order to work, then those rules need to be in that AI, not in some unrelated AI that happens to modify that paragraph. That's especially true as AI-32 is an Amendment AI, and may not be implemented for years if we don't vote to change the status. --- Good point, Randy. AI05-0032 should be amended instead of adding it to this AI. **************************************************************** From: Tucker Taft Sent: Wednesday, October 31, 2007 12:34 AM Here is an AI [version /03 - ED.] that Steve started, and I finished, which attempts to deal with the case where, from an accessibility level point of view, a caller has one view of a function, and the return statement has a different view. This can happen in four ways: 1) In a dispatching call, where the caller is calling an operation of a parent type T1, and is actually reaching an overriding defined for a nested extension T2. (caller view is not as deep as called function) 2) In a dispatching call, where the caller is calling an operation of a nested extension T2, and is actually reaching an inherited operation from a parent T1. (caller view is deeper than called function) 3) In a call through an access-to-subprogram value, where the access type is declared at a deeper level than the designated subprogram. (caller view is deeper than called function) 4) In a call through an access-to-subprogram value that is an access parameter, which has an "infinite" accessibility level, clearly deeper than the designated subprogram. (caller view is *much* deeper than called function) After much head scratching and trying various solutions, we settled on uniformly using the caller's view for the run-time accessibility checks performed at a return statement. This also means that the caller has an accurate view of the accessibility characteristics of the returned object. In addition it means that if an access parameter is passed by the caller, the level of the access parameter can be meaningfully compared with the "result" level passed in, in case the access parameter is used as part of the result in one way or another (e.g. as an access discriminant, or as the access result). Case (4) represented an existing hole in 3.10.2, where we hadn't properly figured out the accessibility characteristics of results returned from calls through an access-to-subprogram parameter. In this AI, we suggest that rather than the result of such a call also having an "infinite" accessibility level, it should have a level corresponding to the master containing the access parameter, as though the access value were of an access-to-subprogram type declared local to the master with the access parameter. Anyway, I am giving away too much in the cover letter. Time to read the AI! **************************************************************** From: Stephen W. Baird Sent: Wednesday, October 31, 2007 6:12 PM > The accessibility level against which to perform > run-time accessibility checks at the return statement > of a dispatching function is determined by the > level of the function named at the call-site, even if > that is different from the accessibility level of the > function body reached by the dispatching call. If the > function has an access result, and the expression of the > return statement is an (anonymous) allocator, then this > accessibility level in turn determines the lifetime of > the allocated object. I think this means that if a function with an anonymous access result type returns an allocator, then the allocated object may have a different master than if the function had returned a recursive call which returned an allocator. This makes me nervous. Is this what we want? If a caller makes a dispatching call on a subprogram declared in a less nested scope than the scope of the executed function body and the callee then attempts to return the result of a nondispatching recursive call, is Program_Error raised because the recursive call might have successfully returned a "bad" reference? It seems odd to have a simple recursive call like function F return access Integer is begin if ... then return F; end if; ... end F; failing the accessibility check associated with an access type conversion. It seems like the source type and the target type are the same, but this AI suggests that they aren't (at least dynamically). I suspect that I'm just confused; this stuff is tricky. **************************************************************** From: Tucker Taft Sent: Wednesday, October 31, 2007 7:19 PM > I think this means that if a function with an anonymous > access result type returns an allocator, then the allocated > object may have a different master than if the function > had returned a recursive call which returned an allocator. > This makes me nervous. Is this what we want? Probably not. I think we need to presume that when calling such a function in a return statement, the level against which the returned value is being checked must be passed on to the called function. That is, in: return Func(...); if Func has a result type that is classwide, access-discriminated, or an anonymous access type, then if a level is passed into the enclosing function, the same level should be passed to Func. I suspect AI05-51 will need some extra wording to that effect, though conceivably it may be implied by something already in the AI. > > If a caller makes a dispatching call on a subprogram > declared in a less nested scope than the scope of the > executed function body and the callee then attempts to > return the result of a nondispatching recursive call, > is Program_Error raised because the recursive > call might have successfully returned a "bad" reference? > > It seems odd to have a simple recursive call like > function F return access Integer is > begin > if ... then > return F; > end if; > ... > end F; > failing the accessibility check associated with an > access type conversion. It seems like the source type > and the target type are the same, but this AI > suggests that they aren't (at least dynamically). I think your example would work as desired if the return statement passes along the dynamic accessibility level it received implicitly. > I suspect that I'm just confused; this stuff is tricky. I think you have identified an important additional requirement, namely that the return statement passes along the dynamic accessibility level. **************************************************************** From: Tucker Taft Sent: Wednesday, October 31, 2007 8:38 PM > ... > I think we need to presume that when calling such > a function in a return statement, the level against > which the returned value is being checked must be > passed on to the called function. That is, in: > > return Func(...); > > if Func has a result type that is classwide, > access-discriminated, or an anonymous access > type, then if a level is passed into the > enclosing function, the same level should be > passed to Func. I suspect AI05-51 will need > some extra wording to that effect, though > conceivably it may be implied by something > already in the AI. I think the paragraph proposed in the AI to follow 3.10.2(10.1) could be rewritten roughly as follows: Within a return statement that applies to a function with a result subtype that is an access result, that is class-wide, or that has one or more unconstrained access discriminants, the (dynamic) accessibility level of this function, and other functions with such a result subtype elaborated by the same master, are considered to have the same accessibility level as that of the view of the function named at the point of call. [Redundant: Note that for a dispatching call or a call through an access-to-subprogram value, the level of the function named at the point of call might be different from that of the function containing the return statement.] The basic idea is that within the return statement, the function and all other similar functions act as though there were of the same accessibility level as the function named at the call site. That means that any function that has a level passed in, and that is of the same level as the function with the return statement, will be passed in the same dynamic level. **************************************************************** From: Stephen W. Baird Sent: Thursday, November 1, 2007 4:07 PM This seems like it is getting way too complicated. I don't like this call-site sensitivity (if you call this function from here, it means something different than if you call it from there). Before looking at any of the details of this proposal, let's back up and look at why access result types were introduced. They were only intended to be a notational convenience, to spare the user the inconvenience of declaring a named access type. We were introducing anonymous access types in other places and access result types for functions fit in nicely with these other changes. They were not intended to have any interesting dynamic semantics, nor was it realized at the time that they would have to. Now we've realized that there are some fairly complicated problems in this area, I'd like to at least consider the possibility of solving them all by statically disallowing the problematic cases. We could introduce a rule that the profile of a dispatching function or of an access-to-function type cannot have an (anonymous) access result type. Or, if that is too restrictive, we could impose more stringent restrictions on the use of the 'Access attribute for subprograms if the profile has an access result type, along with similar restrictions for nested extensions if there is a primitive operation with an access result type. The point is that we could devise rules of some sort for statically disallowing the problematic cases. What do folks think about this general approach? Ok, now for a more specific point about this proposal. In implementation terms, the general idea is that within a return statement that is returning from a function which was passed an accessibility_level/storage_pool/whatever descriptor, you pass the descriptor reference along whenever you call any function that also takes such a descriptor parameter. Consider the following example: function F (Controlling_Operand : Some_Tagged_Type; Flag : Boolean := False) return access Designated is begin return Result : access Designated do declare task T; task body T is procedure P is begin if Flag then Result := new Designated; else Result := F (not Flag); end if; end P; begin P; end T; begin null; end; end return; end F; This example demonstrates (I think) that when you say "within a return statement", you really mean it. The rule even applies inside of nested procedures, tasks, etc. But that would mean that it would even apply inside of a nested return statement (returning from a nested function), and so now you have contradictory requrements (I think) imposed by the two enclosing return statements. The simple solution (let the innermost return statement win) doesn't work. Consider modifying the example so that a recursive call to F Result := new F (not Flag); occurs within a return statement within a function declared within the declare block that is declared within F's (one and only) return statement. I expect that we can work this particular corner case out, but it seems likely that there are other dragons lurking in the vicinity. **************************************************************** From: Stephen W. Baird Sent: Thursday, November 1, 2007 5:05 PM A correction to the example of my previous message on this thread: The recursive call to F lacks a value for the Controlling_Operand parameter. I intended to pass along the existing parameter in a nondispatching call: Result := F (Controlling_Operand, not Flag); **************************************************************** From: John Barnes Sent: Thursday, November 1, 2007 4:35 PM As time has gone on I am becoming to feel that anonymous access types in general (that is universally) were perhaps a mistake. I know I had great trouble weaving them into my book. Personnally I would now never use them. Ada prides itself in being precise and anon types seem to have slid us down a slippery slope of confusion leading to accessibility problems. Of course anonymous access to subprogram parameters are important but they are just essentially subprogram parameters as in Algol 60 so that is no problem. Anyway. So I am very sympathetic to Steve's view. Too late too late the virgin cried no doubt! See y'all next week. Or a subset thereof. **************************************************************** From: Tucker Taft Sent: Thursday, November 1, 2007 9:41 PM > ... We could introduce a > rule that the profile of a dispatching function or of an access-to-function type > cannot have an (anonymous) access result type. Or, if that is too > restrictive, we could impose more stringent restrictions on the use of the > 'Access attribute for subprograms if the profile has an access result type, > along with similar restrictions for nested extensions if there is a primitive > operation with an access result type. The point is that we could devise > rules of some sort for statically disallowing the problematic cases. > > What do folks think about this general approach? I think it depends how draconian is the restriction. Disallowing them altogether for dispatching functions would seems pretty draconian. Disallowing nested extensions of types with such functions is less draconian. I think once you do that, then you can eliminate the issue with calling through access-to-subprogram values, because such functions no longer need to be passed a level. But remember, we also have the same issue for functions returning class-wide types and types with unconstrained access discriminants, though perhaps those aren't as bad. > Ok, now for a more specific point about this proposal. > > In implementation terms, the general idea is that within a return > statement that is returning from a function which was passed an > accessibility_level/storage_pool/whatever descriptor, you pass the > descriptor reference along whenever you call any function that also > takes such a descriptor parameter. Only if the function called in the return statement is at the *same* level as the function to which the return statement applies. > Consider the following example: > > function F (Controlling_Operand : Some_Tagged_Type; > Flag : Boolean := False) return > access Designated is > begin > return Result : access Designated do > declare > task T; > task body T is > procedure P is > begin > if Flag then > Result := new Designated; > else > Result := F (not Flag); > end if; > end P; > begin > P; > end T; > begin > null; > end; > end return; > end F; > > This example demonstrates (I think) that when you say "within a > return statement", you really mean it. The rule even applies inside > of nested procedures, tasks, etc. But that would mean that it would > even apply inside of a nested return statement (returning from a nested > function), and so now you have contradictory requrements (I think) > imposed by the two enclosing return statements. Not true, because the rule only applies if the called function is at the same level as the function to which the return statement applies. If you have a nested return statement, clearly the function called could only match at most one of them, and it is that one whose dynamic level is passed along. > ... The simple > solution (let the innermost return statement win) doesn't work. > Consider modifying the example so that a recursive call to F > Result := new F (not Flag); > occurs within a return statement within a function declared within > the declare block that is declared within F's (one and only) > return statement. > > I expect that we can work this particular corner case out, but it > seems likely that there are other dragons lurking in the vicinity. I don't think this particular one is a problem. But I will never underestimate your amazing ability to find bizarro cases. **************************************************************** From: Pascal Leroy Sent: Monday, November 5, 2007 3:10 PM > > What do folks think about this general approach? > > I think it depends how draconian is the restriction. > Disallowing them altogether for dispatching functions > would seems pretty draconian. Disallowing nested > extensions of types with such functions is less > draconian. I think once you do that, then you > can eliminate the issue with calling through > access-to-subprogram values, because such functions > no longer need to be passed a level. I am sympathetic to the notion of finding a way to disallow the problematic cases statically, if only because Tuck's proposed rule caused my brain to explode. However, I foresee difficulties with privacy and perhaps the contract model. If the function with an anonymous access result is declared in a private part, how do you disallow nested extensions outside of the package? Perhaps other rules of the language prevent problems in this case, but I wouldn't bet on it. **************************************************************** From: Stephen W. Baird Sent: Monday, November 5, 2007 3:51 PM Another option would be to introduce a rule that the Storage_Size of an access result type is "defined by the language to be zero" (4.8(5.3/2)). This would only help with the allocator-related problems mentioned in the AI, but it still might turn out to be a useful part of a more complete solution if it isn't rejected as being too restrictive. In any case, this rule doesn't seem to suffer from any of the contract model difficulties that you describe. **************************************************************** From: Tucker Taft Sent: Monday, November 5, 2007 9:40 PM > I am sympathetic to the notion of finding a way to disallow the > problematic cases statically, if only because Tuck's proposed rule > caused my brain to explode. ... My brain was exploding when I proposed it as well. How about this alternative: A) Don't do anything special (at the call site) for dispatching operations that return classwide or access-discriminated result types. Instead, just use the tag of the controlling operand to determine the run-time accessibility level of the result of calling such a dispatching function. For example: type T is tagged null record; type Has_Acc_Discrim(D : access Q) is null record; function Gizmo(X : T) return Has_Acc_Discrim; If Gizmo is a dispatching operation of T, then when we make a dispatching call on it using an actual parameter of type T'Class, we dispatch to a routine that is associated with the type identified by the tag of the actual. E.g: Y : T2; HAD_Obj : Has_Acc_Discrim renames Gizmo(T'Class(Y)); So what is the run-time accessibility level associated with HAD_Obj? With this model, it is determined by the run-time tag of the actual controlling operand, in this case "T'Class(Y)", i.e. the level implied by T2'Tag. We know that all controlling operands have the same tag, so there is no ambiguity. If the corresponding operation is actually inherited for the given descendant, that is OK, because the actual accessibility level of the return object is no deeper than that implied by the controlling tag. B) For dispatching operations with access result, we need to worry about two things, one, what sort of run-time accessibility check is performed, and two, what storage pool is used if the return statement consists of an allocator. I would propose we model an access result like "return access Q" roughly like "return Has_Acc_Discrim". That is, we treat the access result as though it were an access discriminant of an imaginary "wrapper" type. The context of the call-site would determine the storage pool to be used for an anonymous allocator in the return statement. The accessibility level check would be the same as that given above for A, namely we check against the level of the master of the function body, and the caller presumes the level is that of the actual controlling tag. To be more precise what I mean by using the context of the call-site to determine the storage pool, if the result of the call is converted explicitly or implicitly to another access type, the storage pool of that target access type is passed in as the storage pool for the access result. If the result of the call is dereferenced, then it is treated the same way a function returning an object with access discrims, and the context of the usage of the dereference determines the storage pool. The reason I like this rule is that it means that there is some chance you can avoid storage leaks associated with access results, while also solving the problem associated with calls on dispatching operations on nested type extensions. The current rule pretty much guarantees storage leaks, since the default storage pool from which the anonymous allocator allocates does not support reclamation, and it is at a level that means the result will often live forever. By instead determining the storage pool by the usage of the result, we can avoid the storage leak, while still remaining safe with respect to accessibility. For example: function Gizmo2(X : T) return access Q; ... type Z is access Q; R : Z := Gizmo2(blah); -- pass in storage pool associated with Z -- to be used by anonymous allocator S : Z := new Q'(Gizmo2(blurfo).all); -- again, here we pass in storage pool -- associated with Z, as we would have -- if Gizmo2 returned an object with -- access discrims V : access Q := Gizmo2(blech); -- pass in a storage pool associated with -- the implicit access type of V, essentially -- the local frame ... return Gizmo2(belch); -- pass in the storage pool associated with -- the result type, which if it is also "access Q" -- means the same storage pool passed into the -- enclosing function. ------------ I think the above approach gives an answer to the interesting questions, such as: 1) what run-time accessibility check is performed at a return statement when the result type is classwide, access-discriminated, or an access result? Answer: exactly what we currently say in Ada 2005 -- check against the level of the master that elab'ed the function body. 2) what run-time accessibility level does the result of a call on a dispatching function have? Answer: the same as that of the controlling tag of the call. 3) What storage pool should be used for access results? Answer: a storage pool passed in from the caller, determined by the context of the call, using a model where we treat the result as though it were an access discriminant of a "wrapper" result type. **************************************************************** From: Stephen W. Baird Sent: Tuesday, November 27, 2007 5:23 PM I don't like the general approach of the AI05-51 proposal we discussed at the Fairfax meeting (see Tuck's message of 11/06/07). In general, I feel that this proposal adds unnecessary complexity to the dynamic semantics of the language. By making the runtime accessibility checking rules of the language more complex, we would be making it harder to write reliable software. Consider the following example: procedure Foo is package Pkg is type T is tagged record Int : Integer; end record; function F (X : T) return access String; end Pkg; package body Pkg is type String_List_Node is record Item : access String; Link : access String_List_Node; end record; Allocated_Strings : access String_List_Node; function F (X : T) return access String is begin return Result : access String := new String'(Integer'Image (X.Int)) do Allocated_Strings := new String_List_Node' (Item => Result, Link => Allocated_Strings); end return; end F; end Pkg; begin declare type D is new Pkg.T with null record; X : D := (Int => 123); procedure Check (F_Result : access String) is Check_Failed : exception; begin if F_Result.all /= Integer'Image (X.Int) then raise Check_Failed; end if; end Check; begin Check (X.F); Check (Pkg.T (X).F); end; end Foo; As I understand the proposed changes, this example would raise Program_Error during the evaluation of the aggregate (specifically, during the type conversion associated with the "Item => Result" portion of the aggregate). I think that users would find this surprising. I believe that this example should execute without raising an exception. It's clear that we have make changes of some kind to deal with the problems identified in the AI. Let's consider another alternative. The root cause of these problems is a disagreement between the accessibility level of the function named in a call and the accessibility level of the function whose body is executed. I would like to eliminate this case without imposing draconian restrictions. Very roughly speaking, the idea is to get a function to behave as though it were declared at the least deeply nested level from which it could be called. If a function is callable from a less nested level than where it is declared, then that is part of the contract which the function's body is responsible for honoring. We define a new accessibility level, the "inherited accessibility level" (for lack of a better name), of a subprogram as the most deeply nested accessibility level that satisfies the following conditions: The inherited accessibility level of a subprogram is not deeper than the accessibility level of the subprogram. The inherited accessibility level of an inherited subprogram is not deeper than the inherited accessibility level of the subprogram's parent subprogram. The inherited accessibility level of a dispatching subprogram is not deeper than the inherited accessibility level of any subprogram which the given subprogram overrides. The function result accessibility checks discussed in the AI are then performed relative to the inherited accessibility level of the function, as opposed to the "normal" accessibility level of the function. Similarly, the properties of an (anonymous) access result type (including its accessibility level) are determined by the inherited accessibility level of the function. In determining this new accessibility level, there are no dependencies on dynamic values such as the tag of the controlling operand or the subprogram view referenced by the caller. A few legality rules would be needed in order to deal with some corner cases. In the case of a dispatching operation which is declared as a rename of some other subprogram, the inherited accessibility level of the dispatching operation and of the renamed subprogram must match. We don't want to allow declare package Outer is type T1 is tagged null record; function F1 (X : T1) return access String; end Outer; ... ; begin declare package Inner is type T2 is new Outer.T1 with null record; not overriding function F2 (X : T2) return access String; overriding function F1 (X : T2) return access String renames F2; end Inner; ... ; begin ...; end; end; , because Inner.F2's inherited accessibility level is deeper than Inner.F1's. In the case of a dispatching inherited subprogram which is not overridden, the inherited accessibility level of the subprogram and of its parent subprogram must match. We don't want to allow declare package Outer is type Ifc is interface; function F (X : Ifc) return access String is abstract; end Outer; begin declare package Inner1 is type T1 is tagged null record; function F (X : T1) return access String; end Inner1; package Inner2 is type T2 is new Inner1.T1 and Outer.Ifc with null record; end Inner; ...; begin ...; end; end; If this were allowed, a call to Outer.F might end up executing the body of Inner1.F, thereby violating the rule that a function's inherited accessibility level corresponds to the least deeply nested level from which the function might be called. These restrictions are only needed if the subprogram is a function with an "interesting" result type (anonymous access, class-wide, or has access discriminants). I believe they are not needed for an abstract subprogram. Does this approach look like it would work? Does it look preferable to the Fairfax proposal? I don't like the name "inherited accessibility level" very much. Any suggestions? Would "caller's accessibility level" or "exported accessibility level" be better? **************************************************************** From: Tucker Taft Sent: Wednesday, November 28, 2007 11:15 AM > I don't like the general approach of the AI05-51 proposal we > discussed at the Fairfax meeting (see Tuck's message > of 11/06/07). > > In general, I feel that this proposal adds > unnecessary complexity to the dynamic semantics of > the language. By making the runtime accessibility > checking rules of the language more complex, we > would be making it harder to write reliable software. On the other hand, it seems valuable to me to have the same rules for anonymous allocators as access discriminants and as "unwrapped" values in a return statement. That is, the rules proposed on 11/6 have the advantage of uniformity, where: return new T; and return (Discrim => new T, ...); both use a storage pool determined by the caller (presuming the access types are anonymous in both cases). > Consider the following example: > > procedure Foo is > package Pkg is > type T is tagged > record > Int : Integer; > end record; > function F (X : T) return access String; > end Pkg; > > package body Pkg is > type String_List_Node is > record > Item : access String; > Link : access String_List_Node; > end record; > > Allocated_Strings : access String_List_Node; > > function F (X : T) return access String is > begin > return Result : access String := > new String'(Integer'Image (X.Int)) do > Allocated_Strings := > new String_List_Node' > (Item => Result, > Link => Allocated_Strings); > end return; It seems easy enough to avoid using the storage pool provided by the caller if that is desirable, by allocating the result as part of the creation of the String_List_Node, and then returning that: Allocated_Strings := new String_List_Node' (Item => new String'(Integer'Image (X.Int)), Link => Allocated_Strings); return Allocated_Strings.Item; It seems that programmers will need to get used to the idea that the storage pool for anonymous allocators in a return statement are determined by the caller. To have different rules for whether the anonymous allocators are inside or outside an aggregate seems potentially more error prone, and has the added advantage of avoiding a certain storage leak. > end F; > end Pkg; > begin > declare > type D is new Pkg.T with null record; > X : D := (Int => 123); > > procedure Check (F_Result : access String) is > Check_Failed : exception; > begin > if F_Result.all /= Integer'Image (X.Int) then > raise Check_Failed; > end if; > end Check; > begin > Check (X.F); > Check (Pkg.T (X).F); > end; > end Foo; > > As I understand the proposed changes, this example > would raise Program_Error during the evaluation of > the aggregate (specifically, during the type > conversion associated with the "Item => Result" > portion of the aggregate). A similar result would occur if Result were an object with an access discriminant, and the programmer attempted to string the value of the access discriminant on a local list before returning it. > I think that users would find this surprising. > I believe that this example should execute without > raising an exception. Clearly this is a matter of expectation. Programmers will not be surprised if they understand the general rule that anonymous allocators in return statements use the caller's storage pool. That makes sense since the primary user of the result is the caller. If the programmer wants to squirrel away a copy of something before it is returned, they clearly have a special case and will have to use special care to avoid dangling references. > It's clear that we have make changes of some kind > to deal with the problems identified in the AI. > Let's consider another alternative. > > The root cause of these problems is a disagreement > between the accessibility level of the function > named in a call and the accessibility level of the > function whose body is executed. I would claim that isn't really the heart of the problem for anonymous allocators. There it is that the caller uses the result of the anonymous allocator in a particular context, and it is not helpful to allocate in a storage pool over which the caller has no control. Having anonymous allocators defining all or part of a return value is new with Ada 2005. It seems quite helpful if all such anonymous allocators are treated similarly, whether they are used to define the whole return value, or an access discriminant of one. > ... I would like to > eliminate this case without imposing draconian > restrictions. Very roughly speaking, the idea is to > get a function to behave as though it were declared > at the least deeply nested level from which it > could be called. If a function is callable from a > less nested level than where it is declared, then > that is part of the contract which the function's > body is responsible for honoring. > > We define a new accessibility level, the "inherited > accessibility level" (for lack of a better name), > of a subprogram as the most deeply nested > accessibility level that satisfies the following > conditions: > > The inherited accessibility level of a subprogram > is not deeper than the accessibility level of the > subprogram. > > The inherited accessibility level of an inherited > subprogram is not deeper than the inherited > accessibility level of the subprogram's parent > subprogram. > > The inherited accessibility level of a > dispatching subprogram is not deeper than the > inherited accessibility level of any subprogram > which the given subprogram overrides. > > The function result accessibility checks discussed > in the AI are then performed relative to the > inherited accessibility level of the function, as > opposed to the "normal" accessibility level of > the function. Similarly, the properties of an > (anonymous) access result type (including its > accessibility level) are determined by the > inherited accessibility level of the function. > In determining this new accessibility level, there > are no dependencies on dynamic values such as > the tag of the controlling operand or the > subprogram view referenced by the caller. > ... > Does this approach look like it would work? > Does it look preferable to the Fairfax proposal? I'm not very fond of effectively pushing the accessibility level up to the global level in most cases, and making a storage leak even more likely. Once we accept that anonymous allocators in return statements generally depend on the context provided by the caller, it seems natural for this to apply to access results as well as access discriminants of results. **************************************************************** From: Randy Brukardt Sent: Wednesday, November 28, 2007 9:40 PM > > In general, I feel that this proposal adds > > unnecessary complexity to the dynamic semantics of > > the language. By making the runtime accessibility > > checking rules of the language more complex, we > > would be making it harder to write reliable software. > > On the other hand, it seems valuable to me to have > the same rules for anonymous allocators as access > discriminants and as "unwrapped" values in a return > statement. That is, the rules proposed on 11/6 have the > advantage of uniformity, where: > > return new T; > and > return (Discrim => new T, ...); > > both use a storage pool determined by the caller (presuming > the access types are anonymous in both cases). Surely having them illegal would have that effect. ;-) But I'm not sure I agree with your basic point. In the case of an access discriminant, the discriminant cannot be changed on the target object; either that object is already constrained (and this one had better match) or this is an initial value, in which case your point holds. But in the case of an access return, the returned access can be used in many ways, and there is no reason to assume that the level of the call is any more correct than the level of the function. For instance, consider: function New_Node return access T is begin return new T; end New_Node; type List is access all T; My_List : List; Another_List : access T; procedure Yuck is begin if ... My_List := List(New_Node); else Another_List := New_Node; end if; end Yuck; With the rules as they are, this will work as expected. With the "level of the call" rule, this will have fail an accessibility check, because List is library-level, and surely the level of the call is nested in Yuck. (And the same is true for the accessibility level of the anonymous type of Another_List.) The net effect is that the correct pool for an access return depends on how it is going to be used: it's not the call that matters, it is how the result of the call is "consumed". I'm not sure that there is much point to a rule with complex dynamic semantics unless it can properly handle simple cases like these. I could imagine trying to factor the accessibility of the target type into this, but that just seems too complex to me. The net is that I don't think we should even be allowing allocators for access results, because we have no idea what the appropriate pool is (it should probably be that of List for the first call). In the access discriminant case, the (ultimate) caller's level seems to be the right answer (in that the object has to be declared there), but that doesn't make sense for access results. It makes sense to be consistent, but only as long as the result makes sense. Allocators for access results don't seem to make sense. (One could argue that the entire access result thing is an attempt to be overly consistent, but it seems useful to replace the misguided return-by-reference -- but we don't need allocators to do that). ... > > I think that users would find this surprising. > > I believe that this example should execute without > > raising an exception. > > Clearly this is a matter of expectation. Programmers > will not be surprised if they understand the general > rule that anonymous allocators in return statements > use the caller's storage pool. That makes sense since > the primary user of the result is the caller. If > the programmer wants to squirrel away a copy of something > before it is returned, they clearly have a special > case and will have to use special care to avoid > dangling references. As I showed above, the caller can't squirrel it away, either (with your proposed model), so when/how can they do it???? The fact is that your proposed rules are next to useless, because they almost never do what the user really wants. Indeed, *any* rules that try to guess the user intent for access results are going to fail, because there are so many things that could be usefully done. It makes more sense to *make* the user say what pool they want (by using a named access type) for an allocator in a return statement, and that is easily accomplished by defining the storage pool to be zero for access result types. Why cause our heads to explode for this one??? ... > I would claim that isn't really the heart of the > problem for anonymous allocators. There it is that the > caller uses the result of the anonymous allocator > in a particular context, and it is not helpful > to allocate in a storage pool over which the > caller has no control. Absolutely right. But even more than that, it doesn't make sense to allocate in the *wrong* storage pool, either: only the programmer knows what they want, and the language should not be in the business of guessing what it is. Make them write it out. > Having anonymous allocators defining all or part > of a return value is new with Ada 2005. It seems > quite helpful if all such anonymous allocators are > treated similarly, whether they are used to define > the whole return value, or an access discriminant > of one. And a horrible mistake, at that. Instead of getting rid of the mistake, we've made it 10 times worse. Grrrr. P.S. I'd suggest making all allocators for anonymous access types illegal, too, but I think people are getting tired of hearing that. ;-) [They logically leak, and fooling with the rules to "define" that away does nothing to make the language more consistent or usable. Our implementation leaks for all such allocators, and no one has ever complained about that...] **************************************************************** From: Tucker Taft Sent: Wednesday, November 28, 2007 10:36 PM I don't have time to give a full response at this moment, but when I say it uses caller "context" to determine the storage pool, I mean it does something analogous to what we have defined to happen for functions that return limited types. That is, the level is not that of the function call, but rather a level determined by how the value is used. Effectively for an anonymous access type, the level/storage pool used for the anonymous allocator in the return statement is determined by the level/storage pool of the type to which the result is converted at the call site. I gave more details in my original proposal of 11/6, I believe. More later... **************************************************************** From: Randy Brukardt Sent: Wednesday, November 28, 2007 11:33 PM ... > I don't have time to give a full response at > this moment, but when I say it uses > caller "context" to determine the storage pool, > I mean it does something analogous to what > we have defined to happen for functions that > return limited types. Riiiggghhhttt... But that sort of definition doesn't take assignment into account (it's not necessary for limited return types), and adding that complicates things a lot. (And they're already too complicated.) Your note of 11/6 says that access return types would be treated like they were the discriminants of an anonymous and invisible wrapper object. But that is precisely the model that I was objecting to, because while there is no obvious problem with access discriminants (since they can't be assigned into an existing object), that model doesn't work with bare access types (since they can be assigned, and usually would be). I'll wait for your full rebuttal before writing more. (Well, at least I'll try... ;-) **************************************************************** From: Randy Brukardt Sent: Monday, January 28, 2008 9:37 PM Tucker wrote: > Here is another attempt at AI-51 [this is version /04 - ED], > addressing Steve's issue with what happens when a function > that has an accessibility check upon return > based on its level, is reached via a dispatching > call through a name that denotes a primitive > declared at an outer level. The problem is that > the caller and the callee don't agree about > the accessibility level. Your wording has (in several places): "...is determined as though the allocator were in place of the call of the function..." "were in place of" doesn't make much sense to me. I know that you mean "...as though the allocator were given at the place of the call..." -- so why didn't you say that? I think it's a lot clearer. --- The wording change of 6.5(8/2) doesn't take AI05-0024-1 into account. That's OK, other than that it means that a simple level comparision will not do for this check. That means that the check will be quite expensive - and that means that something that looks cheap (anonymous return types) will be anything but cheap. Since the level will be passed in, there won't be any practical way (short of inlining) to eliminate this check (unlike the current semantics). --- You said in your discussion: > Here is an example. In the following, the > initialization of X and the initialization of Y > produce essentially the same thing, namely a pointer > to a heap object allocated from the storage pool > associated with Lim_T_Ptr: > > function Blurfo return Lim_T is ... > > function Blah return access Lim_T is > begin > return new Lim_T'(Blurfo); > end Blah; > > type Lim_T_Ptr is access Lim_T; > > X : Lim_T_Ptr := new Lim_T'(Blurfo); > Y : Lim_T_Ptr := Lim_T_Ptr(Blah); You didn't mention it, but you have a hack so this works when nested. For instance: procedure Something is X : Lim_T_Ptr := new Lim_T'(Blurfo); -- OK Y : Lim_T_Ptr := Lim_T_Ptr(Blah); -- OK, because the level is taken from the -- type conversion, not the call. begin null; end Something; But it depends on the type conversion being at the point of the call (which is why I call it a hack). That isn't necessarily true. Consider: procedure Do_Something (A : access Lim_T) is X : Lim_T_Ptr := Lim_T_Ptr(A); begin ... end Do_Something; Do_Something (new Lim_T'(Blurfo)); -- OK. Do_Something (Blah); -- OK. procedure Nested is begin Do_Something (new Lim_T'(Blurfo)); -- OK. Do_Something (Blah); -- Runtime error. Yuck. end Nested; **************************************************************** From: Tucker Taft Sent: Monday, January 28, 2008 10:27 PM ... > Your wording has (in several places): > > "...is determined as though the allocator were in place of the call of the > function..." > > "were in place of" doesn't make much sense to me. I know that you mean > "...as though the allocator were given at the place of the call..." -- so > why didn't you say that? I think it's a lot clearer. Actually, I meant what I wrote. I'm using the phrase "be in place of" in its sense of "substitute." I think it is grammatically correct to use the subjunctive "as though A were in place of B" in the sense of "as though A were substituted for B." Perhaps I should have used the word "substituted." > --- > > The wording change of 6.5(8/2) doesn't take AI05-0024-1 into account. That's > OK, other than that it means that a simple level comparision will not do for > this check. That means that the check will be quite expensive - and that > means that something that looks cheap (anonymous return types) will be > anything but cheap. Since the level will be passed in, there won't be any > practical way (short of inlining) to eliminate this check (unlike the > current semantics). The change in 6.5(8/2) deals with a special case having to do with accept statements. I'm not too worried if code inside of accept statements that fiddles around with accessibility levels is a bit more expensive. Do you think there is some distributed overhead as well? I think the check will generally be a simple level comparison, similar to the one currently used with access parameters. ... > But it depends on the type conversion being at the point of the call (which > is why I call it a hack). I think you are mistaken (see below). > ... That isn't necessarily true. Consider: > > procedure Do_Something (A : access Lim_T) is > X : Lim_T_Ptr := Lim_T_Ptr(A); > begin > ... > end Do_Something; > > Do_Something (new Lim_T'(Blurfo)); -- OK. > Do_Something (Blah); -- OK. These both fail the accessibility check associated with the type conversion, because they are both allocated at a level associated with the innermost master of the call, which in this case is the procedure call statement. That is more nested than Lim_T_Ptr. > procedure Nested is > begin > Do_Something (new Lim_T'(Blurfo)); -- OK. > Do_Something (Blah); -- Runtime error. Yuck. These also both fail. > end Nested; ***************************************************************** From: Randy Brukardt Sent: Monday, January 28, 2008 10:54 PM ... > Actually, I meant what I wrote. I'm using the phrase > "be in place of" in its sense of "substitute." I think > it is grammatically correct to use the subjunctive > "as though A were in place of B" in the sense of > "as though A were substituted for B." Perhaps > I should have used the word "substituted." I think "substituted" would be better. "in place of" is much too close to "at the place of". But I still think "given at the place of" is best, because I don't think "substitute" quite reflects what is going on (especially in the case of access discriminants, where you surely aren't substituting the whole call with just one of the discriminants). > > --- > > > > The wording change of 6.5(8/2) doesn't take AI05-0024-1 into account. That's > > OK, other than that it means that a simple level comparision will not do for > > this check. That means that the check will be quite expensive - and that > > means that something that looks cheap (anonymous return types) will be > > anything but cheap. Since the level will be passed in, there won't be any > > practical way (short of inlining) to eliminate this check (unlike the > > current semantics). > > The change in 6.5(8/2) deals with a special case > having to do with accept statements. I'm not > too worried if code inside of accept statements > that fiddles around with accessibility levels > is a bit more expensive. Do you think there is > some distributed overhead as well? I think the > check will generally be a simple level comparison, > similar to the one currently used with access > parameters. I do think that there is some distributed overhead that comes from this rule. With the old rule, most of the accessibility checks for return statements can be done at compile-time, so there is no check code at all. With this rule, all functions need check code. And that check code cannot be a "simple level comparison", because that would allow bad stuff (the accept cases we've talked about). A function cannot generate code assuming how it will be called! ... > > ... That isn't necessarily true. Consider: > > > > procedure Do_Something (A : access Lim_T) is > > X : Lim_T_Ptr := Lim_T_Ptr(A); > > begin > > ... > > end Do_Something; > > > > Do_Something (new Lim_T'(Blurfo)); -- OK. > > Do_Something (Blah); -- OK. > > These both fail the accessibility check associated > with the type conversion, because they are both > allocated at a level associated with the innermost > master of the call, which in this case is the > procedure call statement. That is more nested than > Lim_T_Ptr. Yuck! Again, an anonymous access type does the wrong thing. Is there any case where accessibility and/or anonymous access types actually do something useful in Ada?? (No, don't answer that. ;-) Anonymous access types are one of the worst ideas ever put into Ada. All of bizarre rules for accessibility, coextensions, and finalization issues -- and there is no sign that we're remotely close to getting these "right". And then they're not really intuitive. All of this crap because we were unwilling to have in out parameters on functions. Bleeh! ***************************************************************** From: Tucker Taft Sent: Monday, January 28, 2008 11:34 PM > I do think that there is some distributed overhead that comes from this > rule. With the old rule, most of the accessibility checks for return > statements can be done at compile-time, so there is no check code at all. > With this rule, all functions need check code. And that check code cannot be > a "simple level comparison", because that would allow bad stuff (the accept > cases we've talked about). A function cannot generate code assuming how it > will be called! I'll have to restudy AI05-0024-1, but I don't think most code needs to worry about it at all, unless it is *lexically* nested inside of a task body. Are you implying that any old function that gets an access parameter can't use a simple level check to see whether it can convert it to a named access type, presuming it is not lexically inside a task body? That surprises me, to say the least. ***************************************************************** From: Randy Brukardt Sent: Monday, January 28, 2008 11:59 PM Umm, 6.5(8/2) is the *class-wide* accessibility check, not the access accessibility check. Although it looks like the access check gets involved too. And it's pretty obvious that you can trigger the problem in a function. Here's the original example, suitably changed: declare type T1 is tagged null record; function Something (Default : in T1'Class) return access T1'Class is begin ... return new T1'Class(Default); end Something; task T is entry E (X : T1'Class); end T; task body T is type Ref2 is access T1'Class; R2 : Ref2; begin accept E (X : T1'Class) do R2 := new T1'Class'(X); -- Original problem case. R2 := Ref2(Something (X)); -- New problem case. end; -- Delay for a while and then do nasty things with R2. end; procedure Proc is type T2 is new T1 with null record; X2 : T2; begin T.E (X2); end; begin Proc; end; In the accept statement, X is passed in and the complex check is needed for the direct allocator (otherwise we get trouble because the type has gone before the object). By your proposed rules, the call to Something has the same accessibility as Ref2, and thus the allocator inside of Something needs the same sort of complex check as the allocator directly inside of the accept statement. Notice that the function and the task could declared far apart. Indeed, the function has to be capable of handling the case, even if there is no task in existence. Moreover, unless you are willing to peek into the body, the function has to have the complex check (and the complex data passed in to support it) even if it doesn't return the classwide object. A similar check is needed for a class-wide object return (in this case, if Something returned Obj directly). I'm not sure if there is any way to trigger this problem without a tagged parameter. If not, I suppose it would be possible to have two kinds of checks, and only use the complex one if the profile includes a tagged parameter and returns a class-wide type or tagged type. But this sure sounds distributed to me... ***************************************************************** From: Randy Brukardt Sent: Tuesday, January 29, 2008 12:09 AM > I'll have to restudy AI05-0024-1, but I don't think most code > needs to worry about it at all, unless it is *lexically* nested > inside of a task body. Under your new rule, it only matters where the *call* is lexically nested. I suppose you could generate two bodies for every affected function, one with a simple check and one with the complex check. That would eliminate the overhead other than in the bad calls. But if you're seriously suggesting that, I'd have to suggest a check of your sanity... ***************************************************************** From: Randy Brukardt Sent: Tuesday, January 29, 2008 12:14 AM (Sorry, I keep hitting "Send" and then thinking of something else I should have said...) ... > Are you implying that any old function > that gets an access parameter can't use a simple level > check to see whether it can convert it to a named access > type, presuming it is not lexically inside a task body? Possibly, since you can call a function from inside aof an accept statement. If Something had been declared: function Something (Ptr : access T1'Class) return access T1'Class is begin return Ptr; end Something; and the call was R2 := Ref2(Something (X'Access)); -- Still gotta problem. But I think the parameter has to be access-to-tagged to cause the problem, so it isn't *every* function. Note that the class-wide check has a similar issue: function Something (Ptr : not null access T1'Class) return T1'Class is begin return Ptr.all; end Something; ***************************************************************** From: Tucker Taft Sent: Tuesday, January 29, 2008 12:33 AM One way to minimize the distributed overhead would be to only do the more expensive check if the simple level-based check *fails.* Presumably the passed-in level can be chosen so that it will be quite rare that the expensive check will pass while the cheap check will fail. And the more expensive check seems to be relevant only when checking a class-wide type. ***************************************************************** From: Randy Brukardt Sent: Tuesday, January 29, 2008 12:49 AM I suppose, but I would expect that the primary extra cost would simply be the need to pass in the more complex data needed to do the complex check. (And to pass it along with access parameters.) Most of the time, even the expensive check will succeed very quickly (most things are library level, after all, or nested a single level). So I'm not sure it is worth it to duplicate that code. But surely there is a distributed space overhead, because of the more complex checks and the more complex data to support them. ***************************************************************** From: Tucker Taft Sent: Tuesday, January 29, 2008 7:55 AM ... > Possibly, since you can call a function from inside aof an accept statement. > If Something had been declared: > function Something (Ptr : access T1'Class) return access T1'Class is > begin > return Ptr; > end Something; > > and the call was > > R2 := Ref2(Something (X'Access)); -- Still gotta problem. I don't think so, since access parameters can't be passed via an entry call, so you never get two different stacks involved in the accessibility of an access parameter. So I believe you never need more than a simple level for an access parameter. ***************************************************************** From: Tucker Taft Sent: Tuesday, January 29, 2008 7:58 AM I think your access parameter example is incorrect. I believe the only time you get two different stacks involved in accessibility is a check on the level associated with a tag, and then presumably the information you need is carried with the tag or with the allocation context you need to pass for these kinds of functions anyway. ***************************************************************** From: Randy Brukardt Sent: Tuesday, January 29, 2008 1:18 PM > I don't think so, since access parameters can't be passed > via an entry call, so you never get two different stacks > involved in the accessibility of an access parameter. > So I believe you never need more than a simple level for > an access parameter. Humm, I think you're wrong. The two stacks get involved because I took the 'Access of an item on a different stack. Perhaps it's easier to see in a complete example: declare type T1 is tagged null record; function Something (Ptr : access T1'Class) return access T1'Class is begin return Ptr; end Something; task T is entry E (X : T1'Class); end T; task body T is type Ref2 is access T1'Class; R2 : Ref2; begin accept E (X : T1'Class) do R2 := new T1'Class'(X); -- Original problem case. R2 := Ref2(Something (X'Access)); -- New problem case. end; -- Delay for a while and then do nasty things with R2. end; procedure Proc is type T2 is new T1 with null record; X2 : T2; begin T.E (X2); end; begin Proc; end; Since the accessibility of the anonymous return subtype comes from the call in your new rule, you have to be able to make the full check. And the accessibility of anonymous access parameters come from the actual object. So it looks like we have the full complexity here. ***************************************************************** From: Tucker Taft Sent: Tuesday, January 29, 2008 12:10 PM When you take 'Access of a formal parameter of a tagged type, it is treated like a local variable. Hence, in your example the accessibility passed via the access parameter associated with "X'Access" is guaranteed to be on the same stack as the task itself, and can't come from the entry caller's stack. See below. ... > Humm, I think you're wrong. The two stacks get involved because I took the > 'Access of an item on a different stack. You took 'Access of a formal parameter, and that is always treated like a local variable (since "normal" formals don't carry along their own accessibility level from the actual -- only access parameters do, and we don't allow those). > ... Perhaps it's easier to see in a > complete example: > > declare > type T1 is tagged null record; > > function Something (Ptr : access T1'Class) return access T1'Class is > begin > return Ptr; > end Something; > > task T is > entry E (X : T1'Class); > end T; > > task body T is > type Ref2 is access T1'Class; > R2 : Ref2; > begin > accept E (X : T1'Class) do > R2 := new T1'Class'(X); -- Original problem case. > R2 := Ref2(Something (X'Access)); -- New problem case. > end; > -- Delay for a while and then do nasty things with R2. > end; > > procedure Proc is > type T2 is new T1 with null record; > X2 : T2; > begin > T.E (X2); > end; > > begin > Proc; > end; > > Since the accessibility of the anonymous return subtype comes from the call > in your new rule, you have to be able to make the full check. And the > accessibility of anonymous access parameters come from the actual object. True, but the actual object (X) in this case is a formal parameter, which is treated like a local variable. It is true that the accessibility associated with the tag of T2 has the "full complexity," but there are never two stacks involved when passing the accessibility level of an access parameter. > ... So > it looks like we have the full complexity here. Access parameters never have the "full" complexity, thanks to the fact that we don't allow them in entry calls. ***************************************************************** From: Tucker Taft Sent: Monday, February 4, 2008 5:06 PM Randy pointed out that we had already revised 6.5(8/2), which this AI also touches. So this version [/05 - ED] is based on the latest 6.5(8/2), but is otherwise identical to version 4 sent earlier. ***************************************************************** From: Tucker Taft Sent: Monday, July 21, 2008 3:07 PM Here is an update to AI-51, incorporating the wording changes from AI-75. [This is version /07 of the AI - ED.] These AI's address accessibility checks on function return and on allocators when the designated type is classwide, and/or the function is a primitive of a nested extension. ***************************************************************** From: Robert Dewar Sent: Monday, July 21, 2008 3:14 PM Re: AI05-51 + AI05-75 -- fun with accessibility AARGH fun + accessibility in the same sentence does not compute! does not compute! smoke starts coming out of head :-) ***************************************************************** From: Bob Duff Sent: Sunday, April 12, 2009 2:36 PM This is my homework, "Create an static alternative to AI-51." Add to 3.10.2: The accessibility level of an anonymous access function result type is that of the function, except in the case of an overriding function, in which case it is that of the ultimate ancestor of the controlling type. Actually, I think this alternative is better: The accessibility level of an anonymous access function result type is that of the designated subtype. Replace 6.5(5.6/2): - If the result subtype of the function is class-wide, the accessibility level of the type of the expression of the return statement shall not be statically deeper than that of the master that elaborated the function body. If the result subtype has one or more unconstrained access discriminants, the accessibility level of the anonymous access type of each access discriminant, as determined by the expression of the simple_return_statement or the return_subtype_indication, shall not be statically deeper than that of the master that elaborated the function body. with two bulleted items: - If the result subtype of the function is class-wide, the accessibility level of the type of the expression of the return statement shall not be statically deeper than that of the master that elaborated the ultimate ancestor of the result type. - If the subtype determined by the expression of the simple_return_statement or by the return_subtype_indication has one or more access discriminants, the accessibility level of the anonymous access type of each access discriminant shall not be statically deeper than that of the master that elaborated the ultimate ancestor of the result type. This seems vastly simpler, in both language terms and implementation terms, than the dynamic proposal in the current AI-51. ***************************************************************** From: Bob Duff Sent: Sunday, April 12, 2009 2:49 PM I realize it is also somewhat limiting. I think that's just fine. But we've also discussed some way of makeing accessibility part of the contract, which would alleviate the limitations. ***************************************************************** From: Tucker Taft Sent: Sunday, April 12, 2009 6:40 PM I feel pretty strongly that if we come up with a satisfactory solution for build-in-place results with access discriminants, then we can piggy-back on those rules for access results, with no particular added implementation burden, and a much more useful and symmetrical set of features. Let's wait until we discuss Randy's proposals for build-in-place results before deciding about access results. ***************************************************************** From: Steve Baird Sent: Monday, April 13, 2009 11:13 AM I think the general approach is appealing. I have been attracted for a long time to the idea that in the case where an overriding primitive function of a tagged type has an anonymous access result type, then the accessibility level of the overrider's result type matches that of the overridden function's result type. The problem with this (and, I think, with most approaches that are defined in terms of an "ultimate ancestor") is interface types. You have a non-library-level root type. At the same level, you have an extension which also implements a library-level interface type. How does this work? ---- When you refer to "the accessibility level ... of the designated subtype", what exactly do you mean? I strongly feel that a constraintless subtype declaration should behave like a rename. Changing function F return access Some_Type_With_A_Very_Long_Name; to subtype Some_Type is Some_Type_With_A_Very_Long_Name; function F return access Some_Type; shouldn't change the behavior of the program. Do you agree? ***************************************************************** From: Steve Baird Sent: Thursday, April 9, 2009 11:40 AM Consider the following example: procedure Defaulted_Access_Discrim is X : aliased Integer; type Discriminated (D : access Integer := X'Access) is limited null record; type Undiscriminated is limited record Comp : Discriminated; end record; type Ref is access Undiscriminated; Ptr : Ref; procedure P is Y : aliased Integer := 0; function F return Undiscriminated is begin return Z : Undiscriminated := (Comp => (D => Y'Access)); end F; begin Ptr := new Undiscriminated'(F); end P; begin P; -- if we reach this point, Ptr.all.Comp.D is dangling end Defaulted_Access_Discrim; What language rule (static or dynamic) is supposed to prevent the generation of a dangling reference in this case? Is the runtime check of 3.10.2(29) supposed to catch this? I don't think so; the "hand off" described in 3.10.2(10.1/2) has not taken place yet at the time of the check and so the accessibility level of Z is deeper than that of Y. It's not clear (to me) whether 3.10.2(12/2) applies here - is a subcomponent of a (nondiscriminant) component considered to be "in the expression ... of a return statement"? - but the runtime check would not fail even if it does apply. ***************************************************************** From: Tucker Taft Sent: Thursday, April 9, 2009 2:47 PM This looks like another job for good old AI05-51. Currently it proposes for 6.5(21/2): If the [result subtype]{return object} of a function has one or more [unconstrained] access discriminants {whose value is not constrained by the result subtype of the function}, a check is made that the accessibility level of the anonymous access type of each access discriminant, as determined by the expression or the return_subtype_indication of the function, is not deeper than [that of the master that elaborated the function body] {the level of the return object as determined by the point of call (see 3.10.2)}. If this check fails, Program_Error is raised. I believe this may need to say something like: If any part or coextension of the return object of a function has one or more access discriminants whose value is not determined by the result subtype of the function, ... I'm surprised it didn't already cover coextensions, but it clearly also needs to cover "parts" based on your example. I think the above change would solve your problem. ***************************************************************** From: Steve Baird Sent: Thursday, April 9, 2009 2:47 PM The general approach sounds fine (although an AARM note explaining that this only imposes overhead in some very obscure cases might be worth including). Do we need a generalization of the term "part" that includes coextensions? Note that the phrase "part or coextension of a return object" does not include a subcomponent of a coextension. I understand that you were not proposing formal wording here. *****************************************************************