!standard 6.5(21/2) 07-05-04 AI05-0051-1/01 !class binding interpretation 07-05-04 !status work item 07-05-04 !status received 07-04-19 !priority Medium !difficulty Easy !qualifier Clarification !subject Accessibility of dispatching function calls (aka another Baird question) !summary *TBD* 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? !recommendation Scream, lest your head explode. !wording *TBD* !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. !ACATS test !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... ****************************************************************