!standard 07.06.01 (17) 97-11-14 AI95-00179/02 !class confirmation 97-03-19 !status WG9 Approved 97-11-14 !status ARG Approved (subject to editorial review) 9-0-1 97-04-11 !status work item 97-03-19 !status received 97-03-19 !priority High !difficulty Medium !subject Finalization and Unchecked_Deallocation !summary 97-05-08 Consider an instance Free of Unchecked_Deallocation for a type with controlled parts. Free will first finalize its parameter. If some Finalize propagates an exception, then it is unspecified whether storage is deallocated, and whether the object ceases to exist. Finalize might be called again on that same object. !question 97-05-08 13.11.2(9) says that Free first performs finalization, then deallocates the storage. 7.6.1(17) says that (if Finalize propagates an exception,) Program_Error is raised after any other finalizations are performed. In the case where Finalize propagates an exception, is the storage deallocated? Is it possible that Finalize will be called again on the same object? !response 97-05-08 The RM is unclear on this point. There are three alternatives: Alternative 1: Storage is deallocated, and the object ceases to exist. Finalize will not be called again on that same object. Alternative 2: It is unspecified whether storage is deallocated. However, the object ceases to exist; Finalize will not be called again on that same object. Alternative 3: It is unspecified whether storage is deallocated, and whether the object ceases to exist. Finalize might be called again on that same object. We choose Alternative 3, because this eases the burden on implementations, and because a Finalize that propagates an exception is a serious bug anyway. If there is a desire to recover from such situations, programmers should either prove that no such exception can happen, or put a "when others =>" clause in each Finalize procedure. !appendix 97-03-19 !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !from Randy Brukardt !reference 97-15710.a Randy Brukardt 97-1-30>> !discussion 13.11.2(9) says that Free first performs finalization, then deallocates the storage. 7.6.1(17) says that (if Finalize raises an exception,) Program_Error is raised after any other finalizations are performed. This implies that the object (and any subcomponents) is not destroyed or deallocated when a Program_Error is raised because of an exception in Finalize. That means that Finalize will be called again on the object (and any subcomponents), when the master of its access type is left. This behavior is similar to that for finalization as part of an assignment, so it is not unprecedented. Is this the intent? (If so, ACVC 2.1 test C761006 needs to be corrected). Note that deallocating the memory anyway can be detected; that's especially true since a user-defined Deallocate may be called during the operation. **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30>> !from Robert Dewar !reference 1997-15711.a Robert Dewar 1997-1-31>> !discussion Regarding the issue of storage reclamation when a finalization routine raises Program_Error. I urge that no further requirements be placed on exactly what happens in this situation. An exception while processing a finalization routine is a serious flaw in a program, and it is good enough to make sure that Program_Error is raised, period. **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30 !from Robert I. Eachus 97-1-31 !reference 97-15713.a Robert I. Eachus 97-1-31>> !discussion > 13.11.2(9) says that Free first performs finalization, then > deallocates the storage. 7.6.1(17) says that (if Finalize raises > an exception,) Program_Error is raised after any other > finalizations are performed. > This implies that the object (and any subcomponents) is not > destroyed or deallocated when a Program_Error is raised because of > an exception in Finalize. No, it says, and as far as I am concerned means, that the storage used by the object might not reclaimed as part of the Free. (But it can be garbage collected.) > That means that Finalize will be called again on the object (and > any subcomponents), when the master of its access type is left. My model of what goes on is as follows: 1. Deallocate subcomponents if necessary, calling finalization routines. 2. Call finalization for the object. 3. Deallocate storage. However, there is a fourth step which can occur at any point before step 3: disconnect the object from the finalization chain. It must be possible for this to occur before step 1 (to avoid infinite recursion) or in step 2 (as a part of user defined finalization). So where does the call to Program_Error come? I think the right way to view it is: 3. If there is a pending exception, raise Program_Error, otherwise deallocate the storage. So a call to an instance of Unchecked_Deallocation may result in storage leaks if it raises Program_Error, but that same object is never finalized again. Note that storage leaks are possible but not required. Garbage collection is a sufficient reason for that. But it is easy to construct cases where the storage for subcomponents must be recovered, and they share storage with their parent. (User defined storage pools remember?) I see no requirement to partially free storage in that case. **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30 !reference 97-15713.a Robert I. Eachus 97-1-31 !reference 97-15714.a Randy Brukardt 97-2-1>> !discussion > My model of what goes on is as follows: > > 1. Deallocate subcomponents if necessary, calling finalization >routines. > > 2. Call finalization for the object. > > 3. Deallocate storage. I like your model, but it isn't supported by the RM. 7.6.1(5-9) defines what it means to finalize an object, and it does not allow interleaving of finalization and deallocation. Also, your model has the finalization actions backwards (the whole object is finalized first, then moving inwards to the components.) **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30>> !reference 1997-15711.a Robert Dewar 1997-1-31>> !from Ted Baker !reference 97-15715.a Ted Baker 97-2-1>> !discussion I cannot agree more strongly with Robert on this issue. Exceptions in finalization routines are failures of the most fundamental kind. We must be wary of going overboard, in our chase to plug "holes" in the language. In this case, we not only risk significant distributed execution time overhead, but we also risk giving the illusion that a programmer can rely on the language implementation to insulate against fundamental logic errors. If one is writing an application that had to be extremely reliable, one would be foolish to trust any Ada implementation to be 100% proof against storage leakage in this context, regardless of whether the language required it and the implementation managed to pass some tests. --Ted Baker **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30 !reference 97-15713.a Robert I. Eachus 97-1-31 !from Tucker Taft 97-2-1 !reference 1997-15716.a Tucker Taft 1997-2-1>> !discussion Randy wrote: > 13.11.2(9) says that Free first performs finalization, then > deallocates the storage. 7.6.1(17) says that (if Finalize raises > an exception,) Program_Error is raised after any other > finalizations are performed. > > This implies that the object (and any subcomponents) is not > destroyed or deallocated when a Program_Error is raised because of > an exception in Finalize. I don't agree. 13.11.2(10) says: After Free(X), the object designated by X, and any subcomponents thereof, no longer exist; ... This implies it doesn't matter whether Free(X) propagates an exception, or never gets around to calling Deallocate. X.all no longer exists once Free(X) completes. It doesn't matter whether or not Free(X) actually reclaims any storage at all. Robert wrote: > No, it says, and as far as I am concerned means, that the storage > used by the object might not reclaimed as part of the Free. (But it can > be garbage collected.) Randy wrote: > That means that Finalize will be called again on the object (and > any subcomponents), when the master of its access type is left. Since X.all no longer exists per 13.11.2(10), it will not be re-finalized as part of finalization of the associated master, per 7.6.1(4). Robert wrote: > My model of what goes on is as follows: > > 1. Deallocate subcomponents if necessary, calling finalization > routines. Subcomponents are not separately deallocated, as far as the language is concerned. There may be multiple calls to Deallocate if the compiler doesn't represent a composite object contiguously, but based on 13.11.2(9), these would all happen after finalizing all parts of the object. -Tuck **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30 !reference 97-15711.a Robert Dewar 1997-1-31 !from Randy Brukardt !reference 97-15717.a Randy Brukardt 97-2-1>> !discussion > Regarding the issue of storage reclamation when a finalization routine > raises Program_Error. I urge that no further requirements be placed on > exactly what happens in this situation. An exception while processing a > finalization routine is a serious flaw in a program, and it is good > enough to make sure that Program_Error is raised, period. I agree with this. However, the immediate question for me is whether the finalization may be called again later (because it didn't succeed the first time). If it can be, then there is at least one validation test which should be repaired proto (while somebody is actually doing that); if not, I'll have to correct our implementation. **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30 !reference 97-15713.a Robert I. Eachus 97-1-31 !reference 97-15716.a Tucker Taft 1997-2-1 !from Randy Brukardt !reference 97-15718.a Randy Brukardt 97-2-3>> !discussion Tucker responds to my message: >I don't agree. 13.11.2(10) says: > > After Free(X), the object designated by X, and any subcomponents > thereof, no longer exist; ... > >This implies it doesn't matter whether Free(X) propagates an exception, >or never gets around to calling Deallocate. X.all no longer exists >once Free(X) completes. It doesn't matter whether or not Free(X) actually >reclaims any storage at all. While I have no objection to using this as a resolution to my question, the logic is not anywhere near as iron-clad as Tucker states. Hardly anywhere in the RM is there a special note that something does not occur if an exception is raised. For instance, 5.2(12) says "In any case, the converted value of the expression is assigned ...". By Tucker's logic here, we would have to make the assignment even if an exception is raised earlier. Obviously (I hope!), this is not true. I can find many similar cases in the RM. Following the pattern of the RM in such cases, I would have to say that 13.11.2(10) does not apply to the case of Free raising an exception. I am perfectly happy to have the ARG say that it DOES apply, but I don't believe that is obvious from the RM. **************************************************************** !section 7.6.1(17) !subject Finalization and Unchecked_Deallocation !reference RM95-7.6.1(17) !reference RM95-13.11.2(9) !reference 97-15710.a Randy Brukardt 97-1-30 !reference 97-15713.a Robert I. Eachus 97-1-31 !reference 1997-15716.a Tucker Taft 97-2-1 !reference 97-15719.a Robert I. Eachus 97-2-3>> !discussion I said: > 1. Deallocate subcomponents if necessary, calling finalization >routines. > > 2. Call finalization for the object. > > 3. Deallocate storage. Randy said: > I like your model, but it isn't supported by the RM... Also, > your model has the finalization actions backwards (the whole > object is finalized first, then moving inwards to the > components.) Yep, I got snookered by disjoint technical terms. I was using subcomponent here for components of (OO) objects, not Ada objects, since the interesting cases for this discussion are where you have objects containing pointers to other (OO and Ada) objects. If all these components have the same master, the (Ada) rules require that they be finalized in reverse order of creation. But more normally you have to explicitly deallocated the components as a part of the Finalization routine for the parent. Randy again: > 7.6.1(5-9) defines what it means to finalize an object, and it > does not allow interleaving of finalization and deallocation. I think Randy is dealing with things from the compiler run-time point of view here. However, if so, you still have to allow for Finalization routines that either have explicit deallocations or, much more usual, creation and finalization of other objects. Writing these things so that they work is tricky, and it is almost always the case that if you have nested controlled objects, you have to explicitly override the default order of finalization. I thought the RM was written this way so that the parent object could: 1) Access the children, saving any needed information in the Finalization routines local variables. 2) Explicitly finalize the children, 3) Use the saved information to do the real work of the Finalize, 4) Implicitly or explicitly deallocate the parent. In summary, I agree with Tuck. (But be careful about objects containing tasks if you try to write hard and fast rules.) Robert I. Eachus with Standard_Disclaimer; use Standard_Disclaimer; function Message (Text: in Clever_Ideas) return Better_Ideas is... ****************************************************************