Skip to content

Commit 65e1a1f

Browse files
authored
bpo-35193: Fix an off by one error in the RETURN_VALUE case. (GH-10418) (GH-10422)
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. 7db3c48#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)
1 parent 11a33e1 commit 65e1a1f

3 files changed

Lines changed: 28 additions & 6 deletions

File tree

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
@@ -628,6 +629,24 @@ def check_same_constant(const):
628629
self.check_constant(f1, frozenset({0}))
629630
self.assertTrue(f1(0))
630631

632+
# This is a regression test for a CPython specific peephole optimizer
633+
# implementation bug present in a few releases. It's assertion verifies
634+
# that peephole optimization was actually done though that isn't an
635+
# indication of the bugs presence or not (crashing is).
636+
@support.cpython_only
637+
def test_peephole_opt_unreachable_code_array_access_in_bounds(self):
638+
"""Regression test for issue35193 when run under clang msan."""
639+
def unused_code_at_end():
640+
return 3
641+
raise RuntimeError("unreachable")
642+
# The above function definition will trigger the out of bounds
643+
# bug in the peephole optimizer as it scans opcodes past the
644+
# RETURN_VALUE opcode. This does not always crash an interpreter.
645+
# When you build with the clang memory sanitizer it reliably aborts.
646+
self.assertEqual(
647+
'RETURN_VALUE',
648+
list(dis.get_instructions(unused_code_at_end))[-1].opname)
649+
631650
def test_dont_merge_constants(self):
632651
# Issue #25843: compile() must not merge constants which are equal
633652
# 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
2+
read bytes beyond the end of bounds of an array when removing unreachable
3+
code. This bug was present in every release of Python 3.6 until now.

Python/peephole.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ lastn_const_start(const _Py_CODEUNIT *codestr, Py_ssize_t i, Py_ssize_t n)
9696

9797
/* Scans through EXTENDED ARGs, seeking the index of the effective opcode */
9898
static Py_ssize_t
99-
find_op(const _Py_CODEUNIT *codestr, Py_ssize_t i)
99+
find_op(const _Py_CODEUNIT *codestr, Py_ssize_t codelen, Py_ssize_t i)
100100
{
101-
while (_Py_OPCODE(codestr[i]) == EXTENDED_ARG) {
101+
while (i < codelen && _Py_OPCODE(codestr[i]) == EXTENDED_ARG) {
102102
i++;
103103
}
104104
return i;
@@ -590,7 +590,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
590590

591591
CONST_STACK_CREATE();
592592

593-
for (i=find_op(codestr, 0) ; i<codelen ; i=nexti) {
593+
for (i=find_op(codestr, codelen, 0) ; i<codelen ; i=nexti) {
594594
opcode = _Py_OPCODE(codestr[i]);
595595
op_start = i;
596596
while (op_start >= 1 && _Py_OPCODE(codestr[op_start-1]) == EXTENDED_ARG) {
@@ -755,7 +755,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
755755
case JUMP_IF_FALSE_OR_POP:
756756
case JUMP_IF_TRUE_OR_POP:
757757
h = get_arg(codestr, i) / sizeof(_Py_CODEUNIT);
758-
tgt = find_op(codestr, h);
758+
tgt = find_op(codestr, codelen, h);
759759

760760
j = _Py_OPCODE(codestr[tgt]);
761761
if (CONDITIONAL_JUMP(j)) {
@@ -796,7 +796,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
796796
case SETUP_WITH:
797797
case SETUP_ASYNC_WITH:
798798
h = GETJUMPTGT(codestr, i);
799-
tgt = find_op(codestr, h);
799+
tgt = find_op(codestr, codelen, h);
800800
/* Replace JUMP_* to a RETURN into just a RETURN */
801801
if (UNCONDITIONAL_JUMP(opcode) &&
802802
_Py_OPCODE(codestr[tgt]) == RETURN_VALUE) {
@@ -825,7 +825,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names,
825825
}
826826
if (h > i + 1) {
827827
fill_nops(codestr, i + 1, h);
828-
nexti = find_op(codestr, h);
828+
nexti = find_op(codestr, codelen, h);
829829
}
830830
break;
831831
}

0 commit comments

Comments
 (0)