summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2010-10-28 22:56:58 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2010-10-28 22:56:58 (GMT)
commit07e20ef50b52d2359213bc5d91f809a76cfd1524 (patch)
tree74803fa7efe3959104b0d9760e4cff172e5c3919
parent1842d0c4d88fffb5bea53b410acb77796b66bc8b (diff)
downloadcpython-07e20ef50b52d2359213bc5d91f809a76cfd1524.zip
cpython-07e20ef50b52d2359213bc5d91f809a76cfd1524.tar.gz
cpython-07e20ef50b52d2359213bc5d91f809a76cfd1524.tar.bz2
Issue #5437: A preallocated MemoryError instance should not hold traceback
data (including local variables caught in the stack trace) alive infinitely.
-rw-r--r--Include/pyerrors.h1
-rw-r--r--Lib/test/test_exceptions.py39
-rw-r--r--Misc/NEWS3
-rw-r--r--Objects/exceptions.c97
-rw-r--r--Python/errors.c24
5 files changed, 130 insertions, 34 deletions
diff --git a/Include/pyerrors.h b/Include/pyerrors.h
index 25843c3..f717afa 100644
--- a/Include/pyerrors.h
+++ b/Include/pyerrors.h
@@ -156,7 +156,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 e2824b2..2cbd972 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -721,6 +721,45 @@ class ExceptionTests(unittest.TestCase):
self.assertEqual(error5.a, 1)
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():
run_unittest(ExceptionTests)
diff --git a/Misc/NEWS b/Misc/NEWS
index 9bbeec2..e194d44 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ What's New in Python 3.2 Beta 1?
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
of the offending line.
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index 5715b26..0106ba3 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -1777,7 +1777,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
@@ -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.
Meant to be used when normalizing the exception for exceeding the recursion
depth will cause its own infinite recursion.
@@ -2012,9 +2091,7 @@ _PyExc_Init(void)
POST_INIT(BytesWarning)
POST_INIT(ResourceWarning)
- 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)
@@ -2045,6 +2122,6 @@ _PyExc_Init(void)
void
_PyExc_Fini(void)
{
- Py_CLEAR(PyExc_MemoryErrorInst);
Py_CLEAR(PyExc_RecursionErrorInst);
+ free_preallocated_memerrors();
}
diff --git a/Python/errors.c b/Python/errors.c
index 0490614..d5a6fae 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -333,29 +333,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;
}