aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--Include/internal/pycore_ceval.h2
-rw-r--r--Include/internal/pycore_instruments.h2
-rw-r--r--Include/internal/pycore_interp.h1
-rw-r--r--Lib/test/test_sys_settrace.py37
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2023-09-15-23-39-43.gh-issue-103615.WZavly.rst1
-rw-r--r--Objects/frameobject.c8
-rw-r--r--Python/instrumentation.c17
-rw-r--r--Python/legacy_tracing.c52
-rw-r--r--Python/pystate.c2
9 files changed, 114 insertions, 8 deletions
diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index 339ced3c87a..c372b7224fb 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -22,6 +22,8 @@ PyAPI_FUNC(int) _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyO
extern int _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg);
+extern int _PyEval_SetOpcodeTrace(PyFrameObject *f, bool enable);
+
// Helper to look up a builtin object
// Export for 'array' shared extension
PyAPI_FUNC(PyObject*) _PyEval_GetBuiltin(PyObject *);
diff --git a/Include/internal/pycore_instruments.h b/Include/internal/pycore_instruments.h
index 97dcfb9f867..eae8371ef7f 100644
--- a/Include/internal/pycore_instruments.h
+++ b/Include/internal/pycore_instruments.h
@@ -63,6 +63,8 @@ typedef uint32_t _PyMonitoringEventSet;
PyObject *_PyMonitoring_RegisterCallback(int tool_id, int event_id, PyObject *obj);
int _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events);
+int _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet events);
+int _PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events);
extern int
_Py_call_instrumentation(PyThreadState *tstate, int event,
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index 78b841afae9..498db8becf1 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -200,7 +200,6 @@ struct _is {
uint32_t next_func_version;
_Py_GlobalMonitors monitors;
- bool f_opcode_trace_set;
bool sys_profile_initialized;
bool sys_trace_initialized;
Py_ssize_t sys_profiling_threads; /* Count of threads with c_profilefunc set */
diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py
index df7dd36274d..292096383bc 100644
--- a/Lib/test/test_sys_settrace.py
+++ b/Lib/test/test_sys_settrace.py
@@ -9,6 +9,10 @@ from functools import wraps
import asyncio
from test.support import import_helper
import contextlib
+import os
+import tempfile
+import textwrap
+import subprocess
import warnings
support.requires_working_socket(module=True)
@@ -1802,6 +1806,39 @@ class TraceOpcodesTestCase(TraceTestCase):
def make_tracer():
return Tracer(trace_opcode_events=True)
+ def test_trace_opcodes_after_settrace(self):
+ """Make sure setting f_trace_opcodes after starting trace works even
+ if it's the first time f_trace_opcodes is being set. GH-103615"""
+
+ code = textwrap.dedent("""
+ import sys
+
+ def opcode_trace_func(frame, event, arg):
+ if event == "opcode":
+ print("opcode trace triggered")
+ return opcode_trace_func
+
+ sys.settrace(opcode_trace_func)
+ sys._getframe().f_trace = opcode_trace_func
+ sys._getframe().f_trace_opcodes = True
+ a = 1
+ """)
+
+ # We can't use context manager because Windows can't execute a file while
+ # it's being written
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.py')
+ tmp.write(code.encode('utf-8'))
+ tmp.close()
+ try:
+ p = subprocess.Popen([sys.executable, tmp.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ p.wait()
+ out = p.stdout.read()
+ finally:
+ os.remove(tmp.name)
+ p.stdout.close()
+ p.stderr.close()
+ self.assertIn(b"opcode trace triggered", out)
+
class RaisingTraceFuncTestCase(unittest.TestCase):
def setUp(self):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-09-15-23-39-43.gh-issue-103615.WZavly.rst b/Misc/NEWS.d/next/Core and Builtins/2023-09-15-23-39-43.gh-issue-103615.WZavly.rst
new file mode 100644
index 00000000000..2a0e10b84cc
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-09-15-23-39-43.gh-issue-103615.WZavly.rst
@@ -0,0 +1 @@
+Use local events for opcode tracing
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 170c1177069..3b72651a1c0 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -127,10 +127,13 @@ frame_settrace_opcodes(PyFrameObject *f, PyObject* value, void *Py_UNUSED(ignore
}
if (value == Py_True) {
f->f_trace_opcodes = 1;
- _PyInterpreterState_GET()->f_opcode_trace_set = true;
+ if (f->f_trace) {
+ return _PyEval_SetOpcodeTrace(f, true);
+ }
}
else {
f->f_trace_opcodes = 0;
+ return _PyEval_SetOpcodeTrace(f, false);
}
return 0;
}
@@ -842,6 +845,9 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
}
if (v != f->f_trace) {
Py_XSETREF(f->f_trace, Py_XNewRef(v));
+ if (v != NULL && f->f_trace_opcodes) {
+ return _PyEval_SetOpcodeTrace(f, true);
+ }
}
return 0;
}
diff --git a/Python/instrumentation.c b/Python/instrumentation.c
index 9ee11588e44..35b0e7a8f35 100644
--- a/Python/instrumentation.c
+++ b/Python/instrumentation.c
@@ -1833,6 +1833,23 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
return 0;
}
+int
+_PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events)
+{
+ assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
+ PyInterpreterState *interp = _PyInterpreterState_GET();
+ if (check_tool(interp, tool_id)) {
+ return -1;
+ }
+ if (code->_co_monitoring == NULL) {
+ *events = 0;
+ return 0;
+ }
+ _Py_LocalMonitors *local = &code->_co_monitoring->local_monitors;
+ *events = get_local_events(local, tool_id);
+ return 0;
+}
+
/*[clinic input]
module monitoring
[clinic start generated code]*/
diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c
index ddc727113af..ccbb3eb3f7c 100644
--- a/Python/legacy_tracing.c
+++ b/Python/legacy_tracing.c
@@ -117,6 +117,35 @@ sys_profile_call_or_return(
Py_RETURN_NONE;
}
+int
+_PyEval_SetOpcodeTrace(
+ PyFrameObject *frame,
+ bool enable
+) {
+ assert(frame != NULL);
+ assert(PyCode_Check(frame->f_frame->f_executable));
+
+ PyCodeObject *code = (PyCodeObject *)frame->f_frame->f_executable;
+ _PyMonitoringEventSet events = 0;
+
+ if (_PyMonitoring_GetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, &events) < 0) {
+ return -1;
+ }
+
+ if (enable) {
+ if (events & (1 << PY_MONITORING_EVENT_INSTRUCTION)) {
+ return 0;
+ }
+ events |= (1 << PY_MONITORING_EVENT_INSTRUCTION);
+ } else {
+ if (!(events & (1 << PY_MONITORING_EVENT_INSTRUCTION))) {
+ return 0;
+ }
+ events &= (~(1 << PY_MONITORING_EVENT_INSTRUCTION));
+ }
+ return _PyMonitoring_SetLocalEvents(code, PY_MONITORING_SYS_TRACE_ID, events);
+}
+
static PyObject *
call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
{
@@ -130,6 +159,12 @@ call_trace_func(_PyLegacyEventHandler *self, PyObject *arg)
"Missing frame when calling trace function.");
return NULL;
}
+ if (frame->f_trace_opcodes) {
+ if (_PyEval_SetOpcodeTrace(frame, true) != 0) {
+ return NULL;
+ }
+ }
+
Py_INCREF(frame);
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, arg);
Py_DECREF(frame);
@@ -230,11 +265,14 @@ sys_trace_instruction_func(
"Missing frame when calling trace function.");
return NULL;
}
- if (!frame->f_trace_opcodes) {
+ PyThreadState *tstate = _PyThreadState_GET();
+ if (!tstate->c_tracefunc || !frame->f_trace_opcodes) {
+ if (_PyEval_SetOpcodeTrace(frame, false) != 0) {
+ return NULL;
+ }
Py_RETURN_NONE;
}
Py_INCREF(frame);
- PyThreadState *tstate = _PyThreadState_GET();
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, Py_None);
frame->f_lineno = 0;
Py_DECREF(frame);
@@ -531,9 +569,15 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
(1 << PY_MONITORING_EVENT_PY_UNWIND) | (1 << PY_MONITORING_EVENT_PY_THROW) |
(1 << PY_MONITORING_EVENT_STOP_ITERATION) |
(1 << PY_MONITORING_EVENT_EXCEPTION_HANDLED);
- if (tstate->interp->f_opcode_trace_set) {
- events |= (1 << PY_MONITORING_EVENT_INSTRUCTION);
+
+ PyFrameObject* frame = PyEval_GetFrame();
+ if (frame->f_trace_opcodes) {
+ int ret = _PyEval_SetOpcodeTrace(frame, true);
+ if (ret != 0) {
+ return ret;
+ }
}
}
+
return _PyMonitoring_SetEvents(PY_MONITORING_SYS_TRACE_ID, events);
}
diff --git a/Python/pystate.c b/Python/pystate.c
index 8970e17a3c1..b369a56d6d5 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -708,7 +708,6 @@ init_interpreter(PyInterpreterState *interp,
/* Fix the self-referential, statically initialized fields. */
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
}
- interp->f_opcode_trace_set = false;
interp->_initialized = 1;
return _PyStatus_OK();
@@ -958,7 +957,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
interp->code_watchers[i] = NULL;
}
interp->active_code_watchers = 0;
- interp->f_opcode_trace_set = false;
// XXX Once we have one allocator per interpreter (i.e.
// per-interpreter GC) we must ensure that all of the interpreter's
// objects have been cleaned up at the point.