From 5e24c80b948da6995990296cf262d9eae265e8ec Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Tue, 5 Jul 2022 19:52:33 +0100 Subject: [3.10] gh-94510: Raise on re-entrant calls to sys.setprofile and syssettrace (GH-94511) (#94579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ɓukasz Langa . Co-authored-by: Pablo Galindo Salgado --- Lib/test/test_sys_setprofile.py | 39 ++++++++++++++++++++++ Lib/test/test_sys_settrace.py | 39 ++++++++++++++++++++++ .../2022-07-02-19-46-30.gh-issue-94510.xOatDC.rst | 2 ++ Modules/_lsprof.c | 2 +- Python/ceval.c | 26 +++++++++++++-- 5 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-07-02-19-46-30.gh-issue-94510.xOatDC.rst diff --git a/Lib/test/test_sys_setprofile.py b/Lib/test/test_sys_setprofile.py index 21a09b5..4c3053a 100644 --- a/Lib/test/test_sys_setprofile.py +++ b/Lib/test/test_sys_setprofile.py @@ -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() diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 4b3d096..1f509ee 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -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 index 0000000..55856d5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-07-02-19-46-30.gh-issue-94510.xOatDC.rst @@ -0,0 +1,2 @@ +Re-entrant calls to :func:`sys.setprofile` and :func:`sys.settrace` now +raise :exc:`RuntimeError`. Patch by Pablo Galindo. diff --git a/Modules/_lsprof.c b/Modules/_lsprof.c index 703067c..8d754aa 100644 --- a/Modules/_lsprof.c +++ b/Modules/_lsprof.c @@ -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); } } diff --git a/Python/ceval.c b/Python/ceval.c index 9a193c9..df4b9a8 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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; } -- cgit v0.12