summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVinay Karanam <vinayinvicible@gmail.com>2018-12-06 18:28:29 +0530
committerTim Graham <timograham@gmail.com>2019-02-02 16:39:36 -0500
commit3634560fa9ddb342c3a823b78cc63e7000ccabd8 (patch)
tree85f4e8d62f37676826e6b0ad0a5c32486831363b
parent199025fa8816dde6c611193adbce1ba6c5a72c6b (diff)
Fixed #29393 -- Prevented infinite loop in ExceptionReporter.get_traceback_frames().
-rw-r--r--django/views/debug.py3
-rw-r--r--tests/view_tests/tests/test_debug.py39
2 files changed, 42 insertions, 0 deletions
diff --git a/django/views/debug.py b/django/views/debug.py
index 1128301c57..0efaf0c6af 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -397,6 +397,9 @@ class ExceptionReporter:
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
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index 1ff1220803..a6e5275c9f 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -4,6 +4,7 @@ import os
import re
import sys
import tempfile
+import threading
from io import StringIO
from pathlib import Path
@@ -403,6 +404,44 @@ class ExceptionReporterTests(SimpleTestCase):
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/')