diff --git a/gdb/completer.c b/gdb/completer.c index 94f70a9..e26cc2d 100644 --- a/gdb/completer.c +++ b/gdb/completer.c @@ -25,6 +25,7 @@ #include "gdb_assert.h" #include "exceptions.h" #include "gdb_signals.h" +#include "location.h" #include "cli/cli-decode.h" @@ -41,6 +42,21 @@ #include "completer.h" +/* An enumeration of the various things a user might + attempt to complete for a location. */ + +enum explicit_location_match_type +{ + /* The filename of a source file. */ + MATCH_SOURCE, + + /* The name of a function or method. */ + MATCH_FUNCTION, + + /* The name of a label. */ + MATCH_LABEL +}; + /* Prototypes for local functions. */ static char *line_completion_function (const char *text, int matches, @@ -173,7 +189,7 @@ filename_completer (struct cmd_list_element *ignore, return return_val; } -/* Complete on locations, which might be of two possible forms: +/* Complete on linespecs, which might be of two possible forms: file:line or @@ -182,9 +198,9 @@ filename_completer (struct cmd_list_element *ignore, This is intended to be used in commands that set breakpoints etc. */ -VEC (char_ptr) * -location_completer (struct cmd_list_element *ignore, - const char *text, const char *word) +static VEC (char_ptr) * +linespec_location_completer (struct cmd_list_element *ignore, + const char *text, const char *word) { int n_syms, n_files, ix; VEC (char_ptr) *fn_list = NULL; @@ -331,6 +347,169 @@ location_completer (struct cmd_list_element *ignore, return list; } +/* A helper function to collect explicit location matches for the given + LOCATION, which is attempting to match on WHICH. */ + +static VEC (char_ptr) * +collect_explicit_location_matches (struct event_location *location, + enum explicit_location_match_type what, + const char *word) +{ + VEC (char_ptr) *matches = NULL; + struct explicit_location *explicit = EVENT_LOCATION_EXPLICIT (location); + + switch (what) + { + case MATCH_SOURCE: + { + const char *text = (explicit->source_filename == NULL + ? "" : explicit->source_filename); + + matches = make_source_files_completion_list (text, word); + } + break; + + case MATCH_FUNCTION: + { + const char *text = (explicit->function_name == NULL + ? "" : explicit->function_name); + + if (explicit->source_filename != NULL) + matches + = make_file_symbol_completion_list (text, word, + explicit->source_filename); + else + matches = make_symbol_completion_list (text, word); + } + break; + + case MATCH_LABEL: + /* Not supported. */ + break; + + default: + gdb_assert_not_reached (_("unhandled which_explicit")); + } + + return matches; +} + +/* A convenience macro to (safely) back up P to the previous + word. */ + +#define BACKUP(P,S) \ + do \ + { \ + while ((P) > (S) && isspace (*(P))) \ + --(P); \ + for (; (P) > (S) && !isspace ((P)[-1]); --(P)) \ + ; \ + } \ + while (0) + +static VEC (char_ptr) * +explicit_location_completer (struct cmd_list_element *ignore, + struct event_location *location, + const char *text, const char *word) +{ + const char *p; + VEC (char_ptr) *matches = NULL; + + /* Find the beginning of the word. This is necessary because + we need to know if we are completing an option name or value. We + don't get the leading '-' from the completer. */ + p = word; + BACKUP (p, text); + + if (*p == '-') + { + size_t len = strlen (word); + + /* Completing on option name. */ + if (strncmp (word, "source", len) == 0) + VEC_safe_push (char_ptr, matches, xstrdup ("source")); + else if (strncmp (word, "function", len) == 0) + VEC_safe_push (char_ptr, matches, xstrdup ("function")); + else if (*word == 'l') + { + if (strncmp (word, "line", len) == 0) + VEC_safe_push (char_ptr, matches, xstrdup ("line")); + if (strncmp (word, "label", len) == 0) + VEC_safe_push (char_ptr, matches, xstrdup ("label")); + } + } + else + { + /* Completing on value (or unknown). Get the previous word to see what + the user is completing on. */ + size_t len, offset; + const char *new_word, *end; + enum explicit_location_match_type what; + struct explicit_location *explicit = EVENT_LOCATION_EXPLICIT (location); + + /* Backup P to the previous word, which should be the option + the user is attempting to complete. */ + offset = word - p; + end = --p; + BACKUP (p, text); + len = end - p; + + if (strncmp (p, "-source", len) == 0) + { + what = MATCH_SOURCE; + new_word = explicit->source_filename + offset; + } + else if (strncmp (p, "-function", len) == 0) + { + what = MATCH_FUNCTION; + new_word = explicit->function_name + offset; + } + else if (strncmp (p, "-label", len) == 0) + { + what = MATCH_LABEL; + new_word = explicit->label_name + offset; + } + else + { + /* The user isn't completing on any valid option name, + e.g., "break -source foo.c [tab]". */ + return NULL; + } + + /* Now gather matches */ + matches = collect_explicit_location_matches (location, what, new_word); + } + + return matches; +} + +/* A completer for locations. */ + +VEC (char_ptr) * +location_completer (struct cmd_list_element *ignore, + const char *text, const char *word) +{ + VEC (char_ptr) *matches = NULL; + const char *copy = text; + struct event_location *location; + + location = string_to_explicit_location (©, current_language, 1); + if (location != NULL) + { + matches = explicit_location_completer (ignore, location, text, word); + delete_event_location (location); + } + else + { + /* This is an address or linespec location. + Right now both of these are handled by the (old) linespec + completer. */ + matches = linespec_location_completer (ignore, text, word); + } + + return matches; +} + /* Helper for expression_completer which recursively adds field and method names from TYPE, a struct or union type, to the array OUTPUT. */ @@ -668,16 +847,6 @@ complete_line_internal (const char *text, rl_completer_word_break_characters = gdb_completer_file_name_break_characters; } - else if (c->completer == location_completer) - { - /* Commands which complete on locations want to - see the entire argument. */ - for (p = word; - p > tmp_command - && p[-1] != ' ' && p[-1] != '\t'; - p--) - ; - } if (reason != handle_brkchars && c->completer != NULL) list = (*c->completer) (c, p, word); } @@ -743,14 +912,6 @@ complete_line_internal (const char *text, rl_completer_word_break_characters = gdb_completer_file_name_break_characters; } - else if (c->completer == location_completer) - { - for (p = word; - p > tmp_command - && p[-1] != ' ' && p[-1] != '\t'; - p--) - ; - } if (reason != handle_brkchars && c->completer != NULL) list = (*c->completer) (c, p, word); } diff --git a/gdb/linespec.c b/gdb/linespec.c index 600773c..be25287 100644 --- a/gdb/linespec.c +++ b/gdb/linespec.c @@ -347,8 +347,6 @@ static int compare_symbols (const void *a, const void *b); static int compare_msymbols (const void *a, const void *b); -static const char *find_toplevel_char (const char *s, char c); - /* Permitted quote characters for the parser. This is different from the completer's quote characters to allow backward compatibility with the previous parser. */ @@ -398,7 +396,7 @@ linespec_lexer_lex_number (linespec_parser *parser, linespec_token *tokenp) /* Does P represent one of the keywords? If so, return the keyword. If not, return NULL. */ -static const char * +const char * linespec_lexer_lex_keyword (const char *p) { int i; @@ -424,7 +422,7 @@ linespec_lexer_lex_keyword (const char *p) /* Does STRING represent an Ada operator? If so, return the length of the decoded operator name. If not, return 0. */ -static int +int is_ada_operator (const char *string) { const struct ada_opname_map *mapping; @@ -1137,7 +1135,7 @@ find_methods (struct type *t, const char *name, strings. Also, ignore the char within a template name, like a ',' within foo. */ -static const char * +const char * find_toplevel_char (const char *s, char c) { int quoted = 0; /* zero if we're not in quotes; @@ -1550,9 +1548,10 @@ source_file_not_found_error (const char *name) /* Parse and return a line offset in STRING. */ -static struct line_offset +struct line_offset linespec_parse_line_offset (const char *string) { + const char *start = string; struct line_offset line_offset = {0, LINE_OFFSET_NONE}; if (*string == '+') @@ -1566,6 +1565,9 @@ linespec_parse_line_offset (const char *string) ++string; } + if (!isdigit (*string)) + error (_("malformed line offset: \"%s\""), start); + /* Right now, we only allow base 10 for offsets. */ line_offset.offset = atoi (string); return line_offset; @@ -3880,3 +3882,11 @@ make_cleanup_destroy_linespec_result (struct linespec_result *ls) { return make_cleanup (cleanup_linespec_result, ls); } + +/* Return the quote characters permitted by the linespec parser. */ + +const char * +get_gdb_linespec_parser_quote_characters (void) +{ + return linespec_quote_characters; +} diff --git a/gdb/linespec.h b/gdb/linespec.h index df5820a..87a678d 100644 --- a/gdb/linespec.h +++ b/gdb/linespec.h @@ -152,6 +152,36 @@ extern struct symtabs_and_lines decode_line_with_current_source (char *, int); extern struct symtabs_and_lines decode_line_with_last_displayed (char *, int); +/* Parse a line offset from STRING. */ + +extern struct line_offset linespec_parse_line_offset (const char *string); + +/* A completion handler for locations. */ + +VEC (char_ptr) *location_completer (struct cmd_list_element *ignore, + const char *text, const char *word); + +/* Return the quote characters permitted by the linespec parser. */ + +extern const char *get_gdb_linespec_parser_quote_characters (void); + +/* Does STRING represent an Ada operator? If so, return the length + of the decoded operator name. If not, return 0. */ + +extern int is_ada_operator (const char *string); + +/* Find an instance of the character C in the string S that is outside + of all parenthesis pairs, single-quoted strings, and double-quoted + strings. Also, ignore the char within a template name, like a ',' + within foo. */ + +extern const char *find_toplevel_char (const char *s, char c); + +/* Does P represent one of the keywords? If so, return + the keyword. If not, return NULL. */ + +extern const char *linespec_lexer_lex_keyword (const char *p); + /* Evaluate the expression pointed to by EXP_PTR into a CORE_ADDR, advancing EXP_PTR past any parsed text. */ diff --git a/gdb/location.c b/gdb/location.c index f52026a..7d1a953 100644 --- a/gdb/location.c +++ b/gdb/location.c @@ -112,6 +112,55 @@ copy_event_location (const struct event_location *src) return dst; } +/* Return a struct event_location with the given explicit location. + The returned event_location is malloc'd and should be freed with + delete_event_location. */ + +struct event_location * +explicit_to_event_location (const struct explicit_location *explicit) +{ + struct event_location *location; + struct cleanup *back_to; + + location = new_event_location (EVENT_LOCATION_EXPLICIT); + back_to = make_cleanup (delete_event_location, location); + + if (explicit->source_filename != NULL) + { + /* Error check -- we must have one of the other + parameters specified. */ + if (explicit->function_name == NULL + && explicit->label_name == NULL + && explicit->line_offset.sign == LINE_OFFSET_UNKNOWN) + error (_("Source filename requires function, label, or " + "line offset.")); + + EVENT_LOCATION_EXPLICIT (location)->source_filename + = xstrdup (explicit->source_filename); + } + + if (explicit->function_name != NULL) + { + EVENT_LOCATION_EXPLICIT (location)->function_name + = xstrdup (explicit->function_name); + } + + if (explicit->label_name != NULL) + { + EVENT_LOCATION_EXPLICIT (location)->label_name + = xstrdup (explicit->label_name); + } + + if (explicit->line_offset.sign != LINE_OFFSET_UNKNOWN) + { + EVENT_LOCATION_EXPLICIT (location)->line_offset + = explicit->line_offset; + } + + discard_cleanups (back_to); + return location; +} + /* Free LOCATION and any associated data. */ void @@ -183,6 +232,222 @@ event_location_to_string (const struct event_location *location) return result; } +/* A lexer for explicit locations. This function will advance INP + past any strings that it lexes. Returns a malloc'd copy of the + lexed string or NULL if no lexing was done. */ + +static char * +explicit_location_lex_one (const char **inp, + const struct language_defn *language) +{ + const char *start = *inp; + + if (*start != '\0') + { + /* If quoted, skip to the ending quote. */ + if (strchr (get_gdb_linespec_parser_quote_characters (), *start)) + { + char quote_char = *start; + + /* If the input is not an Ada operator, skip to the matching + closing quote and return the string. */ + if (!(language->la_language == language_ada + && quote_char == '\"' && is_ada_operator (start))) + { + const char *end = find_toplevel_char (start + 1, quote_char); + + if (end == NULL) + error (_("Unmatched quote, %s."), start); + *inp = end + 1; + return savestring (start + 1, *inp - start - 2); + } + } + + /* If the input starts with '-' or '+', the string ends with the next + whitespace. */ + if (*start == '-' || *start == '+') + *inp = skip_to_space_const (*inp); + else + { + /* Handle numbers first, stopping at the next whitespace or ','. */ + while (isdigit (*inp[0])) + ++(*inp); + if (*inp[0] == '\0' || isspace (*inp[0]) || *inp[0] == ',') + return savestring (start, *inp - start); + + /* Otherwise stop at the next occurrence of "SPACE -", '\0', + keyword, or ','. */ + *inp = start; + while ((*inp)[0] + && (*inp)[0] != ',' + && !(isspace ((*inp)[0]) + && ((*inp)[1] == '-' + || linespec_lexer_lex_keyword (&(*inp)[1])))) + { + /* Special case: C++ operator,. */ + if (language->la_language == language_cplus + && strncmp (*inp, "operator", 8) + && (*inp)[9] == ',') + (*inp) += 9; + ++(*inp); + } + } + } + + if (*inp - start > 0) + return savestring (start, *inp - start); + + return NULL; +} + +/* Attempt to convert the input string in *ARGP into an explicit location. + ARGP is advanced past any processed input. Returns an event_location + (malloc'd) if an explicit location was successfully found in *ARGP, + NULL otherwise. + + IF !DONT_THROW, this function may call error() if *ARGP looks like + properly formed input, e.g., if it is called with missing argument + parameters or invalid options. If DONT_THROW is non-zero, this function + will not throw any exceptions. */ + +struct event_location * +string_to_explicit_location (const char **argp, + const struct language_defn *language, + int dont_throw) +{ + /* It is assumed that input beginning with '-' and a non-digit + character is an explicit location. */ + if (argp != NULL && *argp != NULL) + { + const char *copy, *orig; + + orig = copy = *argp; + if ((*argp[0] == '-' && isalpha ((*argp)[1]))) + { + char *s, *str; + struct cleanup *cleanup; + struct event_location *location; + + location = new_event_location (EVENT_LOCATION_EXPLICIT); + cleanup = make_cleanup (delete_event_location, location); + + /* Process option/argument pairs. dprintf_command + requires that processing stop on ','. */ + while ((*argp)[0] != '\0' && (*argp)[0] != ',') + { + int len; + char *opt, *oarg; + const char *start; + struct cleanup *inner; + + /* If *ARGP starts with a keyword, stop processing + options. */ + if (linespec_lexer_lex_keyword (*argp) != NULL) + break; + + /* Mark the start of the string in case we need to rewind. */ + start = *argp; + + /* Get the option string. */ + opt = explicit_location_lex_one (argp, language); + inner = make_cleanup (xfree, opt); + + /* Skip any whitespace. */ + *argp = skip_spaces_const (*argp); + + /* Get the argument string. */ + oarg = explicit_location_lex_one (argp, language); + + /* Skip any whitespace. */ + *argp = skip_spaces_const (*argp); + + /* Use the length of the option to allow abbreviations. */ + len = strlen (opt); + + /* All options have a required argument. */ + if (strncmp (opt, "-source", len) == 0) + EVENT_LOCATION_EXPLICIT (location)->source_filename = oarg; + else if (strncmp (opt, "-function", len) == 0) + EVENT_LOCATION_EXPLICIT (location)->function_name = oarg; + else if (strncmp (opt, "-line", len) == 0) + { + if (oarg != NULL) + { + volatile struct gdb_exception e; + + TRY_CATCH (e, RETURN_MASK_ERROR) + { + EVENT_LOCATION_EXPLICIT (location)->line_offset + = linespec_parse_line_offset (oarg); + } + + xfree (oarg); + if (e.reason < 0 && !dont_throw) + throw_exception (e); + } + } + else if (strncmp (opt, "-label", len) == 0) + EVENT_LOCATION_EXPLICIT (location)->label_name = oarg; + /* Only emit an "invalid argument" error for options + that look like option strings. */ + else if (opt[0] == '-' && !isdigit (opt[1])) + { + xfree (oarg); + if (!dont_throw) + { + error (_("invalid explicit location argument, \"%s\""), + opt); + } + } + else + { + /* Trailing garbage. This will be handled by + one of the callers. */ + *argp = start; + break; + } + + /* It's a little lame to error after the fact, but in this + case, it provides a much better user experience to issue + the "invalid argument" error before any missing + argument error. */ + if (oarg == NULL && !dont_throw) + error (_("missing argument for \"%s\""), opt); + + /* The option/argument pari was successfully processed; + oarg belongs to the explicit location, and opt should + be freed. */ + do_cleanups (inner); + } + + discard_cleanups (cleanup); + + /* One special error check: If a source filename was given + without offset, function, or label, issue an error. */ + if (EVENT_LOCATION_EXPLICIT (location)->source_filename != NULL + && EVENT_LOCATION_EXPLICIT (location)->function_name == NULL + && EVENT_LOCATION_EXPLICIT (location)->label_name == NULL + && (EVENT_LOCATION_EXPLICIT (location)->line_offset.sign + == LINE_OFFSET_UNKNOWN) + && !dont_throw) + error (_("Source filename requires function, label, or " + "line offset.")); + + /* A valid explicit location was constructed. Save a copy + of the parsed input and save any unparsed input (which might + contain a condition, thread, or task expression). */ + str = savestring (orig, *argp - orig); + s = remove_trailing_whitespace (str, str + (*argp - orig)); + *s = '\0'; + EVENT_LOCATION_SAVE_SPEC (location) = str; + return location; + } + } + + /* Not an explicit location. */ + return NULL; +} + /* Parse the user input in *STRINGP and turn it into a struct event_location, advancing STRINGP past any parsed input. Return value is malloc'd. */ @@ -221,12 +486,26 @@ string_to_event_location (char **stringp, } else { - /* Everything else is a linespec. */ - location = new_event_location (EVENT_LOCATION_LINESPEC); - if (*stringp != NULL) + const char *arg, *orig; + + /* Next, try an explicit location. */ + orig = arg = *stringp; + location = string_to_explicit_location (&arg, language, 0); + if (location != NULL) { - EVENT_LOCATION_LINESPEC (location) = xstrdup (*stringp); - *stringp += strlen (*stringp); + /* It was a valid explicit location. Advance STRINGP to + the end of input. */ + *stringp += arg - orig; + } + else + { + /* Everything else is a linespec. */ + location = new_event_location (EVENT_LOCATION_LINESPEC); + if (*stringp != NULL) + { + EVENT_LOCATION_LINESPEC (location) = xstrdup (*stringp); + *stringp += strlen (*stringp); + } } } } diff --git a/gdb/location.h b/gdb/location.h index 0d3400a..9ec158b 100644 --- a/gdb/location.h +++ b/gdb/location.h @@ -142,6 +142,13 @@ extern struct event_location * extern struct event_location * copy_event_location (const struct event_location *src); +/* Return a struct event_location with the given explicit location. + The returned event_location is malloc'd and should be freed with + delete_event_location. */ + +extern struct event_location * + explicit_to_event_location (const struct explicit_location *explicit); + /* Initialize the given LOCATION. */ extern void initialize_event_location (struct event_location *location, @@ -167,4 +174,19 @@ extern struct event_location * /* A convenience function for testing for unset locations. */ extern int event_location_empty_p (const struct event_location *location); + +/* Attempt to convert the input string in *ARGP into an explicit location. + ARGP is advanced past any processed input. Returns an event_location + (malloc'd) if an explicit location was successfully found in *ARGP, + NULL otherwise. + + IF !DONT_THROW, this function may call error() if *ARGP looks like + properly formed input, e.g., if it is called with missing argument + parameters or invalid options. If DONT_THROW is non-zero, this function + will not throw any exceptions. */ + +extern struct event_location * + string_to_explicit_location (const char **argp, + const struct language_defn *langauge, + int dont_throw); #endif /* LOCATIONS_H */ diff --git a/gdb/testsuite/gdb.base/save-bp.exp b/gdb/testsuite/gdb.base/save-bp.exp index ba98633..5fab7cf 100644 --- a/gdb/testsuite/gdb.base/save-bp.exp +++ b/gdb/testsuite/gdb.base/save-bp.exp @@ -27,6 +27,8 @@ if ![runto_main] { # does not interfere with our testing. delete_breakpoints +set savefile [standard_output_file "bps"] + # Insert a bunch of breakpoints... The goal is to create breakpoints # that we are going to try to save in a file and then reload. So # try to create a good variety of them. @@ -45,6 +47,8 @@ set loc_bp5 [gdb_get_line_number "with commands"] gdb_breakpoint ${srcfile}:${loc_bp5} gdb_test "commands\nsilent\nend" "End with.*" "add breakpoint commands" +gdb_breakpoint "*break_me" + gdb_test "dprintf ${srcfile}:${loc_bp5},\"At foo entry\\n\"" "Dprintf .*" # Now, save the breakpoints into a file... @@ -72,5 +76,20 @@ gdb_test "source $bps" "" "source bps" # Now, verify that all breakpoints have been created correctly... set bp_row_start "\[0-9\]+ +breakpoint +keep +y +0x\[0-9a-f\]+ +in" set dprintf_row_start "\[0-9\]+ +dprintf +keep +y +0x\[0-9a-f\]+ +in" -gdb_test "info break" \ - " *Num +Type +Disp +Enb +Address +What\r\n$bp_row_start break_me at .*$srcfile:\[0-9\]+\r\n$bp_row_start main at .*$srcfile:$loc_bp2\r\n$bp_row_start main at .*$srcfile:$loc_bp3 +thread 1\r\n\[ \t]+stop only in thread 1\r\n$bp_row_start main at .*$srcfile:$loc_bp4\r\n\[ \t\]+stop only if i == 1( \\((host|target) evals\\))?\r\n$bp_row_start main at .*$srcfile:$loc_bp5\r\n\[ \t\]+silent\r\n$dprintf_row_start main at .*$srcfile:$loc_bp5\r\n\[ \t\]+printf.*" + +set row_list \ + [list \ + " *Num +Type +Disp +Enb +Address +What" \ + "$bp_row_start break_me at .*$srcfile:\[0-9\]+" \ + "$bp_row_start main at .*$srcfile:$loc_bp2" \ + "$bp_row_start main at .*$srcfile:$loc_bp3 +thread 1" \ + "\[ \t]+stop only in thread 1" \ + "$bp_row_start main at .*$srcfile:$loc_bp4" \ + "\[ \t\]+stop only if i == 1( \\((host|target) evals\\))?" \ + "$bp_row_start main at .*$srcfile:$loc_bp5\r\n\[ \t\]+silent" \ + "$bp_row_start break_me at .*$srcfile:\[0-9\]+" \ + "$dprintf_row_start main at .*$srcfile:$loc_bp5" \ + "\[ \t\]+printf.*"] +set expected [join $row_list {\r\n}] +verbose -log "Expecting: $expected" +gdb_test "info break" $expected diff --git a/gdb/testsuite/gdb.linespec/3explicit.c b/gdb/testsuite/gdb.linespec/3explicit.c new file mode 100644 index 0000000..12bf277 --- /dev/null +++ b/gdb/testsuite/gdb.linespec/3explicit.c @@ -0,0 +1,28 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2013 Free Software Foundation, Inc. + + 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 . */ + +static int +myfunction4 (int arg) +{ + return arg + 2; +} + +int +myfunction3 (int arg) +{ + return myfunction4 (arg); +} diff --git a/gdb/testsuite/gdb.linespec/cpexplicit.cc b/gdb/testsuite/gdb.linespec/cpexplicit.cc new file mode 100644 index 0000000..42d50c7 --- /dev/null +++ b/gdb/testsuite/gdb.linespec/cpexplicit.cc @@ -0,0 +1,63 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2012-2013 Free Software Foundation, Inc. + + 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 . */ + +class myclass +{ +public: + static int myfunction (int arg) /* entry location */ + { + int i, j, r; + + j = 0; /* myfunction location */ + r = arg; + + top: + ++j; /* top location */ + + if (j == 10) + goto done; + + for (i = 0; i < 10; ++i) + { + r += i; + if (j % 2) + goto top; + } + + done: + return r; + } + + int operator, (const myclass& c) { return 0; } /* operator location */ +}; + +int +main (void) +{ + int i, j; + + /* Call the test function repeatedly, enough times for all our tests + without running forever if something goes wrong. */ + myclass c, d; + for (i = 0, j = 0; i < 1000; ++i) + { + j += myclass::myfunction (0); + j += (c,d); + } + + return j; +} diff --git a/gdb/testsuite/gdb.linespec/cpexplicit.exp b/gdb/testsuite/gdb.linespec/cpexplicit.exp new file mode 100644 index 0000000..2bb9291 --- /dev/null +++ b/gdb/testsuite/gdb.linespec/cpexplicit.exp @@ -0,0 +1,104 @@ +# Copyright 2012-2013 Free Software Foundation, Inc. + +# 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 . + +# Tests for explicit linespecs + +if {[skip_cplus_tests]} { + unsupported "skipping C++ tests" + return +} + +standard_testfile .cc +set exefile $testfile + +if {[prepare_for_testing $testfile $exefile $srcfile \ + {c++ debug nowarnings}]} { + return -1 +} + +# Test the given (explicit) LINESPEC which should cause gdb to break +# at LOCATION. +proc test_breakpoint {linespec location} { + + # Delete all breakpoints, set a new breakpoint at LINESPEC, + # and attempt to run to it. + delete_breakpoints + gdb_breakpoint $linespec + gdb_continue_to_breakpoint $linespec $location +} + +# Add the given LINESPEC to the array named in THEARRAY. GDB is expected +# to stop at LOCATION. +proc add {thearray linespec location} { + upvar $thearray ar + + lappend ar(linespecs) $linespec + lappend ar(locations) $location +} + +# Make sure variables are not already in use +unset -nocomplain lineno location linespecs + +# Some locations used in this test +set lineno(normal) [gdb_get_line_number "myfunction location" $srcfile] +set lineno(entry) [gdb_get_line_number "entry location" $srcfile] +set lineno(top) [gdb_get_line_number "top location" $srcfile] +set lineno(operator) [gdb_get_line_number "operator location" $srcfile] +foreach v [array names lineno] { + set location($v) ".*[string_to_regexp "$srcfile:$lineno($v)"].*" +} + +# A list of explicit linespecs and the corresponding location +set linespecs(linespecs) {} +set linespecs(location) {} + +add linespecs "-source $srcfile -function myclass::myfunction" $location(normal) +add linespecs "-source $srcfile -function myclass::myfunction -label top" \ + $location(top) + +# This isn't implemented yet; -line is silently ignored. +add linespecs \ + "-source $srcfile -function myclass::myfunction -label top -line 3" \ + $location(top) +add linespecs "-source $srcfile -line $lineno(top)" $location(top) +add linespecs "-function myclass::myfunction" $location(normal) +add linespecs "-function myclass::myfunction -label top" $location(top) + +# These are also not yet supported; -line is silently ignored. +add linespecs "-function myclass::myfunction -line 3" $location(normal) +add linespecs "-function myclass::myfunction -label top -line 3" $location(top) +add linespecs "-line 3" $location(normal) +add linespecs "-function myclass::operator," $location(operator) +add linespecs "-function 'myclass::operator,'" $location(operator) +add linespecs "-function \"myclass::operator,\"" $location(operator) + +# Fire up gdb. +if {![runto_main]} { + return -1 +} + +# Test explicit linespecs, with and without conditions. +foreach linespec $linespecs(linespecs) loc_pattern $linespecs(locations) { + # Test the linespec + test_breakpoint $linespec $loc_pattern +} + +# Special (orphaned) dprintf cases. +gdb_test "dprintf -function myclass::operator,,\"hello\"" \ + "Dprintf .*$srcfile, line $lineno(operator)\\." +gdb_test "dprintf -function 'myclass::operator,',\"hello\"" \ + "Dprintf .*$srcfile, line $lineno(operator)\\." +gdb_test "dprintf -function \"myclass::operator,\",\"hello\"" \ + "Dprintf .*$srcfile, line $lineno(operator)\\." diff --git a/gdb/testsuite/gdb.linespec/explicit.c b/gdb/testsuite/gdb.linespec/explicit.c new file mode 100644 index 0000000..4e1c635 --- /dev/null +++ b/gdb/testsuite/gdb.linespec/explicit.c @@ -0,0 +1,56 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2012-2013 Free Software Foundation, Inc. + + 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 . */ + +extern int myfunction2 (int arg); + +static int +myfunction (int arg) +{ + int i, j, r; + + j = 0; /* myfunction location */ + r = arg; + + top: + ++j; /* top location */ + + if (j == 10) + goto done; + + for (i = 0; i < 10; ++i) + { + r += i; + if (j % 2) + goto top; + } + + done: + return r; +} + +int +main (void) +{ + int i, j; + + /* Call the test function repeatedly, enough times for all our tests + without running forever if something goes wrong. */ + for (i = 0, j = 0; i < 1000; ++i) + j += myfunction (0); + + return myfunction2 (j); +} diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp new file mode 100644 index 0000000..f31c72c --- /dev/null +++ b/gdb/testsuite/gdb.linespec/explicit.exp @@ -0,0 +1,353 @@ +# Copyright 2012-2013 Free Software Foundation, Inc. + +# 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 . + +# Tests for explicit locations + +standard_testfile explicit.c explicit2.c 3explicit.c +set exefile $testfile + +if {[prepare_for_testing $testfile $exefile \ + [list $srcfile $srcfile2 $srcfile3] {debug nowarnings}]} { + return -1 +} + +# Test the given (explicit) LINESPEC which should cause gdb to break +# at LOCATION. +proc test_breakpoint {linespec location} { + + set testname "set breakpoint at \"$linespec\"" + # Delete all breakpoints, set a new breakpoint at LINESPEC, + # and attempt to run to it. + delete_breakpoints + if {[gdb_breakpoint $linespec]} { + pass $testname + send_log "\nexpecting locpattern \"$location\"\n" + gdb_continue_to_breakpoint $linespec $location + } else { + fail $testname + } +} + +# Add the given LINESPEC to the array named in THEARRAY. GDB is expected +# to stop at LOCATION. +proc add {thearray linespec location} { + upvar $thearray ar + + lappend ar(linespecs) $linespec + lappend ar(locations) $location +} + +# Make sure variables are not already in use +unset -nocomplain all_arguments lineno location linespecs + +# A list of all explicit linespec arguments. +set all_arguments {"source" "function" "label" "line"} + +# Some locations used in this test +set lineno(normal) [gdb_get_line_number "myfunction location" $srcfile] +set lineno(top) [gdb_get_line_number "top location" $srcfile] +foreach v [array names lineno] { + set location($v) ".*[string_to_regexp "$srcfile:$lineno($v)"].*" +} + +# A list of explicit locations and the corresponding location. +set linespecs(linespecs) {} +set linespecs(location) {} + +add linespecs "-source $srcfile -function myfunction" $location(normal) +add linespecs "-source $srcfile -function myfunction -label top" \ + $location(top) + +# This isn't implemented yet; -line is silently ignored. +add linespecs "-source $srcfile -function myfunction -label top -line 3" \ + $location(top) +add linespecs "-source $srcfile -line $lineno(top)" $location(top) +add linespecs "-function myfunction" $location(normal) +add linespecs "-function myfunction -label top" $location(top) + +# These are also not yet supported; -line is silently ignored. +add linespecs "-function myfunction -line 3" $location(normal) +add linespecs "-function myfunction -label top -line 3" $location(top) +add linespecs "-line 3" $location(normal) + +# Test that static tracepoints on marker ID are not interpreted +# as an erroneous explicit option. +gdb_test "strace -m gdbfoobarbaz" "You can't do that.*" + +# Fire up gdb. +if {![runto_main]} { + return -1 +} + +# Simple error tests (many more are tested in ls-err.exp) +foreach arg $all_arguments { + # Test missing argument + gdb_test "break -$arg" [string_to_regexp "missing argument for \"-$arg\""] + + # Test abbreviations + set short [string range $arg 0 3] + gdb_test "break -$short" \ + [string_to_regexp "missing argument for \"-$short\""] +} + +# Test invalid arguments +foreach arg {"-foo" "-foo bar" "-function myfunction -foo" \ + "-function -myfunction -foo bar"} { + gdb_test "break $arg" \ + [string_to_regexp "invalid explicit location argument, \"-foo\""] +} + +# Test explicit locations, with and without conditions. +# For these tests, it is easiest to turn of pending breakpoint. +gdb_test_no_output "set breakpoint pending off" "turn off pending breakpoints" + +foreach linespec $linespecs(linespecs) loc_pattern $linespecs(locations) { + + # Test the linespec + test_breakpoint $linespec $loc_pattern + + # Test with a valid condition + delete_breakpoints + set tst "set breakpoint at \"$linespec\" with valid condition" + if {[gdb_breakpoint "$linespec if arg == 0"]} { + pass $tst + + gdb_test "info break" ".*stop only if arg == 0.*" \ + "info break of conditional breakpoint at \"$linespec\"" + } else { + fail $tst + } + + # Test with invalid condition + gdb_test "break $linespec if foofoofoo == 1" \ + ".*No symbol \"foofoofoo\" in current context.*" \ + "set breakpoint at \"$linespec\" with invalid condition" + + # Test with thread + delete_breakpoints + gdb_test "break $linespec thread 123" "Unknown thread 123." +} + +# Test the explicit location completer +foreach abbrev {"fun" "so" "lab" "li"} \ + full {"function" "source" "label" "line"} { + set tst "complete 'break -$abbrev'" + send_gdb "break -${abbrev}\t" + gdb_test_multiple "" $tst { + "break -$full " { + send_gdb "\n" + gdb_test_multiple "" $tst { + -re "missing argument for \"-$full\".*$gdb_prompt " { + pass $tst + } + } + } + } +} + +set tst "complete unique function name" +send_gdb "break -function mai\t" +gdb_test_multiple "" $tst { + "break -function mai\\\x07n" { + send_gdb "\n" + gdb_test "" ".*Breakpoint \[0-9\]+.*" $tst + gdb_test_no_output "delete \$bpnum" "delete $tst breakpoint" + } +} + +set tst "complete non-unique function name" +send_gdb "break -function myfunc\t" +gdb_test_multiple "" $tst { + "break -function myfunc\\\x07tion" { + send_gdb "\t\t" + gdb_test_multiple "" $tst { + -re "\\\x07\r\nmyfunction\[ \t\]+myfunction2\[ \t\]+myfunction3\[ \t\]+myfunction4\[ \t\]+\r\n$gdb_prompt " { + gdb_test "2" ".*Breakpoint \[0-9\]+.*" $tst + gdb_test_no_output "delete \$bpnum" "delete $tst breakpoint" + } + } + } +} + +set tst "complete non-existant function name" +send_gdb "break -function foo\t" +gdb_test_multiple "" $tst { + "break -function foo\\\x07" { + send_gdb "\t\t" + gdb_test_multiple "" $tst { + -re "\\\x07\\\x07" { + send_gdb "\n" + gdb_test "" {Function "foo" not defined.} $tst + } + } + } +} + +set tst "complete unique file name" +send_gdb "break -source 3ex\t" +gdb_test_multiple "" $tst { + "break -source 3explicit.c " { + send_gdb "\n" + gdb_test "" \ + {Source filename requires function, label, or line offset.} $tst + } +} + +set tst "complete non-unique file name" +send_gdb "break -source exp\t" +gdb_test_multiple "" $tst { + "break -source exp\\\x07licit" { + send_gdb "\t\t" + gdb_test_multiple "" $tst { + -re "\\\x07\r\nexplicit.c\[ \t\]+explicit2.c\[ \t\]+\r\n$gdb_prompt" { + send_gdb "\n" + gdb_test "" \ + {Source filename requires function, label, or line offset.} \ + $tst + } + } + } +} + + +set tst "complete non-existant file name" +send_gdb "break -source foo\t" +gdb_test_multiple "" $tst { + "break -source foo" { + send_gdb "\t\t" + gdb_test_multiple "" $tst { + "\\\x07\\\x07" { + send_gdb "\n" + gdb_test "" \ + {Source filename requires function, label, or line offset.} \ + $tst + } + } + } +} + +set tst "complete filename and unique function name" +send_gdb "break -source explicit.c -function ma\t" +gdb_test_multiple "" $tst { + "break -source explicit.c -function main " { + send_gdb "\n" + gdb_test "" ".*Breakpoint .*" $tst + gdb_test_no_output "delete \$bpnum" "delete $tst breakpoint" + } +} + +set tst "complete filename and non-unique function name" +send_gdb "break -so 3explicit.c -func myfunc\t" +gdb_test_multiple "" $tst { + "break -so 3explicit.c -func myfunc\\\x07tion" { + send_gdb "\t\t" + gdb_test_multiple "" $tst { + -re "\\\x07\r\nmyfunction3\[ \t\]+myfunction4\[ \t\]+\r\n$gdb_prompt " { + gdb_test "3" ".*Breakpoint \[0-9\]+.*" $tst + gdb_test_no_output "delete \$bpnum" "delete $tst breakpoint" + } + } + } +} + +set tst "complete filename and non-existant function name" +send_gdb "break -sou 3explicit.c -fun foo\t" +gdb_test_multiple "" $tst { + "break -sou 3explicit.c -fun foo\\\x07" { + send_gdb "\t\t" + gdb_test_multiple "" $tst { + "\\\x07\\\x07" { + send_gdb "\n" + gdb_test "" {Function "foo" not defined in "3explicit.c".} $tst + } + } + } +} + +set tst "complete filename and function reversed" +send_gdb "break -func myfunction4 -source 3ex\t" +gdb_test_multiple "" $tst { + "break -func myfunction4 -source 3explicit.c " { + send_gdb "\n" + gdb_test "" "Breakpoint \[0-9\]+.*" $tst + gdb_test_no_output "delete \$bpnum" "delete $tst breakpoint" + } +} + +# NOTE: We don't bother testing more elaborate combinations of options, +# such as "-func main -sour 3ex\t" (main is defined in explicit.c). The +# completer cannot handle these yet. + +# Test pending explicit breakpoints +gdb_exit +gdb_start + +set tst "pending invalid conditional explicit breakpoint" +if {![gdb_breakpoint "-func myfunction if foofoofoo == 1" \ + allow-pending]} { + fail "set $tst" +} else { + gdb_test "info break" ".*PENDING.*myfunction if foofoofoo == 1.*" $tst +} + +gdb_exit +gdb_start + +set tst "pending valid conditional explicit breakpoint" +if {![gdb_breakpoint "-func myfunction if arg == 0" \ + allow-pending]} { + fail "set $tst" +} else { + gdb_test "info break" ".*PENDING.*myfunction if arg == 0" $tst + + gdb_load [standard_output_file $exefile] + gdb_test "info break" \ + ".*in myfunction at .*$srcfile:.*stop only if arg == 0.*" \ + "$tst resolved" +} + + +# Test interaction of condition command and explicit linespec conditons. +gdb_exit +gdb_start +gdb_load [standard_output_file $exefile] + +set tst "condition_command overrides explicit linespec condition" +if {![runto main]} { + fail $tst +} else { + if {![gdb_breakpoint "-func myfunction if arg == 1"]} { + fail "set breakpoint with condition 'arg == 1'" + } else { + gdb_test_no_output "cond 2 arg == 0" \ + "set new breakpoint condition for explicit linespec" + + gdb_continue_to_breakpoint $tst $location(normal) + } +} + +gdb_test "cond 2" [string_to_regexp "Breakpoint 2 now unconditional."] \ + "clear condition for explicit breakpoint" +set tst "info break of cleared condition of explicit breakpoint" +gdb_test_multiple "info break" $tst { + -re ".*in myfunction at .*$srcfile:.*stop only if arg == 0.*" { + fail $tst + } + -re ".*in myfunction at .*$srcfile:.*$gdb_prompt $" { + pass $tst + } +} + +unset -nocomplain lineno tst diff --git a/gdb/testsuite/gdb.linespec/explicit2.c b/gdb/testsuite/gdb.linespec/explicit2.c new file mode 100644 index 0000000..218cccb --- /dev/null +++ b/gdb/testsuite/gdb.linespec/explicit2.c @@ -0,0 +1,24 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2013 Free Software Foundation, Inc. + + 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 . */ + +extern int myfunction3 (int arg); + +int +myfunction2 (int arg) +{ + return myfunction3 (arg); +} diff --git a/gdb/testsuite/gdb.linespec/ls-errs.exp b/gdb/testsuite/gdb.linespec/ls-errs.exp index 86056c5..949bae6 100644 --- a/gdb/testsuite/gdb.linespec/ls-errs.exp +++ b/gdb/testsuite/gdb.linespec/ls-errs.exp @@ -46,11 +46,16 @@ array set error_messages { invalid_var_or_func_f \ "Undefined convenience variable or function \"%s\" not defined in \"%s\"." invalid_label "No label \"%s\" defined in function \"%s\"." + invalid_parm "invalid linespec argument, \"%s\"" invalid_offset "No line %d in the current file." invalid_offset_f "No line %d in file \"%s\"." + malformed_line_offset "malformed line offset: \"%s\"" + source_incomplete \ + "Source filename requires function, label, or line offset." unexpected "malformed linespec error: unexpected %s" unexpected_opt "malformed linespec error: unexpected %s, \"%s\"" unmatched_quote "unmatched quote" + garbage "Garbage '%s' at end of command" } # Some commonly used whitespace tests around ':'. @@ -77,6 +82,7 @@ foreach x $invalid_offsets { incr offset 16 } test_break $x invalid_offset $offset + test_break "-line $x" invalid_offset $offset } # Test offsets with trailing tokens w/ and w/o spaces. @@ -88,13 +94,17 @@ foreach x $spaces { foreach x {1 +1 +100 -10} { test_break "3 $x" unexpected_opt "number" $x + test_break "-line 3 $x" garbage $x test_break "+10 $x" unexpected_opt "number" $x + test_break "-line +10 $x" garbage $x test_break "-10 $x" unexpected_opt "number" $x + test_break "-line -10 $x" garbage $x } -test_break "3 foo" unexpected_opt "string" "foo" -test_break "+10 foo" unexpected_opt "string" "foo" -test_break "-10 foo" unexpected_opt "string" "foo" +foreach x {3 +10 -10} { + test_break "$x foo" unexpected_opt "string" "foo" + test_break "-line $x foo" garbage "foo" +} # Test invalid linespecs starting with filename. foreach x [list "this_file_doesn't_exist.c" \ @@ -110,6 +120,13 @@ foreach x [list "this_file_doesn't_exist.c" \ # Remove any quoting from FILENAME for the error message. test_break "$x:3" invalid_file [string trim $x \"'] } +foreach x [list "this_file_doesn't_exist.c" \ + "this file has spaces.c" \ + "file::colons.c" \ + "'file::colons.c'"] { + test_break "-source $x -line 3" \ + invalid_file [string trim $x \"'] +} # Test unmatched quotes. foreach x {"\"src-file.c'" "'src-file.c"} { @@ -120,7 +137,11 @@ test_break $srcfile invalid_function $srcfile foreach x {"foo" " foo" " foo "} { # Trim any leading/trailing whitespace for error messages. test_break "$srcfile:$x" invalid_function_f [string trim $x] $srcfile + test_break "-source $srcfile -function $x" \ + invalid_function_f [string trim $x] $srcfile test_break "$srcfile:main:$x" invalid_label [string trim $x] "main" + test_break "-source $srcfile -function main -label $x" \ + invalid_label [string trim $x] "main" } foreach x $spaces { @@ -130,20 +151,26 @@ foreach x $spaces { test_break "${srcfile}::" invalid_function "${srcfile}::" test_break "$srcfile:3 1" unexpected_opt "number" "1" +test_break "-source $srcfile -line 3 1" garbage "1" test_break "$srcfile:3 +100" unexpected_opt "number" "+100" +test_break "-source $srcfile -line 3 +100" garbage "+100" test_break "$srcfile:3 -100" unexpected_opt "number" "-100" test_break "$srcfile:3 foo" unexpected_opt "string" "foo" +test_break "-source $srcfile -line 3 foo" garbage "foo" foreach x $invalid_offsets { test_break "$srcfile:$x" invalid_offset_f $x $srcfile test_break "\"$srcfile:$x\"" invalid_offset_f $x $srcfile test_break "'$srcfile:$x'" invalid_offset_f $x $srcfile + test_break "-source $srcfile -line $x" invalid_offset_f $x $srcfile } +test_break "-source $srcfile -line -x" malformed_line_offset "-x" # Test invalid filespecs starting with function. foreach x {"foobar" "foo::bar" "foo.bar" "foo ." "foo bar" "foo 1" \ "foo 0" "foo +10" "foo -10" "foo +100" "foo -100"} { test_break $x invalid_function $x + test_break "-function \"$x\"" invalid_function $x } foreach x $spaces { @@ -152,13 +179,12 @@ foreach x $spaces { test_break "main:here${x}" unexpected "end of input" } -test_break "main 3" invalid_function "main 3" -test_break "main +100" invalid_function "main +100" -test_break "main -100" invalid_function "main -100" -test_break "main foo" invalid_function "main foo" - foreach x {"3" "+100" "-100" "foo"} { + test_break "main 3" invalid_function "main 3" + test_break "-function \"main $x\"" invalid_function "main $x" test_break "main:here $x" invalid_label "here $x" "main" + test_break "-function main -label \"here $x\"" \ + invalid_label "here $x" "main" } foreach x {"if" "task" "thread"} { @@ -175,3 +201,6 @@ test_break "'main.c'+3" unexpected_opt "number" "+3" set x {$zippo} test_break $x invalid_var_or_func $x test_break "$srcfile:$x" invalid_var_or_func_f $x $srcfile + +# Explicit linespec-specific tests +test_break "-source $srcfile" source_incomplete