Skip to content

Commit bf06c2d

Browse files
committed
Initial implementation of support for 'code like' objects in sys.monitoring
1 parent 77d2fd4 commit bf06c2d

File tree

8 files changed

+160
-32
lines changed

8 files changed

+160
-32
lines changed

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ struct _Py_global_strings {
117117
STRUCT_FOR_ID(__fspath__)
118118
STRUCT_FOR_ID(__ge__)
119119
STRUCT_FOR_ID(__get__)
120+
STRUCT_FOR_ID(__get_local_events__)
120121
STRUCT_FOR_ID(__getattr__)
121122
STRUCT_FOR_ID(__getattribute__)
122123
STRUCT_FOR_ID(__getinitargs__)
@@ -203,6 +204,7 @@ struct _Py_global_strings {
203204
STRUCT_FOR_ID(__rtruediv__)
204205
STRUCT_FOR_ID(__rxor__)
205206
STRUCT_FOR_ID(__set__)
207+
STRUCT_FOR_ID(__set_local_events__)
206208
STRUCT_FOR_ID(__set_name__)
207209
STRUCT_FOR_ID(__setattr__)
208210
STRUCT_FOR_ID(__setitem__)

Include/internal/pycore_instruments.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ typedef uint32_t _PyMonitoringEventSet;
2929
PyObject *_PyMonitoring_RegisterCallback(int tool_id, int event_id, PyObject *obj);
3030

3131
int _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events);
32-
int _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet events);
33-
int _PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events);
32+
int _PyMonitoring_SetLocalEvents(PyObject *codelike, int tool_id, _PyMonitoringEventSet events);
33+
int _PyMonitoring_GetLocalEvents(PyObject *codelike, int tool_id, _PyMonitoringEventSet *events);
3434

3535
extern int
3636
_Py_call_instrumentation(PyThreadState *tstate, int event,

Include/internal/pycore_runtime_init_generated.h

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_monitoring.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,6 @@ def check_events(self, func, expected, tool=TEST_TOOL, recorders=()):
14121412
for recorder in recorders:
14131413
sys.monitoring.register_callback(tool, recorder.event_type, None)
14141414

1415-
14161415
def test_simple(self):
14171416

14181417
def func1():
@@ -1461,6 +1460,27 @@ def test_set_non_local_event(self):
14611460
with self.assertRaises(ValueError):
14621461
sys.monitoring.set_local_events(TEST_TOOL, just_call.__code__, E.RAISE)
14631462

1463+
1464+
def test_code_like(self):
1465+
class CodeLike:
1466+
1467+
def __init__(self):
1468+
self.events = [ 0 ] * 8
1469+
1470+
def __get_local_events__(self, tool):
1471+
return self.events[tool]
1472+
1473+
def __set_local_events__(self, tool, events):
1474+
self.events[tool] = events
1475+
1476+
codelike = CodeLike()
1477+
tool = TEST_TOOL
1478+
for events in ((E.LINE | E.PY_START), (E.BRANCH_LEFT | E.BRANCH_RIGHT), 0):
1479+
sys.monitoring.set_local_events(tool, codelike, events)
1480+
self.assertEqual(codelike.__get_local_events__(tool), events)
1481+
self.assertEqual(sys.monitoring.get_local_events(tool, codelike), events)
1482+
1483+
14641484
def line_from_offset(code, offset):
14651485
for start, end, line in code.co_lines():
14661486
if start <= offset < end:
@@ -2062,7 +2082,13 @@ def f():
20622082
pass
20632083

20642084
def test_get_local_events_uninitialized(self):
2065-
self.assertEqual(sys.monitoring.get_local_events(TEST_TOOL, self.f.__code__), 0)
2085+
with self.assertRaises(ValueError):
2086+
sys.monitoring.get_local_events(TEST_TOOL, self.f.__code__)
2087+
sys.monitoring.use_tool_id(TEST_TOOL, "test unitialized")
2088+
try:
2089+
self.assertEqual(sys.monitoring.get_local_events(TEST_TOOL, self.f.__code__), 0)
2090+
finally:
2091+
sys.monitoring.free_tool_id(TEST_TOOL)
20662092

20672093
class TestRegressions(MonitoringTestBase, unittest.TestCase):
20682094

Objects/codeobject.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,73 @@ code_positionsiterator(PyObject *self, PyObject* Py_UNUSED(args))
15401540
return (PyObject*)pi;
15411541
}
15421542

