Skip to content

Commit 83de667

Browse files
authored
gh-158072: Fix JIT crash with omitted default arguments (GH-158128)
1 parent b98c065 commit 83de667

3 files changed

Lines changed: 47 additions & 5 deletions

File tree

‎Lib/test/test_capi/test_opt.py‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,37 @@ def testfunc(n):
18121812
# __init__ resolution allows promotion of range to constant
18131813
self.assertNotIn("_LOAD_GLOBAL_BUILTINS", uops)
18141814

1815+
# See https://github.com/python/cpython/issues/158072
1816+
def test_init_with_default_argument(self):
1817+
script_helper.assert_python_ok("-c", textwrap.dedent(f"""\
1818+
sentinel = object()
1819+
1820+
class WithDefault:
1821+
def __init__(self, value=sentinel):
1822+
if value is not sentinel:
1823+
pass
1824+
1825+
for _ in range({TIER2_THRESHOLD * 3}):
1826+
WithDefault()
1827+
"""), PYTHON_JIT="1")
1828+
1829+
def test_init_with_changed_code_argcount(self):
1830+
class C:
1831+
def __init__(self, value):
1832+
self.value = False
1833+
1834+
def varargs_init(self, *args):
1835+
self.value = isinstance(args, tuple)
1836+
1837+
def testfunc(n):
1838+
for _ in range(n):
1839+
result = C(42).value
1840+
return result
1841+
1842+
self.assertFalse(testfunc(100))
1843+
C.__init__.__code__ = C.varargs_init.__code__
1844+
self.assertTrue(testfunc(TIER2_THRESHOLD * 3))
1845+
18151846
def test_init_guards_removed(self):
18161847
class MyPoint:
18171848
def __init__(self, x, y):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a JIT crash when a class is instantiated with their default arguments

‎Python/optimizer_symbols.c‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,17 +1388,27 @@ _Py_uop_frame_new(
13881388
}
13891389

13901390
// Initialize with the initial state of all local variables
1391+
arg_len = Py_MIN(arg_len, co->co_argcount);
13911392
for (int i = 0; i < arg_len; i++) {
13921393
frame->locals[i] = PyJitRef_RemoveUnique(args[i]);
13931394
}
13941395

1395-
// If the args are known, then it's safe to just initialize
1396-
// every other non-set local to null symbol.
1397-
bool default_null = args != NULL;
1396+
// When args is available, missing parameters get defaults or *args/**kwargs.
1397+
// Other locals start as NULL. When args is NULL, treat all locals as unknown.
1398+
int parameter_count = co->co_argcount + co->co_kwonlyargcount;
1399+
parameter_count += (co->co_flags & CO_VARARGS) != 0;
1400+
parameter_count += (co->co_flags & CO_VARKEYWORDS) != 0;
13981401

13991402
for (int i = arg_len; i < co->co_nlocalsplus; i++) {
1400-
JitOptRef local = default_null ? _Py_uop_sym_new_null(ctx) : _Py_uop_sym_new_unknown(ctx);
1401-
frame->locals[i] = local;
1403+
if (args == NULL) {
1404+
frame->locals[i] = _Py_uop_sym_new_unknown(ctx);
1405+
}
1406+
else if (i < parameter_count) {
1407+
frame->locals[i] = _Py_uop_sym_new_not_null(ctx);
1408+
}
1409+
else {
1410+
frame->locals[i] = _Py_uop_sym_new_null(ctx);
1411+
}
14021412
}
14031413

14041414
frame->callable = _Py_uop_sym_new_not_null(ctx);

0 commit comments

Comments
 (0)