Skip to content

Commit 7def4bb

Browse files
committed
bpo-41654: Fix deallocator of MemoryError to account for subclasses
When allocating MemoryError classes, there is some logic to use pre-allocated instances in a freelist only if the type that is being allocated is not a subclass of MemoryError. Unfortunately in the destructor this logic is not present so the freelist is altered even with subclasses of MemoryError.
1 parent 475a5fb commit 7def4bb

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

Lib/test/test_exceptions.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Python test set -- part 5, built-in exceptions
22

33
import copy
4+
import gc
45
import os
56
import sys
67
import unittest
@@ -1330,6 +1331,38 @@ def test_assert_shadowing(self):
13301331
del AssertionError
13311332
self.fail('Expected exception')
13321333

1334+
def test_memory_error_subclasses(self):
1335+
# MemoryError instances use a freelist of objects that are
1336+
# linked using the 'dict' attribute when they are
1337+
# inactive/dead. Subclasses of MemoryError should not
1338+
# participate in the freelist schema. This test creates a
1339+
# MemoryError object and keeps it alive (therefore
1340+
# advancing the freelist) and then it creates and destroys
1341+
# a subclass object. Finally, it checks that creating a
1342+
# new MemoryError succeeds, proving that the freelist is
1343+
# not corrupted. See bpo-41654 for more information.
1344+
1345+
class TestException(MemoryError):
1346+
pass
1347+
1348+
try:
1349+
raise MemoryError
1350+
except MemoryError as exc:
1351+
inst = exc
1352+
1353+
try:
1354+
raise TestException
1355+
except Exception:
1356+
pass
1357+
1358+
for _ in range(10):
1359+
try:
1360+
raise MemoryError
1361+
except MemoryError as exc:
1362+
pass
1363+
1364+
gc.collect()
1365+
13331366

13341367
class ImportErrorTests(unittest.TestCase):
13351368

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash that occurred when destroying subclasses of
2+
:class:`MemoryError`. Patch by Pablo Galindo.

Objects/exceptions.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2286,8 +2286,11 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
22862286
{
22872287
PyBaseExceptionObject *self;
22882288

2289-
if (type != (PyTypeObject *) PyExc_MemoryError)
2289+
/* If this is a subclass of MemoryError, don't use the freelist
2290+
* and just return a fresh object */
2291+
if (type != (PyTypeObject *) PyExc_MemoryError) {
22902292
return BaseException_new(type, args, kwds);
2293+
}
22912294

22922295
struct _Py_exc_state *state = get_exc_state();
22932296
if (state->memerrors_freelist == NULL) {
@@ -2313,9 +2316,16 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
23132316
static void
23142317
MemoryError_dealloc(PyBaseExceptionObject *self)
23152318
{
2316-
_PyObject_GC_UNTRACK(self);
23172319
BaseException_clear(self);
23182320

2321+
/* If this is a subclass of MemoryError, we don't need to
2322+
* do anything in the free-list*/
2323+
if (!Py_IS_TYPE(self, (PyTypeObject *) PyExc_MemoryError)) {
2324+
return Py_TYPE(self)->tp_free((PyObject *)self);
2325+
}
2326+
2327+
_PyObject_GC_UNTRACK(self);
2328+
23192329
struct _Py_exc_state *state = get_exc_state();
23202330
if (state->memerrors_numfree >= MEMERRORS_SAVE) {
23212331
Py_TYPE(self)->tp_free((PyObject *)self);

0 commit comments

Comments
 (0)