Skip to content

Commit

Permalink
Fixed #29393 -- Prevented infinite loop in ExceptionReporter.get_trac…
Browse files Browse the repository at this point in the history
…eback_frames().
  • Loading branch information
vinayinvicible authored and timgraham committed Feb 2, 2019
1 parent 199025f commit 3634560
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 0 deletions.
3 changes: 3 additions & 0 deletions django/views/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ def explicit_or_implicit_cause(exc_value):
while exc_value:
exceptions.append(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 = []
# No exceptions were supplied to ExceptionReporter
Expand Down
39 changes: 39 additions & 0 deletions tests/view_tests/tests/test_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
import sys
import tempfile
import threading
from io import StringIO
from pathlib import Path

Expand Down Expand Up @@ -403,6 +404,44 @@ def test_reporting_frames_without_source(self):
text = reporter.get_traceback_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):
"A message can be provided in addition to a request"
request = self.rf.get('/test_view/')
Expand Down

0 comments on commit 3634560

Please sign in to comment.