CVS difference for ais/ai-00280.txt
--- ais/ai-00280.txt 2002/01/04 22:50:23 1.2
+++ ais/ai-00280.txt 2002/04/26 20:15:17 1.3
@@ -1,21 +1,44 @@
-!standard 9.04 (20) 01-12-21 AI95-00280/01
+!standard 9.04 (20) 02-04-19 AI95-00280/02
+!standard 4.08 (11)
!class binding interpretation 01-12-21
!status work item 01-12-21
!status received 01-12-04
!qualifier Omission
!priority High
!difficulty Medium
-!subject Access to protected subprograms and entries after finalization
+!subject Allocation, deallocation, and use of objects after finalization
!summary
Calling an entry or subprogram of a protected object after the object is
finalized is a bounded error, either raising Program_Error or working normally.
+Evaluating an allocator for an access type whose designated type has a task
+part after the master has completed waiting for dependent tasks raises
+Program_Error.
+
+Evaluating an allocator after the finalization of the collection for an
+access type has begun:
+ If the designated type has a controlled or protected part, Program_Error
+ is raised.
+ Otherwise, a bound error occurs, either raising Program_Error
+ or working normally (possibly without finalization of the object).
+
!question
What is the effect of calling an entry or subprogram of a protected object
after the object is finalized?
+What is the effect of evaluating an allocator after the access type's
+collection of objects has been finalized (by the rule in 7.6.1(11))?
+
+What is the effect of calling an instance of Unchecked_Deallocation
+after the access type's collection of objects has been finalized (by
+the rule in 7.6.1(11))?
+
+What is the effect of evaluating an allocator for an access type whose
+designated type has a task part after the master of the access type has
+completed waiting for dependent tasks?
+
!recommendation
(See summary.)
@@ -44,6 +67,154 @@
If an implementation allows calls to proceed normally, it is possible that a
task may be queued on the object forever.
+There are analogous problems with access types.
+
+The finalization associated with a non-derived access type is described
+in 7.6.1(11).
+
+This "collection finalization" consists of iterating over the set of
+allocated objects and finalizing them in some order.
+
+The RM states that each allocated object "that still exists" at the point
+of collection finalization is finalized. This says nothing about objects
+which are allocated after the collection finalization is finished, and it seems
+to imply that allocations that occur after the collection finalization has
+started are not included, either.
+
+Note that "still exists" does not imply a fixed, unchanging set of objects. If
+an object in the set ceases to exist (because of a call to an instance of
+Unchecked_Deallocation), obviously it is not finalized by collection
+finalization. Any other interpretation would be nonsense, as the object does
+not exist. If an already finalized object is deallocated, Finalize is called
+again (as specified in 13.11.2(9)). This does not cause a problem, as correct
+Finalize implementations need to be prepared to be called "extra" times (see
+AARM 7.6.1(24-24.b) for the reasons).
+
+There are also problems with tasks which are allocated after the master
+of the access type has completed the first step of its finalization,
+waiting for the termination of any tasks dependent on the master (9.3(5)).
+
+To demonstrate that these scenarios are possible, consider the following
+example:
+
+ with Ada.Finalization;
+ with Ada.Unchecked_Deallocation;
+ package P is
+ type T1 is new Ada.Finalization.Controlled with null record;
+ procedure Finalize (X : in out T1);
+
+ type T2 is new Ada.Finalization.Controlled with null record;
+ procedure Finalize (X : in out T2);
+
+ task type Tsk;
+ type Tsk_Ref is access Tsk;
+
+ X1 : T1;
+
+ type T2_Ref is access T2;
+
+ procedure Free is new Ada.Unchecked_Deallocation (T2, T2_Ref);
+ end P;
+
+Assuming that Ref's finalization is non-null (i.e. that the set of allocated
+objects to be iterated over is non-empty), P.T2's Finalize procedure will be
+invoked during P.T2_Ref's collection finalization.
+
+P.T1's finalize procedure will be invoked after P.T2_Ref's collection
+finalization is complete (because P.X1 is declared before P.T2_Ref).
+
+Either of these two procedures might contain allocators of type T2_Ref or
+Tsk_Ref, or calls to P.Free.
+
+
+As the RM is currently defined, there could exist objects which never would be
+finalized by the program. This would break the invariant that every object in
+Ada is finalized at some point. Breaking this invariant would make it
+impossible to make bullet-proof abstractions, and such a result cannot be
+tolerated.
+
+To fix this problem, we must mandate checks. Where and when should these
+checks be inserted?
+
+Deallocations need to be allowed at least until the end of the
+collection finalization. Finalizing one member of the set may cause the
+deallocation of another member. Consider, for instance, a window system
+where child windows are destroyed when the parent is. Since windows are
+finalized in an arbitrary order, if the parent is finalized first, it will
+destroy and deallocate the child. We certainly do not want to outlaw such
+program designs. A deallocation after the collection finalization is only
+possible if we allow an allocation at that point; thus we do not have to handle
+it specially. Therefore, no check is necessary for deallocations.
+
+On the other hand, allocating an object from the collection after it has
+started collection finalization seems to be a bug. The items are being
+destroyed, not created. In addition, if we do not allow allocation once the
+collection finalization begins, we avoid having to define when it gets
+finalized. Moreover, it is easier to design correct behavior of a collection if
+allocations are not allowed during collection finalization.
+
+Thus, we define a check to occur on allocation - Program_Error is raised if
+an allocation occurs after collection finalization has started. However, this
+check is a distributed overhead on allocations, and provides no benefit for
+types with a trivial finalization. Thus, we require this check only for access
+types that could a non-trivial finalization. For other types, this case is
+declared to be a bounded error, allowing implementation to make the check if
+they wish, or ignore the problem (as there is no ill effect).
+
+This rule is complicated slightly, as class-wide types could have an extension
+which later adds a controlled or protected part. Thus we have to include all
+class-wide types in the required check category.
+
+
+Similarly, an allocation of a task for a master which has completed waiting
+for dependent tasks raises Program_Error. Otherwise, the task would never
+be waited for.
+
+!corrigendum 04.08(11)
+
+@drepl
+If the created object contains any tasks, they are activated (see 9.2).
+Finally, an access value that designates the created object is returned.
+@dby
+If the created object contains any tasks, and the master of the type
+of the @fa<allocator> has finished waiting for dependent tasks (see 9.3),
+Program_Error is raised.
+
+If the designated type of the type of the @fa<allocator> is class-wide or
+has a controlled or protected part, and the finalization of the collection
+of the type of the allocator (see 7.6.1) has started, Program_Error is raised.
+
+If the created object contains any tasks, they are activated (see 9.2).
+Finally, an access value that designates the created object is returned.
+
+@s8<@i<Bounded (Run-Time) Errors>>@hr
+
+It is a bounded error if the finalization of the collection of the type
+(see 7.6.1) of the @fa<allocator> has started. If the error is detected,
+Program_Error is raised. Otherwise, the allocation proceeds normally; when
+the object is finalized (or if it ever is) is unspecified.
+
+!corrigendum 07.06.01(11)
+
+@drepl
+The order in which the finalization of a master performs finalization of
+objects is as follows: Objects created by declarations in the master are
+finalized in the reverse order of their creation. For objects that were
+created by @fa<allocator>s for an access type whose ultimate ancestor is
+declared in the master, this rule is applied as though each such object that
+still exists had been created in an arbitrary order at the first freezing
+point (see 13.14) of the ultimate ancestor type.
+@dby
+The order in which the finalization of a master performs finalization of
+objects is as follows: Objects created by declarations in the master are
+finalized in the reverse order of their creation. At the first freezing
+point (see 13.14) of an access type declared in the master which is not the
+descendant of any other type, @i<finalization of the collection> of the type
+is started for the access type and all of its descendants. During finalization
+of the collection, each object that still exists that was created by
+an @fa<allocator> for the access type or one of its descendants is finalized
+in an arbitrary order.
+
!corrigendum 09.04(20)
@dinsa
@@ -704,6 +875,280 @@
I don't object to "Program_Error or work normally" as a bounded error. But
certainly we need to allow the raising of Program_Error here.
+
+****************************************************************
+
+From: Steve Baird
+Date: Tuesday, March 26, 2002 6:49 PM
+
+In addition to the access-after-finalization problems already described
+in AI-280, there are analogous problems with access types.
+
+The finalization associated with a non-derived access type is described
+in 7.6.1(11).
+
+This "collection finalization" consists of iterating over the set of
+allocated objects and finalizing them in some order.
+
+The RM states that each allocated object "that still exists" at the point
+of collection finalization is finalized. This suggests a fixed set which
+is unaffected by subsequent allocations and deallocations.
+
+It is not clear what happens if
+ a) Objects are added to the set during this iteration (via an
+ allocator).
+ b) Objects are deleted from this set during this iteration (via
+ a call to an instance of Unchecked_Deallocation).
+ c) Objects are added to the set after this finalization process is
+ complete (via an allocator).
+
+There are also problems with tasks which are allocated after the master
+of the access type has completed the first step of its finalization,
+waiting for the termination of any tasks dependent on the master (9.3(5)).
+
+To demonstrate that these scenarios are possible, consider the following
+example:
+
+ with Ada.Finalization;
+ with Ada.Unchecked_Deallocation;
+ package P is
+ type T1 is new Ada.Finalization.Controlled with null record;
+ procedure Finalize (X : in out T1);
+
+ type T2 is new Ada.Finalization.Controlled with null record;
+ procedure Finalize (X : in out T2);
+
+ task type Tsk;
+ type Tsk_Ref is access Tsk;
+
+ X1 : T1;
+
+ type T2_Ref is access T2;
+
+ procedure Free is new Ada.Unchecked_Deallocation (T2, T2_Ref);
+ end P;
+
+ Assuming that Ref's finalization is non-null (i.e. that the set of allocated
+ objects to be iterated over is non-empty), P.T2's Finalize procedure will be
+ invoked during P.T2_Ref's collection finalization.
+
+ P.T1's finalize procedure will be invoked after P.T2_Ref's collection
+ finalization is complete (because P.X1 is declared before P.T2_Ref).
+
+ Either of these two procedures might contain allocators of type T2_Ref or
+ Tsk_Ref, or calls to P.Free.
+
+I propose
+ 1) A clarification of the collection finalization rules to take into account
+ changes to the set of objects associated with a collection during the
+ collection's finalization.
+ a) If an object is allocated during this period, it will be
+ finalized eventually.
+ b) If an object is deallocated during this period, there are 3
+ cases to consider:
+ i) If the object being freed has already been finalized as
+ part of collection finalization, it is simply finalized
+ again and its storage is deallocated.
+ ii) If the object's finalization is currently in progress,
+ the object is recursively finalized, its storage is
+ deallocated, and the first Finalize invocation typically
+ must be careful not to refer to its parameter
+ (whose storage has now been deallocated).
+ iii) If the object's finalization has not begun yet, it is
+ finalized and its storage is deallocated. The (now
+ deallocated) object is not finalized as part of collection
+ finalization.
+
+ 2) After a collection's finalization has finished, evaluation of an
+ allocator of a descendant of the access type should raise Program_Error.
+ This rule is really only needed if the designated type "requires
+ nontrivial finalization" (i.e. it is controlled, is protected, or has
+ protected or controlled subcomponents); without this rule, the
+ resulting object would never be finalized. It might be more regular
+ to impose the rule in all cases, but the costs in overhead and
+ compatibility in the trivial-finalization case are probably too high to
+ justify this.
+
+ 3) An attempt to allocate a task after the master of the access type has
+ finished waiting for the termination of its dependent tasks should raise
+ Program_Error.
+
+****************************************************************
+
+From: Ted Baker
+Date: Wednesday, March 27, 2002 4:58 AM
+
+| 2) After a collection's finalization has finished, evaluation
+| of an allocator of a descendant of the access type should raise
+| Program_Error.
+
+Why not simply raise Program_Error for any attempt to allocate
+after the collections finalization has *started*?
+ =======
+
+It is hard for me to imagine writing (and adequately testing!)
+finalization code for a collection that I would trust to be
+correct for concurrent allocations and deallocations of items in
+the collection.
+
+Requiring/allowing such concurrency of collection finalization
+and allocation/deallocation activity is just asking for trouble.
+
+| 3) An attempt to allocate a task after the master of the
+| access type has finished waiting for the termination of its
+| dependent tasks should raise Program_Error.
+
+Of course.
+
+****************************************************************
+
+From: Randy Brukardt
+Date: Wednesday, April 17, 2002 8:13 PM
+
+Ted said:
+
+> | 2) After a collection's finalization has finished, evaluation
+> | of an allocator of a descendant of the access type should raise
+> | Program_Error.
+>
+> Why not simply raise Program_Error for any attempt to allocate
+> after the collections finalization has *started*?
+
+Good question. It's a lot simpler than Steve's proposal. The problem with
+Steve's proposal is easily seen by looking at his case 1bii:
+
+ If the object's finalization is currently in progress, the object
+ is recursively finalized, its storage is deallocated, and the
+ first Finalize invocation typically must be careful not to refer to
+ its parameter (whose storage has now been deallocated).
+
+But this appears impossible. How can I write a Finalize that "is careful not
+to refer to its parameter"? Even if we don't have to worry about other tasks
+(because they all must necessarily have been waited for), we can still
+easily do this by (mistakenly) calling some subprogram. What happens if we
+aren't "careful"? Presumably the program is erroneous. That doesn't seem
+like a useful way to handle what is most likely a bug.
+
+The question is, is there some useful capability that requires all of this
+extra definition? I would say no: this check is most likely going to turn up
+a bug in the user's finalization code than anything useful. Even when the
+deallocation is benign, we more than likely would prefer to avoid it.
+
+So I would simplify the proposal to:
+
+For an access type with whose designated type has a controlled or protected
+part, once a collection's finalization has started, evaluation of an
+allocator or a call of an instance of Unchecked_Deallocation of a descendant
+of the access type raises Program_Error. For other access types (whose
+designated type does not have a controlled or protected part), evaluation of
+an allocator or a call of an instance of Unchecked_Deallocation of a
+descendant of the access type is a bounded error. If the error is detected,
+Program_Error is raised. Otherwise, the allocation or deallocation proceeds
+normally. For an allocator, whether the object is ever finalized is
+unspecified. [Such a finalization cannot have a visible effect.]
+
+An attempt to allocate a task after the master of the access type has
+finished waiting for the termination of its dependent tasks raises
+Program_Error.
+
+****************************************************************
+
+From: Randy Brukardt
+Date: Wednesday, April 17, 2002 10:10 PM
+
+I said:
+
+> The question is, is there some useful capability that requires all of this
+> extra definition? I would say no: this check is most likely going to turn
+up
+> a bug in the user's finalization code than anything useful. Even when the
+> deallocation is benign, we more than likely would prefer to avoid it.
+
+Unfortunately, I've kept thinking about this while writing up the AI, and
+I've found a reason that (part of) this capability is useful.
+
+Claw has a classwide access type that can point at any window object. Claw
+also enforces a rule that all child windows have to be destroyed before
+their parent (necessary as Windows does it automatically, but if we let
+Windows do it, we wouldn't call the appropriate, possibly overridden
+finalizer). In our examples, Claw objects are generally allocated on the
+stack, but nothing prevents a program from allocating them instead.
+
+So, if we have a program like:
+
+ Window, Button : Claw.Any_Window_Access_Type;
+ begin
+ Button := new Claw.Buttons.Button_Type;
+ Window := new Claw.Basic_Window.Basic_Window_Type;
+ Create (Window.all, ....);
+ Create (Button.all, Parent => Window.all, ...);
+ ...
+
+Now, if these objects are never deallocated, the collection finalization of
+Claw.Any_Window_Access_Type will finalize them. Let's assume that Window is
+finalized first. That finalization will destroy Button (as it is a child of
+Window).
+
+In Claw, there is no problem, since we only operate on objects, leaving
+memory management to the caller. However, it is easy to imagine Claw* which
+uses access types, with Create calls returning pointers to allocated
+objects. (This structure is common in objects ported from C++ or Java.)
+Claw* would deallocate objects when it destroyed them. In that structure,
+the destruction of Button would deallocate an object for the collection that
+we are finalizing.
+
+Clearly, this structure is not so weird that we can safely outlaw it. Thus,
+I reluctantly conclude that we have to allow deallocations until the
+collection is completely finalized.
+
+However, the situation is different for allocators. Creating something (of
+the same type) during finalization of a type is pretty weird. We can safely
+eliminate from consideration.
+
+Steve notes (implicitly) in his writeup that deallocation after the
+finalization completes does not need a special check. It can only do
+something interesting if an allocation after the finalization completes
+previously happened. So we do not need a special rule for that. Moreover, we
+don't need a special rule for either of Steve's cases 1bi and 1biii. These
+are what would happen simply by following the rules of the RM. (We should
+describe them in the AI and AARM, though.) In addition, Ted's concern is a
+non-issue for deallocation alone. That's because the standard says that the
+finalization is done in an arbitrary order. Thus, an implementation which
+depends on the order of finalization is already wrong. And all that explicit
+deallocations do is possibly change the order. Case 1bii (deallocating the
+object itself) is also not a real problem. If it is referenced after
+deallocation, the execution is erroneous (by a reasonable interpretation of
+"nonexistent" in 13.11.2(16)). We may want a rule for this case anyway (to
+avoid making implementations support this case; without a rule, the runtime
+cannot access the object again after calling Finalize, and this seems overly
+constraining). There is no benefit to mandating a check here (and it would
+be hard to perform anyway).
+
+Conclusion: we do not need to change anything in the current RM for the
+deallocation case. In particular, we do not need a check for deallocations.
+
+This conclusion frees us to make the check for allocations at any convenient
+point. And at the start of finalization is certainly easier for
+implementations (the runtime can iterate through a list without having to
+worry about someone adding something), and doesn't seem to cause problems
+for programs (at least until I think of a counter-example. :-)
+
+That makes the proposal:
+
+For an access type with whose designated type has a controlled or protected
+part, once a collection's finalization has started, evaluation of an
+allocator
+of a descendant of the access type raises Program_Error. For other access
+types (whose designated type does not have a controlled or protected part),
+evaluation of an allocator of a descendant of the access type is a bounded
+error. If the error is detected, Program_Error is raised. Otherwise, the
+allocation proceeds normally; when the object is finalized (or if it ever is)
+is unspecified. [Such a finalization cannot have a visible effect.]
+
+An attempt to allocate a task after the master of the access type has
+finished waiting for the termination of its dependent tasks raises
+Program_Error.
+
****************************************************************
Questions? Ask the ACAA Technical Agent