From 22a1db378c5c381272362c5b2f68ac78a368e136 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 13 May 2022 11:24:45 +0100 Subject: [PATCH] GH-92236: Remove spurious "line" event when starting coroutine or generator. (GH-92722) --- Lib/test/test_sys_settrace.py | 53 +++++++++++++++++++ ...2-05-12-13-23-19.gh-issue-92236.sDRzUe.rst | 2 + Modules/_testcapimodule.c | 46 ++++++++++++++++ Python/ceval.c | 6 +++ 4 files changed, 107 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-05-12-13-23-19.gh-issue-92236.sDRzUe.rst diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py index 715204edc44..9cc6bcfcab5 100644 --- a/Lib/test/test_sys_settrace.py +++ b/Lib/test/test_sys_settrace.py @@ -7,6 +7,7 @@ import difflib import gc from functools import wraps import asyncio +from test.support import import_helper support.requires_working_socket(module=True) @@ -1473,6 +1474,58 @@ class TraceTestCase(unittest.TestCase): (3, 'return'), (1, 'return')]) + @support.cpython_only + def test_no_line_event_after_creating_generator(self): + # Spurious line events before call events only show up with C tracer + + # Skip this test if the _testcapi module isn't available. + _testcapi = import_helper.import_module('_testcapi') + + def gen(): + yield 1 + + def func(): + for _ in ( + gen() + ): + pass + + EXPECTED_EVENTS = [ + (0, 'call'), + (2, 'line'), + (1, 'line'), + (-3, 'call'), + (-2, 'line'), + (-2, 'return'), + (4, 'line'), + (1, 'line'), + (-2, 'call'), + (-2, 'return'), + (1, 'return'), + ] + + # C level events should be the same as expected and the same as Python level. + + events = [] + # Turning on and off tracing must be on same line to avoid unwanted LINE events. + _testcapi.settrace_to_record(events); func(); sys.settrace(None) + start_line = func.__code__.co_firstlineno + events = [ + (line-start_line, EVENT_NAMES[what]) + for (what, line, arg) in events + ] + self.assertEqual(events, EXPECTED_EVENTS) + + self.run_and_compare(func, EXPECTED_EVENTS) + + +EVENT_NAMES = [ + 'call', + 'exception', + 'line', + 'return' +] + class SkipLineEventsTraceTestCase(TraceTestCase): """Repeat the trace tests, but with per-line events skipped""" diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-12-13-23-19.gh-issue-92236.sDRzUe.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-12-13-23-19.gh-issue-92236.sDRzUe.rst new file mode 100644 index 00000000000..fe482d505c6 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-12-13-23-19.gh-issue-92236.sDRzUe.rst @@ -0,0 +1,2 @@ +Remove spurious "LINE" event when starting a generator or coroutine, visible +tracing functions implemented in C. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 4371bf7da0e..363dbbb666c 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5787,6 +5787,51 @@ test_code_api(PyObject *self, PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } +static int +record_func(PyObject *obj, PyFrameObject *f, int what, PyObject *arg) +{ + assert(PyList_Check(obj)); + PyObject *what_obj = NULL; + PyObject *line_obj = NULL; + PyObject *tuple = NULL; + int res = -1; + what_obj = PyLong_FromLong(what); + if (what_obj == NULL) { + goto error; + } + int line = PyFrame_GetLineNumber(f); + line_obj = PyLong_FromLong(line); + if (line_obj == NULL) { + goto error; + } + tuple = PyTuple_Pack(3, what_obj, line_obj, arg); + if (tuple == NULL) { + goto error; + } + PyTuple_SET_ITEM(tuple, 0, what_obj); + if (PyList_Append(obj, tuple)) { + goto error; + } + res = 0; +error: + Py_XDECREF(what_obj); + Py_XDECREF(line_obj); + Py_XDECREF(tuple); + return res; +} + +static PyObject * +settrace_to_record(PyObject *self, PyObject *list) +{ + + if (!PyList_Check(list)) { + PyErr_SetString(PyExc_TypeError, "argument must be a list"); + return NULL; + } + PyEval_SetTrace(record_func, list); + Py_RETURN_NONE; +} + static PyObject *negative_dictoffset(PyObject *, PyObject *); static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); static PyObject *getargs_s_hash_int(PyObject *, PyObject *, PyObject*); @@ -6076,6 +6121,7 @@ static PyMethodDef TestMethods[] = { {"frame_getlasti", frame_getlasti, METH_O, NULL}, {"get_feature_macros", get_feature_macros, METH_NOARGS, NULL}, {"test_code_api", test_code_api, METH_NOARGS, NULL}, + {"settrace_to_record", settrace_to_record, METH_O, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Python/ceval.c b/Python/ceval.c index f6b07b4b28e..c73218fcf30 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -5680,6 +5680,12 @@ handle_eval_breaker: TRACE_FUNCTION_ENTRY(); DTRACE_FUNCTION_ENTRY(); break; + case POP_TOP: + if (_Py_OPCODE(next_instr[-1]) == RETURN_GENERATOR) { + /* Frame not fully initialized */ + break; + } + /* fall through */ default: /* line-by-line tracing support */ if (PyDTrace_LINE_ENABLED()) {