diff options
Diffstat (limited to 'gdb/guile/scm-exception.c')
-rw-r--r-- | gdb/guile/scm-exception.c | 691 |
1 files changed, 691 insertions, 0 deletions
diff --git a/gdb/guile/scm-exception.c b/gdb/guile/scm-exception.c new file mode 100644 index 0000000..a96a350 --- /dev/null +++ b/gdb/guile/scm-exception.c @@ -0,0 +1,691 @@ +/* GDB/Scheme exception support. + + Copyright (C) 2014 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 "gdb_assert.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. */ + +typedef struct +{ + /* This always appears first. */ + gdb_smob base; + + /* The key and args parameters to "throw". */ + SCM key; + SCM args; +} exception_smob; + +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; + +/* 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 "mark" function for <gdb:exception>. */ + +static SCM +exscm_mark_exception_smob (SCM self) +{ + exception_smob *e_smob = (exception_smob *) SCM_SMOB_DATA (self); + + scm_gc_mark (e_smob->key); + scm_gc_mark (e_smob->args); + /* Do this last. */ + return gdbscm_mark_gsmob (&e_smob->base); +} + +/* 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. */ + +SCM +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. */ + +SCM +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); +} + +/* 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. */ + +SCM +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); +} + +/* 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 (struct 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 (struct gdb_exception exception) +{ + gdbscm_throw (gdbscm_scm_from_gdb_exception (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-args. */ + +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-args, 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. + + Space for the result is malloc'd, the caller must free. */ + +char * +gdbscm_exception_message_to_string (SCM exception) +{ + SCM port = scm_open_output_string (); + SCM key, args; + char *result; + + 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); + 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, 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, gdbscm_exception_p, + "\ +Return #t if the object is a <gdb:exception> object." }, + + { "exception-key", 1, 0, 0, gdbscm_exception_key, + "\ +Return the exception's key." }, + + { "exception-args", 1, 0, 0, gdbscm_exception_args, + "\ +Return the exception's arg list." }, + + END_FUNCTIONS +}; + +static const scheme_function private_exception_functions[] = +{ + { "%exception-print-style", 0, 0, 0, gdbscm_percent_exception_print_style, + "\ +Return the value of the \"guile print-stack\" option." }, + + { "%exception-count", 0, 0, 0, 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_mark (exception_smob_tag, exscm_mark_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"); + + 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"); +} |