Skip to content

Commit d14775d

Browse files
authored
[3.9] bpo-41654: Fix deallocator of MemoryError to account for subclasses (GH-22020) (GH-22045)
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.. (cherry picked from commit 9b648a9) Co-authored-by: Pablo Galindo <[email protected]>
1 parent 4217b3c commit d14775d

File tree

3 files changed

+45
-2
lines changed

3 files changed

+45
-2
lines changed

Lib/test/test_exceptions.py

Lines changed: 31 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
@@ -1327,6 +1328,36 @@ def test_assert_shadowing(self):
13271328
del AssertionError
13281329
self.fail('Expected exception')
13291330

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

13311362
class ImportErrorTests(unittest.TestCase):
13321363

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
@@ -2282,8 +2282,12 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
22822282
{
22832283
PyBaseExceptionObject *self;
22842284

2285-
if (type != (PyTypeObject *) PyExc_MemoryError)
2285+
/* If this is a subclass of MemoryError, don't use the freelist
2286+
* and just return a fresh object */
2287+
if (type != (PyTypeObject *) PyExc_MemoryError) {
22862288
return BaseException_new(type, args, kwds);
2289+
}
2290+
22872291
if (memerrors_freelist == NULL)
22882292
return BaseException_new(type, args, kwds);
22892293
/* Fetch object from freelist and revive it */
@@ -2303,8 +2307,14 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
23032307
static void
23042308
MemoryError_dealloc(PyBaseExceptionObject *self)
23052309
{
2306-
_PyObject_GC_UNTRACK(self);
23072310
BaseException_clear(self);
2311+
2312+
if (!Py_IS_TYPE(self, (PyTypeObject *) PyExc_MemoryError)) {
2313+
return Py_TYPE(self)->tp_free((PyObject *)self);
2314+
}
2315+
2316+
_PyObject_GC_UNTRACK(self);
2317+
23082318
if (memerrors_numfree >= MEMERRORS_SAVE)
23092319
Py_TYPE(self)->tp_free((PyObject *)self);
23102320
else {

0 commit comments

Comments
 (0)