Skip to content

Commit 121e27c

Browse files
authored
gh-157242: Add _PyBytes_IsMutable() assertion (#157371)
Elaborate on mutability in _PyBytes_Resize() and PyBytesWriter documentation. Check _PyBytes_IsMutable() in functions which require a mutable bytes object like PyBytesWriter_Resize().
1 parent bab97c5 commit 121e27c

4 files changed

Lines changed: 46 additions & 5 deletions

File tree

Doc/c-api/bytes.rst

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ called with a non-bytes parameter.
231231
Resize a bytes object. *newsize* will be the new length of the bytes object.
232232
You can think of it as creating a new bytes object and destroying the old
233233
one, only more efficiently.
234+
234235
Pass the address of an
235236
existing bytes object as an lvalue (it may be written into), and the new size
236237
desired. On success, *\*bytes* holds the resized bytes object and ``0`` is
@@ -239,6 +240,11 @@ called with a non-bytes parameter.
239240
*\*bytes* is set to ``NULL``, :exc:`MemoryError` is set, and ``-1`` is
240241
returned.
241242
243+
While bytes objects are usually immutable in Python, this special C API
244+
allows mutating a bytes object in-place. The returned bytes object can still
245+
be mutated using :c:func:`PyBytesWriter_GetData`; except if *newsize* is
246+
zero in which case it returns the immutable empty bytes string.
247+
242248
.. soft-deprecated:: 3.15
243249
Use the :c:type:`PyBytesWriter` API instead.
244250
@@ -290,10 +296,10 @@ object.
290296
291297
.. c:type:: PyBytesWriter
292298
293-
A bytes writer instance.
299+
A bytes writer object.
294300
295-
The API is **not thread safe**: a writer should only be used by a single
296-
thread at the same time.
301+
The API is **not thread safe**. A :c:type:`PyBytesWriter` object must only
302+
be used by a single thread, it must not be shared between threads.
297303
298304
The instance must be destroyed by :c:func:`PyBytesWriter_Finish` on
299305
success, or :c:func:`PyBytesWriter_Discard` on error.

Include/internal/pycore_bytesobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ PyAPI_FUNC(PyObject *) _PyBytes_Repeat(PyObject *self, Py_ssize_t n);
7777

7878
extern int _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize);
7979

80+
#ifndef NDEBUG
81+
extern int _PyBytes_IsMutable(PyObject *obj);
82+
#endif
83+
8084
/* --- PyBytesWriter ------------------------------------------------------ */
8185

8286
struct PyBytesWriter {

Objects/bytearrayobject.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ bytearray_resize_storage(PyByteArrayObject *self,
256256
bytearray_write_trailing_null_byte(self);
257257
return -1;
258258
}
259+
assert(_PyBytes_IsMutable(self->ob_bytes_object));
259260
return 0;
260261
}
261262

Objects/bytesobject.c

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ static Py_ssize_t _PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer);
3737

3838
#define CHARACTERS _Py_SINGLETON(bytes_characters)
3939
#define CHARACTER(ch) \
40-
((PyBytesObject *)&(CHARACTERS[ch]));
40+
((PyBytesObject *)&(CHARACTERS[ch]))
4141
#define EMPTY (&_Py_SINGLETON(bytes_empty))
4242

4343

@@ -3294,6 +3294,29 @@ PyBytes_ConcatAndDel(PyObject **pv, PyObject *w)
32943294
}
32953295

32963296

3297+
#ifndef NDEBUG
3298+
// Make sure that a bytes object can still be mutated.
3299+
//
3300+
// Usage: assert(_PyBytes_IsMutable(obj)).
3301+
int
3302+
_PyBytes_IsMutable(PyObject *v)
3303+
{
3304+
// Singleton objects must never be modified
3305+
assert(!_Py_IsImmortal(v));
3306+
3307+
Py_ssize_t size = PyBytes_GET_SIZE(v);
3308+
if (size == 0) {
3309+
assert(v != bytes_get_empty());
3310+
}
3311+
else if (size == 1) {
3312+
unsigned char ch = PyBytes_AS_STRING(v)[0];
3313+
assert(v != (PyObject*)CHARACTER(ch));
3314+
}
3315+
return 1;
3316+
}
3317+
#endif
3318+
3319+
32973320
/* The following function breaks the notion that bytes are immutable:
32983321
it changes the size of a bytes object. You can think of it
32993322
as creating a new bytes object and destroying the old one, only
@@ -3331,6 +3354,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize)
33313354
}
33323355
*pv = result;
33333356
Py_DECREF(v);
3357+
assert(_PyBytes_IsMutable(*pv));
33343358
return 0;
33353359
}
33363360

@@ -3352,9 +3376,12 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize)
33523376
Py_MIN(oldsize, newsize));
33533377
*pv = result;
33543378
Py_DECREF(v);
3379+
assert(_PyBytes_IsMutable(*pv));
33553380
return 0;
33563381
}
3357-
assert(v != bytes_get_empty());
3382+
3383+
// Only mutable bytes can be resized in-place
3384+
assert(_PyBytes_IsMutable(v));
33583385

33593386
if ((size_t)newsize > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) {
33603387
PyErr_SetString(PyExc_OverflowError,
@@ -3385,6 +3412,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize)
33853412
Py_SET_SIZE(sv, newsize);
33863413
sv->ob_sval[newsize] = '\0';
33873414
set_ob_shash(sv, -1); /* invalidate cached hash value */
3415+
assert(_PyBytes_IsMutable(*pv));
33883416
return 0;
33893417
}
33903418

@@ -3647,6 +3675,7 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t size, int resize)
36473675
assert(writer->obj != NULL);
36483676
return -1;
36493677
}
3678+
assert(_PyBytes_IsMutable(writer->obj));
36503679
}
36513680
assert(writer->obj != NULL);
36523681
}
@@ -3673,6 +3702,7 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t size, int resize)
36733702
writer->small_buffer,
36743703
sizeof(writer->small_buffer));
36753704
}
3705+
assert(_PyBytes_IsMutable(writer->obj));
36763706
}
36773707

36783708
#ifdef Py_DEBUG

0 commit comments

Comments
 (0)