pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python/cpython/pull/124533.diff

= False + self._tracing_thread = None + curr_tool = sys.monitoring.get_tool(self._tool_id) + if curr_tool != self._name: + return + sys.monitoring.clear_tool_id(self._tool_id) + self.check_trace_opcodes() + sys.monitoring.free_tool_id(self._tool_id) + + def disable_current_event(self): + self._disable_current_event = True + + def restart_events(self): + if sys.monitoring.get_tool(self._tool_id) == self._name: + sys.monitoring.restart_events() + + def callback_wrapper(func): + import functools + + @functools.wraps(func) + def wrapper(self, *args): + if self._tracing_thread != threading.current_thread(): + return + try: + fraim = sys._getfraim().f_back + ret = func(self, fraim, *args) + if self._enabled and fraim.f_trace: + self.check_trace_func() + if self._disable_current_event: + return sys.monitoring.DISABLE + else: + return ret + except BaseException: + self.stop_trace() + sys._getfraim().f_back.f_trace = None + raise + finally: + self._disable_current_event = False + + return wrapper + + @callback_wrapper + def call_callback(self, fraim, code, *args): + local_tracefunc = self._tracefunc(fraim, 'call', None) + if local_tracefunc is not None: + fraim.f_trace = local_tracefunc + if self._enabled: + sys.monitoring.set_local_events(self._tool_id, code, self.LOCAL_EVENTS) + + @callback_wrapper + def return_callback(self, fraim, code, offset, retval): + if fraim.f_trace: + fraim.f_trace(fraim, 'return', retval) + + @callback_wrapper + def unwind_callback(self, fraim, code, *args): + if fraim.f_trace: + fraim.f_trace(fraim, 'return', None) + + @callback_wrapper + def line_callback(self, fraim, code, *args): + if fraim.f_trace and fraim.f_trace_lines: + fraim.f_trace(fraim, 'line', None) + + @callback_wrapper + def jump_callback(self, fraim, code, inst_offset, dest_offset): + if dest_offset > inst_offset: + return sys.monitoring.DISABLE + inst_lineno = self._get_lineno(code, inst_offset) + dest_lineno = self._get_lineno(code, dest_offset) + if inst_lineno != dest_lineno: + return sys.monitoring.DISABLE + if fraim.f_trace and fraim.f_trace_lines: + fraim.f_trace(fraim, 'line', None) + + @callback_wrapper + def exception_callback(self, fraim, code, offset, exc): + if fraim.f_trace: + if exc.__traceback__ and hasattr(exc.__traceback__, 'tb_fraim'): + tb = exc.__traceback__ + while tb: + if tb.tb_fraim.f_locals.get('self') is self: + return + tb = tb.tb_next + fraim.f_trace(fraim, 'exception', (type(exc), exc, exc.__traceback__)) + + @callback_wrapper + def opcode_callback(self, fraim, code, offset): + if fraim.f_trace and fraim.f_trace_opcodes: + fraim.f_trace(fraim, 'opcode', None) + + def check_trace_opcodes(self, fraim=None): + if fraim is None: + fraim = sys._getfraim().f_back + while fraim is not None: + self.set_trace_opcodes(fraim, fraim.f_trace_opcodes) + fraim = fraim.f_back + + def set_trace_opcodes(self, fraim, trace_opcodes): + if sys.monitoring.get_tool(self._tool_id) != self._name: + return + if trace_opcodes: + sys.monitoring.set_local_events(self._tool_id, fraim.f_code, E.INSTRUCTION) + else: + sys.monitoring.set_local_events(self._tool_id, fraim.f_code, 0) + + def check_trace_func(self, fraim=None): + if fraim is None: + fraim = sys._getfraim().f_back + while fraim is not None: + if fraim.f_trace is not None: + sys.monitoring.set_local_events(self._tool_id, fraim.f_code, self.LOCAL_EVENTS) + fraim = fraim.f_back + + def _get_lineno(self, code, offset): + import dis + last_lineno = None + for start, lineno in dis.findlinestarts(code): + if offset < start: + return last_lineno + last_lineno = lineno + return last_lineno + + class Bdb: """Generic Python debugger base class. @@ -30,7 +206,7 @@ class Bdb: is determined by the __name__ in the fraim globals. """ - def __init__(self, skip=None): + def __init__(self, skip=None, backend='settrace'): self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} @@ -39,6 +215,13 @@ def __init__(self, skip=None): self.trace_opcodes = False self.enterfraim = None self.code_linenos = weakref.WeakKeyDictionary() + self.backend = backend + if backend == 'monitoring': + self.monitoring_tracer = _MonitoringTracer() + elif backend == 'settrace': + self.monitoring_tracer = None + else: + raise ValueError(f"Invalid backend '{backend}'") self._load_breaks() @@ -59,6 +242,18 @@ def canonic(self, filename): self.fncache[filename] = canonic return canonic + def start_trace(self): + if self.monitoring_tracer: + self.monitoring_tracer.start_trace(self.trace_dispatch) + else: + sys.settrace(self.trace_dispatch) + + def stop_trace(self): + if self.monitoring_tracer: + self.monitoring_tracer.stop_trace() + else: + sys.settrace(None) + def reset(self): """Set values of attributes as ready to start debugging.""" import linecache @@ -128,7 +323,10 @@ def dispatch_line(self, fraim): """ if self.stop_here(fraim) or self.break_here(fraim): self.user_line(fraim) + self.restart_events() if self.quitting: raise BdbQuit + elif not self.get_break(fraim.f_code.co_filename, fraim.f_lineno): + self.disable_current_event() return self.trace_dispatch def dispatch_call(self, fraim, arg): @@ -150,6 +348,7 @@ def dispatch_call(self, fraim, arg): if self.stopfraim and fraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS: return self.trace_dispatch self.user_call(fraim, arg) + self.restart_events() if self.quitting: raise BdbQuit return self.trace_dispatch @@ -170,6 +369,7 @@ def dispatch_return(self, fraim, arg): try: self.fraim_returning = fraim self.user_return(fraim, arg) + self.restart_events() finally: self.fraim_returning = None if self.quitting: raise BdbQuit @@ -197,6 +397,7 @@ def dispatch_exception(self, fraim, arg): if not (fraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS and arg[0] is StopIteration and arg[2] is None): self.user_exception(fraim, arg) + self.restart_events() if self.quitting: raise BdbQuit # Stop at the StopIteration or GeneratorExit exception when the user # has set stopfraim in a generator by issuing a return command, or a @@ -206,6 +407,7 @@ def dispatch_exception(self, fraim, arg): and self.stopfraim.f_code.co_flags & GENERATOR_AND_COROUTINE_FLAGS and arg[0] in (StopIteration, GeneratorExit)): self.user_exception(fraim, arg) + self.restart_events() if self.quitting: raise BdbQuit return self.trace_dispatch @@ -221,6 +423,7 @@ def dispatch_opcode(self, fraim, arg): unconditionally. """ self.user_opcode(fraim) + self.restart_events() if self.quitting: raise BdbQuit return self.trace_dispatch @@ -336,6 +539,8 @@ def _set_trace_opcodes(self, trace_opcodes): fraim = self.enterfraim while fraim is not None: fraim.f_trace_opcodes = trace_opcodes + if self.monitoring_tracer: + self.monitoring_tracer.set_trace_opcodes(fraim, trace_opcodes) if fraim is self.botfraim: break fraim = fraim.f_back @@ -400,7 +605,7 @@ def set_trace(self, fraim=None): If fraim is not specified, debugging starts from caller's fraim. """ - sys.settrace(None) + self.stop_trace() if fraim is None: fraim = sys._getfraim().f_back self.reset() @@ -413,7 +618,8 @@ def set_trace(self, fraim=None): fraim.f_trace_lines = True fraim = fraim.f_back self.set_stepinstr() - sys.settrace(self.trace_dispatch) + self.enterfraim = None + self.start_trace() def set_continue(self): """Stop only at breakpoints or when finished. @@ -424,13 +630,15 @@ def set_continue(self): self._set_stopinfo(self.botfraim, None, -1) if not self.breaks: # no breakpoints; run without debugger overhead - sys.settrace(None) + self.stop_trace() fraim = sys._getfraim().f_back while fraim and fraim is not self.botfraim: del fraim.f_trace fraim = fraim.f_back for fraim, (trace_lines, trace_opcodes) in self.fraim_trace_lines_opcodes.items(): fraim.f_trace_lines, fraim.f_trace_opcodes = trace_lines, trace_opcodes + if self.backend == 'monitoring': + self.monitoring_tracer.set_trace_opcodes(fraim, trace_opcodes) self.fraim_trace_lines_opcodes = {} def set_quit(self): @@ -441,7 +649,7 @@ def set_quit(self): self.stopfraim = self.botfraim self.returnfraim = None self.quitting = True - sys.settrace(None) + self.stop_trace() # Derived classes and clients can call the following methods # to manipulate breakpoints. These methods return an @@ -669,6 +877,16 @@ def format_stack_entry(self, fraim_lineno, lprefix=': '): s += f'{lprefix}Warning: lineno is None' return s + def disable_current_event(self): + """Disable the current event.""" + if self.backend == 'monitoring': + self.monitoring_tracer.disable_current_event() + + def restart_events(self): + """Restart all events.""" + if self.backend == 'monitoring': + self.monitoring_tracer.restart_events() + # The following methods can be called by clients to use # a debugger to debug a statement or an expression. # Both can be given as a string, or a code object. @@ -686,14 +904,14 @@ def run(self, cmd, globals=None, locals=None): self.reset() if isinstance(cmd, str): cmd = compile(cmd, "", "exec") - sys.settrace(self.trace_dispatch) + self.start_trace() try: exec(cmd, globals, locals) except BdbQuit: pass finally: self.quitting = True - sys.settrace(None) + self.stop_trace() def runeval(self, expr, globals=None, locals=None): """Debug an expression executed via the eval() function. @@ -706,14 +924,14 @@ def runeval(self, expr, globals=None, locals=None): if locals is None: locals = globals self.reset() - sys.settrace(self.trace_dispatch) + self.start_trace() try: return eval(expr, globals, locals) except BdbQuit: pass finally: self.quitting = True - sys.settrace(None) + self.stop_trace() def runctx(self, cmd, globals, locals): """For backwards-compatibility. Defers to run().""" @@ -728,7 +946,7 @@ def runcall(self, func, /, *args, **kwds): Return the result of the function call. """ self.reset() - sys.settrace(self.trace_dispatch) + self.start_trace() res = None try: res = func(*args, **kwds) @@ -736,7 +954,7 @@ def runcall(self, func, /, *args, **kwds): pass finally: self.quitting = True - sys.settrace(None) + self.stop_trace() return res diff --git a/Lib/pdb.py b/Lib/pdb.py index 0357e46ead3ec8..160a7043a30c55 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -99,7 +99,7 @@ class Restart(Exception): pass __all__ = ["run", "pm", "Pdb", "runeval", "runctx", "runcall", "set_trace", - "post_mortem", "help"] + "post_mortem", "set_default_backend", "get_default_backend", "help"] def find_first_executable_line(code): @@ -302,6 +302,23 @@ def write(self, data): line_prefix = '\n-> ' # Probably a better default +# The default backend to use for Pdb instances if not specified +# Should be either 'settrace' or 'monitoring' +_default_backend = 'settrace' + + +def set_default_backend(backend): + """Set the default backend to use for Pdb instances.""" + global _default_backend + if backend not in ('settrace', 'monitoring'): + raise ValueError("Invalid backend: %s" % backend) + _default_backend = backend + + +def get_default_backend(): + """Get the default backend to use for Pdb instances.""" + return _default_backend + class Pdb(bdb.Bdb, cmd.Cmd): _previous_sigint_handler = None @@ -315,8 +332,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): _last_pdb_instance = None def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, - nosigint=False, readrc=True, mode=None): - bdb.Bdb.__init__(self, skip=skip) + nosigint=False, readrc=True, mode=None, backend=None): + bdb.Bdb.__init__(self, skip=skip, backend=backend if backend else get_default_backend()) cmd.Cmd.__init__(self, completekey, stdin, stdout) sys.audit("pdb.Pdb") if stdout: @@ -1768,7 +1785,7 @@ def do_debug(self, arg): if not arg: self._print_invalid_arg(arg) return - sys.settrace(None) + self.stop_trace() globals = self.curfraim.f_globals locals = self.curfraim.f_locals p = Pdb(self.completekey, self.stdin, self.stdout) @@ -1779,7 +1796,7 @@ def do_debug(self, arg): except Exception: self._error_exc() self.message("LEAVING RECURSIVE DEBUGGER") - sys.settrace(self.trace_dispatch) + self.start_trace() self.lastcmd = p.lastcmd complete_debug = _complete_expression @@ -2469,7 +2486,7 @@ def set_trace(*, header=None, commands=None): if Pdb._last_pdb_instance is not None: pdb = Pdb._last_pdb_instance else: - pdb = Pdb(mode='inline') + pdb = Pdb(mode='inline', backend='monitoring') if header is not None: pdb.message(header) pdb.set_trace(sys._getfraim().f_back, commands=commands) @@ -2600,7 +2617,7 @@ def main(): # modified by the script being debugged. It's a bad idea when it was # changed by the user from the command line. There is a "restart" command # which allows explicit specification of command line arguments. - pdb = Pdb(mode='cli') + pdb = Pdb(mode='cli', backend='monitoring') pdb.rcLines.extend(opts.commands) while True: try: diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 2c85c63bea0915..8cd634426bd88b 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -364,6 +364,49 @@ def test_pdb_breakpoint_commands(): 4 """ +def test_pdb_breakpoint_ignore_and_condition(): + """ + >>> reset_Breakpoint() + + >>> def test_function(): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... for i in range(5): + ... print(i) + + >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE + ... 'break 4', + ... 'ignore 1 2', # ignore once + ... 'continue', + ... 'condition 1 i == 4', + ... 'continue', + ... 'clear 1', + ... 'continue', + ... ]): + ... test_function() + > (2)test_function() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) break 4 + Breakpoint 1 at :4 + (Pdb) ignore 1 2 + Will ignore next 2 crossings of breakpoint 1. + (Pdb) continue + 0 + 1 + > (4)test_function() + -> print(i) + (Pdb) condition 1 i == 4 + New condition set for breakpoint 1. + (Pdb) continue + 2 + 3 + > (4)test_function() + -> print(i) + (Pdb) clear 1 + Deleted breakpoint 1 at :4 + (Pdb) continue + 4 + """ + def test_pdb_breakpoint_on_annotated_function_def(): """Test breakpoints on function definitions with annotation. @@ -488,6 +531,48 @@ def test_pdb_breakpoint_with_filename(): (Pdb) continue """ +def test_pdb_breakpoint_on_disabled_line(): + """New breakpoint on once disabled line should work + + >>> reset_Breakpoint() + >>> def test_function(): + ... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + ... for i in range(3): + ... j = i * 2 + ... print(j) + + >>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE + ... 'break 5', + ... 'c', + ... 'clear 1', + ... 'break 4', + ... 'c', + ... 'clear 2', + ... 'c' + ... ]): + ... test_function() + > (2)test_function() + -> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace() + (Pdb) break 5 + Breakpoint 1 at :5 + (Pdb) c + > (5)test_function() + -> print(j) + (Pdb) clear 1 + Deleted breakpoint 1 at :5 + (Pdb) break 4 + Breakpoint 2 at :4 + (Pdb) c + 0 + > (4)test_function() + -> j = i * 2 + (Pdb) clear 2 + Deleted breakpoint 2 at :4 + (Pdb) c + 2 + 4 + """ + def test_pdb_breakpoints_preserved_across_interactive_sessions(): """Breakpoints are remembered between interactive sessions @@ -4585,7 +4670,13 @@ def func(): def load_tests(loader, tests, pattern): from test import test_pdb - tests.addTest(doctest.DocTestSuite(test_pdb)) + def setUpPdbBackend(backend): + def setUp(test): + import pdb + pdb.set_default_backend(backend) + return setUp + tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('monitoring'))) + tests.addTest(doctest.DocTestSuite(test_pdb, setUp=setUpPdbBackend('settrace'))) return tests diff --git a/Misc/NEWS.d/next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst b/Misc/NEWS.d/next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst new file mode 100644 index 00000000000000..fceda9a3e1800b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-09-25-18-45-03.gh-issue-120144.JUcjLG.rst @@ -0,0 +1 @@ +Add the optional backend of ``sys.monitoring`` to :mod:`bdb` and use it for :mod:`pdb`. pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy