Version 1.3 of ais/ai-00179.txt
!standard 07.06.01 (17) 00-01-25 AI95-00179/04
!class confirmation 97-03-19
!status Response 2000 00-01-24
!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
!qualifier Omission
!subject Finalization and Unchecked_Deallocation
!summary
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
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
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.
!ACATS test
No ACATS test is possible. This AI says that the behavior is unspecified,
so there is nothing to test for.
!appendix
!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...
****************************************************************
Questions? Ask the ACAA Technical Agent