Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion Lib/test/test_multibytecodec.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def test_nullcoding(self):
self.assertEqual(str(b'', enc), '')
self.assertEqual(''.encode(enc), b'')

def test_hz_tilde_at_buffer_boundary(self):
text = 'a' + '中a' * 6 + '~'
self.assertEqual(text.encode('hz').decode('hz'), text)

def test_str_decode(self):
for enc in ALL_CJKENCODINGS:
self.assertEqual('abcd'.encode(enc), b'abcd')
Expand Down Expand Up @@ -91,6 +95,12 @@ def test_decode_unicode(self):

class Test_IncrementalEncoder(unittest.TestCase):

def test_iso2022_kr_reset(self):
encoder = codecs.getincrementalencoder('iso2022_kr')()
encoder.encode('가')
encoder.reset()
self.assertEqual(encoder.encode('가', final=True), '가'.encode('iso2022_kr'))

def test_stateless(self):
# cp949 encoder isn't stateful at all.
encoder = codecs.getincrementalencoder('cp949')()
Expand Down Expand Up @@ -242,6 +252,19 @@ def test_subinterp(self):

class Test_IncrementalDecoder(unittest.TestCase):

def test_iso2022_split_escape(self):
raw = b'\x1b$0000000B'
decoder = codecs.getincrementaldecoder('iso2022_jp')('replace')
decoded = decoder.decode(raw[:-1]) + decoder.decode(raw[-1:], final=True)
self.assertEqual(decoded, raw.decode('iso2022_jp', 'replace'))

def test_iso2022_reset(self):
decoder = codecs.getincrementaldecoder('iso2022_jp')()
decoder.decode(b'\x1b%')
decoder.reset()
with self.assertRaises(UnicodeDecodeError):
decoder.decode(b'\xff', final=True)

def test_dbcs(self):
# cp949 decoder is simple with only 1 or 2 bytes sequences.
decoder = codecs.getincrementaldecoder('cp949')()
Expand Down Expand Up @@ -325,7 +348,7 @@ def test_setstate_validates_input(self):
self.assertRaises(TypeError, decoder.setstate, 123)
self.assertRaises(TypeError, decoder.setstate, ("invalid", 0))
self.assertRaises(TypeError, decoder.setstate, (b"1234", "invalid"))
self.assertRaises(UnicodeDecodeError, decoder.setstate, (b"123456789", 0))
self.assertRaises(UnicodeDecodeError, decoder.setstate, (b"0" * 17, 0))

def test_setstate_invalid_designation(self):
# gh-153603: an unknown charset designation in the state must not crash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix HZ encoding of a tilde at an output buffer boundary and ISO-2022 decoding
of split escape sequences. Reset ISO-2022 incremental codecs to their initial
state. Keep multibyte codec tables and custom encoder error policies alive
when Python code replaces them during use.
10 changes: 6 additions & 4 deletions Modules/cjkcodecs/_codecs_cn.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,11 +361,13 @@ ENCODER(hz)
NEXT_OUT(2);
state->c[CN_STATE_OFFSET] = 0;
}
WRITEBYTE1((unsigned char)c);
NEXT(1, 1);
if (c == '~') {
WRITEBYTE1('~');
NEXT_OUT(1);
WRITEBYTE2('~', '~');
NEXT(1, 2);
}
else {
WRITEBYTE1((unsigned char)c);
NEXT(1, 1);
}
continue;
}
Expand Down
7 changes: 2 additions & 5 deletions Modules/cjkcodecs/_codecs_iso2022.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,8 @@ ENCODER_RESET(iso2022)
if (STATE_G0 != CHARSET_ASCII) {
WRITEBYTE3(ESC, '(', 'B');
NEXT_OUT(3);
STATE_SETG0(CHARSET_ASCII);
}
return 0;
return iso2022_encode_init(state, codec);
}

ENCODER(iso2022)
Expand Down Expand Up @@ -311,9 +310,7 @@ DECODER_INIT(iso2022)

DECODER_RESET(iso2022)
{
STATE_SETG0(CHARSET_ASCII);
STATE_CLEARFLAG(F_SHIFTED);
return 0;
return iso2022_decode_init(state, codec);
}

static Py_ssize_t
Expand Down
23 changes: 19 additions & 4 deletions Modules/cjkcodecs/multibytecodec.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,11 @@ multibytecodec_encode(const MultibyteCodec *codec,
if (datalen == 0 && !(flags & MBENC_RESET))
return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);

/* A callback may change a stateful encoder's errors attribute. */
if (ERROR_ISCUSTOM(errors)) {
Py_INCREF(errors);
}

