Skip to content

Commit 764aae9

Browse files
Address code review
Rename SIZE_MAX and SIZE_MIN to SSIZE_MAX and SSIZE_MIN, and the PySlice_GetIndicesEx() wrappers to slice_getindicesex_macro() and slice_getindicesex_func(). Test the limited C API also on free threading. Reduce the number of tested combinations. Add tests for zero and negative length, and document that the length must not be negative.
1 parent 731ae1f commit 764aae9

3 files changed

Lines changed: 67 additions & 49 deletions

File tree

‎Doc/c-api/slice.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Slice Objects
5353
length *length*, and store the length of the slice in *slicelength*. Out
5454
of bounds indices are clipped in a manner consistent with the handling of
5555
normal slices.
56+
*length* must not be negative.
5657
5758
Return ``0`` on success and ``-1`` on error with an exception set.
5859
@@ -108,6 +109,7 @@ Slice Objects
108109
Out of bounds indices are clipped in a manner consistent with the handling
109110
of normal slices.
110111
112+
*length* must not be negative.
111113
*step* must not be zero and must not be less than ``-PY_SSIZE_T_MAX``,
112114
as guaranteed by :c:func:`PySlice_Unpack`.
113115

‎Lib/test/test_capi/test_slice.py‎

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
_testlimitedcapi = import_helper.import_module('_testlimitedcapi')
66

77
NULL = None
8-
SIZE_MAX = sys.maxsize
9-
SIZE_MIN = -sys.maxsize - 1
8+
SSIZE_MAX = sys.maxsize
9+
SSIZE_MIN = -sys.maxsize - 1
1010

11-
VALUES = [None, 0, 1, 2, 3, 5, 7, -1, -2, -3, -5, -7]
12-
STEPS = [None, 1, 2, 3, 5, -1, -2, -3, -5]
13-
LENGTHS = [0, 1, 2, 5, 10]
11+
VALUES = [None, 0, 1, 3, 7, -1, -3, -7]
12+
STEPS = [None, 1, 3, 5, -1, -3, -5]
13+
LENGTHS = [0, 1, 3, 10]
1414

1515

1616
class Index:
@@ -76,11 +76,11 @@ def test_getindices(self):
7676
self.assertIsNone(getindices(slice(1, 'a'), 10))
7777
self.assertIsNone(getindices(slice(1, 7, 'a'), 10))
7878

79-
# negative length
80-
self.assertIsNone(getindices(slice(None), -1))
81-
self.assertIsNone(getindices(slice(1, 7, 2), -1))
82-
self.assertEqual(getindices(slice(-10, -5, 1), -1), (-11, -6, 1))
83-
self.assertEqual(getindices(slice(-5, -10, -2), -1), (-6, -11, -2))
79+
# Negative length is not supported, but does not fail.
80+
self.assertIsNone(getindices(slice(None), -3))
81+
self.assertIsNone(getindices(slice(1, 7, 2), -3))
82+
self.assertEqual(getindices(slice(-10, -5, 1), -3), (-13, -8, 1))
83+
self.assertEqual(getindices(slice(-5, -10, -2), -3), (-8, -13, -2))
8484

8585
# CRASHES getindices(NULL, 10)
8686
# CRASHES getindices(object(), 10)
@@ -92,27 +92,27 @@ def test_unpack(self):
9292
self.assertEqual(unpack(slice(1, 7, 2)), (1, 7, 2))
9393
self.assertEqual(unpack(slice(7, 1, -2)), (7, 1, -2))
9494
self.assertEqual(unpack(slice(None, 7, 2)), (0, 7, 2))
95-
self.assertEqual(unpack(slice(None, 7, -2)), (SIZE_MAX, 7, -2))
96-
self.assertEqual(unpack(slice(1, None, 2)), (1, SIZE_MAX, 2))
97-
self.assertEqual(unpack(slice(1, None, -2)), (1, SIZE_MIN, -2))
98-
self.assertEqual(unpack(slice(None)), (0, SIZE_MAX, 1))
95+
self.assertEqual(unpack(slice(None, 7, -2)), (SSIZE_MAX, 7, -2))
96+
self.assertEqual(unpack(slice(1, None, 2)), (1, SSIZE_MAX, 2))
97+
self.assertEqual(unpack(slice(1, None, -2)), (1, SSIZE_MIN, -2))
98+
self.assertEqual(unpack(slice(None)), (0, SSIZE_MAX, 1))
9999
self.assertEqual(unpack(slice(None, None, -1)),
100-
(SIZE_MAX, SIZE_MIN, -1))
100+
(SSIZE_MAX, SSIZE_MIN, -1))
101101
# Negative indices are not adjusted.
102102
self.assertEqual(unpack(slice(-3, -1)), (-3, -1, 1))
103103
self.assertEqual(unpack(slice(Index(1), Index(7), Index(2))),
104104
(1, 7, 2))
105105

106106
# Values which do not fit in Py_ssize_t are silently clipped.
107-
self.assertEqual(unpack(slice(1, 2**1000)), (1, SIZE_MAX, 1))
108-
self.assertEqual(unpack(slice(1, -2**1000)), (1, SIZE_MIN, 1))
109-
self.assertEqual(unpack(slice(2**1000, 7)), (SIZE_MAX, 7, 1))
110-
self.assertEqual(unpack(slice(-2**1000, 7)), (SIZE_MIN, 7, 1))
111-
self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SIZE_MAX))
107+
self.assertEqual(unpack(slice(1, 2**1000)), (1, SSIZE_MAX, 1))
108+
self.assertEqual(unpack(slice(1, -2**1000)), (1, SSIZE_MIN, 1))
109+
self.assertEqual(unpack(slice(2**1000, 7)), (SSIZE_MAX, 7, 1))
110+
self.assertEqual(unpack(slice(-2**1000, 7)), (SSIZE_MIN, 7, 1))
111+
self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SSIZE_MAX))
112112
# The step is boosted to -PY_SSIZE_T_MAX, not PY_SSIZE_T_MIN, so
113113
# that negating it is safe.
114-
self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SIZE_MAX))
115-
self.assertEqual(unpack(slice(7, 1, SIZE_MIN)), (7, 1, -SIZE_MAX))
114+
self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SSIZE_MAX))
115+
self.assertEqual(unpack(slice(7, 1, SSIZE_MIN)), (7, 1, -SSIZE_MAX))
116116

117117
with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'):
118118
unpack(slice(1, 1, 0))
@@ -153,8 +153,8 @@ def test_adjustindices(self):
153153
# Out of bounds indices are clipped.
154154
self.assertEqual(adjust(10, -100, 100, 1), (10, 0, 10))
155155
self.assertEqual(adjust(10, 100, -100, -1), (10, 9, -1))
156-
self.assertEqual(adjust(10, SIZE_MIN, SIZE_MAX, 1), (10, 0, 10))
157-
self.assertEqual(adjust(10, SIZE_MAX, SIZE_MIN, -1), (10, 9, -1))
156+
self.assertEqual(adjust(10, SSIZE_MIN, SSIZE_MAX, 1), (10, 0, 10))
157+
self.assertEqual(adjust(10, SSIZE_MAX, SSIZE_MIN, -1), (10, 9, -1))
158158
self.assertEqual(adjust(0, 1, 7, 1), (0, 0, 0))
159159
self.assertEqual(adjust(0, 7, 1, -1), (0, -1, -1))
160160

@@ -170,18 +170,24 @@ def test_adjustindices(self):
170170
self.assertEqual(slicelength,
171171
len(range(start2, stop2, step)))
172172

173+
# Negative length is not supported, but does not fail.
174+
self.assertEqual(adjust(-3, 1, 7, 1), (0, -3, -3))
175+
self.assertEqual(adjust(-3, 7, 1, -1), (0, -4, -4))
176+
self.assertEqual(adjust(-3, -10, -5, 1), (0, 0, 0))
177+
173178
# The step is asserted to be neither zero nor less than
174179
# -PY_SSIZE_T_MAX.
175180
# CRASHES adjust(10, 0, 10, 0)
176-
# CRASHES adjust(10, 0, 10, SIZE_MIN)
181+
# CRASHES adjust(10, 0, 10, SSIZE_MIN)
177182

178183

