Skip to content

Commit 7352b6a

Browse files
authored
gh-158213: Don't return a str subclass from PyUnicodeWriter_Finish() (#158214)
Since GH-157861, every writer uses the read-only optimization of _PyUnicodeWriter_WriteStr(), so the first write of a str subclass instance into an empty writer kept that object as the buffer, and PyUnicodeWriter_Finish() returned it. io.StringIO.getvalue() then returned the written object itself, and the next write re-read it through its __str__() method, which changed the contents and could make read() read past the end of the buffer. Only use the read-only optimization for exact str objects. A subclass is copied into a new buffer, as before GH-157861.
1 parent 4e9ea68 commit 7352b6a

3 files changed

Lines changed: 18 additions & 2 deletions

File tree

‎Lib/test/test_capi/test_unicode.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,18 @@ def test_create(self):
19101910
self.assertGreater(writer.get_buffer()[0], len(s))
19111911
self.assertEqual(writer.finish(), s)
19121912

1913+
def test_str_subclass(self):
1914+
# The read-only optimization must not return a str subclass
1915+
class MyStr(str):
1916+
def __str__(self):
1917+
return self
1918+
1919+
writer = self.create_writer(0)
1920+
writer.write_str(MyStr('abc'))
1921+
result = writer.finish()
1922+
self.assertEqual(result, 'abc')
1923+
self.assertIs(type(result), str)
1924+
19131925
def test_repr_null(self):
19141926
writer = self.create_writer(0)
19151927
writer.write_utf8(b'var=', -1)

‎Lib/test/test_io/test_memoryio.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,7 +1118,11 @@ def __str__(self):
11181118
s = MyStr("correct")
11191119
memio = self.ioclass()
11201120
memio.write(s)
1121-
self.assertEqual(memio.getvalue(), "correct")
1121+
value = memio.getvalue()
1122+
self.assertEqual(value, "correct")
1123+
self.assertIs(type(value), str)
1124+
memio.write("!")
1125+
self.assertEqual(memio.getvalue(), "correct!")
11221126

11231127
# Also test the fast path where pos == string_size (STATE_ACCUMULATING)
11241128
memio2 = self.ioclass()

‎Objects/unicode_writer.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ _PyUnicodeWriter_WriteStr(_PyUnicodeWriter *writer, PyObject *str)
313313
Py_UCS4 maxchar = PyUnicode_MAX_CHAR_VALUE(str);
314314

315315
if (maxchar > writer->maxchar || len > writer->size - writer->pos) {
316-
if (writer->buffer == NULL) {
316+
if (writer->buffer == NULL && PyUnicode_CheckExact(str)) {
317317
assert(_PyUnicode_CheckConsistency(str, 1));
318318
writer->readonly = 1;
319319
writer->buffer = Py_NewRef(str);

0 commit comments

Comments
 (0)