Skip to content

Commit eeea1ab

Browse files
author
neal.norwitz
committed
Patch #1538606, Patch to fix __index__() clipping.
I modified this patch some by fixing style, some error checking, and adding XXX comments. This patch requires review and some changes are to be expected. I'm checking in now to get the greatest possible review and establish a baseline for moving forward. I don't want this to hold up release if possible. git-svn-id: http://svn.python.org/projects/python/trunk@51236 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 4c16933 commit eeea1ab

19 files changed

Lines changed: 319 additions & 234 deletions

Doc/api/abstract.tex

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -693,12 +693,31 @@ \section{Number Protocol \label{number}}
693693
\samp{float(\var{o})}.\bifuncindex{float}
694694
\end{cfuncdesc}
695695

696-
\begin{cfuncdesc}{Py_ssize_t}{PyNumber_Index}{PyObject *o}
697-
Returns the \var{o} converted to a Py_ssize_t integer on success, or
698-
-1 with an exception raised on failure.
696+
\begin{cfuncdesc}{PyObject*}{PyNumber_Index}{PyObject *o}
697+
Returns the \var{o} converted to a Python int or long on success or \NULL{}
698+
with a TypeError exception raised on failure.
699699
\versionadded{2.5}
700700
\end{cfuncdesc}
701701

702+
\begin{cfuncdesc}{Py_ssize_t}{PyNumber_AsSsize_t}{PyObject *o, PyObject *exc}
703+
Returns \var{o} converted to a Py_ssize_t value if \var{o}
704+
can be interpreted as an integer. If \var{o} can be converted to a Python
705+
int or long but the attempt to convert to a Py_ssize_t value
706+
would raise an \exception{OverflowError}, then the \var{exc} argument
707+
is the type of exception that will be raised (usually \exception{IndexError}
708+
or \exception{OverflowError}). If \var{exc} is \NULL{}, then the exception
709+
is cleared and the value is clipped to \var{PY_SSIZE_T_MIN}
710+
for a negative integer or \var{PY_SSIZE_T_MAX} for a positive integer.
711+
\versionadded{2.5}
712+
\end{cfuncdesc}
713+
714+
\begin{cfuncdesc}{int}{PyIndex_Check}{PyObject *o}
715+
Returns True if \var{o} is an index integer (has the nb_index slot of
716+
the tp_as_number structure filled in).
717+
\versionadded{2.5}
718+
\end{cfuncdesc}
719+
720+
702721
\section{Sequence Protocol \label{sequence}}
703722

704723
\begin{cfuncdesc}{int}{PySequence_Check}{PyObject *o}

Include/abstract.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -758,13 +758,27 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
758758
759759
*/
760760

761-
PyAPI_FUNC(Py_ssize_t) PyNumber_Index(PyObject *);
761+
#define PyIndex_Check(obj) \
762+
((obj)->ob_type->tp_as_number != NULL && \
763+
PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_INDEX) && \
764+
(obj)->ob_type->tp_as_number->nb_index != NULL)
765+
766+
PyAPI_FUNC(PyObject *) PyNumber_Index(PyObject *o);
762767

763768
/*
764-
Returns the object converted to Py_ssize_t on success
765-
or -1 with an error raised on failure.
769+
Returns the object converted to a Python long or int
770+
or NULL with an error raised on failure.
766771
*/
767772

773+
PyAPI_FUNC(Py_ssize_t) PyNumber_AsSsize_t(PyObject *o, PyObject *exc);
774+
775+
/*
776+
Returns the object converted to Py_ssize_t by going through
777+
PyNumber_Index first. If an overflow error occurs while
778+
converting the int-or-long to Py_ssize_t, then the second argument
779+
is the error-type to return. If it is NULL, then the overflow error
780+
is cleared and the value is clipped.
781+
*/
768782

769783
PyAPI_FUNC(PyObject *) PyNumber_Int(PyObject *o);
770784

Include/object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ typedef struct {
208208
binaryfunc nb_inplace_true_divide;
209209

210210
/* Added in release 2.5 */
211-
lenfunc nb_index;
211+
unaryfunc nb_index;
212212
} PyNumberMethods;
213213