179-
class GetIndicesExTest(unittest.TestCase):
184+
class GetIndicesExMacroTest(unittest.TestCase):
180185
# PySlice_GetIndicesEx() is a macro using PySlice_Unpack() and
181186
# PySlice_AdjustIndices(). It is also a deprecated function, exported
182187
# for the stable ABI.
183-
getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex)
184-
getindicesex_seq = staticmethod(_testlimitedcapi.slice_getindicesex_seq)
188+
getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_macro)
189+
getindicesex_seq = staticmethod(
190+
_testlimitedcapi.slice_getindicesex_seq_macro)
185191
# The macro evaluates the length after calling PySlice_Unpack(), so the
186192
# size of the list after removing an item is used.
187193
resized = (6, 8, 1, 2)
@@ -215,18 +221,21 @@ def test_getindicesex(self):
215221
self.assertEqual(getindicesex(slice(100, -100, -1), 10),
216222
(9, -1, -1, 10))
217223
self.assertEqual(getindicesex(slice(None), 0), (0, 0, 1, 0))
224+
self.assertEqual(getindicesex(slice(1, 7, 2), 0), (0, 0, 2, 0))
225+
self.assertEqual(getindicesex(slice(None, None, -1), 0),
226+
(-1, -1, -1, 0))
218227

219228
# Indices which do not fit in Py_ssize_t are clipped, not rejected.
220229
# Note that slice.indices() does not clip the step.
221230
self.assertEqual(getindicesex(slice(1, 2**1000), 10), (1, 10, 1, 9))
222231
self.assertEqual(getindicesex(slice(2**1000, 7), 10), (10, 7, 1, 0))
223232
self.assertEqual(getindicesex(slice(1, 7, 2**1000), 10),
224-
(1, 7, SIZE_MAX, 1))
233+
(1, 7, SSIZE_MAX, 1))
225234
# -PY_SSIZE_T_MAX-1 is replaced with -PY_SSIZE_T_MAX.
226235
self.assertEqual(getindicesex(slice(7, 1, -2**1000), 10),
227-
(7, 1, -SIZE_MAX, 1))
228-
self.assertEqual(getindicesex(slice(7, 1, SIZE_MIN), 10),
229-
(7, 1, -SIZE_MAX, 1))
236+
(7, 1, -SSIZE_MAX, 1))
237+
self.assertEqual(getindicesex(slice(7, 1, SSIZE_MIN), 10),
238+
(7, 1, -SSIZE_MAX, 1))
230239

231240
with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'):
232241
getindicesex(slice(1, 7, 0), 10)
@@ -246,6 +255,11 @@ def test_getindicesex(self):
246255
with self.assertRaisesRegex(RuntimeError, 'bad index'):
247256
getindicesex(slice(1, 7, BadIndex()), 10)
248257

258+
# Negative length is not supported, but does not fail.
259+
self.assertEqual(getindicesex(slice(None), -3), (-3, -3, 1, 0))
260+
self.assertEqual(getindicesex(slice(1, 7, 2), -3), (-3, -3, 2, 0))
261+
self.assertEqual(getindicesex(slice(7, 1, -2), -3), (-4, -4, -2, 0))
262+
249263
# CRASHES getindicesex(NULL, 10)
250264
# CRASHES getindicesex(object(), 10)
251265

@@ -254,6 +268,7 @@ def test_getindicesex_seq(self):
254268
getindicesex_seq = self.getindicesex_seq
255269
seq = list(range(10))
256270
self.assertEqual(getindicesex_seq(slice(-3, -1), seq), (7, 9, 1, 2))
271+
self.assertEqual(getindicesex_seq(slice(-3, -1), []), (0, 0, 1, 0))
257272

258273
# gh-72054: __index__() can resize the sequence. Negative indices
259274
# are adjusted by the length, so the result depends on when it is
@@ -278,12 +293,12 @@ def test_getindicesex_seq(self):
278293
# CRASHES getindicesex_seq(slice(None), object())
279294

280295

281-
class GetIndicesExDeprecatedTest(GetIndicesExTest):
296+
class GetIndicesExFuncTest(GetIndicesExMacroTest):
282297
# The deprecated function is equivalent to the macro, except that the
283298
# length is evaluated before the call.
284-
getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_deprecated)
299+
getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_func)
285300
getindicesex_seq = staticmethod(
286-
_testlimitedcapi.slice_getindicesex_seq_deprecated)
301+
_testlimitedcapi.slice_getindicesex_seq_func)
287302
# The size of the list before removing an item is used.
288303
resized = (7, 9, 1, 2)
289304

