Issue #5437: A preallocated MemoryError instance should not hold traceback

data (including local variables caught in the stack trace) alive infinitely.
This commit is contained in:
Antoine Pitrou 2010-10-28 22:56:58 +00:00
parent 1842d0c4d8
commit 07e20ef50b
5 changed files with 130 additions and 34 deletions

View File

@ -156,7 +156,6 @@ PyAPI_DATA(PyObject *) PyExc_VMSError;
PyAPI_DATA(PyObject *) PyExc_BufferError; PyAPI_DATA(PyObject *) PyExc_BufferError;
PyAPI_DATA(PyObject *) PyExc_MemoryErrorInst;
PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst; PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst;
/* Predefined warning categories */ /* Predefined warning categories */

View File

@ -721,6 +721,45 @@ class ExceptionTests(unittest.TestCase):
self.assertEqual(error5.a, 1) self.assertEqual(error5.a, 1)
self.assertEqual(error5.__doc__, "") self.assertEqual(error5.__doc__, "")
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(): def test_main():
run_unittest(ExceptionTests) run_unittest(ExceptionTests)

View File

@ -10,6 +10,9 @@ What's New in Python 3.2 Beta 1?
Core and Builtins 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 #10186: Fix the SyntaxError caret when the offset is equal to the length - Issue #10186: Fix the SyntaxError caret when the offset is equal to the length
of the offending line. of the offending line.

View File

@ -1777,7 +1777,91 @@ SimpleExtendsException(PyExc_Exception, ReferenceError,
/* /*
* MemoryError extends Exception * 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 * BufferError extends Exception
@ -1869,11 +1953,6 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning,
/* 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. /* Pre-computed RuntimeError instance for when recursion depth is reached.
Meant to be used when normalizing the exception for exceeding the recursion Meant to be used when normalizing the exception for exceeding the recursion
depth will cause its own infinite recursion. depth will cause its own infinite recursion.
@ -2012,9 +2091,7 @@ _PyExc_Init(void)
POST_INIT(BytesWarning) POST_INIT(BytesWarning)
POST_INIT(ResourceWarning) POST_INIT(ResourceWarning)
PyExc_MemoryErrorInst = BaseException_new(&_PyExc_MemoryError, NULL, NULL); preallocate_memerrors();
if (!PyExc_MemoryErrorInst)
Py_FatalError("Cannot pre-allocate MemoryError instance");
PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RuntimeError, NULL, NULL); PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RuntimeError, NULL, NULL);
if (!PyExc_RecursionErrorInst) if (!PyExc_RecursionErrorInst)
@ -2045,6 +2122,6 @@ _PyExc_Init(void)
void void
_PyExc_Fini(void) _PyExc_Fini(void)
{ {
Py_CLEAR(PyExc_MemoryErrorInst);
Py_CLEAR(PyExc_RecursionErrorInst); Py_CLEAR(PyExc_RecursionErrorInst);
free_preallocated_memerrors();
} }

View File

@ -333,29 +333,7 @@ PyErr_BadArgument(void)
PyObject * PyObject *
PyErr_NoMemory(void) PyErr_NoMemory(void)
{ {
if (PyErr_ExceptionMatches(PyExc_MemoryError)) PyErr_SetNone(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);
return NULL; return NULL;
} }