Skip to content

Commit 8da3d39

Browse files
authored
[3.13] gh-148973: fix segfault on mismatch between consts size and oparg in compiler (GH-148974) (#148997)
1 parent feafd5f commit 8da3d39

3 files changed

Lines changed: 74 additions & 3 deletions

File tree

Lib/test/test_peepholer.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import ast
12
import dis
23
from itertools import combinations, product
34
import opcode
@@ -6,7 +7,11 @@
67
import unittest
78

89
from test import support
9-
from test.support.bytecode_helper import BytecodeTestCase, CfgOptimizationTestCase
10+
from test.support.bytecode_helper import (
11+
BytecodeTestCase,
12+
CfgOptimizationTestCase,
13+
_testinternalcapi,
14+
)
1015

1116

1217
def compile_pattern_with_fast_locals(pattern):
@@ -962,6 +967,45 @@ def trace(frame, event, arg):
962967

963968
class DirectCfgOptimizerTests(CfgOptimizationTestCase):
964969

970+
def test_optimize_cfg_const_index_out_of_range(self):
971+
insts = [
972+
('LOAD_CONST', 2, 0),
973+
('RETURN_VALUE', None, 0),
974+
]
975+
seq = self.seq_from_insts(insts)
976+
with self.assertRaisesRegex(ValueError, "out of range"):
977+
_testinternalcapi.optimize_cfg(seq, [0, 1], 0)
978+
979+
def test_optimize_cfg_consts_must_be_list(self):
980+
insts = [
981+
('LOAD_CONST', 0, 0),
982+
('RETURN_VALUE', None, 0),
983+
]
984+
seq = self.seq_from_insts(insts)
985+
with self.assertRaisesRegex(TypeError, "consts must be a list"):
986+
_testinternalcapi.optimize_cfg(seq, (0,), 0)
987+
988+
def test_compiler_codegen_metadata_consts_roundtrips_optimize_cfg(self):
989+
tree = ast.parse("x = (1, 2)", mode="exec", optimize=1)
990+
insts, meta = _testinternalcapi.compiler_codegen(tree, "<s>", 0)
991+
consts = meta["consts"]
992+
self.assertIsInstance(consts, list)
993+
_testinternalcapi.optimize_cfg(insts, consts, 0)
994+
995+
def test_compiler_codegen_consts_include_none_required_for_implicit_return(self):
996+
tree = ast.parse("pass", mode="exec", optimize=1)
997+
insts, meta = _testinternalcapi.compiler_codegen(tree, "<s>", 0)
998+
consts = meta["consts"]
999+
self.assertEqual(consts, [None])
1000+
1001+
load_const = opcode.opmap["LOAD_CONST"]
1002+
self.assertEqual(
1003+
[t[1] for t in insts.get_instructions() if t[0] == load_const],
1004+
[0],
1005+
)
1006+
1007+
_testinternalcapi.optimize_cfg(insts, list(consts), 0)
1008+
9651009
def cfg_optimization_test(self, insts, expected_insts,
9661010
consts=None, expected_consts=None,
9671011
nlocals=0):

Python/compile.c

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7844,6 +7844,7 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags,
78447844
{
78457845
PyObject *res = NULL;
78467846
PyObject *metadata = NULL;
7847+
PyObject *consts_list = NULL;
78477848

78487849
if (!PyAST_Check(ast)) {
78497850
PyErr_SetString(PyExc_TypeError, "expected an AST");
@@ -7889,7 +7890,6 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags,
78897890

78907891
SET_MATADATA_ITEM("name", umd->u_name);
78917892
SET_MATADATA_ITEM("qualname", umd->u_qualname);
7892-
SET_MATADATA_ITEM("consts", umd->u_consts);
78937893
SET_MATADATA_ITEM("names", umd->u_names);
78947894
SET_MATADATA_ITEM("varnames", umd->u_varnames);
78957895
SET_MATADATA_ITEM("cellvars", umd->u_cellvars);
@@ -7915,12 +7915,21 @@ _PyCompile_CodeGen(PyObject *ast, PyObject *filename, PyCompilerFlags *pflags,
79157915
}
79167916

79177917
if (_PyInstructionSequence_ApplyLabelMap(INSTR_SEQUENCE(c)) < 0) {
7918-
return NULL;
7918+
goto finally;
7919+
}
7920+
/* After add_return_at_end: const indices match final instruction stream. */
7921+
consts_list = consts_dict_keys_inorder(umd->u_consts);
7922+
if (consts_list == NULL) {
7923+
goto finally;
7924+
}
7925+
if (PyDict_SetItemString(metadata, "consts", consts_list) < 0) {
7926+
goto finally;
79197927
}
79207928
/* Allocate a copy of the instruction sequence on the heap */
79217929
res = PyTuple_Pack(2, INSTR_SEQUENCE(c), metadata);
79227930

79237931
finally:
7932+
Py_XDECREF(consts_list);
79247933
Py_XDECREF(metadata);
79257934
compiler_exit_scope(c);
79267935
compiler_free(c);
@@ -7935,6 +7944,10 @@ _PyCompile_OptimizeCfg(PyObject *seq, PyObject *consts, int nlocals)
79357944
PyErr_SetString(PyExc_ValueError, "expected an instruction sequence");
79367945
return NULL;
79377946
}
7947+
if (!PyList_Check(consts)) {
7948+
PyErr_SetString(PyExc_TypeError, "consts must be a list");
7949+
return NULL;
7950+
}
79387951
PyObject *const_cache = PyDict_New();
79397952
if (const_cache == NULL) {
79407953
return NULL;

Python/flowgraph.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,14 @@ get_const_value(int opcode, int oparg, PyObject *co_consts)
12441244
PyObject *constant = NULL;
12451245
assert(OPCODE_HAS_CONST(opcode));
12461246
if (opcode == LOAD_CONST) {
1247+
assert(PyList_Check(co_consts));
1248+
Py_ssize_t n = PyList_GET_SIZE(co_consts);
1249+
if (oparg < 0 || oparg >= n) {
1250+
PyErr_Format(PyExc_ValueError,
1251+
"LOAD_CONST index %d is out of range for consts (len=%zd)",
1252+
oparg, n);
1253+
return NULL;
1254+
}
12471255
constant = PyList_GET_ITEM(co_consts, oparg);
12481256
}
12491257

@@ -2045,6 +2053,12 @@ remove_unused_consts(basicblock *entryblock, PyObject *consts)
20452053
for (int i = 0; i < b->b_iused; i++) {
20462054
if (OPCODE_HAS_CONST(b->b_instr[i].i_opcode)) {
20472055
int index = b->b_instr[i].i_oparg;
2056+
if (index < 0 || index >= nconsts) {
2057+
PyErr_Format(PyExc_ValueError,
2058+
"LOAD_CONST index %d is out of range for consts (len=%zd)",
2059+
index, nconsts);
2060+
goto end;
2061+
}
20482062
index_map[index] = index;
20492063
}
20502064
}

0 commit comments

Comments
 (0)