buf.excobj = NULL;
buf.writer = NULL;
buf.inobj = text; /* borrowed reference */
Expand Down Expand Up @@ -567,10 +572,12 @@ multibytecodec_encode(const MultibyteCodec *codec,
if (inpos_t)
*inpos_t = buf.inpos;
Py_XDECREF(buf.excobj);
ERROR_DECREF(errors);
return PyBytesWriter_FinishWithPointer(buf.writer, buf.outbuf);

errorexit:
Py_XDECREF(buf.excobj);
ERROR_DECREF(errors);
PyBytesWriter_Discard(buf.writer);
return NULL;
}
Expand Down Expand Up @@ -1101,7 +1108,7 @@ mbiencoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->codec->encinit(&self->state, self->codec) != 0)
goto errorexit;

Py_DECREF(codec);
self->codec_owner = codec;
return (PyObject *)self;

errorexit:
Expand All @@ -1122,6 +1129,7 @@ mbiencoder_traverse(PyObject *op, visitproc visit, void *arg)
MultibyteIncrementalEncoderObject *self = _MultibyteIncrementalEncoderObject_CAST(op);
if (ERROR_ISCUSTOM(self->errors))
Py_VISIT(self->errors);
Py_VISIT(self->codec_owner);
return 0;
}

Expand All @@ -1133,6 +1141,7 @@ mbiencoder_dealloc(PyObject *op)
PyObject_GC_UnTrack(self);
ERROR_DECREF(self->errors);
Py_CLEAR(self->pending);
Py_XDECREF(self->codec_owner);
tp->tp_free(self);
Py_DECREF(tp);
}
Expand Down Expand Up @@ -1384,7 +1393,7 @@ mbidecoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->codec->decinit(&self->state, self->codec) != 0)
goto errorexit;

Py_DECREF(codec);
self->codec_owner = codec;
return (PyObject *)self;

errorexit:
Expand All @@ -1405,6 +1414,7 @@ mbidecoder_traverse(PyObject *op, visitproc visit, void *arg)
MultibyteIncrementalDecoderObject *self = _MultibyteIncrementalDecoderObject_CAST(op);
if (ERROR_ISCUSTOM(self->errors))
Py_VISIT(self->errors);
Py_VISIT(self->codec_owner);
return 0;
}

Expand All @@ -1415,6 +1425,7 @@ mbidecoder_dealloc(PyObject *op)
PyTypeObject *tp = Py_TYPE(self);
PyObject_GC_UnTrack(self);
ERROR_DECREF(self->errors);
Py_XDECREF(self->codec_owner);
tp->tp_free(self);
Py_DECREF(tp);
}
Expand Down Expand Up @@ -1703,7 +1714,7 @@ mbstreamreader_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->codec->decinit(&self->state, self->codec) != 0)
goto errorexit;

Py_DECREF(codec);
self->codec_owner = codec;
return (PyObject *)self;

errorexit:
Expand All @@ -1725,6 +1736,7 @@ mbstreamreader_traverse(PyObject *op, visitproc visit, void *arg)
if (ERROR_ISCUSTOM(self->errors))
Py_VISIT(self->errors);
Py_VISIT(self->stream);
Py_VISIT(self->codec_owner);
return 0;
}

Expand All @@ -1736,6 +1748,7 @@ mbstreamreader_dealloc(PyObject *op)
PyObject_GC_UnTrack(self);
ERROR_DECREF(self->errors);
Py_XDECREF(self->stream);
Py_XDECREF(self->codec_owner);
tp->tp_free(self);
Py_DECREF(tp);
}
Expand Down Expand Up @@ -1927,7 +1940,7 @@ mbstreamwriter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->codec->encinit(&self->state, self->codec) != 0)
goto errorexit;

Py_DECREF(codec);
self->codec_owner = codec;
return (PyObject *)self;

errorexit:
Expand All @@ -1949,6 +1962,7 @@ mbstreamwriter_traverse(PyObject *op, visitproc visit, void *arg)
if (ERROR_ISCUSTOM(self->errors))
Py_VISIT(self->errors);
Py_VISIT(self->stream);
Py_VISIT(self->codec_owner);
return 0;
}

Expand All @@ -1960,6 +1974,7 @@ mbstreamwriter_dealloc(PyObject *op)
PyObject_GC_UnTrack(self);
ERROR_DECREF(self->errors);
Py_XDECREF(self->stream);
Py_XDECREF(self->codec_owner);
tp->tp_free(self);
Py_DECREF(tp);
}
Expand Down
4 changes: 3 additions & 1 deletion Modules/cjkcodecs/multibytecodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ typedef struct {
#define _MultibyteStatefulCodec_HEAD \
PyObject_HEAD \
const MultibyteCodec *codec; \
PyObject *codec_owner; \
MultibyteCodec_State state; \
PyObject *errors;
typedef struct {
Expand All @@ -91,7 +92,8 @@ typedef struct {
_MultibyteStatefulEncoder_HEAD
} MultibyteStatefulEncoderContext;

#define MAXDECPENDING 8
/* ISO-2022 may retain an incomplete escape sequence up to 15 bytes. */
#define MAXDECPENDING 16
#define _MultibyteStatefulDecoder_HEAD \
_MultibyteStatefulCodec_HEAD \
unsigned char pending[MAXDECPENDING]; \
Expand Down
Loading