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:
parent
1842d0c4d8
commit
07e20ef50b
|
@ -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 */
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue