Skip to content

Commit 4bb672d

Browse files
committed
pythonGH-106895: Raise a ValueError when attempting to disable events that cannot be disabled. (pythonGH-107337)
1 parent 0063ad8 commit 4bb672d

File tree

8 files changed

+113
-50
lines changed

8 files changed

+113
-50
lines changed

Include/internal/pycore_instruments.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ extern "C" {
2828
#define PY_MONITORING_EVENT_BRANCH 8
2929
#define PY_MONITORING_EVENT_STOP_ITERATION 9
3030

31-
#define PY_MONITORING_INSTRUMENTED_EVENTS 10
31+
#define PY_MONITORING_IS_INSTRUMENTED_EVENT(ev) \
32+
((ev) <= PY_MONITORING_EVENT_STOP_ITERATION)
3233

3334
/* Other events, mainly exceptions */
3435

Lib/test/test_monitoring.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -136,20 +136,27 @@ def test_c_return_count(self):
136136

137137
E = sys.monitoring.events
138138

139-
SIMPLE_EVENTS = [
139+
INSTRUMENTED_EVENTS = [
140140
(E.PY_START, "start"),
141141
(E.PY_RESUME, "resume"),
142142
(E.PY_RETURN, "return"),
143143
(E.PY_YIELD, "yield"),
144144
(E.JUMP, "jump"),
145145
(E.BRANCH, "branch"),
146+
]
147+
148+
EXCEPT_EVENTS = [
146149
(E.RAISE, "raise"),
147150
(E.PY_UNWIND, "unwind"),
148151
(E.EXCEPTION_HANDLED, "exception_handled"),
152+
]
153+
154+
SIMPLE_EVENTS = INSTRUMENTED_EVENTS + EXCEPT_EVENTS + [
149155
(E.C_RAISE, "c_raise"),
150156
(E.C_RETURN, "c_return"),
151157
]
152158

159+
153160
SIMPLE_EVENT_SET = functools.reduce(operator.or_, [ev for (ev, _) in SIMPLE_EVENTS], 0) | E.CALL
154161

155162

@@ -619,6 +626,49 @@ def func2():
619626

620627
self.check_lines(func2, [1,2,3,4,5,6])
621628

629+
class TestDisable(MonitoringTestBase, unittest.TestCase):
630+
631+
def gen(self, cond):
632+
for i in range(10):
633+
if cond:
634+
yield 1
635+
else:
636+
yield 2
637+
638+
def raise_handle_reraise(self):
639+
try:
640+
1/0
641+
except:
642+
raise
643+
644+
def test_disable_legal_events(self):
645+
for event, name in INSTRUMENTED_EVENTS:
646+
try:
647+
counter = CounterWithDisable()
648+
counter.disable = True
649+
sys.monitoring.register_callback(TEST_TOOL, event, counter)
650+
sys.monitoring.set_events(TEST_TOOL, event)
651+
for _ in self.gen(1):
652+
pass
653+
self.assertLess(counter.count, 4)
654+
finally:
655+
sys.monitoring.set_events(TEST_TOOL, 0)
656+
sys.monitoring.register_callback(TEST_TOOL, event, None)
657+
658+
659+
def test_disable_illegal_events(self):
660+
for event, name in EXCEPT_EVENTS:
661+
try:
662+
counter = CounterWithDisable()
663+
counter.disable = True
664+
sys.monitoring.register_callback(TEST_TOOL, event, counter)
665+
sys.monitoring.set_events(TEST_TOOL, event)
666+
with self.assertRaises(ValueError):
667+
self.raise_handle_reraise()
668+
finally:
669+
sys.monitoring.set_events(TEST_TOOL, 0)
670+
sys.monitoring.register_callback(TEST_TOOL, event, None)
671+
622672

623673
class ExceptionRecorder:
624674

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

Objects/classobject.c

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ method_vectorcall(PyObject *method, PyObject *const *args,
4848
PyObject *self = PyMethod_GET_SELF(method);
4949
PyObject *func = PyMethod_GET_FUNCTION(method);
5050
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
51+
assert(nargs == 0 || args[nargs-1]);
5152

5253
PyObject *result;
5354
if (nargsf & PY_VECTORCALL_ARGUMENTS_OFFSET) {
@@ -56,6 +57,7 @@ method_vectorcall(PyObject *method, PyObject *const *args,
5657
nargs += 1;
5758
PyObject *tmp = newargs[0];
5859
newargs[0] = self;
60+
assert(newargs[nargs-1]);
5961
result = _PyObject_VectorcallTstate(tstate, func, newargs,
6062
nargs, kwnames);
6163
newargs[0] = tmp;

Python/bytecodes.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -2469,7 +2469,12 @@ dummy_func(
24692469
assert(val && PyExceptionInstance_Check(val));
24702470
exc = PyExceptionInstance_Class(val);
24712471
tb = PyException_GetTraceback(val);
2472-
Py_XDECREF(tb);
2472+
if (tb == NULL) {
2473+
tb = Py_None;
2474+
}
2475+
else {
2476+
Py_DECREF(tb);
2477+
}
24732478
assert(PyLong_Check(lasti));
24742479
(void)lasti; // Shut up compiler warning if asserts are off
24752480
PyObject *stack[4] = {NULL, exc, val, tb};

Python/ceval.c

+8-5
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ static int monitor_stop_iteration(PyThreadState *tstate,
191191
static void monitor_unwind(PyThreadState *tstate,
192192
_PyInterpreterFrame *frame,
193193
_Py_CODEUNIT *instr);
194-
static void monitor_handled(PyThreadState *tstate,
194+
static int monitor_handled(PyThreadState *tstate,
195195
_PyInterpreterFrame *frame,
196196
_Py_CODEUNIT *instr, PyObject *exc);
197197
static void monitor_throw(PyThreadState *tstate,
@@ -967,7 +967,9 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
967967
PyObject *exc = _PyErr_GetRaisedException(tstate);
968968
PUSH(exc);
969969
JUMPTO(handler);
970-
monitor_handled(tstate, frame, next_instr, exc);
970+
if (monitor_handled(tstate, frame, next_instr, exc) < 0) {
971+
goto exception_unwind;
972+
}
971973
/* Resume normal execution */
972974
DISPATCH();
973975
}
@@ -2005,6 +2007,7 @@ do_monitor_exc(PyThreadState *tstate, _PyInterpreterFrame *frame,
20052007
PyErr_SetRaisedException(exc);
20062008
}
20072009
else {
2010+
assert(PyErr_Occurred());
20082011
Py_DECREF(exc);
20092012
}
20102013
return err;
@@ -2059,15 +2062,15 @@ monitor_unwind(PyThreadState *tstate,
20592062
}
20602063

20612064

2062-
static void
2065+
static int
20632066
monitor_handled(PyThreadState *tstate,
20642067
_PyInterpreterFrame *frame,
20652068
_Py_CODEUNIT *instr, PyObject *exc)
20662069
{
20672070
if (no_tools_for_event(tstate, frame, PY_MONITORING_EVENT_EXCEPTION_HANDLED)) {
2068-
return;
2071+
return 0;
20692072
}
2070-
_Py_call_instrumentation_arg(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc);
2073+
return _Py_call_instrumentation_arg(tstate, PY_MONITORING_EVENT_EXCEPTION_HANDLED, frame, instr, exc);
20712074
}
20722075

20732076
static void

Python/generated_cases.c.h

+6-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/instrumentation.c

+36-41
Original file line numberDiff line numberDiff line change
@@ -696,29 +696,13 @@ instrument_per_instruction(PyCodeObject *code, int i)
696696
*opcode_ptr = INSTRUMENTED_INSTRUCTION;
697697
}
698698

699-
#ifndef NDEBUG
700-
static bool
701-
instruction_has_event(PyCodeObject *code, int offset)
702-
{
703-
_Py_CODEUNIT instr = _PyCode_CODE(code)[offset];
704-
int opcode = instr.op.code;
705-
if (opcode == INSTRUMENTED_LINE) {
706-
opcode = code->_co_monitoring->lines[offset].original_opcode;
707-
}
708-
if (opcode == INSTRUMENTED_INSTRUCTION) {
709-
opcode = code->_co_monitoring->per_instruction_opcodes[offset];
710-
}
711-
return opcode_has_event(opcode);
712-
}
713-
#endif
714-
715699
static void
716700
remove_tools(PyCodeObject * code, int offset, int event, int tools)
717701
{
718702
assert(event != PY_MONITORING_EVENT_LINE);
719703
assert(event != PY_MONITORING_EVENT_INSTRUCTION);
720-
assert(event < PY_MONITORING_INSTRUMENTED_EVENTS);
721-
assert(instruction_has_event(code, offset));
704+
assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
705+
assert(opcode_has_event(_Py_GetBaseOpcode(code, offset)));
722706
_PyCoMonitoringData *monitoring = code->_co_monitoring;
723707
if (monitoring && monitoring->tools) {
724708
monitoring->tools[offset] &= ~tools;
@@ -773,7 +757,7 @@ add_tools(PyCodeObject * code, int offset, int event, int tools)
773757
{
774758
assert(event != PY_MONITORING_EVENT_LINE);
775759
assert(event != PY_MONITORING_EVENT_INSTRUCTION);
776-
assert(event < PY_MONITORING_INSTRUMENTED_EVENTS);
760+
assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
777761
assert(code->_co_monitoring);
778762
if (code->_co_monitoring &&
779763
code->_co_monitoring->tools
@@ -915,7 +899,7 @@ get_tools_for_instruction(PyCodeObject * code, int i, int event)
915899
event == PY_MONITORING_EVENT_C_RETURN);
916900
event = PY_MONITORING_EVENT_CALL;
917901
}
918-
if (event < PY_MONITORING_INSTRUMENTED_EVENTS && monitoring->tools) {
902+
if (PY_MONITORING_IS_INSTRUMENTED_EVENT(event) && monitoring->tools) {
919903
tools = monitoring->tools[i];
920904
}
921905
else {
@@ -926,6 +910,25 @@ get_tools_for_instruction(PyCodeObject * code, int i, int event)
926910
return tools;
927911
}
928912

913+
static const char *const event_names [] = {
914+
[PY_MONITORING_EVENT_PY_START] = "PY_START",
915+
[PY_MONITORING_EVENT_PY_RESUME] = "PY_RESUME",
916+
[PY_MONITORING_EVENT_PY_RETURN] = "PY_RETURN",
917+
[PY_MONITORING_EVENT_PY_YIELD] = "PY_YIELD",
918+
[PY_MONITORING_EVENT_CALL] = "CALL",
919+
[PY_MONITORING_EVENT_LINE] = "LINE",
920+
[PY_MONITORING_EVENT_INSTRUCTION] = "INSTRUCTION",
921+
[PY_MONITORING_EVENT_JUMP] = "JUMP",
922+
[PY_MONITORING_EVENT_BRANCH] = "BRANCH",
923+
[PY_MONITORING_EVENT_C_RETURN] = "C_RETURN",
924+
[PY_MONITORING_EVENT_PY_THROW] = "PY_THROW",
925+
[PY_MONITORING_EVENT_RAISE] = "RAISE",
926+
[PY_MONITORING_EVENT_EXCEPTION_HANDLED] = "EXCEPTION_HANDLED",
927+
[PY_MONITORING_EVENT_C_RAISE] = "C_RAISE",
928+
[PY_MONITORING_EVENT_PY_UNWIND] = "PY_UNWIND",
929+
[PY_MONITORING_EVENT_STOP_ITERATION] = "STOP_ITERATION",
930+
};
931+
929932
static int
930933
call_instrumentation_vector(
931934
PyThreadState *tstate, int event,
@@ -973,7 +976,18 @@ call_instrumentation_vector(
973976
}
974977
else {
975978
/* DISABLE */
976-
remove_tools(code, offset, event, 1 << tool);
979+
if (!PY_MONITORING_IS_INSTRUMENTED_EVENT(event)) {
980+
PyErr_Format(PyExc_ValueError,
981+
"Cannot disable %s events. Callback removed.",
982+
event_names[event]);
983+
/* Clear tool to prevent infinite loop */
984+
Py_CLEAR(interp->monitoring_callables[tool][event]);
985+
err = -1;
986+
break;
987+
}
988+
else {
989+
remove_tools(code, offset, event, 1 << tool);
990+
}
977991
}
978992
}
979993
Py_DECREF(offset_obj);
@@ -1251,7 +1265,7 @@ initialize_tools(PyCodeObject *code)
12511265
assert(event > 0);
12521266
}
12531267
assert(event >= 0);
1254-
assert(event < PY_MONITORING_INSTRUMENTED_EVENTS);
1268+
assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
12551269
tools[i] = code->_co_monitoring->active_monitors.tools[event];
12561270
CHECK(tools[i] != 0);
12571271
}
@@ -2024,25 +2038,6 @@ add_power2_constant(PyObject *obj, const char *name, int i)
20242038
return err;
20252039
}
20262040

2027-
static const char *const event_names [] = {
2028-
[PY_MONITORING_EVENT_PY_START] = "PY_START",
2029-
[PY_MONITORING_EVENT_PY_RESUME] = "PY_RESUME",
2030-
[PY_MONITORING_EVENT_PY_RETURN] = "PY_RETURN",
2031-
[PY_MONITORING_EVENT_PY_YIELD] = "PY_YIELD",
2032-
[PY_MONITORING_EVENT_CALL] = "CALL",
2033-
[PY_MONITORING_EVENT_LINE] = "LINE",
2034-
[PY_MONITORING_EVENT_INSTRUCTION] = "INSTRUCTION",
2035-
[PY_MONITORING_EVENT_JUMP] = "JUMP",
2036-
[PY_MONITORING_EVENT_BRANCH] = "BRANCH",
2037-
[PY_MONITORING_EVENT_C_RETURN] = "C_RETURN",
2038-
[PY_MONITORING_EVENT_PY_THROW] = "PY_THROW",
2039-
[PY_MONITORING_EVENT_RAISE] = "RAISE",
2040-
[PY_MONITORING_EVENT_EXCEPTION_HANDLED] = "EXCEPTION_HANDLED",
2041-
[PY_MONITORING_EVENT_C_RAISE] = "C_RAISE",
2042-
[PY_MONITORING_EVENT_PY_UNWIND] = "PY_UNWIND",
2043-
[PY_MONITORING_EVENT_STOP_ITERATION] = "STOP_ITERATION",
2044-
};
2045-
20462041
/*[clinic input]
20472042
monitoring._all_events
20482043
[clinic start generated code]*/

0 commit comments

Comments
 (0)