mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
[3.10] gh-94510: Raise on re-entrant calls to sys.setprofile and syssettrace (GH-94511) (#94579)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>. Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
This commit is contained in:
parent
fd34bfe484
commit
5e24c80b94
5 changed files with 105 additions and 3 deletions
|
|
@ -2,6 +2,7 @@
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -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 @@ async def test_jump_backward_over_async_listcomp_v2(output):
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Re-entrant calls to :func:`sys.setprofile` and :func:`sys.settrace` now
|
||||
raise :exc:`RuntimeError`. Patch by Pablo Galindo.
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue