diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index 83b3c978da3581..3e7f84b674fa00 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -1367,6 +1367,21 @@ def test_frombytearray(self): b = array.array(self.typecode, a) self.assertEqual(a, b) + def test_tofile_concurrent_mutation(self): + # Prevent crash when a writer concurrently mutates the array. + # See https://github.com/python/cpython/issues/142884. + # Keep 'BLOCKSIZE' in sync with the array.tofile() C implementation. + BLOCKSIZE = 64 * 1024 + victim = array.array('B', b'\0' * (BLOCKSIZE * 2)) + + class Writer: + def write(self, data): + victim.clear() + return 0 + + victim.tofile(Writer()) # should not crash + + class IntegerNumberTest(NumberTest): def test_type_error(self): a = array.array(self.typecode) diff --git a/Misc/NEWS.d/next/Library/2025-12-18-11-41-37.gh-issue-142884.kjgukd.rst b/Misc/NEWS.d/next/Library/2025-12-18-11-41-37.gh-issue-142884.kjgukd.rst new file mode 100644 index 00000000000000..dd5b28de2089be --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-18-11-41-37.gh-issue-142884.kjgukd.rst @@ -0,0 +1 @@ +:mod:`array`: fix a crash in :mod:`array.array.tofile` when the array is concurrently modified by the writer. diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 729e085c19f006..60d477b9aa9e9f 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -1587,27 +1587,37 @@ static PyObject * array_array_tofile_impl(arrayobject *self, PyTypeObject *cls, PyObject *f) /*[clinic end generated code: output=4560c628d9c18bc2 input=5a24da7a7b407b52]*/ { - Py_ssize_t nbytes = Py_SIZE(self) * self->ob_descr->itemsize; /* Write 64K blocks at a time */ /* XXX Make the block size settable */ - int BLOCKSIZE = 64*1024; - Py_ssize_t nblocks = (nbytes + BLOCKSIZE - 1) / BLOCKSIZE; - Py_ssize_t i; + Py_ssize_t BLOCKSIZE = 64*1024; + Py_ssize_t max_items = PY_SSIZE_T_MAX / self->ob_descr->itemsize; + Py_ssize_t total_size = Py_SIZE(self); + Py_ssize_t current_nbytes = total_size * self->ob_descr->itemsize; + Py_ssize_t offset = 0; if (Py_SIZE(self) == 0) goto done; - array_state *state = get_array_state_by_class(cls); assert(state != NULL); - for (i = 0; i < nblocks; i++) { - char* ptr = self->ob_item + i*BLOCKSIZE; + if (total_size == 0) { + goto done; + } + + if (total_size > max_items) { + return PyErr_NoMemory(); + } + + while (self->ob_item != NULL && offset < current_nbytes) { Py_ssize_t size = BLOCKSIZE; + if (offset + size > current_nbytes) { + size = current_nbytes - offset; + } + + char* ptr = self->ob_item + offset; PyObject *bytes, *res; - if (i*BLOCKSIZE + size > nbytes) - size = nbytes - i*BLOCKSIZE; bytes = PyBytes_FromStringAndSize(ptr, size); if (bytes == NULL) return NULL; @@ -1616,6 +1626,8 @@ array_array_tofile_impl(arrayobject *self, PyTypeObject *cls, PyObject *f) if (res == NULL) return NULL; Py_DECREF(res); /* drop write result */ + + offset += size; } done: