bpo-42500: Fix recursion in or after except (GH-23568)
* Use counter, rather boolean state when handling soft overflows.
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index 4dbf5fe..1bdd3f2 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -1046,7 +1046,7 @@ def gen():
# tstate->recursion_depth is equal to (recursion_limit - 1)
# and is equal to recursion_limit when _gen_throw() calls
# PyErr_NormalizeException().
- recurse(setrecursionlimit(depth + 2) - depth - 1)
+ recurse(setrecursionlimit(depth + 2) - depth)
finally:
sys.setrecursionlimit(recursionlimit)
print('Done.')
@@ -1076,6 +1076,54 @@ def test_recursion_normalizing_infinite_exception(self):
b'while normalizing an exception', err)
self.assertIn(b'Done.', out)
+
+ def test_recursion_in_except_handler(self):
+
+ def set_relative_recursion_limit(n):
+ depth = 1
+ while True:
+ try:
+ sys.setrecursionlimit(depth)
+ except RecursionError:
+ depth += 1
+ else:
+ break
+ sys.setrecursionlimit(depth+n)
+
+ def recurse_in_except():
+ try:
+ 1/0
+ except:
+ recurse_in_except()
+
+ def recurse_after_except():
+ try:
+ 1/0
+ except:
+ pass
+ recurse_after_except()
+
+ def recurse_in_body_and_except():
+ try:
+ recurse_in_body_and_except()
+ except:
+ recurse_in_body_and_except()
+
+ recursionlimit = sys.getrecursionlimit()
+ try:
+ set_relative_recursion_limit(10)
+ for func in (recurse_in_except, recurse_after_except, recurse_in_body_and_except):
+ with self.subTest(func=func):
+ try:
+ func()
+ except RecursionError:
+ pass
+ else:
+ self.fail("Should have raised a RecursionError")
+ finally:
+ sys.setrecursionlimit(recursionlimit)
+
+
@cpython_only
def test_recursion_normalizing_with_no_memory(self):
# Issue #30697. Test that in the abort that occurs when there is no
@@ -1112,7 +1160,7 @@ def raiseMemError():
except MemoryError as e:
tb = e.__traceback__
else:
- self.fail("Should have raises a MemoryError")
+ self.fail("Should have raised a MemoryError")
return traceback.format_tb(tb)
tb1 = raiseMemError()
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 173ef9e..3860656 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -221,7 +221,7 @@ def test_recursionlimit_recovery(self):
def f():
f()
try:
- for depth in (10, 25, 50, 75, 100, 250, 1000):
+ for depth in (50, 75, 100, 250, 1000):
try:
sys.setrecursionlimit(depth)
except RecursionError:
@@ -231,17 +231,17 @@ def f():
# Issue #5392: test stack overflow after hitting recursion
# limit twice
- self.assertRaises(RecursionError, f)
- self.assertRaises(RecursionError, f)
+ with self.assertRaises(RecursionError):
+ f()
+ with self.assertRaises(RecursionError):
+ f()
finally:
sys.setrecursionlimit(oldlimit)
@test.support.cpython_only
def test_setrecursionlimit_recursion_depth(self):
# Issue #25274: Setting a low recursion limit must be blocked if the
- # current recursion depth is already higher than the "lower-water
- # mark". Otherwise, it may not be possible anymore to
- # reset the overflowed flag to 0.
+ # current recursion depth is already higher than limit.
from _testinternalcapi import get_recursion_depth
@@ -262,42 +262,10 @@ def set_recursion_limit_at_depth(depth, limit):
sys.setrecursionlimit(1000)
for limit in (10, 25, 50, 75, 100, 150, 200):
- # formula extracted from _Py_RecursionLimitLowerWaterMark()
- if limit > 200:
- depth = limit - 50
- else:
- depth = limit * 3 // 4
- set_recursion_limit_at_depth(depth, limit)
+ set_recursion_limit_at_depth(limit, limit)
finally:
sys.setrecursionlimit(oldlimit)
- # The error message is specific to CPython
- @test.support.cpython_only
- def test_recursionlimit_fatalerror(self):
- # A fatal error occurs if a second recursion limit is hit when recovering
- # from a first one.
- code = textwrap.dedent("""
- import sys
-
- def f():
- try:
- f()
- except RecursionError:
- f()
-
- sys.setrecursionlimit(%d)
- f()""")
- with test.support.SuppressCrashReport():
- for i in (50, 1000):
- sub = subprocess.Popen([sys.executable, '-c', code % i],
- stderr=subprocess.PIPE)
- err = sub.communicate()[1]
- self.assertTrue(sub.returncode, sub.returncode)
- self.assertIn(
- b"Fatal Python error: _Py_CheckRecursiveCall: "
- b"Cannot recover from stack overflow",
- err)
-
def test_getwindowsversion(self):
# Raise SkipTest if sys doesn't have getwindowsversion attribute
test.support.get_attribute(sys, "getwindowsversion")