diff --git a/Include/internal/pycore_genobject.h b/Include/internal/pycore_genobject.h index c1fc3511f849ad..110cbc6f324a49 100644 --- a/Include/internal/pycore_genobject.h +++ b/Include/internal/pycore_genobject.h @@ -37,6 +37,17 @@ extern PyTypeObject _PyCoroWrapper_Type; extern PyTypeObject _PyAsyncGenWrappedValue_Type; extern PyTypeObject _PyAsyncGenAThrow_Type; +typedef struct _PyAsyncGenWrappedValue { + PyObject_HEAD + PyObject *agw_val; +} _PyAsyncGenWrappedValue; + +#define _PyAsyncGenWrappedValue_CheckExact(o) \ + Py_IS_TYPE(o, &_PyAsyncGenWrappedValue_Type) +#define _PyAsyncGenWrappedValue_CAST(op) \ + (assert(_PyAsyncGenWrappedValue_CheckExact(op)), \ + _Py_CAST(_PyAsyncGenWrappedValue*, (op))) + #ifdef __cplusplus } #endif diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 263e4e6f394155..b722ba4451658c 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -2157,6 +2157,30 @@ def callback(code, instruction_offset): sys.monitoring.restart_events() sys.monitoring.set_events(0, 0) + @test.support.requires_working_socket() # For asyncio + def test_yield_async_generator(self): + # gh-129013: Async generators have a special type that they + # use to yield values. This type shouldn't be exposed by PY_YIELD + # events. + asyncio = test.support.import_helper.import_module("asyncio") + + async def gen(): + yield 42 + + async def main(): + async for _ in gen(): + pass + + def handle_yield(code, offset, value): + self.assertEqual(value, 42) + + sys.monitoring.use_tool_id(0, "test") + sys.monitoring.register_callback(0, sys.monitoring.events.PY_YIELD, handle_yield) + sys.monitoring.set_events(0, sys.monitoring.events.PY_YIELD) + + asyncio.run(main()) + sys.monitoring.set_events(0, 0) + class TestOptimizer(MonitoringTestBase, unittest.TestCase): @@ -2403,3 +2427,6 @@ def test_enter_scope_two_events(self): finally: sys.monitoring.set_events(TEST_TOOL, 0) + +if __name__ == "__main__": + unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-14-15-36.gh-issue-129013.28rL6Z.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-14-15-36.gh-issue-129013.28rL6Z.rst new file mode 100644 index 00000000000000..de435d75f41c78 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-14-15-36.gh-issue-129013.28rL6Z.rst @@ -0,0 +1,2 @@ +Fix odd/unusable value when using :monitoring-event:`PY_YIELD` with an +:term:`asynchronous generator`. diff --git a/Objects/genobject.c b/Objects/genobject.c index 98b2c5004df8ac..f245ca79469210 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -1457,19 +1457,6 @@ typedef struct PyAsyncGenAThrow { } PyAsyncGenAThrow; -typedef struct _PyAsyncGenWrappedValue { - PyObject_HEAD - PyObject *agw_val; -} _PyAsyncGenWrappedValue; - - -#define _PyAsyncGenWrappedValue_CheckExact(o) \ - Py_IS_TYPE(o, &_PyAsyncGenWrappedValue_Type) -#define _PyAsyncGenWrappedValue_CAST(op) \ - (assert(_PyAsyncGenWrappedValue_CheckExact(op)), \ - _Py_CAST(_PyAsyncGenWrappedValue*, (op))) - - static int async_gen_traverse(PyObject *self, visitproc visit, void *arg) { diff --git a/Python/bytecodes.c b/Python/bytecodes.c index b2900ba951a52f..ac0adc574cfe1a 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -1321,9 +1321,16 @@ dummy_func( } tier1 op(_YIELD_VALUE_EVENT, (val -- val)) { + PyObject *yielded = PyStackRef_AsPyObjectBorrow(val); + if (_PyAsyncGenWrappedValue_CheckExact(yielded)) + { + /* gh-129013: Async generators have a special wrapper that they + yield. Don't expose that to the user. */ + yielded = _PyAsyncGenWrappedValue_CAST(yielded)->agw_val; + } int err = _Py_call_instrumentation_arg( tstate, PY_MONITORING_EVENT_PY_YIELD, - frame, this_instr, PyStackRef_AsPyObjectBorrow(val)); + frame, this_instr, yielded); if (err) { ERROR_NO_POP(); } diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index c75371d12b0ba1..79368a826eff97 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -7286,10 +7286,19 @@ // _YIELD_VALUE_EVENT { val = stack_pointer[-1]; + PyObject *yielded = PyStackRef_AsPyObjectBorrow(val); + if (_PyAsyncGenWrappedValue_CheckExact(yielded)) + { + /* gh-129013: Async generators have a special wrapper that they + yield. Don't expose that to the user. */ + _PyFrame_SetStackPointer(frame, stack_pointer); + yielded = _PyAsyncGenWrappedValue_CAST(yielded)->agw_val; + stack_pointer = _PyFrame_GetStackPointer(frame); + } _PyFrame_SetStackPointer(frame, stack_pointer); int err = _Py_call_instrumentation_arg( tstate, PY_MONITORING_EVENT_PY_YIELD, - frame, this_instr, PyStackRef_AsPyObjectBorrow(val)); + frame, this_instr, yielded); stack_pointer = _PyFrame_GetStackPointer(frame); if (err) { JUMP_TO_LABEL(error);