Commit c8a6967e authored by Mark Dickinson's avatar Mark Dickinson

Issue #14794: slice.indices no longer returns OverflowError for out-of-range...

Issue #14794: slice.indices no longer returns OverflowError for out-of-range start, stop, step or length.
parent ff3d5159
...@@ -4,8 +4,70 @@ import unittest ...@@ -4,8 +4,70 @@ import unittest
from test import support from test import support
from pickle import loads, dumps from pickle import loads, dumps
import itertools
import operator
import sys import sys
def evaluate_slice_index(arg):
"""
Helper function to convert a slice argument to an integer, and raise
TypeError with a suitable message on failure.
"""
if hasattr(arg, '__index__'):
return operator.index(arg)
else:
raise TypeError(
"slice indices must be integers or "
"None or have an __index__ method")
def slice_indices(slice, length):
"""
Reference implementation for the slice.indices method.
"""
# Compute step and length as integers.
length = operator.index(length)
step = 1 if slice.step is None else evaluate_slice_index(slice.step)
# Raise ValueError for negative length or zero step.
if length < 0:
raise ValueError("length should not be negative")
if step == 0:
raise ValueError("slice step cannot be zero")
# Find lower and upper bounds for start and stop.
lower = -1 if step < 0 else 0
upper = length - 1 if step < 0 else length
# Compute start.
if slice.start is None:
start = upper if step < 0 else lower
else:
start = evaluate_slice_index(slice.start)
start = max(start + length, lower) if start < 0 else min(start, upper)
# Compute stop.
if slice.stop is None:
stop = lower if step < 0 else upper
else:
stop = evaluate_slice_index(slice.stop)
stop = max(stop + length, lower) if stop < 0 else min(stop, upper)
return start, stop, step
# Class providing an __index__ method. Used for testing slice.indices.
class MyIndexable(object):
def __init__(self, value):
self.value = value
def __index__(self):
return self.value
class SliceTest(unittest.TestCase): class SliceTest(unittest.TestCase):
def test_constructor(self): def test_constructor(self):
...@@ -75,6 +137,22 @@ class SliceTest(unittest.TestCase): ...@@ -75,6 +137,22 @@ class SliceTest(unittest.TestCase):
s = slice(obj) s = slice(obj)
self.assertTrue(s.stop is obj) self.assertTrue(s.stop is obj)
def check_indices(self, slice, length):
try:
actual = slice.indices(length)
except ValueError:
actual = "valueerror"
try:
expected = slice_indices(slice, length)
except ValueError:
expected = "valueerror"
self.assertEqual(actual, expected)
if length >= 0 and slice.step != 0:
actual = range(*slice.indices(length))
expected = range(length)[slice]
self.assertEqual(actual, expected)
def test_indices(self): def test_indices(self):
self.assertEqual(slice(None ).indices(10), (0, 10, 1)) self.assertEqual(slice(None ).indices(10), (0, 10, 1))
self.assertEqual(slice(None, None, 2).indices(10), (0, 10, 2)) self.assertEqual(slice(None, None, 2).indices(10), (0, 10, 2))
...@@ -108,7 +186,41 @@ class SliceTest(unittest.TestCase): ...@@ -108,7 +186,41 @@ class SliceTest(unittest.TestCase):
self.assertEqual(list(range(10))[::sys.maxsize - 1], [0]) self.assertEqual(list(range(10))[::sys.maxsize - 1], [0])
self.assertRaises(OverflowError, slice(None).indices, 1<<100) # Check a variety of start, stop, step and length values, including
# values exceeding sys.maxsize (see issue #14794).
vals = [None, -2**100, -2**30, -53, -7, -1, 0, 1, 7, 53, 2**30, 2**100]
lengths = [0, 1, 7, 53, 2**30, 2**100]
for slice_args in itertools.product(vals, repeat=3):
s = slice(*slice_args)
for length in lengths:
self.check_indices(s, length)
self.check_indices(slice(0, 10, 1), -3)
# Negative length should raise ValueError
with self.assertRaises(ValueError):
slice(None).indices(-1)
# Zero step should raise ValueError
with self.assertRaises(ValueError):
slice(0, 10, 0).indices(5)
# Using a start, stop or step or length that can't be interpreted as an
# integer should give a TypeError ...
with self.assertRaises(TypeError):
slice(0.0, 10, 1).indices(5)
with self.assertRaises(TypeError):
slice(0, 10.0, 1).indices(5)
with self.assertRaises(TypeError):
slice(0, 10, 1.0).indices(5)
with self.assertRaises(TypeError):
slice(0, 10, 1).indices(5.0)
# ... but it should be fine to use a custom class that provides index.
self.assertEqual(slice(0, 10, 1).indices(5), (0, 5, 1))
self.assertEqual(slice(MyIndexable(0), 10, 1).indices(5), (0, 5, 1))
self.assertEqual(slice(0, MyIndexable(10), 1).indices(5), (0, 5, 1))
self.assertEqual(slice(0, 10, MyIndexable(1)).indices(5), (0, 5, 1))
self.assertEqual(slice(0, 10, 1).indices(MyIndexable(5)), (0, 5, 1))
def test_setslice_without_getslice(self): def test_setslice_without_getslice(self):
tmp = [] tmp = []
......
...@@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1? ...@@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #14794: Fix slice.indices to return correct results for huge values,
rather than raising OverflowError.
- Issue #15001: fix segfault on "del sys.module['__main__']". Patch by Victor - Issue #15001: fix segfault on "del sys.module['__main__']". Patch by Victor
Stinner. Stinner.
......
...@@ -299,25 +299,192 @@ static PyMemberDef slice_members[] = { ...@@ -299,25 +299,192 @@ static PyMemberDef slice_members[] = {
{0} {0}
}; };
/* Helper function to convert a slice argument to a PyLong, and raise TypeError
with a suitable message on failure. */
static PyObject* static PyObject*
slice_indices(PySliceObject* self, PyObject* len) evaluate_slice_index(PyObject *v)
{ {
Py_ssize_t ilen, start, stop, step, slicelength; if (PyIndex_Check(v)) {
return PyNumber_Index(v);
}
else {
PyErr_SetString(PyExc_TypeError,
"slice indices must be integers or "
"None or have an __index__ method");
return NULL;
}
}
ilen = PyNumber_AsSsize_t(len, PyExc_OverflowError); /* Implementation of slice.indices. */
static PyObject*
slice_indices(PySliceObject* self, PyObject* len)
{
PyObject *start=NULL, *stop=NULL, *step=NULL;
PyObject *length=NULL, *upper=NULL, *lower=NULL, *zero=NULL;
int step_is_negative, cmp;
if (ilen == -1 && PyErr_Occurred()) { zero = PyLong_FromLong(0L);
if (zero == NULL)
return NULL; return NULL;
/* Compute step and length as integers. */
length = PyNumber_Index(len);
if (length == NULL)
goto error;
if (self->step == Py_None)
step = PyLong_FromLong(1L);
else
step = evaluate_slice_index(self->step);
if (step == NULL)
goto error;
/* Raise ValueError for negative length or zero step. */
cmp = PyObject_RichCompareBool(length, zero, Py_LT);
if (cmp < 0) {
goto error;
}
if (cmp) {
PyErr_SetString(PyExc_ValueError,
"length should not be negative");
goto error;
} }
if (PySlice_GetIndicesEx((PyObject*)self, ilen, &start, &stop, cmp = PyObject_RichCompareBool(step, zero, Py_EQ);
&step, &slicelength) < 0) { if (cmp < 0) {
return NULL; goto error;
}
if (cmp) {
PyErr_SetString(PyExc_ValueError,
"slice step cannot be zero");
goto error;
} }
return Py_BuildValue("(nnn)", start, stop, step); /* Find lower and upper bounds for start and stop. */
step_is_negative = PyObject_RichCompareBool(step, zero, Py_LT);
if (step_is_negative < 0) {
goto error;
}
if (step_is_negative) {
lower = PyLong_FromLong(-1L);
if (lower == NULL)
goto error;
upper = PyNumber_Add(length, lower);
if (upper == NULL)
goto error;
}
else {
lower = zero;
Py_INCREF(lower);
upper = length;
Py_INCREF(upper);
}
/* Compute start. */
if (self->start == Py_None) {
start = step_is_negative ? upper : lower;
Py_INCREF(start);
}
else {
start = evaluate_slice_index(self->start);
if (start == NULL)
goto error;
cmp = PyObject_RichCompareBool(start, zero, Py_LT);
if (cmp < 0)
goto error;
if (cmp) {
/* start += length */
PyObject *tmp = PyNumber_Add(start, length);
Py_DECREF(start);
start = tmp;
if (start == NULL)
goto error;
cmp = PyObject_RichCompareBool(start, lower, Py_LT);
if (cmp < 0)
goto error;
if (cmp) {
Py_INCREF(lower);
Py_DECREF(start);
start = lower;
}
}
else {
cmp = PyObject_RichCompareBool(start, upper, Py_GT);
if (cmp < 0)
goto error;
if (cmp) {
Py_INCREF(upper);
Py_DECREF(start);
start = upper;
}
}
}
/* Compute stop. */
if (self->stop == Py_None) {
stop = step_is_negative ? lower : upper;
Py_INCREF(stop);
}
else {
stop = evaluate_slice_index(self->stop);
if (stop == NULL)
goto error;
cmp = PyObject_RichCompareBool(stop, zero, Py_LT);
if (cmp < 0)
goto error;
if (cmp) {
/* stop += length */
PyObject *tmp = PyNumber_Add(stop, length);
Py_DECREF(stop);
stop = tmp;
if (stop == NULL)
goto error;
cmp = PyObject_RichCompareBool(stop, lower, Py_LT);
if (cmp < 0)
goto error;
if (cmp) {
Py_INCREF(lower);
Py_DECREF(stop);
stop = lower;
}
}
else {
cmp = PyObject_RichCompareBool(stop, upper, Py_GT);
if (cmp < 0)
goto error;
if (cmp) {
Py_INCREF(upper);
Py_DECREF(stop);
stop = upper;
}
}
}
Py_DECREF(upper);
Py_DECREF(lower);
Py_DECREF(length);
Py_DECREF(zero);
return Py_BuildValue("(NNN)", start, stop, step);
error:
Py_XDECREF(start);
Py_XDECREF(stop);
Py_XDECREF(step);
Py_XDECREF(upper);
Py_XDECREF(lower);
Py_XDECREF(length);
Py_XDECREF(zero);
return NULL;
} }
PyDoc_STRVAR(slice_indices_doc, PyDoc_STRVAR(slice_indices_doc,
"S.indices(len) -> (start, stop, stride)\n\ "S.indices(len) -> (start, stop, stride)\n\
\n\ \n\
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment