Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 45f5aa8

Browse filesBrowse files
authored
GH-103082: Filter LINE events in VM, to simplify tool implementation. (GH-104387)
When monitoring LINE events, instrument all instructions that can have a predecessor on a different line. Then check that the a new line has been hit in the instrumentation code. This brings the behavior closer to that of 3.11, simplifying implementation and porting of tools.
1 parent 19ee53d commit 45f5aa8
Copy full SHA for 45f5aa8

File tree

Expand file treeCollapse file tree

16 files changed

+252
-158
lines changed
Filter options
Expand file treeCollapse file tree

16 files changed

+252
-158
lines changed

‎Include/internal/pycore_frame.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_frame.h
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ struct _frame {
1919
struct _PyInterpreterFrame *f_frame; /* points to the frame data */
2020
PyObject *f_trace; /* Trace function */
2121
int f_lineno; /* Current line number. Only valid if non-zero */
22-
int f_last_traced_line; /* The last line traced for this frame */
2322
char f_trace_lines; /* Emit per-line trace events? */
2423
char f_trace_opcodes; /* Emit per-opcode trace events? */
2524
char f_fast_as_locals; /* Have the fast locals of this frame been converted to a dict? */

‎Include/internal/pycore_instruments.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_instruments.h
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ _Py_call_instrumentation(PyThreadState *tstate, int event,
6969

7070
extern int
7171
_Py_call_instrumentation_line(PyThreadState *tstate, _PyInterpreterFrame* frame,
72-
_Py_CODEUNIT *instr);
72+
_Py_CODEUNIT *instr, _Py_CODEUNIT *prev);
7373

7474
extern int
7575
_Py_call_instrumentation_instruction(
7676
PyThreadState *tstate, _PyInterpreterFrame* frame, _Py_CODEUNIT *instr);
7777

78-
int
78+
_Py_CODEUNIT *
7979
_Py_call_instrumentation_jump(
8080
PyThreadState *tstate, int event,
8181
_PyInterpreterFrame *frame, _Py_CODEUNIT *instr, _Py_CODEUNIT *target);
@@ -100,6 +100,7 @@ extern int
100100
_Py_Instrumentation_GetLine(PyCodeObject *code, int index);
101101

102102
extern PyObject _PyInstrumentation_MISSING;
103+
extern PyObject _PyInstrumentation_DISABLE;
103104

104105
#ifdef __cplusplus
105106
}

‎Lib/test/test_monitoring.py

Copy file name to clipboardExpand all lines: Lib/test/test_monitoring.py
+61-2Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ def test_lines_loop(self):
524524
sys.monitoring.set_events(TEST_TOOL, 0)
525525
sys.monitoring.register_callback(TEST_TOOL, E.LINE, None)
526526
start = LineMonitoringTest.test_lines_loop.__code__.co_firstlineno
527-
self.assertEqual(events, [start+7, 21, 22, 22, 21, start+8])
527+
self.assertEqual(events, [start+7, 21, 22, 21, 22, 21, start+8])
528528
finally:
529529
sys.monitoring.set_events(TEST_TOOL, 0)
530530
sys.monitoring.register_callback(TEST_TOOL, E.LINE, None)
@@ -1050,6 +1050,8 @@ def func3():
10501050
def line_from_offset(code, offset):
10511051
for start, end, line in code.co_lines():
10521052
if start <= offset < end:
1053+
if line is None:
1054+
return f"[offset={offset}]"
10531055
return line - code.co_firstlineno
10541056
return -1
10551057

@@ -1072,9 +1074,20 @@ class BranchRecorder(JumpRecorder):
10721074
event_type = E.BRANCH
10731075
name = "branch"
10741076

1077+
class ReturnRecorder:
1078+
1079+
event_type = E.PY_RETURN
1080+
1081+
def __init__(self, events):
1082+
self.events = events
1083+
1084+
def __call__(self, code, offset, val):
1085+
self.events.append(("return", val))
1086+
10751087

10761088
JUMP_AND_BRANCH_RECORDERS = JumpRecorder, BranchRecorder
10771089
JUMP_BRANCH_AND_LINE_RECORDERS = JumpRecorder, BranchRecorder, LineRecorder
1090+
FLOW_AND_LINE_RECORDERS = JumpRecorder, BranchRecorder, LineRecorder, ExceptionRecorder, ReturnRecorder
10781091

10791092
class TestBranchAndJumpEvents(CheckEvents):
10801093
maxDiff = None
@@ -1098,7 +1111,6 @@ def func():
10981111
('jump', 'func', 4, 2),
10991112
('branch', 'func', 2, 2)])
11001113

