Fixed #29393 -- Prevented infinite loop in ExceptionReporter.get_traceback_frames().

This commit is contained in:
Vinay Karanam 2018-12-06 18:28:29 +05:30 committed by Tim Graham
parent 199025fa88
commit 3634560fa9
2 changed files with 42 additions and 0 deletions

View File

@ -397,6 +397,9 @@ class ExceptionReporter:
while exc_value: while exc_value:
exceptions.append(exc_value) exceptions.append(exc_value)
exc_value = explicit_or_implicit_cause(exc_value) exc_value = explicit_or_implicit_cause(exc_value)
if exc_value in exceptions:
# Avoid infinite loop if there's a cyclic reference (#29393).
break
frames = [] frames = []
# No exceptions were supplied to ExceptionReporter # No exceptions were supplied to ExceptionReporter

View File

@ -4,6 +4,7 @@ import os
import re import re
import sys import sys
import tempfile import tempfile
import threading
from io import StringIO from io import StringIO
from pathlib import Path from pathlib import Path
@ -403,6 +404,44 @@ class ExceptionReporterTests(SimpleTestCase):
text = reporter.get_traceback_text() text = reporter.get_traceback_text()
self.assertIn('"generated" in funcName', text) self.assertIn('"generated" in funcName', text)
def test_reporting_frames_for_cyclic_reference(self):
try:
def test_func():
try:
raise RuntimeError('outer') from RuntimeError('inner')
except RuntimeError as exc:
raise exc.__cause__
test_func()
except Exception:
exc_type, exc_value, tb = sys.exc_info()
request = self.rf.get('/test_view/')
reporter = ExceptionReporter(request, exc_type, exc_value, tb)
def generate_traceback_frames(*args, **kwargs):
nonlocal tb_frames
tb_frames = reporter.get_traceback_frames()
tb_frames = None
tb_generator = threading.Thread(target=generate_traceback_frames, daemon=True)
tb_generator.start()
tb_generator.join(timeout=5)
if tb_generator.is_alive():
# tb_generator is a daemon that runs until the main thread/process
# exits. This is resource heavy when running the full test suite.
# Setting the following values to None makes
# reporter.get_traceback_frames() exit early.
exc_value.__traceback__ = exc_value.__context__ = exc_value.__cause__ = None
tb_generator.join()
self.fail('Cyclic reference in Exception Reporter.get_traceback_frames()')
if tb_frames is None:
# can happen if the thread generating traceback got killed
# or exception while generating the traceback
self.fail('Traceback generation failed')
last_frame = tb_frames[-1]
self.assertIn('raise exc.__cause__', last_frame['context_line'])
self.assertEqual(last_frame['filename'], __file__)
self.assertEqual(last_frame['function'], 'test_func')
def test_request_and_message(self): def test_request_and_message(self):
"A message can be provided in addition to a request" "A message can be provided in addition to a request"
request = self.rf.get('/test_view/') request = self.rf.get('/test_view/')