diff options
author | Marc Mutz <[email protected]> | 2024-03-21 11:56:56 +0100 |
---|---|---|
committer | Marc Mutz <[email protected]> | 2024-04-29 09:23:50 +0200 |
commit | ab0b2a490eb98f3216403e87e4ea108a10fbeed2 (patch) | |
tree | bb78402984bf529fcd53557d06d07433fdb912bc | |
parent | 4946982534d4d256590dffd44046fe642456703f (diff) |
QSignalSpy: fix -Wweak-vtable by removing the QObject inheritance
Normally, we'd fix -Wweak-vtable by exporting the class and making at
least one virtual method out-of-line (typically the dtor), thereby
pinning the vtable to exactly one TU.
We can't export QSignalSpy, though, because it also inherits QList,
and we don't want to export QList subclasses to avoid QList API
becoming part of the ABI.
So remove the vtable, and therefore its being a weak symbol, by moving
the qt_metacall implementation from QSignalSpy into its newly-added
Private, at the cost of an additional memory allocation at
construction (though there was already the wish to make this class
pimpl'ed for extensibility, and this patch accomplishes exactly that).
This class used to be one of few places left that prevents adding
-Wweak-vtable to headersclean, so while this is a breaking change,
QSignalSpy doesn't really model is-a QObject. It uses QObject to
reuse, not to be reused. In fact, no external code should use the
QObject-ness of QSignalSpy, so it should be an acceptable SC break to
drop the inheritance.
We don't need to care about BC here, as we don't promise BC for
QtTest.
This now also allows (and requires) to make the dtor and the private
ctor out-of-line, avoiding the need for the init() hack.
[ChangeLog][QtTest][Potentially Source-Incompatible Changes]
QSignalSpy no longer inherits from QObject. If your code uses the fact
that QSignalSpy is-a QObject, you need to redesign around this now.
Task-number: QTBUG-45582
Fixes: QTBUG-123544
Change-Id: Id93ba0ee6bbb811455d3744a045e38e1b9f9c584
Reviewed-by: Tor Arne Vestbø <[email protected]>
Reviewed-by: Jason McDonald <[email protected]>
Reviewed-by: Ahmad Samir <[email protected]>
-rw-r--r-- | src/testlib/qsignalspy.cpp | 47 | ||||
-rw-r--r-- | src/testlib/qsignalspy.h | 33 |
2 files changed, 49 insertions, 31 deletions
diff --git a/src/testlib/qsignalspy.cpp b/src/testlib/qsignalspy.cpp index 5fd61d42dcd..a45ca59378e 100644 --- a/src/testlib/qsignalspy.cpp +++ b/src/testlib/qsignalspy.cpp @@ -110,10 +110,6 @@ QT_BEGIN_NAMESPACE Returns the normalized signal the spy is currently listening to. */ -/*! \fn int QSignalSpy::qt_metacall(QMetaObject::Call call, int id, void **a) - \internal -*/ - /*! \fn bool QSignalSpy::wait(int timeout) \since 5.0 @@ -244,22 +240,42 @@ QList<int> QSignalSpy::makeArgs(const QMetaMethod &member, const QObject *obj) return result; } -void QSignalSpy::init(ObjectSignal os) +class QSignalSpyPrivate : public QObject +{ + QSignalSpy * const q; +public: + explicit QSignalSpyPrivate(QSignalSpy *qq) : q(qq) {} + + int qt_metacall(QMetaObject::Call call, int methodId, void **a) override; +}; + +QSignalSpy::QSignalSpy(ObjectSignal os) + : args(os.obj ? makeArgs(os.sig, os.obj) : QList<int>{}) { if (!os.obj) return; + auto i = std::make_unique<QSignalSpyPrivate>(this); + const auto signalIndex = os.sig.methodIndex(); const auto slotIndex = QObject::staticMetaObject.methodCount(); if (!QMetaObject::connect(os.obj, signalIndex, - this, slotIndex, Qt::DirectConnection)) { + i.get(), slotIndex, Qt::DirectConnection)) { qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect."); return; } + d_ptr = std::move(i); + sig = os.sig.methodSignature(); } +/*! + Destructor. +*/ +QSignalSpy::~QSignalSpy() + = default; + void QSignalSpy::appendArgs(void **a) { QList<QVariant> list; @@ -280,4 +296,23 @@ void QSignalSpy::appendArgs(void **a) } } +/*! + \reimp + \internal +*/ +int QSignalSpyPrivate::qt_metacall(QMetaObject::Call call, int methodId, void **a) +{ + methodId = QObject::qt_metacall(call, methodId, a); + if (methodId < 0) + return methodId; + + if (call == QMetaObject::InvokeMetaMethod) { + if (methodId == 0) { + q->appendArgs(a); + } + --methodId; + } + return methodId; +} + QT_END_NAMESPACE diff --git a/src/testlib/qsignalspy.h b/src/testlib/qsignalspy.h index f4c322ae718..591545b4d5d 100644 --- a/src/testlib/qsignalspy.h +++ b/src/testlib/qsignalspy.h @@ -6,24 +6,26 @@ #include <QtCore/qbytearray.h> #include <QtCore/qlist.h> -#include <QtCore/qobject.h> #include <QtCore/qmetaobject.h> #include <QtTest/qtesteventloop.h> #include <QtCore/qvariant.h> #include <QtCore/qmutex.h> +#include <memory> + QT_BEGIN_NAMESPACE class QVariant; - -class QSignalSpy: public QObject, public QList<QList<QVariant> > +class QSignalSpyPrivate; +class QSignalSpy : public QList<QList<QVariant> > { struct ObjectSignal { const QObject *obj; QMetaMethod sig; }; - + friend class QSignalSpyPrivate; + std::unique_ptr<QSignalSpyPrivate> d_ptr; public: explicit QSignalSpy(const QObject *obj, const char *aSignal) : QSignalSpy(verify(obj, aSignal)) {} @@ -37,6 +39,7 @@ public: #endif // Q_QDOC QSignalSpy(const QObject *obj, QMetaMethod signal) : QSignalSpy(verify(obj, signal)) {} + Q_TESTLIB_EXPORT ~QSignalSpy(); inline bool isValid() const { return !sig.isEmpty(); } inline QByteArray signal() const { return sig; } @@ -46,28 +49,8 @@ public: Q_TESTLIB_EXPORT bool wait(std::chrono::milliseconds timeout = std::chrono::seconds{5}); - int qt_metacall(QMetaObject::Call call, int methodId, void **a) override - { - methodId = QObject::qt_metacall(call, methodId, a); - if (methodId < 0) - return methodId; - - if (call == QMetaObject::InvokeMetaMethod) { - if (methodId == 0) { - appendArgs(a); - } - --methodId; - } - return methodId; - } - private: - explicit QSignalSpy(ObjectSignal os) - : args(os.obj ? makeArgs(os.sig, os.obj) : QList<int>{}) - { - init(os); - } - Q_TESTLIB_EXPORT void init(ObjectSignal os); + Q_TESTLIB_EXPORT explicit QSignalSpy(ObjectSignal os); Q_TESTLIB_EXPORT static ObjectSignal verify(const QObject *obj, QMetaMethod signal); Q_TESTLIB_EXPORT static ObjectSignal verify(const QObject *obj, const char *aSignal); |