diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 59be17455a4e4e..e7b6ff39fdb9a1 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -795,6 +795,32 @@ def test_weakref(self): gc_collect() self.assertIs(wr(), None) + def test_write_overlapping_memoryview(self): + data = bytes(range(256)) * 16 + expected = data[:1] + data[:-1] + with mmap.mmap(-1, len(data)) as mm, memoryview(mm) as view: + mm[:] = data + mm.seek(1) + self.assertEqual(mm.write(view[:-1]), len(data) - 1) + self.assertEqual(mm[:], expected) + mm[:] = data + mm[1:] = view[:-1] + self.assertEqual(mm[:], expected) + + def test_extended_slice_overlapping_memoryview(self): + data = b'abcdefghijkl' + for step in (-3, -2, -1, 2, 3): + target = slice(None, None, step) + size = len(data[target]) + for offset in (0, len(data) - size): + with self.subTest(step=step, offset=offset): + expected = bytearray(data) + expected[target] = data[offset:offset + size] + with mmap.mmap(-1, len(data)) as mm, memoryview(mm) as view: + mm[:] = data + mm[target] = view[offset:offset + size] + self.assertEqual(mm[:], expected) + def test_write_returning_the_number_of_bytes_written(self): mm = mmap.mmap(-1, 16) self.assertEqual(mm.write(b""), 0) diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst new file mode 100644 index 00000000000000..71e75d277cc554 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.yuOQX5.rst @@ -0,0 +1 @@ +Fix overlapping writes and slice assignments to :class:`mmap.mmap` objects. diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c index ce5c59cb021270..7c9fae2ba96042 100644 --- a/Modules/mmapmodule.c +++ b/Modules/mmapmodule.c @@ -752,7 +752,7 @@ mmap_mmap_write_impl(mmap_object *self, Py_buffer *data) CHECK_VALID(NULL); PyObject *result; - if (safe_memcpy(self->data + self->pos, data->buf, data->len) < 0) { + if (safe_memmove(self->data + self->pos, data->buf, data->len) < 0) { result = NULL; } else { @@ -1722,16 +1722,27 @@ mmap_ass_subscript_impl(mmap_object *self, PyObject *item, PyObject *value) if (slicelen == 0) { } else if (step == 1) { - if (safe_memcpy(self->data + start, vbuf.buf, slicelen) < 0) { + if (safe_memmove(self->data + start, vbuf.buf, slicelen) < 0) { result = -1; } } else { - if (safe_copy_from_slice(self->data, (char *)vbuf.buf, start, step, - slicelen) < 0) - { + /* The source may overlap the destination, so copy it before + assigning to the extended slice. */ + char *source = PyMem_Malloc(slicelen); + if (source == NULL) { + PyErr_NoMemory(); result = -1; } + else { + if (safe_memcpy(source, vbuf.buf, slicelen) < 0 || + safe_copy_from_slice(self->data, source, start, step, + slicelen) < 0) + { + result = -1; + } + PyMem_Free(source); + } } PyBuffer_Release(&vbuf); return result;