[PATCH 36/36] Make TRY/CATCH use real C++ try/catch in C++ mode

Pedro Alves
2015-02-09T23:22:02Z

Although the current TRY/CATCH implementation works in C++ mode too,
it relies on setjmp/longjmp, and longjmp bypasses calling the
destructors of objects on the stack, which is obviously bad for C++.

This patch fixes this by makes TRY/CATCH use real try/catch in C++
mode behind the scenes. The way this is done allows RAII and cleanups
to coexist while we phase out cleanups, instead of requiring a flag
day.

This patch is not strictly necessary until we require a C++ compiler
and start actually using RAII, though I'm all for baby steps, and it
shows my proposed way forward. I think we should put it in now, to
allow easier experimentation and exposure of potential problems with
real C++ exceptions.

gdb/ChangeLog:
2015-02-09 Pedro Alves <palves@redhat.com>

* common/common-exceptions.c [!__cplusplus] (enum catcher_state)
(exceptions_state_mc_action_iter)
(exceptions_state_mc_action_iter_1, exceptions_state_mc_catch):
Don't define.
[__cplusplus] (try_scope_depth): New global.
[__cplusplus] (exception_try_scope_entry)
(exception_try_scope_exit, gdb_exception_sliced_copy)
(exception_rethrow): New functions.
(throw_exception): In C++ mode, throw
gdb_exception_RETURN_MASK_QUIT for RETURN_QUIT and
gdb_exception_RETURN_MASK_ERROR for RETURN_ERROR.
(throw_it): In C++ mode, use try_scope_depth.
* common/common-exceptions.h [!__cplusplus]
(exceptions_state_mc_action_iter)
(exceptions_state_mc_action_iter_1, exceptions_state_mc_catch):
Don't declare.
[__cplusplus] (exception_try_scope_entry)
(exception_try_scope_exit, exception_rethrow): Declare.
[__cplusplus] (struct exception_try_scope): New struct.
[__cplusplus] (TRY, CATCH, END_CATCH): Reimplement on top of real
C++ exceptions.
(struct gdb_exception_RETURN_MASK_ALL)
(struct gdb_exception_RETURN_MASK_ERROR)
(struct gdb_exception_RETURN_MASK_QUIT): New types.
---
gdb/common/common-exceptions.c | 77 +++++++++++++++++++++++++++++++++++++++++-
gdb/common/common-exceptions.h | 67 ++++++++++++++++++++++++++++++++++++
2 files changed, 143 insertions(+), 1 deletion(-)

diff --git a/gdb/common/common-exceptions.c b/gdb/common/common-exceptions.c
index 2ad0ce1..b300a9d 100644
--- a/gdb/common/common-exceptions.c
+++ b/gdb/common/common-exceptions.c
@@ -22,6 +22,8 @@

const struct gdb_exception exception_none = { 0, GDB_NO_ERROR, NULL };

