diff --git a/Doc/c-api/bytes.rst b/Doc/c-api/bytes.rst index ff68ecafcda4d08..6618f8ae1a61667 100644 --- a/Doc/c-api/bytes.rst +++ b/Doc/c-api/bytes.rst @@ -231,6 +231,7 @@ called with a non-bytes parameter. Resize a bytes object. *newsize* will be the new length of the bytes object. You can think of it as creating a new bytes object and destroying the old one, only more efficiently. + 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 @@ -239,6 +240,11 @@ called with a non-bytes parameter. *\*bytes* is set to ``NULL``, :exc:`MemoryError` is set, and ``-1`` is returned. + While bytes objects are usually immutable in Python, this special C API + allows mutating a bytes object in-place. The returned bytes object can still + be mutated using :c:func:`PyBytesWriter_GetData`; except if *newsize* is + zero in which case it returns the immutable empty bytes string. + .. soft-deprecated:: 3.15 Use the :c:type:`PyBytesWriter` API instead. @@ -290,10 +296,10 @@ object. .. c:type:: PyBytesWriter - A bytes writer instance. + A bytes writer object. - The API is **not thread safe**: a writer should only be used by a single - thread at the same time. + The API is **not thread safe**. A :c:type:`PyBytesWriter` object must only + be used by a single thread, it must not be shared between threads. The instance must be destroyed by :c:func:`PyBytesWriter_Finish` on success, or :c:func:`PyBytesWriter_Discard` on error. diff --git a/Include/internal/pycore_bytesobject.h b/Include/internal/pycore_bytesobject.h index 32da177c637c268..443bdb26ff8738c 100644 --- a/Include/internal/pycore_bytesobject.h +++ b/Include/internal/pycore_bytesobject.h @@ -77,6 +77,10 @@ PyAPI_FUNC(PyObject *) _PyBytes_Repeat(PyObject *self, Py_ssize_t n); extern int _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize); +#ifndef NDEBUG +extern int _PyBytes_IsMutable(PyObject *obj); +#endif + /* --- PyBytesWriter ------------------------------------------------------ */ struct PyBytesWriter { diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index 05e1b27dc82558a..de30c6118ba176b 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -256,6 +256,7 @@ bytearray_resize_storage(PyByteArrayObject *self, bytearray_write_trailing_null_byte(self); return -1; } + assert(_PyBytes_IsMutable(self->ob_bytes_object)); return 0; } diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index ace5fe9d86a7d33..4f98bdd89cc3810 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -37,7 +37,7 @@ static Py_ssize_t _PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer); #define CHARACTERS _Py_SINGLETON(bytes_characters) #define CHARACTER(ch) \ - ((PyBytesObject *)&(CHARACTERS[ch])); + ((PyBytesObject *)&(CHARACTERS[ch])) #define EMPTY (&_Py_SINGLETON(bytes_empty)) @@ -3294,6 +3294,29 @@ PyBytes_ConcatAndDel(PyObject **pv, PyObject *w) } +#ifndef NDEBUG +// Make sure that a bytes object can still be mutated. +// +// Usage: assert(_PyBytes_IsMutable(obj)). +int +_PyBytes_IsMutable(PyObject *v) +{ + // Singleton objects must never be modified + assert(!_Py_IsImmortal(v)); + + Py_ssize_t size = PyBytes_GET_SIZE(v); + if (size == 0) { + assert(v != bytes_get_empty()); + } + else if (size == 1) { + unsigned char ch = PyBytes_AS_STRING(v)[0]; + assert(v != (PyObject*)CHARACTER(ch)); + } + return 1; +} +#endif + + /* The following function breaks the notion that bytes are immutable: it changes the size of a bytes object. You can think of it as creating a new bytes object and destroying the old one, only @@ -3331,6 +3354,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize) } *pv = result; Py_DECREF(v); + assert(_PyBytes_IsMutable(*pv)); return 0; } @@ -3352,9 +3376,12 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize) Py_MIN(oldsize, newsize)); *pv = result; Py_DECREF(v); + assert(_PyBytes_IsMutable(*pv)); return 0; } - assert(v != bytes_get_empty()); + + // Only mutable bytes can be resized in-place + assert(_PyBytes_IsMutable(v)); if ((size_t)newsize > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) { PyErr_SetString(PyExc_OverflowError, @@ -3385,6 +3412,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize) Py_SET_SIZE(sv, newsize); sv->ob_sval[newsize] = '\0'; set_ob_shash(sv, -1); /* invalidate cached hash value */ + assert(_PyBytes_IsMutable(*pv)); return 0; } @@ -3647,6 +3675,7 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t size, int resize) assert(writer->obj != NULL); return -1; } + assert(_PyBytes_IsMutable(writer->obj)); } assert(writer->obj != NULL); } @@ -3673,6 +3702,7 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t size, int resize) writer->small_buffer, sizeof(writer->small_buffer)); } + assert(_PyBytes_IsMutable(writer->obj)); } #ifdef Py_DEBUG