]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.10] gh-94510: Raise on re-entrant calls to sys.setprofile and syssettrace (GH...
authorPablo Galindo Salgado <Pablogsal@gmail.com>
Tue, 5 Jul 2022 18:52:33 +0000 (19:52 +0100)
committerGitHub <noreply@github.com>
Tue, 5 Jul 2022 18:52:33 +0000 (19:52 +0100)
Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>.
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
Lib/test/test_sys_setprofile.py
Lib/test/test_sys_settrace.py
Misc/NEWS.d/next/Library/2022-07-02-19-46-30.gh-issue-94510.xOatDC.rst [new file with mode: 0644]
Modules/_lsprof.c
Python/ceval.c

index 21a09b51926e68133d5d2578155dec5569254bb9..4c3053a1e3e9e6c03d3cc6951bf338d3a700e425 100644 (file)
@@ -2,6 +2,7 @@ import gc
 import pprint
 import sys
 import unittest
+from test import support
 
 
 class TestGetProfile(unittest.TestCase):
@@ -415,5 +416,43 @@ def show_events(callable):
     pprint.pprint(capture_events(callable))
 
 
+class TestEdgeCases(unittest.TestCase):
+
+    def setUp(self):
+        self.addCleanup(sys.setprofile, sys.getprofile())
+        sys.setprofile(None)
+
+    def test_reentrancy(self):
+        def foo(*args):
+            ...
+
+        def bar(*args):
+            ...
+
+        class A:
+            def __call__(self, *args):
+                pass
+
+            def __del__(self):
+                sys.setprofile(bar)
+
+        sys.setprofile(A())
+        with support.catch_unraisable_exception() as cm:
+            sys.setprofile(foo)
+            self.assertEqual(cm.unraisable.object, A.__del__)
+            self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
+
+        self.assertEqual(sys.getprofile(), foo)
+
+
+    def test_same_object(self):
+        def foo(*args):
+            ...
+
+        sys.setprofile(foo)
+        del foo
+        sys.setprofile(sys.getprofile())
+
+
 if __name__ == "__main__":
     unittest.main()
index 4b3d096f1631596a33adc1f07faf44abed86057f..1f509eee556b01b6be53eb13144eb6d32102cdfd 100644 (file)
@@ -2,6 +2,7 @@
 
 from test import support
 import unittest
+from unittest.mock import MagicMock
 import sys
 import difflib
 import gc
@@ -2196,5 +2197,43 @@ output.append(4)
         output.append(8)
 
 
+class TestEdgeCases(unittest.TestCase):
+
+    def setUp(self):
+        self.addCleanup(sys.settrace, sys.gettrace())
+        sys.settrace(None)
+
+    def test_reentrancy(self):
+        def foo(*args):
+            ...
+
+        def bar(*args):
+            ...
+
+        class A:
+            def __call__(self, *args):
+                pass
+
+            def __del__(self):
+                sys.settrace(bar)
+
+        sys.settrace(A())
+        with support.catch_unraisable_exception() as cm:
+            sys.settrace(foo)
+            self.assertEqual(cm.unraisable.object, A.__del__)
+            self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
+
+        self.assertEqual(sys.gettrace(), foo)
+
+
+    def test_same_object(self):
+        def foo(*args):
+            ...
+
+        sys.settrace(foo)
+        del foo
+        sys.settrace(sys.gettrace())
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2022-07-02-19-46-30.gh-issue-94510.xOatDC.rst b/Misc/NEWS.d/next/Library/2022-07-02-19-46-30.gh-issue-94510.xOatDC.rst
new file mode 100644 (file)
index 0000000..55856d5
--- /dev/null
@@ -0,0 +1,2 @@
+Re-entrant calls to :func:`sys.setprofile` and :func:`sys.settrace` now
+raise :exc:`RuntimeError`. Patch by Pablo Galindo.
index 703067cc743a5593350c3cef5c2cb490a5bebb59..8d754aa7e3ca29646d677a1e611751dc624171fe 100644 (file)
@@ -744,7 +744,7 @@ profiler_dealloc(ProfilerObject *op)
     if (op->flags & POF_ENABLED) {
         PyThreadState *tstate = PyThreadState_GET();
         if (_PyEval_SetProfile(tstate, NULL, NULL) < 0) {
-            PyErr_WriteUnraisable((PyObject *)op);
+            _PyErr_WriteUnraisableMsg("When destroying _lsprof profiler", NULL);
         }
     }
 
index 9a193c994d12ec67eb4dfd2e341601464ce90b67..df4b9a84a2defa9829a977eb8fcffaec9f5e03f8 100644 (file)
@@ -5525,10 +5525,20 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
     /* The caller must hold the GIL */
     assert(PyGILState_Check());
 
+    static int reentrant = 0;
+    if (reentrant) {
+        _PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a profile function "
+                         "while another profile function is being installed");
+        reentrant = 0;
+        return -1;
+    }
+    reentrant = 1;
+
     /* Call _PySys_Audit() in the context of the current thread state,
        even if tstate is not the current thread state. */
     PyThreadState *current_tstate = _PyThreadState_GET();
     if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
+        reentrant = 0;
         return -1;
     }
 
@@ -5546,6 +5556,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
 
     /* Flag that tracing or profiling is turned on */
     tstate->cframe->use_tracing = (func != NULL) || (tstate->c_tracefunc != NULL);
+    reentrant = 0;
     return 0;
 }
 
@@ -5566,10 +5577,21 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
     /* The caller must hold the GIL */
     assert(PyGILState_Check());
 
+    static int reentrant = 0;
+
+    if (reentrant) {
+        _PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a trace function "
+                         "while another trace function is being installed");
+        reentrant = 0;
+        return -1;
+    }
+    reentrant = 1;
+
     /* Call _PySys_Audit() in the context of the current thread state,
        even if tstate is not the current thread state. */
     PyThreadState *current_tstate = _PyThreadState_GET();
     if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
+        reentrant = 0;
         return -1;
     }
 
@@ -5579,9 +5601,8 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
     tstate->c_traceobj = NULL;
     /* Must make sure that profiling is not ignored if 'traceobj' is freed */
     tstate->cframe->use_tracing = (tstate->c_profilefunc != NULL);
-    Py_XDECREF(traceobj);
-
     Py_XINCREF(arg);
+    Py_XDECREF(traceobj);
     tstate->c_traceobj = arg;
     tstate->c_tracefunc = func;
 
@@ -5589,6 +5610,7 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
     tstate->cframe->use_tracing = ((func != NULL)
                            || (tstate->c_profilefunc != NULL));
 
+    reentrant = 0;
     return 0;
 }