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

Skip to content

Commit 8e0bdfd

Browse files
committed
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.
1 parent f556211 commit 8e0bdfd

3 files changed

Lines changed: 35 additions & 4 deletions

File tree

Lib/test/test_capi.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
# these are all functions _testcapi exports whose name begins with 'test_'.
33

44
from __future__ import with_statement
5+
import random
6+
import subprocess
57
import sys
68
import time
7-
import random
89
import unittest
910
from test import support
1011
try:
@@ -35,6 +36,19 @@ def test_instancemethod(self):
3536
self.assertEqual(testfunction.attribute, "test")
3637
self.assertRaises(AttributeError, setattr, inst.testfunction, "attribute", "test")
3738

39+
def test_no_FatalError_infinite_loop(self):
40+
p = subprocess.Popen([sys.executable, "-c",
41+
'import _testcapi;'
42+
'_testcapi.crash_no_current_thread()'],
43+
stdout=subprocess.PIPE,
44+
stderr=subprocess.PIPE)
45+
(out, err) = p.communicate()
46+
self.assertEqual(out, b'')
47+
# This used to cause an infinite loop.
48+
self.assertEqual(err,
49+
b'Fatal Python error:'
50+
b' PyThreadState_Get: no current thread\n')
51+
3852

3953
@unittest.skipUnless(threading, 'Threading required for this test.')
4054
class TestPendingCalls(unittest.TestCase):

Modules/_testcapimodule.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2005,6 +2005,17 @@ make_exception_with_doc(PyObject *self, PyObject *args, PyObject *kwargs)
20052005
return PyErr_NewExceptionWithDoc(name, doc, base, dict);
20062006
}
20072007

2008+
/* Test that the fatal error from not having a current thread doesn't
2009+
cause an infinite loop. Run via Lib/test/test_capi.py */
2010+
static PyObject *
2011+
crash_no_current_thread(PyObject *self)
2012+
{
2013+
Py_BEGIN_ALLOW_THREADS
2014+
PyErr_SetString(PyExc_SystemError, "bork bork bork");
2015+
Py_END_ALLOW_THREADS
2016+
return NULL;
2017+
}
2018+
20082019
static PyMethodDef TestMethods[] = {
20092020
{"raise_exception", raise_exception, METH_VARARGS},
20102021
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
@@ -2069,6 +2080,7 @@ static PyMethodDef TestMethods[] = {
20692080
{"code_newempty", code_newempty, METH_VARARGS},
20702081
{"make_exception_with_doc", (PyCFunction)make_exception_with_doc,
20712082
METH_VARARGS | METH_KEYWORDS},
2083+
{"crash_no_current_thread", (PyCFunction)crash_no_current_thread, METH_NOARGS},
20722084
{NULL, NULL} /* sentinel */
20732085
};
20742086

Python/errors.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,14 @@ PyErr_SetString(PyObject *exception, const char *string)
130130
PyObject *
131131
PyErr_Occurred(void)
132132
{
133-
PyThreadState *tstate = PyThreadState_GET();
134-
135-
return tstate->curexc_type;
133+
/* If there is no thread state, PyThreadState_GET calls
134+
Py_FatalError, which calls PyErr_Occurred. To avoid the
135+
resulting infinite loop, we inline PyThreadState_GET here and
136+
treat no thread as no error. */
137+
PyThreadState *tstate =
138+
((PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current));
139+
140+
return tstate == NULL ? NULL : tstate->curexc_type;
136141
}
137142

138143

0 commit comments

Comments
 (0)