Handle exception chaining and groups in Dev's traceback handling (#6178)

This commit is contained in:
Jakub Kuczys
2023-06-22 01:42:01 +02:00
committed by GitHub
parent fdcbe00143
commit 9c85917dad
2 changed files with 188 additions and 37 deletions

View File

@@ -337,49 +337,74 @@ class DevOutput:
pass
else:
exc.lineno -= line_offset
if sys.version_info >= (3, 10) and exc.end_lineno is not None:
exc.end_lineno -= line_offset
else:
exc.lineno -= line_offset
if sys.version_info >= (3, 10) and exc.end_lineno is not None:
exc.end_lineno -= line_offset
traceback_exc = traceback.TracebackException(exc_type, exc, tb)
top_traceback_exc = traceback.TracebackException(exc_type, exc, tb)
py311_or_above = sys.version_info >= (3, 11)
stack_summary = traceback_exc.stack
for idx, frame_summary in enumerate(stack_summary):
try:
source_lines, line_offset = self.source_cache[frame_summary.filename]
except KeyError:
continue
lineno = frame_summary.lineno
if lineno is None:
continue
queue = [ # actually a stack but 'stack' is easy to confuse with actual traceback stack
top_traceback_exc,
]
seen = {id(top_traceback_exc)}
while queue:
traceback_exc = queue.pop()
try:
# line numbers are 1-based, the list indexes are 0-based
line = source_lines[lineno - 1]
except IndexError:
# the frame might be pointing at a different source code, ignore...
continue
lineno -= line_offset
# support for enhanced error locations in tracebacks
if py311_or_above:
end_lineno = frame_summary.end_lineno
if end_lineno is not None:
end_lineno -= line_offset
frame_summary = traceback.FrameSummary(
frame_summary.filename,
lineno,
frame_summary.name,
line=line,
end_lineno=end_lineno,
colno=frame_summary.colno,
end_colno=frame_summary.end_colno,
)
else:
frame_summary = traceback.FrameSummary(
frame_summary.filename, lineno, frame_summary.name, line=line
)
stack_summary[idx] = frame_summary
# handle exception groups; this uses getattr() to support `exceptiongroup` backport lib
exceptions: List[traceback.TracebackException] = (
getattr(traceback_exc, "exceptions", None) or []
)
# handle exception chaining
if traceback_exc.__cause__ is not None:
exceptions.append(traceback_exc.__cause__)
if traceback_exc.__context__ is not None:
exceptions.append(traceback_exc.__context__)
for te in exceptions:
if id(te) not in seen:
queue.append(te)
seen.add(id(te))
return "".join(traceback_exc.format())
stack_summary = traceback_exc.stack
for idx, frame_summary in enumerate(stack_summary):
try:
source_lines, line_offset = self.source_cache[frame_summary.filename]
except KeyError:
continue
lineno = frame_summary.lineno
if lineno is None:
continue
try:
# line numbers are 1-based, the list indexes are 0-based
line = source_lines[lineno - 1]
except IndexError:
# the frame might be pointing at a different source code, ignore...
continue
lineno -= line_offset
# support for enhanced error locations in tracebacks
if py311_or_above:
end_lineno = frame_summary.end_lineno
if end_lineno is not None:
end_lineno -= line_offset
frame_summary = traceback.FrameSummary(
frame_summary.filename,
lineno,
frame_summary.name,
line=line,
end_lineno=end_lineno,
colno=frame_summary.colno,
end_colno=frame_summary.end_colno,
)
else:
frame_summary = traceback.FrameSummary(
frame_summary.filename, lineno, frame_summary.name, line=line
)
stack_summary[idx] = frame_summary
return "".join(top_traceback_exc.format())
@cog_i18n(_)