GH-106895: Raise a ValueError when attempting to disable events that cannot be disabled. (GH-107337)

This commit is contained in:
Mark Shannon 2023-07-27 15:27:11 +01:00 committed by GitHub
parent d77d973335
commit c6539b36c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 52 deletions

View file

@ -28,7 +28,8 @@ extern "C" {
#define PY_MONITORING_EVENT_BRANCH 8 #define PY_MONITORING_EVENT_BRANCH 8
#define PY_MONITORING_EVENT_STOP_ITERATION 9 #define PY_MONITORING_EVENT_STOP_ITERATION 9
#define PY_MONITORING_INSTRUMENTED_EVENTS 10 #define PY_MONITORING_IS_INSTRUMENTED_EVENT(ev) \
((ev) <= PY_MONITORING_EVENT_STOP_ITERATION)
/* Other events, mainly exceptions */ /* Other events, mainly exceptions */

View file

@ -136,20 +136,27 @@ class MonitoringCountTest(MonitoringTestBase, unittest.TestCase):
E = sys.monitoring.events E = sys.monitoring.events
SIMPLE_EVENTS = [ INSTRUMENTED_EVENTS = [
(E.PY_START, "start"), (E.PY_START, "start"),
(E.PY_RESUME, "resume"), (E.PY_RESUME, "resume"),
(E.PY_RETURN, "return"), (E.PY_RETURN, "return"),
(E.PY_YIELD, "yield"), (E.PY_YIELD, "yield"),
(E.JUMP, "jump"), (E.JUMP, "jump"),
(E.BRANCH, "branch"), (E.BRANCH, "branch"),
]
EXCEPT_EVENTS = [
(E.RAISE, "raise"), (E.RAISE, "raise"),
(E.PY_UNWIND, "unwind"), (E.PY_UNWIND, "unwind"),
(E.EXCEPTION_HANDLED, "exception_handled"), (E.EXCEPTION_HANDLED, "exception_handled"),
]
SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [
(E.C_RAISE, "c_raise"), (E.C_RAISE, "c_raise"),
(E.C_RETURN, "c_return"), (E.C_RETURN, "c_return"),
] ]
SIMPLE_EVENT_SET = functools.reduce(operator.or_, [ev for (ev, _) in SIMPLE_EVENTS], 0) | E.CALL SIMPLE_EVENT_SET = functools.reduce(operator.or_, [ev for (ev, _) in SIMPLE_EVENTS], 0) | E.CALL
@ -618,6 +625,49 @@ class LineMonitoringTest(MonitoringTestBase, unittest.TestCase):
self.check_lines(func2, [1,2,3,4,5,6]) self.check_lines(func2, [1,2,3,4,5,6])
class TestDisable(MonitoringTestBase, unittest.TestCase):
def gen(self, cond):
for i in range(10):
if cond:
yield 1
else:
yield 2
def raise_handle_reraise(self):
try:
1/0
except:
raise
def test_disable_legal_events(self):
for event, name in INSTRUMENTED_EVENTS:
try:
counter = CounterWithDisable()
counter.disable = True
sys.monitoring.register_callback(TEST_TOOL, event, counter)
sys.monitoring.set_events(TEST_TOOL, event)
for _ in self.gen(1):
pass
self.assertLess(counter.count, 4)
finally:
sys.monitoring.set_events(TEST_TOOL, 0)
sys.monitoring.register_callback(TEST_TOOL, event, None)
def test_disable_illegal_events(self):
for event, name in EXCEPT_EVENTS:
try:
counter = CounterWithDisable()
counter.disable = True
sys.monitoring.register_callback(TEST_TOOL, event, counter)
sys.monitoring.set_events(TEST_TOOL, event)
with self.assertRaises(ValueError):
self.raise_handle_reraise()
finally:
sys.monitoring.set_events(TEST_TOOL, 0)
sys.monitoring.register_callback(TEST_TOOL, event, None)
class ExceptionRecorder: class ExceptionRecorder:

View file

@ -0,0 +1,2 @@
Raise a ``ValueError`` when a monitoring callback funtion returns
``DISABLE`` for events that cannot be disabled locally.

View file

@ -48,6 +48,7 @@ method_vectorcall(PyObject *method, PyObject *const *args,
PyObject *self = PyMethod_GET_SELF(method); PyObject *self = PyMethod_GET_SELF(method);
PyObject *func = PyMethod_GET_FUNCTION(method); PyObject *func = PyMethod_GET_FUNCTION(method);
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
assert(nargs == 0 || args[nargs-1]);
PyObject *result; PyObject *result;
if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) { if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {
@ -56,6 +57,7 @@ method_vectorcall(PyObject *method, PyObject *const *args,
nargs += 1; nargs += 1;
PyObject *tmp = newargs[0]; PyObject *tmp = newargs[0];
newargs[0] = self; newargs[0] = self;
assert(newargs[nargs-1]);
result = _PyObject_VectorcallTstate(tstate, func, newargs, result = _PyObject_VectorcallTstate(tstate, func, newargs,
nargs, kwnames); nargs, kwnames);
newargs[0] = tmp; newargs[0] = tmp;

View file

@ -2672,7 +2672,12 @@ dummy_func(
assert(val && PyExceptionInstance_Check(val)); assert(val && PyExceptionInstance_Check(val));
exc = PyExceptionInstance_Class(val); exc = PyExceptionInstance_Class(val);
tb = PyException_GetTraceback(val); tb = PyException_GetTraceback(val);
Py_XDECREF(tb); if (tb == NULL) {
tb = Py_None;
}
else {
Py_DECREF(tb);
}
assert(PyLong_Check(lasti)); assert(PyLong_Check(lasti));
(void)lasti; // Shut up compiler warning if asserts are off (void)lasti; // Shut up compiler warning if asserts are off
PyObject *stack[4] = {NULL, exc, val, tb}; PyObject *stack[4] = {NULL, exc, val, tb};

View file

@ -193,7 +193,7 @@ static int monitor_stop_iteration(PyThreadState *tstate,
static void monitor_unwind(PyThreadState *tstate, static void monitor_unwind(PyThreadState *tstate,
_PyInterpreterFrame *frame, _PyInterpreterFrame *frame,
_Py_CODEUNIT *instr); _Py_CODEUNIT *instr);
static void monitor_handled(PyThreadState *tstate, static int monitor_handled(PyThreadState *tstate,
_PyInterpreterFrame *frame, _PyInterpreterFrame *frame,
_Py_CODEUNIT *instr, PyObject *exc); _Py_CODEUNIT *instr, PyObject *exc);
static void monitor_throw(PyThreadState *tstate, static void monitor_throw(PyThreadState *tstate,
@ -886,7 +886,9 @@ exception_unwind:
PyObject *exc = _PyErr_GetRaisedException(tstate); PyObject *exc = _PyErr_GetRaisedException(tstate);
PUSH(exc); PUSH(exc);
JUMPTO(handler); JUMPTO(handler);
monitor_handled(tstate, frame, next_instr, exc); if (monitor_handled(tstate, frame, next_instr, exc) < 0) {
goto exception_unwind;
}
/* Resume normal execution */ /* Resume normal execution */
DISPATCH(); DISPATCH();
} }
@ -1924,6 +1926,7 @@ do_monitor_exc(PyThreadState *tstate, _PyInterpreterFrame *frame,
PyErr_SetRaisedException(exc); PyErr_SetRaisedException(exc);
} }
else { else {
assert(PyErr_Occurred());
Py_DECREF(exc); Py_DECREF(exc);
} }
return err; return err;
@ -1988,15 +1991,15 @@ monitor_unwind(PyThreadState *tstate,
} }
static void static int
monitor_handled(PyThreadState *tstate, monitor_handled(PyThreadState *tstate,
_PyInterpreterFrame *frame, _PyInterpreterFrame *frame,
_Py_CODEUNIT *instr, PyObject *exc) _Py_CODEUNIT *instr, PyObject *exc)
{ {
if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) { if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
return; return 0;
} }
_Py_call_instrumentation_arg(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc); return _Py_call_instrumentation_arg(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc);
} }
static void static void

View file

@ -1949,7 +1949,12 @@
assert(val && PyExceptionInstance_Check(val)); assert(val && PyExceptionInstance_Check(val));
exc = PyExceptionInstance_Class(val); exc = PyExceptionInstance_Class(val);
tb = PyException_GetTraceback(val); tb = PyException_GetTraceback(val);
Py_XDECREF(tb); if (tb == NULL) {
tb = Py_None;
}
else {
Py_DECREF(tb);
}
assert(PyLong_Check(lasti)); assert(PyLong_Check(lasti));
(void)lasti; // Shut up compiler warning if asserts are off (void)lasti; // Shut up compiler warning if asserts are off
PyObject *stack[4] = {NULL, exc, val, tb}; PyObject *stack[4] = {NULL, exc, val, tb};

View file

@ -3315,7 +3315,12 @@
assert(val && PyExceptionInstance_Check(val)); assert(val && PyExceptionInstance_Check(val));
exc = PyExceptionInstance_Class(val); exc = PyExceptionInstance_Class(val);
tb = PyException_GetTraceback(val); tb = PyException_GetTraceback(val);
Py_XDECREF(tb); if (tb == NULL) {
tb = Py_None;
}
else {
Py_DECREF(tb);
}
assert(PyLong_Check(lasti)); assert(PyLong_Check(lasti));
(void)lasti; // Shut up compiler warning if asserts are off (void)lasti; // Shut up compiler warning if asserts are off
PyObject *stack[4] = {NULL, exc, val, tb}; PyObject *stack[4] = {NULL, exc, val, tb};

View file

@ -702,29 +702,13 @@ instrument_per_instruction(PyCodeObject *code, int i)
*opcode_ptr = INSTRUMENTED_INSTRUCTION; *opcode_ptr = INSTRUMENTED_INSTRUCTION;
} }
#ifndef NDEBUG
static bool
instruction_has_event(PyCodeObject *code, int offset)
{
_Py_CODEUNIT instr = _PyCode_CODE(code)[offset];
int opcode = instr.op.code;
if (opcode == INSTRUMENTED_LINE) {
opcode = code->_co_monitoring->lines[offset].original_opcode;
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
opcode = code->_co_monitoring->per_instruction_opcodes[offset];
}
return opcode_has_event(opcode);
}
#endif
static void static void
remove_tools(PyCodeObject * code, int offset, int event, int tools) remove_tools(PyCodeObject * code, int offset, int event, int tools)
{ {
assert(event != PY_MONITORING_EVENT_LINE); assert(event != PY_MONITORING_EVENT_LINE);
assert(event != PY_MONITORING_EVENT_INSTRUCTION); assert(event != PY_MONITORING_EVENT_INSTRUCTION);
assert(event < PY_MONITORING_INSTRUMENTED_EVENTS); assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
assert(instruction_has_event(code, offset)); assert(opcode_has_event(_Py_GetBaseOpcode(code, offset)));
_PyCoMonitoringData *monitoring = code->_co_monitoring; _PyCoMonitoringData *monitoring = code->_co_monitoring;
if (monitoring && monitoring->tools) { if (monitoring && monitoring->tools) {
monitoring->tools[offset] &= ~tools; monitoring->tools[offset] &= ~tools;
@ -779,7 +763,7 @@ add_tools(PyCodeObject * code, int offset, int event, int tools)
{ {
assert(event != PY_MONITORING_EVENT_LINE); assert(event != PY_MONITORING_EVENT_LINE);
assert(event != PY_MONITORING_EVENT_INSTRUCTION); assert(event != PY_MONITORING_EVENT_INSTRUCTION);
assert(event < PY_MONITORING_INSTRUMENTED_EVENTS); assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
assert(code->_co_monitoring); assert(code->_co_monitoring);
if (code->_co_monitoring && if (code->_co_monitoring &&
code->_co_monitoring->tools code->_co_monitoring->tools
@ -919,7 +903,7 @@ get_tools_for_instruction(PyCodeObject *code, PyInterpreterState *interp, int i,
event == PY_MONITORING_EVENT_C_RETURN); event == PY_MONITORING_EVENT_C_RETURN);
event = PY_MONITORING_EVENT_CALL; event = PY_MONITORING_EVENT_CALL;
} }
if (event < PY_MONITORING_INSTRUMENTED_EVENTS) { if (PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
CHECK(is_version_up_to_date(code, interp)); CHECK(is_version_up_to_date(code, interp));
CHECK(instrumentation_cross_checks(interp, code)); CHECK(instrumentation_cross_checks(interp, code));
if (code->_co_monitoring->tools) { if (code->_co_monitoring->tools) {
@ -940,6 +924,26 @@ get_tools_for_instruction(PyCodeObject *code, PyInterpreterState *interp, int i,
return tools; return tools;
} }
static const char *const event_names [] = {
[PY_MONITORING_EVENT_PY_START] = "PY_START",
[PY_MONITORING_EVENT_PY_RESUME] = "PY_RESUME",
[PY_MONITORING_EVENT_PY_RETURN] = "PY_RETURN",
[PY_MONITORING_EVENT_PY_YIELD] = "PY_YIELD",
[PY_MONITORING_EVENT_CALL] = "CALL",
[PY_MONITORING_EVENT_LINE] = "LINE",
[PY_MONITORING_EVENT_INSTRUCTION] = "INSTRUCTION",
[PY_MONITORING_EVENT_JUMP] = "JUMP",
[PY_MONITORING_EVENT_BRANCH] = "BRANCH",
[PY_MONITORING_EVENT_C_RETURN] = "C_RETURN",
[PY_MONITORING_EVENT_PY_THROW] = "PY_THROW",
[PY_MONITORING_EVENT_RAISE] = "RAISE",
[PY_MONITORING_EVENT_RERAISE] = "RERAISE",
[PY_MONITORING_EVENT_EXCEPTION_HANDLED] = "EXCEPTION_HANDLED",
[PY_MONITORING_EVENT_C_RAISE] = "C_RAISE",
[PY_MONITORING_EVENT_PY_UNWIND] = "PY_UNWIND",
[PY_MONITORING_EVENT_STOP_ITERATION] = "STOP_ITERATION",
};
static int static int
call_instrumentation_vector( call_instrumentation_vector(
PyThreadState *tstate, int event, PyThreadState *tstate, int event,
@ -984,7 +988,18 @@ call_instrumentation_vector(
} }
else { else {
/* DISABLE */ /* DISABLE */
remove_tools(code, offset, event, 1 << tool); if (!PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
PyErr_Format(PyExc_ValueError,
"Cannot disable %s events. Callback removed.",
event_names[event]);
/* Clear tool to prevent infinite loop */
Py_CLEAR(interp->monitoring_callables[tool][event]);
err = -1;
break;
}
else {
remove_tools(code, offset, event, 1 << tool);
}
} }
} }
Py_DECREF(offset_obj); Py_DECREF(offset_obj);
@ -1262,7 +1277,7 @@ initialize_tools(PyCodeObject *code)
assert(event > 0); assert(event > 0);
} }
assert(event >= 0); assert(event >= 0);
assert(event < PY_MONITORING_INSTRUMENTED_EVENTS); assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
tools[i] = code->_co_monitoring->active_monitors.tools[event]; tools[i] = code->_co_monitoring->active_monitors.tools[event];
CHECK(tools[i] != 0); CHECK(tools[i] != 0);
} }
@ -2017,26 +2032,6 @@ add_power2_constant(PyObject *obj, const char *name, int i)
return err; return err;
} }
static const char *const event_names [] = {
[PY_MONITORING_EVENT_PY_START] = "PY_START",
[PY_MONITORING_EVENT_PY_RESUME] = "PY_RESUME",
[PY_MONITORING_EVENT_PY_RETURN] = "PY_RETURN",
[PY_MONITORING_EVENT_PY_YIELD] = "PY_YIELD",
[PY_MONITORING_EVENT_CALL] = "CALL",
[PY_MONITORING_EVENT_LINE] = "LINE",
[PY_MONITORING_EVENT_INSTRUCTION] = "INSTRUCTION",
[PY_MONITORING_EVENT_JUMP] = "JUMP",
[PY_MONITORING_EVENT_BRANCH] = "BRANCH",
[PY_MONITORING_EVENT_C_RETURN] = "C_RETURN",
[PY_MONITORING_EVENT_PY_THROW] = "PY_THROW",
[PY_MONITORING_EVENT_RAISE] = "RAISE",
[PY_MONITORING_EVENT_RERAISE] = "RERAISE",
[PY_MONITORING_EVENT_EXCEPTION_HANDLED] = "EXCEPTION_HANDLED",
[PY_MONITORING_EVENT_C_RAISE] = "C_RAISE",
[PY_MONITORING_EVENT_PY_UNWIND] = "PY_UNWIND",
[PY_MONITORING_EVENT_STOP_ITERATION] = "STOP_ITERATION",
};
/*[clinic input] /*[clinic input]
monitoring._all_events monitoring._all_events
[clinic start generated code]*/ [clinic start generated code]*/