|
108 | 108 | importos |
109 | 109 |
|
110 | 110 | fromIPythonimportget_ipython |
| 111 | +fromcontextlibimportcontextmanager |
111 | 112 | fromIPython.utilsimportPyColorize |
112 | 113 | fromIPython.utilsimportcoloransi,py3compat |
113 | 114 | fromIPython.core.excolorsimportexception_colors |
|
127 | 128 | DEBUGGERSKIP="__debuggerskip__" |
128 | 129 |
|
129 | 130 |
|
| 131 | +# this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676 |
| 132 | +# on lower python versions, we backported the feature. |
| 133 | +CHAIN_EXCEPTIONS=sys.version_info< (3,13) |
| 134 | + |
| 135 | + |
130 | 136 | defmake_arrow(pad): |
131 | 137 | """generate the leading arrow in front of traceback or debugger""" |
132 | 138 | ifpad>=2: |
@@ -185,6 +191,9 @@ class Pdb(OldPdb): |
185 | 191 |
|
186 | 192 | """ |
187 | 193 |
|
| 194 | +ifCHAIN_EXCEPTIONS: |
| 195 | +MAX_CHAINED_EXCEPTION_DEPTH=999 |
| 196 | + |
188 | 197 | default_predicates= { |
189 | 198 | "tbhide":True, |
190 | 199 | "readonly":False, |
@@ -281,6 +290,10 @@ def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwarg |
281 | 290 | # list of predicates we use to skip frames |
282 | 291 | self._predicates=self.default_predicates |
283 | 292 |
|
| 293 | +ifCHAIN_EXCEPTIONS: |
| 294 | +self._chained_exceptions=tuple() |
| 295 | +self._chained_exception_index=0 |
| 296 | + |
284 | 297 | # |
285 | 298 | defset_colors(self,scheme): |
286 | 299 | """Shorthand access to the color table scheme selector method.""" |
@@ -330,9 +343,106 @@ def hidden_frames(self, stack): |
330 | 343 | ip_hide= [hifi>ip_start[0]elseTruefor (i,h)inenumerate(ip_hide)] |
331 | 344 | returnip_hide |
332 | 345 |
|
333 | | -definteraction(self,frame,traceback): |
| 346 | +ifCHAIN_EXCEPTIONS: |
| 347 | + |
| 348 | +def_get_tb_and_exceptions(self,tb_or_exc): |
| 349 | +""" |
| 350 | + Given a tracecack or an exception, return a tuple of chained exceptions |
| 351 | + and current traceback to inspect. |
| 352 | + This will deal with selecting the right ``__cause__`` or ``__context__`` |
| 353 | + as well as handling cycles, and return a flattened list of exceptions we |
| 354 | + can jump to with do_exceptions. |
| 355 | + """ |
| 356 | +_exceptions= [] |
| 357 | +ifisinstance(tb_or_exc,BaseException): |
| 358 | +traceback,current=tb_or_exc.__traceback__,tb_or_exc |
| 359 | + |
| 360 | +whilecurrentisnotNone: |
| 361 | +ifcurrentin_exceptions: |
| 362 | +break |
| 363 | +_exceptions.append(current) |
| 364 | +ifcurrent.__cause__isnotNone: |
| 365 | +current=current.__cause__ |
| 366 | +elif ( |
| 367 | +current.__context__isnotNone |
| 368 | +andnotcurrent.__suppress_context__ |
| 369 | + ): |
| 370 | +current=current.__context__ |
| 371 | + |
| 372 | +iflen(_exceptions)>=self.MAX_CHAINED_EXCEPTION_DEPTH: |
| 373 | +self.message( |
| 374 | +f"More than{self.MAX_CHAINED_EXCEPTION_DEPTH}" |
| 375 | +" chained exceptions found, not all exceptions" |
| 376 | +"will be browsable with `exceptions`." |
| 377 | + ) |
| 378 | +break |
| 379 | +else: |
| 380 | +traceback=tb_or_exc |
| 381 | +returntuple(reversed(_exceptions)),traceback |
| 382 | + |
| 383 | +@contextmanager |
| 384 | +def_hold_exceptions(self,exceptions): |
| 385 | +""" |
| 386 | + Context manager to ensure proper cleaning of exceptions references |
| 387 | + When given a chained exception instead of a traceback, |
| 388 | + pdb may hold references to many objects which may leak memory. |
| 389 | + We use this context manager to make sure everything is properly cleaned |
| 390 | + """ |
| 391 | +try: |
| 392 | +self._chained_exceptions=exceptions |
| 393 | +self._chained_exception_index=len(exceptions)-1 |
| 394 | +yield |
| 395 | +finally: |
| 396 | +# we can't put those in forget as otherwise they would |
| 397 | +# be cleared on exception change |
| 398 | +self._chained_exceptions=tuple() |
| 399 | +self._chained_exception_index=0 |
| 400 | + |
| 401 | +defdo_exceptions(self,arg): |
| 402 | +"""exceptions [number] |
| 403 | + List or change current exception in an exception chain. |
| 404 | + Without arguments, list all the current exception in the exception |
| 405 | + chain. Exceptions will be numbered, with the current exception indicated |
| 406 | + with an arrow. |
| 407 | + If given an integer as argument, switch to the exception at that index. |
| 408 | + """ |
| 409 | +ifnotself._chained_exceptions: |
| 410 | +self.message( |
| 411 | +"Did not find chained exceptions. To move between" |
| 412 | +" exceptions, pdb/post_mortem must be given an exception" |
| 413 | +" object rather than a traceback." |
| 414 | + ) |
| 415 | +return |
| 416 | +ifnotarg: |
| 417 | +forix,excinenumerate(self._chained_exceptions): |
| 418 | +prompt=">"ifix==self._chained_exception_indexelse" " |
| 419 | +rep=repr(exc) |
| 420 | +iflen(rep)>80: |
| 421 | +rep=rep[:77]+"..." |
| 422 | +self.message(f"{prompt}{ix:>3}{rep}") |
| 423 | +else: |
| 424 | +try: |
| 425 | +number=int(arg) |
| 426 | +exceptValueError: |
| 427 | +self.error("Argument must be an integer") |
| 428 | +return |
| 429 | +if0<=number<len(self._chained_exceptions): |
| 430 | +self._chained_exception_index=number |
| 431 | +self.setup(None,self._chained_exceptions[number].__traceback__) |
| 432 | +self.print_stack_entry(self.stack[self.curindex]) |
| 433 | +else: |
| 434 | +self.error("No exception with that number") |
| 435 | + |
| 436 | +definteraction(self,frame,tb_or_exc): |
334 | 437 | try: |
335 | | -OldPdb.interaction(self,frame,traceback) |
| 438 | +ifCHAIN_EXCEPTIONS: |
| 439 | +# this context manager is part of interaction in 3.13 |
| 440 | +_chained_exceptions,tb=self._get_tb_and_exceptions(tb_or_exc) |
| 441 | +withself._hold_exceptions(_chained_exceptions): |
| 442 | +OldPdb.interaction(self,frame,tb) |
| 443 | +else: |
| 444 | +OldPdb.interaction(self,frame,traceback) |
| 445 | + |
336 | 446 | exceptKeyboardInterrupt: |
337 | 447 | self.stdout.write("\n"+self.shell.get_exception_only()) |
338 | 448 |
|
|