214214
typedef struct {

Lib/test/test_index.py

Lines changed: 126 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import unittest
22
from test import test_support
33
import operator
4+
from sys import maxint
45

56
class oldstyle:
67
def __index__(self):
@@ -10,68 +11,115 @@ class newstyle(object):
1011
def __index__(self):
1112
return self.ind
1213

14+
class TrapInt(int):
15+
def __index__(self):
16+
return self
17+
18+
class TrapLong(long):
19+
def __index__(self):
20+
return self
21+
1322
class BaseTestCase(unittest.TestCase):
1423
def setUp(self):
1524
self.o = oldstyle()
1625
self.n = newstyle()
17-
self.o2 = oldstyle()
18-
self.n2 = newstyle()
1926

2027
def test_basic(self):
2128
self.o.ind = -2
2229
self.n.ind = 2
23-
assert(self.seq[self.n] == self.seq[2])
24-
assert(self.seq[self.o] == self.seq[-2])
25-
assert(operator.index(self.o) == -2)
26-
assert(operator.index(self.n) == 2)
30+
self.assertEqual(operator.index(self.o), -2)
31+
self.assertEqual(operator.index(self.n), 2)
32+
33+
def test_slice(self):
34+
self.o.ind = 1
35+
self.n.ind = 2
36+
slc = slice(self.o, self.o, self.o)
37+
check_slc = slice(1, 1, 1)
38+
self.assertEqual(slc.indices(self.o), check_slc.indices(1))
39+
slc = slice(self.n, self.n, self.n)
40+
check_slc = slice(2, 2, 2)
41+
self.assertEqual(slc.indices(self.n), check_slc.indices(2))
2742

43+
def test_wrappers(self):
44+
self.o.ind = 4
45+
self.n.ind = 5
46+
self.assertEqual(6 .__index__(), 6)
47+
self.assertEqual(-7L.__index__(), -7)
48+
self.assertEqual(self.o.__index__(), 4)
49+
self.assertEqual(self.n.__index__(), 5)
50+
51+
def test_infinite_recursion(self):
52+
self.failUnlessRaises(TypeError, operator.index, TrapInt())
53+
self.failUnlessRaises(TypeError, operator.index, TrapLong())
54+
self.failUnless(slice(TrapInt()).indices(0)==(0,0,1))
55+
self.failUnlessRaises(TypeError, slice(TrapLong()).indices, 0)
56+
2857
def test_error(self):
2958
self.o.ind = 'dumb'
3059
self.n.ind = 'bad'
31-
myfunc = lambda x, obj: obj.seq[x]
3260
self.failUnlessRaises(TypeError, operator.index, self.o)
3361
self.failUnlessRaises(TypeError, operator.index, self.n)
34-
self.failUnlessRaises(TypeError, myfunc, self.o, self)
35-
self.failUnlessRaises(TypeError, myfunc, self.n, self)
62+
self.failUnlessRaises(TypeError, slice(self.o).indices, 0)
63+
self.failUnlessRaises(TypeError, slice(self.n).indices, 0)
64+
65+
66+
class SeqTestCase(unittest.TestCase):
67+
# This test case isn't run directly. It just defines common tests
68+
# to the different sequence types below
69+
def setUp(self):
70+
self.o = oldstyle()
71+
self.n = newstyle()
72+
self.o2 = oldstyle()
73+
self.n2 = newstyle()
74+
75+
def test_index(self):
76+
self.o.ind = -2
77+
self.n.ind = 2
78+
self.assertEqual(self.seq[self.n], self.seq[2])
79+
self.assertEqual(self.seq[self.o], self.seq[-2])
3680

3781
def test_slice(self):
3882
self.o.ind = 1
3983
self.o2.ind = 3
4084
self.n.ind = 2
4185
self.n2.ind = 4
42-
assert(self.seq[self.o:self.o2] == self.seq[1:3])
43-
assert(self.seq[self.n:self.n2] == self.seq[2:4])
86+
self.assertEqual(self.seq[self.o:self.o2], self.seq[1:3])
87+
self.assertEqual(self.seq[self.n:self.n2], self.seq[2:4])
4488

4589
def test_repeat(self):
4690
self.o.ind = 3
4791
self.n.ind = 2
48-
assert(self.seq * self.o == self.seq * 3)
49-
assert(self.seq * self.n == self.seq * 2)
50-
assert(self.o * self.seq == self.seq * 3)
51-
assert(self.n * self.seq == self.seq * 2)
92+
self.assertEqual(self.seq * self.o, self.seq * 3)
93+
self.assertEqual(self.seq * self.n, self.seq * 2)
94+
self.assertEqual(self.o * self.seq, self.seq * 3)
95+
self.assertEqual(self.n * self.seq, self.seq * 2)
5296

5397
def test_wrappers(self):
54-
n = self.n
55-
n.ind = 5
56-
assert n.__index__() == 5
57-
assert 6 .__index__() == 6
58-
assert -7L.__index__() == -7
59-
assert self.seq.__getitem__(n) == self.seq[5]
60-
assert self.seq.__mul__(n) == self.seq * 5
61-
assert self.seq.__rmul__(n) == self.seq * 5
62-
63-
def test_infinite_recusion(self):
64-
class Trap1(int):
65-
def __index__(self):
66-
return self
67-
class Trap2(long):
68-
def __index__(self):
69-
return self
70-
self.failUnlessRaises(TypeError, operator.getitem, self.seq, Trap1())
71-
self.failUnlessRaises(TypeError, operator.getitem, self.seq, Trap2())
72-
73-
74-
class ListTestCase(BaseTestCase):
98+
self.o.ind = 4
99+
self.n.ind = 5
100+
self.assertEqual(self.seq.__getitem__(self.o), self.seq[4])
101+
self.assertEqual(self.seq.__mul__(self.o), self.seq * 4)
102+
self.assertEqual(self.seq.__rmul__(self.o), self.seq * 4)
103+
self.assertEqual(self.seq.__getitem__(self.n), self.seq[5])
104+
self.assertEqual(self.seq.__mul__(self.n), self.seq * 5)
105+
self.assertEqual(self.seq.__rmul__(self.n), self.seq * 5)
106+
107+
def test_infinite_recursion(self):
108+
self.failUnlessRaises(TypeError, operator.getitem, self.seq, TrapInt())
109+
self.failUnlessRaises(TypeError, operator.getitem, self.seq, TrapLong())
110+
111+
def test_error(self):
112+
self.o.ind = 'dumb'
113+
self.n.ind = 'bad'
114+
indexobj = lambda x, obj: obj.seq[x]
115+
self.failUnlessRaises(TypeError, indexobj, self.o, self)
116+
self.failUnlessRaises(TypeError, indexobj, self.n, self)
117+
sliceobj = lambda x, obj: obj.seq[x:]
118+
self.failUnlessRaises(TypeError, sliceobj, self.o, self)
119+
self.failUnlessRaises(TypeError, sliceobj, self.n, self)
120+
121+
122+
class ListTestCase(SeqTestCase):
75123
seq = [0,10,20,30,40,50]
76124

77125
def test_setdelitem(self):
@@ -82,36 +130,36 @@ def test_setdelitem(self):
82130
del lst[self.n]
83131
lst[self.o] = 'X'
84132
lst[self.n] = 'Y'
85-
assert lst == list('abYdefghXj')
133+
self.assertEqual(lst, list('abYdefghXj'))
86134

87135
lst = [5, 6, 7, 8, 9, 10, 11]
88136
lst.__setitem__(self.n, "here")
89-
assert lst == [5, 6, "here", 8, 9, 10, 11]
137+
self.assertEqual(lst, [5, 6, "here", 8, 9, 10, 11])
90138
lst.__delitem__(self.n)
91-
assert lst == [5, 6, 8, 9, 10, 11]
139+
self.assertEqual(lst, [5, 6, 8, 9, 10, 11])
92140

93141
def test_inplace_repeat(self):
94142
self.o.ind = 2
95143
self.n.ind = 3
96144
lst = [6, 4]
97145
lst *= self.o
98-
assert lst == [6, 4, 6, 4]
146+
self.assertEqual(lst, [6, 4, 6, 4])
99147
lst *= self.n
100-
assert lst == [6, 4, 6, 4] * 3
148+
self.assertEqual(lst, [6, 4, 6, 4] * 3)
101149

102150
lst = [5, 6, 7, 8, 9, 11]
103151
l2 = lst.__imul__(self.n)
104-
assert l2 is lst
105-
assert lst == [5, 6, 7, 8, 9, 11] * 3
152+
self.assert_(l2 is lst)
153+
self.assertEqual(lst, [5, 6, 7, 8, 9, 11] * 3)
106154

107155

108-
class TupleTestCase(BaseTestCase):
156+
class TupleTestCase(SeqTestCase):
109157
seq = (0,10,20,30,40,50)
110158

111-
class StringTestCase(BaseTestCase):
159+
class StringTestCase(SeqTestCase):
112160
seq = "this is a test"
113161

114-
class UnicodeTestCase(BaseTestCase):
162+
class UnicodeTestCase(SeqTestCase):
115163
seq = u"this is a test"
116164

117165

@@ -120,17 +168,47 @@ class XRangeTestCase(unittest.TestCase):
120168
def test_xrange(self):
121169
n = newstyle()
122170
n.ind = 5
123-
assert xrange(1, 20)[n] == 6
124-
assert xrange(1, 20).__getitem__(n) == 6
171+
self.assertEqual(xrange(1, 20)[n], 6)
172+
self.assertEqual(xrange(1, 20).__getitem__(n), 6)
173+
174+
class OverflowTestCase(unittest.TestCase):
175+
176+
def setUp(self):
177+
self.pos = 2**100
178+
self.neg = -self.pos
179+
180+
def test_large_longs(self):
181+
self.assertEqual(self.pos.__index__(), self.pos)
182+
self.assertEqual(self.neg.__index__(), self.neg)
183+
184+
def test_getitem(self):
185+
class GetItem(object):
186+
def __len__(self):
187+
return maxint
188+
def __getitem__(self, key):
189+
return key
190+
def __getslice__(self, i, j):
191+
return i, j
192+
x = GetItem()
193+
self.assertEqual(x[self.pos], self.pos)
194+
self.assertEqual(x[self.neg], self.neg)
195+
self.assertEqual(x[self.neg:self.pos], (-1, maxint))
196+
self.assertEqual(x[self.neg:self.pos:1].indices(maxint), (0, maxint, 1))
197+
198+
def test_sequence_repeat(self):
199+
self.failUnlessRaises(OverflowError, lambda: "a" * self.pos)
200+
self.failUnlessRaises(OverflowError, lambda: "a" * self.neg)
125201

126202

127203
def test_main():
128204
test_support.run_unittest(
205+
BaseTestCase,
129206
ListTestCase,
130207
TupleTestCase,
131208
StringTestCase,
132209
UnicodeTestCase,
133210
XRangeTestCase,
211+
OverflowTestCase,
134212
)
135213

136214
if __name__ == "__main__":

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ What's New in Python 2.5 release candidate 1?
1212
Core and builtins
1313
-----------------
1414

15+
- Patch #1538606, Fix __index__() clipping. There were some problems
16+
discovered with the API and how integers that didn't fit into Py_ssize_t
17+
were handled. This patch attempts to provide enough alternatives
18+
to effectively use __index__.
19+
1520
- Bug #1536021: __hash__ may now return long int; the final hash
1621
value is obtained by invoking hash on the long int.
1722

Modules/arraymodule.c

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,14 +1572,11 @@ array_repr(arrayobject *a)
15721572
return s;
15731573
}
15741574

