diff --git a/Doc/c-api/slice.rst b/Doc/c-api/slice.rst index c6d761fe7fd1c96..ce2b64a8aca8530 100644 --- a/Doc/c-api/slice.rst +++ b/Doc/c-api/slice.rst @@ -53,6 +53,7 @@ Slice Objects length *length*, and store the length of the slice in *slicelength*. Out of bounds indices are clipped in a manner consistent with the handling of normal slices. + *length* must not be negative. Return ``0`` on success and ``-1`` on error with an exception set. @@ -108,6 +109,10 @@ Slice Objects Out of bounds indices are clipped in a manner consistent with the handling of normal slices. + *length* must not be negative. + *step* must not be zero and must not be less than ``-PY_SSIZE_T_MAX``, + as guaranteed by :c:func:`PySlice_Unpack`. + Return the length of the slice. Always successful. Doesn't call Python code. diff --git a/Lib/test/test_capi/test_slice.py b/Lib/test/test_capi/test_slice.py new file mode 100644 index 000000000000000..3f41b55a609f48f --- /dev/null +++ b/Lib/test/test_capi/test_slice.py @@ -0,0 +1,307 @@ +import sys +import unittest +from test.support import import_helper + +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') + +NULL = None +SSIZE_MAX = sys.maxsize +SSIZE_MIN = -sys.maxsize - 1 + +VALUES = [None, 0, 1, 3, 7, -1, -3, -7] +STEPS = [None, 1, 3, 5, -1, -3, -5] +LENGTHS = [0, 1, 3, 10] + + +class Index: + def __init__(self, value): + self.value = value + + def __index__(self): + return self.value + + +class BadIndex: + def __index__(self): + raise RuntimeError('bad index') + + +class PopIndex: + # __index__() removes the last item of the list. + def __init__(self, value, seq): + self.value = value + self.seq = seq + + def __index__(self): + self.seq.pop() + return self.value + + +class SliceTest(unittest.TestCase): + + def test_check(self): + # Test PySlice_Check() + check = _testlimitedcapi.slice_check + self.assertTrue(check(slice(1, 7, 2))) + self.assertFalse(check(object())) + + # CRASHES check(NULL) + + def test_new(self): + # Test PySlice_New() + new = _testlimitedcapi.slice_new + self.assertEqual(new(1, 7, 2), slice(1, 7, 2)) + self.assertEqual(new(7, 1, -2), slice(7, 1, -2)) + self.assertEqual(new('a', 'b', 'c'), slice('a', 'b', 'c')) + self.assertEqual(new(NULL, NULL, NULL), slice(None, None, None)) + + def test_getindices(self): + # Test PySlice_GetIndices() + getindices = _testlimitedcapi.slice_getindices + self.assertEqual(getindices(slice(1, 7, 2), 10), (1, 7, 2)) + self.assertEqual(getindices(slice(None), 10), (0, 10, 1)) + self.assertEqual(getindices(slice(None, None, -1), 10), (9, -1, -1)) + self.assertEqual(getindices(slice(-3, -1), 10), (7, 9, 1)) + self.assertEqual(getindices(slice(-1, -3, -1), 10), (9, 7, -1)) + self.assertEqual(getindices(slice(None, None, -2), 0), (-1, -1, -2)) + self.assertEqual(getindices(slice(-3, -5, 1), 0), (-3, -5, 1)) + + # It fails without setting an exception for out of bounds indices, + # a zero step and non-integer indices. + self.assertIsNone(getindices(slice(1, 11), 10)) + self.assertIsNone(getindices(slice(10, 1), 10)) + self.assertIsNone(getindices(slice(1, 7, 0), 10)) + self.assertIsNone(getindices(slice(Index(1)), 10)) + self.assertIsNone(getindices(slice('a'), 10)) + self.assertIsNone(getindices(slice(1, 'a'), 10)) + self.assertIsNone(getindices(slice(1, 7, 'a'), 10)) + + # Negative length is not supported, but does not fail. + self.assertIsNone(getindices(slice(None), -3)) + self.assertIsNone(getindices(slice(1, 7, 2), -3)) + self.assertEqual(getindices(slice(-10, -5, 1), -3), (-13, -8, 1)) + self.assertEqual(getindices(slice(-5, -10, -2), -3), (-8, -13, -2)) + + # CRASHES getindices(NULL, 10) + # CRASHES getindices(object(), 10) + + def test_unpack(self): + # Test PySlice_Unpack() + unpack = _testlimitedcapi.slice_unpack + self.assertEqual(unpack(slice(1, 7)), (1, 7, 1)) + self.assertEqual(unpack(slice(1, 7, 2)), (1, 7, 2)) + self.assertEqual(unpack(slice(7, 1, -2)), (7, 1, -2)) + self.assertEqual(unpack(slice(None, 7, 2)), (0, 7, 2)) + self.assertEqual(unpack(slice(None, 7, -2)), (SSIZE_MAX, 7, -2)) + self.assertEqual(unpack(slice(1, None, 2)), (1, SSIZE_MAX, 2)) + self.assertEqual(unpack(slice(1, None, -2)), (1, SSIZE_MIN, -2)) + self.assertEqual(unpack(slice(None)), (0, SSIZE_MAX, 1)) + self.assertEqual(unpack(slice(None, None, -1)), + (SSIZE_MAX, SSIZE_MIN, -1)) + # Negative indices are not adjusted. + self.assertEqual(unpack(slice(-3, -1)), (-3, -1, 1)) + self.assertEqual(unpack(slice(Index(1), Index(7), Index(2))), + (1, 7, 2)) + + # Values which do not fit in Py_ssize_t are silently clipped. + self.assertEqual(unpack(slice(1, 2**1000)), (1, SSIZE_MAX, 1)) + self.assertEqual(unpack(slice(1, -2**1000)), (1, SSIZE_MIN, 1)) + self.assertEqual(unpack(slice(2**1000, 7)), (SSIZE_MAX, 7, 1)) + self.assertEqual(unpack(slice(-2**1000, 7)), (SSIZE_MIN, 7, 1)) + self.assertEqual(unpack(slice(1, 7, 2**1000)), (1, 7, SSIZE_MAX)) + # The step is boosted to -PY_SSIZE_T_MAX, not PY_SSIZE_T_MIN, so + # that negating it is safe. + self.assertEqual(unpack(slice(7, 1, -2**1000)), (7, 1, -SSIZE_MAX)) + self.assertEqual(unpack(slice(7, 1, SSIZE_MIN)), (7, 1, -SSIZE_MAX)) + + with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): + unpack(slice(1, 1, 0)) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + unpack(slice('a', 7)) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + unpack(slice(1, 'a')) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + unpack(slice(1, 7, 'a')) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + unpack(slice(BadIndex(), 7)) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + unpack(slice(1, BadIndex())) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + unpack(slice(1, 7, BadIndex())) + + # CRASHES unpack(NULL) + # CRASHES unpack(object()) + + def test_adjustindices(self): + # Test PySlice_AdjustIndices() + adjust = _testlimitedcapi.slice_adjustindices + self.assertEqual(adjust(10, 1, 7, 1), (6, 1, 7)) + self.assertEqual(adjust(10, 1, 7, 2), (3, 1, 7)) + self.assertEqual(adjust(10, 7, 1, -1), (6, 7, 1)) + self.assertEqual(adjust(10, 7, 1, -2), (3, 7, 1)) + # An empty slice keeps the adjusted indices. + self.assertEqual(adjust(10, 7, 1, 1), (0, 7, 1)) + self.assertEqual(adjust(10, 1, 7, -1), (0, 1, 7)) + + # Negative indices are added to the length. + self.assertEqual(adjust(10, -9, -3, 1), (6, 1, 7)) + self.assertEqual(adjust(10, -3, -9, -1), (6, 7, 1)) + + # Out of bounds indices are clipped. + self.assertEqual(adjust(10, -100, 100, 1), (10, 0, 10)) + self.assertEqual(adjust(10, 100, -100, -1), (10, 9, -1)) + self.assertEqual(adjust(10, SSIZE_MIN, SSIZE_MAX, 1), (10, 0, 10)) + self.assertEqual(adjust(10, SSIZE_MAX, SSIZE_MIN, -1), (10, 9, -1)) + self.assertEqual(adjust(0, 1, 7, 1), (0, 0, 0)) + self.assertEqual(adjust(0, 7, 1, -1), (0, -1, -1)) + + # The returned length is the length of the corresponding range. + for length in LENGTHS: + for start in VALUES[1:]: + for stop in VALUES[1:]: + for step in STEPS[1:]: + with self.subTest(length=length, start=start, + stop=stop, step=step): + slicelength, start2, stop2 = adjust(length, start, + stop, step) + self.assertEqual(slicelength, + len(range(start2, stop2, step))) + + # Negative length is not supported, but does not fail. + self.assertEqual(adjust(-3, 1, 7, 1), (0, -3, -3)) + self.assertEqual(adjust(-3, 7, 1, -1), (0, -4, -4)) + self.assertEqual(adjust(-3, -10, -5, 1), (0, 0, 0)) + + # The step is asserted to be neither zero nor less than + # -PY_SSIZE_T_MAX. + # CRASHES adjust(10, 0, 10, 0) + # CRASHES adjust(10, 0, 10, SSIZE_MIN) + + +class GetIndicesExMacroTest(unittest.TestCase): + # PySlice_GetIndicesEx() is a macro using PySlice_Unpack() and + # PySlice_AdjustIndices(). It is also a deprecated function, exported + # for the stable ABI. + getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_macro) + getindicesex_seq = staticmethod( + _testlimitedcapi.slice_getindicesex_seq_macro) + # The macro evaluates the length after calling PySlice_Unpack(), so the + # size of the list after removing an item is used. + resized = (6, 8, 1, 2) + + def test_getindicesex(self): + # Test PySlice_GetIndicesEx() + getindicesex = self.getindicesex + self.assertEqual(getindicesex(slice(1, 7, 2), 10), (1, 7, 2, 3)) + self.assertEqual(getindicesex(slice(7, 1, -2), 10), (7, 1, -2, 3)) + self.assertEqual(getindicesex(slice(Index(1), Index(7), Index(2)), 10), + (1, 7, 2, 3)) + + # The result agrees with slice.indices() and the slice length is + # the length of the corresponding range. + for length in LENGTHS: + for start in VALUES: + for stop in VALUES: + for step in STEPS: + s = slice(start, stop, step) + with self.subTest(slice=s, length=length): + indices = s.indices(length) + self.assertEqual(getindicesex(s, length), + indices + (len(range(*indices)),)) + + # Negative indices are added to the length. + self.assertEqual(getindicesex(slice(-9, -3), 10), (1, 7, 1, 6)) + self.assertEqual(getindicesex(slice(-3, -9, -1), 10), (7, 1, -1, 6)) + + # Out of bounds indices are clipped. + self.assertEqual(getindicesex(slice(-100, 100), 10), (0, 10, 1, 10)) + self.assertEqual(getindicesex(slice(100, -100, -1), 10), + (9, -1, -1, 10)) + self.assertEqual(getindicesex(slice(None), 0), (0, 0, 1, 0)) + self.assertEqual(getindicesex(slice(1, 7, 2), 0), (0, 0, 2, 0)) + self.assertEqual(getindicesex(slice(None, None, -1), 0), + (-1, -1, -1, 0)) + + # Indices which do not fit in Py_ssize_t are clipped, not rejected. + # Note that slice.indices() does not clip the step. + self.assertEqual(getindicesex(slice(1, 2**1000), 10), (1, 10, 1, 9)) + self.assertEqual(getindicesex(slice(2**1000, 7), 10), (10, 7, 1, 0)) + self.assertEqual(getindicesex(slice(1, 7, 2**1000), 10), + (1, 7, SSIZE_MAX, 1)) + # -PY_SSIZE_T_MAX-1 is replaced with -PY_SSIZE_T_MAX. + self.assertEqual(getindicesex(slice(7, 1, -2**1000), 10), + (7, 1, -SSIZE_MAX, 1)) + self.assertEqual(getindicesex(slice(7, 1, SSIZE_MIN), 10), + (7, 1, -SSIZE_MAX, 1)) + + with self.assertRaisesRegex(ValueError, 'slice step cannot be zero'): + getindicesex(slice(1, 7, 0), 10) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + getindicesex(slice('a', 7), 10) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + getindicesex(slice(1, 'a'), 10) + with self.assertRaisesRegex(TypeError, + 'slice indices must be integers'): + getindicesex(slice(1, 7, 'a'), 10) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + getindicesex(slice(BadIndex(), 7), 10) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + getindicesex(slice(1, BadIndex()), 10) + with self.assertRaisesRegex(RuntimeError, 'bad index'): + getindicesex(slice(1, 7, BadIndex()), 10) + + # Negative length is not supported, but does not fail. + self.assertEqual(getindicesex(slice(None), -3), (-3, -3, 1, 0)) + self.assertEqual(getindicesex(slice(1, 7, 2), -3), (-3, -3, 2, 0)) + self.assertEqual(getindicesex(slice(7, 1, -2), -3), (-4, -4, -2, 0)) + + # CRASHES getindicesex(NULL, 10) + # CRASHES getindicesex(object(), 10) + + def test_getindicesex_seq(self): + # The length is the size of a sequence. + getindicesex_seq = self.getindicesex_seq + seq = list(range(10)) + self.assertEqual(getindicesex_seq(slice(-3, -1), seq), (7, 9, 1, 2)) + self.assertEqual(getindicesex_seq(slice(-3, -1), []), (0, 0, 1, 0)) + + # gh-72054: __index__() can resize the sequence. Negative indices + # are adjusted by the length, so the result depends on when it is + # evaluated. + seq = list(range(10)) + self.assertEqual(getindicesex_seq(slice(PopIndex(-3, seq), -1), seq), + self.resized) + self.assertEqual(len(seq), 9, seq) + + seq = list(range(10)) + self.assertEqual(getindicesex_seq(slice(-3, PopIndex(-1, seq)), seq), + self.resized) + self.assertEqual(len(seq), 9, seq) + + seq = list(range(10)) + self.assertEqual( + getindicesex_seq(slice(-3, -1, PopIndex(1, seq)), seq), + self.resized) + self.assertEqual(len(seq), 9, seq) + + # CRASHES getindicesex_seq(slice(None), NULL) + # CRASHES getindicesex_seq(slice(None), object()) + + +class GetIndicesExFuncTest(GetIndicesExMacroTest): + # The deprecated function is equivalent to the macro, except that the + # length is evaluated before the call. + getindicesex = staticmethod(_testlimitedcapi.slice_getindicesex_func) + getindicesex_seq = staticmethod( + _testlimitedcapi.slice_getindicesex_seq_func) + # The size of the list before removing an item is used. + resized = (7, 9, 1, 2) + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 696fec1290e5f3e..0ca1d90ac4f30b7 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -174,7 +174,7 @@ @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c @MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slice.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c _testlimitedcapi/hash.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index b30e32c56c57041..0d290eb5ef5b9e4 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -58,6 +58,9 @@ module_exec(PyObject *mod) if (_PyTestLimitedCAPI_Init_Set(mod) < 0) { return -1; } + if (_PyTestLimitedCAPI_Init_Slice(mod) < 0) { + return -1; + } if (_PyTestLimitedCAPI_Init_Slots(mod) < 0) { return -1; } diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h index e8a2d82fb94aea4..ab6f76f3d06699e 100644 --- a/Modules/_testlimitedcapi/parts.h +++ b/Modules/_testlimitedcapi/parts.h @@ -40,6 +40,7 @@ int _PyTestLimitedCAPI_Init_List(PyObject *module); int _PyTestLimitedCAPI_Init_Long(PyObject *module); int _PyTestLimitedCAPI_Init_PyOS(PyObject *module); int _PyTestLimitedCAPI_Init_Set(PyObject *module); +int _PyTestLimitedCAPI_Init_Slice(PyObject *module); int _PyTestLimitedCAPI_Init_Slots(PyObject *module); int _PyTestLimitedCAPI_Init_Sys(PyObject *module); int _PyTestLimitedCAPI_Init_ThreadState(PyObject *module); diff --git a/Modules/_testlimitedcapi/slice.c b/Modules/_testlimitedcapi/slice.c new file mode 100644 index 000000000000000..febb3d0de3cfa6c --- /dev/null +++ b/Modules/_testlimitedcapi/slice.c @@ -0,0 +1,240 @@ +#include "pyconfig.h" // Py_GIL_DISABLED +#ifdef Py_GIL_DISABLED +# define Py_TARGET_ABI3T 0x030f0000 +#else + // Need limited C API 3.6.1 for PySlice_Unpack() and PySlice_AdjustIndices() + // and for PySlice_GetIndicesEx() implemented as a macro. +# define Py_LIMITED_API 0x03060100 +#endif + +#include "parts.h" +#include "util.h" + + +static PyObject * +slice_check(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PySlice_Check(obj)); +} + +static PyObject * +slice_new(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *start, *stop, *step; + + if (!PyArg_ParseTuple(args, "OOO", &start, &stop, &step)) { + return NULL; + } + NULLABLE(start); + NULLABLE(stop); + NULLABLE(step); + return PySlice_New(start, stop, step); +} + +/* Returns the (start, stop, step) triple on success. If PySlice_GetIndices() + * fails without setting an exception, returns None. */ +static PyObject * +slice_getindices(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice; + Py_ssize_t length; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "On", &slice, &length)) { + return NULL; + } + NULLABLE(slice); + if (PySlice_GetIndices(slice, length, &start, &stop, &step) < 0) { + if (PyErr_Occurred()) { + return NULL; + } + Py_RETURN_NONE; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + return Py_BuildValue("nnn", start, stop, step); +} + +/* Test PySlice_GetIndicesEx() implemented as a macro using PySlice_Unpack() + * and PySlice_AdjustIndices(). */ +static PyObject * +slice_getindicesex_macro(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice; + Py_ssize_t length = UNINITIALIZED_SIZE; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "On", &slice, &length)) { + return NULL; + } + NULLABLE(slice); + if (PySlice_GetIndicesEx(slice, length, + &start, &stop, &step, &slicelength) < 0) { + assert(PyErr_Occurred()); + /* The macro sets the slice length to 0 on error. */ + assert(slicelength == 0); + return NULL; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + assert(slicelength != UNINITIALIZED_SIZE); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + +/* Same as slice_getindicesex_macro(), but the length is the size of a sequence. + * The macro evaluates it after calling PySlice_Unpack(), which can execute + * arbitrary Python code and resize the sequence. */ +static PyObject * +slice_getindicesex_seq_macro(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice, *seq; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "OO", &slice, &seq)) { + return NULL; + } + NULLABLE(slice); + NULLABLE(seq); + if (PySlice_GetIndicesEx(slice, Py_SIZE(seq), + &start, &stop, &step, &slicelength) < 0) { + assert(PyErr_Occurred()); + assert(slicelength == 0); + return NULL; + } + assert(!PyErr_Occurred()); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + +static PyObject * +slice_unpack(PyObject *Py_UNUSED(module), PyObject *slice) +{ + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + + NULLABLE(slice); + if (PySlice_Unpack(slice, &start, &stop, &step) < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + return Py_BuildValue("nnn", start, stop, step); +} + +static PyObject * +slice_adjustindices(PyObject *Py_UNUSED(module), PyObject *args) +{ + Py_ssize_t length, start, stop, step; + + if (!PyArg_ParseTuple(args, "nnnn", &length, &start, &stop, &step)) { + return NULL; + } + Py_ssize_t slicelength = PySlice_AdjustIndices(length, &start, &stop, step); + assert(!PyErr_Occurred()); + return Py_BuildValue("nnn", slicelength, start, stop); +} + +#undef PySlice_GetIndicesEx + +/* Test the deprecated PySlice_GetIndicesEx() function. It is still exported + * for the stable ABI and used if Py_LIMITED_API is older than 3.5.4. */ +static PyObject * +slice_getindicesex_func(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice; + Py_ssize_t length; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "On", &slice, &length)) { + return NULL; + } + NULLABLE(slice); +// Ignore deprecation warnings +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS + int res = PySlice_GetIndicesEx(slice, length, + &start, &stop, &step, &slicelength); +_Py_COMP_DIAG_POP + if (res < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + assert(start != UNINITIALIZED_SIZE); + assert(stop != UNINITIALIZED_SIZE); + assert(step != UNINITIALIZED_SIZE); + assert(slicelength != UNINITIALIZED_SIZE); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + + +/* Same as slice_getindicesex_seq_macro(), but using the deprecated function. + * The length is evaluated before the call. */ +static PyObject * +slice_getindicesex_seq_func(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *slice, *seq; + Py_ssize_t start = UNINITIALIZED_SIZE; + Py_ssize_t stop = UNINITIALIZED_SIZE; + Py_ssize_t step = UNINITIALIZED_SIZE; + Py_ssize_t slicelength = UNINITIALIZED_SIZE; + + if (!PyArg_ParseTuple(args, "OO", &slice, &seq)) { + return NULL; + } + NULLABLE(slice); + NULLABLE(seq); +// Ignore deprecation warnings +_Py_COMP_DIAG_PUSH +_Py_COMP_DIAG_IGNORE_DEPR_DECLS + int res = PySlice_GetIndicesEx(slice, Py_SIZE(seq), + &start, &stop, &step, &slicelength); +_Py_COMP_DIAG_POP + if (res < 0) { + assert(PyErr_Occurred()); + return NULL; + } + assert(!PyErr_Occurred()); + return Py_BuildValue("nnnn", start, stop, step, slicelength); +} + + +static PyMethodDef test_methods[] = { + {"slice_check", slice_check, METH_O}, + {"slice_new", slice_new, METH_VARARGS}, + {"slice_getindices", slice_getindices, METH_VARARGS}, + {"slice_getindicesex_macro", slice_getindicesex_macro, METH_VARARGS}, + {"slice_getindicesex_seq_macro", slice_getindicesex_seq_macro, METH_VARARGS}, + {"slice_getindicesex_func", slice_getindicesex_func, + METH_VARARGS}, + {"slice_getindicesex_seq_func", slice_getindicesex_seq_func, + METH_VARARGS}, + {"slice_unpack", slice_unpack, METH_O}, + {"slice_adjustindices", slice_adjustindices, METH_VARARGS}, + {NULL}, +}; + +int +_PyTestLimitedCAPI_Init_Slice(PyObject *m) +{ + return PyModule_AddFunctions(m, test_methods); +} diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj index 4218e0ed3945d27..2995dbb469ed3dd 100644 --- a/PCbuild/_testlimitedcapi.vcxproj +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -110,6 +110,7 @@ + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters index ddef60e599d4853..92f1c5dedbd62e8 100644 --- a/PCbuild/_testlimitedcapi.vcxproj.filters +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -25,6 +25,7 @@ +