[3.9] bpo-41654: Fix deallocator of MemoryError to account for subclasses (GH-22020)

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 9b648a95cc)

Co-authored-by: Pablo Galindo <Pablogsal@gmail.com>
This commit is contained in:
Pablo Galindo 2020-09-01 19:39:46 +01:00 committed by Pablo Galindo
parent 4217b3c128
commit 87e91ae2e5
No known key found for this signature in database
GPG Key ID: FFE87404168BD847
3 changed files with 45 additions and 2 deletions

View File

@ -1,6 +1,7 @@
# Python test set -- part 5, built-in exceptions
import copy
import gc
import os
import sys
import unittest
@ -1327,6 +1328,36 @@ class ExceptionTests(unittest.TestCase):
del AssertionError
self.fail('Expected exception')
def test_memory_error_subclasses(self):
# bpo-41654: MemoryError instances use a freelist of objects that are
# linked using the 'dict' attribute when they are inactive/dead.
# Subclasses of MemoryError should not participate in the freelist
# schema. This test creates a MemoryError object and keeps it alive
# (therefore advancing the freelist) and then it creates and destroys a
# subclass object. Finally, it checks that creating a new MemoryError
# succeeds, proving that the freelist is not corrupted.
class TestException(MemoryError):
pass
try:
raise MemoryError
except MemoryError as exc:
inst = exc
try:
raise TestException
except Exception:
pass
for _ in range(10):
try:
raise MemoryError
except MemoryError as exc:
pass
gc_collect()
class ImportErrorTests(unittest.TestCase):

View File

@ -0,0 +1,2 @@
Fix a crash that occurred when destroying subclasses of
:class:`MemoryError`. Patch by Pablo Galindo.

View File

@ -2282,8 +2282,12 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyBaseExceptionObject *self;
if (type != (PyTypeObject *) PyExc_MemoryError)
/* If this is a subclass of MemoryError, don't use the freelist
* and just return a fresh object */
if (type != (PyTypeObject *) PyExc_MemoryError) {
return BaseException_new(type, args, kwds);
}
if (memerrors_freelist == NULL)
return BaseException_new(type, args, kwds);
/* Fetch object from freelist and revive it */
@ -2303,8 +2307,14 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
static void
MemoryError_dealloc(PyBaseExceptionObject *self)
{
_PyObject_GC_UNTRACK(self);
BaseException_clear(self);
if (!Py_IS_TYPE(self, (PyTypeObject *) PyExc_MemoryError)) {
return Py_TYPE(self)->tp_free((PyObject *)self);
}
_PyObject_GC_UNTRACK(self);
if (memerrors_numfree >= MEMERRORS_SAVE)
Py_TYPE(self)->tp_free((PyObject *)self);
else {