Skip to content

Commit 2052c77

Browse files
committed
handle recursion errors (vert deeply nested EGs)
1 parent 5170f00 commit 2052c77

File tree

3 files changed

+62
-11
lines changed

3 files changed

+62
-11
lines changed

Lib/test/test_traceback.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -987,6 +987,35 @@ def __eq__(self, other):
987987
self.assertIn('UnhashableException: ex2', tb[4])
988988
self.assertIn('UnhashableException: ex1', tb[12])
989989

990+
def deep_eg(self):
991+
e = TypeError(1)
992+
for i in range(2000):
993+
e = ExceptionGroup('eg', [e])
994+
return e
995+
996+
@cpython_only
997+
def test_exception_group_deep_recursion_capi(self):
998+
from _testcapi import exception_print
999+
LIMIT = 75
1000+
eg = self.deep_eg()
1001+
with captured_output("stderr") as stderr_f:
1002+
with support.infinite_recursion(max_depth=LIMIT):
1003+
exception_print(eg)
1004+
output = stderr_f.getvalue()
1005+
self.assertIn('ExceptionGroup', output)
1006+
self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
1007+
1008+
def test_exception_group_deep_recursion_traceback(self):
1009+
LIMIT = 75
1010+
eg = self.deep_eg()
1011+
with captured_output("stderr") as stderr_f:
1012+
with support.infinite_recursion(max_depth=LIMIT):
1013+
traceback.print_exception(type(eg), eg, eg.__traceback__)
1014+
output = stderr_f.getvalue()
1015+
self.assertIn('ExceptionGroup', output)
1016+
self.assertLessEqual(output.count('ExceptionGroup'), LIMIT)
1017+
1018+
9901019
cause_message = (
9911020
"\nThe above exception was the direct cause "
9921021
"of the following exception:\n\n")

Lib/traceback.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,10 @@ def format(self, *, chain=True, _ctx=None):
920920
f'+---------------- {label} ----------------\n')
921921
_ctx.exception_group_depth += 1
922922
_ctx.parent_label = label
923-
yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx)
923+
try:
924+
yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx)
925+
except RecursionError:
926+
pass
924927
_ctx.parent_label = parent_label
925928
if last_exc and _ctx.need_close:
926929
yield (_ctx.get_indent() +

Python/pythonrun.c

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,7 @@ print_exception(struct exception_print_context *ctx, PyObject *value)
947947
Py_DECREF(filename);
948948
if (line != NULL) {
949949
err += WRITE_INDENTED_MARGIN(ctx, f);
950+
PyErr_Clear();
950951
PyFile_WriteObject(line, f, Py_PRINT_RAW);
951952
Py_DECREF(line);
952953
}
@@ -997,6 +998,7 @@ print_exception(struct exception_print_context *ctx, PyObject *value)
997998
if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins) &&
998999
!_PyUnicode_EqualToASCIIId(modulename, &PyId___main__))
9991000
{
1001+
PyErr_Clear();
10001002
err += PyFile_WriteObject(modulename, f, Py_PRINT_RAW);
10011003
err += PyFile_WriteString(".", f);
10021004
}
@@ -1010,6 +1012,7 @@ print_exception(struct exception_print_context *ctx, PyObject *value)
10101012
err = PyFile_WriteString("<unknown>", f);
10111013
}
10121014
else {
1015+
PyErr_Clear();
10131016
err = PyFile_WriteObject(qualname, f, Py_PRINT_RAW);
10141017
Py_DECREF(qualname);
10151018
}
@@ -1072,7 +1075,6 @@ static int
10721075
print_chained(struct exception_print_context* ctx, PyObject *value,
10731076
const char * message, const char *tag) {
10741077
PyObject *f = ctx->file;
1075-
int err = 0;
10761078
PyObject *parent_label = ctx->parent_label;
10771079
PyObject *label = NULL;
10781080
int need_close = ctx->need_close;
@@ -1084,16 +1086,25 @@ print_chained(struct exception_print_context* ctx, PyObject *value,
10841086
}
10851087
ctx->parent_label = label;
10861088

1087-
print_exception_recursive(ctx, value);
1088-
err |= WRITE_INDENTED_MARGIN(ctx, f);
1089-
err |= PyFile_WriteString("\n", f);
1090-
err |= WRITE_INDENTED_MARGIN(ctx, f);
1091-
err |= PyFile_WriteString(message, f);
1092-
err |= WRITE_INDENTED_MARGIN(ctx, f);
1093-
err |= PyFile_WriteString("\n", f);
1089+
int err = Py_EnterRecursiveCall(" in print_chained");
1090+
if (!err) {
1091+
print_exception_recursive(ctx, value);
1092+
Py_LeaveRecursiveCall();
1093+
1094+
err |= WRITE_INDENTED_MARGIN(ctx, f);
1095+
err |= PyFile_WriteString("\n", f);
1096+
err |= WRITE_INDENTED_MARGIN(ctx, f);
1097+
err |= PyFile_WriteString(message, f);
1098+
err |= WRITE_INDENTED_MARGIN(ctx, f);
1099+
err |= PyFile_WriteString("\n", f);
1100+
}
1101+
else {
1102+
PyErr_Clear();
1103+
}
10941104

10951105
ctx->need_close = need_close;
10961106
ctx->parent_label = parent_label;
1107+
10971108
Py_XDECREF(label);
10981109
return err;
10991110
}
@@ -1156,7 +1167,6 @@ print_exception_recursive(struct exception_print_context* ctx, PyObject *value)
11561167

11571168
/* TODO: add arg to limit number of exceptions printed? */
11581169

1159-
11601170
if (ctx->exception_group_depth == 0) {
11611171
ctx->exception_group_depth += 1;
11621172
}
@@ -1190,12 +1200,21 @@ print_exception_recursive(struct exception_print_context* ctx, PyObject *value)
11901200
"%s+---------------- %U ----------------\n",
11911201
(i == 0) ? "+-" : " ", label);
11921202
ctx->exception_group_depth += 1;
1203+
PyErr_Clear();
11931204
err |= PyFile_WriteObject(line, f, Py_PRINT_RAW);
11941205
Py_XDECREF(line);
11951206

11961207
ctx->parent_label = label;
11971208
PyObject *exc = PyTuple_GetItem(excs, i);
1198-
print_exception_recursive(ctx, exc);
1209+
1210+
if (!Py_EnterRecursiveCall(" in print_exception_recursive")) {
1211+
print_exception_recursive(ctx, exc);
1212+
Py_LeaveRecursiveCall();
1213+
}
1214+
else {
1215+
err = -1;
1216+
PyErr_Clear();
1217+
}
11991218
ctx->parent_label = parent_label;
12001219
Py_XDECREF(label);
12011220

0 commit comments

Comments
 (0)