Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 07e20ef

Browse files
committed
Issue #5437: A preallocated MemoryError instance should not hold traceback
data (including local variables caught in the stack trace) alive infinitely.
1 parent 1842d0c commit 07e20ef

5 files changed

Lines changed: 130 additions & 34 deletions

File tree

Include/pyerrors.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ PyAPI_DATA(PyObject *) PyExc_VMSError;
156156

157157
PyAPI_DATA(PyObject *) PyExc_BufferError;
158158

159-
PyAPI_DATA(PyObject *) PyExc_MemoryErrorInst;
160159
PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst;
161160

162161
/* Predefined warning categories */

Lib/test/test_exceptions.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,45 @@ class C(object):
721721
self.assertEqual(error5.a, 1)
722722
self.assertEqual(error5.__doc__, "")
723723

724+
def test_memory_error_cleanup(self):
725+
# Issue #5437: preallocated MemoryError instances should not keep
726+
# traceback objects alive.
727+
from _testcapi import raise_memoryerror
728+
class C:
729+
pass
730+
wr = None
731+
def inner():
732+
nonlocal wr
733+
c = C()
734+
wr = weakref.ref(c)
735+
raise_memoryerror()
736+
# We cannot use assertRaises since it manually deletes the traceback
737+
try:
738+
inner()
739+
except MemoryError as e:
740+
self.assertNotEqual(wr(), None)
741+
else:
742+
self.fail("MemoryError not raised")
743+
self.assertEqual(wr(), None)
744+
745+
def test_recursion_error_cleanup(self):
746+
# Same test as above, but with "recursion exceeded" errors
747+
class C:
748+
pass
749+
wr = None
750+
def inner():
751+
nonlocal wr
752+
c = C()
753+
wr = weakref.ref(c)
754+
inner()
755+
# We cannot use assertRaises since it manually deletes the traceback
756+
try:
757+
inner()
758+
except RuntimeError as e:
759+
self.assertNotEqual(wr(), None)
760+
else:
761+
self.fail("RuntimeError not raised")
762+
self.assertEqual(wr(), None)
724763

725764
def test_main():
726765
run_unittest(ExceptionTests)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.2 Beta 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #5437: A preallocated MemoryError instance should not hold traceback
14+
data (including local variables caught in the stack trace) alive infinitely.
15+
1316
- Issue #10186: Fix the SyntaxError caret when the offset is equal to the length
1417
of the offending line.
1518

