Skip to content

Commit 990213c

Browse files
gpsheadclaude
andcommitted
Add max_group_width and max_group_depth limits to IDLE's ExceptionGroup rendering
Match the traceback module defaults (max_group_width=15, max_group_depth=10) to prevent pathological exception groups from producing unbounded output or stack overflow via recursion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 576b223 commit 990213c

File tree

2 files changed

+59
-8
lines changed

2 files changed

+59
-8
lines changed

Lib/idlelib/idle_test/test_run.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,36 @@ def test_print_exception_group_seen(self):
145145
self.assertIn('ValueError: shared', tb)
146146
self.assertIn('<exception ValueError has printed>', tb)
147147

148+
@force_not_colorized
149+
def test_print_exception_group_max_width(self):
150+
excs = [ValueError(f'v{i}') for i in range(20)]
151+
try:
152+
raise ExceptionGroup('eg', excs)
153+
except ExceptionGroup:
154+
tb = self._capture_exception()
155+
156+
self.assertIn('+---------------- 15 ----------------', tb)
157+
self.assertIn('+---------------- ... ----------------', tb)
158+
self.assertIn('and 5 more exceptions', tb)
159+
self.assertNotIn('+---------------- 16 ----------------', tb)
160+
161+
@force_not_colorized
162+
def test_print_exception_group_max_depth(self):
163+
def make_nested(depth):
164+
if depth == 0:
165+
return ValueError('leaf')
166+
return ExceptionGroup(f'level{depth}',
167+
[make_nested(depth - 1)])
168+
169+
try:
170+
raise make_nested(15)
171+
except ExceptionGroup:
172+
tb = self._capture_exception()
173+
174+
self.assertIn('... (max_group_depth is 10)', tb)
175+
self.assertIn('ExceptionGroup: level15', tb)
176+
self.assertNotIn('ValueError: leaf', tb)
177+
148178
# StdioFile tests.
149179

150180
class S(str):

Lib/idlelib/run.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,19 @@ def print_exception():
251251
seen = set()
252252
exclude = ("run.py", "rpc.py", "threading.py", "queue.py",
253253
"debugger_r.py", "bdb.py")
254+
max_group_width = 15
255+
max_group_depth = 10
256+
group_depth = 0
254257

255258
def print_exc_group(typ, exc, tb, prefix=""):
259+
nonlocal group_depth
260+
group_depth += 1
256261
prefix2 = prefix or " "
262+
if group_depth > max_group_depth:
263+
print(f"{prefix2}| ... (max_group_depth is {max_group_depth})",
264+
file=efile)
265+
group_depth -= 1
266+
return
257267
if tb:
258268
if not prefix:
259269
print(" + Exception Group Traceback (most recent call last):", file=efile)
@@ -267,13 +277,23 @@ def print_exc_group(typ, exc, tb, prefix=""):
267277
lines = get_message_lines(typ, exc, tb)
268278
for line in lines:
269279
print(f"{prefix2}| {line}", end="", file=efile)
270-
for i, sub in enumerate(exc.exceptions, 1):
271-
if i == 1:
272-
first_line_pre = "+-"
273-
else:
274-
first_line_pre = " "
275-
print(f"{prefix2}{first_line_pre}+---------------- {i} ----------------", file=efile)
276-
if id(sub) not in seen:
280+
num_excs = len(exc.exceptions)
281+
if num_excs <= max_group_width:
282+
n = num_excs
283+
else:
284+
n = max_group_width + 1
285+
for i, sub in enumerate(exc.exceptions[:n], 1):
286+
truncated = (i > max_group_width)
287+
first_line_pre = "+-" if i == 1 else " "
288+
title = str(i) if not truncated else '...'
289+
print(f"{prefix2}{first_line_pre}+---------------- {title} ----------------", file=efile)
290+
if truncated:
291+
remaining = num_excs - max_group_width
292+
plural = 's' if remaining > 1 else ''
293+
print(f"{prefix2} | and {remaining} more exception{plural}",
294+
file=efile)
295+
need_print_underline = True
296+
elif id(sub) not in seen:
277297
if not prefix:
278298
print_exc(type(sub), sub, sub.__traceback__, " ")
279299
else:
@@ -282,8 +302,9 @@ def print_exc_group(typ, exc, tb, prefix=""):
282302
else:
283303
print(f"{prefix2} | <exception {type(sub).__name__} has printed>", file=efile)
284304
need_print_underline = True
285-
if need_print_underline and i == len(exc.exceptions):
305+
if need_print_underline and i == n:
286306
print(f"{prefix2} +------------------------------------", file=efile)
307+
group_depth -= 1
287308

288309
def print_exc(typ, exc, tb, prefix=""):
289310
seen.add(id(exc))

0 commit comments

Comments
 (0)