1543+
static PyObject*
1544+
code_offset_to_position(PyObject *self, PyObject* offset)
1545+
{
1546+
PyCodeObject *code = (PyCodeObject*)self;
1547+
int addrq;
1548+
int err = PyLong_AsInt32(offset, &addrq);
1549+
if (err != 0) {
1550+
return NULL;
1551+
}
1552+
positionsiterator pi;
1553+
pi.pi_code = (PyCodeObject*)Py_NewRef(code);
1554+
_PyCode_InitAddressRange(code, &pi.pi_range);
1555+
pi.pi_offset = pi.pi_range.ar_end;
1556+
while (pi.pi_range.ar_end <= addrq) {
1557+
if (pi.pi_offset >= pi.pi_range.ar_end) {
1558+
assert(pi.pi_offset == pi.pi_range.ar_end);
1559+
if (at_end(&pi.pi_range)) {
1560+
return NULL;
1561+
}
1562+
advance_with_locations(&pi.pi_range, &pi.pi_endline, &pi.pi_column, &pi.pi_endcolumn);
1563+
}
1564+
pi.pi_offset += 2;
1565+
}
1566+
return Py_BuildValue("(O&O&O&O&)",
1567+
_source_offset_converter, &pi.pi_range.ar_line,
1568+
_source_offset_converter, &pi.pi_endline,
1569+
_source_offset_converter, &pi.pi_column,
1570+
_source_offset_converter, &pi.pi_endcolumn);
1571+
}
1572+
1573+
static PyObject*
1574+
code_get_local_events(PyObject *self, PyObject* tool) {
1575+
int tool_id;
1576+
int err = PyLong_AsInt32(tool, &tool_id);
1577+
if (err != 0) {
1578+
return NULL;
1579+
}
1580+
_PyMonitoringEventSet events;
1581+
err = _PyMonitoring_GetLocalEvents(self, tool_id, &events);
1582+
if (err != 0) {
1583+
return NULL;
1584+
}
1585+
return PyLong_FromUInt32(events);
1586+
}
1587+
1588+
static PyObject*
1589+
code_set_local_events(PyObject *self, PyObject *const *args, Py_ssize_t nargs) {
1590+
int tool_id;
1591+
_PyMonitoringEventSet events;
1592+
if (!_PyArg_CheckPositional("__set_local_events__", nargs, 2, 2)) {
1593+
return NULL;
1594+
}
1595+
int err = PyLong_AsInt32(args[0], &tool_id);
1596+
if (err != 0) {
1597+
return NULL;
1598+
}
1599+
err = PyLong_AsUInt32(args[1], &events);
1600+
if (err != 0) {
1601+
return NULL;
1602+
}
1603+
err = _PyMonitoring_SetLocalEvents(self, tool_id, events);
1604+
if (err != 0) {
1605+
return NULL;
1606+
}
1607+
Py_RETURN_NONE;
1608+
}
1609+
15431610

15441611
/******************
15451612
* "extra" frame eval info (see PEP 523)
@@ -2394,6 +2461,9 @@ static struct PyMethodDef code_methods[] = {
23942461
CODE__VARNAME_FROM_OPARG_METHODDEF
23952462
{"__replace__", _PyCFunction_CAST(code_replace), METH_FASTCALL|METH_KEYWORDS,
23962463
PyDoc_STR("__replace__($self, /, **changes)\n--\n\nThe same as replace().")},
2464+
{"co_offset_to_position", code_offset_to_position, METH_O},
2465+
{"__get_local_events__", code_get_local_events, METH_O},
2466+
{"__set_local_events__", _PyCFunction_CAST(code_set_local_events), METH_FASTCALL},
23972467
{NULL, NULL} /* sentinel */
23982468
};
23992469

@@ -2802,6 +2872,9 @@ _PyCode_Fini(PyInterpreterState *interp)
28022872
#endif
28032873
}
28042874

2875+
2876+
2877+
28052878
#ifdef Py_GIL_DISABLED
28062879

28072880
// Thread-local bytecode (TLBC)

Python/instrumentation.c

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,8 +2035,29 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events)
20352035
}
20362036

