GH-103082: Filter LINE events in VM, to simplify tool implementation. (GH-104387)

When monitoring LINE events, instrument all instructions that can have a predecessor on a different line.
Then check that the a new line has been hit in the instrumentation code.
This brings the behavior closer to that of 3.11, simplifying implementation and porting of tools.
This commit is contained in:
Mark Shannon 2023-05-12 12:21:20 +01:00 committed by GitHub
parent 19ee53d52e
commit 45f5aa8fc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 255 additions and 161 deletions

View file

@ -4,6 +4,7 @@
#include <stddef.h>
#include "Python.h"
#include "opcode.h"
#include "pycore_ceval.h"
#include "pycore_object.h"
#include "pycore_sysmodule.h"
@ -213,7 +214,6 @@ trace_line(
if (line < 0) {
Py_RETURN_NONE;
}
frame ->f_last_traced_line = line;
Py_INCREF(frame);
frame->f_lineno = line;
int err = tstate->c_tracefunc(tstate->c_traceobj, frame, self->event, Py_None);
@ -245,14 +245,12 @@ sys_trace_line_func(
return NULL;
}
assert(args[0] == (PyObject *)frame->f_frame->f_code);
if (frame ->f_last_traced_line == line) {
/* Already traced this line */
Py_RETURN_NONE;
}
return trace_line(tstate, self, frame, line);
}
/* sys.settrace generates line events for all backward
* edges, even if on the same line.
* Handle that case here */
static PyObject *
sys_trace_jump_func(
_PyLegacyEventHandler *self, PyObject *const *args,
@ -268,61 +266,33 @@ sys_trace_jump_func(
assert(from >= 0);
int to = _PyLong_AsInt(args[2])/sizeof(_Py_CODEUNIT);
assert(to >= 0);
if (to > from) {
/* Forward jump */
return &_PyInstrumentation_DISABLE;
}
PyCodeObject *code = (PyCodeObject *)args[0];
assert(PyCode_Check(code));
/* We can call _Py_Instrumentation_GetLine because we always set
* line events for tracing */
int to_line = _Py_Instrumentation_GetLine(code, to);
int from_line = _Py_Instrumentation_GetLine(code, from);
if (to_line != from_line) {
/* Will be handled by target INSTRUMENTED_LINE */
return &_PyInstrumentation_DISABLE;
}
PyFrameObject *frame = PyEval_GetFrame();
if (frame == NULL) {
PyErr_SetString(PyExc_SystemError,
"Missing frame when calling trace function.");
return NULL;
}
if (!frame->f_trace_lines) {
Py_RETURN_NONE;
}
PyCodeObject *code = (PyCodeObject *)args[0];
assert(PyCode_Check(code));
assert(code == frame->f_frame->f_code);
/* We can call _Py_Instrumentation_GetLine because we always set
* line events for tracing */
int to_line = _Py_Instrumentation_GetLine(code, to);
/* Backward jump: Always generate event
* Forward jump: Only generate event if jumping to different line. */
if (to > from && frame->f_last_traced_line == to_line) {
/* Already traced this line */
if (!frame->f_trace_lines) {
Py_RETURN_NONE;
}
return trace_line(tstate, self, frame, to_line);
}
/* We don't care about the exception here,
* we just treat it as a possible new line
*/
static PyObject *
sys_trace_exception_handled(
_PyLegacyEventHandler *self, PyObject *const *args,
size_t nargsf, PyObject *kwnames
) {
assert(kwnames == NULL);
PyThreadState *tstate = _PyThreadState_GET();
if (tstate->c_tracefunc == NULL) {
Py_RETURN_NONE;
}
assert(PyVectorcall_NARGS(nargsf) == 3);
PyFrameObject *frame = PyEval_GetFrame();
PyCodeObject *code = (PyCodeObject *)args[0];
assert(PyCode_Check(code));
assert(code == frame->f_frame->f_code);
assert(PyLong_Check(args[1]));
int offset = _PyLong_AsInt(args[1])/sizeof(_Py_CODEUNIT);
/* We can call _Py_Instrumentation_GetLine because we always set
* line events for tracing */
int line = _Py_Instrumentation_GetLine(code, offset);
if (frame->f_last_traced_line == line) {
/* Already traced this line */
Py_RETURN_NONE;
}
return trace_line(tstate, self, frame, line);
}
PyTypeObject _PyLegacyEventHandler_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"sys.legacy_event_handler",
@ -487,7 +457,7 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
}
if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
(vectorcallfunc)sys_trace_jump_func, PyTrace_LINE,
PY_MONITORING_EVENT_JUMP, PY_MONITORING_EVENT_BRANCH)) {
PY_MONITORING_EVENT_JUMP, -1)) {
return -1;
}
if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
@ -495,11 +465,6 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
PY_MONITORING_EVENT_INSTRUCTION, -1)) {
return -1;
}
if (set_callbacks(PY_MONITORING_SYS_TRACE_ID,
(vectorcallfunc)sys_trace_exception_handled, PyTrace_LINE,
PY_MONITORING_EVENT_EXCEPTION_HANDLED, -1)) {
return -1;
}
}
int delta = (func != NULL) - (tstate->c_tracefunc != NULL);