]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] GH-107263: Increase C stack limit for most functions, except `_PyEval_EvalFram...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 4 Aug 2023 10:25:51 +0000 (03:25 -0700)
committerGitHub <noreply@github.com>
Fri, 4 Aug 2023 10:25:51 +0000 (12:25 +0200)
GH-107263: Increase C stack limit for most functions, except `_PyEval_EvalFrameDefault()` (GH-107535)

* Set C recursion limit to 1500, set cost of eval loop to 2 frames, and compiler mutliply to 2.
(cherry picked from commit fa45958450aa3489607daf9855ca0474a2a20878)

Co-authored-by: Mark Shannon <mark@hotpy.org>
21 files changed:
Doc/whatsnew/3.12.rst
Include/cpython/pystate.h
Lib/test/list_tests.py
Lib/test/mapping_tests.py
Lib/test/support/__init__.py
Lib/test/test_ast.py
Lib/test/test_call.py
Lib/test/test_compile.py
Lib/test/test_dict.py
Lib/test/test_dictviews.py
Lib/test/test_exception_group.py
Lib/test/test_zlib.py
Misc/NEWS.d/next/Core and Builtins/2023-07-30-05-20-16.gh-issue-107263.q0IU2M.rst [new file with mode: 0644]
Parser/asdl_c.py
Python/Python-ast.c
Python/ast.c
Python/ast_opt.c
Python/bytecodes.c
Python/ceval.c
Python/generated_cases.c.h
Python/symtable.c

index 39e702bb012fb936aec1209470d6a8a41c4bf0ca..79e618588143a8ae70066657c8e832e3a0524b32 100644 (file)
@@ -802,6 +802,11 @@ sys
   exception instance, rather than to a ``(typ, exc, tb)`` tuple.
   (Contributed by Irit Katriel in :gh:`103176`.)
 
+* :func:`sys.setrecursionlimit` and :func:`sys.getrecursionlimit`.
+  The recursion limit now applies only to Python code. Builtin functions do
+  not use the recursion limit, but are protected by a different mechanism
+  that prevents recursion from causing a virtual machine crash.
+
 tempfile
 --------
 
index f33c72d4cf4d2abf4c000f2325ab30ec9b9ec087..628f2e0996e4695b0d72516cf69d9fb1847eba32 100644 (file)
@@ -255,7 +255,8 @@ struct _ts {
 #  ifdef __wasi__
 #    define C_RECURSION_LIMIT 500
 #  else
-#    define C_RECURSION_LIMIT 800
+    // This value is duplicated in Lib/test/support/__init__.py
+#    define C_RECURSION_LIMIT 1500
 #  endif
 #endif
 
index fe3ee80b8d461f547e8501ac3b07fee480585e8d..b1ef332522d2ce5cfefb1fb3638d7c326ea77f5c 100644 (file)
@@ -6,7 +6,7 @@ import sys
 from functools import cmp_to_key
 
 from test import seq_tests
-from test.support import ALWAYS_EQ, NEVER_EQ
+from test.support import ALWAYS_EQ, NEVER_EQ, C_RECURSION_LIMIT
 
 
 class CommonTest(seq_tests.CommonTest):
@@ -61,7 +61,7 @@ class CommonTest(seq_tests.CommonTest):
 
     def test_repr_deep(self):
         a = self.type2test([])
-        for i in range(sys.getrecursionlimit() + 100):
+        for i in range(C_RECURSION_LIMIT + 1):
             a = self.type2test([a])
         self.assertRaises(RecursionError, repr, a)
 
index 613206a0855aea858819afc51becb874f558a4bc..5492bbf86d1f879fba9000a58d36d085e0a2f7cb 100644 (file)
@@ -2,6 +2,7 @@
 import unittest
 import collections
 import sys
+from test.support import C_RECURSION_LIMIT
 
 
 class BasicTestMappingProtocol(unittest.TestCase):
@@ -624,7 +625,7 @@ class TestHashMappingProtocol(TestMappingProtocol):
 
     def test_repr_deep(self):
         d = self._empty_mapping()
-        for i in range(sys.getrecursionlimit() + 100):
+        for i in range(C_RECURSION_LIMIT + 1):
             d0 = d
             d = self._empty_mapping()
             d[1] = d0
index 3b332f49951f0c86ab1afe602ba2f7debf0bce1b..c3c3cf0a71596cce01fada828b92866351941ce9 100644 (file)
@@ -64,7 +64,8 @@ __all__ = [
     "run_with_tz", "PGO", "missing_compiler_executable",
     "ALWAYS_EQ", "NEVER_EQ", "LARGEST", "SMALLEST",
     "LOOPBACK_TIMEOUT", "INTERNET_TIMEOUT", "SHORT_TIMEOUT", "LONG_TIMEOUT",
-    "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT",
+    "Py_DEBUG", "EXCEEDS_RECURSION_LIMIT", "C_RECURSION_LIMIT",
+    "skip_on_s390x",
     ]
 
 
@@ -2460,3 +2461,10 @@ def adjust_int_max_str_digits(max_digits):
 
 #For recursion tests, easily exceeds default recursion limit
 EXCEEDS_RECURSION_LIMIT = 5000
+
+# The default C recursion limit (from Include/cpython/pystate.h).
+C_RECURSION_LIMIT = 1500
+
+#Windows doesn't have os.uname() but it doesn't support s390x.
+skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
+                                'skipped on s390x')
index a03fa4c7187b05295d6a34423d2fa5cb10691957..5346b39043f0f5c156e36887a7fd0368eba700eb 100644 (file)
@@ -1084,6 +1084,7 @@ class AST_Tests(unittest.TestCase):
                     return self
         enum._test_simple_enum(_Precedence, ast._Precedence)
 
+    @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
     @support.cpython_only
     def test_ast_recursion_limit(self):
         fail_depth = support.EXCEEDS_RECURSION_LIMIT
index 12759c53bb662c19e2b5f7a7be527c5dad866fcf..ec8dc29d36c16a659c89137ab90181497d700a30 100644 (file)
@@ -1,5 +1,5 @@
 import unittest
-from test.support import cpython_only, requires_limited_api
+from test.support import cpython_only, requires_limited_api, skip_on_s390x
 try:
     import _testcapi
 except ImportError:
@@ -931,6 +931,7 @@ class TestErrorMessagesUseQualifiedName(unittest.TestCase):
 @cpython_only
 class TestRecursion(unittest.TestCase):
 
+    @skip_on_s390x
     def test_super_deep(self):
 
         def recurse(n):
index 784c0550cc09b10b0553c45a96a89f23b1e5e0fa..142d3f5c125b6d6bfdfa66109f3f7de606e30665 100644 (file)
@@ -11,10 +11,9 @@ import textwrap
 import warnings
 from test import support
 from test.support import (script_helper, requires_debug_ranges,
-                          requires_specialization)
+                          requires_specialization, C_RECURSION_LIMIT)
 from test.support.os_helper import FakePath
 
-
 class TestSpecifics(unittest.TestCase):
 
     def compile_single(self, source):
@@ -112,7 +111,7 @@ class TestSpecifics(unittest.TestCase):
 
     @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
     def test_extended_arg(self):
-        repeat = 2000
+        repeat = int(C_RECURSION_LIMIT * 0.9)
         longexpr = 'x = x or ' + '-x' * repeat
         g = {}
         code = textwrap.dedent('''
@@ -558,16 +557,12 @@ class TestSpecifics(unittest.TestCase):
     @support.cpython_only
     @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI")
     def test_compiler_recursion_limit(self):
-        # Expected limit is sys.getrecursionlimit() * the scaling factor
-        # in symtable.c (currently 3)
-        # We expect to fail *at* that limit, because we use up some of
-        # the stack depth limit in the test suite code
-        # So we check the expected limit and 75% of that
-        # XXX (ncoghlan): duplicating the scaling factor here is a little
-        # ugly. Perhaps it should be exposed somewhere...
-        fail_depth = sys.getrecursionlimit() * 3
-        crash_depth = sys.getrecursionlimit() * 300
-        success_depth = int(fail_depth * 0.75)
+        # Expected limit is C_RECURSION_LIMIT * 2
+        # Duplicating the limit here is a little ugly.
+        # Perhaps it should be exposed somewhere...
+        fail_depth = C_RECURSION_LIMIT * 2 + 1
+        crash_depth = C_RECURSION_LIMIT * 100
+        success_depth = int(C_RECURSION_LIMIT * 1.8)
 
         def check_limit(prefix, repeated, mode="single"):
             expect_ok = prefix + repeated * success_depth
index 79638340059f650d03dba441d26ae8bbf75832a0..fbc6ce8282de3cefbbb2d92ebec35b0060d9f792 100644 (file)
@@ -8,7 +8,7 @@ import sys
 import unittest
 import weakref
 from test import support
-from test.support import import_helper
+from test.support import import_helper, C_RECURSION_LIMIT
 
 
 class DictTest(unittest.TestCase):
@@ -596,7 +596,7 @@ class DictTest(unittest.TestCase):
 
     def test_repr_deep(self):
         d = {}
-        for i in range(sys.getrecursionlimit() + 100):
+        for i in range(C_RECURSION_LIMIT + 1):
             d = {1: d}
         self.assertRaises(RecursionError, repr, d)
 
index 924f4a6829e19c3f16cfad7bba70135611bdff23..2bd9d6eef8cfc617407932faf6228b478caa6f77 100644 (file)
@@ -3,6 +3,7 @@ import copy
 import pickle
 import sys
 import unittest
+from test.support import C_RECURSION_LIMIT
 
 class DictSetTest(unittest.TestCase):
 
@@ -279,7 +280,7 @@ class DictSetTest(unittest.TestCase):
 
     def test_deeply_nested_repr(self):
         d = {}
-        for i in range(sys.getrecursionlimit() + 100):
+        for i in range(C_RECURSION_LIMIT//2 + 100):
             d = {42: d.values()}
         self.assertRaises(RecursionError, repr, d)
 
index fa159a76ec1aff6604afe509cf3a8922d1fd803e..22be3a7795cb9a336bcab3294c7230a5ba97827d 100644 (file)
@@ -1,7 +1,7 @@
 import collections.abc
 import types
 import unittest
-
+from test.support import C_RECURSION_LIMIT
 
 class TestExceptionGroupTypeHierarchy(unittest.TestCase):
     def test_exception_group_types(self):
@@ -433,7 +433,7 @@ class ExceptionGroupSplitTests(ExceptionGroupTestBase):
 class DeepRecursionInSplitAndSubgroup(unittest.TestCase):
     def make_deep_eg(self):
         e = TypeError(1)
-        for i in range(2000):
+        for i in range(C_RECURSION_LIMIT + 1):
             e = ExceptionGroup('eg', [e])
         return e
 
index 3dac70eb12852cd82fe032f51ea9f0014d92ccfc..2113757254c0ed920db133f9107399a914a9f62d 100644 (file)
@@ -7,7 +7,7 @@ import os
 import pickle
 import random
 import sys
-from test.support import bigmemtest, _1G, _4G
+from test.support import bigmemtest, _1G, _4G, skip_on_s390x
 
 
 zlib = import_helper.import_module('zlib')
@@ -44,10 +44,7 @@ requires_Decompress_copy = unittest.skipUnless(
 #   zlib.decompress(func1(data)) == zlib.decompress(func2(data)) == data
 #
 # Make the assumption that s390x always has an accelerator to simplify the skip
-# condition. Windows doesn't have os.uname() but it doesn't support s390x.
-skip_on_s390x = unittest.skipIf(hasattr(os, 'uname') and os.uname().machine == 's390x',
-                                'skipped on s390x')
-
+# condition.
 
 class VersionTestCase(unittest.TestCase):
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-07-30-05-20-16.gh-issue-107263.q0IU2M.rst b/Misc/NEWS.d/next/Core and Builtins/2023-07-30-05-20-16.gh-issue-107263.q0IU2M.rst
new file mode 100644 (file)
index 0000000..fb0940b
--- /dev/null
@@ -0,0 +1,3 @@
+Increase C recursion limit for functions other than the main interpreter
+from 800 to 1500. This should allow functions like ``list.__repr__`` and
+``json.dumps`` to handle all the inputs that they could prior to 3.12
index d4763ea260a5a21652261b12c363e32f9ca0d924..aa093b30a74d70adbfd122abb3ae0bfda5a69b06 100755 (executable)
@@ -1393,7 +1393,7 @@ PyObject* PyAST_mod2obj(mod_ty t)
 
     int starting_recursion_depth;
     /* Be careful here to prevent overflow. */
-    int COMPILER_STACK_FRAME_SCALE = 3;
+    int COMPILER_STACK_FRAME_SCALE = 2;
     PyThreadState *tstate = _PyThreadState_GET();
     if (!tstate) {
         return 0;
index 5db9ade3af4547ad90516ba4dbe1d3a47e235b16..f8bb6124fa48786b06e088ba05b0cd3e7f99b22c 100644 (file)
@@ -13074,7 +13074,7 @@ PyObject* PyAST_mod2obj(mod_ty t)
 
     int starting_recursion_depth;
     /* Be careful here to prevent overflow. */
-    int COMPILER_STACK_FRAME_SCALE = 3;
+    int COMPILER_STACK_FRAME_SCALE = 2;
     PyThreadState *tstate = _PyThreadState_GET();
     if (!tstate) {
         return 0;
index 68600ce683b97498a6d524ee9c037d317b1095d8..74c97f948d15e63d5b02fade7b09d7188218adc4 100644 (file)
@@ -1029,7 +1029,7 @@ validate_type_params(struct validator *state, asdl_type_param_seq *tps)
 
 
 /* See comments in symtable.c. */
-#define COMPILER_STACK_FRAME_SCALE 3
+#define COMPILER_STACK_FRAME_SCALE 2
 
 int
 _PyAST_Validate(mod_ty mod)
index 274bd134e1435be6bc28d2ad09aafdab3d9b410a..f8c4a9513236b98322f09e7b67d10dab0e61caf8 100644 (file)
@@ -1103,7 +1103,7 @@ astfold_type_param(type_param_ty node_, PyArena *ctx_, _PyASTOptimizeState *stat
 #undef CALL_SEQ
 
 /* See comments in symtable.c. */
-#define COMPILER_STACK_FRAME_SCALE 3
+#define COMPILER_STACK_FRAME_SCALE 2
 
 int
 _PyAST_Optimize(mod_ty mod, PyArena *arena, _PyASTOptimizeState *state)
index 7ee48c70b000541b9e212d9db8fde9161ad8a13c..dc2ae221f0bdb1b2357cc7709b11fda7b8863e64 100644 (file)
@@ -635,7 +635,7 @@ dummy_func(
             tstate->cframe = cframe.previous;
             assert(tstate->cframe->current_frame == frame->previous);
             assert(!_PyErr_Occurred(tstate));
-            _Py_LeaveRecursiveCallTstate(tstate);
+            tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
             return retval;
         }
 
index 494742061621308c6defa9969edddad432a082dd..88d6362bf48a1f06805b462968ca7b03be7c59ca 100644 (file)
@@ -637,6 +637,11 @@ static inline void _Py_LeaveRecursiveCallPy(PyThreadState *tstate)  {
 #  pragma warning(disable:4102)
 #endif
 
+
+/* _PyEval_EvalFrameDefault() is a *big* function,
+ * so consume 3 units of C stack */
+#define PY_EVAL_C_STACK_UNITS 2
+
 PyObject* _Py_HOT_FUNCTION
 _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag)
 {
@@ -691,6 +696,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
     frame->previous = &entry_frame;
     cframe.current_frame = frame;
 
+    tstate->c_recursion_remaining -= (PY_EVAL_C_STACK_UNITS - 1);
     if (_Py_EnterRecursiveCallTstate(tstate, "")) {
         tstate->c_recursion_remaining--;
         tstate->py_recursion_remaining--;
@@ -990,7 +996,7 @@ exit_unwind:
         /* Restore previous cframe and exit */
         tstate->cframe = cframe.previous;
         assert(tstate->cframe->current_frame == frame->previous);
-        _Py_LeaveRecursiveCallTstate(tstate);
+        tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
         return NULL;
     }
 
index eabb9fe5fac4d5236f00be678eb76006353b51ab..b0a363ce9aa111ba36c5645e4f337fd33532b018 100644 (file)
             tstate->cframe = cframe.previous;
             assert(tstate->cframe->current_frame == frame->previous);
             assert(!_PyErr_Occurred(tstate));
-            _Py_LeaveRecursiveCallTstate(tstate);
+            tstate->c_recursion_remaining += PY_EVAL_C_STACK_UNITS;
             return retval;
             #line 928 "Python/generated_cases.c.h"
         }
index e2c00d17480dd135414ee171b9e0175f8f519a6c..115882da09b75a4828a09da82dc9f2b22cbfd014 100644 (file)
@@ -282,17 +282,10 @@ symtable_new(void)
     return NULL;
 }
 
-/* When compiling the use of C stack is probably going to be a lot
-   lighter than when executing Python code but still can overflow
-   and causing a Python crash if not checked (e.g. eval("()"*300000)).
-   Using the current recursion limit for the compiler seems too
-   restrictive (it caused at least one test to fail) so a factor is
-   used to allow deeper recursion when compiling an expression.
-
-   Using a scaling factor means this should automatically adjust when
+/* Using a scaling factor means this should automatically adjust when
    the recursion limit is adjusted for small or large C stack allocations.
 */
-#define COMPILER_STACK_FRAME_SCALE 3
+#define COMPILER_STACK_FRAME_SCALE 2
 
 struct symtable *
 _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)