QLineEdit: fix UB (invalid downcast) in Private::removeAction()
QLineEdit remembers whether a QWidget size-widget came from a QWidgetAction or is just one of its own QLineEditIconButtons. On removal of the action, if QWidgetAction, it will ask the action to deal with its widget, calling QWidgetAction::releaseWidget(), otherwise it just deletes the QLineEditIconButton itself. This is fine if the action is being removed from the line edit, but not immediately deleted. But if the action is being deleted while its widget is still in a QLineEdit, then the QLineEdit is informed of this only by ~QAction(), at which point the QWidgetAction has ceased to exist and the cast, in QLineEditPrivate::removeAction(), to QWidgetAction is UB. Says UBSan: qlineedit_p.cpp:656:10: runtime error: downcast of address 0x6020000c3950 which does not point to an object of type 'QWidgetAction' 0x6020000c3950: note: object is of type 'QAction' 00 00 00 00 70 57 49 81 3d 7f 00 00 80 bc 0c 00 50 61 00 00 03 02 00 00 10 00 00 00 a6 01 00 71 ^~~~~~~~~~~~~~~~~~~~~~~ vptr for 'QAction' #0 0x7f3d93910686 in QLineEditPrivate::removeAction(QAction*) qlineedit_p.cpp:656 #1 0x7f3d938b44bc in QLineEdit::event(QEvent*) qlineedit.cpp:1469 #2 0x7f3d91c50211 in QApplicationPrivate::notify_helper(QObject*, QEvent*) qapplication.cpp:3309 #3 0x7f3d91cd160a in QApplication::notify(QObject*, QEvent*) qapplication.cpp:3259 #4 0x7f3d697f6ada in QCoreApplication::notifyInternal2(QObject*, QEvent*) qcoreapplication.cpp:1111 #5 0x7f3d697f94e3 in QCoreApplication::sendEvent(QObject*, QEvent*) qcoreapplication.cpp:1551 #6 0x7f3d92345199 in QWidget::removeAction(QAction*) qwidget.cpp:3215 #7 0x7f3d92e47202 in QtWidgetsActionPrivate::destroy() qaction_widgets.cpp:35 #8 0x7f3d7f75d5fb in QAction::~QAction() qaction.cpp:451 #9 0x7f3d92e4cc76 in QWidgetAction::~QWidgetAction() qwidgetaction.cpp:94 #10 0x7f3d92e4e965 in QWidgetAction::~QWidgetAction() qwidgetaction.cpp:94 #11 0x558c993da3b0 in tst_QLineEdit::sideWidgets() tst_qlineedit.cpp:4693 To fix, check the dynamic type of the action (using qobject_cast) before calling QWidgetAction::releaseWidget(). If the cast fails, the QWidgetAction will have dealt with the widget itself, in its own dtor, so QLineEdit needn't do anything. Unfortunately, fixing this centrally by just dragging destroy() down from ~QAction() to ~QWidgetAction() causes use-after-free in other test functions of tst_QLineEdit, so I deciced to fix this at the QLineEdit level only. Amends 9ce12cc8de940cdd450a28f4bd079acfc3621aa3. Pick-to: 6.8 6.5 5.15 Change-Id: I3c514dbd1f1a4e1510df3dd9ac67b4bab50e63e9 Reviewed-by: Axel Spoerl <axel.spoerl@qt.io> (cherry picked from commit bf62a9762b04689094d1411168ad785b2152cba4) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
799237eaae
commit
82cf1c0c16
@ -652,10 +652,16 @@ void QLineEditPrivate::removeAction(QAction *action)
|
||||
SideWidgetEntryList &list = location.position == QLineEdit::TrailingPosition ? trailingSideWidgets : leadingSideWidgets;
|
||||
SideWidgetEntry entry = list[location.index];
|
||||
list.erase(list.begin() + location.index);
|
||||
if (entry.flags & SideWidgetCreatedByWidgetAction)
|
||||
static_cast<QWidgetAction *>(entry.action)->releaseWidget(entry.widget);
|
||||
else
|
||||
if (entry.flags & SideWidgetCreatedByWidgetAction) {
|
||||
// If the cast fails, the QAction is in the process of being deleted
|
||||
// and has already ceased to be a QWidgetAction; in the process, it
|
||||
// will release its widget itself, and calling releaseWidget() here
|
||||
// would be UB, so don't:
|
||||
if (const auto a = qobject_cast<QWidgetAction*>(entry.action))
|
||||
a->releaseWidget(entry.widget);
|
||||
} else {
|
||||
delete entry.widget;
|
||||
}
|
||||
positionSideWidgets();
|
||||
if (!hasSideWidgets()) // Last widget, remove connection
|
||||
QObjectPrivate::connect(q, &QLineEdit::textChanged,
|
||||
|
Loading…
x
Reference in New Issue
Block a user