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

https://github.com/python/cpython/commit/77f4000ae0d43a2685face80e7f14d4aba0... commit: 77f4000ae0d43a2685face80e7f14d4aba053973 branch: 3.8 author: Pablo Galindo <Pablogsal@gmail.com> committer: GitHub <noreply@github.com> date: 2020-09-01T21:40:48+01:00 summary: [3.8] [3.9] bpo-41654: Fix deallocator of MemoryError to account for subclasses (GH-22020) (GH-22046) 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 9b648a95ccb4c3b14f1e87158f5c9f5dbb2f62c0) Co-authored-by: Pablo Galindo <Pablogsal@gmail.com>. (cherry picked from commit 87e91ae2e5f81e096c32839f211c68a749a4435a) Co-authored-by: Pablo Galindo <Pablogsal@gmail.com> files: A Misc/NEWS.d/next/Core and Builtins/2020-08-30-20-38-33.bpo-41654.HtnhAM.rst M Lib/test/test_exceptions.py M Objects/exceptions.c diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 3a32253157369..457b46e01ce31 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -1,6 +1,7 @@ # Python test set -- part 5, built-in exceptions import copy +import gc import os import sys import unittest @@ -1297,6 +1298,35 @@ def g(): next(i) next(i) + 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): diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-08-30-20-38-33.bpo-41654.HtnhAM.rst b/Misc/NEWS.d/next/Core and Builtins/2020-08-30-20-38-33.bpo-41654.HtnhAM.rst new file mode 100644 index 0000000000000..e05c3133e1262 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-08-30-20-38-33.bpo-41654.HtnhAM.rst @@ -0,0 +1,2 @@ +Fix a crash that occurred when destroying subclasses of +:class:`MemoryError`. Patch by Pablo Galindo. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 38723d93653c3..966983810cd1e 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2268,8 +2268,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 */ @@ -2289,8 +2293,14 @@ MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static void MemoryError_dealloc(PyBaseExceptionObject *self) { - _PyObject_GC_UNTRACK(self); BaseException_clear(self); + + if (Py_TYPE(self) != 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 {
participants (1)
-
Pablo Galindo