1575-
#define HASINDEX(o) PyType_HasFeature((o)->ob_type, Py_TPFLAGS_HAVE_INDEX)
1576-
15771575
static PyObject*
15781576
array_subscr(arrayobject* self, PyObject* item)
15791577
{
1580-
PyNumberMethods *nb = item->ob_type->tp_as_number;
1581-
if (nb != NULL && HASINDEX(item) && nb->nb_index != NULL) {
1582-
Py_ssize_t i = nb->nb_index(item);
1578+
if (PyIndex_Check(item)) {
1579+
Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
15831580
if (i==-1 && PyErr_Occurred()) {
15841581
return NULL;
15851582
}
@@ -1627,9 +1624,8 @@ array_subscr(arrayobject* self, PyObject* item)
16271624
static int
16281625
array_ass_subscr(arrayobject* self, PyObject* item, PyObject* value)
16291626
{
1630-
PyNumberMethods *nb = item->ob_type->tp_as_number;
1631-
if (nb != NULL && HASINDEX(item) && nb->nb_index != NULL) {
1632-
Py_ssize_t i = nb->nb_index(item);
1627+
if (PyIndex_Check(item)) {
1628+
Py_ssize_t i = PyNumber_AsSsize_t(item, PyExc_IndexError);
16331629
if (i==-1 && PyErr_Occurred())
16341630
return -1;
16351631
if (i < 0)

0 commit comments

Comments
 (0)