Skip to content

Commit d0441c4

Browse files
committed
gh-157242: Leave bytearray unchanged if resize() fails
If bytearray.resize() or bytearray.take_bytes() fails, leave the bytearray unchanged. If PyBytesWriter_Resize() fails, leave the writer unchanged. Add a new internal _PyBytes_ResizeKeepOnError() function similar to _PyBytes_Resize() but leaves the bytes object unchanged on error.
1 parent 9398655 commit d0441c4

5 files changed

Lines changed: 98 additions & 39 deletions

File tree

Include/internal/pycore_bytesobject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ PyAPI_FUNC(PyObject *) _PyBytes_Repeat(PyObject *self, Py_ssize_t n);
7575
*/
7676
#define _PyBytesObject_SIZE (offsetof(PyBytesObject, ob_sval) + 1)
7777

78+
extern int _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize);
79+
7880
/* --- PyBytesWriter ------------------------------------------------------ */
7981

8082
struct PyBytesWriter {

Lib/test/test_bytes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,21 @@ def test_resize(self):
15551555
self.assertRaises(MemoryError, bytearray().resize, sys.maxsize)
15561556
self.assertRaises(MemoryError, bytearray(1000).resize, sys.maxsize)
15571557

1558+
def test_resize_error(self):
1559+
# gh-157242: If bytearray.resize() fails (memory allocation failure),
1560+
# the bytearray must be left unchanged.
1561+
_testcapi = import_helper.import_module('_testcapi')
1562+
1563+
data = b'some data'
1564+
ba = bytearray(data)
1565+
try:
1566+
with self.assertRaises(MemoryError):
1567+
_testcapi.set_nomemory(0)
1568+
ba.resize(1024)
1569+
finally:
1570+
_testcapi.remove_mem_hooks()
1571+
self.assertEqual(ba, data)
1572+
15581573
def test_take_bytes(self):
15591574
ba = bytearray(b'ab')
15601575
self.assertEqual(ba.take_bytes(), b'ab')

Lib/test/test_capi/test_bytes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,21 @@ def test_resize(self):
373373
writer.resize(len(b'number=123456'), b'456')
374374
self.assertEqual(writer.finish(), self.result_type(b'number=123456'))
375375

376+
def test_resize_error(self):
377+
small_buffer = _testcapi.PyBytesWriter_small_buffer
378+
init = b'x' * (small_buffer * 2)
379+
writer = self.create_writer(len(init), init)
380+
size = len(init) + 100
381+
try:
382+
with self.assertRaises(MemoryError):
383+
_testcapi.set_nomemory(0)
384+
writer.resize(size, b'')
385+
finally:
386+
_testcapi.remove_mem_hooks()
387+
suffix = b'still working'
388+
writer.write_bytes(suffix, -1)
389+
self.assertEqual(writer.finish(), self.result_type(init + suffix))
390+
376391
def test_format_i(self):
377392
# Test PyBytesWriter_Format()
378393
writer = self.create_writer()

Objects/bytearrayobject.c

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -288,18 +288,16 @@ bytearray_resize_lock_held(PyObject *self, Py_ssize_t requested_size)
288288
Py_MIN(requested_size, Py_SIZE(self)));
289289
}
290290

291-
int ret = _PyBytes_Resize(&obj->ob_bytes_object, alloc);
292-
if (ret == -1) {
293-
obj->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
294-
size = alloc = 0;
291+
if (_PyBytes_ResizeKeepOnError(&obj->ob_bytes_object, alloc) < 0) {
292+
return -1;
295293
}
294+
296295
bytearray_reinit_from_bytes(obj, size, alloc);
297296
if (alloc != size) {
298297
/* Add mid-buffer null; end provided by bytes. */
299298
obj->ob_bytes[size] = '\0';
300299
}
301-
302-
return ret;
300+
return 0;
303301
}
304302

305303
int
@@ -1632,10 +1630,7 @@ bytearray_take_bytes_impl(PyByteArrayObject *self, PyObject *n)
16321630
self->ob_start = self->ob_bytes;
16331631
}
16341632

1635-
if (_PyBytes_Resize(&self->ob_bytes_object, to_take) == -1) {
1636-
assert(self->ob_bytes_object == NULL);
1637-
self->ob_bytes_object = Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
1638-
bytearray_reinit_from_bytes(self, 0, 0);
1633+
if (_PyBytes_ResizeKeepOnError(&self->ob_bytes_object, to_take) == -1) {
16391634
Py_DECREF(remaining);
16401635
return NULL;
16411636
}

Objects/bytesobject.c

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3344,68 +3344,99 @@ PyBytes_ConcatAndDel(PyObject **pv, PyObject *w)
33443344
does *not* include that), and a trailing \0 byte is stored.
33453345
*/
33463346