Objects/exceptions.c

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,7 +1777,91 @@ SimpleExtendsException(PyExc_Exception, ReferenceError,
17771777
/*
17781778
* MemoryError extends Exception
17791779
*/
1780-
SimpleExtendsException(PyExc_Exception, MemoryError, "Out of memory.");
1780+
1781+
#define MEMERRORS_SAVE 16
1782+
static PyBaseExceptionObject *memerrors_freelist = NULL;
1783+
static int memerrors_numfree = 0;
1784+
1785+
static PyObject *
1786+
MemoryError_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1787+
{
1788+
PyBaseExceptionObject *self;
1789+
1790+
if (type != (PyTypeObject *) PyExc_MemoryError)
1791+
return BaseException_new(type, args, kwds);
1792+
if (memerrors_freelist == NULL)
1793+
return BaseException_new(type, args, kwds);
1794+
/* Fetch object from freelist and revive it */
1795+
self = memerrors_freelist;
1796+
self->args = PyTuple_New(0);
1797+
/* This shouldn't happen since the empty tuple is persistent */
1798+
if (self->args == NULL)
1799+
return NULL;
1800+
memerrors_freelist = (PyBaseExceptionObject *) self->dict;
1801+
memerrors_numfree--;
1802+
self->dict = NULL;
1803+
_Py_NewReference((PyObject *)self);
1804+
_PyObject_GC_TRACK(self);
1805+
return (PyObject *)self;
1806+
}
1807+
1808+
static void
1809+
MemoryError_dealloc(PyBaseExceptionObject *self)
1810+
{
1811+
_PyObject_GC_UNTRACK(self);
1812+
BaseException_clear(self);
1813+
if (memerrors_numfree >= MEMERRORS_SAVE)
1814+
Py_TYPE(self)->tp_free((PyObject *)self);
1815+
else {
1816+
self->dict = (PyObject *) memerrors_freelist;
1817+
memerrors_freelist = self;
1818+
memerrors_numfree++;
1819+
}
1820+
}
1821+
1822+
static void
1823+
preallocate_memerrors(void)
1824+
{
1825+
/* We create enough MemoryErrors and then decref them, which will fill
1826+
up the freelist. */
1827+
int i;
1828+
PyObject *errors[MEMERRORS_SAVE];
1829+
for (i = 0; i < MEMERRORS_SAVE; i++) {
1830+
errors[i] = MemoryError_new((PyTypeObject *) PyExc_MemoryError,
1831+
NULL, NULL);
1832+
if (!errors[i])
1833+
Py_FatalError("Could not preallocate MemoryError object");
1834+
}
1835+
for (i = 0; i < MEMERRORS_SAVE; i++) {
1836+
Py_DECREF(errors[i]);
1837+
}
1838+
}
1839+
1840+
static void
1841+
free_preallocated_memerrors(void)
1842+
{
1843+
while (memerrors_freelist != NULL) {
1844+
PyObject *self = (PyObject *) memerrors_freelist;
1845+
memerrors_freelist = (PyBaseExceptionObject *) memerrors_freelist->dict;
1846+
Py_TYPE(self)->tp_free((PyObject *)self);
1847+
}
1848+
}
1849+
1850+
1851+
static PyTypeObject _PyExc_MemoryError = {
1852+
PyVarObject_HEAD_INIT(NULL, 0)
1853+
"MemoryError",
1854+
sizeof(PyBaseExceptionObject),
1855+
0, (destructor)MemoryError_dealloc, 0, 0, 0, 0, 0, 0, 0,
1856+
0, 0, 0, 0, 0, 0, 0,
1857+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
1858+
PyDoc_STR("Out of memory."), (traverseproc)BaseException_traverse,
1859+
(inquiry)BaseException_clear, 0, 0, 0, 0, 0, 0, 0, &_PyExc_Exception,
1860+
0, 0, 0, offsetof(PyBaseExceptionObject, dict),
1861+
(initproc)BaseException_init, 0, MemoryError_new
1862+
};
1863+
PyObject *PyExc_MemoryError = (PyObject *) &_PyExc_MemoryError;
1864+
17811865

17821866
/*
17831867
* BufferError extends Exception
@@ -1869,11 +1953,6 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning,
18691953

18701954

18711955

1872-
/* Pre-computed MemoryError instance. Best to create this as early as
1873-
* possible and not wait until a MemoryError is actually raised!
1874-
*/
1875-
PyObject *PyExc_MemoryErrorInst=NULL;
1876-
18771956
/* Pre-computed RuntimeError instance for when recursion depth is reached.
18781957
Meant to be used when normalizing the exception for exceeding the recursion
18791958
depth will cause its own infinite recursion.
@@ -2012,9 +2091,7 @@ _PyExc_Init(void)
20122091
POST_INIT(BytesWarning)
20132092
POST_INIT(ResourceWarning)
20142093

2015-
PyExc_MemoryErrorInst = BaseException_new(&_PyExc_MemoryError, NULL, NULL);
2016-
if (!PyExc_MemoryErrorInst)
2017-
Py_FatalError("Cannot pre-allocate MemoryError instance");
2094+
preallocate_memerrors();
20182095

20192096
PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RuntimeError, NULL, NULL);
20202097
if (!PyExc_RecursionErrorInst)
@@ -2045,6 +2122,6 @@ _PyExc_Init(void)
20452122
void
20462123
_PyExc_Fini(void)
20472124
{
2048-
Py_CLEAR(PyExc_MemoryErrorInst);
20492125
Py_CLEAR(PyExc_RecursionErrorInst);
2126+
free_preallocated_memerrors();
20502127
}

Python/errors.c

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -333,29 +333,7 @@ PyErr_BadArgument(void)
333333
PyObject *
334334
PyErr_NoMemory(void)
335335
{
336-
if (PyErr_ExceptionMatches(PyExc_MemoryError))
337-
/* already current */
338-
return NULL;
339-
340-
/* raise the pre-allocated instance if it still exists */
341-
if (PyExc_MemoryErrorInst)
342-
{
343-
/* Clear the previous traceback, otherwise it will be appended
344-
* to the current one.
345-
*
346-
* The following statement is not likely to raise any error;
347-
* if it does, we simply discard it.
348-
*/
349-
PyException_SetTraceback(PyExc_MemoryErrorInst, Py_None);
350-
351-
PyErr_SetObject(PyExc_MemoryError, PyExc_MemoryErrorInst);
352-
}
353-
else
354-
/* this will probably fail since there's no memory and hee,
355-
hee, we have to instantiate this class
356-
*/
357-
PyErr_SetNone(PyExc_MemoryError);
358-
336+
PyErr_SetNone(PyExc_MemoryError);
359337
return NULL;
360338
}
361339

0 commit comments

Comments
 (0)