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.
........
This commit is contained in:
Antoine Pitrou 2010-10-28 23:06:57 +00:00
parent d7a3ab962b
commit 98e2b45297
5 changed files with 132 additions and 35 deletions

View File

@ -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 */

View File

@ -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)

View File

@ -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

View File

@ -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();
}

View File

@ -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;
}