]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-137883: Check the recursion limit for specialized keyword argument calls (GH-137887)
authorPeter Bierma <zintensitydev@gmail.com>
Tue, 19 Aug 2025 08:53:38 +0000 (04:53 -0400)
committerGitHub <noreply@github.com>
Tue, 19 Aug 2025 08:53:38 +0000 (09:53 +0100)
Include/internal/pycore_opcode_metadata.h
Lib/test/test_call.py
Misc/NEWS.d/next/Core_and_Builtins/2025-08-17-13-36-53.gh-issue-137883.55VDCN.rst [new file with mode: 0644]
Python/bytecodes.c
Python/generated_cases.c.h

index 7f468bbb9321847ebba563e5afc98d54f8874ebb..bd6b84ec7fd908b3a6e4d202cc7b08897596d9bb 100644 (file)
@@ -1354,7 +1354,7 @@ _PyOpcode_macro_expansion[256] = {
     [CALL_ISINSTANCE] = { .nuops = 3, .uops = { { _GUARD_THIRD_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_ISINSTANCE, OPARG_SIMPLE, 3 }, { _CALL_ISINSTANCE, OPARG_SIMPLE, 3 } } },
     [CALL_KW_BOUND_METHOD] = { .nuops = 6, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_METHOD_VERSION_KW, 2, 1 }, { _EXPAND_METHOD_KW, OPARG_SIMPLE, 3 }, { _PY_FRAME_KW, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } },
     [CALL_KW_NON_PY] = { .nuops = 3, .uops = { { _CHECK_IS_NOT_PY_CALLABLE_KW, OPARG_SIMPLE, 3 }, { _CALL_KW_NON_PY, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } },
-    [CALL_KW_PY] = { .nuops = 5, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_FUNCTION_VERSION_KW, 2, 1 }, { _PY_FRAME_KW, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } },
+    [CALL_KW_PY] = { .nuops = 6, .uops = { { _CHECK_PEP_523, OPARG_SIMPLE, 1 }, { _CHECK_FUNCTION_VERSION_KW, 2, 1 }, { _CHECK_RECURSION_REMAINING, OPARG_SIMPLE, 3 }, { _PY_FRAME_KW, OPARG_SIMPLE, 3 }, { _SAVE_RETURN_OFFSET, OPARG_SAVE_RETURN_OFFSET, 3 }, { _PUSH_FRAME, OPARG_SIMPLE, 3 } } },
     [CALL_LEN] = { .nuops = 3, .uops = { { _GUARD_NOS_NULL, OPARG_SIMPLE, 3 }, { _GUARD_CALLABLE_LEN, OPARG_SIMPLE, 3 }, { _CALL_LEN, OPARG_SIMPLE, 3 } } },
     [CALL_LIST_APPEND] = { .nuops = 4, .uops = { { _GUARD_CALLABLE_LIST_APPEND, OPARG_SIMPLE, 3 }, { _GUARD_NOS_NOT_NULL, OPARG_SIMPLE, 3 }, { _GUARD_NOS_LIST, OPARG_SIMPLE, 3 }, { _CALL_LIST_APPEND, OPARG_SIMPLE, 3 } } },
     [CALL_METHOD_DESCRIPTOR_FAST] = { .nuops = 2, .uops = { { _CALL_METHOD_DESCRIPTOR_FAST, OPARG_SIMPLE, 3 }, { _CHECK_PERIODIC_AT_END, OPARG_REPLACED, 3 } } },
index 1c73aaafb71fd5081aa6f9cf20e09489072d51bc..2c28f106ec7cb68552fece184bb3cd50522e0629 100644 (file)
@@ -1074,6 +1074,14 @@ class TestRecursion(unittest.TestCase):
             with self.assertRaises(RecursionError):
                 c_py_recurse(100_000)
 
+    def test_recursion_with_kwargs(self):
+        # GH-137883: The interpreter forgot to check the recursion limit when
+        # calling with keywords.
+        def recurse_kw(a=0):
+            recurse_kw(a=0)
+        with self.assertRaises(RecursionError):
+            recurse_kw()
+
 
 class TestFunctionWithManyArgs(unittest.TestCase):
     def test_function_with_many_args(self):
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-17-13-36-53.gh-issue-137883.55VDCN.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-17-13-36-53.gh-issue-137883.55VDCN.rst
new file mode 100644 (file)
index 0000000..0bdb263
--- /dev/null
@@ -0,0 +1 @@
+Fix runaway recursion when calling a function with keyword arguments.
index 8a60e48cd465b5c21c78480c86cafc657590beef..21bae689320eaeec9b32a2c60561cf2b6190e207 100644 (file)
@@ -4721,6 +4721,7 @@ dummy_func(
             unused/1 + // Skip over the counter
             _CHECK_PEP_523 +
             _CHECK_FUNCTION_VERSION_KW +
+            _CHECK_RECURSION_REMAINING +
             _PY_FRAME_KW +
             _SAVE_RETURN_OFFSET +
             _PUSH_FRAME;
index d63225bb0cd3cd42979a271e7e6432d644981931..b6d183a3e63a9c9b15e829ec94b0d647d6ec9b78 100644 (file)
                     JUMP_TO_PREDICTED(CALL_KW);
                 }
             }
+            // _CHECK_RECURSION_REMAINING
+            {
+                if (tstate->py_recursion_remaining <= 1) {
+                    UPDATE_MISS_STATS(CALL_KW);
+                    assert(_PyOpcode_Deopt[opcode] == (CALL_KW));
+                    JUMP_TO_PREDICTED(CALL_KW);
+                }
+            }
             // _PY_FRAME_KW
             {
                 kwnames = stack_pointer[-1];