1101-
11021114
self.check_events(func, recorders = JUMP_BRANCH_AND_LINE_RECORDERS, expected = [
11031115
('line', 'check_events', 10),
11041116
('line', 'func', 1),
@@ -1108,15 +1120,62 @@ def func():
11081120
('branch', 'func', 3, 6),
11091121
('line', 'func', 6),
11101122
('jump', 'func', 6, 2),
1123+
('line', 'func', 2),
11111124
('branch', 'func', 2, 2),
11121125
('line', 'func', 3),
11131126
('branch', 'func', 3, 4),
11141127
('line', 'func', 4),
11151128
('jump', 'func', 4, 2),
1129+
('line', 'func', 2),
11161130
('branch', 'func', 2, 2),
1131+
('line', 'check_events', 11)])
1132+
1133+
def test_except_star(self):
1134+
1135+
class Foo:
1136+
def meth(self):
1137+
pass
1138+
1139+
def func():
1140+
try:
1141+
try:
1142+
raise KeyError
1143+
except* Exception as e:
1144+
f = Foo(); f.meth()
1145+
except KeyError:
1146+
pass
1147+
1148+
1149+
self.check_events(func, recorders = JUMP_BRANCH_AND_LINE_RECORDERS, expected = [
1150+
('line', 'check_events', 10),
1151+
('line', 'func', 1),
11171152
('line', 'func', 2),
1153+
('line', 'func', 3),
1154+
('line', 'func', 4),
1155+
('branch', 'func', 4, 4),
1156+
('line', 'func', 5),
1157+
('line', 'meth', 1),
1158+
('jump', 'func', 5, 5),
1159+
('jump', 'func', 5, '[offset=114]'),
1160+
('branch', 'func', '[offset=120]', '[offset=122]'),
11181161
('line', 'check_events', 11)])
11191162

1163+
self.check_events(func, recorders = FLOW_AND_LINE_RECORDERS, expected = [
1164+
('line', 'check_events', 10),
1165+
('line', 'func', 1),
1166+
('line', 'func', 2),
1167+
('line', 'func', 3),
1168+
('raise', KeyError),
1169+
('line', 'func', 4),
1170+
('branch', 'func', 4, 4),
1171+
('line', 'func', 5),
1172+
('line', 'meth', 1),
1173+
('return', None),
1174+
('jump', 'func', 5, 5),
1175+
('jump', 'func', 5, '[offset=114]'),
1176+
('branch', 'func', '[offset=120]', '[offset=122]'),
1177+
('return', None),
1178+
('line', 'check_events', 11)])
11201179

11211180
class TestSetGetEvents(MonitoringTestBase, unittest.TestCase):
11221181

‎Lib/test/test_pdb.py

