Skip to content

Commit 0ae60b6

Browse files
authored
GH-113486: Do not emit spurious PY_UNWIND events for optimized calls to classes. (GH-113680)
1 parent ed6ea3e commit 0ae60b6

File tree

6 files changed

+64
-28
lines changed

6 files changed

+64
-28
lines changed

Include/cpython/code.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ struct PyCodeObject _PyCode_DEF(1);
208208
#define CO_FUTURE_GENERATOR_STOP 0x800000
209209
#define CO_FUTURE_ANNOTATIONS 0x1000000
210210

211+
#define CO_NO_MONITORING_EVENTS 0x2000000
212+
211213
/* This should be defined if a future statement modifies the syntax.
212214
For example, when a keyword is added.
213215
*/

Lib/test/test_monitoring.py

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ class UnwindRecorder(ExceptionRecorder):
750750
event_type = E.PY_UNWIND
751751

752752
def __call__(self, code, offset, exc):
753-
self.events.append(("unwind", type(exc)))
753+
self.events.append(("unwind", type(exc), code.co_name))
754754

755755
class ExceptionHandledRecorder(ExceptionRecorder):
756756

@@ -766,8 +766,27 @@ class ThrowRecorder(ExceptionRecorder):
766766
def __call__(self, code, offset, exc):
767767
self.events.append(("throw", type(exc)))
768768

769-
class ExceptionMonitoringTest(CheckEvents):
769+
class CallRecorder:
770+
771+
event_type = E.CALL
772+
773+
def __init__(self, events):
774+
self.events = events
775+
776+
def __call__(self, code, offset, func, arg):
777+
self.events.append(("call", func.__name__, arg))
778+
779+
class ReturnRecorder:
780+
781+
event_type = E.PY_RETURN
782+
783+
def __init__(self, events):
784+
self.events = events
770785

786+
def __call__(self, code, offset, val):
787+
self.events.append(("return", code.co_name, val))
788+
789+
class ExceptionMonitoringTest(CheckEvents):
771790

772791
exception_recorders = (
773792
ExceptionRecorder,
@@ -936,26 +955,48 @@ def func():
936955
)
937956
self.assertEqual(events[0], ("throw", IndexError))
938957

939-
class LineRecorder:
958+
def test_no_unwind_for_shim_frame(self):
940959

941-
event_type = E.LINE
960+
class B:
961+
def __init__(self):
962+
raise ValueError()
963+
964+
def f():
965+
try:
966+
return B()
967+
except ValueError:
968+
pass
942969

970+
for _ in range(100):
971+
f()
972+
recorders = (
973+
ReturnRecorder,
974+
UnwindRecorder
975+
)
976+
events = self.get_events(f, TEST_TOOL, recorders)
977+
adaptive_insts = dis.get_instructions(f, adaptive=True)
978+
self.assertIn(
979+
"CALL_ALLOC_AND_ENTER_INIT",
980+
[i.opname for i in adaptive_insts]
981+
)
982+
#There should be only one unwind event
983+
expected = [
984+
('unwind', ValueError, '__init__'),
985+
('return', 'f', None),
986+
]
943987

944-
def __init__(self, events):
945-
self.events = events
988+
self.assertEqual(events, expected)
946989

947-
def __call__(self, code, line):
948-
self.events.append(("line", code.co_name, line - code.co_firstlineno))
990+
class LineRecorder:
949991

950-
class CallRecorder:
992+
event_type = E.LINE
951993

952-
event_type = E.CALL
953994

954995
def __init__(self, events):
955996
self.events = events
956997

957-
def __call__(self, code, offset, func, arg):
958-
self.events.append(("call", func.__name__, arg))
998+
def __call__(self, code, line):
999+
self.events.append(("line", code.co_name, line - code.co_firstlineno))
9591000

9601001
class CEventRecorder:
9611002

@@ -1351,15 +1392,6 @@ class BranchRecorder(JumpRecorder):
13511392
event_type = E.BRANCH
13521393
name = "branch"
13531394

1354-
class ReturnRecorder:
1355-
1356-
event_type = E.PY_RETURN
1357-
1358-
def __init__(self, events):
1359-
self.events = events
1360-
1361-
def __call__(self, code, offset, val):
1362-
self.events.append(("return", val))
13631395

13641396

13651397
JUMP_AND_BRANCH_RECORDERS = JumpRecorder, BranchRecorder
@@ -1449,11 +1481,11 @@ def func():
14491481
('branch', 'func', 4, 4),
14501482
('line', 'func', 5),
14511483
('line', 'meth', 1),
1452-
('return', None),
1484+
('return', 'meth', None),
14531485
('jump', 'func', 5, 5),
14541486
('jump', 'func', 5, '[offset=114]'),
14551487
('branch', 'func', '[offset=120]', '[offset=124]'),
1456-
('return', None),
1488+
('return', 'func', None),
14571489
('line', 'get_events', 11)])
14581490

14591491
class TestLoadSuperAttr(CheckEvents):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No longer issue spurious ``PY_UNWIND`` events for optimized calls to classes.

Python/ceval.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,9 @@ do_monitor_exc(PyThreadState *tstate, _PyInterpreterFrame *frame,
21112111
_Py_CODEUNIT *instr, int event)
21122112
{
21132113
assert(event < _PY_MONITORING_UNGROUPED_EVENTS);
2114+
if (_PyFrame_GetCode(frame)->co_flags & CO_NO_MONITORING_EVENTS) {
2115+
return 0;
2116+
}
21142117
PyObject *exc = PyErr_GetRaisedException();
21152118
assert(exc != NULL);
21162119
int err = _Py_call_instrumentation_arg(tstate, event, frame, instr, exc);

Python/instrumentation.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,13 +1576,11 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
15761576
}
15771577
_Py_Executors_InvalidateDependency(interp, code);
15781578
int code_len = (int)Py_SIZE(code);
1579-
/* code->_co_firsttraceable >= code_len indicates
1580-
* that no instrumentation can be inserted.
1581-
* Exit early to avoid creating instrumentation
1579+
/* Exit early to avoid creating instrumentation
15821580
* data for potential statically allocated code
15831581
* objects.
15841582
* See https://github.com/python/cpython/issues/108390 */
1585-
if (code->_co_firsttraceable >= code_len) {
1583+
if (code->co_flags & CO_NO_MONITORING_EVENTS) {
15861584
return 0;
15871585
}
15881586
if (update_instrumentation_data(code, interp)) {

Python/specialize.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2534,7 +2534,7 @@ const struct _PyCode_DEF(8) _Py_InitCleanup = {
25342534
.co_consts = (PyObject *)&_Py_SINGLETON(tuple_empty),
25352535
.co_names = (PyObject *)&_Py_SINGLETON(tuple_empty),
25362536
.co_exceptiontable = (PyObject *)&_Py_SINGLETON(bytes_empty),
2537-
.co_flags = CO_OPTIMIZED,
2537+
.co_flags = CO_OPTIMIZED | CO_NO_MONITORING_EVENTS,
25382538
.co_localsplusnames = (PyObject *)&_Py_SINGLETON(tuple_empty),
25392539
.co_localspluskinds = (PyObject *)&_Py_SINGLETON(bytes_empty),
25402540
.co_filename = &_Py_ID(__init__),

0 commit comments

Comments
 (0)