From d08f8c2109456ae236eedc359fab4561fb613c6a Mon Sep 17 00:00:00 2001 From: Fabian Kosmale Date: Wed, 31 Mar 2021 16:02:19 +0200 Subject: QV4QObjectWrapper: Store the whole signal 90be89d771425044a84e9e79e4e668e065acc825 changed the connection logic to actually pass the receiver to connect in order to fix disconnect cleanup. However, we omitted to change QObjectSlotDispatcher::impl accordingly. The previous logic was: - store the index of the signal in signalIndex - In impl, in the call case, we would get passed the emitting object (sic!) as the receiver parameter. Then we would use the object and the signal index to obtain the QMetaMethod. - From the QMetaMethod, we could get the signal's number of parameters. After the aforementioned change, that does not work anymore: The receiver is now the actual receiver of the signal, thus we get the wrong method, and potentially the wrong number of parameters. To fix this, we now store the complete QMetaMethod of the signal. Change-Id: I868c51edf24a61d14eaf958ed7942da27f54a5c3 Reviewed-by: Ulf Hermann (cherry picked from commit e7e4eba6875c0f375c4fd03af9b3ed9ea44d0ba1) Reviewed-by: Qt CI Bot --- src/qml/jsruntime/qv4qobjectwrapper.cpp | 13 +++++++------ src/qml/qml/qqmlmetaobject.cpp | 14 ++++++++++++-- src/qml/qml/qqmlmetaobject_p.h | 5 ++--- tests/auto/qml/qqmlecmascript/data/scriptConnect.7.qml | 11 +++++++++++ tests/auto/qml/qqmlecmascript/testtypes.cpp | 3 +++ tests/auto/qml/qqmlecmascript/testtypes.h | 14 ++++++++++++++ tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 8 ++++++++ 7 files changed, 57 insertions(+), 11 deletions(-) create mode 100644 tests/auto/qml/qqmlecmascript/data/scriptConnect.7.qml diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index 98d69b2f80..1d3b12f083 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -939,15 +939,15 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase { QV4::PersistentValue function; QV4::PersistentValue thisObject; - int signalIndex; + QMetaMethod signal; QObjectSlotDispatcher() : QtPrivate::QSlotObjectBase(&impl) - , signalIndex(-1) {} - static void impl(int which, QSlotObjectBase *this_, QObject *r, void **metaArgs, bool *ret) + static void impl(int which, QSlotObjectBase *this_, QObject *receiver, void **metaArgs, bool *ret) { + Q_UNUSED(receiver); switch (which) { case Destroy: { delete static_cast(this_); @@ -963,7 +963,7 @@ struct QObjectSlotDispatcher : public QtPrivate::QSlotObjectBase break; QQmlMetaObject::ArgTypeStorage storage; - int *argsTypes = QQmlMetaObject(r).methodParameterTypes(This->signalIndex, &storage, nullptr); + int *argsTypes = QQmlMetaObject::methodParameterTypes(This->signal, &storage, nullptr); int argCount = argsTypes ? argsTypes[0]:0; @@ -1071,7 +1071,8 @@ ReturnedValue QObjectWrapper::method_connect(const FunctionObject *b, const Valu if (!signalObject) THROW_GENERIC_ERROR("Function.prototype.connect: cannot connect to deleted QObject"); - if (signalObject->metaObject()->method(signalIndex).methodType() != QMetaMethod::Signal) + auto signalMetaMethod = signalObject->metaObject()->method(signalIndex); + if (signalMetaMethod.methodType() != QMetaMethod::Signal) THROW_GENERIC_ERROR("Function.prototype.connect: this object is not a signal"); QV4::ScopedFunctionObject f(scope); @@ -1091,7 +1092,7 @@ ReturnedValue QObjectWrapper::method_connect(const FunctionObject *b, const Valu THROW_GENERIC_ERROR("Function.prototype.connect: target this is not an object"); QV4::QObjectSlotDispatcher *slot = new QV4::QObjectSlotDispatcher; - slot->signalIndex = signalIndex; + slot->signal = signalMetaMethod; slot->thisObject.set(scope.engine, object); slot->function.set(scope.engine, f); diff --git a/src/qml/qml/qqmlmetaobject.cpp b/src/qml/qml/qqmlmetaobject.cpp index 89c08f3841..b6454e083d 100644 --- a/src/qml/qml/qqmlmetaobject.cpp +++ b/src/qml/qml/qqmlmetaobject.cpp @@ -184,7 +184,7 @@ int *QQmlMetaObject::constructorParameterTypes(int index, ArgTypeStorage *dummy, } int *QQmlMetaObject::methodParameterTypes(const QMetaMethod &m, ArgTypeStorage *argStorage, - QByteArray *unknownTypeError) const + QByteArray *unknownTypeError) { Q_ASSERT(argStorage); @@ -203,7 +203,17 @@ int *QQmlMetaObject::methodParameterTypes(const QMetaMethod &m, ArgTypeStorage * } else { if (argTypeNames.isEmpty()) argTypeNames = m.parameterTypes(); - if (isNamedEnumerator(_m, argTypeNames.at(ii))) { + // hack only needed in Qt 6.1 to access the metaobject of the metamethod + // In 6.2 this method has been refactored and does not need acccess to the metaobject + union HackyAccessor { + HackyAccessor(QMetaMethod method) : m(method) {} + QMetaMethod m; + struct { + const QMetaObject *mobj = nullptr; + } accessor; + }; + HackyAccessor hack { m }; + if (isNamedEnumerator(hack.accessor.mobj, argTypeNames.at(ii))) { type = QMetaType::Int; } else if (type == QMetaType::UnknownType) { if (unknownTypeError) diff --git a/src/qml/qml/qqmlmetaobject_p.h b/src/qml/qml/qqmlmetaobject_p.h index a5b9b758a8..299218c10a 100644 --- a/src/qml/qml/qqmlmetaobject_p.h +++ b/src/qml/qml/qqmlmetaobject_p.h @@ -101,11 +101,10 @@ public: // we need a helper to find the correct meta object and property/method index. static void resolveGadgetMethodOrPropertyIndex(QMetaObject::Call type, const QMetaObject **metaObject, int *index); + static int *methodParameterTypes(const QMetaMethod &method, ArgTypeStorage *argStorage, + QByteArray *unknownTypeError); protected: const QMetaObject *_m = nullptr; - int *methodParameterTypes(const QMetaMethod &method, ArgTypeStorage *argStorage, - QByteArray *unknownTypeError) const; - }; QQmlMetaObject::QQmlMetaObject(const QObject *o) diff --git a/tests/auto/qml/qqmlecmascript/data/scriptConnect.7.qml b/tests/auto/qml/qqmlecmascript/data/scriptConnect.7.qml new file mode 100644 index 0000000000..9dab8d7815 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/scriptConnect.7.qml @@ -0,0 +1,11 @@ +import Qt.test +import QtQml + +QtObject { + readonly property Sender s: Sender {id: sender} + readonly property Receiver r: Receiver {id: receiver} + Component.onCompleted: () => { + sender.sig1.connect(receiver.slot1) + sender.sig1() + } +} diff --git a/tests/auto/qml/qqmlecmascript/testtypes.cpp b/tests/auto/qml/qqmlecmascript/testtypes.cpp index 688ed2c946..9b699f485b 100644 --- a/tests/auto/qml/qqmlecmascript/testtypes.cpp +++ b/tests/auto/qml/qqmlecmascript/testtypes.cpp @@ -556,6 +556,9 @@ void registerTypes() qmlRegisterType("Qt.test", 1, 0, "ClassWithQProperty"); qmlRegisterType("Qt.test", 1, 0, "ClassWithQProperty2"); + + qmlRegisterType("Qt.test", 1,0, "Receiver"); + qmlRegisterType("Qt.test", 1,0, "Sender"); } #include "testtypes.moc" diff --git a/tests/auto/qml/qqmlecmascript/testtypes.h b/tests/auto/qml/qqmlecmascript/testtypes.h index 7074ffaa10..09c3fcaf40 100644 --- a/tests/auto/qml/qqmlecmascript/testtypes.h +++ b/tests/auto/qml/qqmlecmascript/testtypes.h @@ -1781,6 +1781,20 @@ public: // QNotifiedProperty value; }; +struct Sender : QObject +{ + Q_OBJECT +signals: + void sig1(); +}; + +struct Receiver : QObject +{ + Q_OBJECT +public slots: + int slot1(int i, int j, int k) {return i+j+k;} +}; + void registerTypes(); #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 1ac1c04fd4..59f2b1ab88 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -3617,6 +3617,14 @@ void tst_qqmlecmascript::scriptConnect() delete object; } + + { + QRegularExpression msg {".*scriptConnect.7.qml:9: Error: Insufficient arguments"}; + QTest::ignoreMessage(QtMsgType::QtWarningMsg, msg); + QQmlComponent component(&engine, testFileUrl("scriptConnect.7.qml")); + QScopedPointer root { component.create() }; + QVERIFY2(root, qPrintable(component.errorString())); + } } void tst_qqmlecmascript::scriptDisconnect() -- cgit v1.2.3