Make PyErr_Occurred return NULL if there is no current thread. Previously it
would Py_FatalError, which called PyErr_Occurred, resulting in a semi-infinite recursion. Fixes issue 3605.
This commit is contained in:
parent
f55621115c
commit
8e0bdfd1d4
|
@ -2,9 +2,10 @@
|
|||
# these are all functions _testcapi exports whose name begins with 'test_'.
|
||||
|
||||
from __future__ import with_statement
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import random
|
||||
import unittest
|
||||
from test import support
|
||||
try:
|
||||
|
@ -35,6 +36,19 @@ class CAPITest(unittest.TestCase):
|
|||
self.assertEqual(testfunction.attribute, "test")
|
||||
self.assertRaises(AttributeError, setattr, inst.testfunction, "attribute", "test")
|
||||
|
||||
def test_no_FatalError_infinite_loop(self):
|
||||
p = subprocess.Popen([sys.executable, "-c",
|
||||
'import _testcapi;'
|
||||
'_testcapi.crash_no_current_thread()'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
self.assertEqual(out, b'')
|
||||
# This used to cause an infinite loop.
|
||||
self.assertEqual(err,
|
||||
b'Fatal Python error:'
|
||||
b' PyThreadState_Get: no current thread\n')
|
||||
|
||||
|
||||
@unittest.skipUnless(threading, 'Threading required for this test.')
|
||||
class TestPendingCalls(unittest.TestCase):
|
||||
|
|
|
@ -2005,6 +2005,17 @@ make_exception_with_doc(PyObject *self, PyObject *args, PyObject *kwargs)
|
|||
return PyErr_NewExceptionWithDoc(name, doc, base, dict);
|
||||
}
|
||||
|
||||
/* Test that the fatal error from not having a current thread doesn't
|
||||
cause an infinite loop. Run via Lib/test/test_capi.py */
|
||||
static PyObject *
|
||||
crash_no_current_thread(PyObject *self)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
PyErr_SetString(PyExc_SystemError, "bork bork bork");
|
||||
Py_END_ALLOW_THREADS
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyMethodDef TestMethods[] = {
|
||||
{"raise_exception", raise_exception, METH_VARARGS},
|
||||
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
|
||||
|
@ -2069,6 +2080,7 @@ static PyMethodDef TestMethods[] = {
|
|||
{"code_newempty", code_newempty, METH_VARARGS},
|
||||
{"make_exception_with_doc", (PyCFunction)make_exception_with_doc,
|
||||
METH_VARARGS | METH_KEYWORDS},
|
||||
{"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS},
|
||||
{NULL, NULL} /* sentinel */
|
||||
};
|
||||
|
||||
|
|
|
@ -130,9 +130,14 @@ PyErr_SetString(PyObject *exception, const char *string)
|
|||
PyObject *
|
||||
PyErr_Occurred(void)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_GET();
|
||||
/* If there is no thread state, PyThreadState_GET calls
|
||||
Py_FatalError, which calls PyErr_Occurred. To avoid the
|
||||
resulting infinite loop, we inline PyThreadState_GET here and
|
||||
treat no thread as no error. */
|
||||
PyThreadState *tstate =
|
||||
((PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current));
|
||||
|
||||
return tstate->curexc_type;
|
||||
return tstate == NULL ? NULL : tstate->curexc_type;
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue