Pedro Alves c45ec17c07 A smarter linespec completer
Continuing the theme of the explicit locations patch, this patch gets
rid of the need for quoting function names in linespec TAB completion.
To recap, when you have overloads in your program, and you want to set
a breakpoint in one of them:

 void function(int);  // set breakpoint here.
 void function(long);

 (gdb) b function(i[TAB]
 <all the symbols in the program that start with "i" are uselessly shown...>

This patch gets rid of the need for quoting by switching the linespec
completer to use the custom completion word point mechanism added in
the previous explicit location patch (extending it as needed), to
correctly determine the right completion word point.  In the case
above, we want the completer to figure out that it's completing a
function name that starts with "function(i", and it now does.

We also want the completer to know when it's potentially completing a
source file name, for:

(gdb) break source.[TAB] -> source.c:
(gdb) break source.c:  # Type line number or function name now

And we want it to know to complete label names, which it doesn't today:

(gdb) break function:lab[TAB]

etc., etc.

So what we want is for completion to grok the input string as closely
to how the linespec parser groks it.

With that in mind, the solution suggests itself - make the linespec
completer use the same parsing code as normal linespec parsing.

That's what the patch does.  The old completer is replaced by one that
reuses the actual linespec parser as much as possible.  This (ideally)
eliminate differences between what completion understands and actually
setting breakpoints understands by design.

The completer now offers sensible completion candidates depending on
which component of the linespec is being completed, source filename,
function, line number, expression, and (a new addition), labels.  For
example, when completing the function part, we now show the full name
of the method as completion candidates, instead of showing whatever
comes after what readline considered the word break character:

 (gdb) break klass::method[TAB]
 klass:method1(int)
 klass:method2()

If input is past the function, then we now offer keyword condidates:

  (gdb) b function(int) [TAB]
  if      task    thread

If input is past a keyword, we offer expression completion, which is
different from linespec completion:

  (gdb) b main if 1 + glo[TAB]
  global

(e.g., completes on types, struct data fields, etc.)

As mentioned, this teaches the linespec completer about completing
label symbols too:

  (gdb) b source.c:function:lab[TAB]

A nice convenience is that when completion uniquely matches a source
name, gdb adds the ":" automatically for you:

  (gdb) b filenam[TAB]
  (gdb) b filename.c:  # ':' auto-added, cursor right after it.

It's the little details.  :-)

I worked on this patch in parallel with writing the (big) testcase
added closer to the end of the series, which exercises many many
tricky cases around quoting and whitespace insertion placement.  In
general, I think it now all Just Works.

gdb/ChangeLog:
2017-07-17  Pedro Alves  <palves@redhat.com>

	* completer.c (complete_source_filenames): New function.
	(complete_address_and_linespec_locations): New function.
	(location_completer): Use complete_address_and_linespec_locations.
	(completion_tracker::build_completion_result): Honor the tracker's
	request to suppress append.
	* completer.h (completion_tracker::suppress_append_ws)
	(completion_tracker::set_suppress_append_ws): New methods.
	(completion_tracker::m_suppress_append_ws): New field.
	(complete_source_filenames): New declaration.
	* linespec.c (linespec_complete_what): New.
	(struct ls_parser) <complete_what, completion_word,
	completion_quote_char, completion_quote_end, completion_tracker>:
	New fields.
	(string_find_incomplete_keyword_at_end): New.
	(linespec_lexer_lex_string): Record quote char.  If in completion
	mode, don't throw.
	(linespec_lexer_consume_token): Advance the completion word point.
	(linespec_lexer_peek_token): Save/restore completion info.
	(save_stream_and_consume_token): New.
	(set_completion_after_number): New.
	(linespec_parse_basic): Set what to complete next depending on
	token.  Handle function and label completions specially.
	(parse_linespec): Disable objc shortcut in completion mode.  Set
	what to complete next depending on token type.  Skip keyword if in
	completion mode.
	(complete_linespec_component, linespec_complete): New.
	* linespec.h (linespec_complete): Declare.

gdb/testsuite/ChangeLog:
2017-07-17  Pedro Alves  <palves@redhat.com>

	* gdb.base/completion.exp: Adjust expected output.
	* gdb.linespec/ls-errs.exp: Don't send tab characters, now that
	the completer works.
2017-07-17 20:29:37 +01:00

275 lines
9.1 KiB
Plaintext

# Copyright 2012-2017 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 <http://www.gnu.org/licenses/>.
# Tests for linespec errors with C and C++.
# The test proper. LANG is either C or C++.
proc do_test {lang} {
global testfile srcfile error_messages compiler_info
standard_testfile
set exefile $testfile
if [info exists compiler_info] {
# Unsetting compiler_info allows us to switch compilers
# used by prepare_for_testing.
unset compiler_info
}
set options {debug}
if {$lang == "C++"} {
if {[skip_cplus_tests]} {
return 0
}
# Build ".c" source file with g++.
lappend options "c++"
}
if {[prepare_for_testing "failed to prepare" $exefile $srcfile $options]} {
return -1
}
# Turn off the pending breakpoint queries.
gdb_test_no_output "set breakpoint pending off"
# Turn off completion limiting
gdb_test_no_output "set max-completions unlimited"
if {![runto_main]} {
fail "can't run to main"
return 0
}
# Run to a location in the file.
set bp_location [gdb_get_line_number "set breakpoint here"]
gdb_test "break $srcfile:$bp_location" \
"Breakpoint.*at.* file .*$srcfile, line $bp_location\\." \
"breakpoint line number in file"
gdb_continue_to_breakpoint "$bp_location"
# Common error message format strings.
array set error_messages {
invalid_file "No source file named %s."
invalid_function "Function \"%s\" not defined."
invalid_var_or_func
"Undefined convenience variable or function \"%s\" not defined."
invalid_function_f "Function \"%s\" not defined in \"%s\"."
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"
}
# We intentionally do not use gdb_breakpoint for these tests.
# Break at 'linespec' and expect the message in ::error_messages
# indexed by msg_id with the associated args.
proc test_break {linespec msg_id args} {
global error_messages
gdb_test "break $linespec" [string_to_regexp \
[eval format \$error_messages($msg_id) \
$args]]
}
# Some commonly used whitespace tests around ':'.
set spaces [list \
":" \
": " \
" :" \
" : " \
" : " \
]
# A list of invalid offsets.
set invalid_offsets [list -100 +500 1000]
# Try some simple, invalid linespecs involving spaces.
foreach x $spaces {
test_break $x unexpected "colon"
}
# Test invalid filespecs starting with offset. This is done
# first so that default offsets are tested.
foreach x $invalid_offsets {
set offset $x
# Relative offsets are relative to line 16. Adjust
# expected offset from error message accordingly.
if {[string index $x 0] == "+" || [string index $x 0] == "-"} {
incr offset 24
}
test_break $x invalid_offset $offset
test_break "-line $x" invalid_offset $offset
}
# Test offsets with trailing tokens w/ and w/o spaces.
foreach x $spaces {
test_break "3$x" unexpected "colon"
test_break "+10$x" unexpected "colon"
test_break "-10$x" unexpected "colon"
}
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
}
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.
# It's OK to use the ".c" extension for the C++ test
# since the extension doesn't affect GDB's lookup.
set invalid_files [list "this_file_doesn't_exist.c" \
"this file has spaces.c" \
"\"file::colons.c\"" \
"'file::colons.c'" \
"\"this \"file\" has quotes.c\"" \
"'this \"file\" has quotes.c'" \
"'this 'file' has quotes.c'" \
"\"this 'file' has quotes.c\"" \
"\"spaces: and :colons.c\"" \
"'more: :spaces: :and colons::.c'" \
"C:/nonexist-with-windrive.c"]
foreach x $invalid_files {
# 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" \
"file::colons.c" \
"'file::colons.c'"] {
test_break "-source $x -line 3" invalid_file [string trim $x \"']
}
# Test that option lexing stops at whitespace boundaries, except
# when lexing function names, where we want to handle setting
# breakpoints on e.g., "int template_function<int>()".
test_break "-source this file has spaces.c -line 3" invalid_file "this"
test_break "-function ret_type tmpl_function" \
invalid_function "ret_type tmpl_function"
test_break "-source $srcfile -function ret_type tmpl_function" \
invalid_function_f "ret_type tmpl_function" $srcfile
test_break "-function main -label label whitespace" \
invalid_label "label" "main"
# Test unmatched quotes.
foreach x {"\"src-file.c'" "'src-file.c"} {
test_break "$x:3" unmatched_quote
}
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 {
test_break "$srcfile$x" unexpected "end of input"
test_break "$srcfile:main$x" unexpected "end of input"
}
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 {
test_break "main${x}there" invalid_label "there" "main"
if {[test_compiler_info {clang-*-*}]} {
setup_xfail clang/14500 *-*-*
}
test_break "main:here${x}" unexpected "end of input"
}
foreach x {"3" "+100" "-100" "foo"} {
test_break "main 3" invalid_function "main 3"
test_break "-function \"main $x\"" invalid_function "main $x"
if {$x == "foo"} {
test_break "main:here $x" unexpected_opt "string" $x
} else {
test_break "main:here $x" unexpected_opt "number" $x
}
test_break "-function main -label \"here $x\"" \
invalid_label "here $x" "main"
}
foreach x {"if" "task" "thread"} {
test_break $x invalid_function $x
}
test_break "'main.c'flubber" unexpected_opt "string" "flubber"
test_break "'main.c',21" invalid_function "main.c"
test_break "'main.c' " invalid_function "main.c"
test_break "'main.c'3" unexpected_opt "number" "3"
test_break "'main.c'+3" unexpected_opt "number" "+3"
# Test undefined convenience variables.
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
}
foreach_with_prefix lang {"C" "C++"} {
do_test ${lang}
}