Skip to content

Commit 646b7ce

Browse files
committed
pythonGH-108976. Keep monitoring data structures valid during de-optimization during callback. (pythonGH-109131)
1 parent af83d1e commit 646b7ce

File tree

4 files changed

+77
-55
lines changed

4 files changed

+77
-55
lines changed

Lib/test/test_monitoring.py

+8
Original file line numberDiff line numberDiff line change
@@ -1718,3 +1718,11 @@ def make_foo_optimized_then_set_event():
17181718
make_foo_optimized_then_set_event()
17191719
finally:
17201720
sys.monitoring.set_events(TEST_TOOL, 0)
1721+
1722+
def test_gh108976(self):
1723+
sys.monitoring.use_tool_id(0, "test")
1724+
sys.monitoring.set_events(0, 0)
1725+
sys.monitoring.register_callback(0, E.LINE, lambda *args: sys.monitoring.set_events(0, 0))
1726+
sys.monitoring.register_callback(0, E.INSTRUCTION, lambda *args: 0)
1727+
sys.monitoring.set_events(0, E.LINE | E.INSTRUCTION)
1728+
sys.monitoring.set_events(0, 0)

Lib/test/test_pdb.py

+18
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,24 @@ def test_pdb_issue_gh_101517():
17991799
(Pdb) continue
18001800
"""
18011801

1802+
def test_pdb_issue_gh_108976():
1803+
"""See GH-108976
1804+
Make sure setting f_trace_opcodes = True won't crash pdb
1805+
>>> def test_function():
1806+
... import sys
1807+
... sys._getframe().f_trace_opcodes = True
1808+
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
1809+
... a = 1
1810+
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
1811+
... 'continue'
1812+
... ]):
1813+
... test_function()
1814+
bdb.Bdb.dispatch: unknown debugging event: 'opcode'
1815+
> <doctest test.test_pdb.test_pdb_issue_gh_108976[0]>(5)test_function()
1816+
-> a = 1
1817+
(Pdb) continue
1818+
"""
1819+
18021820
def test_pdb_ambiguous_statements():
18031821
"""See GH-104301
18041822
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash that occurs after de-instrumenting a code object in a monitoring
2+
callback.

Python/instrumentation.c

+49-55
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22

33

4+
45
#include "Python.h"
56
#include "pycore_call.h"
67
#include "pycore_frame.h"
@@ -355,45 +356,21 @@ dump_instrumentation_data_per_instruction(PyCodeObject *code, _PyCoMonitoringDat
355356
}
356357

357358
static void
358-
dump_monitors(const char *prefix, _Py_Monitors monitors, FILE*out)
359+
dump_global_monitors(const char *prefix, _Py_GlobalMonitors monitors, FILE*out)
359360
{
360361
fprintf(out, "%s monitors:\n", prefix);
361362
for (int event = 0; event < _PY_MONITORING_UNGROUPED_EVENTS; event++) {
362363
fprintf(out, " Event %d: Tools %x\n", event, monitors.tools[event]);
363364
}
364365
}
365366

