]> granicus.if.org Git - python/commitdiff
Make PyErr_Occurred return NULL if there is no current thread. Previously it
authorJeffrey Yasskin <jyasskin@gmail.com>
Thu, 13 May 2010 18:31:05 +0000 (18:31 +0000)
committerJeffrey Yasskin <jyasskin@gmail.com>
Thu, 13 May 2010 18:31:05 +0000 (18:31 +0000)
would Py_FatalError, which called PyErr_Occurred, resulting in a semi-infinite
recursion.

Fixes issue 3605.

Lib/test/test_capi.py
Modules/_testcapimodule.c
Python/errors.c

index 29f7a71b155c8abc69cf58f2b14c476476c43924..7a6870dc6af6026286626925be8c922fb9668d9f 100644 (file)
@@ -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):
index 2868014d3abf6001974410f5edc24b21a3ade56f..ba81f717d4062bb37977bec07c7e29a0a9d935d7 100644 (file)
@@ -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 */
 };
 
index e98d7a946cad3cda3297bc1612e0f688179d7d06..37669739c159d27ce253bf5c0741ac65b23bc533 100644 (file)
@@ -130,9 +130,14 @@ PyErr_SetString(PyObject *exception, const char *string)
 PyObject *
 PyErr_Occurred(void)
 {
-    PyThreadState *tstate = PyThreadState_GET();
-
-    return tstate->curexc_type;
+    /* 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 == NULL ? NULL : tstate->curexc_type;
 }