blob: 151a3e6d8d27a3eca84556ae4f9f1c5e4ebfaec6 [file] [log] [blame]
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 --
-------------------