codekingpro/portable-devtools
114k
1"""2The Python Debugger Pdb3=======================4 5To use the debugger in its simplest form:6 7 >>> import pdb8 >>> pdb.run('<a statement>')9 10The debugger's prompt is '(Pdb) '. This will stop in the first11function call in <a statement>.12 13Alternatively, if a statement terminated with an unhandled exception,14you can use pdb's post-mortem facility to inspect the contents of the15traceback:16 17 >>> <a statement>18 <exception traceback>19 >>> import pdb20 >>> pdb.pm()21 22The commands recognized by the debugger are listed in the next23section. Most can be abbreviated as indicated; e.g., h(elp) means24that 'help' can be typed as 'h' or 'help' (but not as 'he' or 'hel',25nor as 'H' or 'Help' or 'HELP'). Optional arguments are enclosed in26square brackets. Alternatives in the command syntax are separated27by a vertical bar (|).28 29A blank line repeats the previous command literally, except for30'list', where it lists the next 11 lines.31 32Commands that the debugger doesn't recognize are assumed to be Python33statements and are executed in the context of the program being34debugged. Python statements can also be prefixed with an exclamation35point ('!'). This is a powerful way to inspect the program being36debugged; it is even possible to change variables or call functions.37When an exception occurs in such a statement, the exception name is38printed but the debugger's state is not changed.39 40The debugger supports aliases, which can save typing. And aliases can41have parameters (see the alias help entry) which allows one a certain42level of adaptability to the context under examination.43 44Multiple commands may be entered on a single line, separated by the45pair ';;'. No intelligence is applied to separating the commands; the46input is split at the first ';;', even if it is in the middle of a47quoted string.48 49If a file ".pdbrc" exists in your home directory or in the current50directory, it is read in and executed as if it had been typed at the51debugger prompt. This is particularly useful for aliases. If both52files exist, the one in the home directory is read first and aliases53defined there can be overridden by the local file. This behavior can be54disabled by passing the "readrc=False" argument to the Pdb constructor.55 56Aside from aliases, the debugger is not directly programmable; but it57is implemented as a class from which you can derive your own debugger58class, which you can make as fancy as you like.59 60 61Debugger commands62=================63 64"""65# NOTE: the actual command documentation is collected from docstrings of the66# commands and is appended to __doc__ after the class has been defined.67 68import os69import io70import re71import sys72import cmd73import bdb74import dis75import code76import glob77import json78import stat79import token80import types81import atexit82import codeop83import pprint84import signal85import socket86import typing87import asyncio88import inspect89import weakref90import builtins91import tempfile92import textwrap93import tokenize94import itertools95import traceback96import linecache97import selectors98import threading99import _colorize100import _pyrepl.utils101 102from contextlib import ExitStack, closing, contextmanager103from types import CodeType104from warnings import deprecated105 106 107class Restart(Exception):108 """Causes a debugger to be restarted for the debugged python program."""109 pass110 111__all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace",112 "post_mortem", "set_default_backend", "get_default_backend", "help"]113 114 115def find_first_executable_line(code):116 """ Try to find the first executable line of the code object.117 118 Equivalently, find the line number of the instruction that's119 after RESUME120 121 Return code.co_firstlineno if no executable line is found.122 """123 prev = None124 for instr in dis.get_instructions(code):125 if prev is not None and prev.opname == 'RESUME':126 if instr.positions.lineno is not None:127 return instr.positions.lineno128 return code.co_firstlineno129 prev = instr130 return code.co_firstlineno131 132def find_function(funcname, filename):133 cre = re.compile(r'def\s+%s(\s*\[.+\])?\s*[(]' % re.escape(funcname))134 try:135 fp = tokenize.open(filename)136 except OSError:137 lines = linecache.getlines(filename)138 if not lines:139 return None140 fp = io.StringIO(''.join(lines))141 funcdef = ""142 funcstart = 0143 # consumer of this info expects the first line to be 1144 with fp:145 for lineno, line in enumerate(fp, start=1):146 if cre.match(line):147 funcstart, funcdef = lineno, line148 elif funcdef:149 funcdef += line150 151 if funcdef:152 try:153 code = compile(funcdef, filename, 'exec')154 except SyntaxError:155 continue156 # We should always be able to find the code object here157 funccode = next(c for c in code.co_consts if158 isinstance(c, CodeType) and c.co_name == funcname)159 lineno_offset = find_first_executable_line(funccode)160 return funcname, filename, funcstart + lineno_offset - 1161 return None162 163def lasti2lineno(code, lasti):164 linestarts = list(dis.findlinestarts(code))165 linestarts.reverse()166 for i, lineno in linestarts:167 if lasti >= i:168 return lineno169 return 0170 171 172class _rstr(str):173 """String that doesn't quote its repr."""174 def __repr__(self):175 return self176 177 178class _ExecutableTarget:179 filename: str180 code: CodeType | str181 namespace: dict182 183 184class _ScriptTarget(_ExecutableTarget):185 def __init__(self, target):186 self._check(target)187 self._target = self._safe_realpath(target)188 189 # If PYTHONSAFEPATH (-P) is not set, sys.path[0] is the directory190 # of pdb, and we should replace it with the directory of the script191 if not sys.flags.safe_path:192 sys.path[0] = os.path.dirname(self._target)193 194 @staticmethod195 def _check(target):196 """197 Check that target is plausibly a script.198 """199 if not os.path.exists(target):200 print(f'Error: {target} does not exist')201 sys.exit(1)202 if os.path.isdir(target):203 print(f'Error: {target} is a directory')204 sys.exit(1)205 206 @staticmethod207 def _safe_realpath(path):208 """209 Return the canonical path (realpath) if it is accessible from the userspace.210 Otherwise (for example, if the path is a symlink to an anonymous pipe),211 return the original path.212 213 See GH-142315.214 """215 realpath = os.path.realpath(path)216 return realpath if os.path.exists(realpath) else path217 218 def __repr__(self):219 return self._target220 221 @property222 def filename(self):223 return self._target224 225 @property226 def code(self):227 # Open the file each time because the file may be modified228 with io.open_code(self._target) as fp:229 return f"exec(compile({fp.read()!r}, {self._target!r}, 'exec'))"230 231 @property232 def namespace(self):233 return dict(234 __name__='__main__',235 __file__=self._target,236 __builtins__=__builtins__,237 __spec__=None,238 )239 240 241class _ModuleTarget(_ExecutableTarget):242 def __init__(self, target):243 self._target = target244 245 import runpy246 try:247 _, self._spec, self._code = runpy._get_module_details(self._target)248 except ImportError as e:249 print(f"ImportError: {e}")250 sys.exit(1)251 except Exception:252 traceback.print_exc()253 sys.exit(1)254 255 def __repr__(self):256 return self._target257 258 @property259 def filename(self):260 return self._code.co_filename261 262 @property263 def code(self):264 return self._code265 266 @property267 def namespace(self):268 return dict(269 __name__='__main__',270 __file__=os.path.normcase(os.path.abspath(self.filename)),271 __package__=self._spec.parent,272 __loader__=self._spec.loader,273 __spec__=self._spec,274 __builtins__=__builtins__,275 )276 277 278class _ZipTarget(_ExecutableTarget):279 def __init__(self, target):280 import runpy281 282 self._target = os.path.realpath(target)283 sys.path.insert(0, self._target)284 try:285 _, self._spec, self._code = runpy._get_main_module_details()286 except ImportError as e:287 print(f"ImportError: {e}")288 sys.exit(1)289 except Exception:290 traceback.print_exc()291 sys.exit(1)292 293 def __repr__(self):294 return self._target295 296 @property297 def filename(self):298 return self._code.co_filename299 300 @property301 def code(self):302 return self._code303 304 @property305 def namespace(self):306 return dict(307 __name__='__main__',308 __file__=os.path.normcase(os.path.abspath(self.filename)),309 __package__=self._spec.parent,310 __loader__=self._spec.loader,311 __spec__=self._spec,312 __builtins__=__builtins__,313 )314 315 316class _PdbInteractiveConsole(code.InteractiveConsole):317 def __init__(self, ns, message):318 self._message = message319 super().__init__(locals=ns, local_exit=True)320 321 def write(self, data):322 self._message(data, end='')323 324 325# Interaction prompt line will separate file and call info from code326# text using value of line_prefix string. A newline and arrow may327# be to your liking. You can set it once pdb is imported using the328# command "pdb.line_prefix = '\n% '".329# line_prefix = ': ' # Use this to get the old situation back330line_prefix = '\n-> ' # Probably a better default331 332 333# The default backend to use for Pdb instances if not specified334# Should be either 'settrace' or 'monitoring'335_default_backend = 'settrace'336 337 338def set_default_backend(backend):339 """Set the default backend to use for Pdb instances."""340 global _default_backend341 if backend not in ('settrace', 'monitoring'):342 raise ValueError("Invalid backend: %s" % backend)343 _default_backend = backend344 345 346def get_default_backend():347 """Get the default backend to use for Pdb instances."""348 return _default_backend349 350 351class Pdb(bdb.Bdb, cmd.Cmd):352 _previous_sigint_handler = None353 354 # Limit the maximum depth of chained exceptions, we should be handling cycles,355 # but in case there are recursions, we stop at 999.356 MAX_CHAINED_EXCEPTION_DEPTH = 999357 358 _file_mtime_table = {}359 360 _last_pdb_instance = None361 362 def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,363 nosigint=False, readrc=True, mode=None, backend=None, colorize=False):364 bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else get_default_backend())365 cmd.Cmd.__init__(self, completekey, stdin, stdout)366 sys.audit("pdb.Pdb")367 if stdout:368 self.use_rawinput = 0369 self.prompt = '(Pdb) '370 self.aliases = {}371 self.displaying = {}372 self.mainpyfile = ''373 self._wait_for_mainpyfile = False374 self.tb_lineno = {}375 self.mode = mode376 self.colorize = colorize and _colorize.can_colorize(file=stdout or sys.stdout)377 # Try to load readline if it exists378 try:379 import readline380 # remove some common file name delimiters381 readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?')382 except ImportError:383 pass384 385 self.allow_kbdint = False386 self.nosigint = nosigint387 # Consider these characters as part of the command so when the users type388 # c.a or c['a'], it won't be recognized as a c(ontinue) command389 self.identchars = cmd.Cmd.identchars + '=.[](),"\'+-*/%@&|<>~^'390 391 # Read ~/.pdbrc and ./.pdbrc392 self.rcLines = []393 if readrc:394 try:395 with open(os.path.expanduser('~/.pdbrc'), encoding='utf-8') as rcFile:396 self.rcLines.extend(rcFile)397 except OSError:398 pass399 try:400 with open(".pdbrc", encoding='utf-8') as rcFile:401 self.rcLines.extend(rcFile)402 except OSError:403 pass404 405 self.commands = {} # associates a command list to breakpoint numbers406 self.commands_defining = False # True while in the process of defining407 # a command list408 self.commands_bnum = None # The breakpoint number for which we are409 # defining a list410 411 self.async_shim_frame = None412 self.async_awaitable = None413 414 self._chained_exceptions = tuple()415 self._chained_exception_index = 0416 417 self._current_task = None418 419 def set_trace(self, frame=None, *, commands=None):420 Pdb._last_pdb_instance = self421 if frame is None:422 frame = sys._getframe().f_back423 424 if commands is not None:425 self.rcLines.extend(commands)426 427 super().set_trace(frame)428 429 async def set_trace_async(self, frame=None, *, commands=None):430 if self.async_awaitable is not None:431 # We are already in a set_trace_async call, do not mess with it432 return433 434 if frame is None:435 frame = sys._getframe().f_back436 437 # We need set_trace to set up the basics, however, this will call438 # set_stepinstr() will we need to compensate for, because we don't439 # want to trigger on calls440 self.set_trace(frame, commands=commands)441 # Changing the stopframe will disable trace dispatch on calls442 self.stopframe = frame443 # We need to stop tracing because we don't have the privilege to avoid444 # triggering tracing functions as normal, as we are not already in445 # tracing functions446 self.stop_trace()447 448 self.async_shim_frame = sys._getframe()449 self.async_awaitable = None450 451 while True:452 self.async_awaitable = None453 # Simulate a trace event454 # This should bring up pdb and make pdb believe it's debugging the455 # caller frame456 self.trace_dispatch(frame, "opcode", None)457 if self.async_awaitable is not None:458 try:459 if self.breaks:460 with self.set_enterframe(frame):461 # set_continue requires enterframe to work462 self.set_continue()463 self.start_trace()464 await self.async_awaitable465 except Exception:466 self._error_exc()467 else:468 break469 470 self.async_shim_frame = None471 472 # start the trace (the actual command is already set by set_* calls)473 if self.returnframe is None and self.stoplineno == -1 and not self.breaks:474 # This means we did a continue without any breakpoints, we should not475 # start the trace476 return477 478 self.start_trace()479 480 def sigint_handler(self, signum, frame):481 if self.allow_kbdint:482 raise KeyboardInterrupt483 self.message("\nProgram interrupted. (Use 'cont' to resume).")484 self.set_step()485 self.set_trace(frame)486 487 def reset(self):488 bdb.Bdb.reset(self)489 self.forget()490 491 def forget(self):492 self.lineno = None493 self.stack = []494 self.curindex = 0495 if hasattr(self, 'curframe') and self.curframe:496 self.curframe.f_globals.pop('__pdb_convenience_variables', None)497 self.curframe = None498 self.tb_lineno.clear()499 500 def setup(self, f, tb):501 self.forget()502 self.stack, self.curindex = self.get_stack(f, tb)503 while tb:504 # when setting up post-mortem debugging with a traceback, save all505 # the original line numbers to be displayed along the current line506 # numbers (which can be different, e.g. due to finally clauses)507 lineno = lasti2lineno(tb.tb_frame.f_code, tb.tb_lasti)508 self.tb_lineno[tb.tb_frame] = lineno509 tb = tb.tb_next510 self.curframe = self.stack[self.curindex][0]511 self.set_convenience_variable(self.curframe, '_frame', self.curframe)512 if self._current_task:513 self.set_convenience_variable(self.curframe, '_asynctask', self._current_task)514 self._save_initial_file_mtime(self.curframe)515 516 if self._chained_exceptions:517 self.set_convenience_variable(518 self.curframe,519 '_exception',520 self._chained_exceptions[self._chained_exception_index],521 )522 523 if self.rcLines:524 self.cmdqueue = [525 line for line in self.rcLines526 if line.strip() and not line.strip().startswith("#")527 ]528 self.rcLines = []529 530 @property531 @deprecated("The frame locals reference is no longer cached. Use 'curframe.f_locals' instead.")532 def curframe_locals(self):533 return self.curframe.f_locals534 535 @curframe_locals.setter536 @deprecated("Setting 'curframe_locals' no longer has any effect. Update the contents of 'curframe.f_locals' instead.")537 def curframe_locals(self, value):538 pass539 540 # Override Bdb methods541 542 def user_call(self, frame, argument_list):543 """This method is called when there is the remote possibility544 that we ever need to stop in this function."""545 if self._wait_for_mainpyfile:546 return547 if self.stop_here(frame):548 self.message('--Call--')549 self.interaction(frame, None)550 551 def user_line(self, frame):552 """This function is called when we stop or break at this line."""553 if self._wait_for_mainpyfile:554 if (self.mainpyfile != self.canonic(frame.f_code.co_filename)):555 return556 self._wait_for_mainpyfile = False557 if self.trace_opcodes:558 # GH-127321559 # We want to avoid stopping at an opcode that does not have560 # an associated line number because pdb does not like it561 if frame.f_lineno is None:562 self.set_stepinstr()563 return564 self.bp_commands(frame)565 self.interaction(frame, None)566 567 user_opcode = user_line568 569 def bp_commands(self, frame):570 """Call every command that was set for the current active breakpoint571 (if there is one).572 573 Returns True if the normal interaction function must be called,574 False otherwise."""575 # self.currentbp is set in bdb in Bdb.break_here if a breakpoint was hit576 if getattr(self, "currentbp", False) and \577 self.currentbp in self.commands:578 currentbp = self.currentbp579 self.currentbp = 0580 for line in self.commands[currentbp]:581 self.cmdqueue.append(line)582 self.cmdqueue.append(f'_pdbcmd_restore_lastcmd {self.lastcmd}')583 584 def user_return(self, frame, return_value):585 """This function is called when a return trap is set here."""586 if self._wait_for_mainpyfile:587 return588 frame.f_locals['__return__'] = return_value589 self.set_convenience_variable(frame, '_retval', return_value)590 self.message('--Return--')591 self.interaction(frame, None)592 593 def user_exception(self, frame, exc_info):594 """This function is called if an exception occurs,595 but only if we are to stop at or just below this level."""596 if self._wait_for_mainpyfile:597 return598 exc_type, exc_value, exc_traceback = exc_info599 frame.f_locals['__exception__'] = exc_type, exc_value600 self.set_convenience_variable(frame, '_exception', exc_value)601 602 # An 'Internal StopIteration' exception is an exception debug event603 # issued by the interpreter when handling a subgenerator run with604 # 'yield from' or a generator controlled by a for loop. No exception has605 # actually occurred in this case. The debugger uses this debug event to606 # stop when the debuggee is returning from such generators.607 prefix = 'Internal ' if (not exc_traceback608 and exc_type is StopIteration) else ''609 self.message('%s%s' % (prefix, self._format_exc(exc_value)))610 self.interaction(frame, exc_traceback)611 612 # General interaction function613 def _cmdloop(self):614 while True:615 try:616 # keyboard interrupts allow for an easy way to cancel617 # the current command, so allow them during interactive input618 self.allow_kbdint = True619 self.cmdloop()620 self.allow_kbdint = False621 break622 except KeyboardInterrupt:623 self.message('--KeyboardInterrupt--')624 625 def _save_initial_file_mtime(self, frame):626 """save the mtime of the all the files in the frame stack in the file mtime table627 if they haven't been saved yet."""628 while frame:629 filename = frame.f_code.co_filename630 if filename not in self._file_mtime_table:631 try:632 self._file_mtime_table[filename] = os.path.getmtime(filename)633 except Exception:634 pass635 frame = frame.f_back636 637 def _validate_file_mtime(self):638 """Check if the source file of the current frame has been modified.639 If so, give a warning and reset the modify time to current."""640 try:641 filename = self.curframe.f_code.co_filename642 mtime = os.path.getmtime(filename)643 except Exception:644 return645 if (filename in self._file_mtime_table and646 mtime != self._file_mtime_table[filename]):647 self.message(f"*** WARNING: file '{filename}' was edited, "648 "running stale code until the program is rerun")649 self._file_mtime_table[filename] = mtime650 651 # Called before loop, handles display expressions652 # Set up convenience variable containers653 def _show_display(self):654 displaying = self.displaying.get(self.curframe)655 if displaying:656 for expr, oldvalue in displaying.items():657 newvalue = self._getval_except(expr)658 # check for identity first; this prevents custom __eq__ to659 # be called at every loop, and also prevents instances whose660 # fields are changed to be displayed661 if newvalue is not oldvalue and newvalue != oldvalue:662 displaying[expr] = newvalue663 self.message('display %s: %s [old: %s]' %664 (expr, self._safe_repr(newvalue, expr),665 self._safe_repr(oldvalue, expr)))666 667 def _get_tb_and_exceptions(self, tb_or_exc):668 """669 Given a tracecack or an exception, return a tuple of chained exceptions670 and current traceback to inspect.671 672 This will deal with selecting the right ``__cause__`` or ``__context__``673 as well as handling cycles, and return a flattened list of exceptions we674 can jump to with do_exceptions.675 676 """677 _exceptions = []678 if isinstance(tb_or_exc, BaseException):679 traceback, current = tb_or_exc.__traceback__, tb_or_exc680 681 while current is not None:682 if current in _exceptions:683 break684 _exceptions.append(current)685 if current.__cause__ is not None:686 current = current.__cause__687 elif (688 current.__context__ is not None and not current.__suppress_context__689 ):690 current = current.__context__691 692 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:693 self.message(694 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"695 " chained exceptions found, not all exceptions"696 "will be browsable with `exceptions`."697 )698 break699 else:700 traceback = tb_or_exc701 return tuple(reversed(_exceptions)), traceback702 703 @contextmanager704 def _hold_exceptions(self, exceptions):705 """706 Context manager to ensure proper cleaning of exceptions references707 708 When given a chained exception instead of a traceback,709 pdb may hold references to many objects which may leak memory.710 711 We use this context manager to make sure everything is properly cleaned712 713 """714 try:715 self._chained_exceptions = exceptions716 self._chained_exception_index = len(exceptions) - 1717 yield718 finally:719 # we can't put those in forget as otherwise they would720 # be cleared on exception change721 self._chained_exceptions = tuple()722 self._chained_exception_index = 0723 724 def _get_asyncio_task(self):725 try:726 task = asyncio.current_task()727 except RuntimeError:728 task = None729 return task730 731 def interaction(self, frame, tb_or_exc):732 # Restore the previous signal handler at the Pdb prompt.733 if Pdb._previous_sigint_handler:734 try:735 signal.signal(signal.SIGINT, Pdb._previous_sigint_handler)736 except ValueError: # ValueError: signal only works in main thread737 pass738 else:739 Pdb._previous_sigint_handler = None740 741 self._current_task = self._get_asyncio_task()742 743 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)744 if isinstance(tb_or_exc, BaseException):745 assert tb is not None, "main exception must have a traceback"746 with self._hold_exceptions(_chained_exceptions):747 self.setup(frame, tb)748 # We should print the stack entry if and only if the user input749 # is expected, and we should print it right before the user input.750 # We achieve this by appending _pdbcmd_print_frame_status to the751 # command queue. If cmdqueue is not exhausted, the user input is752 # not expected and we will not print the stack entry.753 self.cmdqueue.append('_pdbcmd_print_frame_status')754 self._cmdloop()755 # If _pdbcmd_print_frame_status is not used, pop it out756 if self.cmdqueue and self.cmdqueue[-1] == '_pdbcmd_print_frame_status':757 self.cmdqueue.pop()758 self.forget()759 760 def displayhook(self, obj):761 """Custom displayhook for the exec in default(), which prevents762 assignment of the _ variable in the builtins.763 """764 # reproduce the behavior of the standard displayhook, not printing None765 if obj is not None:766 self.message(repr(obj))767 768 @contextmanager769 def _enable_multiline_input(self):770 try:771 import readline772 except ImportError:773 yield774 return775 776 def input_auto_indent():777 last_index = readline.get_current_history_length()778 last_line = readline.get_history_item(last_index)779 if last_line:780 if last_line.isspace():781 # If the last line is empty, we don't need to indent782 return783 784 last_line = last_line.rstrip('\r\n')785 indent = len(last_line) - len(last_line.lstrip())786 if last_line.endswith(":"):787 indent += 4788 readline.insert_text(' ' * indent)789 790 completenames = self.completenames791 try:792 self.completenames = self.complete_multiline_names793 readline.set_startup_hook(input_auto_indent)794 yield795 finally:796 readline.set_startup_hook()797 self.completenames = completenames798 return799 800 def _exec_in_closure(self, source, globals, locals):801 """ Run source code in closure so code object created within source802 can find variables in locals correctly803 804 returns True if the source is executed, False otherwise805 """806 807 # Determine if the source should be executed in closure. Only when the808 # source compiled to multiple code objects, we should use this feature.809 # Otherwise, we can just raise an exception and normal exec will be used.810 811 code = compile(source, "<string>", "exec")812 if not any(isinstance(const, CodeType) for const in code.co_consts):813 return False814 815 # locals could be a proxy which does not support pop816 # copy it first to avoid modifying the original locals817 locals_copy = dict(locals)818 819 locals_copy["__pdb_eval__"] = {820 "result": None,821 "write_back": {}822 }823 824 # If the source is an expression, we need to print its value825 try:826 compile(source, "<string>", "eval")827 except SyntaxError:828 pass829 else:830 source = "__pdb_eval__['result'] = " + source831 832 # Add write-back to update the locals833 source = ("try:\n" +834 textwrap.indent(source, " ") + "\n" +835 "finally:\n" +836 " __pdb_eval__['write_back'] = locals()")837 838 # Build a closure source code with freevars from locals like:839 # def __pdb_outer():840 # var = None841 # def __pdb_scope(): # This is the code object we want to execute842 # nonlocal var843 # <source>844 # return __pdb_scope.__code__845 source_with_closure = ("def __pdb_outer():\n" +846 "\n".join(f" {var} = None" for var in locals_copy) + "\n" +847 " def __pdb_scope():\n" +848 "\n".join(f" nonlocal {var}" for var in locals_copy) + "\n" +849 textwrap.indent(source, " ") + "\n" +850 " return __pdb_scope.__code__"851 )852 853 # Get the code object of __pdb_scope()854 # The exec fills locals_copy with the __pdb_outer() function and we can call855 # that to get the code object of __pdb_scope()856 ns = {}857 try:858 exec(source_with_closure, {}, ns)859 except Exception:860 return False861 code = ns["__pdb_outer"]()862 863 cells = tuple(types.CellType(locals_copy.get(var)) for var in code.co_freevars)864 865 try:866 exec(code, globals, locals_copy, closure=cells)867 except Exception:868 return False869 870 # get the data we need from the statement871 pdb_eval = locals_copy["__pdb_eval__"]872 873 # __pdb_eval__ should not be updated back to locals874 pdb_eval["write_back"].pop("__pdb_eval__")875 876 # Write all local variables back to locals877 locals.update(pdb_eval["write_back"])878 eval_result = pdb_eval["result"]879 if eval_result is not None:880 self.message(repr(eval_result))881 882 return True883 884 def _exec_await(self, source, globals, locals):885 """ Run source code that contains await by playing with async shim frame"""886 # Put the source in an async function887 source_async = (888 "async def __pdb_await():\n" +889 textwrap.indent(source, " ") + '\n' +890 " __pdb_locals.update(locals())"891 )892 ns = globals | locals893 # We use __pdb_locals to do write back894 ns["__pdb_locals"] = locals895 exec(source_async, ns)896 self.async_awaitable = ns["__pdb_await"]()897 898 def _read_code(self, line):899 buffer = line900 is_await_code = False901 code = None902 try:903 if (code := codeop.compile_command(line + '\n', '<stdin>', 'single')) is None:904 # Multi-line mode905 with self._enable_multiline_input():906 buffer = line907 continue_prompt = "... "908 while (code := codeop.compile_command(buffer, '<stdin>', 'single')) is None:909 if self.use_rawinput:910 try:911 line = input(continue_prompt)912 except (EOFError, KeyboardInterrupt):913 self.lastcmd = ""914 print('\n')915 return None, None, False916 else:917 self.stdout.write(continue_prompt)918 self.stdout.flush()919 line = self.stdin.readline()920 if not len(line):921 self.lastcmd = ""922 self.stdout.write('\n')923 self.stdout.flush()924 return None, None, False925 else:926 line = line.rstrip('\r\n')927 if line.isspace():928 # empty line, just continue929 buffer += '\n'930 else:931 buffer += '\n' + line932 self.lastcmd = buffer933 except SyntaxError as e:934 # Maybe it's an await expression/statement935 if (936 self.async_shim_frame is not None937 and e.msg == "'await' outside function"938 ):939 is_await_code = True940 else:941 raise942 943 return code, buffer, is_await_code944 945 def default(self, line):946 if line[:1] == '!': line = line[1:].strip()947 locals = self.curframe.f_locals948 globals = self.curframe.f_globals949 try:950 code, buffer, is_await_code = self._read_code(line)951 if buffer is None:952 return953 save_stdout = sys.stdout954 save_stdin = sys.stdin955 save_displayhook = sys.displayhook956 try:957 sys.stdin = self.stdin958 sys.stdout = self.stdout959 sys.displayhook = self.displayhook960 if is_await_code:961 self._exec_await(buffer, globals, locals)962 return True963 else:964 if not self._exec_in_closure(buffer, globals, locals):965 exec(code, globals, locals)966 finally:967 sys.stdout = save_stdout968 sys.stdin = save_stdin969 sys.displayhook = save_displayhook970 except:971 self._error_exc()972 973 def _replace_convenience_variables(self, line):974 """Replace the convenience variables in 'line' with their values.975 e.g. $foo is replaced by __pdb_convenience_variables["foo"].976 Note: such pattern in string literals will be skipped"""977 978 if "$" not in line:979 return line980 981 dollar_start = dollar_end = (-1, -1)982 replace_variables = []983 try:984 for t in tokenize.generate_tokens(io.StringIO(line).readline):985 token_type, token_string, start, end, _ = t986 if token_type == token.OP and token_string == '$':987 dollar_start, dollar_end = start, end988 elif start == dollar_end and token_type == token.NAME:989 # line is a one-line command so we only care about column990 replace_variables.append((dollar_start[1], end[1], token_string))991 except tokenize.TokenError:992 return line993 994 if not replace_variables:995 return line996 997 last_end = 0998 line_pieces = []999 for start, end, name in replace_variables:1000 line_pieces.append(line[last_end:start] + f'__pdb_convenience_variables["{name}"]')1001 last_end = end1002 line_pieces.append(line[last_end:])1003 1004 return ''.join(line_pieces)1005 1006 def precmd(self, line):1007 """Handle alias expansion and ';;' separator."""1008 if not line.strip():1009 return line1010 args = line.split()1011 while args[0] in self.aliases:1012 line = self.aliases[args[0]]1013 for idx in range(1, 10):1014 if f'%{idx}' in line:1015 if idx >= len(args):1016 self.error(f"Not enough arguments for alias '{args[0]}'")1017 # This is a no-op1018 return "!"1019 line = line.replace(f'%{idx}', args[idx])1020 elif '%*' not in line:1021 if idx < len(args):1022 self.error(f"Too many arguments for alias '{args[0]}'")1023 # This is a no-op1024 return "!"1025 break1026 1027 line = line.replace("%*", ' '.join(args[1:]))1028 args = line.split()1029 # split into ';;' separated commands1030 # unless it's an alias command1031 if args[0] != 'alias':1032 marker = line.find(';;')1033 if marker >= 0:1034 # queue up everything after marker1035 next = line[marker+2:].lstrip()1036 self.cmdqueue.insert(0, next)1037 line = line[:marker].rstrip()1038 1039 # Replace all the convenience variables1040 line = self._replace_convenience_variables(line)1041 1042 return line1043 1044 def onecmd(self, line):1045 """Interpret the argument as though it had been typed in response1046 to the prompt.1047 1048 Checks whether this line is typed at the normal prompt or in1049 a breakpoint command list definition.1050 """1051 if not self.commands_defining:1052 if line.startswith('_pdbcmd'):1053 command, arg, line = self.parseline(line)1054 if hasattr(self, command):1055 return getattr(self, command)(arg)1056 return cmd.Cmd.onecmd(self, line)1057 else:1058 return self.handle_command_def(line)1059 1060 def handle_command_def(self, line):1061 """Handles one command line during command list definition."""1062 cmd, arg, line = self.parseline(line)1063 if not cmd:1064 return False1065 if cmd == 'end':1066 return True # end of cmd list1067 elif cmd == 'EOF':1068 self.message('')1069 return True # end of cmd list1070 cmdlist = self.commands[self.commands_bnum]1071 if cmd == 'silent':1072 cmdlist.append('_pdbcmd_silence_frame_status')1073 return False # continue to handle other cmd def in the cmd list1074 if arg:1075 cmdlist.append(cmd+' '+arg)1076 else:1077 cmdlist.append(cmd)1078 # Determine if we must stop1079 try:1080 func = getattr(self, 'do_' + cmd)1081 except AttributeError:1082 func = self.default1083 # one of the resuming commands1084 if func.__name__ in self.commands_resuming:1085 return True1086 return False1087 1088 def _colorize_code(self, code):1089 if self.colorize:1090 colors = list(_pyrepl.utils.gen_colors(code))1091 chars, _ = _pyrepl.utils.disp_str(code, colors=colors, force_color=True)1092 code = "".join(chars)1093 return code1094 1095 # interface abstraction functions1096 1097 def message(self, msg, end='\n'):1098 print(msg, end=end, file=self.stdout)1099 1100 def error(self, msg):1101 print('***', msg, file=self.stdout)1102 1103 # convenience variables1104 1105 def set_convenience_variable(self, frame, name, value):1106 if '__pdb_convenience_variables' not in frame.f_globals:1107 frame.f_globals['__pdb_convenience_variables'] = {}1108 frame.f_globals['__pdb_convenience_variables'][name] = value1109 1110 # Generic completion functions. Individual complete_foo methods can be1111 # assigned below to one of these functions.1112 1113 @property1114 def rlcompleter(self):1115 """Return the `Completer` class from `rlcompleter`, while avoiding the1116 side effects of changing the completer from `import rlcompleter`.1117 1118 This is a compromise between GH-138860 and GH-139289. If GH-139289 is1119 fixed, then we don't need this and we can just `import rlcompleter` in1120 `Pdb.__init__`.1121 """1122 if not hasattr(self, "_rlcompleter"):1123 try:1124 import readline1125 except ImportError:1126 # readline is not available, just get the Completer1127 from rlcompleter import Completer1128 self._rlcompleter = Completer1129 else:1130 # importing rlcompleter could have side effect of changing1131 # the current completer, we need to restore it1132 prev_completer = readline.get_completer()1133 from rlcompleter import Completer1134 self._rlcompleter = Completer1135 readline.set_completer(prev_completer)1136 return self._rlcompleter1137 1138 def completenames(self, text, line, begidx, endidx):1139 # Overwrite completenames() of cmd so for the command completion,1140 # if no current command matches, check for expressions as well1141 commands = super().completenames(text, line, begidx, endidx)1142 for alias in self.aliases:1143 if alias.startswith(text):1144 commands.append(alias)1145 if commands:1146 return commands1147 else:1148 expressions = self._complete_expression(text, line, begidx, endidx)1149 if expressions:1150 return expressions1151 return self.completedefault(text, line, begidx, endidx)1152 1153 def _complete_location(self, text, line, begidx, endidx):1154 # Complete a file/module/function location for break/tbreak/clear.1155 if line.strip().endswith((':', ',')):1156 # Here comes a line number or a condition which we can't complete.1157 return []1158 # First, try to find matching functions (i.e. expressions).1159 try:1160 ret = self._complete_expression(text, line, begidx, endidx)1161 except Exception:1162 ret = []1163 # Then, try to complete file names as well.1164 globs = glob.glob(glob.escape(text) + '*')1165 for fn in globs:1166 if os.path.isdir(fn):1167 ret.append(fn + '/')1168 elif os.path.isfile(fn) and fn.lower().endswith(('.py', '.pyw')):1169 ret.append(fn + ':')1170 return ret1171 1172 def _complete_bpnumber(self, text, line, begidx, endidx):1173 # Complete a breakpoint number. (This would be more helpful if we could1174 # display additional info along with the completions, such as file/line1175 # of the breakpoint.)1176 return [str(i) for i, bp in enumerate(bdb.Breakpoint.bpbynumber)1177 if bp is not None and str(i).startswith(text)]1178 1179 def _complete_expression(self, text, line, begidx, endidx):1180 # Complete an arbitrary expression.1181 if not self.curframe:1182 return []1183 # Collect globals and locals. It is usually not really sensible to also1184 # complete builtins, and they clutter the namespace quite heavily, so we1185 # leave them out.1186 ns = {**self.curframe.f_globals, **self.curframe.f_locals}1187 if '.' in text:1188 # Walk an attribute chain up to the last part, similar to what1189 # rlcompleter does. This will bail if any of the parts are not1190 # simple attribute access, which is what we want.1191 dotted = text.split('.')1192 try:1193 if dotted[0].startswith('$'):1194 obj = self.curframe.f_globals['__pdb_convenience_variables'][dotted[0][1:]]1195 else:1196 obj = ns[dotted[0]]1197 for part in dotted[1:-1]:1198 obj = getattr(obj, part)1199 except (KeyError, AttributeError):1200 return []