Skip to content

Commit 125aa2a

Browse files
Apply Cody's suggestions
Co-authored-by: Cody Maloney <cmaloney@theoreticalchaos.com>
1 parent a67dbe9 commit 125aa2a

4 files changed

Lines changed: 26 additions & 11 deletions

File tree

‎Lib/test/test_bytes.py‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3063,5 +3063,18 @@ def resize_stress(ba):
30633063
with threading_helper.start_threads(threads):
30643064
pass
30653065

3066+
@threading_helper.reap_threads
3067+
@threading_helper.requires_working_threading()
3068+
def test_free_threading_bytearray_resize_other_thread(self):
3069+
# Shrinking a bytearray whose buffer another thread owns must not
3070+
# adopt the immortal single-byte bytes object a the buffer.
3071+
ba = bytearray(b'abc')
3072+
thread = threading.Thread(target=ba.resize, args=(1,))
3073+
with threading_helper.start_threads([thread]):
3074+
pass
3075+
ba[0] = ord('X')
3076+
self.assertEqual(ba, bytearray(b'X'))
3077+
self.assertEqual(ord(b'a'), ord('a'))
3078+
30663079
if __name__ == "__main__":
30673080
unittest.main()
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
Fix :meth:`bytearray.take_bytes` leaving the bytearray with a shared buffer
2-
when exactly one byte remained, so that writing to the bytearray modified
3-
the single-byte :class:`bytes` object with the same value.
1+
Fix :class:`bytearray` sharing its buffer with the single-byte :class:`bytes`
2+
object of the same value, so that writing to the bytearray modified that
3+
:class:`bytes` object. This happened with :meth:`bytearray.take_bytes` when
4+
exactly one byte remained, and on the free-threaded build when a bytearray was
5+
shrunk to one byte from another thread.

‎Objects/bytearrayobject.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ _getbytevalue(PyObject* arg, int *value)
4646
static void
4747
bytearray_reinit_from_bytes(PyByteArrayObject *self, Py_ssize_t size,
4848
Py_ssize_t alloc) {
49+
/* Only the empty bytes may be immortal. */
50+
assert((alloc == 0) == _Py_IsImmortal(self->ob_bytes_object));
4951
self->ob_bytes = self->ob_start = PyBytes_AS_STRING(self->ob_bytes_object);
5052
Py_SET_SIZE(self, size);
5153
FT_ATOMIC_STORE_SSIZE_RELAXED(self->ob_alloc, alloc);

‎Objects/bytesobject.c‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3368,14 +3368,12 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
33683368
return 0;
33693369
}
33703370
if (!_PyObject_IsUniquelyReferenced(v)) {
3371-
if (oldsize < newsize) {
3372-
*pv = _PyBytes_FromSize(newsize, 0);
3373-
if (*pv) {
3374-
memcpy(PyBytes_AS_STRING(*pv), PyBytes_AS_STRING(v), oldsize);
3375-
}
3376-
}
3377-
else {
3378-
*pv = PyBytes_FromStringAndSize(PyBytes_AS_STRING(v), newsize);
3371+
// Allocate and then copy so we don't get a shared immortal
3372+
// one-character singleton!
3373+
*pv = _PyBytes_FromSize(newsize, 0);
3374+
if (*pv) {
3375+
memcpy(PyBytes_AS_STRING(*pv), PyBytes_AS_STRING(v),
3376+
Py_MIN(oldsize, newsize));
33793377
}
33803378
Py_DECREF(v);
33813379
return (*pv == NULL) ? -1 : 0;

0 commit comments

Comments
 (0)