| /* General utility routines for GDB/Scheme code. | |
| Copyright (C) 2014-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/>. */ | |
| /* 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; | |
| char *string; | |
| va_start (args, format); | |
| string = xstrvprintf (format, args); | |
| va_end (args); | |
| scm_puts (string, port); | |
| xfree (string); | |
| } | |
| /* 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); | |
| 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; | |
| } | |
| /* 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; | |
| 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. */ | |
| VEC (char_ptr) *allocated_strings = NULL; | |
| char *ptr; | |
| have_rest = validate_arg_format (format); | |
| num_keywords = count_keywords (keywords); | |
| va_start (args, format); | |
| 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') | |
| VEC_safe_push (char_ptr, allocated_strings, *(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') | |
| { | |
| VEC_safe_push (char_ptr, allocated_strings, | |
| *(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; | |
| } | |
| } | |
| va_end (args); | |
| VEC_free (char_ptr, allocated_strings); | |
| return; | |
| fail: | |
| va_end (args); | |
| for (i = 0; VEC_iterate (char_ptr, allocated_strings, i, ptr); ++i) | |
| xfree (ptr); | |
| VEC_free (char_ptr, allocated_strings); | |
| gdbscm_throw (status); | |
| } | |
| /* 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; | |
| } |