‎Modules/_testlimitedcapi/slice.c‎

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#include "pyconfig.h" // Py_GIL_DISABLED
2-
3-
// Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices()
4-
// and for PySlice_GetIndicesEx() implemented as a macro.
5-
#if !defined(Py_GIL_DISABLED) && !defined(Py_LIMITED_API)
2+
#ifdef Py_GIL_DISABLED
3+
# define Py_TARGET_ABI3T 0x030f0000
4+
#else
5+
// Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices()
6+
// and for PySlice_GetIndicesEx() implemented as a macro.
67
# define Py_LIMITED_API 0x03060100
78
#endif
89

@@ -62,7 +63,7 @@ slice_getindices(PyObject *Py_UNUSED(module), PyObject *args)
6263
/* Test PySlice_GetIndicesEx() implemented as a macro using PySlice_Unpack()
6364
* and PySlice_AdjustIndices(). */
6465
static PyObject *
65-
slice_getindicesex(PyObject *Py_UNUSED(module), PyObject *args)
66+
slice_getindicesex_macro(PyObject *Py_UNUSED(module), PyObject *args)
6667
{
6768
PyObject *slice;
6869
Py_ssize_t length = UNINITIALIZED_SIZE;
@@ -90,11 +91,11 @@ slice_getindicesex(PyObject *Py_UNUSED(module), PyObject *args)
9091
return Py_BuildValue("nnnn", start, stop, step, slicelength);
9192
}
9293

93-
/* Same as slice_getindicesex(), but the length is the size of a sequence.
94+
/* Same as slice_getindicesex_macro(), but the length is the size of a sequence.
9495
* The macro evaluates it after calling PySlice_Unpack(), which can execute
9596
* arbitrary Python code and resize the sequence. */
9697
static PyObject *
97-
slice_getindicesex_seq(PyObject *Py_UNUSED(module), PyObject *args)
98+
slice_getindicesex_seq_macro(PyObject *Py_UNUSED(module), PyObject *args)
9899
{
99100
PyObject *slice, *seq;
100101
Py_ssize_t start = UNINITIALIZED_SIZE;
@@ -154,7 +155,7 @@ slice_adjustindices(PyObject *Py_UNUSED(module), PyObject *args)
154155
/* Test the deprecated PySlice_GetIndicesEx() function. It is still exported
155156
* for the stable ABI and used if Py_LIMITED_API is older than 3.5.4. */
156157
static PyObject *
157-
slice_getindicesex_deprecated(PyObject *Py_UNUSED(module), PyObject *args)
158+
slice_getindicesex_func(PyObject *Py_UNUSED(module), PyObject *args)
158159
{
159160
PyObject *slice;
160161
Py_ssize_t length;
@@ -186,10 +187,10 @@ _Py_COMP_DIAG_POP
186187
}
187188

188189

189-
/* Same as slice_getindicesex_seq(), but using the deprecated function.
190+
/* Same as slice_getindicesex_seq_macro(), but using the deprecated function.
190191
* The length is evaluated before the call. */
191192
static PyObject *
192-
slice_getindicesex_seq_deprecated(PyObject *Py_UNUSED(module), PyObject *args)
193+
slice_getindicesex_seq_func(PyObject *Py_UNUSED(module), PyObject *args)
193194
{
194195
PyObject *slice, *seq;
195196
Py_ssize_t start = UNINITIALIZED_SIZE;
@@ -221,11 +222,11 @@ static PyMethodDef test_methods[] = {
221222
{"slice_check", slice_check, METH_O},
222223
{"slice_new", slice_new, METH_VARARGS},
223224
{"slice_getindices", slice_getindices, METH_VARARGS},
224-
{"slice_getindicesex", slice_getindicesex, METH_VARARGS},
225-
{"slice_getindicesex_seq", slice_getindicesex_seq, METH_VARARGS},
226-
{"slice_getindicesex_deprecated", slice_getindicesex_deprecated,
225+
{"slice_getindicesex_macro", slice_getindicesex_macro, METH_VARARGS},
226+
{"slice_getindicesex_seq_macro", slice_getindicesex_seq_macro, METH_VARARGS},
227+
{"slice_getindicesex_func", slice_getindicesex_func,
227228
METH_VARARGS},
228-
{"slice_getindicesex_seq_deprecated", slice_getindicesex_seq_deprecated,
229+
{"slice_getindicesex_seq_func", slice_getindicesex_seq_func,
229230
METH_VARARGS},
230231
{"slice_unpack", slice_unpack, METH_O},
231232
{"slice_adjustindices", slice_adjustindices, METH_VARARGS},

0 commit comments

Comments
 (0)