Skip to content

Commit f16ebcd

Browse files
bpo-35193: Fix an off by one error in the RETURN_VALUE case. (GH-10418)
Fix an off by one error in the peephole optimizer when checking for unreachable code beyond a return. Do a bounds check within find_op so it can return before going past the end as a safety measure. 7db3c488335168993689ddae5914a28e16188447GH-diff-a33329ae6ae0bb295d742f0caf93c137 introduced this off by one error while fixing another one nearby. This bug was shipped in all Python 3.6 and 3.7 releases. The included unittest won't fail unless you do a clang msan build. (cherry picked from commit 49fa4a9) Co-authored-by: Gregory P. Smith <[email protected]>
1 parent 732f745 commit f16ebcd

File tree

3 files changed

+34
-10
lines changed

3 files changed

+34
-10
lines changed

‎Lib/test/test_compile.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import dis
12
import math
23
import os
34
import unittest
@@ -621,6 +622,24 @@ def check_same_constant(const):
621622
self.check_constant(f1, frozenset({0}))
622623
self.assertTrue(f1(0))
623624

625+
# This is a regression test for a CPython specific peephole optimizer
626+
# implementation bug present in a few releases. It's assertion verifies
627+
# that peephole optimization was actually done though that isn't an
628+
# indication of the bugs presence or not (crashing is).
629+
@support.cpython_only
630+
def test_peephole_opt_unreachable_code_array_access_in_bounds(self):
631+
"""Regression test for issue35193 when run under clang msan."""
632+
def unused_code_at_end():
633+
return 3
634+
raise RuntimeError("unreachable")
635+
# The above function definition will trigger the out of bounds
636+
# bug in the peephole optimizer as it scans opcodes past the
637+
# RETURN_VALUE opcode. This does not always crash an interpreter.
638+
# When you build with the clang memory sanitizer it reliably aborts.
639+
self.assertEqual(
640+
'RETURN_VALUE',
641+
list(dis.get_instructions(unused_code_at_end))[-1].opname)
642+
624643
def test_dont_merge_constants(self):
625644
# Issue #25843: compile() must not merge constants which are equal
626645
# but have a different type.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an off by one error in the bytecode peephole optimizer where it could read
2+
bytes beyond the end of bounds of an array when removing unreachable code.
3+
This bug was present in every release of Python 3.6 and 3.7 until now.

‎Python/peephole.c‎

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ lastn_const_start(const _Py_CODEUNIT *codestr, Py_ssize_t i, Py_ssize_t n)
5050

5151
/* Scans through EXTENDED ARGs, seeking the index of the effective opcode */
5252
static Py_ssize_t
53-
find_op(const _Py_CODEUNIT *codestr, Py_ssize_t i)
53+
find_op(const _Py_CODEUNIT *codestr, Py_ssize_t codelen, Py_ssize_t i)
5454
{
55-
while (_Py_OPCODE(codestr[i]) == EXTENDED_ARG) {
55+
while (i < codelen && _Py_OPCODE(codestr[i]) == EXTENDED_ARG) {
5656
i++;
5757
}
5858
return i;
@@ -128,8 +128,9 @@ copy_op_arg(_Py_CODEUNIT *codestr, Py_ssize_t i, unsigned char op,
128128
Called with codestr pointing to the first LOAD_CONST.
129129
*/
130130
static Py_ssize_t
131-
fold_tuple_on_constants(_Py_CODEUNIT *codestr, Py_ssize_t c_start,
132-
Py_ssize_t opcode_end, PyObject *consts, int n)
131+
fold_tuple_on_constants(_Py_CODEUNIT *codestr, Py_ssize_t codelen,
132+
Py_ssize_t c_start, Py_ssize_t opcode_end,
133+
PyObject *consts, int n)
133134
{
134135
/* Pre-conditions */
135136
assert(PyList_CheckExact(consts));
@@ -142,7 +143,7 @@ fold_tuple_on_constants(_Py_CODEUNIT *codestr, Py_ssize_t c_start,
142143

143144
for (Py_ssize_t i = 0, pos = c_start; i < n; i++, pos++) {
144145
assert(pos < opcode_end);
145-
pos = find_op(codestr, pos);
146+
pos = find_op(codestr, codelen, pos);
146147
assert(_Py_OPCODE(codestr[pos]) == LOAD_CONST);
147148

148149
unsigned int arg = get_arg(codestr, pos);
@@ -267,7 +268,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
267268
goto exitError;
268269
assert(PyList_Check(consts));
269270

270-
for (i=find_op(codestr, 0) ; i<codelen ; i=nexti) {
271+
for (i=find_op(codestr, codelen, 0) ; i<codelen ; i=nexti) {
271272
opcode = _Py_OPCODE(codestr[i]);
272273
op_start = i;
273274
while (op_start >= 1 && _Py_OPCODE(codestr[op_start-1]) == EXTENDED_ARG) {
@@ -305,7 +306,8 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
305306
if (j > 0 && lastlc >= j) {
306307
h = lastn_const_start(codestr, op_start, j);
307308
if (ISBASICBLOCK(blocks, h, op_start)) {
308-
h = fold_tuple_on_constants(codestr, h, i+1, consts, j);
309+
h = fold_tuple_on_constants(codestr, codelen,
310+
h, i+1, consts, j);
309311
break;
310312
}
311313
}
@@ -342,7 +344,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
342344
case JUMP_IF_FALSE_OR_POP:
343345
case JUMP_IF_TRUE_OR_POP:
344346
h = get_arg(codestr, i) / sizeof(_Py_CODEUNIT);
345-
tgt = find_op(codestr, h);
347+
tgt = find_op(codestr, codelen, h);
346348

347349
j = _Py_OPCODE(codestr[tgt]);
348350
if (CONDITIONAL_JUMP(j)) {
@@ -383,7 +385,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
383385
case SETUP_WITH:
384386
case SETUP_ASYNC_WITH:
385387
h = GETJUMPTGT(codestr, i);
386-
tgt = find_op(codestr, h);
388+
tgt = find_op(codestr, codelen, h);
387389
/* Replace JUMP_* to a RETURN into just a RETURN */
388390
if (UNCONDITIONAL_JUMP(opcode) &&
389391
_Py_OPCODE(codestr[tgt]) == RETURN_VALUE) {
@@ -412,7 +414,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
412414
}
413415
if (h > i + 1) {
414416
fill_nops(codestr, i + 1, h);
415-
nexti = find_op(codestr, h);
417+
nexti = find_op(codestr, codelen, h);
416418
}
417419
break;
418420
}

0 commit comments

Comments
 (0)