Skip to content

Commit 17530cf

Browse files
committed
Hold the conversion in a decoder type instead of a capsule
The capsule was passed to iconv_decode() next to an encoding argument that it then ignored, so the two could disagree. IconvDecoder owns both, which also leaves iconv_decode() with its original signature.
1 parent 386b0d8 commit 17530cf

3 files changed

Lines changed: 279 additions & 93 deletions

File tree

Lib/encodings/_iconv_codecs.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import codecs
22

33
def create_iconv_codec(name, encoding):
4-
from _codecs import iconv_encode, iconv_decode
4+
from _codecs import iconv_encode, iconv_decode, IconvDecoder
55

66
def encode(input, errors='strict'):
77
return iconv_encode(encoding, input, errors)
@@ -14,16 +14,32 @@ def encode(self, input, final=False):
1414
return iconv_encode(encoding, input, self.errors)[0]
1515

1616
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
17+
def __init__(self, errors='strict'):
18+
super().__init__(errors)
19+
self._decoder = IconvDecoder(encoding)
20+
1721
def _buffer_decode(self, input, errors, final):
18-
return iconv_decode(encoding, input, errors, final)
22+
return self._decoder.decode(input, errors, final)
23+
24+
def reset(self):
25+
super().reset()
26+
self._decoder = IconvDecoder(encoding)
1927

2028
class StreamWriter(codecs.StreamWriter):
2129
def encode(self, input, errors='strict'):
2230
return iconv_encode(encoding, input, errors)
2331

2432
class StreamReader(codecs.StreamReader):
33+
def __init__(self, stream, errors='strict'):
34+
super().__init__(stream, errors)
35+
self._decoder = IconvDecoder(encoding)
36+
2537
def decode(self, input, errors, final=False):
26-
return iconv_decode(encoding, input, errors, final)
38+
return self._decoder.decode(input, errors, final)
39+
40+
def reset(self):
41+
super().reset()
42+
self._decoder = IconvDecoder(encoding)
2743

2844
return codecs.CodecInfo(
2945
name=name,

Modules/_codecsmodule.c

Lines changed: 163 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,45 @@ Copyright (c) Corporation for National Research Initiatives.
3232

3333
#include "Python.h"
3434
#include "pycore_codecs.h" // _PyCodec_Lookup()
35+
#include "pycore_pymem.h" // _PyMem_Strdup()
3536
#include "pycore_unicodeobject.h" // _PyUnicode_EncodeCharmap
3637

3738
#ifdef MS_WINDOWS
3839
#include <windows.h>
3940
#endif
4041

42+
typedef struct {
43+
PyTypeObject *IconvDecoderType;
44+
} _codecs_state;
45+
46+
static inline _codecs_state *
47+
get_codecs_state(PyObject *module)
48+
{
49+
void *state = PyModule_GetState(module);
50+
assert(state != NULL);
51+
return (_codecs_state *)state;
52+
}
53+
54+
static struct PyModuleDef codecsmodule;
55+
56+
#define get_codecs_state_by_type(type) \
57+
(get_codecs_state(PyType_GetModuleByDef(type, &codecsmodule)))
58+
59+
#ifdef HAVE_ICONV
60+
typedef struct {
61+
PyObject_HEAD
62+
iconv_t cd;
63+
char *encoding;
64+
} iconv_decoder_object;
65+
66+
#define iconv_decoder_CAST(op) ((iconv_decoder_object *)(op))
67+
#endif
68+
4169
/*[clinic input]
4270
module _codecs
71+
class _codecs.IconvDecoder "iconv_decoder_object *" "get_codecs_state_by_type(type)->IconvDecoderType"
4372
[clinic start generated code]*/
44-
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e1390e3da3cb9deb]*/
73+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=17a98f0bef095afe]*/
4574

