10000 [3.12] gh-110543: Fix CodeType.replace in presence of comprehensions … · python/cpython@4f976c3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4f976c3

Browse files
[3.12] gh-110543: Fix CodeType.replace in presence of comprehensions (GH-110586) (#111866)
gh-110543: Fix CodeType.replace in presence of comprehensions (GH-110586) (cherry picked from commit 0b718e6) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent 759168a commit 4f976c3

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

Lib/test/test_listcomps.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import doctest
22
import textwrap
3+
import types
34
import unittest
45

56

@@ -92,7 +93,8 @@
9293

9394

9495
class ListComprehensionTest(unittest.TestCase):
95-
def _check_in_scopes(self, code, outputs=None, ns=None, scopes=None, raises=()):
96+
def _check_in_scopes(self, code, outputs=None, ns=None, scopes=None, raises=(),
97+
exec_func=exec):
9698
code = textwrap.dedent(code)
9799
scopes = scopes or ["module", "class", "function"]
98100
for scope in scopes:
@@ -119,7 +121,7 @@ def get_output(moddict, name):
119121
return moddict[name]
120122
newns = ns.copy() if ns else {}
121123
try:
122-
exec(newcode, newns)
124+
exec_func(newcode, newns)
123125
except raises as e:
124126
# We care about e.g. NameError vs UnboundLocalError
125127
self.assertIs(type(e), raises)
@@ -613,6 +615,45 @@ def test_frame_locals(self):
613615
import sys
614616
self._check_in_scopes(code, {"val": 0}, ns={"sys": sys})
615617

618+
def _recursive_replace(self, maybe_code):
619+
if not isinstance(maybe_code, types.CodeType):
620+
return maybe_code
621+
return maybe_code.replace(co_consts=tuple(
622+
self._recursive_replace(c) for c in maybe_code.co_consts
623+
))
624+
625+
def _replacing_exec(self, code_string, ns):
626+
co = compile(code_string, "<string>", "exec")
627+
co = self._recursive_replace(co)
628+
exec(co, ns)
629+
630+
def test_code_replace(self):
631+
code = """
632+
x = 3
633+
[x for x in (1, 2)]
634+
dir()
635+
y = [x]
636+
"""
637+
self._check_in_scopes(code, {"y": [3], "x": 3})
638+
self._check_in_scopes(code, {"y": [3], "x": 3}, exec_func=self._replacing_exec)
639+
640+
def test_code_replace_extended_arg(self):
641+
num_names = 300
< 10000 /td>642+
assignments = "; ".join(f"x{i} = {i}" for i in range(num_names))
643+
name_list = ", ".join(f"x{i}" for i in range(num_names))
644+
expected = {
645+
"y": list(range(num_names)),
646+
**{f"x{i}": i for i in range(num_names)}
647+
}
648+
code = f"""
649+
{assignments}
650+
[({name_list}) for {name_list} in (range(300),)]
651+
dir()
652+
y = [{name_list}]
653+
"""
654+
self._check_in_scopes(code, expected)
655+
self._check_in_scopes(code, expected, exec_func=self._replacing_exec)
656+
616657

617658
__test__ = {'doctests' : doctests}
618659

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix regression in Python 3.12 where :meth:`types.CodeType.replace` would
2+
produce a broken code object if called on a module or class code object that
3+
contains a comprehension. Patch by Jelle Zijlstra.

Objects/codeobject.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,35 @@ PyUnstable_Code_NewWithPosOnlyArgs(
665665
_Py_set_localsplus_info(offset, name, CO_FAST_FREE,
666666
localsplusnames, localspluskinds);
667667
}
668+
669+
// gh-110543: Make sure the CO_FAST_HIDDEN flag is set correctly.
670+
if (!(flags & CO_OPTIMIZED)) {
671+
Py_ssize_t code_len = PyBytes_GET_SIZE(code);
672+
_Py_CODEUNIT *code_data = (_Py_CODEUNIT *)PyBytes_AS_STRING(code);
673+
Py_ssize_t num_code_units = code_len / sizeof(_Py_CODEUNIT);
674+
int extended_arg = 0;
675+
for (int i = 0; i < num_code_units; i += 1 + _PyOpcode_Caches[code_data[i].op.code]) {
676 57AE +
_Py_CODEUNIT *instr = &code_data[i];
677+
uint8_t opcode = instr->op.code;
678+
if (opcode == EXTENDED_ARG) {
679+
extended_arg = extended_arg << 8 | instr->op.arg;
680+
continue;
681+
}
682+
if (opcode == LOAD_FAST_AND_CLEAR) {
683+
int oparg = extended_arg << 8 | instr->op.arg;
684+
if (oparg >= nlocalsplus) {
685+
PyErr_Format(PyExc_ValueError,
686+
"code: LOAD_FAST_AND_CLEAR oparg %d out of range",
687+
oparg);
688+
goto error;
689+
}
690+
_PyLocals_Kind kind = _PyLocals_GetKind(localspluskinds, oparg);
691+
_PyLocals_SetKind(localspluskinds, oparg, kind | CO_FAST_HIDDEN);
692+
}
693+
extended_arg = 0;
694+
}
695+
}
696+
668697
// If any cells were args then nlocalsplus will have shrunk.
669698
if (nlocalsplus != PyTuple_GET_SIZE(localsplusnames)) {
670699
if (_PyTuple_Resize(&localsplusnames, nlocalsplus) < 0

0 commit comments

Comments
 (0)
0