!standard E.4(8) 13-10-07 AI12-0034-1/05 !class binding interpretation 12-06-06 !status Amendment 202x 13-07-17 !status ARG Approved 9-0-0 13-06-16 !status work item 12-06-06 !status received 12-05-11 !priority Low !difficulty Medium !subject Remote stream attribute calls !summary Dereferencing a remote access-to-class-wide type to make a dispatching call to a stream attribute is not allowed. !question It should not be possible to make a remote stream attribute call (because such a stream attribute call by definition could not be used in marshalling and unmarshalling), and because all stream attributes contain an anonymous access type that does not support external streaming. However, it is certainly possible to do this with a remote access-to-class-wide type. For instance: with Ada.Streams; package Pack1 is pragma Pure; type Root is tagged limited private; procedure Prim (N : Integer; X : Root); package Inner is -- needed to make Do_Write non-primitive procedure Do_Write (Stream : access Ada.Streams.Root_Stream_Type'Class; X : Root); end Inner; for Root'Write use Inner.Do_Write; private type Root is tagged limited record F1 : Integer; end record; end Pack1; with Pack1; package Pack2 is pragma Remote_Types; type Root_Acc is access all Pack1.Root'Class; end Pack2; with Pack1, Pack2, Ada.Streams; procedure Test1 (The_Obj : Pack2.Root_Acc) is begin Pack1.Root'Class'Write (Stream, The_Obj.all); -- Legal? (No.) Pack1.Inner.Do_Write (Stream, The_Obj.all); -- Illegal. Pack1.Prim (4, The_Obj.all); -- OK. end Test1; Should this be fixed? (Yes.) !recommendation (See wording.) !wording Modify E.2.2(16): * A value of a remote access-to-class-wide type shall be dereferenced (or implicitly converted to an anonymous access type) only as part of a dispatching call {to a primitive operation of the designated type} where the value designates a controlling operand of the call (see E.4, "Remote Subprogram Calls"); AARM Ramification: Stream attributes of the designated type are not primitive operations of the designated type, and thus remote calls to them are prohibited by this rule. This is good, as the access parameter of a stream attribute does not have external streaming, and thus cannot be a parameter of a remote call. [Editor's note: This is the solution proposed by Thomas Quinot. He's right that stream attributes aren't primitive operations of any type (see the definition on 3.2.3(2-7)), so this works. Note that this is suspiously similar to the original solution provided by Adam in the original comment.] !discussion We could require the root designated type to not have available stream attributes. However, this solution is incompatible, and potentially penalizes users that do not use any problematic calls to stream attributes. We can't ban the declarations of the subprograms like we do for other subprograms with access parameters, because that would make it impossible to define marshalling and unmarshalling for these types. Which also would make remote calls impossible, defeating the entire purpose. So we modify E.2.2(16) to prevent this specific problem. This leaves the concern that the door is left open for other ways to cause the problem to be uncovered. It also complicates the model (there is nothing wrong with the dereference, given that it is used in a dispatching call); the problem is that the call has a profile that shouldn't be allowed. Blaming that on the access type usage seems wrong. But this solution is compatible with all programs except those that actually try to do the bad thing. Thus it seems the best of a bad lot of solutions. Note that there is another issue brought up by this example that we're not fixing. Specifically, note that the calls are defined to be remote, even though there is no RCI packages in this partition (and no other partitions). That means that marshalling and unmarshalling and PCS use is required, even though the call has to be ultimately local. Even in programs that have multiple partitions, types declared in Pure and Remote_Types packages will be replicated in each partition, meaning that the associated calls on primitives are going to be local. Moreover, it is necessary to dispatch in order to do marshalling (that's the only way to know how to marshall the specific type of the designated object), and it is silly to dispatch multiple times when once would do. Thus, it's likely that the implementation will marshall in stubs that are dispatched to, one for each specific type; with that implementation there is no need for remote calls (specifically marshalling and unmarshalling) if the specific subprogram is local. As such, there should be a permission to make local calls to local routines (and perhaps an aspect "All_Calls_Remote" on the remote access-to-class-wide to revoke that permission if that is actually useful). [This second issue is related to the one covered by AI12-0031-1. That AI concludes that optimization of calls is in fact allowed unless All_Calls_Remote is given; see that AI.] !corrigendum E.2.2(16) @drepl @xbullet @dby @xbullet !ASIS No ASIS effect. !ACATS test Create an ACATS B-Test to test this issue. !appendix From: Adam Beneschan Sent: Thursday, May 17, 2012 1:09 PM !topic Small language hole involving remote access-to-classwide types !reference E.2.2(16) !from Adam Beneschan 12-05-17 !discussion What, if anything, makes this program illegal? with Ada.Streams; package Pack1 is pragma Pure; type Root is tagged limited private; procedure Prim (N : Integer; X : Root); package Inner is -- needed to make Do_Write non-primitive procedure Do_Write (Stream : access Ada.Streams.Root_Stream_Type'Class; X : Root); end Inner; for Root'Write use Inner.Do_Write; private type Root is tagged limited record F1 : Integer; end record; end Pack1; with Pack1; package Pack2 is pragma Remote_Types; type Root_Acc is access all Pack1.Root'Class; end Pack2; with Pack2; package Pack3 is pragma Remote_Call_Interface; procedure Test1 (A : Pack2.Root_Acc); end Pack3; with Pack1; with Ada.Streams; package body Pack3 is procedure Do_Write (Stream : access Ada.Streams.Root_Stream_Type'Class; The_Obj : Pack2.Root_Acc) is begin Pack1.Prim (4, The_Obj.all); Pack1.Root'Class'Write (Stream, The_Obj.all); -- ILLEGAL? end Do_Write; procedure Test1 (A : Pack2.Root_Acc) is begin ... end Test1; end Pack3; The statement marked ILLEGAL shouldn't be allowed, since it involves an access parameter in a remote dispatching call. GNAT rejects the statement saying "invalid dereference of a remote access-to-class-wide value", apparently referring to E.2.2(16). However, the language of E.2.2(16) doesn't seem to make this illegal, since The_Obj.all is part of a dispatching call and its value is a controlling operand according to the definitions in 3.9.2. [It would have been good enough when E.2.2(16) was written, but the rule that made stream attributes dispatching operations in 3.9.2 appeared later.] If there's nothing else that makes this illegal, maybe something needs to be added to E.2.2(16). (I realize this is an obscure, low priority issue.) **************************************************************** From: Randy Brukardt Sent: Wednesday, June 6, 2012 9:20 PM > The statement marked ILLEGAL shouldn't be allowed, since it involves > an access parameter in a remote dispatching call. I don't understand. I *thought* I did, but now I don't. I don't see any rule that makes this illegal for *any* call; so that implies that there is a *large* hole, or none. If there is anything wrong here, it's the idea that a stream attribute can ever be a remote call (if we allow that, then we can't use stream attributes for marshalling, which is the route to madness since everything in Annex E depends on that). AI12-0002-1 covers *that* issue. I also don't understand the significance of making Do_Write a non-primitive operation. There isn't anything in 10.2.1 or E.2.2 that would make this work differently if Do_Write was a primitive operation, so why the extra complication?? > GNAT rejects the statement saying "invalid dereference of a remote > access-to-class-wide value", apparently referring to E.2.2(16). > However, the language of > E.2.2(16) doesn't seem to make this illegal, since The_Obj.all is part > of a dispatching call and its value is a controlling operand according > to the definitions in 3.9.2. > [It would have been good enough when > E.2.2(16) was written, but the rule that made stream attributes > dispatching operations in 3.9.2 appeared later.] If there's nothing > else that makes this illegal, maybe something needs to be added to > E.2.2(16). (I realize this is an obscure, low priority issue.) This seems like a GNAT bug (at least in the error message) to me. There is nothing wrong with the dereference of the remote access-to-class-wide value. If Do_Write had been primitive, I don't see any reason that the following dispatching call would not have worked: Do_Write (Stream, The_Obj.all); -- ??? and if there is any reason that the above call would not be allowed, then that reason would automatically apply to the call to the stream attribute. (Specifically, if the access type doesn't have an appropriate stream attribute, I think remote calls are always illegal. That surely applies here.) I suspect that the GNAT error message is a complete red herring; the message most likely should simply say that a remote stream attribute call is not allowed. Anyway, please explain what you think the problem here is (and please do so by Friday morning, so I can put this on the Stockholm agenda). **************************************************************** From: Randy Brukardt Sent: Wednesday, June 6, 2012 9:53 PM ... > > with Pack2; > > package Pack3 is > > pragma Remote_Call_Interface; > > procedure Test1 (A : Pack2.Root_Acc); > > end Pack3; > > > > with Pack1; > > with Ada.Streams; > > package body Pack3 is > > > > procedure Do_Write (Stream : access Ada.Streams.Root_Stream_Type'Class; > > The_Obj : Pack2.Root_Acc) is > > begin > > Pack1.Prim (4, The_Obj.all); > > Pack1.Root'Class'Write (Stream, The_Obj.all); -- ILLEGAL? > > end Do_Write; > > > > procedure Test1 (A : Pack2.Root_Acc) is > > begin > > ... > > end Test1; > > > > end Pack3; I don't understand the significance of this package. There is no remote call here (Do_Write is not a remote call interface because it is solely in the body). So far as I can tell, any remote call on a stream attribute here would be illegal (presuming we plug the hole noted in AI12-0002-1). So while this is a dispatching call through a remote-access-to-classwide type, the actual call executed cannot be remote. It's also true that you'd want the Do_Write routine to be non-primitive if it could appear in the spec of a Remote_Call_Interface (because inheriting such a routine would be illegal), but since you didn't derive any such type, there is no illegality from having it be primitive. So, let's simplify the example removing stuff that ought to be irrelevant: with Ada.Streams; package Pack1 is pragma Pure; type Root is tagged limited private; procedure Do_Write (Stream : access Ada.Streams.Root_Stream_Type'Class; X : Root); for Root'Write use Do_Write; private type Root is tagged limited record F1 : Integer; end record; end Pack1; with Pack1; package Pack2 is pragma Remote_Types; type Root_Acc is access all Pack1.Root'Class; end Pack2; with Pack1, Pack2, Ada.Streams; procedure Test1 (A : Pack2.Root_Acc) is begin Pack1.Root'Class'Write (Stream, The_Obj.all); -- OK? I think so. Pack1.Do_Write (Stream, The_Obj.all); -- OK? I think so. end Test1; The calls marked "OK?" are both dispatching calls through a remote access-to-classwide type, but none of the actual calls are remote (how could they be?; there are no RCI units in this program!) and there is no restriction on having anonymous access parameters for such routines. (I surely couldn't find any rules in E.2.2 or 10.2.1 that would make any of this illegal.) Therefore, I conclude that both calls are legal as written. Moving the calls into a RCI body doesn't have any effect on this: there still are not any remote calls here. So I cannot find any reason for either of these calls to be illegal, even in your original program. As I previously noted, there is a known issue with stream attributes of types declared in an RCI unit, which we were not confident of the solution (when we discussed it in Edinburgh) and thus didn't put a fix into Ada 2012. Surely no remote calls of stream attributes can ever be allowed - both because there is no marshalling for the access-to-stream parameter, and because they couldn't be used to implement marshalling if they in fact were remote themselves. But none of this seems to have anything whatsoever to do with your original report. I think you were just confused by a GNAT bug (more accurately, an unimplemented feature). I realize that Annex E issues are amazingly hard to understand, so I might very well have made a mistake somewhere. Please tell me (preferably by Friday) if you see any such thing. **************************************************************** From: Adam Beneschan Sent: Wednesday, June 6, 2012 10:09 PM > I also don't understand the significance of making Do_Write a > non-primitive operation. There isn't anything in 10.2.1 or E.2.2 that > would make this work differently if Do_Write was a primitive > operation, so why the extra complication?? E.2.2(14) says that for a remote access-to-class-wide type, "The primitive subprograms of the corresponding specific type shall only have access parameters if they are controlling formal parameters". Do_Write in Pack1 has an access parameter (Stream) that is not a controlling formal parameter, so the declaration of Root_Acc would be illegal if Do_Write were primitive. Making Do_Write non-primitive makes Root_Acc legal. However, since Do_Write is non-primitive, a direct call to it would not be a dispatching call. Thus, if Pack1.Root'Class'Write (Stream, The_Obj.all); were replaced by Pack1.Inner.Do_Write (Stream, The_Obj.all); --(A) then E.2.2(16) would clearly be violated. So if I recall correctly (I'm trying to reconstruct what I was thinking a month ago), the language normally works to prevent calls like (A) because -- if it involves a primitive subprogram, the remote access-to-classwide type can't be declared (E.2.2(14)), and -- if it involves a non-primitive subprogram, the call isn't a dispatching call and the dereference is invalid by E.2.2(16). This seems to be a case that fell through the cracks, in which a non-primitive subprogram can be called through a dispatching call using classwide stream attributes. I don't see how AI12-0002 would help, at least with the current !wording: Specification of a stream-oriented attribute is illegal in the specification of a remote call interface library unit. In addition to the places where Legality Rules normally apply (see 12.3), this rule applies also in the private part of an instance of a generic unit. because there's only one RCI library unit in my example and there aren't any stream attribute specifications there. **************************************************************** From: Adam Beneschan Sent: Wednesday, June 6, 2012 10:21 PM ... > I don't understand the significance of this package. There is no > remote call here (Do_Write is not a remote call interface because it > is solely in the body). So far as I can tell, any remote call on a > stream attribute here would be illegal (presuming we plug the hole > noted in AI12-0002-1). So while this is a dispatching call through a > remote-access-to-classwide type, the actual call executed cannot be remote. It shouldn't be, but I don't see where the language says so. 3.9.2 says the call is dispatching, and E.4(5) says that a dispatching call with a controlling operand designated by a value of a remote access-to-class-wide type is a remote call. So I think the language says this is a remote call and there's nothing anywhere that says it isn't. This is probably the hole that needs to be plugged. My thinking is that if E.2.2(16) were changed to A value of a remote access-to-class-wide type shall be dereferenced (or implicitly converted to an anonymous access type) only as part of a dispatching call {that is not a call to a stream attribute subprogram} where the value designates a controlling operand of the call. it would solve the problem. I'm not sure I have the wording right. > It's also true that you'd want the Do_Write routine to be > non-primitive if it could appear in the spec of a > Remote_Call_Interface (because inheriting such a routine would be > illegal), but since you didn't derive any such type, there is no illegality > from having it be primitive. As I noted before, E.2.2(14) makes it illegal. **************************************************************** From: Randy Brukardt Sent: Thursday, June 7, 2012 12:29 AM ... > > I don't understand the significance of this package. There is no > > remote call here (Do_Write is not a remote call interface because it > > is solely in the body). So far as I can tell, any remote call on a > > stream attribute here would be illegal (presuming we plug the hole > > noted in AI12-0002-1). So while this is a dispatching call through a > > remote-access-to-classwide type, the actual call executed cannot be remote. > > It shouldn't be, but I don't see where the language says so. > 3.9.2 says the call is dispatching, and E.4(5) says that a dispatching > call with a controlling operand designated by a value of a remote > access-to-class-wide type is a remote call. > So I think the language says this is a remote call and there's > nothing anywhere that says it isn't. This is probably the hole that > needs to be plugged. Yes, I see what you mean. I never even noticed E.4 in reading about this. It seems insane to require a remote call (with all of that marshalling and unmarshalling) when the call is necessarily to the same partition (as in your Pure package). You don't even know how to marshall the parameters until you've determined the tag of the designated object, so I don't see any implementation value to marshalling first -- you have to do the marshalling/unmarshalling in the routine (stub) that you dispatch to -- so there is no need to make an actual remote call if the type belongs to the same partition (as it must if it is declared in a Pure or Remote Types package). OTOH, one could argue that this is insufficiently broken, in that it doesn't have any visible semantic effects (presuming no junk in the [user-defined] stream attributes - something that no one ought to be depending on). Implementers probably can and should ignore it. OT3H, this ties into your complaint about All_Calls_Remote, which apparently is really Most_Calls_Remote. It seems silly to force any local call into the PCS, and since it can't even be done as explained in the ImpReq, I don't see the point. I'd just get rid of both things and allow the implementation to use a local call anytime that makes sense. (And if an implementation knows of a real need for forcing all calls through a PCS, they can define how that should work in a way that actually covers all of the cases that their customers care about; we can't do that, we have to cover everything.) > My thinking is that if E.2.2(16) were changed to > > A value of a remote access-to-class-wide type shall be dereferenced > (or implicitly converted to an anonymous access > type) only as part of a dispatching call {that is not a call to a > stream attribute subprogram} where the value designates a controlling > operand of the call. > > it would solve the problem. I'm not sure I have the wording right. That would fix this problem, I suppose, but it seems silly. There should never be any sort of remote stream attribute call, and I really see no reason for blaming the problem on a dereference of an access type for which there is no implementation problem at all (presuming that the implementation doesn't try to marshall parameters for a local call, which is already idiotic, as noted above). I'd probably just stick "A remote subprogram call shall not be a call on a stream attribute." into E.4, because we don't want anyone finding a way to do that anywhere. > > It's also true that you'd want the Do_Write routine to be > > non-primitive if it could appear in the spec of a > > Remote_Call_Interface (because inheriting such a routine would be > > illegal), but since you didn't derive any such type, there > is no illegality from having it be primitive. > > As I noted before, E.2.2(14) makes it illegal. I looked an hour at E.2.2 and 10.2.1 without seeing that rule. Which just means that it's impossible to wade through this Annex and understand it, so probably everything I know is wrong. So I think I'll just make an AI saying that there are a boatload of problems with Annex E (actually, we already have one, AI12-0002-1 -- you looked at it and mistakenly thought that it was complete in some way, but actually we had discarded that version and never have worked on it since, so the existing AI is worthless junk except for the problem descriptions and mail). I've suggested before that Annex E simply be dropped from the Standard since it is a mess, thus sucks up a lot of time yet we never get it any better, doesn't come close to matching the one and only implementation (which is not going to change whether or not we confirm the Annex), and has almost no demand (I don't think anyone ever asked RRS for an implementation). I haven't got much traction with such a position, but perhaps wasting many more hours on this junk again will change some views. **************************************************************** From: Thomas Quinot Sent: Saturday, June 15, 2013 11:56 AM > Need informed advice on AI12-0034 : remote stream attribute calls First observation: in any case a call to stream attributes can't possibly be a remote call -- for one thing, as is mentioned, the Stream parameter does not support external streaming. Second, it seems silly to make a remote call in order to prepare data for sending. Now in any case I think the proposed solution is a ugly hack. I see two possible alternative approaches. Both solutions have the same underlying philosophy that a dereference of an RACW is intended to be permitted only in the context of a call to a primitive operation of the type, i.e. to a service that the user specifies is provided by the designated object. (S'Write'Class is not a service that is intended to be provided by the object, it's a collateral subprogram produced "on the side"). 1. Approach without need for amendment -------------------------------------- A bit of creative exegesis, the idea is to take advantage of the fact that there is no declaration for the procedure denoted by S'Class'Write, and from there derive that the user does not intend this procedure to be a service provided [to remote callers] by the designated to object. Consider 13.13.2(11): " S'Class'Write S'Class'Write denotes a procedure with the following specification: procedure S'Class'Write( Stream : not null access Ada.Streams.Root_Stream_Type'Class; Item : in T'Class) Dispatches to the subprogram denoted by the Write attribute of the specific type identified by the tag of Item. " in a call "S'Class'Write (Stream, Item)", is Item a controlling operand in a dispatching call? I don't think so. The call dispatches according to the tag of Item (as would be the case if Item were a controlling operand), BUT it is not "a call on a dispatching operation" because its name (S'Class'Write) does not denote the declaration of a primitive operation (the name denotes a procedure, but there is no declaration for this procedure). So, Item is not a controlling operand per 3.9.2(2/3), so having a RACW dereference here is illegal per E.2.2(16). 2. Approach with amendment -------------------------- Same idea, but making an explicit change instead of leveraging existing bits. Clarify E.2.2(16) as follows: A value of a remote access-to-class-wide type shall be dereferenced (or implicitly converted to an anonymous access type) only as part of a dispatching call to a primitive operation of the designated type <<< where the value designates a controlling operand of the call I think that's the best solutions I can think of in 10 minutes time. Unless I'm missing something obvious, they should adequately address the raised issue, and I feel they are clearer than the proposed alternative solution described in the AI. If you agree with that, could you please forward them to the ARG? **************************************************************** From: Randy Brukardt Sent: Saturday, June 15, 2013 2:51 PM I rewrote AI12-0034-1 using Thomas' solution so we can dispose of it quickly tomorrow. [This is version /03 of the AI - ED] ****************************************************************