Skip to content

Commit 5f4a16b

Browse files
authored
[3.11] gh-94510: Raise on re-entrant calls to sys.setprofile and sys.settrace (GH-94511) (GH-94578)
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl> (cherry picked from commit 40d81fd)
1 parent 552fc9a commit 5f4a16b

File tree

5 files changed

+105
-3
lines changed

5 files changed

+105
-3
lines changed

Lib/test/test_sys_setprofile.py

+39
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pprint
33
import sys
44
import unittest
5+
from test import support
56

67

78
class TestGetProfile(unittest.TestCase):
@@ -415,5 +416,43 @@ def show_events(callable):
415416
pprint.pprint(capture_events(callable))
416417

417418

419+
class TestEdgeCases(unittest.TestCase):
420+
421+
def setUp(self):
422+
self.addCleanup(sys.setprofile, sys.getprofile())
423+
sys.setprofile(None)
424+
425+
def test_reentrancy(self):
426+
def foo(*args):
427+
...
428+
429+
def bar(*args):
430+
...
431+
432+
class A:
433+
def __call__(self, *args):
434+
pass
435+
436+
def __del__(self):
437+
sys.setprofile(bar)
438+
439+
sys.setprofile(A())
440+
with support.catch_unraisable_exception() as cm:
441+
sys.setprofile(foo)
442+
self.assertEqual(cm.unraisable.object, A.__del__)
443+
self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
444+
445+
self.assertEqual(sys.getprofile(), foo)
446+
447+
448+
def test_same_object(self):
449+
def foo(*args):
450+
...
451+
452+
sys.setprofile(foo)
453+
del foo
454+
sys.setprofile(sys.getprofile())
455+
456+
418457
if __name__ == "__main__":
419458
unittest.main()

Lib/test/test_sys_settrace.py

+39
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from test import support
44
import unittest
5+
from unittest.mock import MagicMock
56
import sys
67
import difflib
78
import gc
@@ -2684,5 +2685,43 @@ def f():
26842685
self.assertEqual(counts, {'call': 1, 'line': 2000, 'return': 1})
26852686

26862687

2688+
class TestEdgeCases(unittest.TestCase):
2689+
2690+
def setUp(self):
2691+
self.addCleanup(sys.settrace, sys.gettrace())
2692+
sys.settrace(None)
2693+
2694+
def test_reentrancy(self):
2695+
def foo(*args):
2696+
...
2697+
2698+
def bar(*args):
2699+
...
2700+
2701+
class A:
2702+
def __call__(self, *args):
2703+
pass
2704+
2705+
def __del__(self):
2706+
sys.settrace(bar)
2707+
2708+
sys.settrace(A())
2709+
with support.catch_unraisable_exception() as cm:
2710+
sys.settrace(foo)
2711+
self.assertEqual(cm.unraisable.object, A.__del__)
2712+
self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
2713+
2714+
self.assertEqual(sys.gettrace(), foo)
2715+
2716+
2717+
def test_same_object(self):
2718+
def foo(*args):
2719+
...
2720+
2721+
sys.settrace(foo)
2722+
del foo
2723+
sys.settrace(sys.gettrace())
2724+
2725+
26872726
if __name__ == "__main__":
26882727
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Re-entrant calls to :func:`sys.setprofile` and :func:`sys.settrace` now
2+
raise :exc:`RuntimeError`. Patch by Pablo Galindo.

Modules/_lsprof.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ profiler_dealloc(ProfilerObject *op)
750750
if (op->flags & POF_ENABLED) {
751751
PyThreadState *tstate = _PyThreadState_GET();
752752
if (_PyEval_SetProfile(tstate, NULL, NULL) < 0) {
753-
PyErr_WriteUnraisable((PyObject *)op);
753+
_PyErr_WriteUnraisableMsg("When destroying _lsprof profiler", NULL);
754754
}
755755
}
756756

Python/ceval.c

+24-2
Original file line numberDiff line numberDiff line change
@@ -6930,10 +6930,20 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69306930
/* The caller must hold the GIL */
69316931
assert(PyGILState_Check());
69326932

6933+
static int reentrant = 0;
6934+
if (reentrant) {
6935+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a profile function "
6936+
"while another profile function is being installed");
6937+
reentrant = 0;
6938+
return -1;
6939+
}
6940+
reentrant = 1;
6941+
69336942
/* Call _PySys_Audit() in the context of the current thread state,
69346943
even if tstate is not the current thread state. */
69356944
PyThreadState *current_tstate = _PyThreadState_GET();
69366945
if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
6946+
reentrant = 0;
69376947
return -1;
69386948
}
69396949

@@ -6951,6 +6961,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69516961

69526962
/* Flag that tracing or profiling is turned on */
69536963
_PyThreadState_UpdateTracingState(tstate);
6964+
reentrant = 0;
69546965
return 0;
69556966
}
69566967

@@ -6971,10 +6982,21 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69716982
/* The caller must hold the GIL */
69726983
assert(PyGILState_Check());
69736984

6985+
static int reentrant = 0;
6986+
6987+
if (reentrant) {
6988+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a trace function "
6989+
"while another trace function is being installed");
6990+
reentrant = 0;
6991+
return -1;
6992+
}
6993+
reentrant = 1;
6994+
69746995
/* Call _PySys_Audit() in the context of the current thread state,
69756996
even if tstate is not the current thread state. */
69766997
PyThreadState *current_tstate = _PyThreadState_GET();
69776998
if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
6999+
reentrant = 0;
69787000
return -1;
69797001
}
69807002

@@ -6984,15 +7006,15 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69847006
tstate->c_traceobj = NULL;
69857007
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
69867008
_PyThreadState_UpdateTracingState(tstate);
6987-
Py_XDECREF(traceobj);
6988-
69897009
Py_XINCREF(arg);
7010+
Py_XDECREF(traceobj);
69907011
tstate->c_traceobj = arg;
69917012
tstate->c_tracefunc = func;
69927013

69937014
/* Flag that tracing or profiling is turned on */
69947015
_PyThreadState_UpdateTracingState(tstate);
69957016

7017+
reentrant = 0;
69967018
return 0;
69977019
}
69987020

0 commit comments

Comments
 (0)