From 40a8e2cb8cc1624b6c4f15f6d4070c2027a1e774 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Sat, 16 Jul 2022 13:13:03 -0700 Subject: QMetaObjectPublisher: use new way to QMetaObject::invokeMethod The new API isn't limited to 10 parameters. It also does its own parameter matching, which we use here to locate the method to be called. I don't think that was necessary, though, because we sort the methods to be called in order of preference. Fixes: QTBUG-105596 Change-Id: I36b24183fbd041179f2ffffd170268620633a72b Reviewed-by: Arno Rehn --- src/webchannel/CMakeLists.txt | 1 - src/webchannel/qmetaobjectpublisher.cpp | 97 +++++++++++++++++++++------------ src/webchannel/qmetaobjectpublisher_p.h | 10 +++- src/webchannel/variantargument_p.h | 45 --------------- 4 files changed, 70 insertions(+), 83 deletions(-) delete mode 100644 src/webchannel/variantargument_p.h diff --git a/src/webchannel/CMakeLists.txt b/src/webchannel/CMakeLists.txt index 60f3492..9354973 100644 --- a/src/webchannel/CMakeLists.txt +++ b/src/webchannel/CMakeLists.txt @@ -13,7 +13,6 @@ qt_internal_add_module(WebChannel qwebchannel.cpp qwebchannel.h qwebchannel_p.h qwebchannelabstracttransport.cpp qwebchannelabstracttransport.h signalhandler_p.h - variantargument_p.h qwebchannelglobal.h LIBRARIES Qt::CorePrivate diff --git a/src/webchannel/qmetaobjectpublisher.cpp b/src/webchannel/qmetaobjectpublisher.cpp index c97b504..62d5319 100644 --- a/src/webchannel/qmetaobjectpublisher.cpp +++ b/src/webchannel/qmetaobjectpublisher.cpp @@ -20,6 +20,8 @@ #endif #include +#include + QT_BEGIN_NAMESPACE namespace { @@ -507,6 +509,63 @@ void QMetaObjectPublisher::sendPendingPropertyUpdates() sendEnqueuedPropertyUpdates(state.key()); } +QVariant QMetaObjectPublisher::invokeMethod_helper(QObject *const object, const QMetaMethod &method, + const QJsonArray &args) +{ + // a good value for the number of arguments we'll preallocate in QVLA + constexpr qsizetype ArgumentCount = 16; + + QVarLengthArray variants; + QVarLengthArray names(method.parameterCount() + 1); + QVarLengthArray parameters(names.size()); + variants.reserve(names.size()); + variants << QVariant(); + + // start with the formal parameters + for (qsizetype i = 0; i < names.size() - 1; ++i) { + QMetaType mt = method.parameterMetaType(i); + QVariant &v = variants.emplace_back(toVariant(args.at(i), mt.id())); + parameters[i + 1] = v.data(); + names[i + 1] = mt.name(); + } + + // now, the return type + QMetaType mt = method.returnMetaType(); + names[0] = mt.name(); + if (int id = mt.id(); id != QMetaType::Void) { + // Only init variant with return type if its not a variant itself, + // which would lead to nested variants which is not what we want. + if (id == QMetaType::QVariant) { + parameters[0] = &variants[0]; + } else { + variants[0] = QVariant(mt); + parameters[0] = variants[0].data(); + } + } else { + parameters[0] = nullptr; + } + + // step 3: make the call + QMetaMethodInvoker::InvokeFailReason r = + QMetaMethodInvoker::invokeImpl(method, object, Qt::AutoConnection, + parameters.size(), parameters.constData(), + names.constData()); + + if (r == QMetaMethodInvoker::InvokeFailReason::None) + return variants.first(); + + // print warnings for failures to match + if (int(r) >= int(QMetaMethodInvoker::InvokeFailReason::FormalParameterMismatch)) { + int n = int(r) - int(QMetaMethodInvoker::InvokeFailReason::FormalParameterMismatch); + QByteArray callee = object->metaObject()->className() + QByteArrayView("::") + + method.methodSignature(); + qWarning() << "Cannot convert formal parameter" << n << "from" << names[n + 1] + << "in call to" << callee.constData(); + } + + return QJsonValue(); +} + QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const QMetaMethod &method, const QJsonArray &args) { @@ -523,41 +582,12 @@ QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const QMetaMe } else if (method.methodType() != QMetaMethod::Method && method.methodType() != QMetaMethod::Slot) { qWarning() << "Cannot invoke non-public method" << method.name() << "on object" << object << '.'; return QJsonValue(); - } else if (args.size() > 10) { - qWarning() << "Cannot invoke method" << method.name() << "on object" << object << "with more than 10 arguments, as that is not supported by QMetaMethod::invoke."; - return QJsonValue(); } else if (args.size() > method.parameterCount()) { qWarning() << "Ignoring additional arguments while invoking method" << method.name() << "on object" << object << ':' << args.size() << "arguments given, but method only takes" << method.parameterCount() << '.'; } - // construct converter objects of QVariant to QGenericArgument - VariantArgument arguments[10]; - for (int i = 0; i < qMin(args.size(), method.parameterCount()); ++i) { - arguments[i].value = toVariant(args.at(i), method.parameterType(i)); - arguments[i].type = method.parameterType(i); - } - // construct QGenericReturnArgument - QVariant returnValue; - if (method.returnType() == QMetaType::Void) { - // Skip return for void methods (prevents runtime warnings inside Qt), and allows - // QMetaMethod to invoke void-returning methods on QObjects in a different thread. - method.invoke(object, - arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], - arguments[5], arguments[6], arguments[7], arguments[8], arguments[9]); - } else { - // Only init variant with return type if its not a variant itself, which would - // lead to nested variants which is not what we want. - if (method.returnType() != QMetaType::QVariant) - returnValue = QVariant(QMetaType(method.returnType()), nullptr); - - QGenericReturnArgument returnArgument(method.typeName(), returnValue.data()); - method.invoke(object, returnArgument, - arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], - arguments[5], arguments[6], arguments[7], arguments[8], arguments[9]); - } - // now we can call the method - return returnValue; + return invokeMethod_helper(object, method, args); } QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const int methodIndex, @@ -583,8 +613,7 @@ QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const QByteAr if (method.name() != methodName || method.parameterCount() != args.count() || method.access() != QMetaMethod::Public || (method.methodType() != QMetaMethod::Method - && method.methodType() != QMetaMethod::Slot) - || method.parameterCount() > 10) + && method.methodType() != QMetaMethod::Slot)) { // Not a candidate continue; @@ -600,13 +629,13 @@ QVariant QMetaObjectPublisher::invokeMethod(QObject *const object, const QByteAr } std::sort(candidates.begin(), candidates.end()); - if (candidates.size() > 1 && candidates[0].badness == candidates[1].badness) { qWarning().nospace() << "Ambiguous overloads for method " << methodName << ". Choosing " << candidates.first().method.methodSignature(); + } - return invokeMethod(object, candidates.first().method, args); + return invokeMethod_helper(object, candidates.first().method, args); } void QMetaObjectPublisher::setProperty(QObject *object, const int propertyIndex, const QJsonValue &value) diff --git a/src/webchannel/qmetaobjectpublisher_p.h b/src/webchannel/qmetaobjectpublisher_p.h index 2029225..8605798 100644 --- a/src/webchannel/qmetaobjectpublisher_p.h +++ b/src/webchannel/qmetaobjectpublisher_p.h @@ -15,7 +15,7 @@ // We mean it. // -#include "variantargument_p.h" +#include "qwebchannelglobal.h" #include "signalhandler_p.h" #include @@ -29,8 +29,6 @@ #include -#include "qwebchannelglobal.h" - QT_BEGIN_NAMESPACE // NOTE: keep in sync with corresponding maps in qwebchannel.js and WebChannelTest.qml @@ -153,6 +151,12 @@ public: */ void sendPendingPropertyUpdates(); + /** + * Helper function for the invokeMehtods below + */ + QVariant invokeMethod_helper(QObject *const object, const QMetaMethod &method, + const QJsonArray &args); + /** * Invoke the @p method on @p object with the arguments @p args. * diff --git a/src/webchannel/variantargument_p.h b/src/webchannel/variantargument_p.h deleted file mode 100644 index 09b864f..0000000 --- a/src/webchannel/variantargument_p.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef VARIANTARGUMENT_H -#define VARIANTARGUMENT_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include -#include - -QT_BEGIN_NAMESPACE - -/** - * RAII QVariant to Q[Generic]Argument conversion - */ -struct VariantArgument -{ - operator QGenericArgument() const - { - if (type == QMetaType::QVariant) { - return QGenericArgument("QVariant", &value); - } - if (!value.isValid()) { - return QGenericArgument(); - } - return QGenericArgument(value.typeName(), value.constData()); - } - - QVariant value; - int type; -}; - -QT_END_NAMESPACE - -#endif // VARIANTARGUMENT_H -- cgit v1.2.3