Skip to content

Commit 6d18fc9

Browse files
[3.15] gh-160185: Fix CJK StreamWriter.reset() not returning to ASCII (GH-160186) (#161067)
gh-160185: Fix CJK StreamWriter.reset() not returning to ASCII (GH-160186) reset() returned early when nothing was pending, so the shift-back sequence of hz and iso2022_* was never written (regression from gh-67436). Also fix a reference leak of the stream.write() result. (cherry picked from commit aa4f541) Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com>
1 parent 3135a08 commit 6d18fc9

4 files changed

Lines changed: 56 additions & 8 deletions

File tree

‎Lib/codecs.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -400,9 +400,10 @@ def reset(self):
400400
pass
401401

402402
def seek(self, offset, whence=0):
403-
self.stream.seek(offset, whence)
403+
# reset() may write to the stream: call it at the old position.
404404
if whence == 0 and offset == 0:
405405
self.reset()
406+
self.stream.seek(offset, whence)
406407

407408
def __getattr__(self, name,
408409
getattr=getattr):
@@ -739,10 +740,11 @@ def reset(self):
739740
self.writer.reset()
740741

741742
def seek(self, offset, whence=0):
742-
self.stream.seek(offset, whence)
743-
self.reader.reset()
743+
# writer.reset() may write to the stream: call it at the old position.
744744
if whence == 0 and offset == 0:
745745
self.writer.reset()
746+
self.stream.seek(offset, whence)
747+
self.reader.reset()
746748

747749
def __getattr__(self, name,
748750
getattr=getattr):
@@ -862,8 +864,8 @@ def reset(self):
862864
def seek(self, offset, whence=0):
863865
# Seeks must be propagated to both the readers and writers
864866
# as they might need to reset their internal buffers.
865-
self.reader.seek(offset, whence)
866867
self.writer.seek(offset, whence)
868+
self.reader.seek(offset, whence)
867869

868870
def __getattr__(self, name,
869871
getattr=getattr):

‎Lib/test/test_multibytecodec.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,42 @@ def test_incrementalencoder_final(self):
461461
self.assertEqual(output, self.expected_reset)
462462
self.assertEqual(encoder.encode('', final=True), b'')
463463

464+
def test_streamwriter_reset(self):
465+
stream = io.BytesIO()
466+
writer = codecs.getwriter(self.encoding)(stream)
467+
writer.reset()
468+
self.assertEqual(stream.getvalue(), b'')
469+
writer.write(self.text)
470+
self.assertEqual(stream.getvalue(), self.expected)
471+
writer.reset()
472+
self.assertEqual(stream.getvalue(), self.expected_reset)
473+
writer.reset()
474+
self.assertEqual(stream.getvalue(), self.expected_reset)
475+
writer.write(self.text)
476+
writer.reset()
477+
self.assertEqual(stream.getvalue(), self.expected_reset * 2)
478+
479+
def test_streamwriter_seek0(self):
480+
# The reset sequence is written before the stream is rewound.
481+
info = codecs.lookup(self.encoding)
482+
for wrap in (
483+
info.streamwriter,
484+
lambda stream: codecs.StreamReaderWriter(
485+
stream, info.streamreader, info.streamwriter),
486+
lambda stream: codecs.EncodedFile(stream, 'utf-8', self.encoding),
487+
):
488+
stream = io.BytesIO()
489+
writer = wrap(stream)
490+
text = self.text
491+
if isinstance(writer, codecs.StreamRecoder):
492+
text = text.encode()
493+
writer.write(text)
494+
writer.seek(0)
495+
self.assertEqual(stream.tell(), 0)
496+
self.assertEqual(stream.getvalue(), self.expected_reset)
497+
writer.write(text)
498+
self.assertEqual(stream.getvalue(), self.expected_reset)
499+
464500
def test_incrementalencoder_error(self):
465501
# The output of a failed encode() is lost, so the shift state
466502
# must not be changed.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix :meth:`codecs.StreamWriter.reset` of the stateful CJK codecs (``hz`` and
2+
the ``iso2022_*`` family) not writing the sequence that returns the output to
3+
ASCII. ``seek(0)`` of :class:`codecs.StreamWriter`,
4+
:class:`codecs.StreamReaderWriter` and :class:`codecs.StreamRecoder` now
5+
resets the writer before moving the stream position.

‎Modules/cjkcodecs/multibytecodec.c‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1879,13 +1879,17 @@ _multibytecodec_MultibyteStreamWriter_reset_impl(MultibyteStreamWriterObject *se
18791879
PyTypeObject *cls)
18801880
/*[clinic end generated code: output=32ef224c2a38aa3d input=28af6a9cd38d1979]*/
18811881
{
1882-
PyObject *pwrt;
1882+
PyObject *pwrt, *pending;
18831883

1884-
if (!self->pending)
1885-
Py_RETURN_NONE;
1884+
/* even without pending characters, a stateful codec may have to
1885+
* write a sequence that returns the output to its initial state */
1886+
pending = self->pending;
1887+
if (pending == NULL) {
1888+
pending = Py_GetConstantBorrowed(Py_CONSTANT_EMPTY_STR);
1889+
}
18861890

18871891
pwrt = multibytecodec_encode(self->codec, &self->state,
1888-
self->pending, NULL, self->errors,
1892+
pending, NULL, self->errors,
18891893
MBENC_FLUSH | MBENC_RESET);
18901894
/* some pending buffer can be truncated when UnicodeEncodeError is
18911895
* raised on 'strict' mode. but, 'reset' method is designed to
@@ -1908,6 +1912,7 @@ _multibytecodec_MultibyteStreamWriter_reset_impl(MultibyteStreamWriterObject *se
19081912
Py_DECREF(pwrt);
19091913
return NULL;
19101914
}
1915+
Py_DECREF(wr);
19111916
}
19121917
Py_DECREF(pwrt);
19131918

0 commit comments

Comments
 (0)