@@ -455,6 +455,30 @@ mark_reachable(PyObject *op)
455455}
456456
457457#ifdef GC_DEBUG
458+ static bool
459+ validate_refcounts (const mi_heap_t * heap , const mi_heap_area_t * area ,
460+ void * block , size_t block_size , void * args )
461+ {
462+ PyObject * op = op_from_block (block , args , false);
463+ if (op == NULL ) {
464+ return true;
465+ }
466+
467+ _PyObject_ASSERT_WITH_MSG (op , !gc_is_unreachable (op ),
468+ "object should not be marked as unreachable yet" );
469+
470+ if (_Py_REF_IS_MERGED (op -> ob_ref_shared )) {
471+ _PyObject_ASSERT_WITH_MSG (op , op -> ob_tid == 0 ,
472+ "merged objects should have ob_tid == 0" );
473+ }
474+ else if (!_Py_IsImmortal (op )) {
475+ _PyObject_ASSERT_WITH_MSG (op , op -> ob_tid != 0 ,
476+ "unmerged objects should have ob_tid != 0" );
477+ }
478+
479+ return true;
480+ }
481+
458482static bool
459483validate_gc_objects (const mi_heap_t * heap , const mi_heap_area_t * area ,
460484 void * block , size_t block_size , void * args )
@@ -498,6 +522,19 @@ mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
498522 return true;
499523}
500524
525+ static bool
526+ restore_refs (const mi_heap_t * heap , const mi_heap_area_t * area ,
527+ void * block , size_t block_size , void * args )
528+ {
529+ PyObject * op = op_from_block (block , args , false);
530+ if (op == NULL ) {
531+ return true;
532+ }
533+ gc_restore_tid (op );
534+ gc_clear_unreachable (op );
535+ return true;
536+ }
537+
501538/* Return true if object has a pre-PEP 442 finalization method. */
502539static int
503540has_legacy_finalizer (PyObject * op )
@@ -549,6 +586,13 @@ static int
549586deduce_unreachable_heap (PyInterpreterState * interp ,
550587 struct collection_state * state )
551588{
589+
590+ #ifdef GC_DEBUG
591+ // Check that all objects are marked as unreachable and that the computed
592+ // reference count difference (stored in `ob_tid`) is non-negative.
593+ gc_visit_heaps (interp , & validate_refcounts , & state -> base );
594+ #endif
595+
552596 // Identify objects that are directly reachable from outside the GC heap
553597 // by computing the difference between the refcount and the number of
554598 // incoming references.
@@ -563,6 +607,8 @@ deduce_unreachable_heap(PyInterpreterState *interp,
563607 // Transitively mark reachable objects by clearing the
564608 // _PyGC_BITS_UNREACHABLE flag.
565609 if (gc_visit_heaps (interp , & mark_heap_visitor , & state -> base ) < 0 ) {
610+ // On out-of-memory, restore the refcounts and bail out.
611+ gc_visit_heaps (interp , & restore_refs , & state -> base );
566612 return -1 ;
567613 }
568614
@@ -1066,7 +1112,8 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
10661112 int err = deduce_unreachable_heap (interp , state );
10671113 if (err < 0 ) {
10681114 _PyEval_StartTheWorld (interp );
1069- goto error ;
1115+ PyErr_NoMemory ();
1116+ return ;
10701117 }
10711118
10721119 // Print debugging information.
@@ -1100,7 +1147,12 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
11001147 _PyEval_StartTheWorld (interp );
11011148
11021149 if (err < 0 ) {
1103- goto error ;
1150+ cleanup_worklist (& state -> unreachable );
1151+ cleanup_worklist (& state -> legacy_finalizers );
1152+ cleanup_worklist (& state -> wrcb_to_call );
1153+ cleanup_worklist (& state -> objs_to_decref );
1154+ PyErr_NoMemory ();
1155+ return ;
11041156 }
11051157
11061158 // Call tp_clear on objects in the unreachable set. This will cause
@@ -1110,15 +1162,6 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
11101162
11111163 // Append objects with legacy finalizers to the "gc.garbage" list.
11121164 handle_legacy_finalizers (state );
1113- return ;
1114-
1115- error :
1116- cleanup_worklist (& state -> unreachable );
1117- cleanup_worklist (& state -> legacy_finalizers );
1118- cleanup_worklist (& state -> wrcb_to_call );
1119- cleanup_worklist (& state -> objs_to_decref );
1120- PyErr_NoMemory ();
1121- PyErr_FormatUnraisable ("Out of memory during garbage collection" );
11221165}
11231166
11241167/* This is the main function. Read this to understand how the
0 commit comments