20372037
int
2038-
_PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet events)
2038+
_PyMonitoring_SetLocalEvents(PyObject *codelike, int tool_id, _PyMonitoringEventSet events)
20392039
{
2040+
if (!PyCode_Check(codelike)) {
2041+
PyObject *tool = PyLong_FromInt32(tool_id);
2042+
assert(tool != NULL);
2043+
PyObject *events_obj = PyLong_FromUInt32(events);
2044+
if (events_obj == NULL) {
2045+
return -1;
2046+
}
2047+
PyObject *args[3] = {
2048+
codelike,
2049+
tool,
2050+
events_obj
2051+
};
2052+
PyObject *res = PyObject_VectorcallMethod(&_Py_ID(__set_local_events__), args, 3, NULL);
2053+
Py_DECREF(events_obj);
2054+
if (res == NULL) {
2055+
return -1;
2056+
}
2057+
Py_DECREF(res);
2058+
return 0;
2059+
}
2060+
PyCodeObject *code = (PyCodeObject *)codelike;
20402061
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
20412062
PyInterpreterState *interp = _PyInterpreterState_GET();
20422063
assert(events < (1 << _PY_MONITORING_LOCAL_EVENTS));
@@ -2073,9 +2094,24 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
20732094
}
20742095

20752096
int
2076-
_PyMonitoring_GetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEventSet *events)
2097+
_PyMonitoring_GetLocalEvents(PyObject *codelike, int tool_id, _PyMonitoringEventSet *events)
20772098
{
20782099
assert(0 <= tool_id && tool_id < PY_MONITORING_TOOL_IDS);
2100+
if (!PyCode_Check(codelike)) {
2101+
PyObject *tool = PyLong_FromInt32(tool_id);
2102+
assert(tool != NULL);
2103+
PyObject *res = PyObject_CallMethodOneArg(codelike, &_Py_ID(__get_local_events__), tool);
2104+
if (res == NULL) {
2105+
return -1;
2106+
}
2107+
int err = PyLong_AsUInt32(res, events);
2108+
if (err < 0) {
2109+
return -1;
2110+
}
2111+
Py_DECREF(res);
2112+
return 0;
2113+
}
2114+
PyCodeObject *code = (PyCodeObject *)codelike;
20792115
PyInterpreterState *interp = _PyInterpreterState_GET();
20802116
if (check_tool(interp, tool_id)) {
20812117
return -1;
@@ -2362,24 +2398,10 @@ monitoring_get_local_events_impl(PyObject *module, int tool_id,
23622398
PyObject *code)
23632399
/*[clinic end generated code: output=d3e92c1c9c1de8f9 input=bb0f927530386a94]*/
23642400
{
2365-
if (!PyCode_Check(code)) {
2366-
PyErr_Format(
2367-
PyExc_TypeError,
2368-
"code must be a code object"
2369-
);
2370-
return -1;
2371-
}
2372-
if (check_valid_tool(tool_id)) {
2373-
return -1;
2374-
}
2375-
_PyMonitoringEventSet event_set = 0;
2376-
_PyCoMonitoringData *data = ((PyCodeObject *)code)->_co_monitoring;
2377-
if (data != NULL) {
2378-
for (int e = 0; e < _PY_MONITORING_LOCAL_EVENTS; e++) {
2379-
if ((data->local_monitors.tools[e] >> tool_id) & 1) {
2380-
event_set |= (1 << e);
2381-
}
2382-
}
2401+
_PyMonitoringEventSet event_set;
2402+
int err = _PyMonitoring_GetLocalEvents(code, tool_id, &event_set);
2403+
if (err < 0) {
2404+
return err;
23832405
}
23842406
return event_set;
23852407
}
@@ -2399,13 +2421,6 @@ monitoring_set_local_events_impl(PyObject *module, int tool_id,
23992421
PyObject *code, int event_set)
24002422
/*[clinic end generated code: output=68cc755a65dfea99 input=5655ecd78d937a29]*/
24012423
{
2402-
if (!PyCode_Check(code)) {
2403-
PyErr_Format(
2404-
PyExc_TypeError,
2405-
"code must be a code object"
2406-
);
2407-
return NULL;
2408-
}
24092424
if (check_valid_tool(tool_id)) {
24102425
return NULL;
24112426
}
@@ -2423,7 +2438,7 @@ monitoring_set_local_events_impl(PyObject *module, int tool_id,
24232438
return NULL;
24242439
}
24252440

2426-
if (_PyMonitoring_SetLocalEvents((PyCodeObject*)code, tool_id, event_set)) {
2441+
if (_PyMonitoring_SetLocalEvents(code, tool_id, event_set)) {
24272442
return NULL;
24282443
}
24292444
Py_RETURN_NONE;

0 commit comments

Comments
 (0)