3347-
int
3348-
_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
3347+
static int
3348+
bytes_resize(PyObject **pv, Py_ssize_t newsize, int clear_obj_on_error)
33493349
{
3350-
PyObject *v;
3351-
PyBytesObject *sv;
3352-
v = *pv;
3350+
PyObject *v = *pv;
3351+
PyObject *result;
3352+
33533353
if (!PyBytes_Check(v) || newsize < 0) {
3354-
*pv = 0;
3355-
Py_DECREF(v);
33563354
PyErr_BadInternalCall();
3357-
return -1;
3355+
goto error;
33583356
}
3357+
33593358
Py_ssize_t oldsize = PyBytes_GET_SIZE(v);
33603359
if (oldsize == newsize) {
33613360
/* return early if newsize equals to v->ob_size */
33623361
return 0;
33633362
}
3363+
33643364
if (oldsize == 0) {
3365-
*pv = _PyBytes_FromSize(newsize, 0);
3365+
result = _PyBytes_FromSize(newsize, 0);
3366+
if (result == NULL) {
3367+
goto error;
3368+
}
3369+
*pv = result;
33663370
Py_DECREF(v);
3367-
return (*pv == NULL) ? -1 : 0;
3371+
return 0;
33683372
}
3373+
33693374
if (newsize == 0) {
3370-
*pv = bytes_get_empty();
3375+
*pv = bytes_get_empty(); // cannot fail
33713376
Py_DECREF(v);
33723377
return 0;
33733378
}
3379+
33743380
if (!_PyObject_IsUniquelyReferenced(v)) {
3375-
if (oldsize < newsize) {
3376-
*pv = _PyBytes_FromSize(newsize, 0);
3377-
if (*pv) {
3378-
memcpy(PyBytes_AS_STRING(*pv), PyBytes_AS_STRING(v), oldsize);
3379-
}
3380-
}
3381-
else {
3382-
*pv = PyBytes_FromStringAndSize(PyBytes_AS_STRING(v), newsize);
3381+
result = _PyBytes_FromSize(newsize, 0);
3382+
if (!result) {
3383+
goto error;
33833384
}
3385+
3386+
memcpy(PyBytes_AS_STRING(result), PyBytes_AS_STRING(v), Py_MIN(oldsize, newsize));
3387+
*pv = result;
33843388
Py_DECREF(v);
3385-
return (*pv == NULL) ? -1 : 0;
3389+
return 0;
33863390
}
3387-
assert(v != bytes_get_empty());
33883391

3392+
assert(v != bytes_get_empty());
33893393
#ifdef Py_TRACE_REFS
33903394
_Py_ForgetReference(v);
33913395
#endif
33923396
_PyReftracerTrack(v, PyRefTracer_DESTROY);
3393-
*pv = (PyObject *)
3394-
PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
3395-
if (*pv == NULL) {
3397+
result = (PyObject *)PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
3398+
if (result == NULL) {
3399+
if (clear_obj_on_error) {
3400+
*pv = NULL;
33963401
#ifdef Py_REF_DEBUG
3397-
_Py_DecRefTotal(_PyThreadState_GET());
3402+
_Py_DecRefTotal(_PyThreadState_GET());
33983403
#endif
3399-
PyObject_Free(v);
3404+
PyObject_Free(v);
3405+
}
34003406
PyErr_NoMemory();
34013407
return -1;
34023408
}
3403-
_Py_NewReferenceNoTotal(*pv);
3404-
sv = (PyBytesObject *) *pv;
3409+
3410+
v = result;
3411+
_Py_NewReferenceNoTotal(v);
3412+
PyBytesObject *sv = (PyBytesObject *)v;
34053413
Py_SET_SIZE(sv, newsize);
34063414
sv->ob_sval[newsize] = '\0';
34073415
set_ob_shash(sv, -1); /* invalidate cached hash value */
3416+
*pv = v;
34083417
return 0;
3418+
3419+
error:
3420+
if (clear_obj_on_error) {
3421+
*pv = NULL;
3422+
Py_DECREF(v);
3423+
}
3424+
return -1;
3425+
}
3426+
3427+
3428+
int
3429+
_PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
3430+
{
3431+
return bytes_resize(pv, newsize, 1);
3432+
}
3433+
3434+
3435+
// Similar to _PyBytes_Resize(), but leaves the object unchanged on error.
3436+
int
3437+
_PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize)
3438+
{
3439+
return bytes_resize(pv, newsize, 0);
34093440
}
34103441

34113442

@@ -3646,7 +3677,8 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t size, int resize)
36463677
}
36473678
}
36483679
else {
3649-
if (_PyBytes_Resize(&writer->obj, size)) {
3680+
if (_PyBytes_ResizeKeepOnError(&writer->obj, size)) {
3681+
assert(writer->obj != NULL);
36503682
return -1;
36513683
}
36523684
}

0 commit comments

Comments
 (0)