diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 36efab518781410..41f2c3fb153bdd1 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -1812,6 +1812,37 @@ def testfunc(n): # __init__ resolution allows promotion of range to constant self.assertNotIn("_LOAD_GLOBAL_BUILTINS", uops) + # See https://github.com/python/cpython/issues/158072 + def test_init_with_default_argument(self): + script_helper.assert_python_ok("-c", textwrap.dedent(f"""\ + sentinel = object() + + class WithDefault: + def __init__(self, value=sentinel): + if value is not sentinel: + pass + + for _ in range({TIER2_THRESHOLD * 3}): + WithDefault() + """), PYTHON_JIT="1") + + def test_init_with_changed_code_argcount(self): + class C: + def __init__(self, value): + self.value = False + + def varargs_init(self, *args): + self.value = isinstance(args, tuple) + + def testfunc(n): + for _ in range(n): + result = C(42).value + return result + + self.assertFalse(testfunc(100)) + C.__init__.__code__ = C.varargs_init.__code__ + self.assertTrue(testfunc(TIER2_THRESHOLD * 3)) + def test_init_guards_removed(self): class MyPoint: def __init__(self, x, y): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-24-18-44-40.gh-issue-158072.mHveWw.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-24-18-44-40.gh-issue-158072.mHveWw.rst new file mode 100644 index 000000000000000..faff8fff4aec5c5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-24-18-44-40.gh-issue-158072.mHveWw.rst @@ -0,0 +1 @@ +Fix a JIT crash when a class is instantiated with their default arguments diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 79f81482d247e37..3147e9c5065edd2 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -1388,17 +1388,27 @@ _Py_uop_frame_new( } // Initialize with the initial state of all local variables + arg_len = Py_MIN(arg_len, co->co_argcount); for (int i = 0; i < arg_len; i++) { frame->locals[i] = PyJitRef_RemoveUnique(args[i]); } - // If the args are known, then it's safe to just initialize - // every other non-set local to null symbol. - bool default_null = args != NULL; + // When args is available, missing parameters get defaults or *args/**kwargs. + // Other locals start as NULL. When args is NULL, treat all locals as unknown. + int parameter_count = co->co_argcount + co->co_kwonlyargcount; + parameter_count += (co->co_flags & CO_VARARGS) != 0; + parameter_count += (co->co_flags & CO_VARKEYWORDS) != 0; for (int i = arg_len; i < co->co_nlocalsplus; i++) { - JitOptRef local = default_null ? _Py_uop_sym_new_null(ctx) : _Py_uop_sym_new_unknown(ctx); - frame->locals[i] = local; + if (args == NULL) { + frame->locals[i] = _Py_uop_sym_new_unknown(ctx); + } + else if (i < parameter_count) { + frame->locals[i] = _Py_uop_sym_new_not_null(ctx); + } + else { + frame->locals[i] = _Py_uop_sym_new_null(ctx); + } } frame->callable = _Py_uop_sym_new_not_null(ctx);