| /* Exception (throw catch) mechanism, for GDB, the GNU debugger. |
| |
| Copyright (C) 1986-2024 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-exceptions.h" |
| #include <forward_list> |
| |
| /* 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 = CATCHER_CREATED; |
| /* Jump buffer pointing back at the exception handler. */ |
| jmp_buf buf; |
| /* Status buffer belonging to the exception handler. */ |
| struct gdb_exception exception; |
| }; |
| |
| /* Where to go for throw_exception(). */ |
| static std::forward_list<struct catcher> catchers; |
| |
| jmp_buf * |
| exceptions_state_mc_init () |
| { |
| catchers.emplace_front (); |
| return &catchers.front ().buf; |
| } |
| |
| /* 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 (catchers.front ().state) |
| { |
| case CATCHER_CREATED: |
| switch (action) |
| { |
| case CATCH_ITER: |
| /* Allow the code to run the catcher. */ |
| catchers.front ().state = CATCHER_RUNNING; |
| return 1; |
| default: |
| internal_error (_("bad state")); |
| } |
| case CATCHER_RUNNING: |
| switch (action) |
| { |
| case CATCH_ITER: |
| /* No error/quit has occurred. */ |
| return 0; |
| case CATCH_ITER_1: |
| catchers.front ().state = CATCHER_RUNNING_1; |
| return 1; |
| case CATCH_THROWING: |
| catchers.front ().state = CATCHER_ABORTING; |
| /* See also throw_exception. */ |
| return 1; |
| default: |
| internal_error (_("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: |
| catchers.front ().state = CATCHER_RUNNING; |
| return 0; |
| case CATCH_THROWING: |
| catchers.front ().state = CATCHER_ABORTING; |
| /* See also throw_exception. */ |
| return 1; |
| default: |
| internal_error (_("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 (_("bad state")); |
| } |
| default: |
| internal_error (_("bad switch")); |
| } |
| } |
| |
| int |
| exceptions_state_mc_catch (struct gdb_exception *exception, |
| int mask) |
| { |
| *exception = std::move (catchers.front ().exception); |
| catchers.pop_front (); |
| |
| 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); |
| } |
| |
| /* Return EXCEPTION to the nearest containing CATCH_SJLJ block. */ |
| |
| void |
| throw_exception_sjlj (const struct gdb_exception &exception) |
| { |
| /* 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); |
| enum return_reason reason = exception.reason; |
| catchers.front ().exception = exception; |
| longjmp (catchers.front ().buf, reason); |
| } |
| |
| /* Implementation of throw_exception that uses C++ try/catch. */ |
| |
| void |
| throw_exception (gdb_exception &&exception) |
| { |
| if (exception.reason == RETURN_QUIT) |
| throw gdb_exception_quit (std::move (exception)); |
| else if (exception.reason == RETURN_FORCED_QUIT) |
| throw gdb_exception_forced_quit (std::move (exception)); |
| else if (exception.reason == RETURN_ERROR) |
| throw gdb_exception_error (std::move (exception)); |
| else |
| gdb_assert_not_reached ("invalid return reason"); |
| } |
| |
| [[noreturn]] static void ATTRIBUTE_PRINTF (3, 0) |
| throw_it (enum return_reason reason, enum errors error, const char *fmt, |
| va_list ap) |
| { |
| if (reason == RETURN_QUIT) |
| throw gdb_exception_quit (fmt, ap); |
| else if (reason == RETURN_FORCED_QUIT) |
| throw gdb_exception_forced_quit (fmt, ap); |
| else if (reason == RETURN_ERROR) |
| throw gdb_exception_error (error, fmt, ap); |
| else |
| gdb_assert_not_reached ("invalid return reason"); |
| } |
| |
| 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); |
| } |
| |
| void |
| throw_forced_quit (const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start (args, fmt); |
| throw_it (RETURN_FORCED_QUIT, GDB_NO_ERROR, fmt, args); |
| va_end (args); |
| } |