]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-149046: fix: correctly handle `str` subclasses in `io.StringIO` (#149047)
authorThomas Kowalski <thom.kowa@gmail.com>
Mon, 1 Jun 2026 13:01:57 +0000 (15:01 +0200)
committerGitHub <noreply@github.com>
Mon, 1 Jun 2026 13:01:57 +0000 (13:01 +0000)
Lib/test/test_io/test_memoryio.py
Misc/NEWS.d/next/Library/2026-04-27-11-12-00.gh-issue-149046.74shDd.rst [new file with mode: 0644]
Modules/_io/stringio.c

index 482b183da23ffa259664e52592021d5c3c12d34d..3669ac0b038b71b5bbe541320857c5732fe55ec6 100644 (file)
@@ -967,6 +967,25 @@ class CStringIOTest(PyStringIOTest):
         memio.close()
         self.assertRaises(ValueError, memio.__setstate__, ("closed", "", 0, None))
 
+    def test_write_str_subclass(self):
+        # Writing a str subclass should use the subclass's unicode data
+        # directly, not call __str__ on it (which may return a different
+        # value).  gh-149047
+        class MyStr(str):
+            def __str__(self):
+                return "WRONG"
+
+        s = MyStr("correct")
+        memio = self.ioclass()
+        memio.write(s)
+        self.assertEqual(memio.getvalue(), "correct")
+
+        # Also test the fast path where pos == string_size (STATE_ACCUMULATING)
+        memio2 = self.ioclass()
+        memio2.write(MyStr("hello "))
+        memio2.write(MyStr("world"))
+        self.assertEqual(memio2.getvalue(), "hello world")
+
 
 class CStringIOPickleTest(PyStringIOPickleTest):
     UnsupportedOperation = io.UnsupportedOperation
diff --git a/Misc/NEWS.d/next/Library/2026-04-27-11-12-00.gh-issue-149046.74shDd.rst b/Misc/NEWS.d/next/Library/2026-04-27-11-12-00.gh-issue-149046.74shDd.rst
new file mode 100644 (file)
index 0000000..b05c422
--- /dev/null
@@ -0,0 +1,2 @@
+:mod:`io`: Fix :class:`io.StringIO` serialization: no longer call ``str(obj)`` on :class:`str`
+subclasses. Patch by Thomas Kowalski.
index 0d9196f3647dde8cc492b4d037ada90292f6c2ed..b8601383ad0a26fa7f1eb607bdce87fab3befb3e 100644 (file)
@@ -225,7 +225,9 @@ write_str(stringio *self, PyObject *obj)
 
     if (self->state == STATE_ACCUMULATING) {
         if (self->string_size == self->pos) {
-            if (PyUnicodeWriter_WriteStr(self->writer, decoded))
+            // gh-149046: Avoid PyUnicodeWriter_WriteStr() which calls str(obj)
+            // on str subclasses
+            if (_PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)self->writer, decoded))
                 goto fail;
             goto success;
         }