diff --git a/Lib/test/test_array.py b/Lib/test/test_array.py index 83b3c978da3581..81e3ac8f17cefb 100755 --- a/Lib/test/test_array.py +++ b/Lib/test/test_array.py @@ -1367,6 +1367,23 @@ def test_frombytearray(self): b = array.array(self.typecode, a) self.assertEqual(a, b) + def test_tofile_concurrent_mutation(self): + # Keep this test in sync with the implementation in + # Modules/arraymodule.c:array_array_tofile_impl() + BLOCKSIZE = 64 * 1024 + victim = array.array('B', b'\0' * (BLOCKSIZE * 2)) + + class Writer: + cleared = False + def write(self, data): + if not self.cleared: + self.cleared = True + victim.clear() + return 0 + + victim.tofile(Writer()) + + 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..1a5cd63300b1cc 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -1587,27 +1587,41 @@ 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; 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; - Py_ssize_t size = BLOCKSIZE; + Py_ssize_t offset = 0; + while (1) { + Py_ssize_t total_size = Py_SIZE(self); + if (self->ob_item == NULL || total_size == 0) { + break; + } + + if (total_size > max_items) { + return PyErr_NoMemory(); + } + + Py_ssize_t current_nbytes = total_size * self->ob_descr->itemsize; + if (offset >= current_nbytes) { + break; + } + + Py_ssize_t size = current_nbytes - offset; + if (size > BLOCKSIZE) { + size = BLOCKSIZE; + } + + 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 +1630,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: