forked from Imagelibrary/binutils-gdb
This commit makes the gdb.Command.complete methods more verbose when it comes to error handling. Previous to this commit if any commands implemented in Python implemented the complete method, and if there were any errors encountered when calling that complete method, then GDB would silently hide the error and continue as if there were no completions. This makes is difficult to debug any errors encountered when writing completion methods, and encourages the idea that Python extensions can be broken, and GDB will just silently work around them. I don't think this is a good idea. GDB should encourage extensions to be written correctly, and robustly, and one way in which GDB can (I think) support this, is by pointing out when an extension goes wrong. In this commit I've gone through the Python command completion code, and added calls to gdbpy_print_stack() or gdbpy_print_stack_or_quit() in places where we were either clearing the Python error, or, in some cases, just not handling the error at all. One thing I have not changed is in cmdpy_completer (py-cmd.c) where we process the list of completions returned from the Command.complete method; this routine includes a call to gdbpy_is_string to check a possible completion is a string, if not the completion is ignored. I was tempted to remove this check, attempt to complete each result to a string, and display an error if the conversion fails. After all, returning anything but a string is surely a mistake by the extension author. However, the docs clearly say that only strings within the returned list will be considered as completions. Anything else is ignored. As such, and to avoid (what I think is pretty unlikely) breakage of existing code, I've retained the gdbpy_is_string check. After the gdbpy_is_string check we call python_string_to_host_string, if this call fails then I do now print the error, where before we ignored the error. I think this is OK; if GDB thinks something is a string, but still can't convert it to a string, then I think it's OK to display the error in that case. Another case which I was a little unsure about was in cmdpy_completer_helper, and the call to PyObject_CallMethodObjArgs, which is when we actually call Command.complete. Previously, if this call resulted in an exception then we would ignore this and just pretend there were no completions. Of all the changes, this is possibly the one with the biggest potential for breaking existing scripts, but also, is, I think, the most useful change. If the user code is wrong in some way, such that an exception is raised, then previously the user would have no obvious feedback about this breakage. Now GDB will print the exception for them, making it, I think, much easier to debug their extension. But, if there is user code in the wild that relies on raising an exception as a means to indicate there are no completions .... well, that code is going to break after this commit. I think we can live with this though, the exceptions means no completions thing was never documented behaviour. I also added a new error() call if the PyObject_CallMethodObjArgs call raises an exception. This causes the completion mechanism within GDB to stop. Within GDB the completion code is called twice, the first time to compute the work break characters, and then a second time to compute the actual completions. If PyObject_CallMethodObjArgs raises an exception when computing the word break character, and we print it by calling gdbpy_print_stack_or_quit(), but then carry on as if PyObject_CallMethodObjArgs had returns no completions, GDB will call the Python completion code again, which results in another call to PyObject_CallMethodObjArgs, which might raise the same exception again. This results in the Python exception being printed twice. By throwing a C++ exception after the failed PyObject_CallMethodObjArgs call, the completion mechanism is aborted, and no completions are offered. But importantly, the Python exception is only printed once. I think this gives a much better user experience. I've added some tests to cover this case, as I think this is the most likely case that a user will run into. Approved-By: Tom Tromey <tom@tromey.com>
257 lines
6.5 KiB
Python
257 lines
6.5 KiB
Python
# Copyright (C) 2014-2023 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/>.
|
|
|
|
# This testcase tests PR python/16699
|
|
|
|
import gdb
|
|
|
|
|
|
class CompleteFileInit(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(
|
|
self, "completefileinit", gdb.COMMAND_USER, gdb.COMPLETE_FILENAME
|
|
)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
|
|
class CompleteFileNone(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completefilenone", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return None
|
|
|
|
|
|
class CompleteFileMethod(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completefilemethod", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return gdb.COMPLETE_FILENAME
|
|
|
|
|
|
class CompleteFileCommandCond(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completefilecommandcond", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
# This is a test made to know if the command
|
|
# completion still works fine. When the user asks to
|
|
# complete something like "completefilecommandcond
|
|
# /path/to/py-completion-t", it should not complete to
|
|
# "/path/to/py-completion-test/", but instead just
|
|
# wait for input.
|
|
if "py-completion-t" in text:
|
|
return gdb.COMPLETE_COMMAND
|
|
else:
|
|
return gdb.COMPLETE_FILENAME
|
|
|
|
|
|
class CompleteLimit1(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completelimit1", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return ["cl11", "cl12", "cl13"]
|
|
|
|
|
|
class CompleteLimit2(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completelimit2", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return [
|
|
"cl21",
|
|
"cl23",
|
|
"cl25",
|
|
"cl27",
|
|
"cl29",
|
|
"cl22",
|
|
"cl24",
|
|
"cl26",
|
|
"cl28",
|
|
"cl210",
|
|
]
|
|
|
|
|
|
class CompleteLimit3(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completelimit3", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return [
|
|
"cl31",
|
|
"cl33",
|
|
"cl35",
|
|
"cl37",
|
|
"cl39",
|
|
"cl32",
|
|
"cl34",
|
|
"cl36",
|
|
"cl38",
|
|
"cl310",
|
|
]
|
|
|
|
|
|
class CompleteLimit4(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completelimit4", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return [
|
|
"cl41",
|
|
"cl43",
|
|
"cl45",
|
|
"cl47",
|
|
"cl49",
|
|
"cl42",
|
|
"cl44",
|
|
"cl46",
|
|
"cl48",
|
|
"cl410",
|
|
]
|
|
|
|
|
|
class CompleteLimit5(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completelimit5", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return [
|
|
"cl51",
|
|
"cl53",
|
|
"cl55",
|
|
"cl57",
|
|
"cl59",
|
|
"cl52",
|
|
"cl54",
|
|
"cl56",
|
|
"cl58",
|
|
"cl510",
|
|
]
|
|
|
|
|
|
class CompleteLimit6(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completelimit6", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return [
|
|
"cl61",
|
|
"cl63",
|
|
"cl65",
|
|
"cl67",
|
|
"cl69",
|
|
"cl62",
|
|
"cl64",
|
|
"cl66",
|
|
"cl68",
|
|
"cl610",
|
|
]
|
|
|
|
|
|
class CompleteLimit7(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "completelimit7", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
return [
|
|
"cl71",
|
|
"cl73",
|
|
"cl75",
|
|
"cl77",
|
|
"cl79",
|
|
"cl72",
|
|
"cl74",
|
|
"cl76",
|
|
"cl78",
|
|
"cl710",
|
|
]
|
|
|
|
|
|
class CompleteBrkCharException(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "complete_brkchar_exception", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
if word is None:
|
|
raise gdb.GdbError("brkchars exception")
|
|
else:
|
|
raise gdb.GdbError("completion exception")
|
|
|
|
|
|
class CompleteRaiseException(gdb.Command):
|
|
def __init__(self):
|
|
gdb.Command.__init__(self, "complete_raise_exception", gdb.COMMAND_USER)
|
|
|
|
def invoke(self, argument, from_tty):
|
|
raise gdb.GdbError("not implemented")
|
|
|
|
def complete(self, text, word):
|
|
if word is None:
|
|
return []
|
|
else:
|
|
raise gdb.GdbError("completion exception")
|
|
|
|
|
|
CompleteFileInit()
|
|
CompleteFileNone()
|
|
CompleteFileMethod()
|
|
CompleteFileCommandCond()
|
|
CompleteLimit1()
|
|
CompleteLimit2()
|
|
CompleteLimit3()
|
|
CompleteLimit4()
|
|
CompleteLimit5()
|
|
CompleteLimit6()
|
|
CompleteLimit7()
|
|
CompleteBrkCharException()
|
|
CompleteRaiseException()
|