@@ -513,19 +513,24 @@ parse_task(
513513 * TASK AWAITED_BY PROCESSING
514514 * ============================================================================ */
515515
516- // Forward declaration for mutual recursion
517- static int process_waiter_task (RemoteUnwinderObject * unwinder , uintptr_t key_addr , void * context );
518-
519- // Carries the recursion depth so a cyclic or corrupted remote awaited_by graph
520- // cannot drive unbounded C recursion and overflow the debugger's stack.
516+ // The awaited_by graph is walked with an explicit, heap-allocated work-stack
517+ // rather than C recursion. A deeply nested -- or, in a corrupted or
518+ // concurrently-mutated remote process, cyclic -- chain would otherwise drive
519+ // unbounded recursion and overflow the debugger's own C stack: each level holds
520+ // a SIZEOF_TASK_OBJ buffer (see process_task_awaited_by), so even a few hundred
521+ // levels can exhaust a 1 MB stack. MAX_TASK_AWAITED_BY_DEPTH bounds the walk so
522+ // a cycle terminates.
521523typedef struct {
522- PyObject * result ;
524+ uintptr_t addr ;
523525 int depth ;
524- } waiter_context_t ;
526+ } awaited_by_entry_t ;
525527
526- static int process_task_and_waiters_impl (
527- RemoteUnwinderObject * unwinder , uintptr_t task_addr , PyObject * result ,
528- int depth );
528+ typedef struct {
529+ awaited_by_entry_t * items ;
530+ Py_ssize_t size ;
531+ Py_ssize_t capacity ;
532+ int current_depth ;
533+ } awaited_by_stack_t ;
529534
530535// Processor function for parsing tasks in sets
531536static int
@@ -670,49 +675,88 @@ process_single_task_node(
670675}
671676
672677static int
673- process_task_and_waiters_impl (
678+ awaited_by_stack_push (
674679 RemoteUnwinderObject * unwinder ,
675- uintptr_t task_addr ,
676- PyObject * result ,
680+ awaited_by_stack_t * stack ,
681+ uintptr_t addr ,
677682 int depth
678683) {
679- // First, add this task to the result
680- if (process_single_task_node (unwinder , task_addr , NULL , result ) < 0 ) {
681- return -1 ;
684+ if (stack -> size >= stack -> capacity ) {
685+ Py_ssize_t new_capacity = stack -> capacity ? stack -> capacity * 2 : 16 ;
686+ awaited_by_entry_t * new_items = PyMem_Realloc (
687+ stack -> items , (size_t )new_capacity * sizeof (awaited_by_entry_t ));
688+ if (new_items == NULL ) {
689+ PyErr_NoMemory ();
690+ set_exception_cause (unwinder , PyExc_MemoryError ,
691+ "Failed to grow awaited_by work-stack" );
692+ return -1 ;
693+ }
694+ stack -> items = new_items ;
695+ stack -> capacity = new_capacity ;
682696 }
683-
684- // Now find all tasks that are waiting for this task and process them
685- waiter_context_t ctx = {result , depth };
686- return process_task_awaited_by (unwinder , task_addr , process_waiter_task , & ctx );
687- }
688-
689- int
690- process_task_and_waiters (
691- RemoteUnwinderObject * unwinder ,
692- uintptr_t task_addr ,
693- PyObject * result
694- ) {
695- return process_task_and_waiters_impl (unwinder , task_addr , result , 0 );
697+ stack -> items [stack -> size ].addr = addr ;
698+ stack -> items [stack -> size ].depth = depth ;
699+ stack -> size ++ ;
700+ return 0 ;
696701}
697702
698- // Processor function for task waiters
703+ // set_entry_processor_func: enqueue a task waiting on the one currently being
704+ // expanded, one level deeper. The depth bound makes a cyclic or corrupted
705+ // awaited_by graph terminate instead of looping forever.
699706static int
700- process_waiter_task (
707+ push_awaited_by_waiter (
701708 RemoteUnwinderObject * unwinder ,
702709 uintptr_t key_addr ,
703710 void * context
704711) {
705- waiter_context_t * ctx = (waiter_context_t * )context ;
706- if (ctx -> depth >= MAX_TASK_AWAITED_BY_DEPTH ) {
712+ awaited_by_stack_t * stack = (awaited_by_stack_t * )context ;
713+ if (stack -> current_depth >= MAX_TASK_AWAITED_BY_DEPTH ) {
707714 PyErr_SetString (PyExc_RuntimeError ,
708715 "Task awaited_by chain is too deep or cyclic "
709716 "(corrupted remote memory)" );
710717 set_exception_cause (unwinder , PyExc_RuntimeError ,
711- "Task awaited_by recursion limit exceeded" );
718+ "Task awaited_by depth limit exceeded" );
712719 return -1 ;
713720 }
714- return process_task_and_waiters_impl (unwinder , key_addr , ctx -> result ,
715- ctx -> depth + 1 );
721+ return awaited_by_stack_push (unwinder , stack , key_addr ,
722+ stack -> current_depth + 1 );
723+ }
724+
725+ // Drain the work-stack: append each task node to result, then enqueue the
726+ // tasks waiting on it. Depth-first, with no C recursion over the graph.
727+ static int
728+ drain_awaited_by_stack (
729+ RemoteUnwinderObject * unwinder ,
730+ PyObject * result ,
731+ awaited_by_stack_t * stack
732+ ) {
733+ while (stack -> size > 0 ) {
734+ awaited_by_entry_t entry = stack -> items [-- stack -> size ];
735+ if (process_single_task_node (unwinder , entry .addr , NULL , result ) < 0 ) {
736+ return -1 ;
737+ }
738+ stack -> current_depth = entry .depth ;
739+ if (process_task_awaited_by (unwinder , entry .addr ,
740+ push_awaited_by_waiter , stack ) < 0 ) {
741+ return -1 ;
742+ }
743+ }
744+ return 0 ;
745+ }
746+
747+ int
748+ process_task_and_waiters (
749+ RemoteUnwinderObject * unwinder ,
750+ uintptr_t task_addr ,
751+ PyObject * result
752+ ) {
753+ awaited_by_stack_t stack = {0 };
754+ int result_code = -1 ;
755+ if (awaited_by_stack_push (unwinder , & stack , task_addr , 0 ) == 0 ) {
756+ result_code = drain_awaited_by_stack (unwinder , result , & stack );
757+ }
758+ PyMem_Free (stack .items );
759+ return result_code ;
716760}
717761
718762/* ============================================================================
@@ -1008,9 +1052,17 @@ process_running_task_chain(
10081052 return -1 ;
10091053 }
10101054
1011- // Now find all tasks that are waiting for this task and process them
1012- waiter_context_t ctx = {result , 0 };
1013- if (process_task_awaited_by (unwinder , running_task_addr , process_waiter_task , & ctx ) < 0 ) {
1055+ // Now find all tasks that are waiting for this task and process them with
1056+ // the same iterative, heap-stacked walk as process_task_and_waiters (the
1057+ // running task itself is already recorded via the frame chain above).
1058+ awaited_by_stack_t stack = {0 };
1059+ int waiters_code = process_task_awaited_by (unwinder , running_task_addr ,
1060+ push_awaited_by_waiter , & stack );
1061+ if (waiters_code == 0 ) {
1062+ waiters_code = drain_awaited_by_stack (unwinder , result , & stack );
1063+ }
1064+ PyMem_Free (stack .items );
1065+ if (waiters_code < 0 ) {
10141066 return -1 ;
10151067 }
10161068
0 commit comments