diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 745ce029aa575c..13cc8821b082a9 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -1,3 +1,4 @@ +import dis import math import os import unittest @@ -628,6 +629,24 @@ def check_same_constant(const): self.check_constant(f1, frozenset({0})) self.assertTrue(f1(0)) + # This is a regression test for a CPython specific peephole optimizer + # implementation bug present in a few releases. It's assertion verifies + # that peephole optimization was actually done though that isn't an + # indication of the bugs presence or not (crashing is). + @support.cpython_only + def test_peephole_opt_unreachable_code_array_access_in_bounds(self): + """Regression test for issue35193 when run under clang msan.""" + def unused_code_at_end(): + return 3 + raise RuntimeError("unreachable") + # The above function definition will trigger the out of bounds + # bug in the peephole optimizer as it scans opcodes past the + # RETURN_VALUE opcode. This does not always crash an interpreter. + # When you build with the clang memory sanitizer it reliably aborts. + self.assertEqual( + 'RETURN_VALUE', + list(dis.get_instructions(unused_code_at_end))[-1].opname) + def test_dont_merge_constants(self): # Issue #25843: compile() must not merge constants which are equal # but have a different type. diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-11-08-23-00-04.bpo-35193.WK2PDg.rst b/Misc/NEWS.d/next/Core and Builtins/2018-11-08-23-00-04.bpo-35193.WK2PDg.rst new file mode 100644 index 00000000000000..a6b3c64beab77e --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-11-08-23-00-04.bpo-35193.WK2PDg.rst @@ -0,0 +1,3 @@ +Fix an off by one error in the bytecode peephole optimizer where it could +read bytes beyond the end of bounds of an array when removing unreachable +code. This bug was present in every release of Python 3.6 until now. diff --git a/Python/peephole.c b/Python/peephole.c index 31d4e92cfd3343..3fa3b7fcee6284 100644 --- a/Python/peephole.c +++ b/Python/peephole.c @@ -96,9 +96,9 @@ lastn_const_start(const _Py_CODEUNIT *codestr, Py_ssize_t i, Py_ssize_t n) /* Scans through EXTENDED ARGs, seeking the index of the effective opcode */ static Py_ssize_t -find_op(const _Py_CODEUNIT *codestr, Py_ssize_t i) +find_op(const _Py_CODEUNIT *codestr, Py_ssize_t codelen, Py_ssize_t i) { - while (_Py_OPCODE(codestr[i]) == EXTENDED_ARG) { + while (i < codelen && _Py_OPCODE(codestr[i]) == EXTENDED_ARG) { i++; } return i; @@ -590,7 +590,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, CONST_STACK_CREATE(); - for (i=find_op(codestr, 0) ; i= 1 && _Py_OPCODE(codestr[op_start-1]) == EXTENDED_ARG) { @@ -755,7 +755,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, case JUMP_IF_FALSE_OR_POP: case JUMP_IF_TRUE_OR_POP: h = get_arg(codestr, i) / sizeof(_Py_CODEUNIT); - tgt = find_op(codestr, h); + tgt = find_op(codestr, codelen, h); j = _Py_OPCODE(codestr[tgt]); if (CONDITIONAL_JUMP(j)) { @@ -796,7 +796,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, case SETUP_WITH: case SETUP_ASYNC_WITH: h = GETJUMPTGT(codestr, i); - tgt = find_op(codestr, h); + tgt = find_op(codestr, codelen, h); /* Replace JUMP_* to a RETURN into just a RETURN */ if (UNCONDITIONAL_JUMP(opcode) && _Py_OPCODE(codestr[tgt]) == RETURN_VALUE) { @@ -825,7 +825,7 @@ PyCode_Optimize(PyObject *code, PyObject* consts, PyObject *names, } if (h > i + 1) { fill_nops(codestr, i + 1, h); - nexti = find_op(codestr, h); + nexti = find_op(codestr, codelen, h); } break; }