@@ -348,21 +348,38 @@ void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept
348348#endif
349349}
350350// TODO: Better state management about when we own the top frame.
351- int PythonState::tp_traverse (visitproc visit, void * arg, bool own_top_frame ) noexcept
351+ int PythonState::tp_traverse (visitproc visit, void * arg, bool visit_top_frame ) noexcept
352352{
353353 Py_VISIT (this ->_context .borrow ());
354- if (own_top_frame ) {
354+ if (visit_top_frame ) {
355355 Py_VISIT (this ->_top_frame .borrow ());
356356 }
357- #if GREENLET_PY314
358- // TODO: Should we be visiting the c_stack_refs objects?
359- // CPython uses a specific macro to do that which takes into
360- // account boxing and null values and then calls
361- // ``_PyGC_VisitStackRef``, but we don't have access to that, and
362- // we can't duplicate it ourself (because it compares
363- // ``visitproc`` to another function we can't access).
364- // The naive way of looping over c_stack_refs->ref and visiting
365- // those crashes the process (at least with GIL disabled).
357+ #if GREENLET_PY315
358+ // Visit the references held by our suspended frames.
359+ // This is important specially on free-threading where the
360+ // the suspended frames may contain deferred references to
361+ // objects, and if they are not traversed then the interpreter
362+ // can free objects early causing a use-after-free crash
363+ // at runtime exit.
364+ if (this ->_top_frame ) {
365+ for (_PyInterpreterFrame* iframe = this ->_top_frame ->f_frame ;
366+ iframe != nullptr ; iframe = iframe->previous ) {
367+ // Skip generator/coroutine frames; their object's traverse
368+ // already visits them (gen_traverse), so we'd double-count.
369+ // expose_frames leaves them in the ->previous chain.
370+ if (iframe->owner != FRAME_OWNED_BY_THREAD ) {
371+ continue ;
372+ }
373+ Py_VISIT (iframe->frame_obj );
374+ Py_VISIT (iframe->f_locals );
375+ _Py_VISIT_STACKREF (iframe->f_funcobj );
376+ _Py_VISIT_STACKREF (iframe->f_executable );
377+ int frame_result = _PyGC_VisitFrameStack (iframe, visit, arg);
378+ if (frame_result) {
379+ return frame_result;
380+ }
381+ }
382+ }
366383#endif
367384 // Note that we DO NOT visit ``delete_later``. Even if it's
368385 // non-null and we technically own a reference to it, its
0 commit comments