@@ -67,6 +67,10 @@ struct collection_state {
6767    PyInterpreterState  * interp ;
6868    GCState  * gcstate ;
6969    _PyGC_Reason  reason ;
70+     // GH-129236: If we see a an active frame without a valid stack pointer, 
71+     // we can't collect objects with deferred references because we may not 
72+     // see all references. 
73+     int  skip_deferred_objects ;
7074    Py_ssize_t  collected ;
7175    Py_ssize_t  uncollectable ;
7276    Py_ssize_t  long_lived_total ;
@@ -413,9 +417,6 @@ gc_visit_heaps(PyInterpreterState *interp, mi_block_visit_fun *visitor,
413417static  inline  void 
414418gc_visit_stackref (_PyStackRef  stackref )
415419{
416-     // Note: we MUST check that it is deferred before checking the rest. 
417-     // Otherwise we might read into invalid memory due to non-deferred references 
418-     // being dead already. 
419420    if  (PyStackRef_IsDeferred (stackref ) &&  !PyStackRef_IsNull (stackref )) {
420421        PyObject  * obj  =  PyStackRef_AsPyObjectBorrow (stackref );
421422        if  (_PyObject_GC_IS_TRACKED (obj ) &&  !gc_is_frozen (obj )) {
@@ -426,20 +427,26 @@ gc_visit_stackref(_PyStackRef stackref)
426427
427428// Add 1 to the gc_refs for every deferred reference on each thread's stack. 
428429static  void 
429- gc_visit_thread_stacks (PyInterpreterState  * interp )
430+ gc_visit_thread_stacks (PyInterpreterState  * interp ,  struct   collection_state   * state )
430431{
431432    _Py_FOR_EACH_TSTATE_BEGIN (interp , p ) {
432433        for  (_PyInterpreterFrame  * f  =  p -> current_frame ; f  !=  NULL ; f  =  f -> previous ) {
433-             PyObject  * executable  =  PyStackRef_AsPyObjectBorrow (f -> f_executable );
434-             if  (executable  ==  NULL  ||  !PyCode_Check (executable )) {
434+             if  (f -> owner  >= FRAME_OWNED_BY_INTERPRETER ) {
435+                 continue ;
436+             }
437+ 
438+             _PyStackRef  * top  =  f -> stackpointer ;
439+             if  (top  ==  NULL ) {
440+                 // GH-129236: The stackpointer may be NULL. Skip this frame 
441+                 // and don't collect objects with deferred references. 
442+                 state -> skip_deferred_objects  =  1 ;
435443                continue ;
436444            }
437445
438-             PyCodeObject  * co  =  (PyCodeObject  * )executable ;
439-             int  max_stack  =  co -> co_nlocalsplus  +  co -> co_stacksize ;
440446            gc_visit_stackref (f -> f_executable );
441-             for  (int  i  =  0 ; i  <  max_stack ; i ++ ) {
442-                 gc_visit_stackref (f -> localsplus [i ]);
447+             while  (top  !=  f -> localsplus ) {
448+                 -- top ;
449+                 gc_visit_stackref (* top );
443450            }
444451        }
445452    }
@@ -519,10 +526,7 @@ gc_abort_mark_alive(PyInterpreterState *interp,
519526static  int 
520527gc_visit_stackref_mark_alive (_PyObjectStack  * stack , _PyStackRef  stackref )
521528{
522-     // Note: we MUST check that it is deferred before checking the rest. 
523-     // Otherwise we might read into invalid memory due to non-deferred references 
524-     // being dead already. 
525-     if  (PyStackRef_IsDeferred (stackref ) &&  !PyStackRef_IsNull (stackref )) {
529+     if  (!PyStackRef_IsNull (stackref )) {
526530        PyObject  * op  =  PyStackRef_AsPyObjectBorrow (stackref );
527531        if  (mark_alive_stack_push (op , stack ) <  0 ) {
528532            return  -1 ;
@@ -534,27 +538,37 @@ gc_visit_stackref_mark_alive(_PyObjectStack *stack, _PyStackRef stackref)
534538static  int 
535539gc_visit_thread_stacks_mark_alive (PyInterpreterState  * interp , _PyObjectStack  * stack )
536540{
541+     int  err  =  0 ;
537542    _Py_FOR_EACH_TSTATE_BEGIN (interp , p ) {
538543        for  (_PyInterpreterFrame  * f  =  p -> current_frame ; f  !=  NULL ; f  =  f -> previous ) {
539-             PyObject  * executable  =  PyStackRef_AsPyObjectBorrow (f -> f_executable );
540-             if  (executable  ==  NULL  ||  !PyCode_Check (executable )) {
544+             if  (f -> owner  >= FRAME_OWNED_BY_INTERPRETER ) {
541545                continue ;
542546            }
543547
544-             PyCodeObject  * co  =  (PyCodeObject  * )executable ;
545-             int  max_stack  =  co -> co_nlocalsplus  +  co -> co_stacksize ;
548+             if  (f -> stackpointer  ==  NULL ) {
549+                 // GH-129236: The stackpointer may be NULL in cases where 
550+                 // the GC is run during a PyStackRef_CLOSE() call. Skip this 
551+                 // frame for now. 
552+                 continue ;
553+             }
554+ 
555+             _PyStackRef  * top  =  f -> stackpointer ;
546556            if  (gc_visit_stackref_mark_alive (stack , f -> f_executable ) <  0 ) {
547-                 return  -1 ;
557+                 err  =  -1 ;
558+                 goto exit ;
548559            }
549-             for  (int  i  =  0 ; i  <  max_stack ; i ++ ) {
550-                 if  (gc_visit_stackref_mark_alive (stack , f -> localsplus [i ]) <  0 ) {
551-                     return  -1 ;
560+             while  (top  !=  f -> localsplus ) {
561+                 -- top ;
562+                 if  (gc_visit_stackref_mark_alive (stack , * top ) <  0 ) {
563+                     err  =  -1 ;
564+                     goto exit ;
552565                }
553566            }
554567        }
555568    }
569+ exit :
556570    _Py_FOR_EACH_TSTATE_END (interp );
557-     return  0 ;
571+     return  err ;
558572}
559573#endif  // GC_MARK_ALIVE_STACKS 
560574#endif  // GC_ENABLE_MARK_ALIVE 
@@ -789,14 +803,22 @@ mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
789803        return  true;
790804    }
791805
792-     if  (gc_is_alive (op )) {
806+     _PyObject_ASSERT_WITH_MSG (op , gc_get_refs (op ) >= 0 ,
807+                                   "refcount is too small" );
808+ 
809+     if  (gc_is_alive (op ) ||  !gc_is_unreachable (op )) {
810+         // Object was already marked as reachable. 
793811        return  true;
794812    }
795813
796-     _PyObject_ASSERT_WITH_MSG (op , gc_get_refs (op ) >= 0 ,
797-                                   "refcount is too small" );
814+     // GH-129236: Hack to avoid collect objects with deferred references 
815+     // if we see a frame without a valid stack pointer. 
816+     struct  collection_state  * state  =  (struct  collection_state  * )args ;
817+     if  (state -> skip_deferred_objects  &&  _PyObject_HasDeferredRefcount (op )) {
818+         gc_add_refs (op , 1 );
819+     }
798820
799-     if  (gc_is_unreachable ( op )  &&   gc_get_refs (op ) !=  0 ) {
821+     if  (gc_get_refs (op ) !=  0 ) {
800822        // Object is reachable but currently marked as unreachable. 
801823        // Mark it as reachable and traverse its pointers to find 
802824        // any other object that may be directly reachable from it. 
@@ -985,7 +1007,7 @@ deduce_unreachable_heap(PyInterpreterState *interp,
9851007#endif 
9861008
9871009    // Visit the thread stacks to account for any deferred references. 
988-     gc_visit_thread_stacks (interp );
1010+     gc_visit_thread_stacks (interp ,  state );
9891011
9901012    // Transitively mark reachable objects by clearing the 
9911013    // _PyGC_BITS_UNREACHABLE flag. 
0 commit comments