From 98e2b452975f13b13052aeb9d166ed8a3ef45063 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Thu, 28 Oct 2010 23:06:57 +0000 Subject: [PATCH] Merged revisions 85896 via svnmerge from svn+ssh://pythondev@svn.python.org/python/branches/py3k ........ r85896 | antoine.pitrou | 2010-10-29 00:56:58 +0200 (ven., 29 oct. 2010) | 4 lines Issue #5437: A preallocated MemoryError instance should not hold traceback data (including local variables caught in the stack trace) alive infinitely. ........ --- Include/pyerrors.h | 1 - Lib/test/test_exceptions.py | 40 +++++++++++++++ Misc/NEWS | 3 ++ Objects/exceptions.c | 99 ++++++++++++++++++++++++++++++++----- Python/errors.c | 24 +-------- 5 files changed, 132 insertions(+), 35 deletions(-) diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 66a70307b7..3c3afab3ff 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -153,7 +153,6 @@ PyAPI_DATA(PyObject *) PyExc_VMSError; PyAPI_DATA(PyObject *) PyExc_BufferError; -PyAPI_DATA(PyObject *) PyExc_MemoryErrorInst; PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst; /* Predefined warning categories */ diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index b10835d9a9..41e3d44620 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -669,6 +669,46 @@ class ExceptionTests(unittest.TestCase): tb2 = raiseMemError() self.assertEqual(tb1, tb2) + def test_memory_error_cleanup(self): + # Issue #5437: preallocated MemoryError instances should not keep + # traceback objects alive. + from _testcapi import raise_memoryerror + class C: + pass + wr = None + def inner(): + nonlocal wr + c = C() + wr = weakref.ref(c) + raise_memoryerror() + # We cannot use assertRaises since it manually deletes the traceback + try: + inner() + except MemoryError as e: + self.assertNotEqual(wr(), None) + else: + self.fail("MemoryError not raised") + self.assertEqual(wr(), None) + + def test_recursion_error_cleanup(self): + # Same test as above, but with "recursion exceeded" errors + class C: + pass + wr = None + def inner(): + nonlocal wr + c = C() + wr = weakref.ref(c) + inner() + # We cannot use assertRaises since it manually deletes the traceback + try: + inner() + except RuntimeError as e: + self.assertNotEqual(wr(), None) + else: + self.fail("RuntimeError not raised") + self.assertEqual(wr(), None) + def test_main(): run_unittest(ExceptionTests) diff --git a/Misc/NEWS b/Misc/NEWS index 0893c7ff15..fb6814fb67 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.1.3? Core and Builtins ----------------- +- Issue #5437: A preallocated MemoryError instance should not hold traceback + data (including local variables caught in the stack trace) alive infinitely. + - Issue #10077: Fix logging of site module errors at startup. - Issue #10186: Fix the SyntaxError caret when the offset is equal to the length diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 841533e721..1531867d8d 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -1763,7 +1763,91 @@ SimpleExtendsException(PyExc_Exception, ReferenceError, /* * MemoryError extends Exception */ -SimpleExtendsException(PyExc_Exception, MemoryError, "Out of memory."); + +#define MEMERRORS_SAVE 16 +static PyBaseExceptionObject *memerrors_freelist = NULL; +static int memerrors_numfree = 0; + +static PyObject * +MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyBaseExceptionObject *self; + + 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 */ + self = memerrors_freelist; + self->args = PyTuple_New(0); + /* This shouldn't happen since the empty tuple is persistent */ + if (self->args == NULL) + return NULL; + memerrors_freelist = (PyBaseExceptionObject *) self->dict; + memerrors_numfree--; + self->dict = NULL; + _Py_NewReference((PyObject *)self); + _PyObject_GC_TRACK(self); + return (PyObject *)self; +} + +static void +MemoryError_dealloc(PyBaseExceptionObject *self) +{ + _PyObject_GC_UNTRACK(self); + BaseException_clear(self); + if (memerrors_numfree >= MEMERRORS_SAVE) + Py_TYPE(self)->tp_free((PyObject *)self); + else { + self->dict = (PyObject *) memerrors_freelist; + memerrors_freelist = self; + memerrors_numfree++; + } +} + +static void +preallocate_memerrors(void) +{ + /* We create enough MemoryErrors and then decref them, which will fill + up the freelist. */ + int i; + PyObject *errors[MEMERRORS_SAVE]; + for (i = 0; i < MEMERRORS_SAVE; i++) { + errors[i] = MemoryError_new((PyTypeObject *) PyExc_MemoryError, + NULL, NULL); + if (!errors[i]) + Py_FatalError("Could not preallocate MemoryError object"); + } + for (i = 0; i < MEMERRORS_SAVE; i++) { + Py_DECREF(errors[i]); + } +} + +static void +free_preallocated_memerrors(void) +{ + while (memerrors_freelist != NULL) { + PyObject *self = (PyObject *) memerrors_freelist; + memerrors_freelist = (PyBaseExceptionObject *) memerrors_freelist->dict; + Py_TYPE(self)->tp_free((PyObject *)self); + } +} + + +static PyTypeObject _PyExc_MemoryError = { + PyVarObject_HEAD_INIT(NULL, 0) + "MemoryError", + sizeof(PyBaseExceptionObject), + 0, (destructor)MemoryError_dealloc, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, + PyDoc_STR("Out of memory."), (traverseproc)BaseException_traverse, + (inquiry)BaseException_clear, 0, 0, 0, 0, 0, 0, 0, &_PyExc_Exception, + 0, 0, 0, offsetof(PyBaseExceptionObject, dict), + (initproc)BaseException_init, 0, MemoryError_new +}; +PyObject *PyExc_MemoryError = (PyObject *) &_PyExc_MemoryError; + /* * BufferError extends Exception @@ -1847,11 +1931,6 @@ SimpleExtendsException(PyExc_Warning, BytesWarning, -/* Pre-computed MemoryError instance. Best to create this as early as - * possible and not wait until a MemoryError is actually raised! - */ -PyObject *PyExc_MemoryErrorInst=NULL; - /* Pre-computed RuntimeError instance for when recursion depth is reached. Meant to be used when normalizing the exception for exceeding the recursion depth will cause its own infinite recursion. @@ -1988,9 +2067,7 @@ _PyExc_Init(void) POST_INIT(UnicodeWarning) POST_INIT(BytesWarning) - PyExc_MemoryErrorInst = BaseException_new(&_PyExc_MemoryError, NULL, NULL); - if (!PyExc_MemoryErrorInst) - Py_FatalError("Cannot pre-allocate MemoryError instance"); + preallocate_memerrors(); PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RuntimeError, NULL, NULL); if (!PyExc_RecursionErrorInst) @@ -2021,6 +2098,6 @@ _PyExc_Init(void) void _PyExc_Fini(void) { - Py_XDECREF(PyExc_MemoryErrorInst); - PyExc_MemoryErrorInst = NULL; + Py_CLEAR(PyExc_RecursionErrorInst); + free_preallocated_memerrors(); } diff --git a/Python/errors.c b/Python/errors.c index 4584205260..db0baf1c24 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -328,29 +328,7 @@ PyErr_BadArgument(void) PyObject * PyErr_NoMemory(void) { - if (PyErr_ExceptionMatches(PyExc_MemoryError)) - /* already current */ - return NULL; - - /* raise the pre-allocated instance if it still exists */ - if (PyExc_MemoryErrorInst) - { - /* Clear the previous traceback, otherwise it will be appended - * to the current one. - * - * The following statement is not likely to raise any error; - * if it does, we simply discard it. - */ - PyException_SetTraceback(PyExc_MemoryErrorInst, Py_None); - - PyErr_SetObject(PyExc_MemoryError, PyExc_MemoryErrorInst); - } - else - /* this will probably fail since there's no memory and hee, - hee, we have to instantiate this class - */ - PyErr_SetNone(PyExc_MemoryError); - + PyErr_SetNone(PyExc_MemoryError); return NULL; } -- 2.40.0