Skip to content

Commit 4a69301

Browse files
authored
GH-108976. Keep monitoring data structures valid during de-optimization during callback. (GH-109131)
1 parent 60b8341 commit 4a69301

File tree

4 files changed

+79
-55
lines changed

4 files changed

+79
-55
lines changed

Lib/test/test_monitoring.py

+8
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,14 @@ def make_foo_optimized_then_set_event():
17191719
finally:
17201720
sys.monitoring.set_events(TEST_TOOL, 0)
17211721

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)
1729+
17221730

17231731
class TestOptimizer(MonitoringTestBase, unittest.TestCase):
17241732

Lib/test/test_pdb.py

+18
Original file line numberDiff line numberDiff line change
@@ -2297,6 +2297,24 @@ def test_pdb_issue_gh_101517():
22972297
(Pdb) continue
22982298
"""
22992299

2300+
def test_pdb_issue_gh_108976():
2301+
"""See GH-108976
2302+
Make sure setting f_trace_opcodes = True won't crash pdb
2303+
>>> def test_function():
2304+
... import sys
2305+
... sys._getframe().f_trace_opcodes = True
2306+
... import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
2307+
... a = 1
2308+
>>> with PdbTestInput([ # doctest: +NORMALIZE_WHITESPACE
2309+
... 'continue'
2310+
... ]):
2311+
... test_function()
2312+
bdb.Bdb.dispatch: unknown debugging event: 'opcode'
2313+
> <doctest test.test_pdb.test_pdb_issue_gh_108976[0]>(5)test_function()
2314+
-> a = 1
2315+
(Pdb) continue
2316+
"""
2317+
23002318
def test_pdb_ambiguous_statements():
23012319
"""See GH-104301
23022320
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

+51-55
Original file line numberDiff line numberDiff line change
@@ -364,45 +364,21 @@ dump_instrumentation_data_per_instruction(PyCodeObject *code, _PyCoMonitoringDat
364364
}
365365

366366
static void
367-
dump_monitors(const char *prefix, _Py_Monitors monitors, FILE*out)
367+
dump_global_monitors(const char *prefix, _Py_GlobalMonitors monitors, FILE*out)
368368
{
369369
fprintf(out, "%s monitors:\n", prefix);
370370
for (int event = 0; event < _PY_MONITORING_UNGROUPED_EVENTS; event++) {
371371
fprintf(out, " Event %d: Tools %x\n", event, monitors.tools[event]);
372372
}
373373
}
374374

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

