From cf6c1f3dd4d14a1808afd7a71a188fa317df8df2 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Mon, 28 Sep 2026 16:52:54 +0300 Subject: [PATCH 1/2] gh-158345: Don't grow an array when append, insert or extend overflows ins1() checks the item with setitem(self, -1, v) before growing the array, but since gh-156865 the 'e', 'f' and 'Zf' setters detect an overflow only when they pack into the array, which that call skips. The array then grew by one item before the real store failed. Pack into a local buffer before the bounds check, so the check call fails too, and copy the result into the array only for a real index. --- Lib/test/test_array.py | 20 ++++++++++++ ...-09-28-13-52-36.gh-issue-158345.GNlDCF.rst | 3 ++ Modules/arraymodule.c | 32 ++++++++++--------- 3 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-28-13-52-36.gh-issue-158345.GNlDCF.rst diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index ba9c25c835bc39d..943420f4c3f4e8b 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -1612,6 +1612,13 @@ def test_overflows(self): self.assertRaises(OverflowError, array.array, self.typecode, [123456]) # Overflows also float type: self.assertRaises(OverflowError, array.array, self.typecode, [1e300]) + # A failed append, insert or extend leaves the array unchanged: + for x in 123456, 1e300: + a = array.array(self.typecode, [1]) + self.assertRaises(OverflowError, a.append, x) + self.assertRaises(OverflowError, a.insert, 0, x) + self.assertRaises(OverflowError, a.extend, [x]) + self.assertEqual(a, array.array(self.typecode, [1])) class FloatTest(FPTest, unittest.TestCase): typecode = 'f' @@ -1619,6 +1626,12 @@ class FloatTest(FPTest, unittest.TestCase): def test_overflows(self): self.assertRaises(OverflowError, array.array, self.typecode, [1e300]) + # A failed append, insert or extend leaves the array unchanged: + a = array.array(self.typecode, [1]) + self.assertRaises(OverflowError, a.append, 1e300) + self.assertRaises(OverflowError, a.insert, 0, 1e300) + self.assertRaises(OverflowError, a.extend, [1e300]) + self.assertEqual(a, array.array(self.typecode, [1])) class DoubleTest(FPTest, unittest.TestCase): typecode = 'd' @@ -1649,6 +1662,13 @@ class ComplexFloatTest(CFPTest, unittest.TestCase): def test_overflows(self): self.assertRaises(OverflowError, array.array, self.typecode, [1e300]) self.assertRaises(OverflowError, array.array, self.typecode, [1e300j]) + # A failed append, insert or extend leaves the array unchanged: + for x in 1e300, 1e300j: + a = array.array(self.typecode, [1]) + self.assertRaises(OverflowError, a.append, x) + self.assertRaises(OverflowError, a.insert, 0, x) + self.assertRaises(OverflowError, a.extend, [x]) + self.assertEqual(a, array.array(self.typecode, [1])) class ComplexDoubleTest(CFPTest, unittest.TestCase): typecode = 'Zd' diff --git a/Misc/NEWS.d/next/Library/2026-09-28-13-52-36.gh-issue-158345.GNlDCF.rst b/Misc/NEWS.d/next/Library/2026-09-28-13-52-36.gh-issue-158345.GNlDCF.rst new file mode 100644 index 000000000000000..208b3e8055e54ae --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-28-13-52-36.gh-issue-158345.GNlDCF.rst @@ -0,0 +1,3 @@ +Fix :meth:`array.array.append`, :meth:`~array.array.insert` and +:meth:`~array.array.extend` leaving an extra item in the array when they +raise :exc:`OverflowError` for the ``'e'``, ``'f'`` or ``'Zf'`` type code. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index ef492d143d56445..9b5afe09a5e7518 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -585,15 +585,18 @@ static int e_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) { double x; + char buf[sizeof(short)]; if (!PyArg_Parse(v, "d;array item must be float", &x)) { return -1; } + if (PyFloat_Pack2(x, buf, PY_LITTLE_ENDIAN) < 0) { + return -1; + } CHECK_ARRAY_BOUNDS(ap, i); if (i >= 0) { - return PyFloat_Pack2(x, ap->ob_item + sizeof(short)*i, - PY_LITTLE_ENDIAN); + memcpy(ap->ob_item + sizeof(short)*i, buf, sizeof(buf)); } return 0; } @@ -608,14 +611,17 @@ static int f_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) { double x; + char buf[sizeof(float)]; if (!PyArg_Parse(v, "d;array item must be float", &x)) return -1; + if (PyFloat_Pack4(x, buf, PY_LITTLE_ENDIAN) < 0) { + return -1; + } CHECK_ARRAY_BOUNDS(ap, i); if (i >= 0) { - return PyFloat_Pack4(x, ap->ob_item + sizeof(float)*i, - PY_LITTLE_ENDIAN); + memcpy(ap->ob_item + sizeof(float)*i, buf, sizeof(buf)); } return 0; } @@ -653,25 +659,21 @@ static int cf_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) { Py_complex x; + char f[8]; if (!PyArg_Parse(v, "D;array item must be complex", &x)) { return -1; } + if (PyFloat_Pack4(x.real, f, PY_LITTLE_ENDIAN) < 0 + || PyFloat_Pack4(x.imag, f + sizeof(float), PY_LITTLE_ENDIAN) < 0) + { + return -1; + } CHECK_ARRAY_BOUNDS(ap, i); if (i >= 0) { - char f[8]; - int ret = PyFloat_Pack4(x.real, f, PY_LITTLE_ENDIAN); - - if (ret) { - return ret; - } - ret = PyFloat_Pack4(x.imag, f + sizeof(float), PY_LITTLE_ENDIAN); - if (!ret) { - memcpy(ap->ob_item + i*sizeof(f), &f, sizeof(f)); - } - return ret; + memcpy(ap->ob_item + i*sizeof(f), &f, sizeof(f)); } return 0; } From f0fe2dc92830e5adc48fed4f8268a9ae4cb51e0e Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Mon, 2 Nov 2026 19:04:33 +0200 Subject: [PATCH 2/2] Call setitem once in ins1() and remove the new slot if it fails --- Modules/arraymodule.c | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 9b5afe09a5e7518..fee9ba3d6debf24 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -585,18 +585,15 @@ static int e_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) { double x; - char buf[sizeof(short)]; if (!PyArg_Parse(v, "d;array item must be float", &x)) { return -1; } - if (PyFloat_Pack2(x, buf, PY_LITTLE_ENDIAN) < 0) { - return -1; - } CHECK_ARRAY_BOUNDS(ap, i); if (i >= 0) { - memcpy(ap->ob_item + sizeof(short)*i, buf, sizeof(buf)); + return PyFloat_Pack2(x, ap->ob_item + sizeof(short)*i, + PY_LITTLE_ENDIAN); } return 0; } @@ -611,17 +608,14 @@ static int f_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) { double x; - char buf[sizeof(float)]; if (!PyArg_Parse(v, "d;array item must be float", &x)) return -1; - if (PyFloat_Pack4(x, buf, PY_LITTLE_ENDIAN) < 0) { - return -1; - } CHECK_ARRAY_BOUNDS(ap, i); if (i >= 0) { - memcpy(ap->ob_item + sizeof(float)*i, buf, sizeof(buf)); + return PyFloat_Pack4(x, ap->ob_item + sizeof(float)*i, + PY_LITTLE_ENDIAN); } return 0; } @@ -659,21 +653,25 @@ static int cf_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v) { Py_complex x; - char f[8]; if (!PyArg_Parse(v, "D;array item must be complex", &x)) { return -1; } - if (PyFloat_Pack4(x.real, f, PY_LITTLE_ENDIAN) < 0 - || PyFloat_Pack4(x.imag, f + sizeof(float), PY_LITTLE_ENDIAN) < 0) - { - return -1; - } CHECK_ARRAY_BOUNDS(ap, i); if (i >= 0) { - memcpy(ap->ob_item + i*sizeof(f), &f, sizeof(f)); + char f[8]; + int ret = PyFloat_Pack4(x.real, f, PY_LITTLE_ENDIAN); + + if (ret) { + return ret; + } + ret = PyFloat_Pack4(x.imag, f + sizeof(float), PY_LITTLE_ENDIAN); + if (!ret) { + memcpy(ap->ob_item + i*sizeof(f), &f, sizeof(f)); + } + return ret; } return 0; } @@ -825,6 +823,8 @@ getarrayitem(PyObject *op, Py_ssize_t i) return (*ap->ob_descr->getitem)(ap, i); } +static int array_del_slice(arrayobject *a, Py_ssize_t ilow, Py_ssize_t ihigh); + static int ins1(arrayobject *self, Py_ssize_t where, PyObject *v) { @@ -834,8 +834,6 @@ ins1(arrayobject *self, Py_ssize_t where, PyObject *v) PyErr_BadInternalCall(); return -1; } - if ((*self->ob_descr->setitem)(self, -1, v) < 0) - return -1; if (array_resize(self, n+1) == -1) return -1; @@ -852,7 +850,11 @@ ins1(arrayobject *self, Py_ssize_t where, PyObject *v) memmove(items + (where+1)*self->ob_descr->itemsize, items + where*self->ob_descr->itemsize, (n-where)*self->ob_descr->itemsize); - return (*self->ob_descr->setitem)(self, where, v); + if ((*self->ob_descr->setitem)(self, where, v) < 0) { + array_del_slice(self, where, where+1); + return -1; + } + return 0; } /* Methods */