@@ -248,13 +248,14 @@ parse_task_name(
248248 * ============================================================================ */
249249
250250static int
251- handle_yield_from_frame (
251+ get_awaited_coro (
252252 RemoteUnwinderObject * unwinder ,
253253 uintptr_t gi_iframe_addr ,
254254 uintptr_t gen_type_addr ,
255- PyObject * render_to ,
256- size_t depth
255+ uintptr_t * next_coro_addr
257256) {
257+ * next_coro_addr = 0 ;
258+
258259 // Read the entire interpreter frame at once
259260 char iframe [SIZEOF_INTERP_FRAME ];
260261 int err = _Py_RemoteDebug_PagedReadRemoteMemory (
@@ -310,12 +311,7 @@ handle_yield_from_frame(
310311 doesn't match the type of whatever it points to
311312 in its cr_await.
312313 */
313- err = parse_coro_chain (unwinder , gi_await_addr , render_to ,
314- depth + 1 );
315- if (err ) {
316- set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to parse coroutine chain in yield_from" );
317- return -1 ;
318- }
314+ * next_coro_addr = gi_await_addr ;
319315 }
320316 }
321317 }
@@ -327,63 +323,94 @@ int
327323parse_coro_chain (
328324 RemoteUnwinderObject * unwinder ,
329325 uintptr_t coro_address ,
330- PyObject * render_to ,
331- size_t depth
326+ PyObject * render_to
332327) {
333328 assert ((void * )coro_address != NULL );
334329
335- if (depth >= MAX_FRAME_CHAIN_DEPTH ) {
336- PyErr_SetString (PyExc_RuntimeError ,
337- "Too many coroutine frames (possible infinite loop)" );
338- set_exception_cause (unwinder , PyExc_RuntimeError ,
339- "Coroutine chain depth limit exceeded" );
340- return -1 ;
341- }
330+ /* Brent's cycle detection algorithm */
331+ uintptr_t checkpoint_addr = 0 ;
332+ uintptr_t checkpoint_code = 0 ;
333+ size_t window = 1 ;
334+ size_t steps = 0 ;
342335
343- // Read the entire generator object at once
344- char gen_object [SIZEOF_GEN_OBJ ];
345- int err = _Py_RemoteDebug_PagedReadRemoteMemory (
346- & unwinder -> handle ,
347- coro_address ,
348- SIZEOF_GEN_OBJ ,
349- gen_object );
350- if (err < 0 ) {
351- set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to read generator object in coro chain" );
352- return -1 ;
353- }
336+ for (size_t depth = 0 ; (void * )coro_address != NULL ; depth ++ ) {
337+ if (depth >= MAX_FRAME_CHAIN_DEPTH ) {
338+ PyErr_SetString (PyExc_RuntimeError ,
339+ "Too many coroutine frames (possible infinite loop)" );
340+ set_exception_cause (unwinder , PyExc_RuntimeError ,
341+ "Coroutine chain depth limit exceeded" );
342+ return -1 ;
343+ }
354344
355- int8_t frame_state = GET_MEMBER (int8_t , gen_object , unwinder -> debug_offsets .gen_object .gi_frame_state );
356- if (frame_state == FRAME_CLEARED ) {
357- return 0 ;
358- }
345+ // Read the entire generator object at once
346+ char gen_object [SIZEOF_GEN_OBJ ];
347+ int err = _Py_RemoteDebug_PagedReadRemoteMemory (
348+ & unwinder -> handle ,
349+ coro_address ,
350+ SIZEOF_GEN_OBJ ,
351+ gen_object );
352+ if (err < 0 ) {
353+ set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to read generator object in coro chain" );
354+ return -1 ;
355+ }
359356
360- uintptr_t gen_type_addr = GET_MEMBER (uintptr_t , gen_object , unwinder -> debug_offsets .pyobject .ob_type );
357+ int8_t frame_state = GET_MEMBER (int8_t , gen_object , unwinder -> debug_offsets .gen_object .gi_frame_state );
358+ if (frame_state == FRAME_CLEARED ) {
359+ return 0 ;
360+ }
361361
362- PyObject * name = NULL ;
362+ uintptr_t gen_type_addr = GET_MEMBER ( uintptr_t , gen_object , unwinder -> debug_offsets . pyobject . ob_type ) ;
363363
364- // Parse the previous frame using the gi_iframe from local copy
365- uintptr_t prev_frame ;
366- uintptr_t gi_iframe_addr = coro_address + (uintptr_t )unwinder -> debug_offsets .gen_object .gi_iframe ;
367- uintptr_t address_of_code_object = 0 ;
368- if (parse_frame_object (unwinder , & name , gi_iframe_addr , & address_of_code_object , & prev_frame ) < 0 ) {
369- set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to parse frame object in coro chain" );
370- return -1 ;
371- }
364+ PyObject * name = NULL ;
372365
373- if (!name ) {
374- return 0 ;
375- }
366+ // Parse the previous frame using the gi_iframe from local copy
367+ uintptr_t prev_frame ;
368+ uintptr_t gi_iframe_addr = coro_address + (uintptr_t )unwinder -> debug_offsets .gen_object .gi_iframe ;
369+ uintptr_t address_of_code_object = 0 ;
370+ if (parse_frame_object (unwinder , & name , gi_iframe_addr , & address_of_code_object , & prev_frame ) < 0 ) {
371+ set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to parse frame object in coro chain" );
372+ return -1 ;
373+ }
376374
377- if (PyList_Append (render_to , name )) {
375+ if (!name ) {
376+ return 0 ;
377+ }
378+
379+ if (PyList_Append (render_to , name )) {
380+ Py_DECREF (name );
381+ set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to append frame to coro chain" );
382+ return -1 ;
383+ }
378384 Py_DECREF (name );
379- set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to append frame to coro chain" );
380- return -1 ;
381- }
382- Py_DECREF (name );
383385
384- if (frame_state == FRAME_SUSPENDED_YIELD_FROM ) {
385- return handle_yield_from_frame (unwinder , gi_iframe_addr , gen_type_addr ,
386- render_to , depth );
386+ if (coro_address == checkpoint_addr
387+ && address_of_code_object == checkpoint_code )
388+ {
389+ PyErr_SetString (PyExc_RuntimeError ,
390+ "Cycle detected in coroutine chain" );
391+ set_exception_cause (unwinder , PyExc_RuntimeError ,
392+ "Coroutine chain contains a cycle" );
393+ return -1 ;
394+ }
395+ if (steps == window ) {
396+ checkpoint_addr = coro_address ;
397+ checkpoint_code = address_of_code_object ;
398+ window *= 2 ;
399+ steps = 0 ;
400+ }
401+ steps ++ ;
402+
403+ if (frame_state != FRAME_SUSPENDED_YIELD_FROM ) {
404+ return 0 ;
405+ }
406+
407+ uintptr_t next_coro_addr ;
408+ if (get_awaited_coro (unwinder , gi_iframe_addr , gen_type_addr ,
409+ & next_coro_addr ) < 0 ) {
410+ set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to parse coroutine chain in yield_from" );
411+ return -1 ;
412+ }
413+ coro_address = next_coro_addr ;
387414 }
388415
389416 return 0 ;
@@ -429,7 +456,7 @@ create_task_result(
429456 coro_addr = GET_MEMBER_NO_TAG (uintptr_t , task_obj , unwinder -> async_debug_offsets .asyncio_task_object .task_coro );
430457
431458 if ((void * )coro_addr != NULL ) {
432- if (parse_coro_chain (unwinder , coro_addr , call_stack , 0 ) < 0 ) {
459+ if (parse_coro_chain (unwinder , coro_addr , call_stack ) < 0 ) {
433460 set_exception_cause (unwinder , PyExc_RuntimeError , "Failed to parse coroutine chain" );
434461 goto error ;
435462 }
0 commit comments