mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #29393 -- Prevented infinite loop in ExceptionReporter.get_traceback_frames().
This commit is contained in:
		
				
					committed by
					
						 Tim Graham
						Tim Graham
					
				
			
			
				
	
			
			
			
						parent
						
							199025fa88
						
					
				
				
					commit
					3634560fa9
				
			| @@ -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 | ||||
|   | ||||
| @@ -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/') | ||||
|   | ||||
		Reference in New Issue
	
	Block a user