4675
#include "pycore_runtime.h"
4776
#include "clinic/_codecsmodule.c.h"
@@ -644,89 +673,126 @@ _codecs_code_page_decode_impl(PyObject *module, int codepage,
644673

645674
#ifdef HAVE_ICONV
646675

647-
#ifdef HAVE_ICONV
648-
#define ICONV_STATE_CAPSULE "_codecs.iconv_state"
649-
650-
static void
651-
iconv_state_destructor(PyObject *capsule)
652-
{
653-
iconv_t *cdp = PyCapsule_GetPointer(capsule, ICONV_STATE_CAPSULE);
654-
if (cdp == NULL) {
655-
PyErr_Clear();
656-
return;
657-
}
658-
iconv_close(*cdp);
659-
PyMem_Free(cdp);
660-
}
661-
#endif
662-
663676
/*[clinic input]
664-
_codecs.iconv_state
677+
@classmethod
678+
_codecs.IconvDecoder.__new__
665679
666680
encoding: str
667681
/
668682
669-
Open an iconv conversion for decoding, to reuse across calls.
683+
Decoder holding one iconv conversion, to reuse across calls.
670684
671-
The result is an opaque object. Reusing one conversion keeps the
672-
shift state of a stateful encoding, such as ISO-2022-CN, across calls.
685+
Reusing one conversion keeps the shift state of a stateful encoding,
686+
such as ISO-2022-CN, from one call to the next.
673687
[clinic start generated code]*/
674688

675689
static PyObject *
676-
_codecs_iconv_state_impl(PyObject *module, const char *encoding)
677-
/*[clinic end generated code: output=4100a9b65a64d65c input=6ffbfa5bb1d2d208]*/
690+
_codecs_IconvDecoder_impl(PyTypeObject *type, const char *encoding)
691+
/*[clinic end generated code: output=6e5181abedc4ae7c input=c53769050ff2b196]*/
678692
{
679-
#ifdef HAVE_ICONV
680-
iconv_t *cdp = PyMem_Malloc(sizeof(iconv_t));
681-
if (cdp == NULL) {
693+
char *name = _PyMem_Strdup(encoding);
694+
if (name == NULL) {
682695
return PyErr_NoMemory();
683696
}
684-
*cdp = _PyUnicode_IconvOpenDecoder(encoding);
685-
if (*cdp == (iconv_t)-1) {
686-
PyMem_Free(cdp);
697+
iconv_t cd = _PyUnicode_IconvOpenDecoder(encoding);
698+
if (cd == (iconv_t)-1) {
699+
PyMem_Free(name);
687700
return NULL;
688701
}
689-
PyObject *capsule = PyCapsule_New(cdp, ICONV_STATE_CAPSULE,
690-
iconv_state_destructor);
691-
if (capsule == NULL) {
692-
iconv_close(*cdp);
693-
PyMem_Free(cdp);
702+
iconv_decoder_object *self = (iconv_decoder_object *)type->tp_alloc(type, 0);
703+
if (self == NULL) {
704+
iconv_close(cd);
705+
PyMem_Free(name);
694706
return NULL;
695707
}
696-
return capsule;
697-
#else
698-
PyErr_SetString(PyExc_LookupError, "iconv is not available");
699-
return NULL;
700-
#endif
708+
self->cd = cd;
709+
self->encoding = name;
710+
return (PyObject *)self;
701711
}
702712

713+
/*[clinic input]
714+
_codecs.IconvDecoder.decode
715+
716+
data: Py_buffer
717+
errors: str(accept={str, NoneType}) = None
718+
final: bool = False
719+
/
720+
[clinic start generated code]*/
721+
722+
static PyObject *
723+
_codecs_IconvDecoder_decode_impl(iconv_decoder_object *self, Py_buffer *data,
724+
const char *errors, int final)
725+
/*[clinic end generated code: output=0e8812b6b422fc97 input=9bea42dd438d03af]*/
726+
{
727+
Py_ssize_t consumed = data->len;
728+
PyObject *decoded = _PyUnicode_DecodeIconv(self->encoding, data->buf,
729+
data->len, errors,
730+
final ? NULL : &consumed,
731+
&self->cd);
732+
return codec_tuple(decoded, consumed);
733+
}
734+
735+
static int
736+
iconv_decoder_traverse(PyObject *op, visitproc visit, void *arg)
737+
{
738+
Py_VISIT(Py_TYPE(op));
739+
return 0;
740+
}
741+
742+
static void
743+
iconv_decoder_dealloc(PyObject *op)
744+
{
745+
iconv_decoder_object *self = iconv_decoder_CAST(op);
746+
PyTypeObject *tp = Py_TYPE(self);
747+
PyObject_GC_UnTrack(self);
748+
if (self->cd != (iconv_t)-1) {
749+
iconv_close(self->cd);
750+
}
751+
PyMem_Free(self->encoding);
752+
tp->tp_free(self);
753+
Py_DECREF(tp);
754+
}
755+
756+
static PyMethodDef iconv_decoder_methods[] = {
757+
_CODECS_ICONVDECODER_DECODE_METHODDEF
758+
{NULL, NULL}
759+
};
760+
761+
static PyType_Slot iconv_decoder_slots[] = {
762+
{Py_tp_new, _codecs_IconvDecoder},
763+
{Py_tp_dealloc, iconv_decoder_dealloc},
764+
{Py_tp_traverse, iconv_decoder_traverse},
765+
{Py_tp_methods, iconv_decoder_methods},
766+
{Py_tp_doc, (void *)_codecs_IconvDecoder__doc__},
767+
{0, NULL}
768+
};
769+
770+
static PyType_Spec iconv_decoder_spec = {
771+
.name = "_codecs.IconvDecoder",
772+
.basicsize = sizeof(iconv_decoder_object),
773+
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE
774+
| Py_TPFLAGS_HAVE_GC),
775+
.slots = iconv_decoder_slots,
776+
};
777+
703778
/*[clinic input]
704779
_codecs.iconv_decode
705780
encoding: str
706781
data: Py_buffer
707782
errors: str(accept={str, NoneType}) = None
708783
final: bool = False
709-
state: object = None
710784
/
711785
[clinic start generated code]*/
712786

713787
static PyObject *
714788
_codecs_iconv_decode_impl(PyObject *module, const char *encoding,
715-
Py_buffer *data, const char *errors, int final,
716-
PyObject *state)
717-
/*[clinic end generated code: output=ed99087a9b21d007 input=b23f4298c963f9c5]*/
789+
Py_buffer *data, const char *errors, int final)
790+
/*[clinic end generated code: output=6c6145a9decc2ba8 input=d15a04d7d3a3e0cd]*/
718791
{
719-
iconv_t *cdp = NULL;
720-
if (state != Py_None) {
721-
cdp = PyCapsule_GetPointer(state, ICONV_STATE_CAPSULE);
722-
if (cdp == NULL) {
723-
return NULL;
724-
}
725-
}
726792
Py_ssize_t consumed = data->len;
727793
PyObject *decoded = _PyUnicode_DecodeIconv(encoding, data->buf, data->len,
728794
errors,
729-
final ? NULL : &consumed, cdp);
795+
final ? NULL : &consumed, NULL);
730796
return codec_tuple(decoded, consumed);
731797
}
732798

@@ -1220,31 +1286,67 @@ static PyMethodDef _codecs_functions[] = {
12201286
_CODECS_CODE_PAGE_DECODE_METHODDEF
12211287
_CODECS_ICONV_ENCODE_METHODDEF
12221288
_CODECS_ICONV_DECODE_METHODDEF
1223-
_CODECS_ICONV_STATE_METHODDEF
12241289
_CODECS_REGISTER_ERROR_METHODDEF
12251290
_CODECS__UNREGISTER_ERROR_METHODDEF
12261291
_CODECS_LOOKUP_ERROR_METHODDEF
12271292
_CODECS__NORMALIZE_ENCODING_METHODDEF
12281293
{NULL, NULL} /* sentinel */
12291294
};
12301295

1296+
static int
1297+
_codecs_exec(PyObject *module)
1298+
{
1299+
#ifdef HAVE_ICONV
1300+
_codecs_state *state = get_codecs_state(module);
1301+
state->IconvDecoderType = (PyTypeObject *)PyType_FromModuleAndSpec(
1302+
module, &iconv_decoder_spec, NULL);
1303+
if (state->IconvDecoderType == NULL) {
1304+
return -1;
1305+
}
1306+
if (PyModule_AddType(module, state->IconvDecoderType) < 0) {
1307+
return -1;
1308+
}
1309+
#endif
1310+
return 0;
1311+
}
1312+
1313+
static int
1314+
_codecs_traverse(PyObject *module, visitproc visit, void *arg)
1315+
{
1316+
Py_VISIT(get_codecs_state(module)->IconvDecoderType);
1317+
return 0;
1318+
}
1319+
1320+
static int
1321+
_codecs_clear(PyObject *module)
1322+
{
1323+
Py_CLEAR(get_codecs_state(module)->IconvDecoderType);
1324+
return 0;
1325+
}
1326+
1327+
static void
1328+
_codecs_free(void *module)
1329+
{
1330+
(void)_codecs_clear((PyObject *)module);
1331+
}
1332+
12311333
static PyModuleDef_Slot _codecs_slots[] = {
12321334
_Py_ABI_SLOT,
1335+
{Py_mod_exec, _codecs_exec},
12331336
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
12341337
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
12351338
{0, NULL}
12361339
};
12371340

12381341
static struct PyModuleDef codecsmodule = {
1239-
PyModuleDef_HEAD_INIT,
1240-
"_codecs",
1241-
NULL,
1242-
0,
1243-
_codecs_functions,
1244-
_codecs_slots,
1245-
NULL,
1246-
NULL,
1247-
NULL
1342+
.m_base = PyModuleDef_HEAD_INIT,
1343+
.m_name = "_codecs",
1344+
.m_size = sizeof(_codecs_state),
1345+
.m_methods = _codecs_functions,
1346+
.m_slots = _codecs_slots,
1347+
.m_traverse = _codecs_traverse,
1348+
.m_clear = _codecs_clear,
1349+
.m_free = _codecs_free,
12481350
};
12491351

12501352
PyMODINIT_FUNC

0 commit comments

Comments
 (0)