QDialogButtonBox: prevent crashes on deletions in slots
As usual, user code connected to signals emitted from Qt may destroy an object's internal status while a signal is being emitted. Guard a bit QDialogButtonBox signal emissions to prevent crashes in case the button or a dialog get deleted from a slot connected to clicked(). Also, be sure to emit the corresponding accepted/rejected/etc. signal. Change-Id: I7b1888070a8f2f56aa60923a17f90fb5efef145c Task-number: QTBUG-45835 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@theqtcompany.com> Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com> Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
This commit is contained in:
parent
4b7d70886f
commit
1ac1ae05f5
@ -852,9 +852,19 @@ void QDialogButtonBoxPrivate::_q_handleButtonClicked()
|
|||||||
{
|
{
|
||||||
Q_Q(QDialogButtonBox);
|
Q_Q(QDialogButtonBox);
|
||||||
if (QAbstractButton *button = qobject_cast<QAbstractButton *>(q->sender())) {
|
if (QAbstractButton *button = qobject_cast<QAbstractButton *>(q->sender())) {
|
||||||
|
// Can't fetch this *after* emitting clicked, as clicked may destroy the button
|
||||||
|
// or change its role. Now changing the role is not possible yet, but arguably
|
||||||
|
// both clicked and accepted/rejected/etc. should be emitted "atomically"
|
||||||
|
// depending on whatever role the button had at the time of the click.
|
||||||
|
const QDialogButtonBox::ButtonRole buttonRole = q->buttonRole(button);
|
||||||
|
QPointer<QDialogButtonBox> guard(q);
|
||||||
|
|
||||||
emit q->clicked(button);
|
emit q->clicked(button);
|
||||||
|
|
||||||
switch (q->buttonRole(button)) {
|
if (!guard)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (buttonRole) {
|
||||||
case QPlatformDialogHelper::AcceptRole:
|
case QPlatformDialogHelper::AcceptRole:
|
||||||
case QPlatformDialogHelper::YesRole:
|
case QPlatformDialogHelper::YesRole:
|
||||||
emit q->accepted();
|
emit q->accepted();
|
||||||
|
@ -90,6 +90,7 @@ private slots:
|
|||||||
// void buttons();
|
// void buttons();
|
||||||
|
|
||||||
void testDelete();
|
void testDelete();
|
||||||
|
void testSignalEmissionAfterDelete_QTBUG_45835();
|
||||||
void testRemove();
|
void testRemove();
|
||||||
void testMultipleAdd();
|
void testMultipleAdd();
|
||||||
void testStandardButtonMapping_data();
|
void testStandardButtonMapping_data();
|
||||||
@ -111,6 +112,7 @@ private:
|
|||||||
|
|
||||||
tst_QDialogButtonBox::tst_QDialogButtonBox()
|
tst_QDialogButtonBox::tst_QDialogButtonBox()
|
||||||
{
|
{
|
||||||
|
qRegisterMetaType<QAbstractButton *>();
|
||||||
}
|
}
|
||||||
|
|
||||||
tst_QDialogButtonBox::~tst_QDialogButtonBox()
|
tst_QDialogButtonBox::~tst_QDialogButtonBox()
|
||||||
@ -414,6 +416,70 @@ void tst_QDialogButtonBox::testDelete()
|
|||||||
QCOMPARE(buttonBox.buttons().count(), 0);
|
QCOMPARE(buttonBox.buttons().count(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ObjectDeleter : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public slots:
|
||||||
|
void deleteButton(QAbstractButton *button)
|
||||||
|
{
|
||||||
|
delete button;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteSender()
|
||||||
|
{
|
||||||
|
delete sender();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void tst_QDialogButtonBox::testSignalEmissionAfterDelete_QTBUG_45835()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
QDialogButtonBox buttonBox;
|
||||||
|
QCOMPARE(buttonBox.buttons().count(), 0);
|
||||||
|
|
||||||
|
QSignalSpy buttonClickedSpy(&buttonBox, &QDialogButtonBox::clicked);
|
||||||
|
QVERIFY(buttonClickedSpy.isValid());
|
||||||
|
|
||||||
|
QSignalSpy buttonBoxAcceptedSpy(&buttonBox, &QDialogButtonBox::accepted);
|
||||||
|
QVERIFY(buttonBoxAcceptedSpy.isValid());
|
||||||
|
|
||||||
|
QPushButton *button = buttonBox.addButton("Test", QDialogButtonBox::AcceptRole);
|
||||||
|
QCOMPARE(buttonBox.buttons().count(), 1);
|
||||||
|
|
||||||
|
ObjectDeleter objectDeleter;
|
||||||
|
connect(&buttonBox, &QDialogButtonBox::clicked, &objectDeleter, &ObjectDeleter::deleteButton);
|
||||||
|
|
||||||
|
button->click();
|
||||||
|
|
||||||
|
QCOMPARE(buttonBox.buttons().count(), 0);
|
||||||
|
QCOMPARE(buttonClickedSpy.count(), 1);
|
||||||
|
QCOMPARE(buttonBoxAcceptedSpy.count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QPointer<QDialogButtonBox> buttonBox(new QDialogButtonBox);
|
||||||
|
QCOMPARE(buttonBox->buttons().count(), 0);
|
||||||
|
|
||||||
|
QSignalSpy buttonClickedSpy(buttonBox.data(), &QDialogButtonBox::clicked);
|
||||||
|
QVERIFY(buttonClickedSpy.isValid());
|
||||||
|
|
||||||
|
QSignalSpy buttonBoxAcceptedSpy(buttonBox.data(), &QDialogButtonBox::accepted);
|
||||||
|
QVERIFY(buttonBoxAcceptedSpy.isValid());
|
||||||
|
|
||||||
|
QPushButton *button = buttonBox->addButton("Test", QDialogButtonBox::AcceptRole);
|
||||||
|
QCOMPARE(buttonBox->buttons().count(), 1);
|
||||||
|
|
||||||
|
ObjectDeleter objectDeleter;
|
||||||
|
connect(buttonBox.data(), &QDialogButtonBox::clicked, &objectDeleter, &ObjectDeleter::deleteSender);
|
||||||
|
|
||||||
|
button->click();
|
||||||
|
|
||||||
|
QVERIFY(buttonBox.isNull());
|
||||||
|
QCOMPARE(buttonClickedSpy.count(), 1);
|
||||||
|
QCOMPARE(buttonBoxAcceptedSpy.count(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void tst_QDialogButtonBox::testMultipleAdd()
|
void tst_QDialogButtonBox::testMultipleAdd()
|
||||||
{
|
{
|
||||||
// Add a button into the thing multiple times.
|
// Add a button into the thing multiple times.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user