| /* Exception (throw catch) mechanism, for GDB, the GNU debugger. | |
| Copyright (C) 1986-2018 Free Software Foundation, Inc. | |
| This file is part of GDB. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
| #include "common-defs.h" | |
| #include "common-exceptions.h" | |
| const struct gdb_exception exception_none = { (enum return_reason) 0, GDB_NO_ERROR, NULL }; | |
| /* Possible catcher states. */ | |
| enum catcher_state { | |
| /* Initial state, a new catcher has just been created. */ | |
| CATCHER_CREATED, | |
| /* The catch code is running. */ | |
| CATCHER_RUNNING, | |
| CATCHER_RUNNING_1, | |
| /* The catch code threw an exception. */ | |
| CATCHER_ABORTING | |
| }; | |
| /* Possible catcher actions. */ | |
| enum catcher_action { | |
| CATCH_ITER, | |
| CATCH_ITER_1, | |
| CATCH_THROWING | |
| }; | |
| struct catcher | |
| { | |
| enum catcher_state state; | |
| /* Jump buffer pointing back at the exception handler. */ | |
| jmp_buf buf; | |
| /* Status buffer belonging to the exception handler. */ | |
| struct gdb_exception exception; | |
| struct cleanup *saved_cleanup_chain; | |
| /* Back link. */ | |
| struct catcher *prev; | |
| }; | |
| /* Where to go for throw_exception(). */ | |
| static struct catcher *current_catcher; | |
| #if GDB_XCPT == GDB_XCPT_SJMP | |
| /* Return length of current_catcher list. */ | |
| static int | |
| catcher_list_size (void) | |
| { | |
| int size; | |
| struct catcher *catcher; | |
| for (size = 0, catcher = current_catcher; | |
| catcher != NULL; | |
| catcher = catcher->prev) | |
| ++size; | |
| return size; | |
| } | |
| #endif | |
| jmp_buf * | |
| exceptions_state_mc_init (void) | |
| { | |
| struct catcher *new_catcher = XCNEW (struct catcher); | |
| /* Start with no exception. */ | |
| new_catcher->exception = exception_none; | |
| /* Prevent error/quit during FUNC from calling cleanups established | |
| prior to here. */ | |
| new_catcher->saved_cleanup_chain = save_cleanups (); | |
| /* Push this new catcher on the top. */ | |
| new_catcher->prev = current_catcher; | |
| current_catcher = new_catcher; | |
| new_catcher->state = CATCHER_CREATED; | |
| return &new_catcher->buf; | |
| } | |
| static void | |
| catcher_pop (void) | |
| { | |
| struct catcher *old_catcher = current_catcher; | |
| current_catcher = old_catcher->prev; | |
| /* Restore the cleanup chain, the error/quit messages, and the uiout | |
| builder, to their original states. */ | |
| restore_cleanups (old_catcher->saved_cleanup_chain); | |
| xfree (old_catcher); | |
| } | |
| /* Catcher state machine. Returns non-zero if the m/c should be run | |
| again, zero if it should abort. */ | |
| static int | |
| exceptions_state_mc (enum catcher_action action) | |
| { | |
| switch (current_catcher->state) | |
| { | |
| case CATCHER_CREATED: | |
| switch (action) | |
| { | |
| case CATCH_ITER: | |
| /* Allow the code to run the catcher. */ | |
| current_catcher->state = CATCHER_RUNNING; | |
| return 1; | |
| default: | |
| internal_error (__FILE__, __LINE__, _("bad state")); | |
| } | |
| case CATCHER_RUNNING: | |
| switch (action) | |
| { | |
| case CATCH_ITER: | |
| /* No error/quit has occured. */ | |
| return 0; | |
| case CATCH_ITER_1: | |
| current_catcher->state = CATCHER_RUNNING_1; | |
| return 1; | |
| case CATCH_THROWING: | |
| current_catcher->state = CATCHER_ABORTING; | |
| /* See also throw_exception. */ | |
| return 1; | |
| default: | |
| internal_error (__FILE__, __LINE__, _("bad switch")); | |
| } | |
| case CATCHER_RUNNING_1: | |
| switch (action) | |
| { | |
| case CATCH_ITER: | |
| /* The did a "break" from the inner while loop. */ | |
| return 0; | |
| case CATCH_ITER_1: | |
| current_catcher->state = CATCHER_RUNNING; | |
| return 0; | |
| case CATCH_THROWING: | |
| current_catcher->state = CATCHER_ABORTING; | |
| /* See also throw_exception. */ | |
| return 1; | |
| default: | |
| internal_error (__FILE__, __LINE__, _("bad switch")); | |
| } | |
| case CATCHER_ABORTING: | |
| switch (action) | |
| { | |
| case CATCH_ITER: | |
| { | |
| /* Exit normally if this catcher can handle this | |
| exception. The caller analyses the func return | |
| values. */ | |
| return 0; | |
| } | |
| default: | |
| internal_error (__FILE__, __LINE__, _("bad state")); | |
| } | |
| default: | |
| internal_error (__FILE__, __LINE__, _("bad switch")); | |
| } | |
| } | |
| int | |
| exceptions_state_mc_catch (struct gdb_exception *exception, | |
| int mask) | |
| { | |
| *exception = current_catcher->exception; | |
| catcher_pop (); | |
| if (exception->reason < 0) | |
| { | |
| if (mask & RETURN_MASK (exception->reason)) | |
| { | |
| /* Exit normally and let the caller handle the | |
| exception. */ | |
| return 1; | |
| } | |
| /* The caller didn't request that the event be caught, relay the | |
| event to the next exception_catch/CATCH_SJLJ. */ | |
| throw_exception_sjlj (*exception); | |
| } | |
| /* No exception was thrown. */ | |
| return 0; | |
| } | |
| int | |
| exceptions_state_mc_action_iter (void) | |
| { | |
| return exceptions_state_mc (CATCH_ITER); | |
| } | |
| int | |
| exceptions_state_mc_action_iter_1 (void) | |
| { | |
| return exceptions_state_mc (CATCH_ITER_1); | |
| } | |
| #if GDB_XCPT != GDB_XCPT_SJMP | |
| /* 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. */ | |
| 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 /* !GDB_XCPT_SJMP */ | |
| /* Return EXCEPTION to the nearest containing CATCH_SJLJ block. */ | |
| void | |
| throw_exception_sjlj (struct gdb_exception exception) | |
| { | |
| do_cleanups (all_cleanups ()); | |
| /* Jump to the nearest CATCH_SJLJ block, communicating REASON to | |
| that call via setjmp's return value. Note that REASON can't be | |
| zero, by definition in common-exceptions.h. */ | |
| exceptions_state_mc (CATCH_THROWING); | |
| current_catcher->exception = exception; | |
| longjmp (current_catcher->buf, exception.reason); | |
| } | |
| #if GDB_XCPT != GDB_XCPT_SJMP | |
| /* Implementation of throw_exception that uses C++ try/catch. */ | |
| static ATTRIBUTE_NORETURN void | |
| throw_exception_cxx (struct gdb_exception exception) | |
| { | |
| do_cleanups (all_cleanups ()); | |
| 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 | |
| void | |
| throw_exception (struct gdb_exception exception) | |
| { | |
| #if GDB_XCPT == GDB_XCPT_SJMP | |
| throw_exception_sjlj (exception); | |
| #else | |
| throw_exception_cxx (exception); | |
| #endif | |
| } | |
| /* A stack of exception messages. | |
| This is needed to handle nested calls to throw_it: we don't want to | |
| xfree space for a message before it's used. | |
| This can happen if we throw an exception during a cleanup: | |
| An outer TRY_CATCH may have an exception message it wants to print, | |
| but while doing cleanups further calls to throw_it are made. | |
| This is indexed by the size of the current_catcher list. | |
| It is a dynamically allocated array so that we don't care how deeply | |
| GDB nests its TRY_CATCHs. */ | |
| static char **exception_messages; | |
| /* The number of currently allocated entries in exception_messages. */ | |
| static int exception_messages_size; | |
| static void ATTRIBUTE_NORETURN ATTRIBUTE_PRINTF (3, 0) | |
| throw_it (enum return_reason reason, enum errors error, const char *fmt, | |
| va_list ap) | |
| { | |
| struct gdb_exception e; | |
| char *new_message; | |
| #if GDB_XCPT == GDB_XCPT_SJMP | |
| int depth = catcher_list_size (); | |
| #else | |
| int depth = try_scope_depth; | |
| #endif | |
| gdb_assert (depth > 0); | |
| /* Note: The new message may use an old message's text. */ | |
| new_message = xstrvprintf (fmt, ap); | |
| if (depth > exception_messages_size) | |
| { | |
| int old_size = exception_messages_size; | |
| exception_messages_size = depth + 10; | |
| exception_messages = XRESIZEVEC (char *, exception_messages, | |
| exception_messages_size); | |
| memset (exception_messages + old_size, 0, | |
| (exception_messages_size - old_size) * sizeof (char *)); | |
| } | |
| xfree (exception_messages[depth - 1]); | |
| exception_messages[depth - 1] = new_message; | |
| /* Create the exception. */ | |
| e.reason = reason; | |
| e.error = error; | |
| e.message = new_message; | |
| /* Throw the exception. */ | |
| throw_exception (e); | |
| } | |
| void | |
| throw_verror (enum errors error, const char *fmt, va_list ap) | |
| { | |
| throw_it (RETURN_ERROR, error, fmt, ap); | |
| } | |
| void | |
| throw_vquit (const char *fmt, va_list ap) | |
| { | |
| throw_it (RETURN_QUIT, GDB_NO_ERROR, fmt, ap); | |
| } | |
| void | |
| throw_error (enum errors error, const char *fmt, ...) | |
| { | |
| va_list args; | |
| va_start (args, fmt); | |
| throw_verror (error, fmt, args); | |
| va_end (args); | |
| } | |
| void | |
| throw_quit (const char *fmt, ...) | |
| { | |
| va_list args; | |
| va_start (args, fmt); | |
| throw_vquit (fmt, args); | |
| va_end (args); | |
| } |