mirror of
https://github.com/python/cpython
synced 2024-10-14 11:02:30 +00:00
bpo-30697: Fix PyErr_NormalizeException() when no memory (GH-2327)
This commit is contained in:
parent
275d2d9c46
commit
56d1f5ca32
|
@ -440,6 +440,11 @@ Build and C API Changes
|
||||||
download a copy of 32-bit Python for this purpose. (Contributed by Zachary
|
download a copy of 32-bit Python for this purpose. (Contributed by Zachary
|
||||||
Ware in :issue:`30450`.)
|
Ware in :issue:`30450`.)
|
||||||
|
|
||||||
|
* The ``PyExc_RecursionErrorInst`` singleton that was part of the public API
|
||||||
|
has been removed as its members being never cleared may cause a segfault
|
||||||
|
during finalization of the interpreter. Contributed by Xavier de Gaye in
|
||||||
|
:issue:`22898` and :issue:`30697`.
|
||||||
|
|
||||||
* Support for building ``--without-threads`` is removed.
|
* Support for building ``--without-threads`` is removed.
|
||||||
(Contributed by Antoine Pitrou in :issue:`31370`.).
|
(Contributed by Antoine Pitrou in :issue:`31370`.).
|
||||||
|
|
||||||
|
|
|
@ -220,8 +220,6 @@ PyAPI_DATA(PyObject *) PyExc_IOError;
|
||||||
PyAPI_DATA(PyObject *) PyExc_WindowsError;
|
PyAPI_DATA(PyObject *) PyExc_WindowsError;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst;
|
|
||||||
|
|
||||||
/* Predefined warning categories */
|
/* Predefined warning categories */
|
||||||
PyAPI_DATA(PyObject *) PyExc_Warning;
|
PyAPI_DATA(PyObject *) PyExc_Warning;
|
||||||
PyAPI_DATA(PyObject *) PyExc_UserWarning;
|
PyAPI_DATA(PyObject *) PyExc_UserWarning;
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
|
|
||||||
from test.support import (TESTFN, captured_stderr, check_impl_detail,
|
from test.support import (TESTFN, captured_stderr, check_impl_detail,
|
||||||
check_warnings, cpython_only, gc_collect, run_unittest,
|
check_warnings, cpython_only, gc_collect, run_unittest,
|
||||||
no_tracing, unlink, import_module, script_helper)
|
no_tracing, unlink, import_module, script_helper,
|
||||||
|
SuppressCrashReport)
|
||||||
class NaiveException(Exception):
|
class NaiveException(Exception):
|
||||||
def __init__(self, x):
|
def __init__(self, x):
|
||||||
self.x = x
|
self.x = x
|
||||||
|
@ -936,6 +936,105 @@ def g():
|
||||||
self.assertIsInstance(v, RecursionError, type(v))
|
self.assertIsInstance(v, RecursionError, type(v))
|
||||||
self.assertIn("maximum recursion depth exceeded", str(v))
|
self.assertIn("maximum recursion depth exceeded", str(v))
|
||||||
|
|
||||||
|
@cpython_only
|
||||||
|
def test_recursion_normalizing_exception(self):
|
||||||
|
# Issue #22898.
|
||||||
|
# Test that a RecursionError is raised when tstate->recursion_depth is
|
||||||
|
# equal to recursion_limit in PyErr_NormalizeException() and check
|
||||||
|
# that a ResourceWarning is printed.
|
||||||
|
# Prior to #22898, the recursivity of PyErr_NormalizeException() was
|
||||||
|
# controled by tstate->recursion_depth and a PyExc_RecursionErrorInst
|
||||||
|
# singleton was being used in that case, that held traceback data and
|
||||||
|
# locals indefinitely and would cause a segfault in _PyExc_Fini() upon
|
||||||
|
# finalization of these locals.
|
||||||
|
code = """if 1:
|
||||||
|
import sys
|
||||||
|
from _testcapi import get_recursion_depth
|
||||||
|
|
||||||
|
class MyException(Exception): pass
|
||||||
|
|
||||||
|
def setrecursionlimit(depth):
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
sys.setrecursionlimit(depth)
|
||||||
|
return depth
|
||||||
|
except RecursionError:
|
||||||
|
# sys.setrecursionlimit() raises a RecursionError if
|
||||||
|
# the new recursion limit is too low (issue #25274).
|
||||||
|
depth += 1
|
||||||
|
|
||||||
|
def recurse(cnt):
|
||||||
|
cnt -= 1
|
||||||
|
if cnt:
|
||||||
|
recurse(cnt)
|
||||||
|
else:
|
||||||
|
generator.throw(MyException)
|
||||||
|
|
||||||
|
def gen():
|
||||||
|
f = open(%a, mode='rb', buffering=0)
|
||||||
|
yield
|
||||||
|
|
||||||
|
generator = gen()
|
||||||
|
next(generator)
|
||||||
|
recursionlimit = sys.getrecursionlimit()
|
||||||
|
depth = get_recursion_depth()
|
||||||
|
try:
|
||||||
|
# Upon the last recursive invocation of recurse(),
|
||||||
|
# tstate->recursion_depth is equal to (recursion_limit - 1)
|
||||||
|
# and is equal to recursion_limit when _gen_throw() calls
|
||||||
|
# PyErr_NormalizeException().
|
||||||
|
recurse(setrecursionlimit(depth + 2) - depth - 1)
|
||||||
|
finally:
|
||||||
|
sys.setrecursionlimit(recursionlimit)
|
||||||
|
print('Done.')
|
||||||
|
""" % __file__
|
||||||
|
rc, out, err = script_helper.assert_python_failure("-Wd", "-c", code)
|
||||||
|
# Check that the program does not fail with SIGABRT.
|
||||||
|
self.assertEqual(rc, 1)
|
||||||
|
self.assertIn(b'RecursionError', err)
|
||||||
|
self.assertIn(b'ResourceWarning', err)
|
||||||
|
self.assertIn(b'Done.', out)
|
||||||
|
|
||||||
|
@cpython_only
|
||||||
|
def test_recursion_normalizing_infinite_exception(self):
|
||||||
|
# Issue #30697. Test that a RecursionError is raised when
|
||||||
|
# PyErr_NormalizeException() maximum recursion depth has been
|
||||||
|
# exceeded.
|
||||||
|
code = """if 1:
|
||||||
|
import _testcapi
|
||||||
|
try:
|
||||||
|
raise _testcapi.RecursingInfinitelyError
|
||||||
|
finally:
|
||||||
|
print('Done.')
|
||||||
|
"""
|
||||||
|
rc, out, err = script_helper.assert_python_failure("-c", code)
|
||||||
|
self.assertEqual(rc, 1)
|
||||||
|
self.assertIn(b'RecursionError: maximum recursion depth exceeded '
|
||||||
|
b'while normalizing an exception', err)
|
||||||
|
self.assertIn(b'Done.', out)
|
||||||
|
|
||||||
|
@cpython_only
|
||||||
|
def test_recursion_normalizing_with_no_memory(self):
|
||||||
|
# Issue #30697. Test that in the abort that occurs when there is no
|
||||||
|
# memory left and the size of the Python frames stack is greater than
|
||||||
|
# the size of the list of preallocated MemoryError instances, the
|
||||||
|
# Fatal Python error message mentions MemoryError.
|
||||||
|
code = """if 1:
|
||||||
|
import _testcapi
|
||||||
|
class C(): pass
|
||||||
|
def recurse(cnt):
|
||||||
|
cnt -= 1
|
||||||
|
if cnt:
|
||||||
|
recurse(cnt)
|
||||||
|
else:
|
||||||
|
_testcapi.set_nomemory(0)
|
||||||
|
C()
|
||||||
|
recurse(16)
|
||||||
|
"""
|
||||||
|
with SuppressCrashReport():
|
||||||
|
rc, out, err = script_helper.assert_python_failure("-c", code)
|
||||||
|
self.assertIn(b'Fatal Python error: Cannot recover from '
|
||||||
|
b'MemoryErrors while normalizing exceptions.', err)
|
||||||
|
|
||||||
@cpython_only
|
@cpython_only
|
||||||
def test_MemoryError(self):
|
def test_MemoryError(self):
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
The `PyExc_RecursionErrorInst` singleton is removed and
|
||||||
|
`PyErr_NormalizeException()` does not use it anymore. This singleton is
|
||||||
|
persistent and its members being never cleared may cause a segfault during
|
||||||
|
finalization of the interpreter. See also issue #22898.
|
|
@ -4940,6 +4940,61 @@ static PyTypeObject awaitType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static int recurse_infinitely_error_init(PyObject *, PyObject *, PyObject *);
|
||||||
|
|
||||||
|
static PyTypeObject PyRecursingInfinitelyError_Type = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
"RecursingInfinitelyError", /* tp_name */
|
||||||
|
sizeof(PyBaseExceptionObject), /* tp_basicsize */
|
||||||
|
0, /* tp_itemsize */
|
||||||
|
0, /* tp_dealloc */
|
||||||
|
0, /* tp_print */
|
||||||
|
0, /* tp_getattr */
|
||||||
|
0, /* tp_setattr */
|
||||||
|
0, /* tp_reserved */
|
||||||
|
0, /* tp_repr */
|
||||||
|
0, /* tp_as_number */
|
||||||
|
0, /* tp_as_sequence */
|
||||||
|
0, /* tp_as_mapping */
|
||||||
|
0, /* tp_hash */
|
||||||
|
0, /* tp_call */
|
||||||
|
0, /* tp_str */
|
||||||
|
0, /* tp_getattro */
|
||||||
|
0, /* tp_setattro */
|
||||||
|
0, /* tp_as_buffer */
|
||||||
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
|
||||||
|
"Instantiating this exception starts infinite recursion.", /* tp_doc */
|
||||||
|
0, /* tp_traverse */
|
||||||
|
0, /* tp_clear */
|
||||||
|
0, /* tp_richcompare */
|
||||||
|
0, /* tp_weaklistoffset */
|
||||||
|
0, /* tp_iter */
|
||||||
|
0, /* tp_iternext */
|
||||||
|
0, /* tp_methods */
|
||||||
|
0, /* tp_members */
|
||||||
|
0, /* tp_getset */
|
||||||
|
0, /* tp_base */
|
||||||
|
0, /* tp_dict */
|
||||||
|
0, /* tp_descr_get */
|
||||||
|
0, /* tp_descr_set */
|
||||||
|
0, /* tp_dictoffset */
|
||||||
|
(initproc)recurse_infinitely_error_init, /* tp_init */
|
||||||
|
0, /* tp_alloc */
|
||||||
|
0, /* tp_new */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
recurse_infinitely_error_init(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
PyObject *type = (PyObject *)&PyRecursingInfinitelyError_Type;
|
||||||
|
|
||||||
|
/* Instantiating this exception starts infinite recursion. */
|
||||||
|
Py_INCREF(type);
|
||||||
|
PyErr_SetObject(type, NULL);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static struct PyModuleDef _testcapimodule = {
|
static struct PyModuleDef _testcapimodule = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_testcapi",
|
"_testcapi",
|
||||||
|
@ -4981,6 +5036,14 @@ PyInit__testcapi(void)
|
||||||
Py_INCREF(&awaitType);
|
Py_INCREF(&awaitType);
|
||||||
PyModule_AddObject(m, "awaitType", (PyObject *)&awaitType);
|
PyModule_AddObject(m, "awaitType", (PyObject *)&awaitType);
|
||||||
|
|
||||||
|
PyRecursingInfinitelyError_Type.tp_base = (PyTypeObject *)PyExc_Exception;
|
||||||
|
if (PyType_Ready(&PyRecursingInfinitelyError_Type) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_INCREF(&PyRecursingInfinitelyError_Type);
|
||||||
|
PyModule_AddObject(m, "RecursingInfinitelyError",
|
||||||
|
(PyObject *)&PyRecursingInfinitelyError_Type);
|
||||||
|
|
||||||
PyModule_AddObject(m, "CHAR_MAX", PyLong_FromLong(CHAR_MAX));
|
PyModule_AddObject(m, "CHAR_MAX", PyLong_FromLong(CHAR_MAX));
|
||||||
PyModule_AddObject(m, "CHAR_MIN", PyLong_FromLong(CHAR_MIN));
|
PyModule_AddObject(m, "CHAR_MIN", PyLong_FromLong(CHAR_MIN));
|
||||||
PyModule_AddObject(m, "UCHAR_MAX", PyLong_FromLong(UCHAR_MAX));
|
PyModule_AddObject(m, "UCHAR_MAX", PyLong_FromLong(UCHAR_MAX));
|
||||||
|
|
|
@ -2409,12 +2409,6 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Pre-computed RecursionError 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.
|
|
||||||
*/
|
|
||||||
PyObject *PyExc_RecursionErrorInst = NULL;
|
|
||||||
|
|
||||||
#define PRE_INIT(TYPE) \
|
#define PRE_INIT(TYPE) \
|
||||||
if (!(_PyExc_ ## TYPE.tp_flags & Py_TPFLAGS_READY)) { \
|
if (!(_PyExc_ ## TYPE.tp_flags & Py_TPFLAGS_READY)) { \
|
||||||
if (PyType_Ready(&_PyExc_ ## TYPE) < 0) \
|
if (PyType_Ready(&_PyExc_ ## TYPE) < 0) \
|
||||||
|
@ -2674,37 +2668,11 @@ _PyExc_Init(PyObject *bltinmod)
|
||||||
ADD_ERRNO(TimeoutError, ETIMEDOUT)
|
ADD_ERRNO(TimeoutError, ETIMEDOUT)
|
||||||
|
|
||||||
preallocate_memerrors();
|
preallocate_memerrors();
|
||||||
|
|
||||||
if (!PyExc_RecursionErrorInst) {
|
|
||||||
PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RecursionError, NULL, NULL);
|
|
||||||
if (!PyExc_RecursionErrorInst)
|
|
||||||
Py_FatalError("Cannot pre-allocate RecursionError instance for "
|
|
||||||
"recursion errors");
|
|
||||||
else {
|
|
||||||
PyBaseExceptionObject *err_inst =
|
|
||||||
(PyBaseExceptionObject *)PyExc_RecursionErrorInst;
|
|
||||||
PyObject *args_tuple;
|
|
||||||
PyObject *exc_message;
|
|
||||||
exc_message = PyUnicode_FromString("maximum recursion depth exceeded");
|
|
||||||
if (!exc_message)
|
|
||||||
Py_FatalError("cannot allocate argument for RecursionError "
|
|
||||||
"pre-allocation");
|
|
||||||
args_tuple = PyTuple_Pack(1, exc_message);
|
|
||||||
if (!args_tuple)
|
|
||||||
Py_FatalError("cannot allocate tuple for RecursionError "
|
|
||||||
"pre-allocation");
|
|
||||||
Py_DECREF(exc_message);
|
|
||||||
if (BaseException_init(err_inst, args_tuple, NULL))
|
|
||||||
Py_FatalError("init of pre-allocated RecursionError failed");
|
|
||||||
Py_DECREF(args_tuple);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyExc_Fini(void)
|
_PyExc_Fini(void)
|
||||||
{
|
{
|
||||||
Py_CLEAR(PyExc_RecursionErrorInst);
|
|
||||||
free_preallocated_memerrors();
|
free_preallocated_memerrors();
|
||||||
Py_CLEAR(errnomap);
|
Py_CLEAR(errnomap);
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,7 +224,6 @@ EXPORTS
|
||||||
PyExc_PermissionError=python37.PyExc_PermissionError DATA
|
PyExc_PermissionError=python37.PyExc_PermissionError DATA
|
||||||
PyExc_ProcessLookupError=python37.PyExc_ProcessLookupError DATA
|
PyExc_ProcessLookupError=python37.PyExc_ProcessLookupError DATA
|
||||||
PyExc_RecursionError=python37.PyExc_RecursionError DATA
|
PyExc_RecursionError=python37.PyExc_RecursionError DATA
|
||||||
PyExc_RecursionErrorInst=python37.PyExc_RecursionErrorInst DATA
|
|
||||||
PyExc_ReferenceError=python37.PyExc_ReferenceError DATA
|
PyExc_ReferenceError=python37.PyExc_ReferenceError DATA
|
||||||
PyExc_ResourceWarning=python37.PyExc_ResourceWarning DATA
|
PyExc_ResourceWarning=python37.PyExc_ResourceWarning DATA
|
||||||
PyExc_RuntimeError=python37.PyExc_RuntimeError DATA
|
PyExc_RuntimeError=python37.PyExc_RuntimeError DATA
|
||||||
|
|
|
@ -218,20 +218,24 @@ PyErr_ExceptionMatches(PyObject *exc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef Py_NORMALIZE_RECURSION_LIMIT
|
||||||
|
#define Py_NORMALIZE_RECURSION_LIMIT 32
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Used in many places to normalize a raised exception, including in
|
/* Used in many places to normalize a raised exception, including in
|
||||||
eval_code2(), do_raise(), and PyErr_Print()
|
eval_code2(), do_raise(), and PyErr_Print()
|
||||||
|
|
||||||
XXX: should PyErr_NormalizeException() also call
|
XXX: should PyErr_NormalizeException() also call
|
||||||
PyException_SetTraceback() with the resulting value and tb?
|
PyException_SetTraceback() with the resulting value and tb?
|
||||||
*/
|
*/
|
||||||
void
|
static void
|
||||||
PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
|
PyErr_NormalizeExceptionEx(PyObject **exc, PyObject **val,
|
||||||
|
PyObject **tb, int recursion_depth)
|
||||||
{
|
{
|
||||||
PyObject *type = *exc;
|
PyObject *type = *exc;
|
||||||
PyObject *value = *val;
|
PyObject *value = *val;
|
||||||
PyObject *inclass = NULL;
|
PyObject *inclass = NULL;
|
||||||
PyObject *initial_tb = NULL;
|
PyObject *initial_tb = NULL;
|
||||||
PyThreadState *tstate = NULL;
|
|
||||||
|
|
||||||
if (type == NULL) {
|
if (type == NULL) {
|
||||||
/* There was no exception, so nothing to do. */
|
/* There was no exception, so nothing to do. */
|
||||||
|
@ -293,6 +297,10 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
|
||||||
finally:
|
finally:
|
||||||
Py_DECREF(type);
|
Py_DECREF(type);
|
||||||
Py_DECREF(value);
|
Py_DECREF(value);
|
||||||
|
if (recursion_depth + 1 == Py_NORMALIZE_RECURSION_LIMIT) {
|
||||||
|
PyErr_SetString(PyExc_RecursionError, "maximum recursion depth "
|
||||||
|
"exceeded while normalizing an exception");
|
||||||
|
}
|
||||||
/* If the new exception doesn't set a traceback and the old
|
/* If the new exception doesn't set a traceback and the old
|
||||||
exception had a traceback, use the old traceback for the
|
exception had a traceback, use the old traceback for the
|
||||||
new exception. It's better than nothing.
|
new exception. It's better than nothing.
|
||||||
|
@ -305,20 +313,26 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
|
||||||
else
|
else
|
||||||
Py_DECREF(initial_tb);
|
Py_DECREF(initial_tb);
|
||||||
}
|
}
|
||||||
/* normalize recursively */
|
/* Normalize recursively.
|
||||||
tstate = PyThreadState_GET();
|
* Abort when Py_NORMALIZE_RECURSION_LIMIT has been exceeded and the
|
||||||
if (++tstate->recursion_depth > Py_GetRecursionLimit()) {
|
* corresponding RecursionError could not be normalized.*/
|
||||||
--tstate->recursion_depth;
|
if (++recursion_depth > Py_NORMALIZE_RECURSION_LIMIT) {
|
||||||
/* throw away the old exception and use the recursion error instead */
|
if (PyErr_GivenExceptionMatches(*exc, PyExc_MemoryError)) {
|
||||||
Py_INCREF(PyExc_RecursionError);
|
Py_FatalError("Cannot recover from MemoryErrors "
|
||||||
Py_SETREF(*exc, PyExc_RecursionError);
|
"while normalizing exceptions.");
|
||||||
Py_INCREF(PyExc_RecursionErrorInst);
|
}
|
||||||
Py_SETREF(*val, PyExc_RecursionErrorInst);
|
else {
|
||||||
/* just keeping the old traceback */
|
Py_FatalError("Cannot recover from the recursive normalization "
|
||||||
return;
|
"of an exception.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PyErr_NormalizeException(exc, val, tb);
|
PyErr_NormalizeExceptionEx(exc, val, tb, recursion_depth);
|
||||||
--tstate->recursion_depth;
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
|
||||||
|
{
|
||||||
|
PyErr_NormalizeExceptionEx(exc, val, tb, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue