[Python-checkins] r85896 - in python/branches/py3k: Include/pyerrors.h Lib/test/test_exceptions.py Misc/NEWS Objects/exceptions.c Python/errors.c

antoine.pitrou python-checkins at python.org
Fri Oct 29 00:56:58 CEST 2010


Author: antoine.pitrou
Date: Fri Oct 29 00:56:58 2010
New Revision: 85896

Log:
Issue #5437: A preallocated MemoryError instance should not hold traceback
data (including local variables caught in the stack trace) alive infinitely.



Modified:
   python/branches/py3k/Include/pyerrors.h
   python/branches/py3k/Lib/test/test_exceptions.py
   python/branches/py3k/Misc/NEWS
   python/branches/py3k/Objects/exceptions.c
   python/branches/py3k/Python/errors.c

Modified: python/branches/py3k/Include/pyerrors.h
==============================================================================
--- python/branches/py3k/Include/pyerrors.h	(original)
+++ python/branches/py3k/Include/pyerrors.h	Fri Oct 29 00:56:58 2010
@@ -156,7 +156,6 @@
 
 PyAPI_DATA(PyObject *) PyExc_BufferError;
 
-PyAPI_DATA(PyObject *) PyExc_MemoryErrorInst;
 PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst;
 
 /* Predefined warning categories */

Modified: python/branches/py3k/Lib/test/test_exceptions.py
==============================================================================
--- python/branches/py3k/Lib/test/test_exceptions.py	(original)
+++ python/branches/py3k/Lib/test/test_exceptions.py	Fri Oct 29 00:56:58 2010
@@ -721,6 +721,45 @@
         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)

Modified: python/branches/py3k/Misc/NEWS
==============================================================================
--- python/branches/py3k/Misc/NEWS	(original)
+++ python/branches/py3k/Misc/NEWS	Fri Oct 29 00:56:58 2010
@@ -10,6 +10,9 @@
 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.
 

Modified: python/branches/py3k/Objects/exceptions.c
==============================================================================
--- python/branches/py3k/Objects/exceptions.c	(original)
+++ python/branches/py3k/Objects/exceptions.c	Fri Oct 29 00:56:58 2010
@@ -1777,7 +1777,91 @@
 /*
  *    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 @@
 
 
 
-/* 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 @@
     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 @@
 void
 _PyExc_Fini(void)
 {
-    Py_CLEAR(PyExc_MemoryErrorInst);
     Py_CLEAR(PyExc_RecursionErrorInst);
+    free_preallocated_memerrors();
 }

Modified: python/branches/py3k/Python/errors.c
==============================================================================
--- python/branches/py3k/Python/errors.c	(original)
+++ python/branches/py3k/Python/errors.c	Fri Oct 29 00:56:58 2010
@@ -333,29 +333,7 @@
 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;
 }
 


More information about the Python-checkins mailing list