| 835 | } |
| 836 | |
| 837 | void PanadapterStack::prepareShutdown() |
| 838 | { |
| 839 | if (m_shutdownPrepared) { |
| 840 | return; |
| 841 | } |
| 842 | m_shutdownPrepared = true; |
| 843 | |
| 844 | setShuttingDown(true); |
| 845 | saveFloatingState(); |
| 846 | |
| 847 | // Explicitly delete floating windows rather than calling close(). |
| 848 | // close() without WA_DeleteOnClose only hides the window, leaving it alive |
| 849 | // as a Qt::Window child of MainWindow. When MainWindow's QWidget::~QWidget() |
| 850 | // later tears down its QRhi, Qt fires the QRhiWidgetPrivate cleanup callbacks |
| 851 | // for those still-parented-but-hidden floating windows in an order that can |
| 852 | // invalidate QRhiWidgetPrivate before the callback returns → crash at 0x1e2. |
| 853 | // Deleting fw explicitly runs QRhiWidget::~QRhiWidget() → removeCleanupCallback |
| 854 | // while the QRhi is still valid, so no stale callbacks remain when MainWindow |
| 855 | // tears down (#2495 exit crash on macOS). |
| 856 | const QList<QString> floatingIds = m_floatingWindows.keys(); |
| 857 | for (const QString& panId : floatingIds) { |
| 858 | PanFloatingWindow* fw = m_floatingWindows.take(panId); |
| 859 | if (!fw) continue; |
| 860 | fw->saveWindowGeometry(); |
| 861 | if (PanadapterApplet* applet = fw->applet()) { |
| 862 | if (SpectrumWidget* sw = applet->spectrumWidget()) { |
| 863 | sw->prepareForShutdown(); |
| 864 | } |
| 865 | } |
| 866 | m_pans.remove(panId); |
| 867 | delete fw; |
| 868 | } |
| 869 | |
| 870 | // Explicitly delete docked applets so ~QRhiWidget() runs (and calls |
| 871 | // removeCleanupCallback) while MainWindow's QRhi is still alive. |
| 872 | // If we leave applets alive, Qt's destructor chain for QWidget destroys |
| 873 | // the QRhi *before* ~QObject()::deleteChildren() deletes the child |
| 874 | // SpectrumWidgets — QRhi::runCleanup() then fires against QRhiWidgetPrivate |
| 875 | // objects that are still live but have internal Qt fields in a stale state |
| 876 | // → crash at $0_cleanup +24 on exit (#2495 macOS). |
| 877 | const QList<QString> dockedIds = m_pans.keys(); |
| 878 | for (const QString& panId : dockedIds) { |
| 879 | PanadapterApplet* applet = m_pans.take(panId); |
| 880 | if (!applet) continue; |
| 881 | if (SpectrumWidget* sw = applet->spectrumWidget()) { |
| 882 | sw->prepareForShutdown(); |
| 883 | } |
| 884 | delete applet; |
| 885 | } |
| 886 | } |
| 887 | |
| 888 | void PanadapterStack::saveFloatingState() const |
| 889 | { |
no test coverage detected