Copy file name to clipboardExpand all lines: Lib/test/test_pdb.py
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1793,8 +1793,9 @@ def test_pdb_issue_gh_101517():
17931793
... 'continue'
17941794
... ]):
17951795
... test_function()
1796-
> <doctest test.test_pdb.test_pdb_issue_gh_101517[0]>(5)test_function()
1797-
-> import pdb; pdb.Pdb(nosigint=True, readrc=False).set_trace()
1796+
--Return--
1797+
> <doctest test.test_pdb.test_pdb_issue_gh_101517[0]>(None)test_function()->None
1798+
-> Warning: lineno is None
17981799
(Pdb) continue
17991800
"""
18001801

‎Lib/test/test_sys.py

Copy file name to clipboardExpand all lines: Lib/test/test_sys.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,7 @@ class C(object): pass
14461446
def func():
14471447
return sys._getframe()
14481448
x = func()
1449-
check(x, size('3Pii3c7P2ic??2P'))
1449+
check(x, size('3Pi3c7P2ic??2P'))
14501450
# function
14511451
def func(): pass
14521452
check(func, size('14Pi'))

‎Lib/test/test_sys_settrace.py

Copy file name to clipboardExpand all lines: Lib/test/test_sys_settrace.py
-1Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2867,6 +2867,5 @@ def func(arg = 1):
28672867
sys.settrace(None)
28682868

28692869

2870-
28712870
if __name__ == "__main__":
28722871
unittest.main()
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Change behavior of ``sys.monitoring.events.LINE`` events in
2+
``sys.monitoring``: Line events now occur when a new line is reached
3+
dynamically, instead of using a static approximation, as before. This makes
4+
the behavior very similar to that of "line" events in ``sys.settrace``. This
5+
should ease porting of tools from 3.11 to 3.12.

‎Objects/frameobject.c

Copy file name to clipboardExpand all lines: Objects/frameobject.c
-3Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -831,7 +831,6 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
831831
start_stack = pop_value(start_stack);
832832
}
833833
/* Finally set the new lasti and return OK. */
834-
f->f_last_traced_line = new_lineno;
835834
f->f_lineno = 0;
836835
f->f_frame->prev_instr = _PyCode_CODE(f->f_frame->f_code) + best_addr;
837836
return 0;
@@ -854,7 +853,6 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
854853
}
855854
if (v != f->f_trace) {
856855
Py_XSETREF(f->f_trace, Py_XNewRef(v));
857-
f->f_last_traced_line = -1;
858856
}
859857
return 0;
860858
}
@@ -1056,7 +1054,6 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
10561054
f->f_trace_opcodes = 0;
10571055
f->f_fast_as_locals = 0;
10581056
f->f_lineno = 0;
1059-
f->f_last_traced_line = -1;
10601057
return f;
10611058
}
10621059

‎Python/bytecodes.c

Copy file name to clipboardExpand all lines: Python/bytecodes.c
-22Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3288,28 +3288,6 @@ dummy_func(
32883288
assert(oparg >= 2);
32893289
}
32903290

3291-
inst(INSTRUMENTED_LINE, ( -- )) {
3292-
_Py_CODEUNIT *here = next_instr-1;
3293-
_PyFrame_SetStackPointer(frame, stack_pointer);
3294-
int original_opcode = _Py_call_instrumentation_line(
3295-
tstate, frame, here);
3296-
stack_pointer = _PyFrame_GetStackPointer(frame);
3297-
if (original_opcode < 0) {
3298-
next_instr = here+1;
3299-
goto error;
3300-
}
3301-
next_instr = frame->prev_instr;
3302-
if (next_instr != here) {
3303-
DISPATCH();
3304-
}
3305-
if (_PyOpcode_Caches[original_opcode]) {
3306-
_PyBinaryOpCache *cache = (_PyBinaryOpCache *)(next_instr+1);
3307-
INCREMENT_ADAPTIVE_COUNTER(cache->counter);
3308-
}
3309-
opcode = original_opcode;
3310-
DISPATCH_GOTO();
3311-
}
3312-
33133291
inst(INSTRUMENTED_INSTRUCTION, ( -- )) {
33143292
int next_opcode = _Py_call_instrumentation_instruction(
33153293
tstate, frame, next_instr-1);

‎Python/ceval.c

Copy file name to clipboardExpand all lines: Python/ceval.c
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,41 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
775775

776776
#include "generated_cases.c.h"
777777

778+
/* INSTRUMENTED_LINE has to be here, rather than in bytecodes.c,
779+
* because it needs to capture frame->prev_instr before it is updated,
780+
* as happens in the standard instruction prologue.
781+
*/
782+
#if USE_COMPUTED_GOTOS
783+
TARGET_INSTRUMENTED_LINE:
784+
#else
785+
case INSTRUMENTED_LINE:
786+
#endif
787+
{
788+
_Py_CODEUNIT *prev = frame->prev_instr;
789+
_Py_CODEUNIT *here = frame->prev_instr = next_instr;
790+
_PyFrame_SetStackPointer(frame, stack_pointer);
791+
int original_opcode = _Py_call_instrumentation_line(
792+
tstate, frame, here, prev);
793+
stack_pointer = _PyFrame_GetStackPointer(frame);
794+
if (original_opcode < 0) {
795+
next_instr = here+1;
796+
goto error;
797+
}
798+
next_instr = frame->prev_instr;
799+
if (next_instr != here) {
800+
DISPATCH();
801+
}
802+
if (_PyOpcode_Caches[original_opcode]) {
803+
_PyBinaryOpCache *cache = (_PyBinaryOpCache *)(next_instr+1);
804+
/* Prevent the underlying instruction from specializing
805+
* and overwriting the instrumentation. */
806+
INCREMENT_ADAPTIVE_COUNTER(cache->counter);
807+
}
808+
opcode = original_opcode;
809+
DISPATCH_GOTO();
810+
}
811+
812+
778813
#if USE_COMPUTED_GOTOS
779814
_unknown_opcode:
780815
#else

‎Python/ceval_macros.h

Copy file name to clipboardExpand all lines: Python/ceval_macros.h
+2-3Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,10 @@ do { \
334334
#define INSTRUMENTED_JUMP(src, dest, event) \
335335
do { \
336336
_PyFrame_SetStackPointer(frame, stack_pointer); \
337-
int err = _Py_call_instrumentation_jump(tstate, event, frame, src, dest); \
337+
next_instr = _Py_call_instrumentation_jump(tstate, event, frame, src, dest); \
338338
stack_pointer = _PyFrame_GetStackPointer(frame); \
339-
if (err) { \
339+
if (next_instr == NULL) { \
340340
next_instr = (dest)+1; \
341341
goto error; \
342342
} \
343-
next_instr = frame->prev_instr; \
344343
} while (0);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.