Skip to content

Commit 50a595b

Browse files
authored
[3.13] gh-122712: Guard against __code__ reassignment in CALL_ALLOC_AND_ENTER_INIT (GH-122713) (GH-123184)
1 parent 159db05 commit 50a595b

File tree

3 files changed

+36
-7
lines changed

3 files changed

+36
-7
lines changed

Lib/test/test_opcache.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ def wrapper(*args, **kwargs):
2828
return wrapper
2929

3030

31+
class TestBase(unittest.TestCase):
32+
def assert_specialized(self, f, opname):
33+
instructions = dis.get_instructions(f, adaptive=True)
34+
opnames = {instruction.opname for instruction in instructions}
35+
self.assertIn(opname, opnames)
36+
37+
3138
class TestLoadSuperAttrCache(unittest.TestCase):
3239
def test_descriptor_not_double_executed_on_spec_fail(self):
3340
calls = []
@@ -479,7 +486,7 @@ def f():
479486
self.assertFalse(f())
480487

481488

482-
class TestCallCache(unittest.TestCase):
489+
class TestCallCache(TestBase):
483490
def test_too_many_defaults_0(self):
484491
def f():
485492
pass
@@ -507,22 +514,40 @@ def f(x, y):
507514
f(None)
508515
f()
509516

517+
@disabling_optimizer
518+
@requires_specialization
519+
def test_assign_init_code(self):
520+
class MyClass:
521+
def __init__(self):
522+
pass
523+
524+
def instantiate():
525+
return MyClass()
526+
527+
# Trigger specialization
528+
for _ in range(1025):
529+
instantiate()
530+
self.assert_specialized(instantiate, "CALL_ALLOC_AND_ENTER_INIT")
531+
532+
def count_args(self, *args):
533+
self.num_args = len(args)
534+
535+
# Set MyClass.__init__.__code__ to a code object that is incompatible
536+
# (uses varargs) with the current specialization
537+
MyClass.__init__.__code__ = count_args.__code__
538+
instantiate()
539+
510540

511541
@threading_helper.requires_working_threading()
512542
@requires_specialization
513-
class TestRacesDoNotCrash(unittest.TestCase):
543+
class TestRacesDoNotCrash(TestBase):
514544
# Careful with these. Bigger numbers have a higher chance of catching bugs,
515545
# but you can also burn through a *ton* of type/dict/function versions:
516546
ITEMS = 1000
517547
LOOPS = 4
518548
WARMUPS = 2
519549
WRITERS = 2
520550

521-
def assert_specialized(self, f, opname):
522-
instructions = dis.get_instructions(f, adaptive=True)
523-
opnames = {instruction.opname for instruction in instructions}
524-
self.assertIn(opname, opnames)
525-
526551
@disabling_optimizer
527552
def assert_races_do_not_crash(
528553
self, opname, get_items, read, write, *, check_items=False

Python/bytecodes.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3407,6 +3407,8 @@ dummy_func(
34073407
PyFunctionObject *init = (PyFunctionObject *)cls->_spec_cache.init;
34083408
PyCodeObject *code = (PyCodeObject *)init->func_code;
34093409
DEOPT_IF(code->co_argcount != oparg+1);
3410+
DEOPT_IF((code->co_flags & (CO_VARKEYWORDS | CO_VARARGS | CO_OPTIMIZED)) != CO_OPTIMIZED);
3411+
DEOPT_IF(code->co_kwonlyargcount);
34103412
DEOPT_IF(!_PyThreadState_HasStackSpace(tstate, code->co_framesize + _Py_InitCleanup.co_framesize));
34113413
STAT_INC(CALL, hit);
34123414
PyObject *self = _PyType_NewManagedObject(tp);

Python/generated_cases.c.h

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)