366-
/* Like _Py_GetBaseOpcode but without asserts.
367-
* Does its best to give the right answer, but won't abort
368-
* if something is wrong */
369-
static int
370-
get_base_opcode_best_attempt(PyCodeObject *code, int offset)
367+
static void
368+
dump_local_monitors(const char *prefix, _Py_LocalMonitors monitors, FILE*out)
371369
{
372-
int opcode = _Py_OPCODE(_PyCode_CODE(code)[offset]);
373-
if (INSTRUMENTED_OPCODES[opcode] != opcode) {
374-
/* Not instrumented */
375-
return _PyOpcode_Deopt[opcode] == 0 ? opcode : _PyOpcode_Deopt[opcode];
376-
}
377-
if (opcode == INSTRUMENTED_INSTRUCTION) {
378-
if (code->_co_monitoring->per_instruction_opcodes[offset] == 0) {
379-
return opcode;
380-
}
381-
opcode = code->_co_monitoring->per_instruction_opcodes[offset];
382-
}
383-
if (opcode == INSTRUMENTED_LINE) {
384-
if (code->_co_monitoring->lines[offset].original_opcode == 0) {
385-
return opcode;
386-
}
387-
opcode = code->_co_monitoring->lines[offset].original_opcode;
388-
}
389-
int deinstrumented = DE_INSTRUMENT[opcode];
390-
if (deinstrumented) {
391-
return deinstrumented;
392-
}
393-
if (_PyOpcode_Deopt[opcode] == 0) {
394-
return opcode;
370+
fprintf(out, "%s monitors:\n", prefix);
371+
for (int event = 0; event < _PY_MONITORING_LOCAL_EVENTS; event++) {
372+
fprintf(out, " Event %d: Tools %x\n", event, monitors.tools[event]);
395373
}
396-
return _PyOpcode_Deopt[opcode];
397374
}
398375

399376
/* No error checking -- Don't use this for anything but experimental debugging */
@@ -408,9 +385,9 @@ dump_instrumentation_data(PyCodeObject *code, int star, FILE*out)
408385
fprintf(out, "NULL\n");
409386
return;
410387
}
411-
dump_monitors("Global", PyInterpreterState_Get()->monitors, out);
412-
dump_monitors("Code", data->local_monitors, out);
413-
dump_monitors("Active", data->active_monitors, out);
388+
dump_global_monitors("Global", _PyInterpreterState_GET()->monitors, out);
389+
dump_local_monitors("Code", data->local_monitors, out);
390+
dump_local_monitors("Active", data->active_monitors, out);
414391
int code_len = (int)Py_SIZE(code);
415392
bool starred = false;
416393
for (int i = 0; i < code_len; i += instruction_length(code, i)) {
@@ -467,18 +444,23 @@ sanity_check_instrumentation(PyCodeObject *code)
467444
if (data == NULL) {
468445
return;
469446
}
470-
_Py_GlobalMonitors active_monitors = _PyInterpreterState_GET()->monitors;
447+
_Py_GlobalMonitors global_monitors = _PyInterpreterState_GET()->monitors;
448+
_Py_LocalMonitors active_monitors;
471449
if (code->_co_monitoring) {
472-
_Py_Monitors local_monitors = code->_co_monitoring->local_monitors;
473-
active_monitors = local_union(active_monitors, local_monitors);
450+
_Py_LocalMonitors local_monitors = code->_co_monitoring->local_monitors;
451+
active_monitors = local_union(global_monitors, local_monitors);
452+
}
453+
else {
454+
_Py_LocalMonitors empty = (_Py_LocalMonitors) { 0 };
455+
active_monitors = local_union(global_monitors, empty);
474456
}
475457
assert(monitors_equals(
476458
code->_co_monitoring->active_monitors,
477-
active_monitors)
478-
);
459+
active_monitors));
479460
int code_len = (int)Py_SIZE(code);
480461
for (int i = 0; i < code_len;) {
481-
int opcode = _PyCode_CODE(code)[i].op.code;
462+
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
463+
int opcode = instr->op.code;
482464
int base_opcode = _Py_GetBaseOpcode(code, i);
483465
CHECK(valid_opcode(opcode));
484466
CHECK(valid_opcode(base_opcode));
@@ -498,23 +480,30 @@ sanity_check_instrumentation(PyCodeObject *code)
498480
opcode = data->lines[i].original_opcode;
499481
CHECK(opcode != END_FOR);
500482
CHECK(opcode != RESUME);
483+
CHECK(opcode != RESUME_CHECK);
501484
CHECK(opcode != INSTRUMENTED_RESUME);
502485
if (!is_instrumented(opcode)) {
503486
CHECK(_PyOpcode_Deopt[opcode] == opcode);
504487
}
505488
CHECK(opcode != INSTRUMENTED_LINE);
506489
}
507-
else if (data->lines && !is_instrumented(opcode)) {
508-
CHECK(data->lines[i].original_opcode == 0 ||
509-
data->lines[i].original_opcode == base_opcode ||
510-
DE_INSTRUMENT[data->lines[i].original_opcode] == base_opcode);
490+
else if (data->lines) {
491+
/* If original_opcode is INSTRUMENTED_INSTRUCTION
492+
* *and* we are executing a INSTRUMENTED_LINE instruction
493+
* that has de-instrumented itself, then we will execute
494+
* an invalid INSTRUMENTED_INSTRUCTION */
495+
CHECK(data->lines[i].original_opcode != INSTRUMENTED_INSTRUCTION);
496+
}
497+
if (opcode == INSTRUMENTED_INSTRUCTION) {
498+
CHECK(data->per_instruction_opcodes[i] != 0);
499+
opcode = data->per_instruction_opcodes[i];
511500
}
512501
if (is_instrumented(opcode)) {
513502
CHECK(DE_INSTRUMENT[opcode] == base_opcode);
514503
int event = EVENT_FOR_OPCODE[DE_INSTRUMENT[opcode]];
515504
if (event < 0) {
516505
/* RESUME fixup */
517-
event = _PyCode_CODE(code)[i].op.arg;
506+
event = instr->op.arg ? 1: 0;
518507
}
519508
CHECK(active_monitors.tools[event] != 0);
520509
}
@@ -599,30 +588,30 @@ static void
599588
de_instrument_line(PyCodeObject *code, int i)
600589
{
601590
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
602-
uint8_t *opcode_ptr = &instr->op.code;
603-
int opcode =*opcode_ptr;
591+
int opcode = instr->op.code;
604592
if (opcode != INSTRUMENTED_LINE) {
605593
return;
606594
}
607595
_PyCoLineInstrumentationData *lines = &code->_co_monitoring->lines[i];
608596
int original_opcode = lines->original_opcode;
597+
if (original_opcode == INSTRUMENTED_INSTRUCTION) {
598+
lines->original_opcode = code->_co_monitoring->per_instruction_opcodes[i];
599+
}
609600
CHECK(original_opcode != 0);
610601
CHECK(original_opcode == _PyOpcode_Deopt[original_opcode]);
611-
*opcode_ptr = instr->op.code = original_opcode;
602+
instr->op.code = original_opcode;
612603
if (_PyOpcode_Caches[original_opcode]) {
613604
instr[1].cache = adaptive_counter_warmup();
614605
}
615-
assert(*opcode_ptr != INSTRUMENTED_LINE);
616606
assert(instr->op.code != INSTRUMENTED_LINE);
617607
}
618608

619-
620609
static void
621610
de_instrument_per_instruction(PyCodeObject *code, int i)
622611
{
623612
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
624613
uint8_t *opcode_ptr = &instr->op.code;
625-
int opcode =*opcode_ptr;
614+
int opcode = *opcode_ptr;
626615
if (opcode == INSTRUMENTED_LINE) {
627616
opcode_ptr = &code->_co_monitoring->lines[i].original_opcode;
628617
opcode = *opcode_ptr;
@@ -633,10 +622,11 @@ de_instrument_per_instruction(PyCodeObject *code, int i)
633622
int original_opcode = code->_co_monitoring->per_instruction_opcodes[i];
634623
CHECK(original_opcode != 0);
635624
CHECK(original_opcode == _PyOpcode_Deopt[original_opcode]);
636-
instr->op.code = original_opcode;
625+
*opcode_ptr = original_opcode;
637626
if (_PyOpcode_Caches[original_opcode]) {
638627
instr[1].cache = adaptive_counter_warmup();
639628
}
629+
assert(*opcode_ptr != INSTRUMENTED_INSTRUCTION);
640630
assert(instr->op.code != INSTRUMENTED_INSTRUCTION);
641631
/* Keep things clean for sanity check */
642632
code->_co_monitoring->per_instruction_opcodes[i] = 0;
@@ -676,7 +666,7 @@ static void
676666
instrument_line(PyCodeObject *code, int i)
677667
{
678668
uint8_t *opcode_ptr = &_PyCode_CODE(code)[i].op.code;
679-
int opcode =*opcode_ptr;
669+
int opcode = *opcode_ptr;
680670
if (opcode == INSTRUMENTED_LINE) {
681671
return;
682672
}
@@ -691,13 +681,14 @@ instrument_per_instruction(PyCodeObject *code, int i)
691681
{
692682
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
693683
uint8_t *opcode_ptr = &instr->op.code;
694-
int opcode =*opcode_ptr;
684+
int opcode = *opcode_ptr;
695685
if (opcode == INSTRUMENTED_LINE) {
696686
_PyCoLineInstrumentationData *lines = &code->_co_monitoring->lines[i];
697687
opcode_ptr = &lines->original_opcode;
698688
opcode = *opcode_ptr;
699689
}
700690
if (opcode == INSTRUMENTED_INSTRUCTION) {
691+
assert(code->_co_monitoring->per_instruction_opcodes[i] > 0);
701692
return;
702693
}
703694
CHECK(opcode != 0);
@@ -1127,7 +1118,6 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
11271118

11281119
_PyCoMonitoringData *monitoring = code->_co_monitoring;
11291120
_PyCoLineInstrumentationData *line_data = &monitoring->lines[i];
1130-
uint8_t original_opcode = line_data->original_opcode;
11311121
if (tstate->tracing) {
11321122
goto done;
11331123
}
@@ -1178,7 +1168,9 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
11781168
}
11791169
}
11801170
Py_DECREF(line_obj);
1171+
uint8_t original_opcode;
11811172
done:
1173+
original_opcode = line_data->original_opcode;
11821174
assert(original_opcode != 0);
11831175
assert(original_opcode < INSTRUMENTED_LINE);
11841176
assert(_PyOpcode_Deopt[original_opcode] == original_opcode);
@@ -1633,7 +1625,9 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
16331625
i += instruction_length(code, i);
16341626
}
16351627
}
1636-
1628+
#ifdef INSTRUMENT_DEBUG
1629+
sanity_check_instrumentation(code);
1630+
#endif
16371631
uint8_t new_line_tools = new_events.tools[PY_MONITORING_EVENT_LINE];
16381632
uint8_t new_per_instruction_tools = new_events.tools[PY_MONITORING_EVENT_INSTRUCTION];
16391633

0 commit comments

Comments
 (0)