https://github.com/python/cpython/commit/d19febb00c5a5ff20fee4b251a9656d088cf6965
commit: d19febb00c5a5ff20fee4b251a9656d088cf6965
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-04T23:22:18+02:00
summary:
Rework locale encoding C functions (#158680)
Add output_length to _Py_EncodeLocale(), _Py_EncodeUTF8(),
encode_current_locale() and encode_ascii(). So unicode_encode_locale() and
wstr_to_utf8() can now use the output_length, instead of having to compute
strlen().
* Rename functions:
* _Py_DecodeUTF8Ex() => _Py_DecodeUTF8()
* _Py_EncodeUTF8Ex() => _Py_EncodeUTF8()
* _Py_EncodeLocaleEx() => _Py_EncodeLocale()
* _Py_DecodeLocaleEx() => _Py_DecodeLocale()
* Add tests on Py_DecodeLocale() and Py_EncodeLocale() in test_codecs.
Clarify also their documentation.
* _Py_EncodeLocale() and _Py_DecodeLocale() now sets all output parameters on
error. Add more tests on these output parameters.
* Add encode_current_locale_impl() to simplify encode_current_locale().
* Remove reason parameter of _Py_EncodeLocale() and _Py_DecodeLocale() to
simplify the code. Only the UTF-8 decoder used specific reasons.
* _Py_EncodeUTF8() now uses size_t instead of Py_ssize_t to iterate
on the input string.
* On error, encode_current_locale_impl() now returns ENCODE_ERROR (new
constant) instead of DECODE_ERROR.
* Mark _Py_EncodeLocaleRaw() as static (it's not used outside fileutils.c).
files:
M Doc/c-api/sys.rst
M Include/internal/pycore_fileutils.h
M Lib/test/test_codecs.py
M Modules/_testinternalcapi.c
M Modules/_testlimitedcapi/codec.c
M Objects/unicodeobject.c
M Python/fileutils.c
M Python/initconfig.c
diff --git a/Doc/c-api/sys.rst b/Doc/c-api/sys.rst
index 0a446f86e22eaf..2f59e8498b88a2 100644
--- a/Doc/c-api/sys.rst
+++ b/Doc/c-api/sys.rst
@@ -152,28 +152,29 @@ Operating System Utilities
<c-preinit>` and so that the LC_CTYPE locale is properly configured: see
the :c:func:`Py_PreInitialize` function.
- Decode a byte string from the :term:`filesystem encoding and error handler`.
- If the error handler is :ref:`surrogateescape error handler
- <surrogateescape>`, undecodable bytes are decoded as characters in range
- U+DC80..U+DCFF; and if a byte sequence can be decoded as a surrogate
- character, the bytes are escaped using the surrogateescape error handler
- instead of decoding them.
+ Decode a byte string from the :term:`filesystem encoding <filesystem
+ encoding and error handler>` with the :ref:`surrogateescape error handler
+ <surrogateescape>`.
+
+ Undecodable bytes are decoded as characters in range U+DC80..U+DCFF. If a
+ byte sequence can be decoded as a surrogate character, escape the bytes
+ using the surrogateescape error handler instead of decoding them.
Return a pointer to a newly allocated wide character string, use
:c:func:`PyMem_RawFree` to free the memory. If size is not ``NULL``, write
the number of wide characters excluding the null character into ``*size``
- Return ``NULL`` on decoding error or memory allocation error. If *size* is
- not ``NULL``, ``*size`` is set to ``(size_t)-1`` on memory error or set to
- ``(size_t)-2`` on decoding error.
+ On memory allocation failure, set *\*size* to ``(size_t)-1`` and return
+ ``NULL``.
+
+ On decode error, set *\*size* to ``(size_t)-2`` and return ``NULL``.
+ Decoding errors should never happen, unless there is a bug in the C
+ library.
The :term:`filesystem encoding and error handler` are selected by
:c:func:`PyConfig_Read`: see :c:member:`~PyConfig.filesystem_encoding` and
:c:member:`~PyConfig.filesystem_errors` members of :c:type:`PyConfig`.
- Decoding errors should never happen, unless there is a bug in the C
- library.
-
Use the :c:func:`Py_EncodeLocale` function to encode the character string
back to a byte string.
@@ -195,17 +196,19 @@ Operating System Utilities
.. c:function:: char* Py_EncodeLocale(const wchar_t *text, size_t *error_pos)
- Encode a wide character string to the :term:`filesystem encoding and error
- handler`. If the error handler is :ref:`surrogateescape error handler
- <surrogateescape>`, surrogate characters in the range U+DC80..U+DCFF are
- converted to bytes 0x80..0xFF.
+ Encode a wide character string to the :term:`filesystem encoding <filesystem
+ encoding and error handler>` with the :ref:`surrogateescape error handler
+ <surrogateescape>`. Surrogate characters in the range U+DC80..U+DCFF are
+ encoded to bytes 0x80..0xFF.
Return a pointer to a newly allocated byte string, use :c:func:`PyMem_Free`
- to free the memory. Return ``NULL`` on encoding error or memory allocation
- error.
+ to free the memory.
+
+ On memory allocation failure, set *\*error_pos* to ``(size_t)-1`` and return
+ ``NULL``.
- If error_pos is not ``NULL``, ``*error_pos`` is set to ``(size_t)-1`` on
- success, or set to the index of the invalid character on encoding error.
+ On encoding error, set *\*error_pos* to the index of the first unencodable
+ character and return ``NULL``.
The :term:`filesystem encoding and error handler` are selected by
:c:func:`PyConfig_Read`: see :c:member:`~PyConfig.filesystem_encoding` and
diff --git a/Include/internal/pycore_fileutils.h
b/Include/internal/pycore_fileutils.h
index 128790823aa879..a765eb5fe2d322 100644
--- a/Include/internal/pycore_fileutils.h
+++ b/Include/internal/pycore_fileutils.h
@@ -24,27 +24,22 @@ extern "C" {
PyAPI_FUNC(_Py_error_handler) _Py_GetErrorHandler(const char *errors);
// Export for '_testinternalcapi' shared extension
-PyAPI_FUNC(int) _Py_DecodeLocaleEx(
+PyAPI_FUNC(int) _Py_DecodeLocale(
const char *arg,
wchar_t **wstr,
size_t *wlen,
- const char **reason,
int current_locale,
_Py_error_handler errors);
// Export for '_testinternalcapi' shared extension
-PyAPI_FUNC(int) _Py_EncodeLocaleEx(
+PyAPI_FUNC(int) _Py_EncodeLocale(
const wchar_t *text,
char **str,
+ size_t *output_length,
size_t *error_pos,
- const char **reason,
int current_locale,
_Py_error_handler errors);
-extern char* _Py_EncodeLocaleRaw(
- const wchar_t *text,
- size_t *error_pos);
-
extern PyObject* _Py_device_encoding(int);
#if defined(MS_WINDOWS) || defined(__APPLE__)
@@ -190,19 +185,23 @@ extern int _Py_open_osfhandle(void *handle, int flags);
? _PyStatus_ERR("cannot decode " NAME) \
: _PyStatus_NO_MEMORY()
-extern int _Py_DecodeUTF8Ex(
+#define _Py_CODEC_MEMORY_ERROR -1
+#define _Py_CODEC_DECODE_ERROR -2
+#define _Py_CODEC_ENCODE_ERROR -2
+#define _Py_CODEC_UNSUPPORTED_ERROR_HANDLER -3
+
+extern int _Py_DecodeUTF8(
const char *arg,
Py_ssize_t arglen,
wchar_t **wstr,
size_t *wlen,
- const char **reason,
_Py_error_handler errors);
-extern int _Py_EncodeUTF8Ex(
+extern int _Py_EncodeUTF8(
const wchar_t *text,
char **str,
+ size_t *output_length,
size_t *error_pos,
- const char **reason,
int raw_malloc,
_Py_error_handler errors);
diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
index 1916ee5507e972..2715ad6d7b3f8a 100644
--- a/Lib/test/test_codecs.py
+++ b/Lib/test/test_codecs.py
@@ -4057,34 +4057,67 @@ def test_pickle(self):
pickle.dumps(sr, proto)
[email protected](_testlimitedcapi is None, 'need _testlimitedcapi module')
@unittest.skipIf(_testinternalcapi is None, 'need _testinternalcapi module')
class LocaleCodecTest(unittest.TestCase):
"""
- Test indirectly _Py_DecodeUTF8Ex() and _Py_EncodeUTF8Ex().
+ Test public Py_EncodeLocale() and Py_DecodeLocale() C API.
+
+ Test internal _Py_EncodeLocale() and _Py_DecodeLocale() C API.
+
+ Test indirectly _Py_DecodeUTF8() and _Py_EncodeUTF8().
"""
ENCODING = sys.getfilesystemencoding()
STRINGS = ("ascii", "ulatin1:\xa7\xe9",
"u255:\xff",
"UCS:\xe9\u20ac\U0010ffff",
- "surrogates:\uDC80\uDCFF")
+ "surrogates:\uDC80\uDCFF",
+ "embed\0char")
BYTES_STRINGS = (b"blatin1:\xa7\xe9", b"b255:\xff")
- SURROGATES = "\uDC80\uDCFF"
- def encode(self, text, errors="strict"):
- return _testinternalcapi.EncodeLocaleEx(text, 0, errors)
+ def encode_locale_surrogateescape(self, text):
+ # Test public Py_EncodeLocale() C API:
+ # use the "surrogateescape" error handler
+ return _testlimitedcapi.encode_locale(text)
+
+ def encode_locale(self, text, errors="strict"):
+ # Test internal _Py_EncodeLocale() C API
+ return _testinternalcapi.encode_locale(text, 0, errors)
def check_encode_strings(self, errors):
for text in self.STRINGS:
with self.subTest(text=text):
try:
expected = text.encode(self.ENCODING, errors)
+ if b"\0" in expected:
+ # Py_EncodeLocale() and _Py_EncodeLocale()
+ # truncate the input string at the first NUL character
+ expected = expected.partition(b'\0')[0]
except UnicodeEncodeError:
+ for error_pos in range(len(text)):
+ try:
+ text[error_pos].encode(self.ENCODING, errors)
+ except UnicodeEncodeError:
+ break
+ else:
+ self.fail("failed to compute error_pos")
+
+ if errors == "surrogateescape":
+ with self.assertRaises(RuntimeError) as cm:
+ self.encode_locale_surrogateescape(text)
+ errmsg = f"encode error: pos={error_pos}"
+ self.assertEqual(str(cm.exception), errmsg)
+
with self.assertRaises(RuntimeError) as cm:
- self.encode(text, errors)
- errmsg = str(cm.exception)
- self.assertRegex(errmsg, r"encode error: pos=[0-9]+,
reason=")
+ self.encode_locale(text, errors)
+ errmsg = f"encode error: pos={error_pos}"
+ self.assertEqual(str(cm.exception), errmsg)
else:
- encoded = self.encode(text, errors)
+ if errors in ("strict", "surrogateescape"):
+ encoded = self.encode_locale_surrogateescape(text)
+ self.assertEqual(encoded, expected)
+
+ encoded = self.encode_locale(text, errors)
self.assertEqual(encoded, expected)
def test_encode_strict(self):
@@ -4095,7 +4128,7 @@ def test_encode_surrogateescape(self):
def test_encode_surrogatepass(self):
try:
- self.encode('', 'surrogatepass')
+ self.encode_locale('', 'surrogatepass')
except ValueError as exc:
if str(exc) == 'unsupported error handler':
self.skipTest(f"{self.ENCODING!r} encoder doesn't support "
@@ -4107,11 +4140,17 @@ def test_encode_surrogatepass(self):
def test_encode_unsupported_error_handler(self):
with self.assertRaises(ValueError) as cm:
- self.encode('', 'backslashreplace')
+ self.encode_locale('', 'backslashreplace')
self.assertEqual(str(cm.exception), 'unsupported error handler')
- def decode(self, encoded, errors="strict"):
- return _testinternalcapi.DecodeLocaleEx(encoded, 0, errors)
+ def decode_locale(self, encoded, errors="strict"):
+ # Test internal _Py_DecodeLocale() C API
+ return _testinternalcapi.decode_locale(encoded, 0, errors)
+
+ def decode_locale_surrogateescape(self, encoded):
+ # Test the public Py_DecodeLocale() C API:
+ # use the "surrogateescape" error handler
+ return _testlimitedcapi.decode_locale(encoded)
def check_decode_strings(self, errors):
is_utf8 = (self.ENCODING == "utf-8")
@@ -4138,13 +4177,37 @@ def check_decode_strings(self, errors):
with self.subTest(encoded=encoded):
try:
expected = encoded.decode(self.ENCODING, errors)
+ if "\0" in expected:
+ # Py_DecodeLocale() and _Py_DecodeLocale() truncate
+ # the input string at the first NUL byte
+ expected = expected.partition('\0')[0]
except UnicodeDecodeError:
+ for error_pos in range(len(encoded) - 1, -1, -1):
+ try:
+ encoded[:error_pos].decode(self.ENCODING, errors)
+ except UnicodeDecodeError:
+ pass
+ else:
+ break
+ else:
+ self.fail("failed to compute error_pos")
+
+ if errors == "surrogateescape":
+ with self.assertRaises(RuntimeError) as cm:
+ self.decode_locale_surrogateescape(encoded)
+ errmsg = f"Py_DecodeLocale failed:
error_pos={error_pos}"
+ self.assertEqual(str(cm.exception), errmsg)
+
with self.assertRaises(RuntimeError) as cm:
- self.decode(encoded, errors)
- errmsg = str(cm.exception)
- self.assertStartsWith(errmsg, "decode error: ")
+ self.decode_locale(encoded, errors)
+ errmsg = f"decode error: pos={error_pos}"
+ self.assertEqual(str(cm.exception), errmsg)
else:
- decoded = self.decode(encoded, errors)
+ if errors == ("strict", "surrogateescape"):
+ decoded = self.decode_locale_surrogateescape(encoded)
+ self.assertEqual(decoded, expected)
+
+ decoded = self.decode_locale(encoded, errors)
self.assertEqual(decoded, expected)
def test_decode_strict(self):
@@ -4155,7 +4218,7 @@ def test_decode_surrogateescape(self):
def test_decode_surrogatepass(self):
try:
- self.decode(b'', 'surrogatepass')
+ self.decode_locale(b'', 'surrogatepass')
except ValueError as exc:
if str(exc) == 'unsupported error handler':
self.skipTest(f"{self.ENCODING!r} decoder doesn't support "
@@ -4167,7 +4230,7 @@ def test_decode_surrogatepass(self):
def test_decode_unsupported_error_handler(self):
with self.assertRaises(ValueError) as cm:
- self.decode(b'', 'backslashreplace')
+ self.decode_locale(b'', 'backslashreplace')
self.assertEqual(str(cm.exception), 'unsupported error handler')
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index a2ce266eb35a65..e1f87eb3b41c9f 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -1046,92 +1046,119 @@ get_getpath_codeobject(PyObject *self, PyObject
*Py_UNUSED(args)) {
}
+// Test _Py_EncodeLocale()
static PyObject *
-encode_locale_ex(PyObject *self, PyObject *args)
+encode_locale(PyObject *self, PyObject *args)
{
PyObject *unicode;
int current_locale = 0;
- wchar_t *wstr;
PyObject *res = NULL;
const char *errors = NULL;
if (!PyArg_ParseTuple(args, "U|is", &unicode, ¤t_locale, &errors)) {
return NULL;
}
- wstr = PyUnicode_AsWideCharString(unicode, NULL);
+
+ // Accept embedded null characters
+ Py_ssize_t unused_wlen;
+ wchar_t *wstr = PyUnicode_AsWideCharString(unicode, &unused_wlen);
if (wstr == NULL) {
return NULL;
}
_Py_error_handler error_handler = _Py_GetErrorHandler(errors);
- char *str = NULL;
- size_t error_pos;
- const char *reason = NULL;
- int ret = _Py_EncodeLocaleEx(wstr,
- &str, &error_pos, &reason,
- current_locale, error_handler);
+ const char *str_canary = (const char*)0x1234;
+ char *str = (char*)str_canary;
+ size_t error_pos_canary = (size_t)-123;
+ size_t error_pos = error_pos_canary;
+ const size_t output_length_canary = (size_t)-456;
+ size_t output_length = output_length_canary;
+ int ret = _Py_EncodeLocale(wstr,
+ &str, &output_length, &error_pos,
+ current_locale, error_handler);
PyMem_Free(wstr);
switch(ret) {
case 0:
- res = PyBytes_FromString(str);
+ assert(str != NULL && str != str_canary);
+ assert(output_length != output_length_canary);
+ assert(error_pos == error_pos_canary);
+ res = PyBytes_FromStringAndSize(str, output_length);
PyMem_RawFree(str);
break;
- case -1:
+ case _Py_CODEC_MEMORY_ERROR:
+ assert(str == NULL);
+ assert(output_length == 0);
+ assert(error_pos == 0);
PyErr_NoMemory();
break;
- case -2:
- PyErr_Format(PyExc_RuntimeError, "encode error: pos=%zu, reason=%s",
- error_pos, reason);
+ case _Py_CODEC_ENCODE_ERROR:
+ assert(str == NULL);
+ assert(output_length == 0);
+ assert(error_pos != error_pos_canary);
+ PyErr_Format(PyExc_RuntimeError, "encode error: pos=%zu", error_pos);
break;
- case -3:
+ case _Py_CODEC_UNSUPPORTED_ERROR_HANDLER:
+ assert(str == NULL);
+ assert(output_length == 0);
+ assert(error_pos == 0);
PyErr_SetString(PyExc_ValueError, "unsupported error handler");
break;
default:
- PyErr_SetString(PyExc_ValueError, "unknown error code");
+ PyErr_SetString(PyExc_SystemError, "unknown error code");
break;
}
return res;
}
+// Test _Py_DecodeLocale()
static PyObject *
-decode_locale_ex(PyObject *self, PyObject *args)
+decode_locale(PyObject *self, PyObject *args)
{
char *str;
+ Py_ssize_t unused_len;
int current_locale = 0;
PyObject *res = NULL;
const char *errors = NULL;
-
- if (!PyArg_ParseTuple(args, "y|is", &str, ¤t_locale, &errors)) {
+ // Accept embedded null bytes in str
+ if (!PyArg_ParseTuple(args, "y#|is",
+ &str, &unused_len, ¤t_locale, &errors)) {
return NULL;
}
_Py_error_handler error_handler = _Py_GetErrorHandler(errors);
- wchar_t *wstr = NULL;
- size_t wlen = 0;
- const char *reason = NULL;
- int ret = _Py_DecodeLocaleEx(str,
- &wstr, &wlen, &reason,
- current_locale, error_handler);
+ const wchar_t *wstr_canary = (const wchar_t*)0x12345;
+ wchar_t *wstr = (wchar_t*)wstr_canary;
+ const size_t wlen_canary = (size_t)-123;
+ size_t wlen = wlen_canary;
+ int ret = _Py_DecodeLocale(str, &wstr, &wlen,
+ current_locale, error_handler);
switch(ret) {
case 0:
+ assert(wstr != NULL && wstr != wstr_canary);
+ assert(wlen != wlen_canary);
res = PyUnicode_FromWideChar(wstr, wlen);
PyMem_RawFree(wstr);
break;
- case -1:
+ case _Py_CODEC_MEMORY_ERROR:
+ assert(wstr == NULL);
+ assert(wlen == 0);
PyErr_NoMemory();
break;
- case -2:
- PyErr_Format(PyExc_RuntimeError, "decode error: pos=%zu, reason=%s",
- wlen, reason);
+ case _Py_CODEC_DECODE_ERROR:
+ assert(wstr == NULL);
+ assert(wlen != wlen_canary);
+ PyErr_Format(PyExc_RuntimeError, "decode error: pos=%zu", wlen);
break;
- case -3:
+ case _Py_CODEC_UNSUPPORTED_ERROR_HANDLER:
+ assert(wstr == NULL);
+ assert(wlen == 0);
PyErr_SetString(PyExc_ValueError, "unsupported error handler");
break;
default:
- PyErr_SetString(PyExc_ValueError, "unknown error code");
+ PyErr_SetString(PyExc_SystemError, "unknown error code");
break;
}
return res;
@@ -3331,8 +3358,8 @@ static PyMethodDef module_functions[] = {
{"test_bytes_find", test_bytes_find, METH_NOARGS},
{"normalize_path", normalize_path, METH_O, NULL},
{"get_getpath_codeobject", get_getpath_codeobject, METH_NOARGS, NULL},
- {"EncodeLocaleEx", encode_locale_ex, METH_VARARGS},
- {"DecodeLocaleEx", decode_locale_ex, METH_VARARGS},
+ {"encode_locale", encode_locale, METH_VARARGS},
+ {"decode_locale", decode_locale, METH_VARARGS},
{"set_eval_frame_default", set_eval_frame_default, METH_NOARGS, NULL},
{"set_eval_frame_interp", set_eval_frame_interp, METH_VARARGS, NULL},
{"set_eval_frame_record", set_eval_frame_record, METH_O, NULL},
diff --git a/Modules/_testlimitedcapi/codec.c b/Modules/_testlimitedcapi/codec.c
index 44eecf99f678bb..61e5d4708c71d6 100644
--- a/Modules/_testlimitedcapi/codec.c
+++ b/Modules/_testlimitedcapi/codec.c
@@ -2,8 +2,8 @@
#ifdef Py_GIL_DISABLED
# define Py_TARGET_ABI3T 0x030f0000
#else
- // Need limited C API version 3.5 for PyCodec_NameReplaceErrors()
-# define Py_LIMITED_API 0x03050000
+ // Need limited C API version 3.13 for PyMem_RawFree()
+# define Py_LIMITED_API 0x030d0000
#endif
#include "parts.h"
@@ -15,16 +15,93 @@ codec_namereplace_errors(PyObject *Py_UNUSED(module),
PyObject *exc)
return PyCodec_NameReplaceErrors(exc);
}
+
+// Test Py_DecodeLocale()
+static PyObject *
+decode_locale(PyObject *Py_UNUSED(module), PyObject *arg)
+{
+ const char *str;
+ Py_ssize_t unused_len;
+ // Accept embedded null bytes
+ if (PyArg_Parse(arg, "y#", &str, &unused_len) < 0) {
+ return NULL;
+ }
+
+ const size_t size_canary = (size_t)-123;
+ size_t size = size_canary;
+ wchar_t *wstr = Py_DecodeLocale(str, &size);
+
+ if (str == NULL) {
+ if (size == (size_t)-1) {
+ PyErr_NoMemory();
+ }
+ else if (size == (size_t)-2) {
+ PyErr_SetString(PyExc_RuntimeError, "decode error");
+ }
+ else {
+ PyErr_Format(PyExc_SystemError,
+ "unknown Py_DecodeLocale() return value: %zd",
+ (Py_ssize_t)size);
+ }
+ return NULL;
+ }
+ assert(wstr != NULL);
+ assert(size != size_canary);
+
+ PyObject *result = PyUnicode_FromWideChar(wstr, size);
+ PyMem_RawFree(wstr);
+ return result;
+}
+
+
+// Test Py_EncodeLocale()
+static PyObject *
+encode_locale(PyObject *Py_UNUSED(module), PyObject *arg)
+{
+ PyObject *unicode;
+ if (PyArg_Parse(arg, "U", &unicode) < 0) {
+ return NULL;
+ }
+
+ // Accept embedded null characters
+ Py_ssize_t unused_wlen;
+ wchar_t *wstr = PyUnicode_AsWideCharString(unicode, &unused_wlen);
+ if (wstr == NULL) {
+ return NULL;
+ }
+
+ const size_t error_pos_canary = (size_t)-123;
+ size_t error_pos = error_pos_canary;
+ char *str = Py_EncodeLocale(wstr, &error_pos);
+ PyMem_Free(wstr);
+
+ if (str == NULL) {
+ if (error_pos == (size_t)-1) {
+ return PyErr_NoMemory();
+ }
+ else {
+ assert(error_pos != error_pos_canary);
+ return PyErr_Format(PyExc_RuntimeError,
+ "encode error: pos=%zd", error_pos);
+ }
+ }
+ assert(error_pos == error_pos_canary);
+
+ PyObject *result = PyBytes_FromString(str);
+ PyMem_Free(str);
+ return result;
+}
+
+
static PyMethodDef test_methods[] = {
{"codec_namereplace_errors", codec_namereplace_errors, METH_O},
+ {"decode_locale", decode_locale, METH_O},
+ {"encode_locale", encode_locale, METH_O},
{NULL},
};
int
_PyTestLimitedCAPI_Init_Codec(PyObject *module)
{
- if (PyModule_AddFunctions(module, test_methods) < 0) {
- return -1;
- }
- return 0;
+ return PyModule_AddFunctions(module, test_methods);
}
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index e630dbb77f8725..157e005c3c0b60 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -3764,35 +3764,37 @@ unicode_encode_locale(PyObject *unicode,
_Py_error_handler error_handler,
}
char *str;
+ size_t str_len;
size_t error_pos;
- const char *reason;
- int res = _Py_EncodeLocaleEx(wstr, &str, &error_pos, &reason,
- current_locale, error_handler);
+ int res = _Py_EncodeLocale(wstr, &str, &str_len, &error_pos,
+ current_locale, error_handler);
PyMem_Free(wstr);
if (res != 0) {
- if (res == -2) {
+ if (res == _Py_CODEC_ENCODE_ERROR) {
PyObject *exc;
+ assert(error_pos <= (size_t)(PY_SSIZE_T_MAX - 1));
exc = PyObject_CallFunction(PyExc_UnicodeEncodeError, "sOnns",
"locale", unicode,
(Py_ssize_t)error_pos,
(Py_ssize_t)(error_pos+1),
- reason);
+ "encode error");
if (exc != NULL) {
PyCodec_StrictErrors(exc);
Py_DECREF(exc);
}
}
- else if (res == -3) {
+ else if (res == _Py_CODEC_UNSUPPORTED_ERROR_HANDLER) {
PyErr_SetString(PyExc_ValueError, "unsupported error handler");
}
else {
+ assert(res == _Py_CODEC_MEMORY_ERROR);
PyErr_NoMemory();
}
return NULL;
}
- PyObject *bytes = PyBytes_FromString(str);
+ PyObject *bytes = PyBytes_FromStringAndSize(str, str_len);
PyMem_RawFree(str);
return bytes;
}
@@ -3983,26 +3985,26 @@ unicode_decode_locale(const char *str, Py_ssize_t len,
wchar_t *wstr;
size_t wlen;
- const char *reason;
- int res = _Py_DecodeLocaleEx(str, &wstr, &wlen, &reason,
- current_locale, errors);
+ int res = _Py_DecodeLocale(str, &wstr, &wlen, current_locale, errors);
if (res != 0) {
- if (res == -2) {
+ if (res == _Py_CODEC_DECODE_ERROR) {
PyObject *exc;
+ assert(wlen <= (size_t)(PY_SSIZE_T_MAX - 1));
exc = PyObject_CallFunction(PyExc_UnicodeDecodeError, "sy#nns",
"locale", str, len,
(Py_ssize_t)wlen,
(Py_ssize_t)(wlen + 1),
- reason);
+ "decode error");
if (exc != NULL) {
PyCodec_StrictErrors(exc);
Py_DECREF(exc);
}
}
- else if (res == -3) {
+ else if (res == _Py_CODEC_UNSUPPORTED_ERROR_HANDLER) {
PyErr_SetString(PyExc_ValueError, "unsupported error handler");
}
else {
+ assert(res == _Py_CODEC_MEMORY_ERROR);
PyErr_NoMemory();
}
return NULL;
@@ -5481,26 +5483,31 @@ PyUnicode_DecodeUTF8Stateful(const char *s,
}
-/* UTF-8 decoder: use surrogateescape error handler if 'surrogateescape' is
- non-zero, use strict error handler otherwise.
-
- On success, write a pointer to a newly allocated wide character string into
- *wstr (use PyMem_RawFree() to free the memory) and write the output length
- (in number of wchar_t units) into *wlen (if wlen is set).
-
- On memory allocation failure, return -1.
-
- On decoding error (if surrogateescape is zero), return -2. If wlen is
- non-NULL, write the start of the illegal byte sequence into *wlen. If reason
- is not NULL, write the decoding error message into *reason. */
+// UTF-8 decoder.
+//
+// Supported error handlers: "strict", "surrogateescape" and "surrogatepass".
+//
+// On success, write a pointer to a newly allocated wide character string into
+// *wstr (use PyMem_RawFree() to free the memory), write the output length
+// (in number of wchar_t units) into *wlen (if wlen is set), and return 0.
+//
+// On error, return a negative number.
+//
+// On memory allocation failure, return _Py_CODEC_MEMORY_ERROR.
+//
+// On decoding error (if errors is "strict"), return _Py_CODEC_DECODE_ERROR.
+// If wlen is non-NULL, write the start of the illegal byte sequence into
+// *wlen.
+//
+// Return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER if errors error handler is not
+// supported.
int
-_Py_DecodeUTF8Ex(const char *s, Py_ssize_t size, wchar_t **wstr, size_t *wlen,
- const char **reason, _Py_error_handler errors)
+_Py_DecodeUTF8(const char *s, Py_ssize_t size, wchar_t **wstr, size_t *wlen,
+ _Py_error_handler errors)
{
- const char *orig_s = s;
- const char *e;
- wchar_t *unicode;
- Py_ssize_t outpos;
+ assert(0 <= size);
+ assert(s != NULL);
+ assert(wstr != NULL);
int surrogateescape = 0;
int surrogatepass = 0;
@@ -5515,23 +5522,23 @@ _Py_DecodeUTF8Ex(const char *s, Py_ssize_t size,
wchar_t **wstr, size_t *wlen,
surrogatepass = 1;
break;
default:
- return -3;
+ return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER;
}
/* Note: size will always be longer than the resulting Unicode
character count */
- if (PY_SSIZE_T_MAX / (Py_ssize_t)sizeof(wchar_t) - 1 < size) {
- return -1;
+ if ((size_t)PY_SSIZE_T_MAX / sizeof(wchar_t) - 1 < (size_t)size) {
+ return _Py_CODEC_MEMORY_ERROR;
}
-
- unicode = PyMem_RawMalloc((size + 1) * sizeof(wchar_t));
+ wchar_t *unicode = PyMem_RawMalloc((size + 1) * sizeof(wchar_t));
if (!unicode) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
/* Unpack UTF-8 encoded data */
- e = s + size;
- outpos = 0;
+ const char *orig_s = s;
+ const char *e = s + size;
+ Py_ssize_t outpos = 0;
while (s < e) {
Py_UCS4 ch;
#if SIZEOF_WCHAR_T == 4
@@ -5571,24 +5578,10 @@ _Py_DecodeUTF8Ex(const char *s, Py_ssize_t size,
wchar_t **wstr, size_t *wlen,
}
else {
PyMem_RawFree(unicode );
- if (reason != NULL) {
- switch (ch) {
- case 0:
- *reason = "unexpected end of data";
- break;
- case 1:
- *reason = "invalid start byte";
- break;
- /* 2, 3, 4 */
- default:
- *reason = "invalid continuation byte";
- break;
- }
- }
if (wlen != NULL) {
*wlen = s - orig_s;
}
- return -2;
+ return _Py_CODEC_DECODE_ERROR;
}
}
}
@@ -5602,17 +5595,20 @@ _Py_DecodeUTF8Ex(const char *s, Py_ssize_t size,
wchar_t **wstr, size_t *wlen,
}
+// Decode from UTF-8 with the "surrogateescape" error handler.
+// On success, set *wlen and return a newly allocated string.
+// On error, set *wlen to the error (_Py_CODEC_MEMORY_ERROR or
+// _Py_CODEC_DECODE_ERROR) and the return NULL
wchar_t*
_Py_DecodeUTF8_surrogateescape(const char *arg, Py_ssize_t arglen,
size_t *wlen)
{
wchar_t *wstr;
- int res = _Py_DecodeUTF8Ex(arg, arglen,
- &wstr, wlen,
- NULL, _Py_ERROR_SURROGATEESCAPE);
+ int res = _Py_DecodeUTF8(arg, arglen, &wstr, wlen,
+ _Py_ERROR_SURROGATEESCAPE);
if (res != 0) {
- /* _Py_DecodeUTF8Ex() must support _Py_ERROR_SURROGATEESCAPE */
- assert(res != -3);
+ /* _Py_DecodeUTF8() must support _Py_ERROR_SURROGATEESCAPE */
+ assert(res != _Py_CODEC_UNSUPPORTED_ERROR_HANDLER);
if (wlen) {
*wlen = (size_t)res;
}
@@ -5627,19 +5623,24 @@ _Py_DecodeUTF8_surrogateescape(const char *arg,
Py_ssize_t arglen,
On success, return 0 and write the newly allocated character string (use
PyMem_Free() to free the memory) into *str.
- On encoding failure, return -2 and write the position of the invalid
- surrogate character into *error_pos (if error_pos is set) and the decoding
- error message into *reason (if reason is set).
+ On encoding failure, return _Py_CODEC_ENCODE_ERROR (-2) and write the
+ position of the invalid surrogate character into *error_pos (if error_pos is
+ set).
- On memory allocation failure, return -1. */
+ On memory allocation failure, return _Py_CODEC_MEMORY_ERROR (-1).
+
+ str and output_length must not be NULL
+*/
int
-_Py_EncodeUTF8Ex(const wchar_t *text, char **str, size_t *error_pos,
- const char **reason, int raw_malloc, _Py_error_handler errors)
+_Py_EncodeUTF8(const wchar_t *text, char **str, size_t *output_length,
+ size_t *error_pos, int raw_malloc, _Py_error_handler errors)
{
- const Py_ssize_t max_char_size = 4;
- Py_ssize_t len = wcslen(text);
+ assert(str != NULL);
+ assert(output_length != NULL);
- assert(len >= 0);
+ // U+10ffff encoded to UTF-8 takes 4 bytes
+ const size_t max_char_size = 4;
+ size_t len = wcslen(text);
int surrogateescape = 0;
int surrogatepass = 0;
@@ -5654,11 +5655,11 @@ _Py_EncodeUTF8Ex(const wchar_t *text, char **str,
size_t *error_pos,
surrogatepass = 1;
break;
default:
- return -3;
+ return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER;
}
- if (len > PY_SSIZE_T_MAX / max_char_size - 1) {
- return -1;
+ if (len > (size_t)PY_SSIZE_T_MAX / max_char_size - 1) {
+ return _Py_CODEC_MEMORY_ERROR;
}
char *bytes;
if (raw_malloc) {
@@ -5668,12 +5669,11 @@ _Py_EncodeUTF8Ex(const wchar_t *text, char **str,
size_t *error_pos,
bytes = PyMem_Malloc((len + 1) * max_char_size);
}
if (bytes == NULL) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
char *p = bytes;
- Py_ssize_t i;
- for (i = 0; i < len; ) {
+ for (size_t i = 0; i < len; ) {
Py_ssize_t ch_pos = i;
Py_UCS4 ch = text[i];
i++;
@@ -5689,7 +5689,6 @@ _Py_EncodeUTF8Ex(const wchar_t *text, char **str, size_t
*error_pos,
if (ch < 0x80) {
/* Encode ASCII */
*p++ = (char) ch;
-
}
else if (ch < 0x0800) {
/* Encode Latin-1 */
@@ -5702,16 +5701,13 @@ _Py_EncodeUTF8Ex(const wchar_t *text, char **str,
size_t *error_pos,
if (error_pos != NULL) {
*error_pos = (size_t)ch_pos;
}
- if (reason != NULL) {
- *reason = "encoding error";
- }
if (raw_malloc) {
PyMem_RawFree(bytes);
}
else {
PyMem_Free(bytes);
}
- return -2;
+ return _Py_CODEC_ENCODE_ERROR;
}
*p++ = (char)(ch & 0xff);
}
@@ -5729,29 +5725,29 @@ _Py_EncodeUTF8Ex(const wchar_t *text, char **str,
size_t *error_pos,
*p++ = (char)(0x80 | (ch & 0x3f));
}
}
- *p++ = '\0';
+ *p++ = '\0'; // trailing NUL byte
size_t final_size = (p - bytes);
- char *bytes2;
+ assert(final_size >= 1);
+ *output_length = final_size - 1; // -1 for the trailing NUL byte
+
+ char *result;
if (raw_malloc) {
- bytes2 = PyMem_RawRealloc(bytes, final_size);
+ result = PyMem_RawRealloc(bytes, final_size);
}
else {
- bytes2 = PyMem_Realloc(bytes, final_size);
+ result = PyMem_Realloc(bytes, final_size);
}
- if (bytes2 == NULL) {
- if (error_pos != NULL) {
- *error_pos = (size_t)-1;
- }
+ if (result == NULL) {
if (raw_malloc) {
PyMem_RawFree(bytes);
}
else {
PyMem_Free(bytes);
}
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
- *str = bytes2;
+ *str = result;
return 0;
}
@@ -15229,8 +15225,11 @@ unicode_iter(PyObject *seq)
static int
encode_wstr_utf8(wchar_t *wstr, char **str, const char *name)
{
- int res;
- res = _Py_EncodeUTF8Ex(wstr, str, NULL, NULL, 1, _Py_ERROR_STRICT);
+ assert(str != NULL);
+
+ size_t unused_output_length;
+ int res = _Py_EncodeUTF8(wstr, str, &unused_output_length,
+ NULL, 1, _Py_ERROR_STRICT);
if (res == -2) {
PyErr_Format(PyExc_RuntimeError, "cannot encode %s", name);
return -1;
diff --git a/Python/fileutils.c b/Python/fileutils.c
index 404fec83385e97..8ed88047b35e4f 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -51,13 +51,19 @@ extern int winerror_to_errno(int);
int _Py_open_cloexec_works = -1;
#endif
+// wcstombs() error
+static const size_t ENCODE_ERROR = (size_t)-1;
// mbstowcs() and mbrtowc() errors
-static const size_t DECODE_ERROR = ((size_t)-1);
+static const size_t DECODE_ERROR = (size_t)-1;
#ifdef HAVE_MBRTOWC
static const size_t INCOMPLETE_CHARACTER = (size_t)-2;
#endif
+// Get the error handler as 'int surrogateescape'.
+// Set '*surrogateescape' and return 0 on success.
+// Return -1 if the error handler is not supported: other than "strict" and
+// "surrogateescape".
static int
get_surrogateescape(_Py_error_handler errors, int *surrogateescape)
{
@@ -227,8 +233,7 @@ extern int _Py_normalize_encoding(const char *, char *,
size_t, int);
Values of force_ascii:
1: the workaround is used: Py_EncodeLocale() uses
- encode_ascii_surrogateescape() and Py_DecodeLocale() uses
- decode_ascii()
+ encode_ascii() and Py_DecodeLocale() uses decode_ascii()
0: the workaround is not used: Py_EncodeLocale() uses wcstombs() and
Py_DecodeLocale() uses mbstowcs()
-1: unknown, need to call check_force_ascii() to get the value
@@ -353,36 +358,41 @@ _Py_ResetForceASCII(void)
}
+// Encode a wide string to the ASCII encoding. If errors is
+// _Py_ERROR_SURROGATEESCAPE, handle non-ASCII character using the
+// surrogateescape error handler. In that case, the output is non-ASCII.
+//
+// Set *str to a newly allocated string on success.
+// Return a negative number on error: _Py_CODEC_MEMORY_ERROR,
+// _Py_CODEC_ENCODE_ERROR or _Py_CODEC_UNSUPPORTED_ERROR_HANDLER.
+//
+// str and output_length must not be NULL
static int
-encode_ascii(const wchar_t *text, char **str,
- size_t *error_pos, const char **reason,
- int raw_malloc, _Py_error_handler errors)
+encode_ascii(const wchar_t *text, char **str, size_t *output_length,
+ size_t *error_pos, int raw_malloc, _Py_error_handler errors)
{
- char *result = NULL, *out;
- size_t len, i;
- wchar_t ch;
+ assert(str != NULL);
+ assert(output_length != NULL);
int surrogateescape;
if (get_surrogateescape(errors, &surrogateescape) < 0) {
- return -3;
+ // Only support "strict" and "surrogateescape"
+ return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER;
}
- len = wcslen(text);
+ size_t len = wcslen(text);
+ *output_length = len;
/* +1 for NULL byte */
- if (raw_malloc) {
- result = PyMem_RawMalloc(len + 1);
- }
- else {
- result = PyMem_Malloc(len + 1);
- }
+ char *result;
+ result = raw_malloc ? PyMem_RawMalloc(len + 1) : PyMem_Malloc(len + 1);
if (result == NULL) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
- out = result;
- for (i=0; i<len; i++) {
- ch = text[i];
+ char *out = result;
+ for (size_t i=0; i < len; i++) {
+ wchar_t ch = text[i];
if (ch <= 0x7f) {
/* ASCII character */
@@ -402,13 +412,12 @@ encode_ascii(const wchar_t *text, char **str,
if (error_pos != NULL) {
*error_pos = i;
}
- if (reason) {
- *reason = "encoding error";
- }
- return -2;
+ return _Py_CODEC_ENCODE_ERROR;
}
}
- *out = '\0';
+ *out++ = '\0';
+ assert((size_t)(out - result) == (*output_length + 1));
+
*str = result;
return 0;
}
@@ -424,14 +433,25 @@ _Py_ResetForceASCII(void)
{
/* nothing to do */
}
-#endif /* !defined(_Py_FORCE_UTF8_FS_ENCODING) && !defined(MS_WINDOWS) */
+#endif /* defined(_Py_FORCE_UTF8_FS_ENCODING) || defined(MS_WINDOWS) */
#if !defined(HAVE_MBRTOWC) || defined(USE_FORCE_ASCII)
+// Decode a bytes string from the ASCII encoding. If 'errors' is
+// "surrogateescape", decode non-ASCII bytes with "surrogateescape" error
+// handler and so the output is non-ASCII.
+//
+// On success, set *wstr and *wlen (if set) and return 0.
+// On error, return a negative number: _Py_CODEC_MEMORY_ERROR,
+// _Py_CODEC_DECODE_ERROR or _Py_CODEC_UNSUPPORTED_ERROR_HANDLER.
+// On decode, set *wlen (if set).
static int
decode_ascii(const char *arg, wchar_t **wstr, size_t *wlen,
- const char **reason, _Py_error_handler errors)
+ _Py_error_handler errors)
{
+ assert(arg != NULL);
+ assert(wstr != NULL);
+
wchar_t *res;
unsigned char *in;
wchar_t *out;
@@ -439,21 +459,22 @@ decode_ascii(const char *arg, wchar_t **wstr, size_t
*wlen,
int surrogateescape;
if (get_surrogateescape(errors, &surrogateescape) < 0) {
- return -3;
+ // Only support "strict" and "surrogateescape"
+ return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER;
}
if (argsize > PY_SSIZE_T_MAX / sizeof(wchar_t)) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
res = PyMem_RawMalloc(argsize * sizeof(wchar_t));
if (!res) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
out = res;
for (in = (unsigned char*)arg; *in; in++) {
unsigned char ch = *in;
- if (ch < 128) {
+ if (ch <= 127) {
*out++ = ch;
}
else {
@@ -462,10 +483,7 @@ decode_ascii(const char *arg, wchar_t **wstr, size_t *wlen,
if (wlen) {
*wlen = in - (unsigned char*)arg;
}
- if (reason) {
- *reason = "decoding error";
- }
- return -2;
+ return _Py_CODEC_DECODE_ERROR;
}
*out++ = 0xdc00 + ch;
}
@@ -480,22 +498,22 @@ decode_ascii(const char *arg, wchar_t **wstr, size_t
*wlen,
}
#endif /* !HAVE_MBRTOWC */
+// Decode a bytes string from the current locale encoding.
+// On success, set *wstr and *wlen (if set) and return 0.
+// On error, return a negative number: _Py_CODEC_MEMORY_ERROR,
+// _Py_CODEC_DECODE_ERROR or _Py_CODEC_UNSUPPORTED_ERROR_HANDLER.
+// On decode, set *wlen (if set).
static int
decode_current_locale(const char* arg, wchar_t **wstr, size_t *wlen,
- const char **reason, _Py_error_handler errors)
+ _Py_error_handler errors)
{
- wchar_t *res;
- size_t argsize;
- size_t count;
-#ifdef HAVE_MBRTOWC
- unsigned char *in;
- wchar_t *out;
- mbstate_t mbs;
-#endif
+ assert(arg != NULL);
+ assert(wstr != NULL);
int surrogateescape;
if (get_surrogateescape(errors, &surrogateescape) < 0) {
- return -3;
+ // Only support "strict" and "surrogateescape"
+ return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER;
}
#ifdef HAVE_BROKEN_MBSTOWCS
@@ -503,21 +521,25 @@ decode_current_locale(const char* arg, wchar_t **wstr,
size_t *wlen,
* mbstowcs which does not count the characters that
* would result from conversion. Use an upper bound.
*/
- argsize = strlen(arg);
+ size_t argsize = strlen(arg);
#else
- argsize = _Py_mbstowcs(NULL, arg, 0);
+ size_t argsize = _Py_mbstowcs(NULL, arg, 0);
#endif
+ wchar_t *res;
if (argsize != DECODE_ERROR) {
if (argsize > PY_SSIZE_T_MAX / sizeof(wchar_t) - 1) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
res = (wchar_t *)PyMem_RawMalloc((argsize + 1) * sizeof(wchar_t));
if (!res) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
- count = _Py_mbstowcs(res, arg, argsize + 1);
+ // +1 to write also the trailing NUL character
+ size_t count = _Py_mbstowcs(res, arg, argsize + 1);
if (count != DECODE_ERROR) {
+ // Success
+ assert(count == argsize);
*wstr = res;
if (wlen != NULL) {
*wlen = count;
@@ -535,15 +557,16 @@ decode_current_locale(const char* arg, wchar_t **wstr,
size_t *wlen,
actual output could use less memory. */
argsize = strlen(arg) + 1;
if (argsize > PY_SSIZE_T_MAX / sizeof(wchar_t)) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
res = (wchar_t*)PyMem_RawMalloc(argsize * sizeof(wchar_t));
if (!res) {
- return -1;
+ return _Py_CODEC_MEMORY_ERROR;
}
- in = (unsigned char*)arg;
- out = res;
+ unsigned char *in = (unsigned char*)arg;
+ wchar_t *out = res;
+ mbstate_t mbs;
memset(&mbs, 0, sizeof mbs);
while (argsize) {
size_t converted = _Py_mbrtowc(out, (char*)in, argsize, &mbs);
@@ -575,6 +598,7 @@ decode_current_locale(const char* arg, wchar_t **wstr,
size_t *wlen,
}
if (wlen != NULL) {
*wlen = out - res;
+ assert(res[*wlen] == 0);
}
*wstr = res;
return 0;
@@ -584,66 +608,37 @@ decode_current_locale(const char* arg, wchar_t **wstr,
size_t *wlen,
if (wlen) {
*wlen = in - (unsigned char*)arg;
}
- if (reason) {
- *reason = "decoding error";
- }
- return -2;
+ return _Py_CODEC_DECODE_ERROR;
#else /* HAVE_MBRTOWC */
/* Cannot use C locale for escaping; manually escape as if charset
is ASCII (i.e. escape all bytes > 128. This will still roundtrip
correctly in the locale's charset, which must be an ASCII superset. */
- return decode_ascii(arg, wstr, wlen, reason, errors);
+ return decode_ascii(arg, wstr, wlen, errors);
#endif /* HAVE_MBRTOWC */
}
-/* Decode a byte string from the locale encoding.
-
- Use the strict error handler if 'surrogateescape' is zero. Use the
- surrogateescape error handler if 'surrogateescape' is non-zero: undecodable
- bytes are decoded as characters in range U+DC80..U+DCFF. If a byte sequence
- can be decoded as a surrogate character, escape the bytes using the
- surrogateescape error handler instead of decoding them.
-
- On success, return 0 and write the newly allocated wide character string
into
- *wstr (use PyMem_RawFree() to free the memory). If wlen is not NULL, write
- the number of wide characters excluding the null character into *wlen.
-
- On memory allocation failure, return -1.
-
- On decoding error, return -2. If wlen is not NULL, write the start of
- invalid byte sequence in the input string into *wlen. If reason is not NULL,
- write the decoding error message into *reason.
-
- Return -3 if the error handler 'errors' is not supported.
-
- Use the Py_EncodeLocaleEx() function to encode the character string back to
- a byte string. */
-int
-_Py_DecodeLocaleEx(const char* arg, wchar_t **wstr, size_t *wlen,
- const char **reason,
+static int
+decode_locale_impl(const char* arg, wchar_t **wstr, size_t *wlen,
int current_locale, _Py_error_handler errors)
{
if (current_locale) {
#ifdef _Py_FORCE_UTF8_LOCALE
- return _Py_DecodeUTF8Ex(arg, strlen(arg), wstr, wlen, reason,
- errors);
+ return _Py_DecodeUTF8(arg, strlen(arg), wstr, wlen, errors);
#else
- return decode_current_locale(arg, wstr, wlen, reason, errors);
+ return decode_current_locale(arg, wstr, wlen, errors);
#endif
}
#ifdef _Py_FORCE_UTF8_FS_ENCODING
- return _Py_DecodeUTF8Ex(arg, strlen(arg), wstr, wlen, reason,
- errors);
+ return _Py_DecodeUTF8(arg, strlen(arg), wstr, wlen, errors);
#else
int use_utf8 = (_PyRuntime.preconfig.utf8_mode >= 1);
#ifdef MS_WINDOWS
use_utf8 |= (_PyRuntime.preconfig.legacy_windows_fs_encoding == 0);
#endif
if (use_utf8) {
- return _Py_DecodeUTF8Ex(arg, strlen(arg), wstr, wlen, reason,
- errors);
+ return _Py_DecodeUTF8(arg, strlen(arg), wstr, wlen, errors);
}
#ifdef USE_FORCE_ASCII
@@ -653,45 +648,112 @@ _Py_DecodeLocaleEx(const char* arg, wchar_t **wstr,
size_t *wlen,
if (force_ascii) {
/* force ASCII encoding to workaround mbstowcs() issue */
- return decode_ascii(arg, wstr, wlen, reason, errors);
+ return decode_ascii(arg, wstr, wlen, errors);
}
#endif
- return decode_current_locale(arg, wstr, wlen, reason, errors);
+ return decode_current_locale(arg, wstr, wlen, errors);
#endif /* !_Py_FORCE_UTF8_FS_ENCODING */
}
-/* Decode a byte string from the locale encoding with the
- surrogateescape error handler: undecodable bytes are decoded as characters
- in range U+DC80..U+DCFF. If a byte sequence can be decoded as a surrogate
- character, escape the bytes using the surrogateescape error handler instead
- of decoding them.
+// Decode a byte string from the locale encoding.
+//
+// Supported error handlers are _Py_ERROR_STRICT and _Py_ERROR_SURROGATEESCAPE.
+// The UTF-8 decoder also supports _Py_ERROR_SURROGATEPASS.
+//
+// If errors is _Py_ERROR_SURROGATEESCAPE, undecodable bytes are decoded as
+// characters in range U+DC80..U+DCFF. If a byte sequence can be decoded as a
+// surrogate character, escape the bytes using the surrogateescape error
+// handler instead of decoding them.
+//
+// On success, return 0 and write the newly allocated wide character string
into
+// *wstr (use PyMem_RawFree() to free the memory). If wlen is not NULL, write
+// the number of wide characters excluding the null character into *wlen.
+//
+// On error, return a negative number.
+//
+// On memory allocation failure, return _Py_CODEC_MEMORY_ERROR.
+//
+// On decoding error, return _Py_CODEC_DECODE_ERROR. If wlen is not NULL,
+// write the start of invalid byte sequence in the input string into *wlen. If
+// reason is not NULL, write the decoding error message into *reason.
+//
+// Return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER if the 'errors' error
+// handler is not supported: other than "strict" and "surrogateescape".
+//
+// Use the _Py_EncodeLocale() function to encode the character string back to
+// a byte string.
+//
+// arg and wstr must not be NULL.
+int
+_Py_DecodeLocale(const char* arg, wchar_t **wstr, size_t *wlen,
+ int current_locale, _Py_error_handler errors)
+{
+ assert(arg != NULL);
+ assert(wstr != NULL);
- Return a pointer to a newly allocated wide character string, use
- PyMem_RawFree() to free the memory. If size is not NULL, write the number of
- wide characters excluding the null character into *size
+#ifdef Py_DEBUG
+ size_t wlen_canary = (size_t)-2;
+ if (wlen) {
+ *wlen = wlen_canary;
+ }
+#endif
- Return NULL on decoding error or memory allocation error. If *size* is not
- NULL, *size is set to (size_t)-1 on memory error or set to (size_t)-2 on
- decoding error.
+ int res = decode_locale_impl(arg, wstr, wlen, current_locale, errors);
+ if (res < 0) {
+ // Error
+ *wstr = NULL;
+ if (res == _Py_CODEC_DECODE_ERROR) {
+#ifdef Py_DEBUG
+ assert(wlen == NULL || *wlen != wlen_canary);
+#endif
+ }
+ else {
+ if (wlen) {
+ *wlen = 0;
+ }
+ }
+ }
+ else {
+ // Success
+ assert(*wstr != NULL);
+#ifdef Py_DEBUG
+ if (wlen != NULL) {
+ assert(*wlen == wcslen(*wstr));
+ }
+#endif
+ }
+ return res;
+}
- Decoding errors should never happen, unless there is a bug in the C
- library.
- Use the Py_EncodeLocale() function to encode the character string back to a
- byte string. */
+// Decode a byte string from the locale encoding with the
+// surrogateescape error handler. Undecodable bytes are decoded as characters
+// in range U+DC80..U+DCFF. If a byte sequence can be decoded as a surrogate
+// character, escape the bytes using the surrogateescape error handler instead
+// of decoding them.
+//
+// Return a pointer to a newly allocated wide character string, use
+// PyMem_RawFree() to free the memory. If size is not NULL, write the number of
+// wide characters excluding the null character into *size.
+//
+// On memory allocation failure, set *size to (size_t)-1 and return NULL.
+//
+// On decode error, set *size to (size_t)-2 and return NULL.
+// Decoding errors should never happen, unless there is a bug in the C library.
+//
+// Use the Py_EncodeLocale() function to encode the character string back to a
+// byte string.
wchar_t*
-Py_DecodeLocale(const char* arg, size_t *wlen)
+Py_DecodeLocale(const char* arg, size_t *size)
{
wchar_t *wstr;
- int res = _Py_DecodeLocaleEx(arg, &wstr, wlen,
- NULL, 0,
- _Py_ERROR_SURROGATEESCAPE);
+ int res = _Py_DecodeLocale(arg, &wstr, size, 0, _Py_ERROR_SURROGATEESCAPE);
if (res != 0) {
- assert(res != -3);
- if (wlen != NULL) {
- *wlen = (size_t)res;
+ assert(res == _Py_CODEC_MEMORY_ERROR || res == _Py_CODEC_DECODE_ERROR);
+ if (size != NULL) {
+ *size = (size_t)res;
}
return NULL;
}
@@ -699,143 +761,154 @@ Py_DecodeLocale(const char* arg, size_t *wlen)
}
-static int
-encode_current_locale(const wchar_t *text, char **str,
- size_t *error_pos, const char **reason,
- int raw_malloc, _Py_error_handler errors)
+// If bytes is NULL, compute the length in 'bytes' of the encoded string.
+// Otherwise, encode the wide string into 'bytes', decrements 'size', and
+// return 0 on success.
+// On encoding error, set 'error_pos' (if set) and return ENCODE_ERROR.
+static size_t
+encode_current_locale_impl(const wchar_t *text, const size_t len,
+ int surrogateescape,
+ char *bytes, size_t size,
+ size_t *error_pos)
{
- const size_t len = wcslen(text);
- char *result = NULL, *bytes = NULL;
- size_t i, size, converted;
- wchar_t c, buf[2];
-
- int surrogateescape;
- if (get_surrogateescape(errors, &surrogateescape) < 0) {
- return -3;
- }
-
- /* The function works in two steps:
- 1. compute the length of the output buffer in bytes (size)
- 2. outputs the bytes */
- size = 0;
+ wchar_t buf[2];
+ // The second character is always the NUL character
buf[1] = 0;
- while (1) {
- for (i=0; i < len; i++) {
- c = text[i];
- if (c >= 0xdc80 && c <= 0xdcff) {
- if (!surrogateescape) {
- goto encode_error;
- }
- /* UTF-8b surrogate */
- if (bytes != NULL) {
- *bytes++ = c - 0xdc00;
- size--;
- }
- else {
- size++;
+
+ for (size_t i=0; i < len; i++) {
+ wchar_t c = text[i];
+ if (c >= 0xdc80 && c <= 0xdcff) {
+ if (!surrogateescape) {
+ if (error_pos != NULL) {
+ *error_pos = i;
}
- continue;
+ return ENCODE_ERROR;
+ }
+ /* UTF-8b surrogate */
+ if (bytes != NULL) {
+ *bytes++ = c - 0xdc00;
+ size--;
}
else {
- buf[0] = c;
- if (bytes != NULL) {
- converted = wcstombs(bytes, buf, size);
- }
- else {
- converted = wcstombs(NULL, buf, 0);
- }
- if (converted == DECODE_ERROR) {
- goto encode_error;
- }
- if (bytes != NULL) {
- bytes += converted;
- size -= converted;
- }
- else {
- size += converted;
- }
+ size++;
}
}
- if (result != NULL) {
- *bytes = '\0';
- break;
+ else {
+ // Encode a single character using wcstombs()
+ buf[0] = c;
+ size_t converted;
+ if (bytes != NULL) {
+ converted = wcstombs(bytes, buf, size);
+ }
+ else {
+ converted = wcstombs(NULL, buf, 0);
+ }
+ if (converted == ENCODE_ERROR) {
+ if (error_pos != NULL) {
+ *error_pos = i;
+ }
+ return ENCODE_ERROR;
+ }
+ if (bytes != NULL) {
+ bytes += converted;
+ size -= converted;
+ }
+ else {
+ size += converted;
+ }
}
+ }
+ if (bytes) {
+ *bytes = '\0';
+ }
+ // Sanity check, it cannot happen in practice
+ assert(size != ENCODE_ERROR);
+ return size;
+}
- size += 1; /* nul byte at the end */
+
+// Encode a wide string to the current locale encoding.
+// Set *str to a newly allocated string on success.
+// Return a negative number on error: _Py_CODEC_MEMORY_ERROR,
+// _Py_CODEC_ENCODE_ERROR or _Py_CODEC_UNSUPPORTED_ERROR_HANDLER.
+//
+// str and output_length must not be NULL
+static int
+encode_current_locale(const wchar_t *text, char **str, size_t *output_length,
+ size_t *error_pos, int raw_malloc,
+ _Py_error_handler errors)
+{
+ assert(str != NULL);
+ assert(output_length != NULL);
+
+ int surrogateescape;
+ if (get_surrogateescape(errors, &surrogateescape) < 0) {
+ // Only support "strict" and "surrogateescape"
+ return _Py_CODEC_UNSUPPORTED_ERROR_HANDLER;
+ }
+
+ // First, compute the output length
+ const size_t len = wcslen(text);
+ // Sanity check, it cannot happen in practice
+ assert(len != ENCODE_ERROR);
+ size_t size = encode_current_locale_impl(text, len, surrogateescape,
+ NULL, 0, error_pos);
+ if (size == ENCODE_ERROR) {
+ return _Py_CODEC_ENCODE_ERROR;
+ }
+
+ *output_length = size;
+ char *result;
+ result = raw_malloc ? PyMem_RawMalloc(size + 1) : PyMem_Malloc(size + 1);
+ if (result == NULL) {
+ return _Py_CODEC_MEMORY_ERROR;
+ }
+
+ // Second, encode characters
+ size = encode_current_locale_impl(text, len, surrogateescape,
+ result, size, error_pos);
+ if (size == ENCODE_ERROR) {
if (raw_malloc) {
- result = PyMem_RawMalloc(size);
+ PyMem_RawFree(result);
}
else {
- result = PyMem_Malloc(size);
- }
- if (result == NULL) {
- return -1;
+ PyMem_Free(result);
}
- bytes = result;
+ return _Py_CODEC_ENCODE_ERROR;
}
+ assert(size == 0);
+
*str = result;
return 0;
-
-encode_error:
- if (raw_malloc) {
- PyMem_RawFree(result);
- }
- else {
- PyMem_Free(result);
- }
- if (error_pos != NULL) {
- *error_pos = i;
- }
- if (reason) {
- *reason = "encoding error";
- }
- return -2;
}
-/* Encode a string to the locale encoding.
-
- Parameters:
-
- * raw_malloc: if non-zero, allocate memory using PyMem_RawMalloc() instead
- of PyMem_Malloc().
- * current_locale: if non-zero, use the current LC_CTYPE, otherwise use
- Python filesystem encoding.
- * errors: error handler like "strict" or "surrogateescape".
-
- Return value:
-
- 0: success, *str is set to a newly allocated decoded string.
- -1: memory allocation failure
- -2: encoding error, set *error_pos and *reason (if set).
- -3: the error handler 'errors' is not supported.
- */
static int
-encode_locale_ex(const wchar_t *text, char **str, size_t *error_pos,
- const char **reason,
- int raw_malloc, int current_locale, _Py_error_handler errors)
+encode_locale_inner(const wchar_t *text, char **str, size_t *output_length,
+ size_t *error_pos, int raw_malloc,
+ int current_locale, _Py_error_handler errors)
{
if (current_locale) {
#ifdef _Py_FORCE_UTF8_LOCALE
- return _Py_EncodeUTF8Ex(text, str, error_pos, reason,
- raw_malloc, errors);
+ return _Py_EncodeUTF8(text, str, output_length,
+ error_pos, raw_malloc, errors);
#else
- return encode_current_locale(text, str, error_pos, reason,
- raw_malloc, errors);
+ return encode_current_locale(text, str, output_length,
+ error_pos, raw_malloc, errors);
#endif
}
#ifdef _Py_FORCE_UTF8_FS_ENCODING
- return _Py_EncodeUTF8Ex(text, str, error_pos, reason,
- raw_malloc, errors);
+ return _Py_EncodeUTF8(text, str, output_length,
+ error_pos, raw_malloc, errors);
#else
int use_utf8 = (_PyRuntime.preconfig.utf8_mode >= 1);
#ifdef MS_WINDOWS
use_utf8 |= (_PyRuntime.preconfig.legacy_windows_fs_encoding == 0);
#endif
if (use_utf8) {
- return _Py_EncodeUTF8Ex(text, str, error_pos, reason,
- raw_malloc, errors);
+ return _Py_EncodeUTF8(text, str, output_length,
+ error_pos, raw_malloc, errors);
}
#ifdef USE_FORCE_ASCII
@@ -844,45 +917,101 @@ encode_locale_ex(const wchar_t *text, char **str, size_t
*error_pos,
}
if (force_ascii) {
- return encode_ascii(text, str, error_pos, reason,
- raw_malloc, errors);
+ return encode_ascii(text, str, output_length,
+ error_pos, raw_malloc, errors);
}
#endif
- return encode_current_locale(text, str, error_pos, reason,
- raw_malloc, errors);
+ return encode_current_locale(text, str, output_length,
+ error_pos, raw_malloc, errors);
#endif /* _Py_FORCE_UTF8_FS_ENCODING */
}
+
+// Encode a wide string to the locale encoding.
+//
+// Parameters:
+//
+// * raw_malloc: if non-zero, allocate memory using PyMem_RawMalloc() instead
+// of PyMem_Malloc().
+// * current_locale: if non-zero, use the current LC_CTYPE, otherwise use
+// Python filesystem encoding.
+// * errors: supported error handlers are _Py_ERROR_STRICT and
+// _Py_ERROR_SURROGATEESCAPE. The UTF-8 encoder also supports
+// _Py_ERROR_SURROGATEPASS.
+//
+// Set *str to a newly allocated decoded string and return 0 on success.
+// Return a negative result on error:
+//
+// * _Py_CODEC_MEMORY_ERROR: memory allocation failure
+// * _Py_CODEC_ENCODE_ERROR: encoding error, set *error_pos.
+// * _Py_CODEC_UNSUPPORTED_ERROR_HANDLER: the 'errors' error handler
+// is not supported.
+//
+// text, str and output_length must not be NULL.
+static int
+encode_locale_impl(const wchar_t *text, char **str, size_t *output_length,
+ size_t *error_pos, int raw_malloc,
+ int current_locale, _Py_error_handler errors)
+{
+ assert(text != NULL);
+ assert(str != NULL);
+ assert(output_length != NULL);
+
+ int res = encode_locale_inner(text, str, output_length,
+ error_pos, raw_malloc,
+ current_locale, errors);
+ if (res < 0) {
+ // Error
+ *str = NULL;
+ *output_length = 0;
+ if (res != _Py_CODEC_ENCODE_ERROR) {
+ if (error_pos) {
+ *error_pos = 0;
+ }
+ }
+ }
+ else {
+ assert(*str != NULL);
+ assert(*output_length == strlen(*str));
+ // *error_pos is left unchanged
+ }
+ return res;
+}
+
static char*
encode_locale(const wchar_t *text, size_t *error_pos,
int raw_malloc, int current_locale)
{
char *str;
- int res = encode_locale_ex(text, &str, error_pos, NULL,
- raw_malloc, current_locale,
- _Py_ERROR_SURROGATEESCAPE);
- if (res != -2 && error_pos) {
- *error_pos = (size_t)-1;
- }
+ size_t unused_output_length;
+ int res = encode_locale_impl(text, &str, &unused_output_length,
+ error_pos, raw_malloc, current_locale,
+ _Py_ERROR_SURROGATEESCAPE);
if (res != 0) {
+ assert(res == _Py_CODEC_MEMORY_ERROR || res == _Py_CODEC_ENCODE_ERROR);
+ if (res == _Py_CODEC_MEMORY_ERROR && error_pos != NULL) {
+ *error_pos = (size_t)-1;
+ }
return NULL;
}
return str;
}
-/* Encode a wide character string to the locale encoding with the
- surrogateescape error handler: surrogate characters in the range
- U+DC80..U+DCFF are converted to bytes 0x80..0xFF.
-
- Return a pointer to a newly allocated byte string, use PyMem_Free() to free
- the memory. Return NULL on encoding or memory allocation error.
-
- If error_pos is not NULL, *error_pos is set to (size_t)-1 on success, or set
- to the index of the invalid character on encoding error.
-
- Use the Py_DecodeLocale() function to decode the bytes string back to a wide
- character string. */
+// Encode a wide character string to the locale encoding with the
+// surrogateescape error handler. Surrogate characters in the range
+// U+DC80..U+DCFF are encoded to bytes 0x80..0xFF.
+//
+// Return a pointer to a newly allocated byte string, use PyMem_Free() to free
+// the memory. Return NULL on encoding or memory allocation error.
+//
+// On memory allocation failure, set *error_pos to (size_t)-1 and return NULL.
+//
+// On encoding error, set *error_pos to the index of the first unencodable
+// character and return NULL.
+//
+// Use the Py_DecodeLocale() function to decode the bytes string back to a wide
+// character string.
char*
Py_EncodeLocale(const wchar_t *text, size_t *error_pos)
{
@@ -892,7 +1021,7 @@ Py_EncodeLocale(const wchar_t *text, size_t *error_pos)
/* Similar to Py_EncodeLocale(), but result must be freed by PyMem_RawFree()
instead of PyMem_Free(). */
-char*
+static char*
_Py_EncodeLocaleRaw(const wchar_t *text, size_t *error_pos)
{
return encode_locale(text, error_pos, 1, 0);
@@ -900,12 +1029,12 @@ _Py_EncodeLocaleRaw(const wchar_t *text, size_t
*error_pos)
int
-_Py_EncodeLocaleEx(const wchar_t *text, char **str,
- size_t *error_pos, const char **reason,
- int current_locale, _Py_error_handler errors)
+_Py_EncodeLocale(const wchar_t *text, char **str, size_t *output_length,
+ size_t *error_pos, int current_locale,
+ _Py_error_handler errors)
{
- return encode_locale_ex(text, str, error_pos, reason, 1,
- current_locale, errors);
+ return encode_locale_impl(text, str, output_length,
+ error_pos, 1, current_locale, errors);
}
@@ -944,9 +1073,8 @@ _Py_GetLocaleEncoding(void)
}
wchar_t *wstr;
- int res = decode_current_locale(encoding, &wstr, NULL,
- NULL, _Py_ERROR_SURROGATEESCAPE);
- if (res < 0) {
+ if (decode_current_locale(encoding, &wstr, NULL,
+ _Py_ERROR_SURROGATEESCAPE) < 0) {
return NULL;
}
return wstr;
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 178e6f992fa11f..363739a1a1b43c 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -4153,7 +4153,9 @@ static char*
wstr_to_utf8(PyInitConfig *config, wchar_t *wstr)
{
char *utf8;
- int res = _Py_EncodeUTF8Ex(wstr, &utf8, NULL, NULL, 1, _Py_ERROR_STRICT);
+ size_t utf8_len;
+ int res = _Py_EncodeUTF8(wstr, &utf8, &utf8_len,
+ NULL, 1, _Py_ERROR_STRICT);
if (res == -2) {
initconfig_set_error(config, "encoding error");
return NULL;
@@ -4164,7 +4166,7 @@ wstr_to_utf8(PyInitConfig *config, wchar_t *wstr)
}
// Copy to use the malloc() memory allocator
- size_t size = strlen(utf8) + 1;
+ size_t size = utf8_len + 1;
char *str = malloc(size);
if (str == NULL) {
PyMem_RawFree(utf8);
@@ -4323,12 +4325,13 @@ utf8_to_wstr(PyInitConfig *config, const char *str)
{
wchar_t *wstr;
size_t wlen;
- int res = _Py_DecodeUTF8Ex(str, strlen(str), &wstr, &wlen, NULL,
_Py_ERROR_STRICT);
- if (res == -2) {
+ int res = _Py_DecodeUTF8(str, strlen(str), &wstr, &wlen, _Py_ERROR_STRICT);
+ if (res == _Py_CODEC_DECODE_ERROR) {
initconfig_set_error(config, "decoding error");
return NULL;
}
if (res < 0) {
+ assert(res == _Py_CODEC_MEMORY_ERROR);
config->status = _PyStatus_NO_MEMORY();
return NULL;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]