8000 gh-94510: Raise on re-entrant calls to sys.setprofile and sys.settrac… · python/cpython@40d81fd · GitHub
[go: up one dir, main page]

Skip to content

Commit 40d81fd

Browse files
pablogsalambv
andauthored
gh-94510: Raise on re-entrant calls to sys.setprofile and sys.settrace (GH-94511)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 5f31930 commit 40d81fd

File tree

5 files changed

+105
-3
lines changed

5 files changed

+105
-3
lines changed

Lib/test/test_sys_setprofile.py

Lines changed: 39 additions & 0 deletions
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

Lines changed: 39 additions & 0 deletions
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()
Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6973,10 +6973,20 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69736973
/* The caller must hold the GIL */
69746974
assert(PyGILState_Check());
69756975

6976+
static int reentrant = 0;
6977+
if (reentrant) {
6978+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a profile function "
6979+
"while another profile function is being installed");
6980+
reentrant = 0;
6981+
return -1;
6982+
}
6983+
reentrant = 1;
6984+
69766985
/* Call _PySys_Audit() in the context of the current thread state,
69776986
even if tstate is not the current thread state. */
69786987
PyThreadState *current_tstate = _PyThreadState_GET();
69796988
if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
6989+
reentrant = 0;
69806990
return -1;
69816991
}
69826992

@@ -6994,6 +7004,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
69947004

69957005
/* Flag that tracing or profiling is turned on */
69967006
_PyThreadState_UpdateTracingState(tstate);
7007+
reentrant = 0;
69977008
return 0;
69987009
}
69997010

@@ -7014,10 +7025,21 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
70147025
/* The caller must hold the GIL */
70157026
assert(PyGILState_Check());
70167027

7028+
static int reentrant = 0;
7029+
7030+
if (reentrant) {
7031+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a trace function "
7032+
"while another trace function is being installed");
7033+
reentrant = 0;
7034+
return -1;
7035+
}
7036+
reentrant = 1;
7037+
70177038
/* Call _PySys_Audit() in the context of the current thread state,
70187039
even if tstate is not the current thread state. */
70197040
PyThreadState *current_tstate = _PyThreadState_GET();
70207041
if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
7042+
reentrant = 0;
70217043
return -1;
70227044
}
70237045

@@ -7027,15 +7049,15 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
70277049
tstate->c_traceobj = NULL;
70287050
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
70297051
_PyThreadState_UpdateTracingState(tstate);
7030-
Py_XDECREF(traceobj);
7031-
70327052
Py_XINCREF(arg);
7053+
Py_XDECREF(traceobj);
70337054
tstate->c_traceobj = arg;
70347055
tstate->c_tracefunc = func;
70357056

70367057
/* Flag that tracing or profiling is turned on */
70377058
_PyThreadState_UpdateTracingState(tstate);
70387059

7060+
reentrant = 0;
70397061
return 0;
70407062
}
70417063

0 commit comments

Comments
 (0)
0