408384
/* No error checking -- Don't use this for anything but experimental debugging */
@@ -417,9 +393,9 @@ dump_instrumentation_data(PyCodeObject *code, int star, FILE*out)
417393
fprintf(out, "NULL\n");
418394
return;
419395
}
420-
dump_monitors("Global", _PyInterpreterState_GET()->monitors, out);
421-
dump_monitors("Code", data->local_monitors, out);
422-
dump_monitors("Active", data->active_monitors, out);
396+
dump_global_monitors("Global", _PyInterpreterState_GET()->monitors, out);
397+
dump_local_monitors("Code", data->local_monitors, out);
398+
dump_local_monitors("Active", data->active_monitors, out);
423399
int code_len = (int)Py_SIZE(code);
424400
bool starred = false;
425401
for (int i = 0; i < code_len; i += _PyInstruction_GetLength(code, i)) {
@@ -458,6 +434,9 @@ dump_instrumentation_data(PyCodeObject *code, int star, FILE*out)
458434
static bool
459435
valid_opcode(int opcode)
460436
{
437+
if (opcode == INSTRUMENTED_LINE) {
438+
return true;
439+
}
461440
if (IS_VALID_OPCODE(opcode) &&
462441
opcode != CACHE &&
463442
opcode != RESERVED &&
@@ -475,18 +454,23 @@ sanity_check_instrumentation(PyCodeObject *code)
475454
if (data == NULL) {
476455
return;
477456
}
478-
_Py_GlobalMonitors active_monitors = _PyInterpreterState_GET()->monitors;
457+
_Py_GlobalMonitors global_monitors = _PyInterpreterState_GET()->monitors;
458+
_Py_LocalMonitors active_monitors;
479459
if (code->_co_monitoring) {
480-
_Py_Monitors local_monitors = code->_co_monitoring->local_monitors;
481-
active_monitors = local_union(active_monitors, local_monitors);
460+
_Py_LocalMonitors local_monitors = code->_co_monitoring->local_monitors;
461+
active_monitors = local_union(global_monitors, local_monitors);
462+
}
463+
else {
464+
_Py_LocalMonitors empty = (_Py_LocalMonitors) { 0 };
465+
active_monitors = local_union(global_monitors, empty);
482466
}
483467
assert(monitors_equals(
484468
code->_co_monitoring->active_monitors,
485-
active_monitors)
486-
);
469+
active_monitors));
487470
int code_len = (int)Py_SIZE(code);
488471
for (int i = 0; i < code_len;) {
489-
int opcode = _PyCode_CODE(code)[i].op.code;
472+
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
473+
int opcode = instr->op.code;
490474
int base_opcode = _Py_GetBaseOpcode(code, i);
491475
CHECK(valid_opcode(opcode));
492476
CHECK(valid_opcode(base_opcode));
@@ -506,23 +490,30 @@ sanity_check_instrumentation(PyCodeObject *code)
506490
opcode = data->lines[i].original_opcode;
507491
CHECK(opcode != END_FOR);
508492
CHECK(opcode != RESUME);
493+
CHECK(opcode != RESUME_CHECK);
509494
CHECK(opcode != INSTRUMENTED_RESUME);
510495
if (!is_instrumented(opcode)) {
511496
CHECK(_PyOpcode_Deopt[opcode] == opcode);
512497
}
513498
CHECK(opcode != INSTRUMENTED_LINE);
514499
}
515-
else if (data->lines && !is_instrumented(opcode)) {
516-
CHECK(data->lines[i].original_opcode == 0 ||
517-
data->lines[i].original_opcode == base_opcode ||
518-
DE_INSTRUMENT[data->lines[i].original_opcode] == base_opcode);
500+
else if (data->lines) {
501+
/* If original_opcode is INSTRUMENTED_INSTRUCTION
502+
* *and* we are executing a INSTRUMENTED_LINE instruction
503+
* that has de-instrumented itself, then we will execute
504+
* an invalid INSTRUMENTED_INSTRUCTION */
505+
CHECK(data->lines[i].original_opcode != INSTRUMENTED_INSTRUCTION);
506+
}
507+
if (opcode == INSTRUMENTED_INSTRUCTION) {
508+
CHECK(data->per_instruction_opcodes[i] != 0);
509+
opcode = data->per_instruction_opcodes[i];
519510
}
520511
if (is_instrumented(opcode)) {
521512
CHECK(DE_INSTRUMENT[opcode] == base_opcode);
522513
int event = EVENT_FOR_OPCODE[DE_INSTRUMENT[opcode]];
523514
if (event < 0) {
524515
/* RESUME fixup */
525-
event = _PyCode_CODE(code)[i].op.arg;
516+
event = instr->op.arg ? 1: 0;
526517
}
527518
CHECK(active_monitors.tools[event] != 0);
528519
}
@@ -608,30 +599,30 @@ static void
608599
de_instrument_line(PyCodeObject *code, int i)
609600
{
610601
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
611-
uint8_t *opcode_ptr = &instr->op.code;
612-
int opcode =*opcode_ptr;
602+
int opcode = instr->op.code;
613603
if (opcode != INSTRUMENTED_LINE) {
614604
return;
615605
}
616606
_PyCoLineInstrumentationData *lines = &code->_co_monitoring->lines[i];
617607
int original_opcode = lines->original_opcode;
608+
if (original_opcode == INSTRUMENTED_INSTRUCTION) {
609+
lines->original_opcode = code->_co_monitoring->per_instruction_opcodes[i];
610+
}
618611
CHECK(original_opcode != 0);
619612
CHECK(original_opcode == _PyOpcode_Deopt[original_opcode]);
620-
*opcode_ptr = instr->op.code = original_opcode;
613+
instr->op.code = original_opcode;
621614
if (_PyOpcode_Caches[original_opcode]) {
622615
instr[1].cache = adaptive_counter_warmup();
623616
}
624-
assert(*opcode_ptr != INSTRUMENTED_LINE);
625617
assert(instr->op.code != INSTRUMENTED_LINE);
626618
}
627619

628-
629620
static void
630621
de_instrument_per_instruction(PyCodeObject *code, int i)
631622
{
632623
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
633624
uint8_t *opcode_ptr = &instr->op.code;
634-
int opcode =*opcode_ptr;
625+
int opcode = *opcode_ptr;
635626
if (opcode == INSTRUMENTED_LINE) {
636627
opcode_ptr = &code->_co_monitoring->lines[i].original_opcode;
637628
opcode = *opcode_ptr;
@@ -642,10 +633,11 @@ de_instrument_per_instruction(PyCodeObject *code, int i)
642633
int original_opcode = code->_co_monitoring->per_instruction_opcodes[i];
643634
CHECK(original_opcode != 0);
644635
CHECK(original_opcode == _PyOpcode_Deopt[original_opcode]);
645-
instr->op.code = original_opcode;
636+
*opcode_ptr = original_opcode;
646637
if (_PyOpcode_Caches[original_opcode]) {
647638
instr[1].cache = adaptive_counter_warmup();
648639
}
640+
assert(*opcode_ptr != INSTRUMENTED_INSTRUCTION);
649641
assert(instr->op.code != INSTRUMENTED_INSTRUCTION);
650642
/* Keep things clean for sanity check */
651643
code->_co_monitoring->per_instruction_opcodes[i] = 0;
@@ -685,7 +677,7 @@ static void
685677
instrument_line(PyCodeObject *code, int i)
686678
{
687679
uint8_t *opcode_ptr = &_PyCode_CODE(code)[i].op.code;
688-
int opcode =*opcode_ptr;
680+
int opcode = *opcode_ptr;
689681
if (opcode == INSTRUMENTED_LINE) {
690682
return;
691683
}
@@ -700,13 +692,14 @@ instrument_per_instruction(PyCodeObject *code, int i)
700692
{
701693
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
702694
uint8_t *opcode_ptr = &instr->op.code;
703-
int opcode =*opcode_ptr;
695+
int opcode = *opcode_ptr;
704696
if (opcode == INSTRUMENTED_LINE) {
705697
_PyCoLineInstrumentationData *lines = &code->_co_monitoring->lines[i];
706698
opcode_ptr = &lines->original_opcode;
707699
opcode = *opcode_ptr;
708700
}
709701
if (opcode == INSTRUMENTED_INSTRUCTION) {
702+
assert(code->_co_monitoring->per_instruction_opcodes[i] > 0);
710703
return;
711704
}
712705
CHECK(opcode != 0);
@@ -1129,7 +1122,6 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
11291122

11301123
_PyCoMonitoringData *monitoring = code->_co_monitoring;
11311124
_PyCoLineInstrumentationData *line_data = &monitoring->lines[i];
1132-
uint8_t original_opcode = line_data->original_opcode;
11331125
if (tstate->tracing) {
11341126
goto done;
11351127
}
@@ -1212,7 +1204,9 @@ _Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
12121204
}
12131205
} while (tools);
12141206
Py_DECREF(line_obj);
1207+
uint8_t original_opcode;
12151208
done:
1209+
original_opcode = line_data->original_opcode;
12161210
assert(original_opcode != 0);
12171211
assert(original_opcode != INSTRUMENTED_LINE);
12181212
assert(_PyOpcode_Deopt[original_opcode] == original_opcode);
@@ -1655,7 +1649,9 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
16551649
i += _PyInstruction_GetLength(code, i);
16561650
}
16571651
}
1658-
1652+
#ifdef INSTRUMENT_DEBUG
1653+
sanity_check_instrumentation(code);
1654+
#endif
16591655
uint8_t new_line_tools = new_events.tools[PY_MONITORING_EVENT_LINE];
16601656
uint8_t new_per_instruction_tools = new_events.tools[PY_MONITORING_EVENT_INSTRUCTION];
16611657

0 commit comments

Comments
 (0)