diff --git a/Doc/c-api/bytes.rst b/Doc/c-api/bytes.rst --- a/Doc/c-api/bytes.rst +++ b/Doc/c-api/bytes.rst @@ -193,10 +193,10 @@ called with a non-bytes parameter. A way to resize a bytes object even though it is "immutable". Only use this to build up a brand new bytes object; don't use this if the bytes may already be known in other parts of the code. It is an error to call this function if the refcount on the input bytes object is not one. Pass the address of an existing bytes object as an lvalue (it may be written into), and the new size desired. On success, *\*bytes* holds the resized bytes object and ``0`` is returned; the address in *\*bytes* may differ from its input value. If the reallocation fails, the original bytes object at *\*bytes* is deallocated, - *\*bytes* is set to *NULL*, a memory exception is set, and ``-1`` is + *\*bytes* is set to *NULL*, :exc:`MemoryError` is set, and ``-1`` is returned. diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -2537,16 +2537,30 @@ class RawUnicodeEscapeTest(unittest.Test self.assertEqual(decode(data, "ignore"), ("[]", len(data))) self.assertEqual(decode(data, "replace"), ("[\ufffd]\ufffd", len(data))) self.assertRaises(UnicodeDecodeError, decode, br"\U00110000") self.assertEqual(decode(br"\U00110000", "ignore"), ("", 10)) self.assertEqual(decode(br"\U00110000", "replace"), ("\ufffd", 10)) +class EscapeEncodeTest(unittest.TestCase): + + def test_escape_encode(self): + tests = [ + (b'', (b'', 0)), + (b'foobar', (b'foobar', 6)), + (b'spam\0eggs', (b'spam\\x00eggs', 9)), + ] + for data, output in tests: + with self.subTest(data=data): + self.assertEqual(codecs.escape_encode(data), output) + self.assertRaises(TypeError, codecs.escape_encode, 'spam') + + class SurrogateEscapeTest(unittest.TestCase): def test_utf8(self): # Bad byte self.assertEqual(b"foo\x80bar".decode("utf-8", "surrogateescape"), "foo\udc80bar") self.assertEqual("foo\udc80bar".encode("utf-8", "surrogateescape"), b"foo\x80bar") diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -2904,22 +2904,27 @@ PyBytes_ConcatAndDel(PyObject **pv, PyOb does *not* include that), and a trailing \0 byte is stored. */ int _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) { PyObject *v; PyBytesObject *sv; + Py_ssize_t oldsize; v = *pv; - if (!PyBytes_Check(v) || Py_REFCNT(v) != 1 || newsize < 0) { - *pv = 0; - Py_DECREF(v); - PyErr_BadInternalCall(); - return -1; + if (!PyBytes_Check(v) || newsize < 0) { + goto error; + } + oldsize = Py_SIZE(v); + if (oldsize == newsize) { + return 0; + } + if (Py_REFCNT(v) != 1) { + goto error; } /* XXX UNREF/NEWREF interface should be more symmetrical */ _Py_DEC_REFTOTAL; _Py_ForgetReference(v); *pv = (PyObject *) PyObject_REALLOC(v, PyBytesObject_SIZE + newsize); if (*pv == NULL) { PyObject_Del(v); @@ -2927,16 +2932,21 @@ int return -1; } _Py_NewReference(*pv); sv = (PyBytesObject *) *pv; Py_SIZE(sv) = newsize; sv->ob_sval[newsize] = '\0'; sv->ob_shash = -1; /* invalidate cached hash value */ return 0; +error: + *pv = 0; + Py_DECREF(v); + PyErr_BadInternalCall(); + return -1; } void PyBytes_Fini(void) { int i; for (i = 0; i < UCHAR_MAX + 1; i++) Py_CLEAR(characters[i]);