Issue #5392: when a very low recursion limit was set, the interpreter would
abort with a fatal error after the recursion limit was hit twice.
diff --git a/Include/ceval.h b/Include/ceval.h
index 919c494..7bd8179 100644
--- a/Include/ceval.h
+++ b/Include/ceval.h
@@ -92,11 +92,10 @@
# define _Py_MakeRecCheck(x) (++(x) > _Py_CheckRecursionLimit)
#endif
-#ifdef USE_STACKCHECK
-# define _Py_MakeEndRecCheck(x) (--(x) < _Py_CheckRecursionLimit - 50)
-#else
-# define _Py_MakeEndRecCheck(x) (--(x) < _Py_CheckRecursionLimit - 50)
-#endif
+#define _Py_MakeEndRecCheck(x) \
+ (--(x) < ((_Py_CheckRecursionLimit > 100) \
+ ? (_Py_CheckRecursionLimit - 50) \
+ : (3 * (_Py_CheckRecursionLimit >> 2))))
#define Py_ALLOW_RECURSION \
do { unsigned char _old = PyThreadState_GET()->recursion_critical;\
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index fad9939..9f0c139 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -2,6 +2,8 @@
import unittest, test.support
import sys, io, os
import struct
+import subprocess
+import textwrap
class SysModuleTest(unittest.TestCase):
@@ -155,6 +157,46 @@
self.assertEqual(sys.getrecursionlimit(), 10000)
sys.setrecursionlimit(oldlimit)
+ def test_recursionlimit_recovery(self):
+ # NOTE: this test is slightly fragile in that it depends on the current
+ # recursion count when executing the test being low enough so as to
+ # trigger the recursion recovery detection in the _Py_MakeEndRecCheck
+ # macro (see ceval.h).
+ oldlimit = sys.getrecursionlimit()
+ def f():
+ f()
+ try:
+ for i in (50, 1000):
+ # Issue #5392: stack overflow after hitting recursion limit twice
+ sys.setrecursionlimit(i)
+ self.assertRaises(RuntimeError, f)
+ self.assertRaises(RuntimeError, f)
+ finally:
+ sys.setrecursionlimit(oldlimit)
+
+ 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 RuntimeError:
+ f()
+
+ sys.setrecursionlimit(%d)
+ f()""")
+ 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.assertTrue(
+ b"Fatal Python error: Cannot recover from stack overflow" in err,
+ err)
+
def test_getwindowsversion(self):
if hasattr(sys, "getwindowsversion"):
v = sys.getwindowsversion()
diff --git a/Misc/NEWS b/Misc/NEWS
index 70ad5e2..4468df6 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@
Core and Builtins
-----------------
+- Issue #5392: when a very low recursion limit was set, the interpreter would
+ abort with a fatal error after the recursion limit was hit twice.
+
Library
-------
@@ -24,8 +27,6 @@
Core and Builtins
-----------------
-=======
-
- The io module has been reimplemented in C for speed.
- Give dict views an informative __repr__.