QMenu: guard for destruction when emitting action signals

If a slot connected to a QMenu-action destroys the QMenu, then
we must not touch data members in subsequent code, and instead return
immediately.

We cannot use QBoolBlocker here, as that would reset the data
member of QMenuPrivate even when trying to return early.

Fixes: QTBUG-106718
Change-Id: I6b5ea471b1bf1f9864e1384382100f8f6c01346f
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
(cherry picked from commit 52ce4d2d29db8a44fa2fa817cab9ebe969db082e)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2023-02-11 17:53:32 +01:00 committed by Qt Cherry-pick Bot
parent 03542e413f
commit 2800481b0d
2 changed files with 31 additions and 3 deletions

View File

@ -1387,9 +1387,18 @@ bool QMenuPrivate::mouseEventTaken(QMouseEvent *e)
void QMenuPrivate::activateCausedStack(const QList<QPointer<QWidget>> &causedStack, QAction *action, void QMenuPrivate::activateCausedStack(const QList<QPointer<QWidget>> &causedStack, QAction *action,
QAction::ActionEvent action_e, bool self) QAction::ActionEvent action_e, bool self)
{ {
QBoolBlocker guard(activationRecursionGuard); Q_Q(QMenu);
// can't use QBoolBlocker here
const bool activationRecursionGuardReset = activationRecursionGuard;
activationRecursionGuard = true;
QPointer<QMenu> guard(q);
if (self) if (self)
action->activate(action_e); action->activate(action_e);
if (!guard)
return;
auto boolBlocker = qScopeGuard([this, activationRecursionGuardReset]{
activationRecursionGuard = activationRecursionGuardReset;
});
for(int i = 0; i < causedStack.size(); ++i) { for(int i = 0; i < causedStack.size(); ++i) {
QPointer<QWidget> widget = causedStack.at(i); QPointer<QWidget> widget = causedStack.at(i);
@ -1465,9 +1474,10 @@ void QMenuPrivate::activateAction(QAction *action, QAction::ActionEvent action_e
#endif #endif
} }
QPointer<QMenu> thisGuard(q);
activateCausedStack(causedStack, action, action_e, self); activateCausedStack(causedStack, action, action_e, self);
if (!thisGuard)
return;
if (action_e == QAction::Hover) { if (action_e == QAction::Hover) {
#if QT_CONFIG(accessibility) #if QT_CONFIG(accessibility)

View File

@ -110,6 +110,7 @@ private slots:
void tearOffMenuNotDisplayed(); void tearOffMenuNotDisplayed();
void QTBUG_61039_menu_shortcuts(); void QTBUG_61039_menu_shortcuts();
void screenOrientationChangedCloseMenu(); void screenOrientationChangedCloseMenu();
void deleteWhenTriggered();
protected slots: protected slots:
void onActivated(QAction*); void onActivated(QAction*);
@ -2011,5 +2012,22 @@ void tst_QMenu::screenOrientationChangedCloseMenu()
QTRY_COMPARE(menu.isVisible(),false); QTRY_COMPARE(menu.isVisible(),false);
} }
/*
Verify that deleting the menu in a slot connected to an
action's triggered signal doesn't crash.
QTBUG-106718
*/
void tst_QMenu::deleteWhenTriggered()
{
QPointer<QMenu> menu = new QMenu;
QAction *action = menu->addAction("Action", [&menu]{
delete menu;
});
menu->popup(QGuiApplication::primaryScreen()->availableGeometry().center());
menu->setActiveAction(action);
QTest::keyClick(menu, Qt::Key_Return);
QTRY_VERIFY(!menu);
}
QTEST_MAIN(tst_QMenu) QTEST_MAIN(tst_QMenu)
#include "tst_qmenu.moc" #include "tst_qmenu.moc"