!standard 4.8(5.3/2) 10-06-08 AI05-0157-1/04 !standard 13.11.2(4) !standard 13.11.2(17) !class binding interpretation 09-06-02 !status Amendment 201Z 09-12-10 !status WG9 Approved 10-06-18 !status ARG Approved 10-0-1 09-11-08 !status work item 09-06-02 !status received 09-02-15 !priority Low !difficulty Medium !qualifier Omission !subject Calling Unchecked_Deallocation is illegal for zero-sized pools !summary Calling an instance of Unchecked_Deallocation is illegal if the pool is known to have Storage_Size equal to zero. An instance of Unchecked_Deallocation raises Program_Error if the pool has Storage_Size equal to zero (if it is not illegal). !question RM-4.8(5.3/2) says: 5.3/2 {AI95-00366-01} An allocator shall not be of an access type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero. But there is not a similar rule for Unchecked_Deallocation. That means that Unchecked_Deallocation can be instantiated for such types, and every call to such an Unchecked_Deallocation is guaranteed to be erroneous. Should such instances be illegal? (No, but calls are.) !recommendation (See Summary.) !wording Delete the last sentence of 4.8(5.3/2). Add before 13.11.2(4): Legality Rules A call on an instance of Unchecked_Deallocation is illegal if the actual access type of the instance is a type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero. 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. [Editor's note: This is nearly a copy of 4.8(5.3/2).] Add after 13.11.2(17): (Implementation Advice) A call on an instance of Unchecked_Deallocation with a non-null access value should raise Program_Error if the actual access type of the instance is a type for which the Storage_Size has been specified to be zero or is defined by the language to be zero. AARM Note: If the call is not illegal (as in a generic body), we recommend that it raise Program_Error. Since the execution of this call is erroneous (any allocator from the pool will have raised Storage_Error, so the non-null access value must have been allocated from a different pool or be a stack-allocated object), we can't require any behavior - anything at all would be a legitimate implementation. [Editor's note: I don't think we want to raise Program_Error if the access value is null; that case is not erroneous and is defined to do nothing. It seems harmless. Humm, reading the wording of 13.11.2(16), one could argue that calling an instance of Unchecked_Deallocation with a null access value is *always* erroneous. It's not clear what "the object" is in that case, but surely it wasn't created by an allocator with Name'Storage_Pool. (Of course, even Adam would be embarrassed to actually claim this seriously - no one could doubt the intent.) Do we need to clarify 13.11.2(16) in some way (perhaps an AARM To Be Honest note would be sufficient)?] !discussion We had a number of choices here: (1) Make the instance of Unchecked_Deallocation illegal. But this is weird, because this is a legality check unrelated to the (normal) generic contract. It would be a magic extra check. A second objection to this choice is that it would make instances of generics illegal if there is merely an instance of Unchecked_Deallocation in the specification. That is quite reasonable, and the instance would not necessarily be called. (Imagine a generic that determines whether or not to call Free based on an additional generic parameter; it would always have to instantiate it.) This would reduce the usability of generics. (2) Make calls on the instance of Unchecked_Deallocation illegal. This is also a magic check, but at least it doesn't run into issues with generic units. (3) Raise Program_Error if such a generic instance is detected. This is less weird, but it turns what otherwise would be a legality rule into a runtime check. This also could render some generics unusable (as in case (1)); this could even be a broader problem as the runtime check could cover cases that would not have been statically illegal. (4) Raise Program_Error on the *call* of such a generic instance. This also turns a legality rule into a runtime check, but at least it doesn't have the problems with generic units. (5) Leave calling the instantiation erroneous, but add a permission for an implementation to reject any program that it can detect would have erroneous execution. (One wonders if a similar permission should be allowed for Bounded Errors.) On the other hand, this too could cause trouble for any code conditionally compiled. Probably it would be better to follow the rule in 1.1.5(6) (which suggests a warning when the compiler can detect errors). Since we want this to remain a legality rule, and we do not want to make generic units less useful, we have chosen option (2), along with a recommendation for option (4) if the call is legal but the size is known to be zero. We delete the last line of 4.8(5.3/2) and do not add a similar line to the new text. That line read: "This rule does not apply in the body of a generic unit or within a body declared within the declarative region of a generic unit, if the type of the allocator is a descendant of a formal access type declared within the formal part of the generic unit." However, this cannot usefully happen. For a formal access type, the Storage_Size is not known and surely isn't static, so the legality rules can never apply. For a formal derived type, the legality rules can only be triggered by a parent type having the appropriate property. But Storage_Size can never be specified for a derived access type, so it always has the same value for all child types. That means that any actual that would match the formal derived type necessarily has the same Storage_Size properties, so it is harmless (and preferable) to check them in the body - they are always known in that case. We cannot imagine changing this particular invariant, as it is necessary so that (potentially implicit) type conversions between derived access types are safe, so there is no maintenance hazard potential from omitting the rule. !corrigendum 4.8(5.3/2) @drepl An @fa shall not be of an access type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero. 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. This rule does not apply in the body of a generic unit or within a body declared within the declarative region of a generic unit, if the type of the allocator is a descendant of a formal access type declared within the formal part of the generic unit. @dby An @fa shall not be of an access type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero. 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. !corrigendum 13.11.2(4) @dinsb Given an instance of Unchecked_Deallocation declared as follows: @dinst @i<@s8> A call on an instance of Unchecked_Deallocation is illegal if the actual access type of the instance is a type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero. 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. !corrigendum 13.11.2(17) @dinsa For a standard storage pool, Free should actually reclaim the storage. @dinst A call on an instance of Unchecked_Deallocation with a non-null access value should raise Program_Error if the actual access type of the instance is a type for which the Storage_Size has been specified to be zero or is defined by the language to be zero. !ACATS Test !appendix From: Bob Duff Date: Sunday, February 15, 2009 11:17 AM RM-4.8(5.3/2) says: 5.3/2 {AI95-00366-01} An allocator shall not be of an access type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero. But I can't find a similar rule for Unchecked_Deallocation. Seems like a hole, to me. Unchecked_Deallocation should be illegal for such types, for the same reasons as 4.8(5.3/2). Otherwise, every call to such an Unchecked_Deallocation is guaranteed to be erroneous. It's a nasty sort of erroneousness, because it will work fine on some implementations. **************************************************************** From: Robert Dewar Date: Sunday, February 15, 2009 11:24 AM Not likely, trying to allocate statically allocated stuff will most likely NOT work and blow up! I agree this is an obvious hole. Bob, you should file this as a bug if GNAT does not detect this case, and flag it as illegal. **************************************************************** From: Bob Duff Date: Sunday, February 15, 2009 12:22 PM Sure, but the usual case is to allocate something with "new", then convert it to another access type. If you then call U_D using the second access type, and that has Storage_Size 0, GNAT will just deallocate the object. It won't blow up. OK, just to be sure, I wrote this: with Ada.Text_IO; use Ada.Text_IO; with Unchecked_Deallocation; procedure Main is type A is access all String; for A'Storage_Size use 0; procedure Free is new Unchecked_Deallocation (String, A); type Allocation_Type is access all String; X : Allocation_Type := new String'("Hello, world."); Y : A := A (X); begin Free (Y); if Y = null then Put_Line ("It's null."); else Put_Line ("It's not null."); end if; end Main; It prints "It's null.". (A more realistic example would have various class-wide types.) The compiler warns, though: deallocation from empty storage pool. So that's not so horrible. >... I agree this is an obvious hole. > Bob, you should file this as a bug if GNAT does not detect this case, >and flag it as illegal. Well, the current language doesn't make it illegal (except perhaps via "Robert's Rule Of Absurdity") and we do give a warning... **************************************************************** From: Tucker Taft Date: Sunday, February 15, 2009 11:38 AM I don't quite see how you can make it illegal (Unchecked_Deallocation is a generic, after all), but you could define it to raise Program_Error (or perhaps to do nothing?). **************************************************************** From: Robert Dewar Date: Sunday, February 15, 2009 11:42 AM The instantiation should be illegal! **************************************************************** From: Tucker Taft Date: Sunday, February 15, 2009 12:03 PM That could be a bit of a pain to implement for some. Generics can be renamed, etc. At least in our implementation, there is nothing very special about Unchecked_Deallocation until it is called, at which point we start treating it specially. **************************************************************** From: Robert Dewar Date: Sunday, February 15, 2009 12:16 PM How about then the following a) a clear statement that instantiating UC or calling UC for such a type is erroneous. b) impl permission to make either the instantiation or the call illegal. Actually it would make some sense to have impl permission that allows the compiler to treat ANY recognized erroneous construct as illegal! **************************************************************** From: Tucker Taft Date: Sunday, February 15, 2009 12:51 PM My only concern about this approach is one could imagine a generic that has a formal access type and declares some kind of "Free" operation implemented using Unchecked_Deallocation. The generic can reasonably be instantiated with an access type with storage size zero, so long as the Free operation is never called. > Actually it would make some sense to have impl permission that allows > the compiler to treat ANY recognized erroneous construct as illegal! This would seem a natural extension of RM 1.1.5(6). **************************************************************** From: Robert Dewar Date: Sunday, February 15, 2009 1:07 PM > My only concern about this approach is one could imagine a generic > that has a formal access type and declares some kind of "Free" > operation implemented using Unchecked_Deallocation. The generic can > reasonably be instantiated with an access type with storage size zero, > so long as the Free operation is never called. That's a good argument, so it should be the Free operation that is always erroneous, and impl permission to consider the call to Free to be illegal ... **************************************************************** From: Bob Duff Date: Sunday, February 15, 2009 12:12 PM > I don't quite see how you can make it illegal (Unchecked_Deallocation > is a generic, after all), but you could define it to raise > Program_Error (or perhaps to do nothing?). Not sure what you mean -- I'm proposing to make it illegal by adding a Legality Rule: For an instantiation of Unchecked_Deallocation, the generic actual parameter for the Name generic formal parameter shall not have a Storage_Size that has been specified by a static expression with value zero, nor defined by the language to be zero. Or: An access type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero shall not be passed to the Name parameter in an instantiation of Unchecked_Deallocation. Plus an AARM note: Including a renaming of Unchecked_Deallocation. **************************************************************** From: Bob Duff Date: Sunday, February 15, 2009 2:10 PM > >> That could be a bit of a pain to implement for some. > >> Generics can be renamed, etc. At least in our implementation, > >> there is nothing very special about Unchecked_Deallocation until it > >> is called, at which point we start treating it specially. I have trouble believing this could be hard to implement in any compiler. This is from the man who invented coextensions. ;-) Seriously, during semantic analysis of an instantiation, you look in your symbol table, and see if what you've got is called "unchecked_deallocation", and if its parent is a root library unit called "ada". Yes, you have to follow renamings -- big deal. It's a special-case kludge. But it's not hard. > > How about then the following > > > > a) a clear statement that instantiating UC or calling UC for such a > > type is erroneous. > > > > b) impl permission to make either the instantiation or the call > > illegal. This is getting too complicated for my taste. I'd rather leave the language alone. We already have a warning, which is good enough in practise. > My only concern about this approach is one could imagine a generic > that has a formal access type and declares some kind of "Free" > operation implemented using Unchecked_Deallocation. The generic can > reasonably be instantiated with an access type with storage size zero, > so long as the Free operation is never called. The rule about "new" does not apply to such a generic formal type (I presume, since otherwise it would be a contract model violation), and my proposed legality rule should not apply to such a generic formal type. It only applies if the Storage_Size is visibly and statically 0. So I think this is a red herring. **************************************************************** From: Randy Brukardt Date: Tuesday, February 17, 2009 12:53 AM > Seriously, during semantic analysis of an instantiation, you look in > your symbol table, and see if what you've got is called > "unchecked_deallocation", and if its parent is a root library unit > called "ada". Yes, you have to follow renamings > -- big deal. > > It's a special-case kludge. But it's not hard. We've been *very* adverse to adding special-case kludges to generic instantiations. Indeed, I proposed one to support the factory generic (Generic_Dispatching_Constructor), and we decided instead to add the entire mechanism to the language outright (abstract formal subprograms) -- which surely was a lot more work. I don't much care either way, BTW. **************************************************************** From: Robert Dewar Date: Tuesday, February 17, 2009 11:55 AM > We've been *very* adverse to adding special-case kludges to generic > instantiations. I really think that Unchecked_Deallocation and Unchecked_Conversion are special cases, they are really fundamental primitives in the language, which just happen to be spelled as generics, but I don't see that we should let this aversion cripple the definition in any way. In particular the rules for erroneousness are already special. **************************************************************** From: Randy Brukardt Date: Tuesday, December 10, 2009 1:21 AM In St. Pete, someone complained that the last sentence of the new wording for 13.11.2(3.1/3) is unnecessary: A call on an instance of Unchecked_Deallocation is illegal if the actual access type of the instance is a type for which the Storage_Size has been specified by a static expression with value zero or is defined by the language to be zero. 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. This rule does not apply in the body of a generic unit or within a body declared within the declarative region of a generic unit, if the actual access type of the instance is a descendant of a formal access type declared within the formal part of the generic unit. The thinking was that a generic formal access type never has a static value for Storage_Size. There is an unusual case: that a generic formal derived type has a parent access type that does have such a static value; but in that case, all child types that would match the formal derived type also must have that static value. So there would be no harm in applying the rule. I argued that we shouldn't make a change because it is exactly the same as the allocator rule, which was considered decisive. But I missed the more important question: why is this sentence in the allocator rule as well? The same points apply to it. AI95-0366-1 does not explain why this sentence was included - it doesn't seem to be needed for allocators, either. In any case, the legality rule (like any other legality rule) doesn't apply in the bodies of instances, so that can't be the problem. I wondered if it had anything to do with the other case: "defined by the language to be zero". That's code for "remote types". But I don't think a generic formal access type can be a remote access type. (A formal derived type can be, but that again would not need any exceptions.) So I suspect that the last sentence can be dropped from both 4.8(5.3/2) and the new 13.11.2(3.1/3). Unless Steve can remember some obscure case involving child generic units and/or formal packages. Any other thoughts?? **************************************************************** From the minutes of the Amendement Subcommittee conference call of December 10th: Drop the extra sentence from both paragraphs. Explain in the AARM why there is no problem that requires this extra sentence. **************************************************************** From: Robert Dewar Date: Sunday, July 11, 2010 1:52 PM unexpectedly, this turned out to be a bit of a pain. GNAT always gave a warning, but it is now required to give an error. But there are legitimate low-level mucking places where GNAT does free from empty storage pools, quite deliberately. There is a storage clause of zero to avoid any allocators, but objects are allocated under the covers, then freed using unchecked deallocation. I am sure I can work around this, but it is interesting that such an innocuous looking AI should cause quite a bit of trouble, and introduce significant incompatibilities. Will be interesting to see if any customers are affected. **************************************************************** From: Robert Dewar Date: Sunday, July 11, 2010 2:02 PM I wonder if this incompatibility is really worth it? I am sure this was agreed to without any idea that it could invalidate existing code in a manner hard to fix. In practice giving a warning is perfectly good enough, oh well! **************************************************************** From: Robert Dewar Date: Sunday, July 11, 2010 2:14 PM I ended up "fixing" this by simply removing the storage size clauses, so now I lose the compile time protection of any allocations being errors, though they would still generate a warning of Storage_Error in these particular cases, so not too bad. But odd that an AI intended to improve security at compile time ends up reducing security at compile time. Of course this is low level mucking, I realie, but stil interesting. If I can do that low level mucking, so can any other Ada user :-) **************************************************************** From: Bob Duff Date: Sunday, July 11, 2010 2:18 PM > But there are legitimate low-level mucking places where GNAT does free > from empty storage pools, quite deliberately. Can you point me to some of those cases? ****************************************************************