diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index a3d31bcbc4ab54..610e3cc2f2024d 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1221,7 +1221,7 @@ _PyOpcode_macro_expansion[256] = { [BUILD_STRING] = { .nuops = 1, .uops = { { _BUILD_STRING, 0, 0 } } }, [BUILD_TUPLE] = { .nuops = 1, .uops = { { _BUILD_TUPLE, 0, 0 } } }, [CALL_BOUND_METHOD_EXACT_ARGS] = { .nuops = 9, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _INIT_CALL_BOUND_METHOD_EXACT_ARGS, 0, 0 }, { _CHECK_FUNCTION_EXACT_ARGS, 2, 1 }, { _CHECK_STACK_SPACE, 0, 0 }, { _CHECK_RECURSION_REMAINING, 0, 0 }, { _INIT_CALL_PY_EXACT_ARGS, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, - [CALL_BOUND_METHOD_GENERAL] = { .nuops = 7, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_METHOD_VERSION, 2, 1 }, { _EXPAND_METHOD, 0, 0 }, { _CHECK_RECURSION_REMAINING, 0, 0 }, { _PY_FRAME_GENERAL, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, + [CALL_BOUND_METHOD_GENERAL] = { .nuops = 7, .uops = { { _CHECK_PEP_523, 0, 0 }, { _CHECK_RECURSION_REMAINING, 0, 0 }, { _CHECK_METHOD_VERSION, 2, 1 }, { _EXPAND_METHOD, 0, 0 }, { _PY_FRAME_GENERAL, 0, 0 }, { _SAVE_RETURN_OFFSET, 7, 3 }, { _PUSH_FRAME, 0, 0 } } }, [CALL_BUILTIN_CLASS] = { .nuops = 2, .uops = { { _CALL_BUILTIN_CLASS, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, [CALL_BUILTIN_FAST] = { .nuops = 2, .uops = { { _CALL_BUILTIN_FAST, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, [CALL_BUILTIN_FAST_WITH_KEYWORDS] = { .nuops = 2, .uops = { { _CALL_BUILTIN_FAST_WITH_KEYWORDS, 0, 0 }, { _CHECK_PERIODIC, 0, 0 } } }, diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-19-23-12.gh-issue-145008.tHYQv6.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-19-23-12.gh-issue-145008.tHYQv6.rst new file mode 100644 index 00000000000000..d378d2acb6924a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-19-23-12.gh-issue-145008.tHYQv6.rst @@ -0,0 +1 @@ +Fix a bug when calling certain methods at the recursion limit which manifested as a corruption of Python's operand stack. Patch by Ken Jin. diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 42cddac048f3f6..24568489f2d25d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -3241,9 +3241,11 @@ dummy_func( macro(CALL_BOUND_METHOD_GENERAL) = unused/1 + // Skip over the counter _CHECK_PEP_523 + + // gh-145008: We must check recursion before expanding method, + // otherwise we may leave the stack in an inconsistent state in 3.13. + _CHECK_RECURSION_REMAINING + _CHECK_METHOD_VERSION + _EXPAND_METHOD + - _CHECK_RECURSION_REMAINING + _PY_FRAME_GENERAL + _SAVE_RETURN_OFFSET + _PUSH_FRAME; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index d21499b2f5724a..ad49becdfcaa67 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1024,6 +1024,10 @@ { DEOPT_IF(tstate->interp->eval_frame, CALL); } + // _CHECK_RECURSION_REMAINING + { + DEOPT_IF(tstate->py_recursion_remaining <= 1, CALL); + } // _CHECK_METHOD_VERSION null = stack_pointer[-1 - oparg]; callable = stack_pointer[-2 - oparg]; @@ -1047,10 +1051,6 @@ Py_INCREF(method); Py_DECREF(callable); } - // _CHECK_RECURSION_REMAINING - { - DEOPT_IF(tstate->py_recursion_remaining <= 1, CALL); - } // _PY_FRAME_GENERAL args = &stack_pointer[-oparg]; self_or_null = self;