+#ifndef __cplusplus
+
/* Possible catcher states. */
enum catcher_state {
/* Initial state, a new catcher has just been created. */
@@ -185,7 +187,7 @@ exceptions_state_mc_catch (struct gdb_exception *exception,
{
if (mask & RETURN_MASK (exception->reason))
{
- /* Exit normally and let the called handle the
+ /* Exit normally and let the caller handle the
exception. */
return 1;
}
@@ -211,6 +213,56 @@ exceptions_state_mc_action_iter_1 (void)
return exceptions_state_mc (CATCH_ITER_1);
}

+#else /* !__cplusplus */
+
+/* How many nested TRY blocks we have. See exception_messages and
+ throw_it. */
+
+static int try_scope_depth;
+
+/* Called on entry to a TRY scope. */
+
+void *
+exception_try_scope_entry (void)
+{
+ ++try_scope_depth;
+ return (void *) save_cleanups ();
+}
+
+/* Called on exit of a TRY scope, either normal exit or exception
+ exit. */
+
+void
+exception_try_scope_exit (void *saved_state)
+{
+ restore_cleanups ((struct cleanup *) saved_state);
+ --try_scope_depth;
+}
+
+/* Called by the default catch block. IOW, we'll get here before
+ jumping out to the next outermost scope an exception if a GDB
+ exception is not caught. */
+
+void
+exception_rethrow (void)
+{
+ /* Run this scope's cleanups before re-throwing to the next
+ outermost scope. */
+ prepare_to_throw_exception ();
+ do_cleanups (all_cleanups ());
+ throw;
+}
+
+/* Copy the 'gdb_exception' portion of FROM to TO. */
+
+static void
+gdb_exception_sliced_copy (struct gdb_exception *to, const struct gdb_exception *from)
+{
+ *to = *from;
+}
+
+#endif /* !__cplusplus */
+
/* Return EXCEPTION to the nearest containing catch_errors(). */

void
@@ -220,12 +272,31 @@ throw_exception (struct gdb_exception exception)

do_cleanups (all_cleanups ());

+#ifndef __cplusplus
/* Jump to the containing catch_errors() call, communicating REASON
to that call via setjmp's return value. Note that REASON can't
be zero, by definition in defs.h. */
exceptions_state_mc (CATCH_THROWING);
current_catcher->exception = exception;
SIGLONGJMP (current_catcher->buf, exception.reason);
+#else
+ if (exception.reason == RETURN_QUIT)
+ {
+ gdb_exception_RETURN_MASK_QUIT ex;
+
+ gdb_exception_sliced_copy (&ex, &exception);
+ throw ex;
+ }
+ else if (exception.reason == RETURN_ERROR)
+ {
+ gdb_exception_RETURN_MASK_ERROR ex;
+
+ gdb_exception_sliced_copy (&ex, &exception);
+ throw ex;
+ }
+ else
+ gdb_assert_not_reached ("invalid return reason");
+#endif
}

/* A stack of exception messages.
@@ -249,7 +320,11 @@ throw_it (enum return_reason reason, enum errors error, const char *fmt,
{
struct gdb_exception e;
char *new_message;
+#ifndef __cplusplus
int depth = catcher_list_size ();
+#else
+ int depth = try_scope_depth;
+#endif

gdb_assert (depth > 0);

diff --git a/gdb/common/common-exceptions.h b/gdb/common/common-exceptions.h
index d2c0bee..2e6a6d9 100644
--- a/gdb/common/common-exceptions.h
+++ b/gdb/common/common-exceptions.h
@@ -121,10 +121,16 @@ struct gdb_exception
the exceptions subsystem and not used other than via the TRY/CATCH
macros defined below. */

+#ifndef __cplusplus
extern SIGJMP_BUF *exceptions_state_mc_init (void);
extern int exceptions_state_mc_action_iter (void);
extern int exceptions_state_mc_action_iter_1 (void);
extern int exceptions_state_mc_catch (struct gdb_exception *, int);
+#else
+extern void *exception_try_scope_entry (void);
+extern void exception_try_scope_exit (void *saved_state);
+extern void exception_rethrow (void);
+#endif

/* Macro to wrap up standard try/catch behavior.

@@ -151,6 +157,8 @@ extern int exceptions_state_mc_catch (struct gdb_exception *, int);

*/

+#ifndef __cplusplus
+
#define TRY \
{ \
SIGJMP_BUF *buf = \
@@ -168,6 +176,65 @@ extern int exceptions_state_mc_catch (struct gdb_exception *, int);
#define END_CATCH \
}

+#else
+
+/* Prevent error/quit during TRY from calling cleanups established
+ prior to here. This pops out the scope in either case of normal
+ exit or exception exit. */
+struct exception_try_scope
+{
+ exception_try_scope ()
+ {
+ saved_state = exception_try_scope_entry ();
+ }
+ ~exception_try_scope ()
+ {
+ exception_try_scope_exit (saved_state);
+ }
+
+ void *saved_state;
+};
+
+/* We still need to wrap TRY/CATCH in C++ so that cleanups and C++
+ exceptions can coexist. The TRY blocked is wrapped in a
+ do/while(0) so that break/continue within the block works the same
+ as in C. */
+#define TRY \
+ try \
+ { \
+ exception_try_scope exception_try_scope_instance; \
+ do \
+ {
+
+#define CATCH(EXCEPTION, MASK) \
+ } while (0); \
+ } \
+ catch (struct gdb_exception ## _ ## MASK &EXCEPTION)
+
+#define END_CATCH \
+ catch (...) \
+ { \
+ exception_rethrow (); \
+ }
+
+/* The exception types client code may catch. They're just shims
+ around gdb_exception that add nothing but type info. Which is used
+ is selected depending on the MASK argument passed to CATCH. */
+
+struct gdb_exception_RETURN_MASK_ALL : public gdb_exception
+{
+};
+
+struct gdb_exception_RETURN_MASK_ERROR : public gdb_exception_RETURN_MASK_ALL
+{
+};
+
+struct gdb_exception_RETURN_MASK_QUIT : public gdb_exception_RETURN_MASK_ALL
+{
+};
+
+#endif
+
/* *INDENT-ON* */

/* Hook to allow client-specific actions to be performed prior to
--
1.9.3