| /* General utility routines for GDB/Scheme code. |
| |
| 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. */ |
| |
| #include "defs.h" |
| #include "guile-internal.h" |
| |
| /* Define VARIABLES in the gdb module. */ |
| |
| void |
| gdbscm_define_variables (const scheme_variable *variables, int is_public) |
| { |
| const scheme_variable *sv; |
| |
| for (sv = variables; sv->name != NULL; ++sv) |
| { |
| scm_c_define (sv->name, sv->value); |
| if (is_public) |
| scm_c_export (sv->name, NULL); |
| } |
| } |
| |
| /* Define FUNCTIONS in the gdb module. */ |
| |
| void |
| gdbscm_define_functions (const scheme_function *functions, int is_public) |
| { |
| const scheme_function *sf; |
| |
| for (sf = functions; sf->name != NULL; ++sf) |
| { |
| SCM proc = scm_c_define_gsubr (sf->name, sf->required, sf->optional, |
| sf->rest, sf->func); |
| |
| scm_set_procedure_property_x (proc, gdbscm_documentation_symbol, |
| gdbscm_scm_from_c_string (sf->doc_string)); |
| if (is_public) |
| scm_c_export (sf->name, NULL); |
| } |
| } |
| |
| /* Define CONSTANTS in the gdb module. */ |
| |
| void |
| gdbscm_define_integer_constants (const scheme_integer_constant *constants, |
| int is_public) |
| { |
| const scheme_integer_constant *sc; |
| |
| for (sc = constants; sc->name != NULL; ++sc) |
| { |
| scm_c_define (sc->name, scm_from_int (sc->value)); |
| if (is_public) |
| scm_c_export (sc->name, NULL); |
| } |
| } |
| |
| /* scm_printf, alas it doesn't exist. */ |
| |
| void |
| gdbscm_printf (SCM port, const char *format, ...) |
| { |
| va_list args; |
| |
| va_start (args, format); |
| std::string string = string_vprintf (format, args); |
| va_end (args); |
| scm_puts (string.c_str (), port); |
| } |
| |
| /* Utility for calling from gdb to "display" an SCM object. */ |
| |
| void |
| gdbscm_debug_display (SCM obj) |
| { |
| SCM port = scm_current_output_port (); |
| |
| scm_display (obj, port); |
| scm_newline (port); |
| scm_force_output (port); |
| } |
| |
| /* Utility for calling from gdb to "write" an SCM object. */ |
| |
| void |
| gdbscm_debug_write (SCM obj) |
| { |
| SCM port = scm_current_output_port (); |
| |
| scm_write (obj, port); |
| scm_newline (port); |
| scm_force_output (port); |
| } |
| |
| /* Subroutine of gdbscm_parse_function_args to simplify it. |
| Return the number of keyword arguments. */ |
| |
| static int |
| count_keywords (const SCM *keywords) |
| { |
| int i; |
| |
| if (keywords == NULL) |
| return 0; |
| for (i = 0; keywords[i] != SCM_BOOL_F; ++i) |
| continue; |
| |
| return i; |
| } |
| |
| /* Subroutine of gdbscm_parse_function_args to simplify it. |
| Validate an argument format string. |
| The result is a boolean indicating if "." was seen. */ |
| |
| static int |
| validate_arg_format (const char *format) |
| { |
| const char *p; |
| int length = strlen (format); |
| int optional_position = -1; |
| int keyword_position = -1; |
| int dot_seen = 0; |
| |
| gdb_assert (length > 0); |
| |
| for (p = format; *p != '\0'; ++p) |
| { |
| switch (*p) |
| { |
| case 's': |
| case 't': |
| case 'i': |
| case 'u': |
| case 'l': |
| case 'n': |
| case 'L': |
| case 'U': |
| case 'O': |
| break; |
| case '|': |
| gdb_assert (keyword_position < 0); |
| gdb_assert (optional_position < 0); |
| optional_position = p - format; |
| break; |
| case '#': |
| gdb_assert (keyword_position < 0); |
| keyword_position = p - format; |
| break; |
| case '.': |
| gdb_assert (p[1] == '\0'); |
| dot_seen = 1; |
| break; |
| default: |
| gdb_assert_not_reached ("invalid argument format character"); |
| } |
| } |
| |
| return dot_seen; |
| } |
| |
| /* Our version of SCM_ASSERT_TYPE that calls gdbscm_make_type_error. */ |
| #define CHECK_TYPE(ok, arg, position, func_name, expected_type) \ |
| do { \ |
| if (!(ok)) \ |
| { \ |
| return gdbscm_make_type_error ((func_name), (position), (arg), \ |
| (expected_type)); \ |
| } \ |
| } while (0) |
| |
| /* Subroutine of gdbscm_parse_function_args to simplify it. |
| Check the type of ARG against FORMAT_CHAR and extract the value. |
| POSITION is the position of ARG in the argument list. |
| The result is #f upon success or a <gdb:exception> object. */ |
| |
| static SCM |
| extract_arg (char format_char, SCM arg, void *argp, |
| const char *func_name, int position) |
| { |
| switch (format_char) |
| { |
| case 's': |
| { |
| char **arg_ptr = (char **) argp; |
| |
| CHECK_TYPE (gdbscm_is_true (scm_string_p (arg)), arg, position, |
| func_name, _("string")); |
| *arg_ptr = gdbscm_scm_to_c_string (arg).release (); |
| break; |
| } |
| case 't': |
| { |
| int *arg_ptr = (int *) argp; |
| |
| /* While in Scheme, anything non-#f is "true", we're strict. */ |
| CHECK_TYPE (gdbscm_is_bool (arg), arg, position, func_name, |
| _("boolean")); |
| *arg_ptr = gdbscm_is_true (arg); |
| break; |
| } |
| case 'i': |
| { |
| int *arg_ptr = (int *) argp; |
| |
| CHECK_TYPE (scm_is_signed_integer (arg, INT_MIN, INT_MAX), |
| arg, position, func_name, _("int")); |
| *arg_ptr = scm_to_int (arg); |
| break; |
| } |
| case 'u': |
| { |
| int *arg_ptr = (int *) argp; |
| |
| CHECK_TYPE (scm_is_unsigned_integer (arg, 0, UINT_MAX), |
| arg, position, func_name, _("unsigned int")); |
| *arg_ptr = scm_to_uint (arg); |
| break; |
| } |
| case 'l': |
| { |
| long *arg_ptr = (long *) argp; |
| |
| CHECK_TYPE (scm_is_signed_integer (arg, LONG_MIN, LONG_MAX), |
| arg, position, func_name, _("long")); |
| *arg_ptr = scm_to_long (arg); |
| break; |
| } |
| case 'n': |
| { |
| unsigned long *arg_ptr = (unsigned long *) argp; |
| |
| CHECK_TYPE (scm_is_unsigned_integer (arg, 0, ULONG_MAX), |
| arg, position, func_name, _("unsigned long")); |
| *arg_ptr = scm_to_ulong (arg); |
| break; |
| } |
| case 'L': |
| { |
| LONGEST *arg_ptr = (LONGEST *) argp; |
| |
| CHECK_TYPE (scm_is_signed_integer (arg, INT64_MIN, INT64_MAX), |
| arg, position, func_name, _("LONGEST")); |
| *arg_ptr = gdbscm_scm_to_longest (arg); |
| break; |
| } |
| case 'U': |
| { |
| ULONGEST *arg_ptr = (ULONGEST *) argp; |
| |
| CHECK_TYPE (scm_is_unsigned_integer (arg, 0, UINT64_MAX), |
| arg, position, func_name, _("ULONGEST")); |
| *arg_ptr = gdbscm_scm_to_ulongest (arg); |
| break; |
| } |
| case 'O': |
| { |
| SCM *arg_ptr = (SCM *) argp; |
| |
| *arg_ptr = arg; |
| break; |
| } |
| default: |
| gdb_assert_not_reached ("invalid argument format character"); |
| } |
| |
| return SCM_BOOL_F; |
| } |
| |
| #undef CHECK_TYPE |
| |
| /* Look up KEYWORD in KEYWORD_LIST. |
| The result is the index of the keyword in the list or -1 if not found. */ |
| |
| static int |
| lookup_keyword (const SCM *keyword_list, SCM keyword) |
| { |
| int i = 0; |
| |
| while (keyword_list[i] != SCM_BOOL_F) |
| { |
| if (scm_is_eq (keyword_list[i], keyword)) |
| return i; |
| ++i; |
| } |
| |
| return -1; |
| } |
| |
| |
| /* Helper for gdbscm_parse_function_args that does most of the work, |
| in a separate function wrapped with gdbscm_wrap so that we can use |
| non-trivial-dtor objects here. The result is #f upon success or a |
| <gdb:exception> object otherwise. */ |
| |
| static SCM |
| gdbscm_parse_function_args_1 (const char *func_name, |
| int beginning_arg_pos, |
| const SCM *keywords, |
| const char *format, va_list args) |
| { |
| const char *p; |
| int i, have_rest, num_keywords, position; |
| int have_optional = 0; |
| SCM status; |
| SCM rest = SCM_EOL; |
| /* Keep track of malloc'd strings. We need to free them upon error. */ |
| std::vector<char *> allocated_strings; |
| |
| have_rest = validate_arg_format (format); |
| num_keywords = count_keywords (keywords); |
| |
| p = format; |
| position = beginning_arg_pos; |
| |
| /* Process required, optional arguments. */ |
| |
| while (*p && *p != '#' && *p != '.') |
| { |
| SCM arg; |
| void *arg_ptr; |
| |
| if (*p == '|') |
| { |
| have_optional = 1; |
| ++p; |
| continue; |
| } |
| |
| arg = va_arg (args, SCM); |
| if (!have_optional || !SCM_UNBNDP (arg)) |
| { |
| arg_ptr = va_arg (args, void *); |
| status = extract_arg (*p, arg, arg_ptr, func_name, position); |
| if (!gdbscm_is_false (status)) |
| goto fail; |
| if (*p == 's') |
| allocated_strings.push_back (*(char **) arg_ptr); |
| } |
| ++p; |
| ++position; |
| } |
| |
| /* Process keyword arguments. */ |
| |
| if (have_rest || num_keywords > 0) |
| rest = va_arg (args, SCM); |
| |
| if (num_keywords > 0) |
| { |
| SCM *keyword_args = XALLOCAVEC (SCM, num_keywords); |
| int *keyword_positions = XALLOCAVEC (int, num_keywords); |
| |
| gdb_assert (*p == '#'); |
| ++p; |
| |
| for (i = 0; i < num_keywords; ++i) |
| { |
| keyword_args[i] = SCM_UNSPECIFIED; |
| keyword_positions[i] = -1; |
| } |
| |
| while (scm_is_pair (rest) |
| && scm_is_keyword (scm_car (rest))) |
| { |
| SCM keyword = scm_car (rest); |
| |
| i = lookup_keyword (keywords, keyword); |
| if (i < 0) |
| { |
| status = gdbscm_make_error (scm_arg_type_key, func_name, |
| _("Unrecognized keyword: ~a"), |
| scm_list_1 (keyword), keyword); |
| goto fail; |
| } |
| if (!scm_is_pair (scm_cdr (rest))) |
| { |
| status = gdbscm_make_error |
| (scm_arg_type_key, func_name, |
| _("Missing value for keyword argument"), |
| scm_list_1 (keyword), keyword); |
| goto fail; |
| } |
| keyword_args[i] = scm_cadr (rest); |
| keyword_positions[i] = position + 1; |
| rest = scm_cddr (rest); |
| position += 2; |
| } |
| |
| for (i = 0; i < num_keywords; ++i) |
| { |
| int *arg_pos_ptr = va_arg (args, int *); |
| void *arg_ptr = va_arg (args, void *); |
| SCM arg = keyword_args[i]; |
| |
| if (! scm_is_eq (arg, SCM_UNSPECIFIED)) |
| { |
| *arg_pos_ptr = keyword_positions[i]; |
| status = extract_arg (p[i], arg, arg_ptr, func_name, |
| keyword_positions[i]); |
| if (!gdbscm_is_false (status)) |
| goto fail; |
| if (p[i] == 's') |
| allocated_strings.push_back (*(char **) arg_ptr); |
| } |
| } |
| } |
| |
| /* Process "rest" arguments. */ |
| |
| if (have_rest) |
| { |
| if (num_keywords > 0) |
| { |
| SCM *rest_ptr = va_arg (args, SCM *); |
| |
| *rest_ptr = rest; |
| } |
| } |
| else |
| { |
| if (! scm_is_null (rest)) |
| { |
| status = gdbscm_make_error (scm_args_number_key, func_name, |
| _("Too many arguments"), |
| SCM_EOL, SCM_BOOL_F); |
| goto fail; |
| } |
| } |
| |
| /* Return anything not-an-exception. */ |
| return SCM_BOOL_F; |
| |
| fail: |
| for (char *ptr : allocated_strings) |
| xfree (ptr); |
| |
| /* Return the exception, which gdbscm_wrap takes care of |
| throwing. */ |
| return status; |
| } |
| |
| /* Utility to parse required, optional, and keyword arguments to Scheme |
| functions. Modelled on PyArg_ParseTupleAndKeywords, but no attempt is made |
| at similarity or functionality. |
| There is no result, if there's an error a Scheme exception is thrown. |
| |
| Guile provides scm_c_bind_keyword_arguments, and feel free to use it. |
| This is for times when we want a bit more parsing. |
| |
| BEGINNING_ARG_POS is the position of the first argument passed to this |
| routine. It should be one of the SCM_ARGn values. It could be > SCM_ARG1 |
| if the caller chooses not to parse one or more required arguments. |
| |
| KEYWORDS may be NULL if there are no keywords. |
| |
| FORMAT: |
| s - string -> char *, malloc'd |
| t - boolean (gdb uses "t", for biT?) -> int |
| i - int |
| u - unsigned int |
| l - long |
| n - unsigned long |
| L - longest |
| U - unsigned longest |
| O - random scheme object |
| | - indicates the next set is for optional arguments |
| # - indicates the next set is for keyword arguments (must follow |) |
| . - indicates "rest" arguments are present, this character must appear last |
| |
| FORMAT must match the definition from scm_c_{make,define}_gsubr. |
| Required and optional arguments appear in order in the format string. |
| Afterwards, keyword-based arguments are processed. There must be as many |
| remaining characters in the format string as their are keywords. |
| Except for "|#.", the number of characters in the format string must match |
| #required + #optional + #keywords. |
| |
| The function is required to be defined in a compatible manner: |
| #required-args and #optional-arguments must match, and rest-arguments |
| must be specified if keyword args are desired, and/or regular "rest" args. |
| |
| Example: For this function, |
| scm_c_define_gsubr ("execute", 2, 3, 1, foo); |
| the format string + keyword list could be any of: |
| 1) "ss|ttt#tt", { "key1", "key2", NULL } |
| 2) "ss|ttt.", { NULL } |
| 3) "ss|ttt#t.", { "key1", NULL } |
| |
| For required and optional args pass the SCM of the argument, and a |
| pointer to the value to hold the parsed result (type depends on format |
| char). After that pass the SCM containing the "rest" arguments followed |
| by pointers to values to hold parsed keyword arguments, and if specified |
| a pointer to hold the remaining contents of "rest". |
| |
| For keyword arguments pass two pointers: the first is a pointer to an int |
| that will contain the position of the argument in the arg list, and the |
| second will contain result of processing the argument. The int pointed |
| to by the first value should be initialized to -1. It can then be used |
| to tell whether the keyword was present. |
| |
| If both keyword and rest arguments are present, the caller must pass a |
| pointer to contain the new value of rest (after keyword args have been |
| removed). |
| |
| There's currently no way, that I know of, to specify default values for |
| optional arguments in C-provided functions. At the moment they're a |
| work-in-progress. The caller should test SCM_UNBNDP for each optional |
| argument. Unbound optional arguments are ignored. */ |
| |
| void |
| gdbscm_parse_function_args (const char *func_name, |
| int beginning_arg_pos, |
| const SCM *keywords, |
| const char *format, ...) |
| { |
| va_list args; |
| va_start (args, format); |
| |
| gdbscm_wrap (gdbscm_parse_function_args_1, func_name, |
| beginning_arg_pos, keywords, format, args); |
| |
| va_end (args); |
| } |
| |
| |
| /* Return longest L as a scheme object. */ |
| |
| SCM |
| gdbscm_scm_from_longest (LONGEST l) |
| { |
| return scm_from_int64 (l); |
| } |
| |
| /* Convert scheme object L to LONGEST. |
| It is an error to call this if L is not an integer in range of LONGEST. |
| (because the underlying Scheme function will thrown an exception, |
| which is not part of our contract with the caller). */ |
| |
| LONGEST |
| gdbscm_scm_to_longest (SCM l) |
| { |
| return scm_to_int64 (l); |
| } |
| |
| /* Return unsigned longest L as a scheme object. */ |
| |
| SCM |
| gdbscm_scm_from_ulongest (ULONGEST l) |
| { |
| return scm_from_uint64 (l); |
| } |
| |
| /* Convert scheme object U to ULONGEST. |
| It is an error to call this if U is not an integer in range of ULONGEST |
| (because the underlying Scheme function will thrown an exception, |
| which is not part of our contract with the caller). */ |
| |
| ULONGEST |
| gdbscm_scm_to_ulongest (SCM u) |
| { |
| return scm_to_uint64 (u); |
| } |
| |
| /* Same as scm_dynwind_free, but uses xfree. */ |
| |
| void |
| gdbscm_dynwind_xfree (void *ptr) |
| { |
| scm_dynwind_unwind_handler (xfree, ptr, SCM_F_WIND_EXPLICITLY); |
| } |
| |
| /* Return non-zero if PROC is a procedure. */ |
| |
| int |
| gdbscm_is_procedure (SCM proc) |
| { |
| return gdbscm_is_true (scm_procedure_p (proc)); |
| } |
| |
| /* Same as xstrdup, but the string is allocated on the GC heap. */ |
| |
| char * |
| gdbscm_gc_xstrdup (const char *str) |
| { |
| size_t len = strlen (str); |
| char *result |
| = (char *) scm_gc_malloc_pointerless (len + 1, "gdbscm_gc_xstrdup"); |
| |
| strcpy (result, str); |
| return result; |
| } |
| |
| /* Return a duplicate of ARGV living on the GC heap. */ |
| |
| const char * const * |
| gdbscm_gc_dup_argv (char **argv) |
| { |
| int i, len; |
| size_t string_space; |
| char *p, **result; |
| |
| for (len = 0, string_space = 0; argv[len] != NULL; ++len) |
| string_space += strlen (argv[len]) + 1; |
| |
| /* Allocating "pointerless" works because the pointers are all |
| self-contained within the object. */ |
| result = (char **) scm_gc_malloc_pointerless (((len + 1) * sizeof (char *)) |
| + string_space, |
| "parameter enum list"); |
| p = (char *) &result[len + 1]; |
| |
| for (i = 0; i < len; ++i) |
| { |
| result[i] = p; |
| strcpy (p, argv[i]); |
| p += strlen (p) + 1; |
| } |
| result[i] = NULL; |
| |
| return (const char * const *) result; |
| } |
| |
| /* Return non-zero if the version of Guile being used it at least |
| MAJOR.MINOR.MICRO. */ |
| |
| int |
| gdbscm_guile_version_is_at_least (int major, int minor, int micro) |
| { |
| if (major > gdbscm_guile_major_version) |
| return 0; |
| if (major < gdbscm_guile_major_version) |
| return 1; |
| if (minor > gdbscm_guile_minor_version) |
| return 0; |
| if (minor < gdbscm_guile_minor_version) |
| return 1; |
| if (micro > gdbscm_guile_micro_version) |
| return 0; |
| return 1; |
| } |