| /* GDB/Scheme exception support. |
| |
| Copyright (C) 2014-2021 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/>. */ |
| |
| /* See README file in this directory for implementation notes, coding |
| conventions, et.al. */ |
| |
| /* Notes: |
| |
| IWBN to support SRFI 34/35. At the moment we follow Guile's own |
| exception mechanism. |
| |
| The non-static functions in this file have prefix gdbscm_ and |
| not exscm_ on purpose. */ |
| |
| #include "defs.h" |
| #include <signal.h> |
| #include "guile-internal.h" |
| |
| /* The <gdb:exception> smob. |
| This is used to record and handle Scheme exceptions. |
| One important invariant is that <gdb:exception> smobs are never a valid |
| result of a function, other than to signify an exception occurred. */ |
| |
| struct exception_smob |
| { |
| /* This always appears first. */ |
| gdb_smob base; |
| |
| /* The key and args parameters to "throw". */ |
| SCM key; |
| SCM args; |
| }; |
| |
| static const char exception_smob_name[] = "gdb:exception"; |
| |
| /* The tag Guile knows the exception smob by. */ |
| static scm_t_bits exception_smob_tag; |
| |
| /* A generic error in struct gdb_exception. |
| I.e., not RETURN_QUIT and not MEMORY_ERROR. */ |
| static SCM error_symbol; |
| |
| /* An error occurred accessing inferior memory. |
| This is not a Scheme programming error. */ |
| static SCM memory_error_symbol; |
| |
| /* User interrupt, e.g., RETURN_QUIT in struct gdb_exception. */ |
| static SCM signal_symbol; |
| |
| /* A user error, e.g., bad arg to gdb command. */ |
| static SCM user_error_symbol; |
| |
| /* Printing the stack is done by first capturing the stack and recording it in |
| a <gdb:exception> object with this key and with the ARGS field set to |
| (cons real-key (cons stack real-args)). |
| See gdbscm_make_exception_with_stack. */ |
| static SCM with_stack_error_symbol; |
| |
| /* The key to use for an invalid object exception. An invalid object is one |
| where the underlying object has been removed from GDB. */ |
| SCM gdbscm_invalid_object_error_symbol; |
| |
| /* Values for "guile print-stack" as symbols. */ |
| static SCM none_symbol; |
| static SCM message_symbol; |
| static SCM full_symbol; |
| |
| static const char percent_print_exception_message_name[] = |
| "%print-exception-message"; |
| |
| /* Variable containing %print-exception-message. |
| It is not defined until late in initialization, after our init routine |
| has run. Cope by looking it up lazily. */ |
| static SCM percent_print_exception_message_var = SCM_BOOL_F; |
| |
| static const char percent_print_exception_with_stack_name[] = |
| "%print-exception-with-stack"; |
| |
| /* Variable containing %print-exception-with-stack. |
| It is not defined until late in initialization, after our init routine |
| has run. Cope by looking it up lazily. */ |
| static SCM percent_print_exception_with_stack_var = SCM_BOOL_F; |
| |
| /* Counter to keep track of the number of times we create a <gdb:exception> |
| object, for performance monitoring purposes. */ |
| static unsigned long gdbscm_exception_count = 0; |
| |
| /* Administrivia for exception smobs. */ |
| |
| /* The smob "print" function for <gdb:exception>. */ |
| |
| static int |
| exscm_print_exception_smob (SCM self, SCM port, scm_print_state *pstate) |
| { |
| exception_smob *e_smob = (exception_smob *) SCM_SMOB_DATA (self); |
| |
| gdbscm_printf (port, "#<%s ", exception_smob_name); |
| scm_write (e_smob->key, port); |
| scm_puts (" ", port); |
| scm_write (e_smob->args, port); |
| scm_puts (">", port); |
| |
| scm_remember_upto_here_1 (self); |
| |
| /* Non-zero means success. */ |
| return 1; |
| } |
| |
| /* (make-exception key args) -> <gdb:exception> */ |
| |
| SCM |
| gdbscm_make_exception (SCM key, SCM args) |
| { |
| exception_smob *e_smob = (exception_smob *) |
| scm_gc_malloc (sizeof (exception_smob), exception_smob_name); |
| SCM smob; |
| |
| e_smob->key = key; |
| e_smob->args = args; |
| smob = scm_new_smob (exception_smob_tag, (scm_t_bits) e_smob); |
| gdbscm_init_gsmob (&e_smob->base); |
| |
| ++gdbscm_exception_count; |
| |
| return smob; |
| } |
| |
| /* Return non-zero if SCM is a <gdb:exception> object. */ |
| |
| int |
| gdbscm_is_exception (SCM scm) |
| { |
| return SCM_SMOB_PREDICATE (exception_smob_tag, scm); |
| } |
| |
| /* (exception? scm) -> boolean */ |
| |
| static SCM |
| gdbscm_exception_p (SCM scm) |
| { |
| return scm_from_bool (gdbscm_is_exception (scm)); |
| } |
| |
| /* (exception-key <gdb:exception>) -> key */ |
| |
| SCM |
| gdbscm_exception_key (SCM self) |
| { |
| exception_smob *e_smob; |
| |
| SCM_ASSERT_TYPE (gdbscm_is_exception (self), self, SCM_ARG1, FUNC_NAME, |
| "gdb:exception"); |
| |
| e_smob = (exception_smob *) SCM_SMOB_DATA (self); |
| return e_smob->key; |
| } |
| |
| /* (exception-args <gdb:exception>) -> arg-list */ |
| |
| SCM |
| gdbscm_exception_args (SCM self) |
| { |
| exception_smob *e_smob; |
| |
| SCM_ASSERT_TYPE (gdbscm_is_exception (self), self, SCM_ARG1, FUNC_NAME, |
| "gdb:exception"); |
| |
| e_smob = (exception_smob *) SCM_SMOB_DATA (self); |
| return e_smob->args; |
| } |
| |
| /* Wrap an exception in a <gdb:exception> object that includes STACK. |
| gdbscm_print_exception_with_stack knows how to unwrap it. */ |
| |
| SCM |
| gdbscm_make_exception_with_stack (SCM key, SCM args, SCM stack) |
| { |
| return gdbscm_make_exception (with_stack_error_symbol, |
| scm_cons (key, scm_cons (stack, args))); |
| } |
| |
| /* Version of scm_error_scm that creates a gdb:exception object that can later |
| be passed to gdbscm_throw. |
| KEY is a symbol denoting the kind of error. |
| SUBR is either #f or a string marking the function in which the error |
| occurred. |
| MESSAGE is either #f or the error message string. It may contain ~a and ~s |
| modifiers, provided by ARGS. |
| ARGS is a list of args to MESSAGE. |
| DATA is an arbitrary object, its value depends on KEY. The value to pass |
| here is a bit underspecified by Guile. */ |
| |
| SCM |
| gdbscm_make_error_scm (SCM key, SCM subr, SCM message, SCM args, SCM data) |
| { |
| return gdbscm_make_exception (key, scm_list_4 (subr, message, args, data)); |
| } |
| |
| /* Version of scm_error that creates a gdb:exception object that can later |
| be passed to gdbscm_throw. |
| See gdbscm_make_error_scm for a description of the arguments. */ |
| |
| SCM |
| gdbscm_make_error (SCM key, const char *subr, const char *message, |
| SCM args, SCM data) |
| { |
| return gdbscm_make_error_scm |
| (key, |
| subr == NULL ? SCM_BOOL_F : scm_from_latin1_string (subr), |
| message == NULL ? SCM_BOOL_F : scm_from_latin1_string (message), |
| args, data); |
| } |
| |
| /* Version of SCM_ASSERT_TYPE/scm_wrong_type_arg_msg that creates a |
| gdb:exception object that can later be passed to gdbscm_throw. */ |
| |
| SCM |
| gdbscm_make_type_error (const char *subr, int arg_pos, SCM bad_value, |
| const char *expected_type) |
| { |
| char *msg; |
| SCM result; |
| |
| if (arg_pos > 0) |
| { |
| if (expected_type != NULL) |
| { |
| msg = xstrprintf (_("Wrong type argument in position %d" |
| " (expecting %s): ~S"), |
| arg_pos, expected_type); |
| } |
| else |
| { |
| msg = xstrprintf (_("Wrong type argument in position %d: ~S"), |
| arg_pos); |
| } |
| } |
| else |
| { |
| if (expected_type != NULL) |
| { |
| msg = xstrprintf (_("Wrong type argument (expecting %s): ~S"), |
| expected_type); |
| } |
| else |
| msg = xstrprintf (_("Wrong type argument: ~S")); |
| } |
| |
| result = gdbscm_make_error (scm_arg_type_key, subr, msg, |
| scm_list_1 (bad_value), scm_list_1 (bad_value)); |
| xfree (msg); |
| return result; |
| } |
| |
| /* A variant of gdbscm_make_type_error for non-type argument errors. |
| ERROR_PREFIX and ERROR are combined to build the error message. |
| Care needs to be taken so that the i18n composed form is still |
| reasonable, but no one is going to translate these anyway so we don't |
| worry too much. |
| ERROR_PREFIX may be NULL, ERROR may not be NULL. */ |
| |
| static SCM |
| gdbscm_make_arg_error (SCM key, const char *subr, int arg_pos, SCM bad_value, |
| const char *error_prefix, const char *error) |
| { |
| char *msg; |
| SCM result; |
| |
| if (error_prefix != NULL) |
| { |
| if (arg_pos > 0) |
| { |
| msg = xstrprintf (_("%s %s in position %d: ~S"), |
| error_prefix, error, arg_pos); |
| } |
| else |
| msg = xstrprintf (_("%s %s: ~S"), error_prefix, error); |
| } |
| else |
| { |
| if (arg_pos > 0) |
| msg = xstrprintf (_("%s in position %d: ~S"), error, arg_pos); |
| else |
| msg = xstrprintf (_("%s: ~S"), error); |
| } |
| |
| result = gdbscm_make_error (key, subr, msg, |
| scm_list_1 (bad_value), scm_list_1 (bad_value)); |
| xfree (msg); |
| return result; |
| } |
| |
| /* Make an invalid-object error <gdb:exception> object. |
| OBJECT is the name of the kind of object that is invalid. */ |
| |
| SCM |
| gdbscm_make_invalid_object_error (const char *subr, int arg_pos, SCM bad_value, |
| const char *object) |
| { |
| return gdbscm_make_arg_error (gdbscm_invalid_object_error_symbol, |
| subr, arg_pos, bad_value, |
| _("Invalid object:"), object); |
| } |
| |
| /* Throw an invalid-object error. |
| OBJECT is the name of the kind of object that is invalid. */ |
| |
| void |
| gdbscm_invalid_object_error (const char *subr, int arg_pos, SCM bad_value, |
| const char *object) |
| { |
| SCM exception |
| = gdbscm_make_invalid_object_error (subr, arg_pos, bad_value, object); |
| |
| gdbscm_throw (exception); |
| } |
| |
| /* Make an out-of-range error <gdb:exception> object. */ |
| |
| SCM |
| gdbscm_make_out_of_range_error (const char *subr, int arg_pos, SCM bad_value, |
| const char *error) |
| { |
| return gdbscm_make_arg_error (scm_out_of_range_key, |
| subr, arg_pos, bad_value, |
| _("Out of range:"), error); |
| } |
| |
| /* Throw an out-of-range error. |
| This is the standard Guile out-of-range exception. */ |
| |
| void |
| gdbscm_out_of_range_error (const char *subr, int arg_pos, SCM bad_value, |
| const char *error) |
| { |
| SCM exception |
| = gdbscm_make_out_of_range_error (subr, arg_pos, bad_value, error); |
| |
| gdbscm_throw (exception); |
| } |
| |
| /* Make a misc-error <gdb:exception> object. */ |
| |
| SCM |
| gdbscm_make_misc_error (const char *subr, int arg_pos, SCM bad_value, |
| const char *error) |
| { |
| return gdbscm_make_arg_error (scm_misc_error_key, |
| subr, arg_pos, bad_value, NULL, error); |
| } |
| |
| /* Throw a misc-error error. */ |
| |
| void |
| gdbscm_misc_error (const char *subr, int arg_pos, SCM bad_value, |
| const char *error) |
| { |
| SCM exception = gdbscm_make_misc_error (subr, arg_pos, bad_value, error); |
| |
| gdbscm_throw (exception); |
| } |
| |
| /* Return a <gdb:exception> object for gdb:memory-error. */ |
| |
| SCM |
| gdbscm_make_memory_error (const char *subr, const char *msg, SCM args) |
| { |
| return gdbscm_make_error (memory_error_symbol, subr, msg, args, |
| SCM_EOL); |
| } |
| |
| /* Throw a gdb:memory-error exception. */ |
| |
| void |
| gdbscm_memory_error (const char *subr, const char *msg, SCM args) |
| { |
| SCM exception = gdbscm_make_memory_error (subr, msg, args); |
| |
| gdbscm_throw (exception); |
| } |
| |
| /* Return non-zero if KEY is gdb:memory-error. |
| Note: This is an excp_matcher_func function. */ |
| |
| int |
| gdbscm_memory_error_p (SCM key) |
| { |
| return scm_is_eq (key, memory_error_symbol); |
| } |
| |
| /* Return non-zero if KEY is gdb:user-error. |
| Note: This is an excp_matcher_func function. */ |
| |
| int |
| gdbscm_user_error_p (SCM key) |
| { |
| return scm_is_eq (key, user_error_symbol); |
| } |
| |
| /* Wrapper around scm_throw to throw a gdb:exception. |
| This function does not return. |
| This function cannot be called from inside TRY_CATCH. */ |
| |
| void |
| gdbscm_throw (SCM exception) |
| { |
| scm_throw (gdbscm_exception_key (exception), |
| gdbscm_exception_args (exception)); |
| gdb_assert_not_reached ("scm_throw returned"); |
| } |
| |
| /* Convert a GDB exception to a <gdb:exception> object. */ |
| |
| SCM |
| gdbscm_scm_from_gdb_exception (const gdbscm_gdb_exception &exception) |
| { |
| SCM key; |
| |
| if (exception.reason == RETURN_QUIT) |
| { |
| /* Handle this specially to be consistent with top-repl.scm. */ |
| return gdbscm_make_error (signal_symbol, NULL, _("User interrupt"), |
| SCM_EOL, scm_list_1 (scm_from_int (SIGINT))); |
| } |
| |
| if (exception.error == MEMORY_ERROR) |
| key = memory_error_symbol; |
| else |
| key = error_symbol; |
| |
| return gdbscm_make_error (key, NULL, "~A", |
| scm_list_1 (gdbscm_scm_from_c_string |
| (exception.message)), |
| SCM_BOOL_F); |
| } |
| |
| /* Convert a GDB exception to the appropriate Scheme exception and throw it. |
| This function does not return. */ |
| |
| void |
| gdbscm_throw_gdb_exception (gdbscm_gdb_exception exception) |
| { |
| SCM scm_exception = gdbscm_scm_from_gdb_exception (exception); |
| xfree (exception.message); |
| gdbscm_throw (scm_exception); |
| } |
| |
| /* Print the error message portion of an exception. |
| If PORT is #f, use the standard error port. |
| KEY cannot be gdb:with-stack. |
| |
| Basically this function is just a wrapper around calling |
| %print-exception-message. */ |
| |
| static void |
| gdbscm_print_exception_message (SCM port, SCM frame, SCM key, SCM args) |
| { |
| SCM printer, status; |
| |
| if (gdbscm_is_false (port)) |
| port = scm_current_error_port (); |
| |
| gdb_assert (!scm_is_eq (key, with_stack_error_symbol)); |
| |
| /* This does not use scm_print_exception because we tweak the output a bit. |
| Compare Guile's print-exception with our %print-exception-message for |
| details. */ |
| if (gdbscm_is_false (percent_print_exception_message_var)) |
| { |
| percent_print_exception_message_var |
| = scm_c_private_variable (gdbscm_init_module_name, |
| percent_print_exception_message_name); |
| /* If we can't find %print-exception-message, there's a problem on the |
| Scheme side. Don't kill GDB, just flag an error and leave it at |
| that. */ |
| if (gdbscm_is_false (percent_print_exception_message_var)) |
| { |
| gdbscm_printf (port, _("Error in Scheme exception printing," |
| " can't find %s.\n"), |
| percent_print_exception_message_name); |
| return; |
| } |
| } |
| printer = scm_variable_ref (percent_print_exception_message_var); |
| |
| status = gdbscm_safe_call_4 (printer, port, frame, key, args, NULL); |
| |
| /* If that failed still tell the user something. |
| But don't use the exception printing machinery! */ |
| if (gdbscm_is_exception (status)) |
| { |
| gdbscm_printf (port, _("Error in Scheme exception printing:\n")); |
| scm_display (status, port); |
| scm_newline (port); |
| } |
| } |
| |
| /* Print the description of exception KEY, ARGS to PORT, according to the |
| setting of "set guile print-stack". |
| If PORT is #f, use the standard error port. |
| If STACK is #f, never print the stack, regardless of whether printing it |
| is enabled. If STACK is #t, then print it if it is contained in ARGS |
| (i.e., KEY is gdb:with-stack). Otherwise STACK is the result of calling |
| scm_make_stack (which will be ignored in favor of the stack in ARGS if |
| KEY is gdb:with-stack). |
| KEY, ARGS are the standard arguments to scm_throw, et.al. |
| |
| Basically this function is just a wrapper around calling |
| %print-exception-with-stack. */ |
| |
| void |
| gdbscm_print_exception_with_stack (SCM port, SCM stack, SCM key, SCM args) |
| { |
| SCM printer, status; |
| |
| if (gdbscm_is_false (port)) |
| port = scm_current_error_port (); |
| |
| if (gdbscm_is_false (percent_print_exception_with_stack_var)) |
| { |
| percent_print_exception_with_stack_var |
| = scm_c_private_variable (gdbscm_init_module_name, |
| percent_print_exception_with_stack_name); |
| /* If we can't find %print-exception-with-stack, there's a problem on the |
| Scheme side. Don't kill GDB, just flag an error and leave it at |
| that. */ |
| if (gdbscm_is_false (percent_print_exception_with_stack_var)) |
| { |
| gdbscm_printf (port, _("Error in Scheme exception printing," |
| " can't find %s.\n"), |
| percent_print_exception_with_stack_name); |
| return; |
| } |
| } |
| printer = scm_variable_ref (percent_print_exception_with_stack_var); |
| |
| status = gdbscm_safe_call_4 (printer, port, stack, key, args, NULL); |
| |
| /* If that failed still tell the user something. |
| But don't use the exception printing machinery! */ |
| if (gdbscm_is_exception (status)) |
| { |
| gdbscm_printf (port, _("Error in Scheme exception printing:\n")); |
| scm_display (status, port); |
| scm_newline (port); |
| } |
| } |
| |
| /* Print EXCEPTION, a <gdb:exception> object, to PORT. |
| If PORT is #f, use the standard error port. */ |
| |
| void |
| gdbscm_print_gdb_exception (SCM port, SCM exception) |
| { |
| gdb_assert (gdbscm_is_exception (exception)); |
| |
| gdbscm_print_exception_with_stack (port, SCM_BOOL_T, |
| gdbscm_exception_key (exception), |
| gdbscm_exception_args (exception)); |
| } |
| |
| /* Return a string description of <gdb:exception> EXCEPTION. |
| If EXCEPTION is a gdb:with-stack exception, unwrap it, a backtrace |
| is never returned as part of the result. */ |
| |
| gdb::unique_xmalloc_ptr<char> |
| gdbscm_exception_message_to_string (SCM exception) |
| { |
| SCM port = scm_open_output_string (); |
| SCM key, args; |
| |
| gdb_assert (gdbscm_is_exception (exception)); |
| |
| key = gdbscm_exception_key (exception); |
| args = gdbscm_exception_args (exception); |
| |
| if (scm_is_eq (key, with_stack_error_symbol) |
| /* Don't crash on a badly generated gdb:with-stack exception. */ |
| && scm_is_pair (args) |
| && scm_is_pair (scm_cdr (args))) |
| { |
| key = scm_car (args); |
| args = scm_cddr (args); |
| } |
| |
| gdbscm_print_exception_message (port, SCM_BOOL_F, key, args); |
| gdb::unique_xmalloc_ptr<char> result |
| = gdbscm_scm_to_c_string (scm_get_output_string (port)); |
| scm_close_port (port); |
| return result; |
| } |
| |
| /* Return the value of the "guile print-stack" option as one of: |
| 'none, 'message, 'full. */ |
| |
| static SCM |
| gdbscm_percent_exception_print_style (void) |
| { |
| if (gdbscm_print_excp == gdbscm_print_excp_none) |
| return none_symbol; |
| if (gdbscm_print_excp == gdbscm_print_excp_message) |
| return message_symbol; |
| if (gdbscm_print_excp == gdbscm_print_excp_full) |
| return full_symbol; |
| gdb_assert_not_reached ("bad value for \"guile print-stack\""); |
| } |
| |
| /* Return the current <gdb:exception> counter. |
| This is for debugging purposes. */ |
| |
| static SCM |
| gdbscm_percent_exception_count (void) |
| { |
| return scm_from_ulong (gdbscm_exception_count); |
| } |
| |
| /* Initialize the Scheme exception support. */ |
| |
| static const scheme_function exception_functions[] = |
| { |
| { "make-exception", 2, 0, 0, as_a_scm_t_subr (gdbscm_make_exception), |
| "\ |
| Create a <gdb:exception> object.\n\ |
| \n\ |
| Arguments: key args\n\ |
| These are the standard key,args arguments of \"throw\"." }, |
| |
| { "exception?", 1, 0, 0, as_a_scm_t_subr (gdbscm_exception_p), |
| "\ |
| Return #t if the object is a <gdb:exception> object." }, |
| |
| { "exception-key", 1, 0, 0, as_a_scm_t_subr (gdbscm_exception_key), |
| "\ |
| Return the exception's key." }, |
| |
| { "exception-args", 1, 0, 0, as_a_scm_t_subr (gdbscm_exception_args), |
| "\ |
| Return the exception's arg list." }, |
| |
| END_FUNCTIONS |
| }; |
| |
| static const scheme_function private_exception_functions[] = |
| { |
| { "%exception-print-style", 0, 0, 0, |
| as_a_scm_t_subr (gdbscm_percent_exception_print_style), |
| "\ |
| Return the value of the \"guile print-stack\" option." }, |
| |
| { "%exception-count", 0, 0, 0, |
| as_a_scm_t_subr (gdbscm_percent_exception_count), |
| "\ |
| Return a count of the number of <gdb:exception> objects created.\n\ |
| This is for debugging purposes." }, |
| |
| END_FUNCTIONS |
| }; |
| |
| void |
| gdbscm_initialize_exceptions (void) |
| { |
| exception_smob_tag = gdbscm_make_smob_type (exception_smob_name, |
| sizeof (exception_smob)); |
| scm_set_smob_print (exception_smob_tag, exscm_print_exception_smob); |
| |
| gdbscm_define_functions (exception_functions, 1); |
| gdbscm_define_functions (private_exception_functions, 0); |
| |
| error_symbol = scm_from_latin1_symbol ("gdb:error"); |
| |
| memory_error_symbol = scm_from_latin1_symbol ("gdb:memory-error"); |
| |
| user_error_symbol = scm_from_latin1_symbol ("gdb:user-error"); |
| |
| gdbscm_invalid_object_error_symbol |
| = scm_from_latin1_symbol ("gdb:invalid-object-error"); |
| |
| with_stack_error_symbol = scm_from_latin1_symbol ("gdb:with-stack"); |
| |
| /* The text of this symbol is taken from Guile's top-repl.scm. */ |
| signal_symbol = scm_from_latin1_symbol ("signal"); |
| |
| none_symbol = scm_from_latin1_symbol ("none"); |
| message_symbol = scm_from_latin1_symbol ("message"); |
| full_symbol = scm_from_latin1_symbol ("full"); |
| } |