| commit 5d733372faa97c1c3943a20a252d000db37c738b |
| Author: Alexandre Oliva <oliva@adacore.com> |
| Date: Fri Aug 2 18:46:51 2019 +0000 |
| |
| rework Ada EH Machine_Occurrence deallocation |
| |
| Introduce exception handler ABI #1 to ensure single release, no access |
| after release of reraised Machine_Occurrences, and no failure to |
| re-reraise a Machine_Occurrence. |
| |
| Unlike Ada exceptions, foreign exceptions do not get a new |
| Machine_Occurrence upon reraise, but each handler would delete the |
| exception upon completion, normal or exceptional, save for the case of |
| a 'raise;' statement within the handler, that avoided the delete by |
| clearing the exception pointer that the cleanup would use to release |
| it. The cleared exception pointer might then be used by a subsequent |
| reraise within the same handler. Get_Current_Excep.all would also |
| expose the Machine_Occurrence to reuse by Reraise_Occurrence, even for |
| native exceptions. |
| |
| Under ABI #1, Begin_Handler_v1 claims responsibility for releasing an |
| exception by saving its cleanup and setting it to Claimed_Cleanup. |
| End_Handler_v1 restores the cleanup and runs it, as long as it isn't |
| still Claimed_Cleanup (which indicates an enclosing handler has |
| already claimed responsibility for releasing it), and as long as the |
| same exception is not being propagated up (the next handler of the |
| propagating exception will then claim responsibility for releasing |
| it), so reraise no longer needs to clear the exception pointer, and it |
| can just propagate the exception, just like Reraise_Occurrence. |
| |
| ABI #1 is fully interoperable with ABI #0, i.e., exception handlers |
| that call the #0 primitives can be linked together with ones that call |
| the #1 primitives, and they will not misbehave. When a #1 handler |
| claims responsibility for releasing an exception, even #0 reraises |
| dynamically nested within it will refrain from releasing it. However, |
| when a #0 handler is a handler of a foreign exception that would have |
| been responsible for releasing it with #1, a Reraise_Occurrence of |
| that foreign or other Machine_Occurrence-carrying exception may still |
| cause the exception to be released multiple times, and to be used |
| after it is first released, even if other handlers of the foreign |
| exception use #1. |
| |
| |
| for gcc/ada/ChangeLog |
| |
| * libgnat/a-exexpr.adb (Begin_Handler_v1, End_Handler_v1): New. |
| (Claimed_Cleanup): New. |
| (Begin_Handler, End_Handler): Document. |
| * gcc-interface/trans.c (gigi): Switch to exception handler |
| ABI #1. |
| (Exception_Handler_to_gnu_gcc): Save the original cleanup |
| returned by begin handler, pass it to end handler, and use |
| EH_ELSE_EXPR to pass a propagating exception to end handler. |
| (gnat_to_gnu): Leave the exception pointer alone for reraise. |
| (add_cleanup): Handle EH_ELSE_EXPR, require it by itself. |
| |
| From-SVN: r274029 |
| |
| diff --git a/gcc/ada/libgnat/a-exexpr.adb b/gcc/ada/libgnat/a-exexpr.adb |
| index b1aa1c6e6ba..5e72fd6e3f2 100644 |
| --- a/gcc/ada/libgnat/a-exexpr.adb |
| +++ b/gcc/ada/libgnat/a-exexpr.adb |
| @@ -197,15 +197,75 @@ package body Exception_Propagation is |
| -- whose machine occurrence is Mo. The message is empty, the backtrace |
| -- is empty too and the exception identity is Foreign_Exception. |
| |
| - -- Hooks called when entering/leaving an exception handler for a given |
| - -- occurrence, aimed at handling the stack of active occurrences. The |
| - -- calls are generated by gigi in tree_transform/N_Exception_Handler. |
| + -- Hooks called when entering/leaving an exception handler for a |
| + -- given occurrence. The calls are generated by gigi in |
| + -- Exception_Handler_to_gnu_gcc. |
| + |
| + -- Begin_Handler_v1, called when entering an exception handler, |
| + -- claims responsibility for the handler to release the |
| + -- GCC_Exception occurrence. End_Handler_v1, called when |
| + -- leaving the handler, releases the occurrence, unless the |
| + -- occurrence is propagating further up, or the handler is |
| + -- dynamically nested in the context of another handler that |
| + -- claimed responsibility for releasing that occurrence. |
| + |
| + -- Responsibility is claimed by changing the Cleanup field to |
| + -- Claimed_Cleanup, which enables claimed exceptions to be |
| + -- recognized, and avoids accidental releases even by foreign |
| + -- handlers. |
| + |
| + function Begin_Handler_v1 |
| + (GCC_Exception : not null GCC_Exception_Access) |
| + return System.Address; |
| + pragma Export (C, Begin_Handler_v1, "__gnat_begin_handler_v1"); |
| + -- Called when entering an exception handler. Claim |
| + -- responsibility for releasing GCC_Exception, by setting the |
| + -- cleanup/release function to Claimed_Cleanup, and return the |
| + -- address of the previous cleanup/release function. |
| + |
| + procedure End_Handler_v1 |
| + (GCC_Exception : not null GCC_Exception_Access; |
| + Saved_Cleanup : System.Address; |
| + Propagating_Exception : GCC_Exception_Access); |
| + pragma Export (C, End_Handler_v1, "__gnat_end_handler_v1"); |
| + -- Called when leaving an exception handler. Restore the |
| + -- Saved_Cleanup in the GCC_Exception occurrence, and then release |
| + -- it, unless it remains claimed by an enclosing handler, or |
| + -- GCC_Exception and Propagating_Exception are the same |
| + -- occurrence. Propagating_Exception could be either an |
| + -- occurrence (re)raised within the handler of GCC_Exception, when |
| + -- we're executing as an exceptional cleanup, or null, if we're |
| + -- completing the handler of GCC_Exception normally. |
| + |
| + procedure Claimed_Cleanup |
| + (Reason : Unwind_Reason_Code; |
| + GCC_Exception : not null GCC_Exception_Access); |
| + pragma Export (C, Claimed_Cleanup, "__gnat_claimed_cleanup"); |
| + -- A do-nothing placeholder installed as GCC_Exception.Cleanup |
| + -- while handling GCC_Exception, to claim responsibility for |
| + -- releasing it, and to stop it from being accidentally released. |
| + |
| + -- The following are version 0 implementations of the version 1 |
| + -- hooks above. They remain in place for compatibility with the |
| + -- output of compilers that still use version 0, such as those |
| + -- used during bootstrap. They are interoperable with the v1 |
| + -- hooks, except that the older versions may malfunction when |
| + -- handling foreign exceptions passed to Reraise_Occurrence. |
| |
| procedure Begin_Handler (GCC_Exception : not null GCC_Exception_Access); |
| pragma Export (C, Begin_Handler, "__gnat_begin_handler"); |
| + -- Called when entering an exception handler translated by an old |
| + -- compiler. It does nothing. |
| |
| procedure End_Handler (GCC_Exception : GCC_Exception_Access); |
| pragma Export (C, End_Handler, "__gnat_end_handler"); |
| + -- Called when leaving an exception handler translated by an old |
| + -- compiler. It releases GCC_Exception, unless it is null. It is |
| + -- only ever null when the handler has a 'raise;' translated by a |
| + -- v0-using compiler. The artificial handler variable passed to |
| + -- End_Handler was set to null to tell End_Handler to refrain from |
| + -- releasing the reraised exception. In v1 safer ways are used to |
| + -- accomplish that. |
| |
| -------------------------------------------------------------------- |
| -- Accessors to Basic Components of a GNAT Exception Data Pointer -- |
| @@ -352,6 +412,128 @@ package body Exception_Propagation is |
| end if; |
| end Setup_Current_Excep; |
| |
| + ---------------------- |
| + -- Begin_Handler_v1 -- |
| + ---------------------- |
| + |
| + function Begin_Handler_v1 |
| + (GCC_Exception : not null GCC_Exception_Access) |
| + return System.Address is |
| + Saved_Cleanup : constant System.Address := GCC_Exception.Cleanup; |
| + begin |
| + -- Claim responsibility for releasing this exception, and stop |
| + -- others from releasing it. |
| + GCC_Exception.Cleanup := Claimed_Cleanup'Address; |
| + return Saved_Cleanup; |
| + end Begin_Handler_v1; |
| + |
| + -------------------- |
| + -- End_Handler_v1 -- |
| + -------------------- |
| + |
| + procedure End_Handler_v1 |
| + (GCC_Exception : not null GCC_Exception_Access; |
| + Saved_Cleanup : System.Address; |
| + Propagating_Exception : GCC_Exception_Access) is |
| + begin |
| + GCC_Exception.Cleanup := Saved_Cleanup; |
| + -- Restore the Saved_Cleanup, so that it is either used to |
| + -- release GCC_Exception below, or transferred to the next |
| + -- handler of the Propagating_Exception occurrence. The |
| + -- following test ensures that an occurrence is only released |
| + -- once, even after reraises. |
| + -- |
| + -- The idea is that the GCC_Exception is not to be released |
| + -- unless it had an unclaimed Cleanup when the handler started |
| + -- (see Begin_Handler_v1 above), but if we propagate across its |
| + -- handler a reraise of the same exception, we transfer to the |
| + -- Propagating_Exception the responsibility for running the |
| + -- Saved_Cleanup when its handler completes. |
| + -- |
| + -- This ownership transfer mechanism ensures safety, as in |
| + -- single release and no dangling pointers, because there is no |
| + -- way to hold on to the Machine_Occurrence of an |
| + -- Exception_Occurrence: the only situations in which another |
| + -- Exception_Occurrence gets the same Machine_Occurrence are |
| + -- through Reraise_Occurrence, and plain reraise, and so we |
| + -- have the following possibilities: |
| + -- |
| + -- - Reraise_Occurrence is handled within the running handler, |
| + -- and so when completing the dynamically nested handler, we |
| + -- must NOT release the exception. A Claimed_Cleanup upon |
| + -- entry of the nested handler, installed when entering the |
| + -- enclosing handler, ensures the exception will not be |
| + -- released by the nested handler, but rather by the enclosing |
| + -- handler. |
| + -- |
| + -- - Reraise_Occurrence/reraise escapes the running handler, |
| + -- and we run as an exceptional cleanup for GCC_Exception. The |
| + -- Saved_Cleanup was reinstalled, but since we're propagating |
| + -- the same machine occurrence, we do not release it. Instead, |
| + -- we transfer responsibility for releasing it to the eventual |
| + -- handler of the propagating exception. |
| + -- |
| + -- - An unrelated exception propagates through the running |
| + -- handler. We restored GCC_Exception.Saved_Cleanup above. |
| + -- Since we're propagating a different exception, we proceed to |
| + -- release GCC_Exception, unless Saved_Cleanup was |
| + -- Claimed_Cleanup, because then we know we're not in the |
| + -- outermost handler for GCC_Exception. |
| + -- |
| + -- - The handler completes normally, so it reinstalls the |
| + -- Saved_Cleanup and runs it, unless it was Claimed_Cleanup. |
| + -- If Saved_Cleanup is null, Unwind_DeleteException (currently) |
| + -- has no effect, so we could skip it, but if it is ever |
| + -- changed to do more in this case, we're ready for that, |
| + -- calling it exactly once. |
| + if Saved_Cleanup /= Claimed_Cleanup'Address |
| + and then |
| + Propagating_Exception /= GCC_Exception |
| + then |
| + declare |
| + Current : constant EOA := Get_Current_Excep.all; |
| + Cur_Occ : constant GCC_Exception_Access |
| + := To_GCC_Exception (Current.Machine_Occurrence); |
| + begin |
| + -- If we are releasing the Machine_Occurrence of the current |
| + -- exception, reset the access to it, so that it is no |
| + -- longer accessible. |
| + if Cur_Occ = GCC_Exception then |
| + Current.Machine_Occurrence := System.Null_Address; |
| + end if; |
| + end; |
| + Unwind_DeleteException (GCC_Exception); |
| + end if; |
| + end End_Handler_v1; |
| + |
| + --------------------- |
| + -- Claimed_Cleanup -- |
| + --------------------- |
| + |
| + procedure Claimed_Cleanup |
| + (Reason : Unwind_Reason_Code; |
| + GCC_Exception : not null GCC_Exception_Access) is |
| + pragma Unreferenced (Reason); |
| + pragma Unreferenced (GCC_Exception); |
| + begin |
| + -- This procedure should never run. If it does, it's either a |
| + -- version 0 handler or a foreign handler, attempting to |
| + -- release an exception while a version 1 handler that claimed |
| + -- responsibility for releasing the exception remains still |
| + -- active. This placeholder stops GCC_Exception from being |
| + -- released by them. |
| + |
| + -- We could get away with just Null_Address instead, with |
| + -- nearly the same effect, but with this placeholder we can |
| + -- detect and report unexpected releases, and we can tell apart |
| + -- a GCC_Exception without a Cleanup, from one with another |
| + -- active handler, so as to still call Unwind_DeleteException |
| + -- exactly once: currently, Unwind_DeleteException does nothing |
| + -- when the Cleanup is null, but should it ever be changed to |
| + -- do more, we'll still be safe. |
| + null; |
| + end Claimed_Cleanup; |
| + |
| ------------------- |
| -- Begin_Handler -- |
| ------------------- |