Skip to content

Commit 90e5bd7

Browse files
[3.12] gh-77894: Fix a crash when the GC breaks a loop containing a memoryview (GH-123898) (GH-123937)
Now a memoryview object can only be cleared if there are no buffers that refer it. (cherry picked from commit a1dbf2e)
1 parent c6ae90a commit 90e5bd7

File tree

4 files changed

+57
-33
lines changed

4 files changed

+57
-33
lines changed

Lib/test/pickletester.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,12 +2019,10 @@ def test_picklebuffer_error(self):
20192019
'PickleBuffer can only be pickled with protocol >= 5')
20202020

20212021
def test_non_continuous_buffer(self):
2022-
if self.pickler is pickle._Pickler:
2023-
self.skipTest('CRASHES (see gh-122306)')
20242022
for proto in protocols[5:]:
20252023
with self.subTest(proto=proto):
20262024
pb = pickle.PickleBuffer(memoryview(b"foobar")[::2])
2027-
with self.assertRaises(pickle.PicklingError):
2025+
with self.assertRaises((pickle.PicklingError, BufferError)):
20282026
self.dumps(pb, proto)
20292027

20302028
def test_buffer_callback_error(self):

Lib/test/test_memoryview.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
from test.support import import_helper
1919

2020

21+
class MyObject:
22+
pass
23+
24+
2125
class AbstractMemoryTests:
2226
source_bytes = b"abcdef"
2327

@@ -228,8 +232,6 @@ def __init__(self, base):
228232
self.m = memoryview(base)
229233
class MySource(tp):
230234
pass
231-
class MyObject:
232-
pass
233235

234236
# Create a reference cycle through a memoryview object.
235237
# This exercises mbuf_clear().
@@ -656,5 +658,26 @@ def __bool__(self):
656658
m[0] = MyBool()
657659
self.assertEqual(ba[:8], b'\0'*8)
658660

661+
def test_buffer_reference_loop(self):
662+
m = memoryview(b'abc').__buffer__(0)
663+
o = MyObject()
664+
o.m = m
665+
o.o = o
666+
wr = weakref.ref(o)
667+
del m, o
668+
gc.collect()
669+
self.assertIsNone(wr())
670+
671+
def test_picklebuffer_reference_loop(self):
672+
pb = pickle.PickleBuffer(memoryview(b'abc'))
673+
o = MyObject()
674+
o.pb = pb
675+
o.o = o
676+
wr = weakref.ref(o)
677+
del pb, o
678+
gc.collect()
679+
self.assertIsNone(wr())
680+
681+
659682
if __name__ == "__main__":
660683
unittest.main()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix possible crash in the garbage collector when it tries to break a
2+
reference loop containing a :class:`memoryview` object. Now a
3+
:class:`!memoryview` object can only be cleared if there are no buffers that
4+
refer it.

Objects/memoryobject.c

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ mbuf_release(_PyManagedBufferObject *self)
108108
if (self->flags&_Py_MANAGED_BUFFER_RELEASED)
109109
return;
110110

111-
/* NOTE: at this point self->exports can still be > 0 if this function
112-
is called from mbuf_clear() to break up a reference cycle. */
113111
self->flags |= _Py_MANAGED_BUFFER_RELEASED;
114112

115113
/* PyBuffer_Release() decrements master->obj and sets it to NULL. */
@@ -1092,32 +1090,19 @@ PyBuffer_ToContiguous(void *buf, const Py_buffer *src, Py_ssize_t len, char orde
10921090
/* Inform the managed buffer that this particular memoryview will not access
10931091
the underlying buffer again. If no other memoryviews are registered with
10941092
the managed buffer, the underlying buffer is released instantly and
1095-
marked as inaccessible for both the memoryview and the managed buffer.
1096-
1097-
This function fails if the memoryview itself has exported buffers. */
1098-
static int
1093+
marked as inaccessible for both the memoryview and the managed buffer. */
1094+
static void
10991095
_memory_release(PyMemoryViewObject *self)
11001096
{
1097+
assert(self->exports == 0);
11011098
if (self->flags & _Py_MEMORYVIEW_RELEASED)
1102-
return 0;
1099+
return;
11031100

1104-
if (self->exports == 0) {
1105-
self->flags |= _Py_MEMORYVIEW_RELEASED;
1106-
assert(self->mbuf->exports > 0);
1107-
if (--self->mbuf->exports == 0)
1108-
mbuf_release(self->mbuf);
1109-
return 0;
1101+
self->flags |= _Py_MEMORYVIEW_RELEASED;
1102+
assert(self->mbuf->exports > 0);
1103+
if (--self->mbuf->exports == 0) {
1104+
mbuf_release(self->mbuf);
11101105
}
1111-
if (self->exports > 0) {
1112-
PyErr_Format(PyExc_BufferError,
1113-
"memoryview has %zd exported buffer%s", self->exports,
1114-
self->exports==1 ? "" : "s");
1115-
return -1;
1116-
}
1117-
1118-
PyErr_SetString(PyExc_SystemError,
1119-
"_memory_release(): negative export count");
1120-
return -1;
11211106
}
11221107

11231108
/*[clinic input]
@@ -1130,17 +1115,29 @@ static PyObject *
11301115
memoryview_release_impl(PyMemoryViewObject *self)
11311116
/*[clinic end generated code: output=d0b7e3ba95b7fcb9 input=bc71d1d51f4a52f0]*/
11321117
{
1133-
if (_memory_release(self) < 0)
1118+
if (self->exports == 0) {
1119+
_memory_release(self);
1120+
Py_RETURN_NONE;
1121+
}
1122+
1123+
if (self->exports > 0) {
1124+
PyErr_Format(PyExc_BufferError,
1125+
"memoryview has %zd exported buffer%s", self->exports,
1126+
self->exports==1 ? "" : "s");
11341127
return NULL;
1135-
Py_RETURN_NONE;
1128+
}
1129+
1130+
PyErr_SetString(PyExc_SystemError,
1131+
"memoryview: negative export count");
1132+
return NULL;
11361133
}
11371134

11381135
static void
11391136
memory_dealloc(PyMemoryViewObject *self)
11401137
{
11411138
assert(self->exports == 0);
11421139
_PyObject_GC_UNTRACK(self);
1143-
(void)_memory_release(self);
1140+
_memory_release(self);
11441141
Py_CLEAR(self->mbuf);
11451142
if (self->weakreflist != NULL)
11461143
PyObject_ClearWeakRefs((PyObject *) self);
@@ -1157,8 +1154,10 @@ memory_traverse(PyMemoryViewObject *self, visitproc visit, void *arg)
11571154
static int
11581155
memory_clear(PyMemoryViewObject *self)
11591156
{
1160-
(void)_memory_release(self);
1161-
Py_CLEAR(self->mbuf);
1157+
if (self->exports == 0) {
1158+
_memory_release(self);
1159+
Py_CLEAR(self->mbuf);
1160+
}
11621161
return 0;
11631162
}
11641163

0 commit comments

Comments
 (0)