Skip to content

Commit 0e396d4

Browse files
pablogsalambv
authored andcommitted
[3.10] pythongh-94510: Raise on re-entrant calls to sys.setprofile and sys.settrace (pythonGH-94511)
Co-authored-by: Łukasz Langa <[email protected]> (cherry picked from commit 40d81fd) Co-authored-by: Pablo Galindo Salgado <[email protected]>
1 parent 697e78c commit 0e396d4

File tree

5 files changed

+105
-2
lines changed

5 files changed

+105
-2
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
@@ -2196,5 +2197,43 @@ async def test_jump_backward_over_async_listcomp_v2(output):
21962197
output.append(8)
21972198

21982199

2200+
class TestEdgeCases(unittest.TestCase):
2201+
2202+
def setUp(self):
2203+
self.addCleanup(sys.settrace, sys.gettrace())
2204+
sys.settrace(None)
2205+
2206+
def test_reentrancy(self):
2207+
def foo(*args):
2208+
...
2209+
2210+
def bar(*args):
2211+
...
2212+
2213+
class A:
2214+
def __call__(self, *args):
2215+
pass
2216+
2217+
def __del__(self):
2218+
sys.settrace(bar)
2219+
2220+
sys.settrace(A())
2221+
with support.catch_unraisable_exception() as cm:
2222+
sys.settrace(foo)
2223+
self.assertEqual(cm.unraisable.object, A.__del__)
2224+
self.assertIsInstance(cm.unraisable.exc_value, RuntimeError)
2225+
2226+
self.assertEqual(sys.gettrace(), foo)
2227+
2228+
2229+
def test_same_object(self):
2230+
def foo(*args):
2231+
...
2232+
2233+
sys.settrace(foo)
2234+
del foo
2235+
sys.settrace(sys.gettrace())
2236+
2237+
21992238
if __name__ == "__main__":
22002239
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
@@ -744,7 +744,7 @@ profiler_dealloc(ProfilerObject *op)
744744
if (op->flags & POF_ENABLED) {
745745
PyThreadState *tstate = PyThreadState_GET();
746746
if (_PyEval_SetProfile(tstate, NULL, NULL) < 0) {
747-
PyErr_WriteUnraisable((PyObject *)op);
747+
_PyErr_WriteUnraisableMsg("When destroying _lsprof profiler", NULL);
748748
}
749749
}
750750

Python/ceval.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5525,10 +5525,20 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
55255525
/* The caller must hold the GIL */
55265526
assert(PyGILState_Check());
55275527

5528+
static int reentrant = 0;
5529+
if (reentrant) {
5530+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a profile function "
5531+
"while another profile function is being installed");
5532+
reentrant = 0;
5533+
return -1;
5534+
}
5535+
reentrant = 1;
5536+
55285537
/* Call _PySys_Audit() in the context of the current thread state,
55295538
even if tstate is not the current thread state. */
55305539
PyThreadState *current_tstate = _PyThreadState_GET();
55315540
if (_PySys_Audit(current_tstate, "sys.setprofile", NULL) < 0) {
5541+
reentrant = 0;
55325542
return -1;
55335543
}
55345544

@@ -5546,6 +5556,7 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
55465556

55475557
/* Flag that tracing or profiling is turned on */
55485558
tstate->cframe->use_tracing = (func != NULL) || (tstate->c_tracefunc != NULL);
5559+
reentrant = 0;
55495560
return 0;
55505561
}
55515562

@@ -5566,10 +5577,21 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
55665577
/* The caller must hold the GIL */
55675578
assert(PyGILState_Check());
55685579

5580+
static int reentrant = 0;
5581+
5582+
if (reentrant) {
5583+
_PyErr_SetString(tstate, PyExc_RuntimeError, "Cannot install a trace function "
5584+
"while another trace function is being installed");
5585+
reentrant = 0;
5586+
return -1;
5587+
}
5588+
reentrant = 1;
5589+
55695590
/* Call _PySys_Audit() in the context of the current thread state,
55705591
even if tstate is not the current thread state. */
55715592
PyThreadState *current_tstate = _PyThreadState_GET();
55725593
if (_PySys_Audit(current_tstate, "sys.settrace", NULL) < 0) {
5594+
reentrant = 0;
55735595
return -1;
55745596
}
55755597

@@ -5579,16 +5601,17 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
55795601
tstate->c_traceobj = NULL;
55805602
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
55815603
tstate->cframe->use_tracing = (tstate->c_profilefunc != NULL);
5582-
Py_XDECREF(traceobj);
55835604

55845605
Py_XINCREF(arg);
5606+
Py_XDECREF(traceobj);
55855607
tstate->c_traceobj = arg;
55865608
tstate->c_tracefunc = func;
55875609

55885610
/* Flag that tracing or profiling is turned on */
55895611
tstate->cframe->use_tracing = ((func != NULL)
55905612
|| (tstate->c_profilefunc != NULL));
55915613

5614+
reentrant = 0;
55925615
return 0;
55935616
}
55945617

0 commit comments

Comments
 (0)