From 0e590c415fa81aa77a901365586997679038fc4e Mon Sep 17 00:00:00 2001 From: kalyanr Date: Wed, 1 Nov 2023 20:40:39 +0530 Subject: [PATCH 01/21] gh-111495: Add tests for PyTuple C API Co-authored-by: Sergey B Kirpichev --- Lib/test/test_capi/test_tuple.py | 189 +++++++++++++++++++++++ Modules/Setup.stdlib.in | 2 +- Modules/_testcapi/tuple.c | 69 ++++++++- Modules/_testlimitedcapi.c | 3 + Modules/_testlimitedcapi/parts.h | 1 + Modules/_testlimitedcapi/tuple.c | 125 +++++++++++++++ PCbuild/_testlimitedcapi.vcxproj | 1 + PCbuild/_testlimitedcapi.vcxproj.filters | 1 + 8 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 Lib/test/test_capi/test_tuple.py create mode 100644 Modules/_testlimitedcapi/tuple.c diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py new file mode 100644 index 00000000000000..72e4ae7d48f1e8 --- /dev/null +++ b/Lib/test/test_capi/test_tuple.py @@ -0,0 +1,189 @@ +import unittest +import sys +from collections import namedtuple +from test.support import import_helper + +_testcapi = import_helper.import_module('_testcapi') +_testlimitedcapi = import_helper.import_module('_testlimitedcapi') + +NULL = None +PY_SSIZE_T_MIN = _testcapi.PY_SSIZE_T_MIN +PY_SSIZE_T_MAX = _testcapi.PY_SSIZE_T_MAX + +class TupleSubclass(tuple): + pass + + +class CAPITest(unittest.TestCase): + def test_check(self): + # Test PyTuple_Check() + check = _testlimitedcapi.tuple_check + + self.assertTrue(check((1, 2))) + self.assertTrue(check(())) + self.assertTrue(check(TupleSubclass((1, 2)))) + self.assertFalse(check({1: 2})) + self.assertFalse(check([1, 2])) + self.assertFalse(check(42)) + self.assertFalse(check(object())) + + # CRASHES check(NULL) + + def test_tuple_checkexact(self): + # Test PyTuple_CheckExact() + check = _testlimitedcapi.tuple_checkexact + + self.assertTrue(check((1, 2))) + self.assertTrue(check(())) + self.assertFalse(check(TupleSubclass((1, 2)))) + self.assertFalse(check({1: 2})) + self.assertFalse(check([1, 2])) + self.assertFalse(check(42)) + self.assertFalse(check(object())) + + # CRASHES check(NULL) + + def test_tuple_new(self): + # Test PyTuple_New() + tuple_new = _testlimitedcapi.tuple_new + size = _testlimitedcapi.tuple_size + + tup1 = tuple_new(0) + self.assertEqual(tup1, ()) + self.assertEqual(size(tup1), 0) + self.assertIs(type(tup1), tuple) + tup2 = tuple_new(1) + self.assertIs(type(tup2), tuple) + self.assertEqual(size(tup2), 1) + self.assertIsNot(tup2, tup1) + + self.assertRaises(SystemError, tuple_new, -1) + self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) + self.assertRaises(MemoryError, tuple_new, PY_SSIZE_T_MAX) + + def test_tuple_pack(self): + # Test PyTuple_Pack() + pack = _testlimitedcapi.tuple_pack + + self.assertEqual(pack(0), ()) + self.assertEqual(pack(1, 1), (1,)) + self.assertEqual(pack(2, 1, 2), (1, 2)) + + self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN) + self.assertRaises(SystemError, pack, -1) + self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX) + + # CRASHES pack(1, NULL) + + def test_tuple_size(self): + # Test PyTuple_Size() + size = _testlimitedcapi.tuple_size + + self.assertEqual(size((1, 2)), 2) + self.assertEqual(size(TupleSubclass((1, 2))), 2) + + self.assertRaises(SystemError, size, []) + self.assertRaises(SystemError, size, 42) + self.assertRaises(SystemError, size, object()) + + # CRASHES size(NULL) + + def test_tuple_get_size(self): + # Test PyTuple_GET_SIZE() + size = _testcapi.tuple_get_size + + self.assertEqual(size(()), 0) + self.assertEqual(size((1, 2)), 2) + self.assertEqual(size(TupleSubclass((1, 2))), 2) + + def test_tuple_getitem(self): + # Test PyTuple_GetItem() + getitem = _testlimitedcapi.tuple_getitem + + tup = TupleSubclass((1, 2, 3)) + self.assertEqual(getitem(tup, 0), 1) + self.assertEqual(getitem(tup, 2), 3) + + tup = (1, 2, 3) + self.assertEqual(getitem(tup, 0), 1) + self.assertEqual(getitem(tup, 2), 3) + + self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MIN) + self.assertRaises(IndexError, getitem, tup, -1) + self.assertRaises(IndexError, getitem, tup, len(tup)) + self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MAX) + self.assertRaises(SystemError, getitem, 42, 1) + + # CRASHES getitem(NULL, 1) + + def test_tuple_get_item(self): + # Test PyTuple_GET_ITEM() + get_item = _testcapi.tuple_get_item + + tup = TupleSubclass((1, 2, 3)) + self.assertEqual(get_item(tup, 0), 1) + self.assertEqual(get_item(tup, 2), 3) + + tup = (1, 2, 3) + self.assertEqual(get_item(tup, 0), 1) + self.assertEqual(get_item(tup, 2), 3) + + def test_tuple_getslice(self): + # Test PyTuple_GetSlice() + getslice = _testlimitedcapi.tuple_getslice + + tup = (1, 2, 3) + + # empty + self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ()) + self.assertEqual(getslice(tup, -1, 0), ()) + self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ()) + + # slice + self.assertEqual(getslice(tup, 1, 3), (2, 3)) + + # whole + self.assertEqual(getslice(tup, 0, 3), tup) + self.assertEqual(getslice(tup, 0, 100), tup) + self.assertEqual(getslice(tup, -100, 100), tup) + + # CRASHES getslice(NULL, 0, 0) + + def test_tuple_setitem(self): + # Test PyTuple_SetItem() + setitem = _testlimitedcapi.tuple_setitem + + tup = (0, 0) + self.assertEqual(setitem(tup, 0, 1), (1, 0)) + self.assertEqual(setitem(tup, 1, 1), (0, 1)) + + self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, 1) + self.assertRaises(IndexError, setitem, tup, -1, 1) + self.assertRaises(IndexError, setitem, tup, len(tup), 1) + self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MAX, 1) + + # CRASHES setitem(NULL, 1, 5) + + def test_tuple_set_item(self): + # Test PyTuple_SET_ITEM() + set_item = _testcapi.tuple_set_item + + tup = (0, 0) + self.assertEqual(set_item(tup, 0, 1), (1, 0)) + self.assertEqual(set_item(tup, 1, 1), (0, 1)) + + def test_tuple_resize(self): + # Test PyTuple_Resize() + tuple_new = _testlimitedcapi.tuple_new + resize = _testcapi.tuple_resize + size = _testlimitedcapi.tuple_size + tup = tuple_new(0) + self.assertEqual(size(tup), 0) + tup = resize(tup, 2) + self.assertEqual(size(tup), 2) + tup = tuple_new(0) + self.assertRaises(MemoryError, resize, tup, PY_SSIZE_T_MAX) + self.assertRaises(SystemError, resize, tup, -1) + self.assertRaises(SystemError, resize, tup, PY_SSIZE_T_MIN) + self.assertRaises(SystemError, resize, object(), 42) + self.assertRaises(SystemError, resize, NULL, 42) diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 78b979698fcd75..06b30feef43e40 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -164,7 +164,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 @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/monitoring.c -@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c +@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 95dde8c0edadbe..c8a4191838c5ab 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -2,14 +2,81 @@ #include "util.h" +static PyObject * +tuple_get_size(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + RETURN_SIZE(PyTuple_GET_SIZE(obj)); +} + +static PyObject * +tuple_get_item(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "On", &obj, &i)) { + return NULL; + } + NULLABLE(obj); + return Py_XNewRef(PyTuple_GET_ITEM(obj, i)); +} + +static PyObject * +tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj, *value, *newtuple; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) { + return NULL; + } + NULLABLE(obj); + NULLABLE(value); + if (obj) { + Py_ssize_t size = PyTuple_Size(obj); + newtuple = PyTuple_New(size); + if (!newtuple) { + return NULL; + } + for (Py_ssize_t n = 0; n < size; n++) { + PyTuple_SetItem(newtuple, n, Py_XNewRef(PyTuple_GetItem(obj, n))); + } + } + else { + newtuple = obj; + } + PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value)); + return newtuple; +} + +static PyObject * +tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t newsize; + if (!PyArg_ParseTuple(args, "On", &obj, &newsize)) { + return NULL; + } + NULLABLE(obj); + int r = _PyTuple_Resize(&obj, newsize); + if (r == -1) { + return NULL; + } + return obj; +} + + static PyMethodDef test_methods[] = { + {"tuple_get_size", tuple_get_size, METH_O}, + {"tuple_get_item", tuple_get_item, METH_VARARGS}, + {"tuple_set_item", tuple_set_item, METH_VARARGS}, + {"tuple_resize", tuple_resize, METH_VARARGS}, {NULL}, }; int _PyTestCapi_Init_Tuple(PyObject *m) { - if (PyModule_AddFunctions(m, test_methods) < 0){ + if (PyModule_AddFunctions(m, test_methods) < 0) { return -1; } diff --git a/Modules/_testlimitedcapi.c b/Modules/_testlimitedcapi.c index f88476f4be2054..eb59c1a4b14e10 100644 --- a/Modules/_testlimitedcapi.c +++ b/Modules/_testlimitedcapi.c @@ -68,6 +68,9 @@ PyInit__testlimitedcapi(void) if (_PyTestLimitedCAPI_Init_Sys(mod) < 0) { return NULL; } + if (_PyTestLimitedCAPI_Init_Tuple(mod) < 0) { + return NULL; + } if (_PyTestLimitedCAPI_Init_Unicode(mod) < 0) { return NULL; } diff --git a/Modules/_testlimitedcapi/parts.h b/Modules/_testlimitedcapi/parts.h index d5e590a8dcd679..140396d6b990ff 100644 --- a/Modules/_testlimitedcapi/parts.h +++ b/Modules/_testlimitedcapi/parts.h @@ -35,6 +35,7 @@ int _PyTestLimitedCAPI_Init_Long(PyObject *module); int _PyTestLimitedCAPI_Init_PyOS(PyObject *module); int _PyTestLimitedCAPI_Init_Set(PyObject *module); int _PyTestLimitedCAPI_Init_Sys(PyObject *module); +int _PyTestLimitedCAPI_Init_Tuple(PyObject *module); int _PyTestLimitedCAPI_Init_Unicode(PyObject *module); int _PyTestLimitedCAPI_Init_VectorcallLimited(PyObject *module); diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c new file mode 100644 index 00000000000000..50df35a2740c0a --- /dev/null +++ b/Modules/_testlimitedcapi/tuple.c @@ -0,0 +1,125 @@ +#include "parts.h" +#include "util.h" + + +static PyObject * +tuple_check(PyObject* Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyTuple_Check(obj)); +} + +static PyObject * +tuple_checkexact(PyObject* Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + return PyLong_FromLong(PyTuple_CheckExact(obj)); +} + +static PyObject * +tuple_new(PyObject* Py_UNUSED(module), PyObject *len) +{ + return PyTuple_New(PyLong_AsSsize_t(len)); +} + +static PyObject * +tuple_pack(PyObject *Py_UNUSED(module), PyObject *args) +{ + Py_ssize_t size = PyLong_AsSize_t(PyTuple_GetItem(args, 0)); + if (size == 1) { + PyObject *arg1 = PyTuple_GetItem(args, 1); + NULLABLE(arg1); + return PyTuple_Pack(1, arg1); + } + else if (size == 2) { + PyObject *arg1 = PyTuple_GetItem(args, 1); + PyObject *arg2 = PyTuple_GetItem(args, 2); + NULLABLE(arg1); + NULLABLE(arg2); + return PyTuple_Pack(2, arg1, arg2); + } + return PyTuple_Pack(size); +} + +static PyObject * +tuple_size(PyObject *Py_UNUSED(module), PyObject *obj) +{ + NULLABLE(obj); + RETURN_SIZE(PyTuple_Size(obj)); +} + +static PyObject * +tuple_getitem(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "On", &obj, &i)) { + return NULL; + } + NULLABLE(obj); + return Py_XNewRef(PyTuple_GetItem(obj, i)); +} + +static PyObject * +tuple_getslice(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t ilow, ihigh; + if (!PyArg_ParseTuple(args, "Onn", &obj, &ilow, &ihigh)) { + return NULL; + } + NULLABLE(obj); + return PyTuple_GetSlice(obj, ilow, ihigh); +} + +static PyObject * +tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj, *value, *newtuple; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) { + return NULL; + } + NULLABLE(obj); + NULLABLE(value); + if (obj) { + Py_ssize_t size = PyTuple_Size(obj); + newtuple = PyTuple_New(size); + if (!newtuple) { + return NULL; + } + for (Py_ssize_t n = 0; n < size; n++) { + PyTuple_SetItem(newtuple, n, Py_XNewRef(PyTuple_GetItem(obj, n))); + } + } + else { + newtuple = obj; + } + if (PyTuple_SetItem(newtuple, i, Py_XNewRef(value)) == -1) { + return NULL; + } + return newtuple; +} + + +static PyMethodDef test_methods[] = { + {"tuple_check", tuple_check, METH_O}, + {"tuple_checkexact", tuple_checkexact, METH_O}, + {"tuple_new", tuple_new, METH_O}, + {"tuple_pack", tuple_pack, METH_VARARGS}, + {"tuple_size", tuple_size, METH_O}, + {"tuple_getitem", tuple_getitem, METH_VARARGS}, + {"tuple_getslice", tuple_getslice, METH_VARARGS}, + {"tuple_setitem", tuple_setitem, METH_VARARGS}, + {NULL}, +}; + +int +_PyTestLimitedCAPI_Init_Tuple(PyObject *m) +{ + if (PyModule_AddFunctions(m, test_methods) < 0) { + return -1; + } + + return 0; +} diff --git a/PCbuild/_testlimitedcapi.vcxproj b/PCbuild/_testlimitedcapi.vcxproj index 252039d93103bd..7e5809fec31791 100644 --- a/PCbuild/_testlimitedcapi.vcxproj +++ b/PCbuild/_testlimitedcapi.vcxproj @@ -107,6 +107,7 @@ + diff --git a/PCbuild/_testlimitedcapi.vcxproj.filters b/PCbuild/_testlimitedcapi.vcxproj.filters index 7efbb0acf8f960..47f059040bed91 100644 --- a/PCbuild/_testlimitedcapi.vcxproj.filters +++ b/PCbuild/_testlimitedcapi.vcxproj.filters @@ -23,6 +23,7 @@ + From ddf0af7dc51c7e540409d5a9f6636544d5cd64db Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 8 May 2024 12:53:53 +0300 Subject: [PATCH 02/21] +1 --- Lib/test/test_capi/test_tuple.py | 14 +++++--------- Modules/_testcapi/tuple.c | 5 ++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 72e4ae7d48f1e8..f38edf2d3029c8 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -174,16 +174,12 @@ def test_tuple_set_item(self): def test_tuple_resize(self): # Test PyTuple_Resize() - tuple_new = _testlimitedcapi.tuple_new resize = _testcapi.tuple_resize size = _testlimitedcapi.tuple_size - tup = tuple_new(0) + tup = resize(0) self.assertEqual(size(tup), 0) - tup = resize(tup, 2) + tup = resize(2) self.assertEqual(size(tup), 2) - tup = tuple_new(0) - self.assertRaises(MemoryError, resize, tup, PY_SSIZE_T_MAX) - self.assertRaises(SystemError, resize, tup, -1) - self.assertRaises(SystemError, resize, tup, PY_SSIZE_T_MIN) - self.assertRaises(SystemError, resize, object(), 42) - self.assertRaises(SystemError, resize, NULL, 42) + self.assertRaises(MemoryError, resize, PY_SSIZE_T_MAX) + self.assertRaises(SystemError, resize, -1) + self.assertRaises(SystemError, resize, PY_SSIZE_T_MIN) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index c8a4191838c5ab..71026acced035d 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -51,12 +51,11 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) static PyObject * tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) { - PyObject *obj; Py_ssize_t newsize; - if (!PyArg_ParseTuple(args, "On", &obj, &newsize)) { + if (!PyArg_ParseTuple(args, "n", &newsize)) { return NULL; } - NULLABLE(obj); + PyObject *obj = PyTuple_New(0); int r = _PyTuple_Resize(&obj, newsize); if (r == -1) { return NULL; From 571da31cf1c10b896930adceb3cbdca95b5dfd11 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 9 May 2024 13:10:46 +0300 Subject: [PATCH 03/21] Update Lib/test/test_capi/test_tuple.py Co-authored-by: Serhiy Storchaka --- Lib/test/test_capi/test_tuple.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index f38edf2d3029c8..ca60af40e9bbc3 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -112,6 +112,7 @@ def test_tuple_getitem(self): self.assertRaises(IndexError, getitem, tup, -1) self.assertRaises(IndexError, getitem, tup, len(tup)) self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MAX) + self.assertRaises(SystemError, getitem, [1, 2, 3], 1) self.assertRaises(SystemError, getitem, 42, 1) # CRASHES getitem(NULL, 1) From 69d9ead3634c95ca63af8b0acb3e27a91161376b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 9 May 2024 13:47:01 +0300 Subject: [PATCH 04/21] address review: more tests --- Lib/test/test_capi/test_tuple.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index ca60af40e9bbc3..3a40c5ba626147 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -133,21 +133,38 @@ def test_tuple_getslice(self): # Test PyTuple_GetSlice() getslice = _testlimitedcapi.tuple_getslice - tup = (1, 2, 3) - # empty + tup = (1, 2, 3) + self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ()) + self.assertEqual(getslice(tup, -1, 0), ()) + self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ()) + self.assertEqual(getslice(tup, 0, 0), ()) + self.assertEqual(getslice(tup, 3, 0), ()) + tup = TupleSubclass((1, 2, 3)) self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ()) self.assertEqual(getslice(tup, -1, 0), ()) self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ()) + self.assertEqual(getslice(tup, 0, 0), ()) + self.assertEqual(getslice(tup, 3, 0), ()) # slice + tup = (1, 2, 3) + self.assertEqual(getslice(tup, 1, 3), (2, 3)) + tup = TupleSubclass((1, 2, 3)) self.assertEqual(getslice(tup, 1, 3), (2, 3)) # whole + tup = (1, 2, 3) + self.assertEqual(getslice(tup, 0, 3), tup) + self.assertEqual(getslice(tup, 0, 100), tup) + self.assertEqual(getslice(tup, -100, 100), tup) + tup = TupleSubclass((1, 2, 3)) self.assertEqual(getslice(tup, 0, 3), tup) self.assertEqual(getslice(tup, 0, 100), tup) self.assertEqual(getslice(tup, -100, 100), tup) + self.assertRaises(SystemError, getslice, [1, 2, 3], 0, 1) + self.assertRaises(SystemError, getslice, 42, 0, 1) # CRASHES getslice(NULL, 0, 0) def test_tuple_setitem(self): @@ -162,6 +179,8 @@ def test_tuple_setitem(self): self.assertRaises(IndexError, setitem, tup, -1, 1) self.assertRaises(IndexError, setitem, tup, len(tup), 1) self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MAX, 1) + self.assertRaises(SystemError, setitem, [1], 0, 1) + self.assertRaises(SystemError, setitem, 42, 0, 1) # CRASHES setitem(NULL, 1, 5) From 3260ef4eae47c818990fa28f82fcd638915bbb37 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 9 May 2024 14:50:26 +0300 Subject: [PATCH 05/21] address review: simplify tuple_pack() --- Lib/test/test_capi/test_tuple.py | 1 + Modules/_testlimitedcapi/tuple.c | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 3a40c5ba626147..4c9914e2037c5e 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -74,6 +74,7 @@ def test_tuple_pack(self): self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX) # CRASHES pack(1, NULL) + # CRASHES pack(2, 1) def test_tuple_size(self): # Test PyTuple_Size() diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c index 50df35a2740c0a..4f856adb7fdedd 100644 --- a/Modules/_testlimitedcapi/tuple.c +++ b/Modules/_testlimitedcapi/tuple.c @@ -25,18 +25,19 @@ tuple_new(PyObject* Py_UNUSED(module), PyObject *len) static PyObject * tuple_pack(PyObject *Py_UNUSED(module), PyObject *args) { - Py_ssize_t size = PyLong_AsSize_t(PyTuple_GetItem(args, 0)); - if (size == 1) { - PyObject *arg1 = PyTuple_GetItem(args, 1); - NULLABLE(arg1); - return PyTuple_Pack(1, arg1); + PyObject *arg1 = NULL, *arg2 = NULL; + Py_ssize_t size; + + if (!PyArg_ParseTuple(args, "n|OO", &size, &arg1, &arg2)) { + return NULL; } - else if (size == 2) { - PyObject *arg1 = PyTuple_GetItem(args, 1); - PyObject *arg2 = PyTuple_GetItem(args, 2); + if (arg1) { NULLABLE(arg1); - NULLABLE(arg2); - return PyTuple_Pack(2, arg1, arg2); + if (arg2) { + NULLABLE(arg2); + return PyTuple_Pack(size, arg1, arg2); + } + return PyTuple_Pack(size, arg1); } return PyTuple_Pack(size); } From b5b4664e982c607d9ab12a5976f228b334c08d69 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 9 May 2024 16:38:13 +0300 Subject: [PATCH 06/21] address review: strict checks in tuple_set_item/setitem --- Modules/_testcapi/tuple.c | 4 ++-- Modules/_testlimitedcapi/tuple.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 71026acced035d..308c91da40481d 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -29,9 +29,8 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) { return NULL; } - NULLABLE(obj); NULLABLE(value); - if (obj) { + if (PyTuple_CheckExact(obj)) { Py_ssize_t size = PyTuple_Size(obj); newtuple = PyTuple_New(size); if (!newtuple) { @@ -42,6 +41,7 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) } } else { + NULLABLE(obj); newtuple = obj; } PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value)); diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c index 4f856adb7fdedd..3eba16209a2a4a 100644 --- a/Modules/_testlimitedcapi/tuple.c +++ b/Modules/_testlimitedcapi/tuple.c @@ -81,9 +81,8 @@ tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) { return NULL; } - NULLABLE(obj); NULLABLE(value); - if (obj) { + if (PyTuple_CheckExact(obj)) { Py_ssize_t size = PyTuple_Size(obj); newtuple = PyTuple_New(size); if (!newtuple) { @@ -94,6 +93,7 @@ tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) } } else { + NULLABLE(obj); newtuple = obj; } if (PyTuple_SetItem(newtuple, i, Py_XNewRef(value)) == -1) { From 1d53b0f7375060d80a578df7ae65493499526d23 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 10 May 2024 06:37:52 +0300 Subject: [PATCH 07/21] address review: redo resize() tests --- Lib/test/test_capi/test_tuple.py | 35 ++++++++++++++++++++++++-------- Modules/_testcapi/tuple.c | 20 ++++++++++++++---- 2 files changed, 43 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 4c9914e2037c5e..6803dcf2054d5d 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -166,6 +166,7 @@ def test_tuple_getslice(self): self.assertRaises(SystemError, getslice, [1, 2, 3], 0, 1) self.assertRaises(SystemError, getslice, 42, 0, 1) + # CRASHES getslice(NULL, 0, 0) def test_tuple_setitem(self): @@ -196,11 +197,29 @@ def test_tuple_set_item(self): def test_tuple_resize(self): # Test PyTuple_Resize() resize = _testcapi.tuple_resize - size = _testlimitedcapi.tuple_size - tup = resize(0) - self.assertEqual(size(tup), 0) - tup = resize(2) - self.assertEqual(size(tup), 2) - self.assertRaises(MemoryError, resize, PY_SSIZE_T_MAX) - self.assertRaises(SystemError, resize, -1) - self.assertRaises(SystemError, resize, PY_SSIZE_T_MIN) + + a = () + b = resize(0, a) + self.assertEqual(b, a) + b = resize(2, a) + self.assertEqual(len(b), 2) + + a = (1, 2, 3) + b = resize(3, a) + self.assertEqual(b, a) + b = resize(2, a) + self.assertEqual(b, a[:2]) + b = resize(5, a) + self.assertEqual(len(b), 5) + self.assertEqual(b[:3], a) + + a = () + self.assertRaises(MemoryError, resize, PY_SSIZE_T_MAX, a) + self.assertRaises(SystemError, resize, -1, a) + self.assertRaises(SystemError, resize, PY_SSIZE_T_MIN, a) + # refcount > 1 + a = (1, 2, 3) + self.assertRaises(SystemError, resize, 2, a, False) + # non-tuple + a = [1, 2, 3] + self.assertRaises(SystemError, resize, 2, a) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 308c91da40481d..4db838b6c6e712 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -51,16 +51,28 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) static PyObject * tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) { + PyObject *tup; Py_ssize_t newsize; - if (!PyArg_ParseTuple(args, "n", &newsize)) { + int new = 1; + if (!PyArg_ParseTuple(args, "nO|p", &newsize, &tup, &new)) { return NULL; } - PyObject *obj = PyTuple_New(0); - int r = _PyTuple_Resize(&obj, newsize); + if (new) { + Py_ssize_t size = PyTuple_Size(tup); + PyObject *newtup = PyTuple_New(size); + for (Py_ssize_t n = 0; n < size; n++) { + PyTuple_SetItem(newtup, n, Py_XNewRef(PyTuple_GetItem(tup, n))); + } + tup = newtup; + } + else { + Py_XINCREF(tup); + } + int r = _PyTuple_Resize(&tup, newsize); if (r == -1) { return NULL; } - return obj; + return tup; } From 8451a4dc1f65c69ce72575c2408fa7f777ed81ea Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 17 Jun 2024 13:35:41 +0300 Subject: [PATCH 08/21] address review: * fix reference leak * add assert * revert arguments of tuple_resize() --- Lib/test/test_capi/test_tuple.py | 20 ++++++++++---------- Modules/_testcapi/tuple.c | 7 ++++--- Modules/_testlimitedcapi/tuple.c | 5 ++++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 6803dcf2054d5d..34bbd193ab04bb 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -199,27 +199,27 @@ def test_tuple_resize(self): resize = _testcapi.tuple_resize a = () - b = resize(0, a) + b = resize(a, 0) self.assertEqual(b, a) - b = resize(2, a) + b = resize(a, 2) self.assertEqual(len(b), 2) a = (1, 2, 3) - b = resize(3, a) + b = resize(a, 3) self.assertEqual(b, a) - b = resize(2, a) + b = resize(a, 2) self.assertEqual(b, a[:2]) - b = resize(5, a) + b = resize(a, 5) self.assertEqual(len(b), 5) self.assertEqual(b[:3], a) a = () - self.assertRaises(MemoryError, resize, PY_SSIZE_T_MAX, a) - self.assertRaises(SystemError, resize, -1, a) - self.assertRaises(SystemError, resize, PY_SSIZE_T_MIN, a) + self.assertRaises(MemoryError, resize, a, PY_SSIZE_T_MAX) + self.assertRaises(SystemError, resize, a, -1) + self.assertRaises(SystemError, resize, a, PY_SSIZE_T_MIN) # refcount > 1 a = (1, 2, 3) - self.assertRaises(SystemError, resize, 2, a, False) + self.assertRaises(SystemError, resize, a, 2, False) # non-tuple a = [1, 2, 3] - self.assertRaises(SystemError, resize, 2, a) + self.assertRaises(SystemError, resize, a, 2) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 4db838b6c6e712..7853868eece28f 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -37,7 +37,7 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) return NULL; } for (Py_ssize_t n = 0; n < size; n++) { - PyTuple_SetItem(newtuple, n, Py_XNewRef(PyTuple_GetItem(obj, n))); + PyTuple_SET_ITEM(newtuple, n, Py_XNewRef(PyTuple_GET_ITEM(obj, n))); } } else { @@ -54,14 +54,14 @@ tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) PyObject *tup; Py_ssize_t newsize; int new = 1; - if (!PyArg_ParseTuple(args, "nO|p", &newsize, &tup, &new)) { + if (!PyArg_ParseTuple(args, "On|p", &tup, &newsize, &new)) { return NULL; } if (new) { Py_ssize_t size = PyTuple_Size(tup); PyObject *newtup = PyTuple_New(size); for (Py_ssize_t n = 0; n < size; n++) { - PyTuple_SetItem(newtup, n, Py_XNewRef(PyTuple_GetItem(tup, n))); + PyTuple_SET_ITEM(newtup, n, Py_XNewRef(PyTuple_GET_ITEM(tup, n))); } tup = newtup; } @@ -70,6 +70,7 @@ tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) } int r = _PyTuple_Resize(&tup, newsize); if (r == -1) { + assert(tup == NULL); return NULL; } return tup; diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c index 3eba16209a2a4a..8bff8da7219b9f 100644 --- a/Modules/_testlimitedcapi/tuple.c +++ b/Modules/_testlimitedcapi/tuple.c @@ -76,7 +76,7 @@ tuple_getslice(PyObject *Py_UNUSED(module), PyObject *args) static PyObject * tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) { - PyObject *obj, *value, *newtuple; + PyObject *obj, *value, *newtuple = NULL; Py_ssize_t i; if (!PyArg_ParseTuple(args, "OnO", &obj, &i, &value)) { return NULL; @@ -97,6 +97,9 @@ tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) newtuple = obj; } if (PyTuple_SetItem(newtuple, i, Py_XNewRef(value)) == -1) { + if (PyTuple_CheckExact(obj)) { + Py_XDECREF(newtuple); + } return NULL; } return newtuple; From b2d0eaca90d87560c51cf244e63f2e7e54e82581 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 06:35:20 +0300 Subject: [PATCH 09/21] address review: check error for PyTuple_SetItem() --- Modules/_testlimitedcapi/tuple.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c index 8bff8da7219b9f..3966697e5426c0 100644 --- a/Modules/_testlimitedcapi/tuple.c +++ b/Modules/_testlimitedcapi/tuple.c @@ -89,7 +89,10 @@ tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) return NULL; } for (Py_ssize_t n = 0; n < size; n++) { - PyTuple_SetItem(newtuple, n, Py_XNewRef(PyTuple_GetItem(obj, n))); + if (PyTuple_SetItem(newtuple, n, + Py_XNewRef(PyTuple_GetItem(obj, n))) == -1) { + return NULL; + } } } else { From a977efa0e674fbfc26c38777fc7b12f4500ae407 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 06:58:01 +0300 Subject: [PATCH 10/21] address review: fix reference leak and add tests for non-immortal objects --- Lib/test/test_capi/test_tuple.py | 35 +++++++++++++++++--------------- Modules/_testcapi/tuple.c | 2 ++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 34bbd193ab04bb..1643231d10c392 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -68,6 +68,7 @@ def test_tuple_pack(self): self.assertEqual(pack(0), ()) self.assertEqual(pack(1, 1), (1,)) self.assertEqual(pack(2, 1, 2), (1, 2)) + self.assertEqual(pack(2, [1], 2), ([1], 2)) self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN) self.assertRaises(SystemError, pack, -1) @@ -105,8 +106,9 @@ def test_tuple_getitem(self): self.assertEqual(getitem(tup, 0), 1) self.assertEqual(getitem(tup, 2), 3) - tup = (1, 2, 3) + tup = (1, [2], 3) self.assertEqual(getitem(tup, 0), 1) + self.assertEqual(getitem(tup, 1), [2]) self.assertEqual(getitem(tup, 2), 3) self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MIN) @@ -126,8 +128,9 @@ def test_tuple_get_item(self): self.assertEqual(get_item(tup, 0), 1) self.assertEqual(get_item(tup, 2), 3) - tup = (1, 2, 3) + tup = (1, [2], 3) self.assertEqual(get_item(tup, 0), 1) + self.assertEqual(get_item(tup, 1), [2]) self.assertEqual(get_item(tup, 2), 3) def test_tuple_getslice(self): @@ -149,22 +152,22 @@ def test_tuple_getslice(self): self.assertEqual(getslice(tup, 3, 0), ()) # slice - tup = (1, 2, 3) - self.assertEqual(getslice(tup, 1, 3), (2, 3)) - tup = TupleSubclass((1, 2, 3)) - self.assertEqual(getslice(tup, 1, 3), (2, 3)) + tup = (1, [2], 3) + self.assertEqual(getslice(tup, 1, 3), ([2], 3)) + tup = TupleSubclass((1, [2], 3)) + self.assertEqual(getslice(tup, 1, 3), ([2], 3)) # whole - tup = (1, 2, 3) + tup = (1, [2], 3) self.assertEqual(getslice(tup, 0, 3), tup) self.assertEqual(getslice(tup, 0, 100), tup) self.assertEqual(getslice(tup, -100, 100), tup) - tup = TupleSubclass((1, 2, 3)) + tup = TupleSubclass((1, [2], 3)) self.assertEqual(getslice(tup, 0, 3), tup) self.assertEqual(getslice(tup, 0, 100), tup) self.assertEqual(getslice(tup, -100, 100), tup) - self.assertRaises(SystemError, getslice, [1, 2, 3], 0, 1) + self.assertRaises(SystemError, getslice, [1, [2], 3], 0, 1) self.assertRaises(SystemError, getslice, 42, 0, 1) # CRASHES getslice(NULL, 0, 0) @@ -173,9 +176,9 @@ def test_tuple_setitem(self): # Test PyTuple_SetItem() setitem = _testlimitedcapi.tuple_setitem - tup = (0, 0) - self.assertEqual(setitem(tup, 0, 1), (1, 0)) - self.assertEqual(setitem(tup, 1, 1), (0, 1)) + tup = (0, [0]) + self.assertEqual(setitem(tup, 0, [1]), ([1], [0])) + self.assertEqual(setitem(tup, 1, [1]), (0, [1])) self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, 1) self.assertRaises(IndexError, setitem, tup, -1, 1) @@ -190,9 +193,9 @@ def test_tuple_set_item(self): # Test PyTuple_SET_ITEM() set_item = _testcapi.tuple_set_item - tup = (0, 0) - self.assertEqual(set_item(tup, 0, 1), (1, 0)) - self.assertEqual(set_item(tup, 1, 1), (0, 1)) + tup = (0, [0]) + self.assertEqual(set_item(tup, 0, [1]), ([1], [0])) + self.assertEqual(set_item(tup, 1, [1]), (0, [1])) def test_tuple_resize(self): # Test PyTuple_Resize() @@ -204,7 +207,7 @@ def test_tuple_resize(self): b = resize(a, 2) self.assertEqual(len(b), 2) - a = (1, 2, 3) + a = (1, [2], 3) b = resize(a, 3) self.assertEqual(b, a) b = resize(a, 2) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 7853868eece28f..89ef0510aaa9dc 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -44,7 +44,9 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) NULLABLE(obj); newtuple = obj; } + PyObject *val = PyTuple_GET_ITEM(newtuple, i); PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value)); + Py_DECREF(val); return newtuple; } From 95d1544f8a5838a3489de4e8c1b1451c9dce9c7b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 07:02:29 +0300 Subject: [PATCH 11/21] Add missing unittest.main() calls --- Lib/test/test_capi/test_hash.py | 4 ++++ Lib/test/test_capi/test_list.py | 4 ++++ Lib/test/test_capi/test_set.py | 4 ++++ Lib/test/test_capi/test_time.py | 4 ++++ Lib/test/test_capi/test_tuple.py | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/Lib/test/test_capi/test_hash.py b/Lib/test/test_capi/test_hash.py index 8436da7c32df10..cb2b3635f01328 100644 --- a/Lib/test/test_capi/test_hash.py +++ b/Lib/test/test_capi/test_hash.py @@ -77,3 +77,7 @@ def python_hash_pointer(x): # Py_HashPointer((void*)(uintptr_t)-1) doesn't return -1 but -2 VOID_P_MAX = -1 & (2 ** (8 * SIZEOF_VOID_P) - 1) self.assertEqual(hash_pointer(VOID_P_MAX), -2) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_capi/test_list.py b/Lib/test/test_capi/test_list.py index 0896a971f5c727..4c64328b50b8f6 100644 --- a/Lib/test/test_capi/test_list.py +++ b/Lib/test/test_capi/test_list.py @@ -347,3 +347,7 @@ def test_list_extend(self): # CRASHES list_extend(NULL, []) # CRASHES list_extend([], NULL) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_capi/test_set.py b/Lib/test/test_capi/test_set.py index 499a5148d782ab..62d90a3f94326d 100644 --- a/Lib/test/test_capi/test_set.py +++ b/Lib/test/test_capi/test_set.py @@ -265,3 +265,7 @@ def test_set_next_entry(self): with self.assertRaises(SystemError): set_next(object(), 0) # CRASHES: set_next(NULL, 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_capi/test_time.py b/Lib/test/test_capi/test_time.py index 17ebd7c1962d48..989c158818ccf0 100644 --- a/Lib/test/test_capi/test_time.py +++ b/Lib/test/test_capi/test_time.py @@ -72,3 +72,7 @@ def test_time(self): # Test PyTime_Time() and PyTime_TimeRaw() self.check_clock(_testcapi.PyTime_Time, time.time) self.check_clock(_testcapi.PyTime_TimeRaw, time.time) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 1643231d10c392..7edd083811e947 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -226,3 +226,7 @@ def test_tuple_resize(self): # non-tuple a = [1, 2, 3] self.assertRaises(SystemError, resize, a, 2) + + +if __name__ == "__main__": + unittest.main() From 14b3594c6679e79ef31faeb58e6f4deaf2a9d5c2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 07:24:15 +0300 Subject: [PATCH 12/21] address review: add tests with NULL for setitem/set_item --- Lib/test/test_capi/test_tuple.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 7edd083811e947..0fd408c309a54e 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -174,11 +174,19 @@ def test_tuple_getslice(self): def test_tuple_setitem(self): # Test PyTuple_SetItem() + + import_helper.import_module('ctypes') + from ctypes import pythonapi, py_object, c_ssize_t + setitem = _testlimitedcapi.tuple_setitem tup = (0, [0]) self.assertEqual(setitem(tup, 0, [1]), ([1], [0])) self.assertEqual(setitem(tup, 1, [1]), (0, [1])) + tup = setitem(tup, 1, NULL) + func = getattr(pythonapi, "PyTuple_GetItem") + func.argtypes = (py_object, c_ssize_t) + self.assertEqual(func(tup, 1), 0) self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, 1) self.assertRaises(IndexError, setitem, tup, -1, 1) @@ -191,11 +199,19 @@ def test_tuple_setitem(self): def test_tuple_set_item(self): # Test PyTuple_SET_ITEM() + + import_helper.import_module('ctypes') + from ctypes import pythonapi, py_object, c_ssize_t + set_item = _testcapi.tuple_set_item tup = (0, [0]) self.assertEqual(set_item(tup, 0, [1]), ([1], [0])) self.assertEqual(set_item(tup, 1, [1]), (0, [1])) + tup = set_item(tup, 1, NULL) + func = getattr(pythonapi, "PyTuple_GetItem") + func.argtypes = (py_object, c_ssize_t) + self.assertEqual(func(tup, 1), 0) def test_tuple_resize(self): # Test PyTuple_Resize() From f9554bc8d9807eaa3abdc47c4561bb0de79e13e4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 13:26:23 +0300 Subject: [PATCH 13/21] address review: fix refleak --- Modules/_testlimitedcapi/tuple.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c index 3966697e5426c0..883d17a208c5d5 100644 --- a/Modules/_testlimitedcapi/tuple.c +++ b/Modules/_testlimitedcapi/tuple.c @@ -91,6 +91,7 @@ tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) for (Py_ssize_t n = 0; n < size; n++) { if (PyTuple_SetItem(newtuple, n, Py_XNewRef(PyTuple_GetItem(obj, n))) == -1) { + Py_XDECREF(newtuple); return NULL; } } From e79616fc2c628c6182d326e1556075877f4c0e20 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 13:27:35 +0300 Subject: [PATCH 14/21] address review: rename tuple_resize --- Lib/test/test_capi/test_tuple.py | 6 +++--- Modules/_testcapi/tuple.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 0fd408c309a54e..90d00cbd858acd 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -213,9 +213,9 @@ def test_tuple_set_item(self): func.argtypes = (py_object, c_ssize_t) self.assertEqual(func(tup, 1), 0) - def test_tuple_resize(self): - # Test PyTuple_Resize() - resize = _testcapi.tuple_resize + def test__tuple_resize(self): + # Test _PyTuple_Resize() + resize = _testcapi._tuple_resize a = () b = resize(a, 0) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 89ef0510aaa9dc..111a8b3ce07bd5 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -51,7 +51,7 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) } static PyObject * -tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) +_tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) { PyObject *tup; Py_ssize_t newsize; @@ -83,7 +83,7 @@ static PyMethodDef test_methods[] = { {"tuple_get_size", tuple_get_size, METH_O}, {"tuple_get_item", tuple_get_item, METH_VARARGS}, {"tuple_set_item", tuple_set_item, METH_VARARGS}, - {"tuple_resize", tuple_resize, METH_VARARGS}, + {"_tuple_resize", _tuple_resize, METH_VARARGS}, {NULL}, }; From 26fc5a2a568932efdd3cd694bf490482f8c52662 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 14:12:58 +0300 Subject: [PATCH 15/21] address review: a helper to test if item is NULL --- Lib/test/test_capi/test_tuple.py | 18 ++++-------------- Modules/_testcapi/tuple.c | 12 ++++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 90d00cbd858acd..62e03579a59cbc 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -174,19 +174,14 @@ def test_tuple_getslice(self): def test_tuple_setitem(self): # Test PyTuple_SetItem() - - import_helper.import_module('ctypes') - from ctypes import pythonapi, py_object, c_ssize_t - setitem = _testlimitedcapi.tuple_setitem + checknull = _testcapi._check_item_is_NULL tup = (0, [0]) self.assertEqual(setitem(tup, 0, [1]), ([1], [0])) self.assertEqual(setitem(tup, 1, [1]), (0, [1])) tup = setitem(tup, 1, NULL) - func = getattr(pythonapi, "PyTuple_GetItem") - func.argtypes = (py_object, c_ssize_t) - self.assertEqual(func(tup, 1), 0) + self.assertTrue(checknull(tup, 1)) self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, 1) self.assertRaises(IndexError, setitem, tup, -1, 1) @@ -199,19 +194,14 @@ def test_tuple_setitem(self): def test_tuple_set_item(self): # Test PyTuple_SET_ITEM() - - import_helper.import_module('ctypes') - from ctypes import pythonapi, py_object, c_ssize_t - set_item = _testcapi.tuple_set_item + checknull = _testcapi._check_item_is_NULL tup = (0, [0]) self.assertEqual(set_item(tup, 0, [1]), ([1], [0])) self.assertEqual(set_item(tup, 1, [1]), (0, [1])) tup = set_item(tup, 1, NULL) - func = getattr(pythonapi, "PyTuple_GetItem") - func.argtypes = (py_object, c_ssize_t) - self.assertEqual(func(tup, 1), 0) + self.assertTrue(checknull(tup, 1)) def test__tuple_resize(self): # Test _PyTuple_Resize() diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 111a8b3ce07bd5..fac10cdd47238c 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -78,12 +78,24 @@ _tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) return tup; } +static PyObject * +_check_item_is_NULL(PyObject *Py_UNUSED(module), PyObject *args) +{ + PyObject *obj; + Py_ssize_t i; + if (!PyArg_ParseTuple(args, "On", &obj, &i)) { + return NULL; + } + return PyLong_FromLong(PyTuple_GET_ITEM(obj, i) == NULL); +} + static PyMethodDef test_methods[] = { {"tuple_get_size", tuple_get_size, METH_O}, {"tuple_get_item", tuple_get_item, METH_VARARGS}, {"tuple_set_item", tuple_set_item, METH_VARARGS}, {"_tuple_resize", _tuple_resize, METH_VARARGS}, + {"_check_item_is_NULL", _check_item_is_NULL, METH_VARARGS}, {NULL}, }; From 5047d6d2e438c39e3406ccf536607b0509e37905 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 14:14:41 +0300 Subject: [PATCH 16/21] address review: check no refleaks if tuple shrinks --- Lib/test/test_capi/test_tuple.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 62e03579a59cbc..ba2608c45ba543 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -213,7 +213,7 @@ def test__tuple_resize(self): b = resize(a, 2) self.assertEqual(len(b), 2) - a = (1, [2], 3) + a = (1, [2], [3]) b = resize(a, 3) self.assertEqual(b, a) b = resize(a, 2) From a2ea4c42f4ebf2e4ba293193de5b237c642ebdc2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 14:19:27 +0300 Subject: [PATCH 17/21] address review: test added elements on resize --- Lib/test/test_capi/test_tuple.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index ba2608c45ba543..c876711129e8f5 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -206,6 +206,7 @@ def test_tuple_set_item(self): def test__tuple_resize(self): # Test _PyTuple_Resize() resize = _testcapi._tuple_resize + checknull = _testcapi._check_item_is_NULL a = () b = resize(a, 0) @@ -221,6 +222,7 @@ def test__tuple_resize(self): b = resize(a, 5) self.assertEqual(len(b), 5) self.assertEqual(b[:3], a) + self.assertTrue(all(checknull(b, i) for i in range(3, 5))) a = () self.assertRaises(MemoryError, resize, a, PY_SSIZE_T_MAX) From 72aa1ed9590e4541b491c5ac385daf3ce1b2f323 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 18 Jun 2024 14:20:03 +0300 Subject: [PATCH 18/21] address review: test new elements --- Lib/test/test_capi/test_tuple.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index c876711129e8f5..12f551981ecbe2 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -47,6 +47,7 @@ def test_tuple_new(self): # Test PyTuple_New() tuple_new = _testlimitedcapi.tuple_new size = _testlimitedcapi.tuple_size + checknull = _testcapi._check_item_is_NULL tup1 = tuple_new(0) self.assertEqual(tup1, ()) @@ -56,6 +57,7 @@ def test_tuple_new(self): self.assertIs(type(tup2), tuple) self.assertEqual(size(tup2), 1) self.assertIsNot(tup2, tup1) + self.assertTrue(checknull(tup2, 0)) self.assertRaises(SystemError, tuple_new, -1) self.assertRaises(SystemError, tuple_new, PY_SSIZE_T_MIN) From 266de84b02d39fe60b3c64c635eaba596215e1b2 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 9 Aug 2024 13:19:54 +0300 Subject: [PATCH 19/21] Polish and fix some errors. --- Lib/test/test_capi/test_tuple.py | 132 +++++++++++++++++-------------- Modules/_testcapi/tuple.c | 19 +++-- Modules/_testlimitedcapi/tuple.c | 19 +++-- 3 files changed, 99 insertions(+), 71 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 12f551981ecbe2..817b403612b6b2 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -68,21 +68,21 @@ def test_tuple_pack(self): pack = _testlimitedcapi.tuple_pack self.assertEqual(pack(0), ()) - self.assertEqual(pack(1, 1), (1,)) - self.assertEqual(pack(2, 1, 2), (1, 2)) - self.assertEqual(pack(2, [1], 2), ([1], 2)) + self.assertEqual(pack(1, [1]), ([1],)) + self.assertEqual(pack(2, [1], [2]), ([1], [2])) self.assertRaises(SystemError, pack, PY_SSIZE_T_MIN) self.assertRaises(SystemError, pack, -1) self.assertRaises(MemoryError, pack, PY_SSIZE_T_MAX) # CRASHES pack(1, NULL) - # CRASHES pack(2, 1) + # CRASHES pack(2, [1]) def test_tuple_size(self): # Test PyTuple_Size() size = _testlimitedcapi.tuple_size + self.assertEqual(size(()), 0) self.assertEqual(size((1, 2)), 2) self.assertEqual(size(TupleSubclass((1, 2))), 2) @@ -104,14 +104,13 @@ def test_tuple_getitem(self): # Test PyTuple_GetItem() getitem = _testlimitedcapi.tuple_getitem - tup = TupleSubclass((1, 2, 3)) - self.assertEqual(getitem(tup, 0), 1) - self.assertEqual(getitem(tup, 2), 3) + tup = ([1], [2], [3]) + self.assertEqual(getitem(tup, 0), [1]) + self.assertEqual(getitem(tup, 2), [3]) - tup = (1, [2], 3) - self.assertEqual(getitem(tup, 0), 1) - self.assertEqual(getitem(tup, 1), [2]) - self.assertEqual(getitem(tup, 2), 3) + tup2 = TupleSubclass(([1], [2], [3])) + self.assertEqual(getitem(tup2, 0), [1]) + self.assertEqual(getitem(tup2, 2), [3]) self.assertRaises(IndexError, getitem, tup, PY_SSIZE_T_MIN) self.assertRaises(IndexError, getitem, tup, -1) @@ -120,57 +119,58 @@ def test_tuple_getitem(self): self.assertRaises(SystemError, getitem, [1, 2, 3], 1) self.assertRaises(SystemError, getitem, 42, 1) - # CRASHES getitem(NULL, 1) + # CRASHES getitem(NULL, 0) def test_tuple_get_item(self): # Test PyTuple_GET_ITEM() get_item = _testcapi.tuple_get_item - tup = TupleSubclass((1, 2, 3)) - self.assertEqual(get_item(tup, 0), 1) - self.assertEqual(get_item(tup, 2), 3) + tup = ([1], [2], [3]) + self.assertEqual(get_item(tup, 0), [1]) + self.assertEqual(get_item(tup, 2), [3]) + + tup2 = TupleSubclass(([1], [2], [3])) + self.assertEqual(get_item(tup2, 0), [1]) + self.assertEqual(get_item(tup2, 2), [3]) - tup = (1, [2], 3) - self.assertEqual(get_item(tup, 0), 1) - self.assertEqual(get_item(tup, 1), [2]) - self.assertEqual(get_item(tup, 2), 3) + # CRASHES get_item(NULL, 0) def test_tuple_getslice(self): # Test PyTuple_GetSlice() getslice = _testlimitedcapi.tuple_getslice # empty - tup = (1, 2, 3) + tup = ([1], [2], [3]) self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ()) self.assertEqual(getslice(tup, -1, 0), ()) self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ()) - self.assertEqual(getslice(tup, 0, 0), ()) - self.assertEqual(getslice(tup, 3, 0), ()) - tup = TupleSubclass((1, 2, 3)) + self.assertEqual(getslice(tup, 1, 1), ()) + self.assertEqual(getslice(tup, 2, 1), ()) + tup = TupleSubclass(([1], [2], [3])) self.assertEqual(getslice(tup, PY_SSIZE_T_MIN, 0), ()) self.assertEqual(getslice(tup, -1, 0), ()) self.assertEqual(getslice(tup, 3, PY_SSIZE_T_MAX), ()) - self.assertEqual(getslice(tup, 0, 0), ()) - self.assertEqual(getslice(tup, 3, 0), ()) + self.assertEqual(getslice(tup, 1, 1), ()) + self.assertEqual(getslice(tup, 2, 1), ()) # slice - tup = (1, [2], 3) - self.assertEqual(getslice(tup, 1, 3), ([2], 3)) - tup = TupleSubclass((1, [2], 3)) - self.assertEqual(getslice(tup, 1, 3), ([2], 3)) + tup = ([1], [2], [3], [4]) + self.assertEqual(getslice(tup, 1, 3), ([2], [3])) + tup = TupleSubclass(([1], [2], [3], [4])) + self.assertEqual(getslice(tup, 1, 3), ([2], [3])) # whole - tup = (1, [2], 3) + tup = ([1], [2], [3]) self.assertEqual(getslice(tup, 0, 3), tup) self.assertEqual(getslice(tup, 0, 100), tup) self.assertEqual(getslice(tup, -100, 100), tup) - tup = TupleSubclass((1, [2], 3)) + tup = TupleSubclass(([1], [2], [3])) self.assertEqual(getslice(tup, 0, 3), tup) self.assertEqual(getslice(tup, 0, 100), tup) self.assertEqual(getslice(tup, -100, 100), tup) - self.assertRaises(SystemError, getslice, [1, [2], 3], 0, 1) - self.assertRaises(SystemError, getslice, 42, 0, 1) + self.assertRaises(SystemError, getslice, [[1], [2], [3]], 0, 0) + self.assertRaises(SystemError, getslice, 42, 0, 0) # CRASHES getslice(NULL, 0, 0) @@ -179,31 +179,45 @@ def test_tuple_setitem(self): setitem = _testlimitedcapi.tuple_setitem checknull = _testcapi._check_item_is_NULL - tup = (0, [0]) - self.assertEqual(setitem(tup, 0, [1]), ([1], [0])) - self.assertEqual(setitem(tup, 1, [1]), (0, [1])) - tup = setitem(tup, 1, NULL) - self.assertTrue(checknull(tup, 1)) + tup = ([1], [2]) + self.assertEqual(setitem(tup, 0, []), ([], [2])) + self.assertEqual(setitem(tup, 1, []), ([1], [])) + + tup2 = setitem(tup, 1, NULL) + self.assertTrue(checknull(tup2, 1)) - self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, 1) - self.assertRaises(IndexError, setitem, tup, -1, 1) - self.assertRaises(IndexError, setitem, tup, len(tup), 1) - self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MAX, 1) - self.assertRaises(SystemError, setitem, [1], 0, 1) - self.assertRaises(SystemError, setitem, 42, 0, 1) + tup2 = TupleSubclass(([1], [2])) + self.assertRaises(SystemError, setitem, tup2, 0, []) - # CRASHES setitem(NULL, 1, 5) + self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MIN, []) + self.assertRaises(IndexError, setitem, tup, -1, []) + self.assertRaises(IndexError, setitem, tup, len(tup), []) + self.assertRaises(IndexError, setitem, tup, PY_SSIZE_T_MAX, []) + self.assertRaises(SystemError, setitem, [1], 0, []) + self.assertRaises(SystemError, setitem, 42, 0, []) + + # CRASHES setitem(NULL, 0, []) def test_tuple_set_item(self): # Test PyTuple_SET_ITEM() set_item = _testcapi.tuple_set_item checknull = _testcapi._check_item_is_NULL - tup = (0, [0]) - self.assertEqual(set_item(tup, 0, [1]), ([1], [0])) - self.assertEqual(set_item(tup, 1, [1]), (0, [1])) - tup = set_item(tup, 1, NULL) - self.assertTrue(checknull(tup, 1)) + tup = ([1], [2]) + self.assertEqual(set_item(tup, 0, []), ([], [2])) + self.assertEqual(set_item(tup, 1, []), ([1], [])) + + tup2 = set_item(tup, 1, NULL) + self.assertTrue(checknull(tup2, 1)) + + tup2 = TupleSubclass(([1], [2])) + self.assertIs(set_item(tup2, 0, []), tup2) + self.assertEqual(tup2, ([], [2])) + + # CRASHES set_item(tup, -1, []) + # CRASHES set_item(tup, len(tup), []) + # CRASHES set_item([1], 0, []) + # CRASHES set_item(NULL, 0, []) def test__tuple_resize(self): # Test _PyTuple_Resize() @@ -211,12 +225,14 @@ def test__tuple_resize(self): checknull = _testcapi._check_item_is_NULL a = () - b = resize(a, 0) - self.assertEqual(b, a) - b = resize(a, 2) + b = resize(a, 0, False) + self.assertEqual(len(b), 0) + self.assertEqual(len(a), 0) + b = resize(a, 2, False) self.assertEqual(len(b), 2) + self.assertEqual(len(a), 0) - a = (1, [2], [3]) + a = ([1], [2], [3]) b = resize(a, 3) self.assertEqual(b, a) b = resize(a, 2) @@ -232,11 +248,11 @@ def test__tuple_resize(self): self.assertRaises(SystemError, resize, a, PY_SSIZE_T_MIN) # refcount > 1 a = (1, 2, 3) - self.assertRaises(SystemError, resize, a, 2, False) + self.assertRaises(SystemError, resize, a, 3, False) + self.assertRaises(SystemError, resize, a, 0, False) # non-tuple - a = [1, 2, 3] - self.assertRaises(SystemError, resize, a, 2) - + self.assertRaises(SystemError, resize, [1, 2, 3], 0, False) + self.assertRaises(SystemError, resize, NULL, 0, False) if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index fac10cdd47238c..bb9452fb548f3a 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -39,15 +39,20 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) for (Py_ssize_t n = 0; n < size; n++) { PyTuple_SET_ITEM(newtuple, n, Py_XNewRef(PyTuple_GET_ITEM(obj, n))); } + + PyObject *val = PyTuple_GET_ITEM(newtuple, i); + PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value)); + Py_DECREF(val); + return newtuple; } else { NULLABLE(obj); - newtuple = obj; + + PyObject *val = PyTuple_GET_ITEM(obj, i); + PyTuple_SET_ITEM(obj, i, Py_XNewRef(value)); + Py_DECREF(val); + return Py_XNewRef(obj); } - PyObject *val = PyTuple_GET_ITEM(newtuple, i); - PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value)); - Py_DECREF(val); - return newtuple; } static PyObject * @@ -62,12 +67,16 @@ _tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) if (new) { Py_ssize_t size = PyTuple_Size(tup); PyObject *newtup = PyTuple_New(size); + if (!newtup) { + return NULL; + } for (Py_ssize_t n = 0; n < size; n++) { PyTuple_SET_ITEM(newtup, n, Py_XNewRef(PyTuple_GET_ITEM(tup, n))); } tup = newtup; } else { + NULLABLE(tup); Py_XINCREF(tup); } int r = _PyTuple_Resize(&tup, newsize); diff --git a/Modules/_testlimitedcapi/tuple.c b/Modules/_testlimitedcapi/tuple.c index 883d17a208c5d5..231ec12d517046 100644 --- a/Modules/_testlimitedcapi/tuple.c +++ b/Modules/_testlimitedcapi/tuple.c @@ -91,22 +91,25 @@ tuple_setitem(PyObject *Py_UNUSED(module), PyObject *args) for (Py_ssize_t n = 0; n < size; n++) { if (PyTuple_SetItem(newtuple, n, Py_XNewRef(PyTuple_GetItem(obj, n))) == -1) { - Py_XDECREF(newtuple); + Py_DECREF(newtuple); return NULL; } } + + if (PyTuple_SetItem(newtuple, i, Py_XNewRef(value)) == -1) { + Py_DECREF(newtuple); + return NULL; + } + return newtuple; } else { NULLABLE(obj); - newtuple = obj; - } - if (PyTuple_SetItem(newtuple, i, Py_XNewRef(value)) == -1) { - if (PyTuple_CheckExact(obj)) { - Py_XDECREF(newtuple); + + if (PyTuple_SetItem(obj, i, Py_XNewRef(value)) == -1) { + return NULL; } - return NULL; + return Py_XNewRef(obj); } - return newtuple; } From 190e88a2fa3e114c734b203e4cad822b726d1085 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 9 Aug 2024 13:29:43 +0300 Subject: [PATCH 20/21] Update Modules/_testcapi/tuple.c Co-authored-by: Victor Stinner --- Modules/_testcapi/tuple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index bb9452fb548f3a..2a1958ab1d385c 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -31,7 +31,7 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) } NULLABLE(value); if (PyTuple_CheckExact(obj)) { - Py_ssize_t size = PyTuple_Size(obj); + Py_ssize_t size = PyTuple_GET_SIZE(obj); newtuple = PyTuple_New(size); if (!newtuple) { return NULL; From d25eae8509c0dcf83ee8710653fc45c0a142be28 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 9 Aug 2024 13:40:26 +0300 Subject: [PATCH 21/21] Address Victor's comments. --- Lib/test/test_capi/test_tuple.py | 17 +++++++++------- Modules/_testcapi/tuple.c | 33 ++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Lib/test/test_capi/test_tuple.py b/Lib/test/test_capi/test_tuple.py index 817b403612b6b2..e6b49caeb51f32 100644 --- a/Lib/test/test_capi/test_tuple.py +++ b/Lib/test/test_capi/test_tuple.py @@ -47,7 +47,7 @@ def test_tuple_new(self): # Test PyTuple_New() tuple_new = _testlimitedcapi.tuple_new size = _testlimitedcapi.tuple_size - checknull = _testcapi._check_item_is_NULL + checknull = _testcapi._check_tuple_item_is_NULL tup1 = tuple_new(0) self.assertEqual(tup1, ()) @@ -177,7 +177,7 @@ def test_tuple_getslice(self): def test_tuple_setitem(self): # Test PyTuple_SetItem() setitem = _testlimitedcapi.tuple_setitem - checknull = _testcapi._check_item_is_NULL + checknull = _testcapi._check_tuple_item_is_NULL tup = ([1], [2]) self.assertEqual(setitem(tup, 0, []), ([], [2])) @@ -201,7 +201,7 @@ def test_tuple_setitem(self): def test_tuple_set_item(self): # Test PyTuple_SET_ITEM() set_item = _testcapi.tuple_set_item - checknull = _testcapi._check_item_is_NULL + checknull = _testcapi._check_tuple_item_is_NULL tup = ([1], [2]) self.assertEqual(set_item(tup, 0, []), ([], [2])) @@ -222,15 +222,17 @@ def test_tuple_set_item(self): def test__tuple_resize(self): # Test _PyTuple_Resize() resize = _testcapi._tuple_resize - checknull = _testcapi._check_item_is_NULL + checknull = _testcapi._check_tuple_item_is_NULL a = () b = resize(a, 0, False) - self.assertEqual(len(b), 0) self.assertEqual(len(a), 0) + self.assertEqual(len(b), 0) b = resize(a, 2, False) - self.assertEqual(len(b), 2) self.assertEqual(len(a), 0) + self.assertEqual(len(b), 2) + self.assertTrue(checknull(b, 0)) + self.assertTrue(checknull(b, 1)) a = ([1], [2], [3]) b = resize(a, 3) @@ -240,7 +242,8 @@ def test__tuple_resize(self): b = resize(a, 5) self.assertEqual(len(b), 5) self.assertEqual(b[:3], a) - self.assertTrue(all(checknull(b, i) for i in range(3, 5))) + self.assertTrue(checknull(b, 3)) + self.assertTrue(checknull(b, 4)) a = () self.assertRaises(MemoryError, resize, a, PY_SSIZE_T_MAX) diff --git a/Modules/_testcapi/tuple.c b/Modules/_testcapi/tuple.c index 2a1958ab1d385c..d9c02ba0ff04fe 100644 --- a/Modules/_testcapi/tuple.c +++ b/Modules/_testcapi/tuple.c @@ -21,6 +21,20 @@ tuple_get_item(PyObject *Py_UNUSED(module), PyObject *args) return Py_XNewRef(PyTuple_GET_ITEM(obj, i)); } +static PyObject * +tuple_copy(PyObject *tuple) +{ + Py_ssize_t size = PyTuple_GET_SIZE(tuple); + PyObject *newtuple = PyTuple_New(size); + if (!newtuple) { + return NULL; + } + for (Py_ssize_t n = 0; n < size; n++) { + PyTuple_SET_ITEM(newtuple, n, Py_XNewRef(PyTuple_GET_ITEM(tuple, n))); + } + return newtuple; +} + static PyObject * tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) { @@ -31,14 +45,10 @@ tuple_set_item(PyObject *Py_UNUSED(module), PyObject *args) } NULLABLE(value); if (PyTuple_CheckExact(obj)) { - Py_ssize_t size = PyTuple_GET_SIZE(obj); - newtuple = PyTuple_New(size); + newtuple = tuple_copy(obj); if (!newtuple) { return NULL; } - for (Py_ssize_t n = 0; n < size; n++) { - PyTuple_SET_ITEM(newtuple, n, Py_XNewRef(PyTuple_GET_ITEM(obj, n))); - } PyObject *val = PyTuple_GET_ITEM(newtuple, i); PyTuple_SET_ITEM(newtuple, i, Py_XNewRef(value)); @@ -65,15 +75,10 @@ _tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) return NULL; } if (new) { - Py_ssize_t size = PyTuple_Size(tup); - PyObject *newtup = PyTuple_New(size); - if (!newtup) { + tup = tuple_copy(tup); + if (!tup) { return NULL; } - for (Py_ssize_t n = 0; n < size; n++) { - PyTuple_SET_ITEM(newtup, n, Py_XNewRef(PyTuple_GET_ITEM(tup, n))); - } - tup = newtup; } else { NULLABLE(tup); @@ -88,7 +93,7 @@ _tuple_resize(PyObject *Py_UNUSED(module), PyObject *args) } static PyObject * -_check_item_is_NULL(PyObject *Py_UNUSED(module), PyObject *args) +_check_tuple_item_is_NULL(PyObject *Py_UNUSED(module), PyObject *args) { PyObject *obj; Py_ssize_t i; @@ -104,7 +109,7 @@ static PyMethodDef test_methods[] = { {"tuple_get_item", tuple_get_item, METH_VARARGS}, {"tuple_set_item", tuple_set_item, METH_VARARGS}, {"_tuple_resize", _tuple_resize, METH_VARARGS}, - {"_check_item_is_NULL", _check_item_is_NULL, METH_VARARGS}, + {"_check_tuple_item_is_NULL", _check_tuple_item_is_NULL, METH_VARARGS}, {NULL}, };