From 6de377a605f2f049d919530fee1f67fd0ba3b5f2 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 14 Oct 2024 22:26:41 -0500 Subject: [PATCH 1/4] Drop copy-pasted set_continue, dispatch_line These were copy-pasted in from CPython in https://github.com/inducer/pudb/commit/6065d5a2c53e953e00bf09f826a7cae3b1cadf13 According to https://bugs.python.org/issue16482, where the patch originated, all supported versions of Python now have the fix. Additionally, Python 3.13 has further modifications, which we probably shouldn't copy. --- pudb/debugger.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 824c186e..67566756 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -227,33 +227,6 @@ def __del__(self): self._tty_file.close() self._tty_file = None - # These (dispatch_line and set_continue) are copied from bdb with the - # patch from https://bugs.python.org/issue16482 applied. See - # https://github.com/inducer/pudb/pull/90. - def dispatch_line(self, frame): - if self.stop_here(frame) or self.break_here(frame): - self.user_line(frame) - if self.quitting: - raise bdb.BdbQuit - # Do not re-install the local trace when we are finished debugging, - # see issues 16482 and 7238. - if not sys.gettrace(): - return None - return self.trace_dispatch - - def set_continue(self): - # Don't stop except at breakpoints or when finished - self._set_stopinfo(self.botframe, None, -1) - if not self.breaks: - # no breakpoints; run without debugger overhead - sys.settrace(None) - frame = sys._getframe().f_back - while frame: - del frame.f_trace - if frame is self.botframe: - break - frame = frame.f_back - def set_jump(self, frame, line): frame.f_lineno = line From 6c12aec3ace9ed80de22f678d18453a0b6d19730 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 14 Oct 2024 22:29:34 -0500 Subject: [PATCH 2/4] Drop deletion of __exc_tuple__ from user_line This code dates from the very early days of Pudb, possibly copy-pasted from early pdb. No recent Python seems to have it, and on Py3.13 it is actively harmful, because its new frame locals proxy prohibits deletion of frame locals. --- pudb/debugger.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 67566756..0cb9af63 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -433,9 +433,6 @@ def user_call(self, frame, argument_list): def user_line(self, frame): """This function is called when we stop or break at this line.""" - if "__exc_tuple__" in frame.f_locals: - del frame.f_locals["__exc_tuple__"] - if self._waiting_for_mainpyfile(frame): return From 41fba1aea3270f9cace663b31573de3eebdb0db1 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 14 Oct 2024 22:30:41 -0500 Subject: [PATCH 3/4] Exception handling fixes to match newer bdb --- pudb/debugger.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 0cb9af63..955e0544 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -456,7 +456,7 @@ def user_return(self, frame, return_value): if self._waiting_for_mainpyfile(frame): return - if "__exc_tuple__" not in frame.f_locals: + if "__exception__" not in frame.f_locals: self.interaction(frame) def _waiting_for_mainpyfile(self, frame): @@ -472,13 +472,15 @@ def _waiting_for_mainpyfile(self, frame): return True return False - def user_exception(self, frame, exc_tuple): + def user_exception(self, frame, exc_info): """This function is called if an exception occurs, but only if we are to stop at or just below this level.""" - frame.f_locals["__exc_tuple__"] = exc_tuple + + exc_type, exc_value, _exc_traceback = exc_info + frame.f_locals["__exception__"] = exc_type, exc_value if not self._wait_for_mainpyfile: - self.interaction(frame, exc_tuple) + self.interaction(frame, exc_info) # {{{ entrypoints From 46d2324ce105299bc49e82f1ac47dfa07b64c81f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Mon, 14 Oct 2024 22:31:41 -0500 Subject: [PATCH 4/4] Clean up, fix set_trace This makes the code a bit easier to follow, and it uses a more appropriate _set_stopinfo in the pause case, instead of set_step. Use of set_step was causing set_trace to stop somewhere in the guts of bdb. Closes gh-663 --- pudb/debugger.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 955e0544..eebc53f2 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -252,29 +252,41 @@ def set_trace(self, frame=None, as_breakpoint=None, paused=True): as_breakpoint = True if frame is None: - frame = thisframe = sys._getframe().f_back - else: - thisframe = frame + frame = sys._getframe().f_back + assert frame is not None + # See pudb issue #52. If this works well enough we should upstream to # stdlib bdb.py. # self.reset() - while frame: - frame.f_trace = self.trace_dispatch - self.botframe = frame - frame = frame.f_back + if paused: + self.enterframe = frame - thisframe_info = ( - self.canonic(thisframe.f_code.co_filename), thisframe.f_lineno) - if thisframe_info not in self.set_traces or self.set_traces[thisframe_info]: + thisframe = frame + while thisframe: + thisframe.f_trace = self.trace_dispatch + self.botframe = thisframe + if sys.version_info >= (3, 13): + # save trace flags, to be restored by set_continue + self.frame_trace_lines_opcodes[thisframe] = ( # pylint: disable=no-member + thisframe.f_trace_lines, + thisframe.f_trace_opcodes) + + # We need f_trace_lines == True for the debugger to work + thisframe.f_trace_lines = True + + thisframe = thisframe.f_back + + frame_info = (self.canonic(frame.f_code.co_filename), frame.f_lineno) + if frame_info not in self.set_traces or self.set_traces[frame_info]: if as_breakpoint: - self.set_traces[thisframe_info] = True + self.set_traces[frame_info] = True if self.ui.source_code_provider is not None: self.ui.set_source_code_provider( self.ui.source_code_provider, force_update=True) if paused: - self.set_step() + self._set_stopinfo(frame, None) else: self.set_continue() sys.settrace(self.trace_dispatch)