diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2022-07-15 13:12:08 -0700 |
---|---|---|
committer | Thiago Macieira <thiago.macieira@intel.com> | 2022-07-28 11:50:12 -0700 |
commit | a1c34d8bd09bfc6e80dcb06c7900d40c2d32518c (patch) | |
tree | e8c5f97da37e2b89f82c8294b29d4803ca3c845e /src/corelib/kernel/qmetaobject.cpp | |
parent | 0881b5d75c8ad01b295edb96ffd1392d4b804e61 (diff) |
QMetaObject: rewrite QMetaMethod::invoke
This adds an internal method to QMetaMethodPrivate to do the work of
actually placing the call on a given meta method. This rewrite should
make the code clearer, but make no otherwise perceptible difference in
behavior.
The next commit will rewrite QMetaObject::invokeMethod to use this new,
internal function to avoid doing a lot of string allocations.
Change-Id: I36b24183fbd041179f2ffffd170219c0deaaf7f5
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'src/corelib/kernel/qmetaobject.cpp')
-rw-r--r-- | src/corelib/kernel/qmetaobject.cpp | 204 |
1 files changed, 141 insertions, 63 deletions
diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp index c73fd425c2..e487a38277 100644 --- a/src/corelib/kernel/qmetaobject.cpp +++ b/src/corelib/kernel/qmetaobject.cpp @@ -186,6 +186,29 @@ public: inline QByteArray tag() const; inline int ownMethodIndex() const; + // shadows the public function + enum class InvokeFailReason : int { + // negative values mean a match was found but the invocation failed + // (and a warning has been printed) + ReturnTypeMismatch = -1, + DeadLockDetected = -2, + CallViaVirtualFailed = -3, // no warning + ConstructorCallOnObject = -4, + + CouldNotQueueParameter = -0x1000, + + // zero is success + None = 0, + + // positive values mean the parameters did not match + TooFewArguments, + FormalParameterMismatch = 0x1000, + }; + + static InvokeFailReason + invokeImpl(QMetaMethod self, void *target, Qt::ConnectionType, qsizetype paramCount, + const void *const *parameters, const char *const *typeNames); + private: QMetaMethodPrivate(); }; @@ -2275,23 +2298,6 @@ bool QMetaMethod::invoke(QObject *object, if (!object || !mobj) return false; - Q_ASSERT(mobj->cast(object)); - - // check return type - if (returnValue.data()) { - const char *retType = typeName(); - if (qstrcmp(returnValue.name(), retType) != 0) { - // normalize the return value as well - QByteArray normalized = QMetaObject::normalizedType(returnValue.name()); - if (qstrcmp(normalized.constData(), retType) != 0) { - // String comparison failed, try compare the metatype. - int t = returnType(); - if (t == QMetaType::UnknownType || t != QMetaType::fromName(normalized).id()) - return false; - } - } - } - // check argument count (we don't allow invoking a method if given too few arguments) const char *typeNames[] = { returnValue.name(), @@ -2306,13 +2312,105 @@ bool QMetaMethod::invoke(QObject *object, val8.name(), val9.name() }; + void *param[] = { + returnValue.data(), + val0.data(), + val1.data(), + val2.data(), + val3.data(), + val4.data(), + val5.data(), + val6.data(), + val7.data(), + val8.data(), + val9.data() + }; + int paramCount; for (paramCount = 1; paramCount < MaximumParamCount; ++paramCount) { if (qstrlen(typeNames[paramCount]) <= 0) break; } - if (paramCount <= QMetaMethodPrivate::get(this)->parameterCount()) - return false; + + QMetaMethodPrivate::InvokeFailReason r = + QMetaMethodPrivate::invokeImpl(*this, object, connectionType, paramCount, param, typeNames); + + if (Q_LIKELY(r == QMetaMethodPrivate::InvokeFailReason::None)) + return true; + + if (int(r) >= int(QMetaMethodPrivate::InvokeFailReason::FormalParameterMismatch)) { + int n = int(r) - int(QMetaMethodPrivate::InvokeFailReason::FormalParameterMismatch); + qWarning("QMetaMethod::invoke: cannot convert formal parameter %d from %s in call to %s::%s", + n, typeNames[n + 1], mobj->className(), methodSignature().constData()); + } + if (r == QMetaMethodPrivate::InvokeFailReason::TooFewArguments) { + qWarning("QMetaMethod::invoke: too few arguments (%d) in call to %s::%s", + int(paramCount), mobj->className(), methodSignature().constData()); + } + return false; +} + +auto QMetaMethodPrivate::invokeImpl(QMetaMethod self, void *target, + Qt::ConnectionType connectionType, + qsizetype paramCount, const void *const *parameters, + const char *const *typeNames) -> InvokeFailReason +{ + auto object = static_cast<QObject *>(target); + auto priv = QMetaMethodPrivate::get(&self); + auto param = const_cast<void **>(parameters); + + Q_ASSERT(priv->mobj); + Q_ASSERT(self.methodType() == Constructor || object); + Q_ASSERT(self.methodType() == Constructor || priv->mobj->cast(object)); + Q_ASSERT(paramCount >= 1); // includes the return type + Q_ASSERT(parameters); + Q_ASSERT(typeNames); + + if ((paramCount - 1) < priv->data.argc()) + return InvokeFailReason::TooFewArguments; + + // 0 is the return type, 1 is the first formal parameter + auto checkTypesAreCompatible = [=](int idx) { + uint typeInfo = priv->parameterTypeInfo(idx - 1); + QLatin1StringView userTypeName(typeNames[idx]); + + if ((typeInfo & IsUnresolvedType) == 0) { + // this is a built-in type + return int(typeInfo) == QMetaType::fromName(userTypeName).id(); + } + + // compare strings + QLatin1StringView methodTypeName = stringDataView(priv->mobj, typeInfo & TypeNameIndexMask); + if (methodTypeName == userTypeName) + return true; + + // maybe the user type needs normalization + QByteArray normalized = normalizeTypeInternal(userTypeName.begin(), userTypeName.end()); + return methodTypeName == QLatin1StringView(normalized); + }; + + // check formal parameters first (overload set) + for (qsizetype i = 1; i < paramCount; ++i) { + if (!checkTypesAreCompatible(i)) + return InvokeFailReason(int(InvokeFailReason::FormalParameterMismatch) + i - 1); + } + + // regular type - check return type + if (parameters[0]) { + if (self.methodType() == Constructor) { + qWarning("QMetaMethod::invokeMethod: cannot call constructor %s on object %p", + self.methodSignature().constData(), object); + return InvokeFailReason::ConstructorCallOnObject; + } + + if (!checkTypesAreCompatible(0)) { + qWarning("QMetaMethod::invokeMethod: return type mismatch for method %s::%s:" + " cannot convert from %s to %s during invocation", + priv->mobj->className(), priv->methodSignature().constData(), + priv->rawReturnTypeName(), typeNames[0]); + return InvokeFailReason::ReturnTypeMismatch; + } + } Qt::HANDLE currentThreadId = QThread::currentThreadId(); QThread *objectThread = object->thread(); @@ -2334,69 +2432,49 @@ bool QMetaMethod::invoke(QObject *object, #endif // invoke! - void *param[] = { - returnValue.data(), - val0.data(), - val1.data(), - val2.data(), - val3.data(), - val4.data(), - val5.data(), - val6.data(), - val7.data(), - val8.data(), - val9.data() - }; - int idx_relative = QMetaMethodPrivate::get(this)->ownMethodIndex(); - int idx_offset = mobj->methodOffset(); - Q_ASSERT(QMetaObjectPrivate::get(mobj)->revision >= 6); - QObjectPrivate::StaticMetaCallFunction callFunction = mobj->d.static_metacall; + int idx_relative = priv->ownMethodIndex(); + int idx_offset = priv->mobj->methodOffset(); + QObjectPrivate::StaticMetaCallFunction callFunction = priv->mobj->d.static_metacall; if (connectionType == Qt::DirectConnection) { - if (callFunction) { + if (callFunction) callFunction(object, QMetaObject::InvokeMetaMethod, idx_relative, param); - return true; - } else { - return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, idx_relative + idx_offset, param) < 0; - } + else if (QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, idx_relative + idx_offset, param) >= 0) + return InvokeFailReason::CallViaVirtualFailed; } else if (connectionType == Qt::QueuedConnection) { - if (returnValue.data()) { + if (parameters[0]) { qWarning("QMetaMethod::invoke: Unable to invoke methods with return values in " "queued connections"); - return false; + return InvokeFailReason::CouldNotQueueParameter; } auto event = std::make_unique<QMetaCallEvent>(idx_offset, idx_relative, callFunction, nullptr, -1, paramCount); QMetaType *types = event->types(); void **args = event->args(); - int argIndex = 0; + // fill in the meta types first for (int i = 1; i < paramCount; ++i) { - types[i] = QMetaType::fromName(typeNames[i]); - if (!types[i].isValid() && param[i]) { - // Try to register the type and try again before reporting an error. - void *argv[] = { &types[i], &argIndex }; - QMetaObject::metacall(object, QMetaObject::RegisterMethodArgumentMetaType, - idx_relative + idx_offset, argv); - if (!types[i].isValid()) { - qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'", - typeNames[i]); - return false; - } - } - if (types[i].isValid()) { - args[i] = QMetaType(types[i]).create(param[i]); - ++argIndex; + types[i] = priv->parameterMetaType(i - 1); + if (!types[i].iface()) + types[i] = QMetaType::fromName(typeNames[i]); + if (!types[i].iface()) { + qWarning("QMetaMethod::invoke: Unable to handle unregistered datatype '%s'", + typeNames[i]); + return InvokeFailReason(int(InvokeFailReason::CouldNotQueueParameter) - i); } } + // now create copies of our parameters using those meta types + for (int i = 1; i < paramCount; ++i) + args[i] = types[i].create(parameters[i]); + QCoreApplication::postEvent(object, event.release()); } else { // blocking queued connection #if QT_CONFIG(thread) if (receiverInSameThread) { - qWarning("QMetaMethod::invoke: Dead lock detected in " - "BlockingQueuedConnection: Receiver is %s(%p)", - mobj->className(), object); + qWarning("QMetaMethod::invoke: Dead lock detected in BlockingQueuedConnection: " + "Receiver is %s(%p)", priv->mobj->className(), object); + return InvokeFailReason::DeadLockDetected; } QSemaphore semaphore; @@ -2405,7 +2483,7 @@ bool QMetaMethod::invoke(QObject *object, semaphore.acquire(); #endif // QT_CONFIG(thread) } - return true; + return {}; } /*! \fn bool QMetaMethod::invoke(QObject *object, |