diff -r e377d568928b Doc/library/sys.rst --- a/Doc/library/sys.rst Thu Oct 01 11:55:52 2015 +0300 +++ b/Doc/library/sys.rst Thu Oct 01 12:53:40 2015 +0200 @@ -975,6 +975,12 @@ always available. that supports a higher limit. This should be done with care, because a too-high limit can lead to a crash. + The lowest possible limit depends on the current recursion depth. + + .. versionchanged:: 3.6 + A :exc:`RecursionError` exception is now raised to prevent a crash + if the new limit is too low depending on the current recursion depth. + .. function:: setswitchinterval(interval) diff -r e377d568928b Lib/test/test_sys.py --- a/Lib/test/test_sys.py Thu Oct 01 11:55:52 2015 +0300 +++ b/Lib/test/test_sys.py Thu Oct 01 12:53:40 2015 +0200 @@ -201,25 +201,58 @@ class SysModuleTest(unittest.TestCase): if hasattr(sys, 'gettrace') and sys.gettrace(): self.skipTest('fatal error if run with a trace function') - # 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: - # FIXME: workaround crash for the issue #25274 - # FIXME: until the crash is fixed - #for i in (50, 1000): - for i in (150, 1000): - # Issue #5392: stack overflow after hitting recursion limit twice - sys.setrecursionlimit(i) + for depth in (10, 25, 50, 75, 100, 250, 1000): + try: + sys.setrecursionlimit(depth) + except RecursionError: + # Issue #25274: The recusion limit is too low for the + # current recursion depth + continue + + # Issue #5392: test stack overflow after hitting recursion + # limit twice self.assertRaises(RecursionError, f) self.assertRaises(RecursionError, f) finally: sys.setrecursionlimit(oldlimit) + @test.support.cpython_only + def test_setrecursionlimit_recusion_depth(self): + # Issue #25274: Setting a low recursion limit must be blocked if the + # current recursion depth is already higher than LIMIT * 3 // 4. + # Otherwise, the overflowed flag cannot be reset anymore. + + from _testcapi import get_recursion_depth + + def set_recursion_limit_at_depth(depth, limit): + recursion_depth = get_recursion_depth() + if recursion_depth >= depth: + with self.assertRaises(RecursionError) as cm: + sys.setrecursionlimit(limit) + self.assertRegex(str(cm.exception), + "cannot set recursion limit to [0-9]+ at " + "the recursion depth [0-9]+: " + "the limit is too low") + else: + set_recursion_limit_at_depth(depth, limit) + + oldlimit = sys.getrecursionlimit() + try: + sys.setrecursionlimit(1000) + + for limit in (10, 25, 50, 75, 100, 150, 200): + if limit > 100: + depth = limit - 50 + else: + depth = limit * 3 // 4 + set_recursion_limit_at_depth(depth, limit) + 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. diff -r e377d568928b Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c Thu Oct 01 11:55:52 2015 +0300 +++ b/Modules/_testcapimodule.c Thu Oct 01 12:53:40 2015 +0200 @@ -3520,6 +3520,15 @@ test_PyTime_AsMicroseconds(PyObject *sel return _PyTime_AsNanosecondsObject(ms); } +static PyObject* +get_recursion_depth(PyObject *self, PyObject *args) +{ + PyThreadState *tstate = PyThreadState_GET(); + + /* substract one to ignore the frame of the get_recursion_depth() call */ + return PyLong_FromLong(tstate->recursion_depth - 1); +} + static PyMethodDef TestMethods[] = { {"raise_exception", raise_exception, METH_VARARGS}, @@ -3696,6 +3705,7 @@ static PyMethodDef TestMethods[] = { #endif {"PyTime_AsMilliseconds", test_PyTime_AsMilliseconds, METH_VARARGS}, {"PyTime_AsMicroseconds", test_PyTime_AsMicroseconds, METH_VARARGS}, + {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; diff -r e377d568928b Python/sysmodule.c --- a/Python/sysmodule.c Thu Oct 01 11:55:52 2015 +0300 +++ b/Python/sysmodule.c Thu Oct 01 12:53:40 2015 +0200 @@ -632,14 +632,43 @@ processor's time-stamp counter." static PyObject * sys_setrecursionlimit(PyObject *self, PyObject *args) { - int new_limit; + int new_limit, min_limit; + PyThreadState *tstate; + if (!PyArg_ParseTuple(args, "i:setrecursionlimit", &new_limit)) return NULL; - if (new_limit <= 0) { + + if (new_limit < 1) { PyErr_SetString(PyExc_ValueError, - "recursion limit must be positive"); + "recursion limit must be greater or equal than 1"); return NULL; } + + /* Issue #25274: When the recursion depth hits the recursion limit in + _Py_CheckRecursiveCall(), the overflowed field of the thread state is + set to 1 and a RecursionError is raised. The overflowed field is reset + to 0 when the recursion depth goes below the low-water mark: see + Py_LeaveRecursiveCall(). + + Reject too low new limit if the current recursion depth is higher than + the new low-water mark, otherwise it may not be possible anymore to + reset overflowed to 0. + + See _Py_MakeEndRecCheck() for the low-water mark. + */ + if (new_limit > 100) + min_limit = new_limit - 50; + else + min_limit = new_limit * 3 / 4; + tstate = PyThreadState_GET(); + if (tstate->recursion_depth >= min_limit) { + PyErr_Format(PyExc_RecursionError, + "cannot set recursion limit to %i at " + "the recursion depth %i: the limit is too low", + new_limit, tstate->recursion_depth); + return NULL; + } + Py_SetRecursionLimit(new_limit); Py_INCREF(Py_None); return Py_None;