|
10 | 10 |
|
11 | 11 | from test.support import (TESTFN, captured_stderr, check_impl_detail, |
12 | 12 | check_warnings, cpython_only, gc_collect, run_unittest, |
13 | | - no_tracing, unlink, import_module, script_helper) |
14 | | - |
| 13 | + no_tracing, unlink, import_module, script_helper, |
| 14 | + SuppressCrashReport) |
15 | 15 | class NaiveException(Exception): |
16 | 16 | def __init__(self, x): |
17 | 17 | self.x = x |
@@ -936,6 +936,105 @@ def g(): |
936 | 936 | self.assertIsInstance(v, RecursionError, type(v)) |
937 | 937 | self.assertIn("maximum recursion depth exceeded", str(v)) |
938 | 938 |
|
| 939 | + @cpython_only |
| 940 | + def test_recursion_normalizing_exception(self): |
| 941 | + # Issue #22898. |
| 942 | + # Test that a RecursionError is raised when tstate->recursion_depth is |
| 943 | + # equal to recursion_limit in PyErr_NormalizeException() and check |
| 944 | + # that a ResourceWarning is printed. |
| 945 | + # Prior to #22898, the recursivity of PyErr_NormalizeException() was |
| 946 | + # controled by tstate->recursion_depth and a PyExc_RecursionErrorInst |
| 947 | + # singleton was being used in that case, that held traceback data and |
| 948 | + # locals indefinitely and would cause a segfault in _PyExc_Fini() upon |
| 949 | + # finalization of these locals. |
| 950 | + code = """if 1: |
| 951 | + import sys |
| 952 | + from _testcapi import get_recursion_depth |
| 953 | +
|
| 954 | + class MyException(Exception): pass |
| 955 | +
|
| 956 | + def setrecursionlimit(depth): |
| 957 | + while 1: |
| 958 | + try: |
| 959 | + sys.setrecursionlimit(depth) |
| 960 | + return depth |
| 961 | + except RecursionError: |
| 962 | + # sys.setrecursionlimit() raises a RecursionError if |
| 963 | + # the new recursion limit is too low (issue #25274). |
| 964 | + depth += 1 |
| 965 | +
|
| 966 | + def recurse(cnt): |
| 967 | + cnt -= 1 |
| 968 | + if cnt: |
| 969 | + recurse(cnt) |
| 970 | + else: |
| 971 | + generator.throw(MyException) |
| 972 | +
|
| 973 | + def gen(): |
| 974 | + f = open(%a, mode='rb', buffering=0) |
| 975 | + yield |
| 976 | +
|
| 977 | + generator = gen() |
| 978 | + next(generator) |
| 979 | + recursionlimit = sys.getrecursionlimit() |
| 980 | + depth = get_recursion_depth() |
| 981 | + try: |
| 982 | + # Upon the last recursive invocation of recurse(), |
| 983 | + # tstate->recursion_depth is equal to (recursion_limit - 1) |
| 984 | + # and is equal to recursion_limit when _gen_throw() calls |
| 985 | + # PyErr_NormalizeException(). |
| 986 | + recurse(setrecursionlimit(depth + 2) - depth - 1) |
| 987 | + finally: |
| 988 | + sys.setrecursionlimit(recursionlimit) |
| 989 | + print('Done.') |
| 990 | + """ % __file__ |
| 991 | + rc, out, err = script_helper.assert_python_failure("-Wd", "-c", code) |
| 992 | + # Check that the program does not fail with SIGABRT. |
| 993 | + self.assertEqual(rc, 1) |
| 994 | + self.assertIn(b'RecursionError', err) |
| 995 | + self.assertIn(b'ResourceWarning', err) |
| 996 | + self.assertIn(b'Done.', out) |
| 997 | + |
| 998 | + @cpython_only |
| 999 | + def test_recursion_normalizing_infinite_exception(self): |
| 1000 | + # Issue #30697. Test that a RecursionError is raised when |
| 1001 | + # PyErr_NormalizeException() maximum recursion depth has been |
| 1002 | + # exceeded. |
| 1003 | + code = """if 1: |
| 1004 | + import _testcapi |
| 1005 | + try: |
| 1006 | + raise _testcapi.RecursingInfinitelyError |
| 1007 | + finally: |
| 1008 | + print('Done.') |
| 1009 | + """ |
| 1010 | + rc, out, err = script_helper.assert_python_failure("-c", code) |
| 1011 | + self.assertEqual(rc, 1) |
| 1012 | + self.assertIn(b'RecursionError: maximum recursion depth exceeded ' |
| 1013 | + b'while normalizing an exception', err) |
| 1014 | + self.assertIn(b'Done.', out) |
| 1015 | + |
| 1016 | + @cpython_only |
| 1017 | + def test_recursion_normalizing_with_no_memory(self): |
| 1018 | + # Issue #30697. Test that in the abort that occurs when there is no |
| 1019 | + # memory left and the size of the Python frames stack is greater than |
| 1020 | + # the size of the list of preallocated MemoryError instances, the |
| 1021 | + # Fatal Python error message mentions MemoryError. |
| 1022 | + code = """if 1: |
| 1023 | + import _testcapi |
| 1024 | + class C(): pass |
| 1025 | + def recurse(cnt): |
| 1026 | + cnt -= 1 |
| 1027 | + if cnt: |
| 1028 | + recurse(cnt) |
| 1029 | + else: |
| 1030 | + _testcapi.set_nomemory(0) |
| 1031 | + C() |
| 1032 | + recurse(16) |
| 1033 | + """ |
| 1034 | + with SuppressCrashReport(): |
| 1035 | + rc, out, err = script_helper.assert_python_failure("-c", code) |
| 1036 | + self.assertIn(b'Fatal Python error: Cannot recover from ' |
| 1037 | + b'MemoryErrors while normalizing exceptions.', err) |
939 | 1038 |
|
940 | 1039 | @cpython_only |
941 | 1040 | def test_MemoryError(self): |
|
0 commit comments