From 2afb597eee0849a020200452972a9de9c23ed388 Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Mon, 7 Sep 2026 03:17:11 -0600 Subject: [PATCH] [3.13] gh-157048: check for buffer errors before mutating in BytesIO.__init__ (GH-157049) (cherry picked from commit d125f009dc2a698de62deb11a0c50d3ef8e33d62) Co-authored-by: Nathan Goldbaum --- Lib/test/test_memoryio.py | 12 ++++++++++++ .../2026-09-06-15-23-24.gh-issue-157048.UtFyiJ.rst | 2 ++ Modules/_io/bytesio.c | 9 +++++---- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-06-15-23-24.gh-issue-157048.UtFyiJ.rst diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py index a1b881dff16244d..c07224cf1bc8cf4 100644 --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -920,6 +920,18 @@ def test_cow_mutable(self): memio = self.ioclass(ba) self.assertEqual(sys.getrefcount(ba), old_rc) + def test_write_with_export(self): + memio = self.ioclass(b"abcd") + memio.seek(2) + with memio.getbuffer() as view: + self.assertRaises(BufferError, memio.__init__, b"replacement") + self.assertEqual(memio.tell(), 2) + self.assertEqual(memio.getvalue(), b"abcd") + self.assertEqual(bytes(view), b"abcd") + memio.write(b"X") + self.assertEqual(memio.getvalue(), b"abXd") + + class CStringIOTest(PyStringIOTest): ioclass = io.StringIO UnsupportedOperation = io.UnsupportedOperation diff --git a/Misc/NEWS.d/next/Library/2026-09-06-15-23-24.gh-issue-157048.UtFyiJ.rst b/Misc/NEWS.d/next/Library/2026-09-06-15-23-24.gh-issue-157048.UtFyiJ.rst new file mode 100644 index 000000000000000..1638968e475ca4a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-06-15-23-24.gh-issue-157048.UtFyiJ.rst @@ -0,0 +1,2 @@ +Preserve the position and contents of :class:`io.BytesIO` when +:meth:`!BytesIO.__init__` fails because a buffer is exported. diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 38d47aafa33372d..f50aa37c92b1ddd 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -943,15 +943,16 @@ static int _io_BytesIO___init___impl(bytesio *self, PyObject *initvalue) /*[clinic end generated code: output=65c0c51e24c5b621 input=aac7f31b67bf0fb6]*/ { - /* In case, __init__ is called multiple times. */ - self->string_size = 0; - self->pos = 0; - if (self->exports > 0) { PyErr_SetString(PyExc_BufferError, "Existing exports of data: object cannot be re-sized"); return -1; } + + /* In case, __init__ is called multiple times. */ + self->string_size = 0; + self->pos = 0; + if (initvalue && initvalue != Py_None) { if (PyBytes_CheckExact(initvalue)) { Py_XSETREF(self->buf, Py_NewRef(initvalue));