Don't access QObjectPrivate::declarativeData unguarded

The QObjectPrivate::declarativeData member is stored in a union with
currentChildBeingDeleted. The QObject destructor always sets the
currentChildBeingDeleted member of the union. It also sets the
isDeletingChildren bool, which is the only way to find out which union
member we can safely access.

While the QObject destructor is deleting children and isDeletingChildren
is set, we must not access the declarativeData member of the union.

Add a test case that initializes the function pointers for the
declarative handlers and constructs a situation where an object
emits a signal while it is destroying children.

Fixes: QTBUG-105286
Change-Id: Iea5ba2f7843b6926a8d157be166e6044d98d6c02
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
(cherry picked from commit 3be99799a675a631c67e05897383af9abbc377b3)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2022-08-16 15:32:58 +02:00 committed by Qt Cherry-pick Bot
parent 196c38a846
commit 77a892affc
3 changed files with 80 additions and 3 deletions

View File

@ -1001,7 +1001,7 @@ QObject::~QObject()
emit destroyed(this);
}
if (d->declarativeData && QAbstractDeclarativeData::destroyed)
if (!d->isDeletingChildren && d->declarativeData && QAbstractDeclarativeData::destroyed)
QAbstractDeclarativeData::destroyed(d->declarativeData, this);
QObjectPrivate::ConnectionData *cd = d->connections.loadRelaxed();
@ -2625,7 +2625,7 @@ int QObject::receivers(const char *signal) const
if (!d->isSignalConnected(signal_index))
return receivers;
if (d->declarativeData && QAbstractDeclarativeData::receivers) {
if (!d->isDeletingChildren && d->declarativeData && QAbstractDeclarativeData::receivers) {
receivers += QAbstractDeclarativeData::receivers(d->declarativeData, this,
signal_index);
}

View File

@ -225,7 +225,7 @@ inline void QObjectPrivate::checkForIncompatibleLibraryVersion(int version) cons
inline bool QObjectPrivate::isDeclarativeSignalConnected(uint signal_index) const
{
return declarativeData && QAbstractDeclarativeData::isSignalConnected
return !isDeletingChildren && declarativeData && QAbstractDeclarativeData::isSignalConnected
&& QAbstractDeclarativeData::isSignalConnected(declarativeData, q_func(), signal_index);
}

View File

@ -147,6 +147,7 @@ private slots:
void singleShotConnection();
void objectNameBinding();
void emitToDestroyedClass();
void declarativeData();
};
struct QObjectCreatedOnShutdown
@ -8241,5 +8242,81 @@ signals:
void aSignal5(const std::unique_ptr<const QObject> &);
};
#ifdef QT_BUILD_INTERNAL
/*
Since QObjectPrivate stores the declarativeData pointer in a union with the pointer
to the currently destroyed child, calls to the QtDeclarative handlers need to be
correctly guarded. QTBUG-105286
*/
namespace QtDeclarative {
static QAbstractDeclarativeData *theData;
static void destroyed(QAbstractDeclarativeData *data, QObject *)
{
QCOMPARE(data, theData);
}
static void signalEmitted(QAbstractDeclarativeData *data, QObject *, int, void **)
{
QCOMPARE(data, theData);
}
// we can't use QCOMPARE in the next two functions, as they don't return void
static int receivers(QAbstractDeclarativeData *data, const QObject *, int)
{
QTest::qCompare(data, theData, "data", "theData", __FILE__, __LINE__);
return 0;
}
static bool isSignalConnected(QAbstractDeclarativeData *data, const QObject *, int)
{
QTest::qCompare(data, theData, "data", "theData", __FILE__, __LINE__);
return true;
}
class Object : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
~Object()
{
if (Object *p = static_cast<Object *>(parent()))
p->emitSignal();
}
void emitSignal()
{
emit theSignal();
}
signals:
void theSignal();
};
}
#endif
void tst_QObject::declarativeData()
{
#ifdef QT_BUILD_INTERNAL
QScopedValueRollback destroyed(QAbstractDeclarativeData::destroyed,
QtDeclarative::destroyed);
QScopedValueRollback signalEmitted(QAbstractDeclarativeData::signalEmitted,
QtDeclarative::signalEmitted);
QScopedValueRollback receivers(QAbstractDeclarativeData::receivers,
QtDeclarative::receivers);
QScopedValueRollback isSignalConnected(QAbstractDeclarativeData::isSignalConnected,
QtDeclarative::isSignalConnected);
QtDeclarative::Object p;
QObjectPrivate *priv = QObjectPrivate::get(&p);
priv->declarativeData = QtDeclarative::theData = new QAbstractDeclarativeData;
connect(&p, &QtDeclarative::Object::theSignal, &p, []{
});
QtDeclarative::Object *child = new QtDeclarative::Object;
child->setParent(&p);
#endif
}
QTEST_MAIN(tst_QObject)
#include "tst_qobject.moc"