diff options
author | shadchin <shadchin@yandex-team.com> | 2024-02-12 07:53:52 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@ydb.tech> | 2024-02-14 14:26:16 +0000 |
commit | 31f2a419764a8ba77c2a970cfc80056c6cd06756 (patch) | |
tree | c1995d239eba8571cefc640f6648e1d5dd4ce9e2 /contrib/tools/python3/src/Lib/pdb.py | |
parent | fe2ef02b38d9c85d80060963b265a1df9f38c3bb (diff) | |
download | ydb-31f2a419764a8ba77c2a970cfc80056c6cd06756.tar.gz |
Update Python from 3.11.8 to 3.12.2
Diffstat (limited to 'contrib/tools/python3/src/Lib/pdb.py')
-rwxr-xr-x | contrib/tools/python3/src/Lib/pdb.py | 235 |
1 files changed, 201 insertions, 34 deletions
diff --git a/contrib/tools/python3/src/Lib/pdb.py b/contrib/tools/python3/src/Lib/pdb.py index 4a4a0b9d90..a838a26b03 100755 --- a/contrib/tools/python3/src/Lib/pdb.py +++ b/contrib/tools/python3/src/Lib/pdb.py @@ -76,6 +76,7 @@ import bdb import dis import code import glob +import token import pprint import signal import inspect @@ -276,6 +277,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.lineno = None self.stack = [] self.curindex = 0 + if hasattr(self, 'curframe') and self.curframe: + self.curframe.f_globals.pop('__pdb_convenience_variables', None) self.curframe = None self.tb_lineno.clear() @@ -294,6 +297,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): # locals whenever the .f_locals accessor is called, so we # cache it here to ensure that modifications are not overwritten. self.curframe_locals = self.curframe.f_locals + self.set_convenience_variable(self.curframe, '_frame', self.curframe) return self.execRcLines() # Can be executed earlier than 'setup' if desired @@ -365,6 +369,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): if self._wait_for_mainpyfile: return frame.f_locals['__return__'] = return_value + self.set_convenience_variable(frame, '_retval', return_value) self.message('--Return--') self.interaction(frame, None) @@ -375,6 +380,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): return exc_type, exc_value, exc_traceback = exc_info frame.f_locals['__exception__'] = exc_type, exc_value + self.set_convenience_variable(frame, '_exception', exc_value) # An 'Internal StopIteration' exception is an exception debug event # issued by the interpreter when handling a subgenerator run with @@ -383,8 +389,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): # stop when the debuggee is returning from such generators. prefix = 'Internal ' if (not exc_traceback and exc_type is StopIteration) else '' - self.message('%s%s' % (prefix, - traceback.format_exception_only(exc_type, exc_value)[-1].strip())) + self.message('%s%s' % (prefix, self._format_exc(exc_value))) self.interaction(frame, exc_traceback) # General interaction function @@ -401,6 +406,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.message('--KeyboardInterrupt--') # Called before loop, handles display expressions + # Set up convenience variable containers def preloop(self): displaying = self.displaying.get(self.curframe) if displaying: @@ -442,7 +448,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.message(repr(obj)) def default(self, line): - if line[:1] == '!': line = line[1:] + if line[:1] == '!': line = line[1:].strip() locals = self.curframe_locals globals = self.curframe.f_globals try: @@ -462,6 +468,39 @@ class Pdb(bdb.Bdb, cmd.Cmd): except: self._error_exc() + def _replace_convenience_variables(self, line): + """Replace the convenience variables in 'line' with their values. + e.g. $foo is replaced by __pdb_convenience_variables["foo"]. + Note: such pattern in string literals will be skipped""" + + if "$" not in line: + return line + + dollar_start = dollar_end = -1 + replace_variables = [] + try: + for t in tokenize.generate_tokens(io.StringIO(line).readline): + token_type, token_string, start, end, _ = t + if token_type == token.OP and token_string == '$': + dollar_start, dollar_end = start, end + elif start == dollar_end and token_type == token.NAME: + # line is a one-line command so we only care about column + replace_variables.append((dollar_start[1], end[1], token_string)) + except tokenize.TokenError: + return line + + if not replace_variables: + return line + + last_end = 0 + line_pieces = [] + for start, end, name in replace_variables: + line_pieces.append(line[last_end:start] + f'__pdb_convenience_variables["{name}"]') + last_end = end + line_pieces.append(line[last_end:]) + + return ''.join(line_pieces) + def precmd(self, line): """Handle alias expansion and ';;' separator.""" if not line.strip(): @@ -485,6 +524,10 @@ class Pdb(bdb.Bdb, cmd.Cmd): next = line[marker+2:].lstrip() self.cmdqueue.append(next) line = line[:marker].rstrip() + + # Replace all the convenience variables + line = self._replace_convenience_variables(line) + return line def onecmd(self, line): @@ -535,6 +578,13 @@ class Pdb(bdb.Bdb, cmd.Cmd): def error(self, msg): print('***', msg, file=self.stdout) + # convenience variables + + def set_convenience_variable(self, frame, name, value): + if '__pdb_convenience_variables' not in frame.f_globals: + frame.f_globals['__pdb_convenience_variables'] = {} + frame.f_globals['__pdb_convenience_variables'][name] = value + # Generic completion functions. Individual complete_foo methods can be # assigned below to one of these functions. @@ -594,7 +644,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): # Return true to exit from the command loop def do_commands(self, arg): - """commands [bpnumber] + """(Pdb) commands [bpnumber] (com) ... (com) end (Pdb) @@ -680,6 +730,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_break(self, arg, temporary = 0): """b(reak) [ ([filename:]lineno | function) [, condition] ] + Without argument, list all breaks. With a line number argument, set a break at this line in the @@ -709,6 +760,9 @@ class Pdb(bdb.Bdb, cmd.Cmd): if comma > 0: # parse stuff after comma: "condition" cond = arg[comma+1:].lstrip() + if err := self._compile_error_message(cond): + self.error('Invalid condition %s: %r' % (cond, err)) + return arg = arg[:comma].rstrip() # parse stuff before comma: [filename:]lineno | function colon = arg.rfind(':') @@ -785,6 +839,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_tbreak(self, arg): """tbreak [ ([filename:]lineno | function) [, condition] ] + Same arguments as break, but sets a temporary breakpoint: it is automatically deleted when first hit. """ @@ -849,6 +904,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_enable(self, arg): """enable bpnumber [bpnumber ...] + Enables the breakpoints given as a space separated list of breakpoint numbers. """ @@ -866,6 +922,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_disable(self, arg): """disable bpnumber [bpnumber ...] + Disables the breakpoints given as a space separated list of breakpoint numbers. Disabling a breakpoint means it cannot cause the program to stop execution, but unlike clearing a @@ -886,6 +943,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_condition(self, arg): """condition bpnumber [condition] + Set a new condition for the breakpoint, an expression which must evaluate to true before the breakpoint is honored. If condition is absent, any existing condition is removed; i.e., @@ -894,6 +952,9 @@ class Pdb(bdb.Bdb, cmd.Cmd): args = arg.split(' ', 1) try: cond = args[1] + if err := self._compile_error_message(cond): + self.error('Invalid condition %s: %r' % (cond, err)) + return except IndexError: cond = None try: @@ -913,6 +974,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_ignore(self, arg): """ignore bpnumber [count] + Set the ignore count for the given breakpoint number. If count is omitted, the ignore count is set to 0. A breakpoint becomes active when the ignore count is zero. When non-zero, @@ -947,7 +1009,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): complete_ignore = _complete_bpnumber def do_clear(self, arg): - """cl(ear) filename:lineno\ncl(ear) [bpnumber [bpnumber...]] + """cl(ear) [filename:lineno | bpnumber ...] + With a space separated list of breakpoint numbers, clear those breakpoints. Without argument, clear all breaks (but first ask confirmation). With a filename:lineno argument, @@ -999,6 +1062,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_where(self, arg): """w(here) + Print a stack trace, with the most recent frame at the bottom. An arrow indicates the "current frame", which determines the context of most commands. 'bt' is an alias for this command. @@ -1012,11 +1076,13 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.curindex = number self.curframe = self.stack[self.curindex][0] self.curframe_locals = self.curframe.f_locals + self.set_convenience_variable(self.curframe, '_frame', self.curframe) self.print_stack_entry(self.stack[self.curindex]) self.lineno = None def do_up(self, arg): """u(p) [count] + Move the current frame count (default one) levels up in the stack trace (to an older frame). """ @@ -1037,6 +1103,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_down(self, arg): """d(own) [count] + Move the current frame count (default one) levels down in the stack trace (to a newer frame). """ @@ -1057,6 +1124,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_until(self, arg): """unt(il) [lineno] + Without argument, continue execution until the line with a number greater than the current one is reached. With a line number, continue execution until a line with a number greater @@ -1081,6 +1149,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_step(self, arg): """s(tep) + Execute the current line, stop at the first possible occasion (either in a function that is called or in the current function). @@ -1091,6 +1160,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_next(self, arg): """n(ext) + Continue execution until the next line in the current function is reached or it returns. """ @@ -1100,6 +1170,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_run(self, arg): """run [args...] + Restart the debugged python program. If a string is supplied it is split with "shlex", and the result is used as the new sys.argv. History, breakpoints, actions and debugger options @@ -1121,6 +1192,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_return(self, arg): """r(eturn) + Continue execution until the current function returns. """ self.set_return(self.curframe) @@ -1129,6 +1201,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_continue(self, arg): """c(ont(inue)) + Continue execution, only stop when a breakpoint is encountered. """ if not self.nosigint: @@ -1147,6 +1220,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_jump(self, arg): """j(ump) lineno + Set the next line that will be executed. Only available in the bottom-most frame. This lets you jump back and execute code again, or jump forward to skip code that you don't want @@ -1176,6 +1250,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_debug(self, arg): """debug code + Enter a recursive debugger that steps through the code argument (which is an arbitrary expression or statement to be executed in the current environment). @@ -1197,7 +1272,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): complete_debug = _complete_expression def do_quit(self, arg): - """q(uit)\nexit + """q(uit) | exit + Quit from the debugger. The program being executed is aborted. """ self._user_requested_quit = True @@ -1209,6 +1285,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_EOF(self, arg): """EOF + Handles the receipt of EOF as a command. """ self.message('') @@ -1218,6 +1295,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_args(self, arg): """a(rgs) + Print the argument list of the current function. """ co = self.curframe.f_code @@ -1235,6 +1313,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_retval(self, arg): """retval + Print the return value for the last return of a function. """ if '__return__' in self.curframe_locals: @@ -1256,14 +1335,12 @@ class Pdb(bdb.Bdb, cmd.Cmd): return eval(arg, self.curframe.f_globals, self.curframe_locals) else: return eval(arg, frame.f_globals, frame.f_locals) - except: - exc_info = sys.exc_info()[:2] - err = traceback.format_exception_only(*exc_info)[-1].strip() - return _rstr('** raised %s **' % err) + except BaseException as exc: + return _rstr('** raised %s **' % self._format_exc(exc)) def _error_exc(self): - exc_info = sys.exc_info()[:2] - self.error(traceback.format_exception_only(*exc_info)[-1].strip()) + exc = sys.exception() + self.error(self._format_exc(exc)) def _msg_val_func(self, arg, func): try: @@ -1283,12 +1360,14 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_p(self, arg): """p expression + Print the value of the expression. """ self._msg_val_func(arg, repr) def do_pp(self, arg): """pp expression + Pretty-print the value of the expression. """ self._msg_val_func(arg, pprint.pformat) @@ -1298,7 +1377,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): complete_pp = _complete_expression def do_list(self, arg): - """l(ist) [first [,last] | .] + """l(ist) [first[, last] | .] List source code for the current file. Without arguments, list 11 lines around the current line or continue the previous @@ -1355,7 +1434,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): do_l = do_list def do_longlist(self, arg): - """longlist | ll + """ll | longlist + List the whole source code for the current function or frame. """ filename = self.curframe.f_code.co_filename @@ -1370,6 +1450,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_source(self, arg): """source expression + Try to get source code for the given object and display it. """ try: @@ -1407,7 +1488,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): self.message(s + '\t' + line.rstrip()) def do_whatis(self, arg): - """whatis arg + """whatis expression + Print the type of the argument. """ try: @@ -1450,13 +1532,19 @@ class Pdb(bdb.Bdb, cmd.Cmd): Without expression, list all display expressions for the current frame. """ if not arg: - self.message('Currently displaying:') - for key, val in self.displaying.get(self.curframe, {}).items(): - self.message('%s: %s' % (key, self._safe_repr(val, key))) + if self.displaying: + self.message('Currently displaying:') + for key, val in self.displaying.get(self.curframe, {}).items(): + self.message('%s: %s' % (key, self._safe_repr(val, key))) + else: + self.message('No expression is being displayed') else: - val = self._getval_except(arg) - self.displaying.setdefault(self.curframe, {})[arg] = val - self.message('display %s: %s' % (arg, self._safe_repr(val, arg))) + if err := self._compile_error_message(arg): + self.error('Unable to display %s: %r' % (arg, err)) + else: + val = self._getval_except(arg) + self.displaying.setdefault(self.curframe, {})[arg] = val + self.message('display %s: %s' % (arg, self._safe_repr(val, arg))) complete_display = _complete_expression @@ -1489,7 +1577,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): code.interact("*interactive*", local=ns) def do_alias(self, arg): - """alias [name [command [parameter parameter ...] ]] + """alias [name [command]] + Create an alias called 'name' that executes 'command'. The command must *not* be enclosed in quotes. Replaceable parameters can be indicated by %1, %2, and so on, while %* is @@ -1528,6 +1617,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_unalias(self, arg): """unalias name + Delete the specified alias. """ args = arg.split() @@ -1570,6 +1660,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): def do_help(self, arg): """h(elp) + Without argument, print the list of available commands. With a command name as argument, print help about that command. "help pdb" shows the full pdb documentation. @@ -1593,17 +1684,21 @@ class Pdb(bdb.Bdb, cmd.Cmd): if command.__doc__ is None: self.error('No help for %r; __doc__ string missing' % arg) return - self.message(command.__doc__.rstrip()) + self.message(self._help_message_from_doc(command.__doc__)) do_h = do_help def help_exec(self): """(!) statement + Execute the (one-line) statement in the context of the current stack frame. The exclamation point can be omitted unless the - first word of the statement resembles a debugger command. To - assign to a global variable you must always prefix the command - with a 'global' command, e.g.: + first word of the statement resembles a debugger command, e.g.: + (Pdb) ! n=42 + (Pdb) + + To assign to a global variable you must always prefix the command with + a 'global' command, e.g.: (Pdb) global list_options; list_options = ['-l'] (Pdb) """ @@ -1661,6 +1756,14 @@ class Pdb(bdb.Bdb, cmd.Cmd): def _format_exc(self, exc: BaseException): return traceback.format_exception_only(exc)[-1].strip() + def _compile_error_message(self, expr): + """Return the error message as string if compiling `expr` fails.""" + try: + compile(expr, "<stdin>", "eval") + except SyntaxError as exc: + return _rstr(self._format_exc(exc)) + return "" + def _getsourcelines(self, obj): # GH-103319 # inspect.getsourcelines() returns lineno = 0 for @@ -1671,6 +1774,26 @@ class Pdb(bdb.Bdb, cmd.Cmd): lineno = max(1, lineno) return lines, lineno + def _help_message_from_doc(self, doc): + lines = [line.strip() for line in doc.rstrip().splitlines()] + if not lines: + return "No help message found." + if "" in lines: + usage_end = lines.index("") + else: + usage_end = 1 + formatted = [] + indent = " " * len(self.prompt) + for i, line in enumerate(lines): + if i == 0: + prefix = "Usage: " + elif i < usage_end: + prefix = " " + else: + prefix = "" + formatted.append(indent + prefix + line) + return "\n".join(formatted) + # Collect all command help into docstring, if not run with -OO if __doc__ is not None: @@ -1693,9 +1816,27 @@ if __doc__ is not None: # Simplified interface def run(statement, globals=None, locals=None): + """Execute the *statement* (given as a string or a code object) + under debugger control. + + The debugger prompt appears before any code is executed; you can set + breakpoints and type continue, or you can step through the statement + using step or next. + + The optional *globals* and *locals* arguments specify the + environment in which the code is executed; by default the + dictionary of the module __main__ is used (see the explanation of + the built-in exec() or eval() functions.). + """ Pdb().run(statement, globals, locals) def runeval(expression, globals=None, locals=None): + """Evaluate the *expression* (given as a string or a code object) + under debugger control. + + When runeval() returns, it returns the value of the expression. + Otherwise this function is similar to run(). + """ return Pdb().runeval(expression, globals, locals) def runctx(statement, globals, locals): @@ -1703,9 +1844,23 @@ def runctx(statement, globals, locals): run(statement, globals, locals) def runcall(*args, **kwds): + """Call the function (a function or method object, not a string) + with the given arguments. + + When runcall() returns, it returns whatever the function call + returned. The debugger prompt appears as soon as the function is + entered. + """ return Pdb().runcall(*args, **kwds) def set_trace(*, header=None): + """Enter the debugger at the calling stack frame. + + This is useful to hard-code a breakpoint at a given point in a + program, even if the code is not otherwise being debugged (e.g. when + an assertion fails). If given, *header* is printed to the console + just before debugging begins. + """ pdb = Pdb() if header is not None: pdb.message(header) @@ -1714,11 +1869,18 @@ def set_trace(*, header=None): # Post-Mortem interface def post_mortem(t=None): + """Enter post-mortem debugging of the given *traceback* object. + + If no traceback is given, it uses the one of the exception that is + currently being handled (an exception must be being handled if the + default is to be used). + """ # handling the default if t is None: - # sys.exc_info() returns (type, value, traceback) if an exception is - # being handled, otherwise it returns None - t = sys.exc_info()[2] + exc = sys.exception() + if exc is not None: + t = exc.__traceback__ + if t is None: raise ValueError("A valid traceback must be passed if no " "exception is being handled") @@ -1728,7 +1890,12 @@ def post_mortem(t=None): p.interaction(None, t) def pm(): - post_mortem(sys.last_traceback) + """Enter post-mortem debugging of the traceback found in sys.last_traceback.""" + if hasattr(sys, 'last_exc'): + tb = sys.last_exc.__traceback__ + else: + tb = sys.last_traceback + post_mortem(tb) # Main program for testing @@ -1797,18 +1964,18 @@ def main(): except Restart: print("Restarting", target, "with arguments:") print("\t" + " ".join(sys.argv[1:])) - except SystemExit: + except SystemExit as e: # In most cases SystemExit does not warrant a post-mortem session. print("The program exited via sys.exit(). Exit status:", end=' ') - print(sys.exc_info()[1]) + print(e) except SyntaxError: traceback.print_exc() sys.exit(1) - except: + except BaseException as e: traceback.print_exc() print("Uncaught exception. Entering post mortem debugging") print("Running 'cont' or 'step' will restart the program") - t = sys.exc_info()[2] + t = e.__traceback__ pdb.interaction(None, t) print("Post mortem debugger finished. The " + target + " will be restarted") |