Skip to content
Merged
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
12 changes: 9 additions & 3 deletions Doc/c-api/bytes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions Include/internal/pycore_bytesobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
34 changes: 32 additions & 2 deletions Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -3331,6 +3354,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize)
}
*pv = result;
Py_DECREF(v);
assert(_PyBytes_IsMutable(*pv));
return 0;
}

Expand All @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand Down
Loading