diff options
Diffstat (limited to 'sources/pyside6/libpyside/signalmanager.cpp')
-rw-r--r-- | sources/pyside6/libpyside/signalmanager.cpp | 737 |
1 files changed, 466 insertions, 271 deletions
diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp index 2c08f4328..557f130e0 100644 --- a/sources/pyside6/libpyside/signalmanager.cpp +++ b/sources/pyside6/libpyside/signalmanager.cpp @@ -1,51 +1,16 @@ -// -*- mode: cpp;-*- -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of Qt for Python. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "signalmanager.h" #include "pysidesignal.h" +#include "pysidelogging_p.h" #include "pysideproperty.h" #include "pysideproperty_p.h" -#include "pyside.h" +#include "pysidecleanup.h" #include "pyside_p.h" #include "dynamicqmetaobject.h" #include "pysidemetafunction_p.h" +#include "pysidestaticstrings.h" #include <autodecref.h> #include <basewrapper.h> @@ -54,19 +19,20 @@ #include <sbkconverter.h> #include <sbkstring.h> #include <sbkstaticstrings.h> +#include <sbkerrors.h> +#include <QtCore/QCoreApplication> +#include <QtCore/QByteArrayView> #include <QtCore/QDebug> #include <QtCore/QHash> +#include <QtCore/QScopedPointer> +#include <QtCore/QTimerEvent> #include <algorithm> #include <limits> +#include <memory> -// These private headers are needed to throw JavaScript exceptions -#if PYSIDE_QML_PRIVATE_API_SUPPORT - #include <private/qv4engine_p.h> - #include <private/qv4context_p.h> - #include <private/qqmldata_p.h> -#endif +using namespace Qt::StringLiterals; #if QSLOT_CODE != 1 || QSIGNAL_CODE != 2 #error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value! @@ -75,26 +41,64 @@ #define PYSIDE_SIGNAL '2' #include "globalreceiverv2.h" -namespace { - static PyObject *metaObjectAttr = nullptr; - - static int callMethod(QObject *object, int id, void **args); - static PyObject *parseArguments(const QList< QByteArray >& paramTypes, void **args); - static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args); - - static void destroyMetaObject(PyObject *obj) - { - void *ptr = PyCapsule_GetPointer(obj, nullptr); - auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr); - SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta); - if (wrapper) - Shiboken::BindingManager::instance().releaseWrapper(wrapper); - delete meta; +static PyObject *metaObjectAttr = nullptr; +static PyObject *parseArguments(const QMetaMethod &method, void **args); +static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args); + +static bool qAppRunning = false; + +static void destroyMetaObject(PyObject *obj) +{ + void *ptr = PyCapsule_GetPointer(obj, nullptr); + auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr); + SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta); + if (wrapper) + Shiboken::BindingManager::instance().releaseWrapper(wrapper); + delete meta; +} + +static const char *metaCallName(QMetaObject::Call call) +{ + static const QHash<QMetaObject::Call, const char *> mapping = { + {QMetaObject::InvokeMetaMethod, "InvokeMetaMethod"}, + {QMetaObject::ReadProperty, "ReadProperty"}, + {QMetaObject::WriteProperty, "WriteProperty"}, + {QMetaObject::ResetProperty, "ResetProperty"}, + {QMetaObject::CreateInstance, "CreateInstance"}, + {QMetaObject::IndexOfMethod, "IndexOfMethod"}, + {QMetaObject::RegisterPropertyMetaType, "RegisterPropertyMetaType"}, + {QMetaObject::RegisterMethodArgumentMetaType, "RegisterMethodArgumentMetaType"}, + {QMetaObject::BindableProperty, "BindableProperty"}, + {QMetaObject::CustomCall, "CustomCall"} + }; + auto it = mapping.constFind(call); + return it != mapping.constEnd() ? it.value() : "<Unknown>"; +} + +static QByteArray methodSignature(const QMetaMethod &method) +{ + QByteArray result; + if (auto *t = method.typeName()) { + result += t; + result += ' '; } + result += method.methodSignature(); + return result; } -namespace PySide { +static QByteArray msgCannotConvertParameter(const QMetaMethod &method, qsizetype p) +{ + return "Cannot call meta function \""_ba + methodSignature(method) + + "\" because parameter " + QByteArray::number(p) + " of type \""_ba + + method.parameterTypeName(p) + "\" cannot be converted."_ba; +} + +static QByteArray msgCannotConvertReturn(const QMetaMethod &method) +{ + return "The return value of \""_ba + methodSignature(method) + "\" cannot be converted."_ba; +} +namespace PySide { PyObjectWrapper::PyObjectWrapper() :m_me(Py_None) @@ -141,7 +145,8 @@ void PyObjectWrapper::reset(PyObject *o) PyObjectWrapper &PyObjectWrapper::operator=(const PySide::PyObjectWrapper &other) { - reset(other.m_me); + if (this != &other) + reset(other.m_me); return *this; } @@ -150,6 +155,14 @@ PyObjectWrapper::operator PyObject *() const return m_me; } + +int PyObjectWrapper::toInt() const +{ + // hold the GIL + Shiboken::GilState state; + return Shiboken::Enum::check(m_me) ? Shiboken::Enum::getValue(m_me) : -1; +} + QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj) { if (Py_IsInitialized() == 0) { @@ -164,7 +177,8 @@ QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj) Shiboken::AutoDecRef pickleModule(PyImport_ImportModule("pickle")); reduce_func = PyObject_GetAttr(pickleModule, Shiboken::PyName::dumps()); } - Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, (PyObject *)myObj, NULL)); + PyObject *pyObj = myObj; + Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, pyObj, nullptr)); if (repr.object()) { const char *buff = nullptr; Py_ssize_t size = 0; @@ -208,29 +222,71 @@ QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj) }; +namespace PySide { +using GlobalReceiverV2Ptr = std::shared_ptr<GlobalReceiverV2>; +using GlobalReceiverV2Map = QHash<PySide::GlobalReceiverKey, GlobalReceiverV2Ptr>; +} + using namespace PySide; -struct SignalManager::SignalManagerPrivate +// Listen for destroy() of main thread objects and ensure cleanup +class SignalManagerDestroyListener : public QObject { - GlobalReceiverV2MapPtr m_globalReceivers; + Q_OBJECT +public: + Q_DISABLE_COPY_MOVE(SignalManagerDestroyListener) - SignalManagerPrivate() : m_globalReceivers(new GlobalReceiverV2Map{}) - { - } + using QObject::QObject; - ~SignalManagerPrivate() - { - if (!m_globalReceivers.isNull()) { - // Delete receivers by always retrieving the current first element, because deleting a - // receiver can indirectly delete another one, and if we use qDeleteAll, that could - // cause either a double delete, or iterator invalidation, and thus undefined behavior. - while (!m_globalReceivers->isEmpty()) - delete *m_globalReceivers->cbegin(); - Q_ASSERT(m_globalReceivers->isEmpty()); - } +public Q_SLOTS: + void destroyNotify(const QObject *); + +protected: + void timerEvent(QTimerEvent *event) override; + +private: + int m_timerId = -1; +}; + +void SignalManagerDestroyListener::destroyNotify(const QObject *) +{ + if (qAppRunning && m_timerId == -1) + m_timerId = startTimer(0); +} + +void SignalManagerDestroyListener::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_timerId) { + killTimer(std::exchange(m_timerId, -1)); + SignalManager::instance().purgeEmptyGlobalReceivers(); } +} + +struct SignalManager::SignalManagerPrivate +{ + Q_DISABLE_COPY_MOVE(SignalManagerPrivate) + + SignalManagerPrivate() noexcept = default; + ~SignalManagerPrivate() { clear(); } + + void deleteGlobalReceiver(const QObject *gr); + void clear(); + void purgeEmptyGlobalReceivers(); + + GlobalReceiverV2Map m_globalReceivers; + static SignalManager::QmlMetaCallErrorHandler m_qmlMetaCallErrorHandler; + + static void handleMetaCallError(QObject *object, int *result); + static int qtPropertyMetacall(QObject *object, QMetaObject::Call call, + int id, void **args); + static int qtMethodMetacall(QObject *object, int id, void **args); + + QPointer<SignalManagerDestroyListener> m_listener; }; +SignalManager::QmlMetaCallErrorHandler + SignalManager::SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr; + static void clearSignalManager() { PySide::SignalManager::instance().clear(); @@ -240,7 +296,7 @@ static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut) { *reinterpret_cast<PyObject **>(cppOut) = pyIn; } -static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject *pyIn) +static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */) { return PyObject_PythonToCpp_PyObject_PTR; } @@ -259,6 +315,8 @@ SignalManager::SignalManager() : m_d(new SignalManagerPrivate) // Register PyObject type to use in queued signal and slot connections qRegisterMetaType<PyObjectWrapper>("PyObject"); + // Register QVariant(enum) conversion to QVariant(int) + QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt); SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr); Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject); @@ -276,8 +334,7 @@ SignalManager::SignalManager() : m_d(new SignalManagerPrivate) void SignalManager::clear() { - delete m_d; - m_d = new SignalManagerPrivate(); + m_d->clear(); } SignalManager::~SignalManager() @@ -291,52 +348,123 @@ SignalManager &SignalManager::instance() return me; } -QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback) +void SignalManager::setQmlMetaCallErrorHandler(QmlMetaCallErrorHandler handler) +{ + SignalManagerPrivate::m_qmlMetaCallErrorHandler = handler; +} + +static void qAppAboutToQuit() +{ + qAppRunning = false; + SignalManager::instance().purgeEmptyGlobalReceivers(); +} + +static bool isInMainThread(const QObject *o) +{ + if (o->isWidgetType() || o->isWindowType() || o->isQuickItemType()) + return true; + auto *app = QCoreApplication::instance(); + return app != nullptr && app->thread() == o->thread(); +} + +QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback, QObject *receiver) { - GlobalReceiverV2MapPtr globalReceivers = m_d->m_globalReceivers; - GlobalReceiverKey key = GlobalReceiverV2::key(callback); - GlobalReceiverV2 *gr = nullptr; - auto it = globalReceivers->find(key); - if (it == globalReceivers->end()) { - gr = new GlobalReceiverV2(callback, globalReceivers); - globalReceivers->insert(key, gr); - if (sender) { - gr->incRef(sender); // create a link reference - gr->decRef(); // remove extra reference + if (m_d->m_listener.isNull() && !QCoreApplication::closingDown()) { + if (auto *app = QCoreApplication::instance()) { + // The signal manager potentially outlives QCoreApplication, ensure deletion + m_d->m_listener = new SignalManagerDestroyListener(app); + m_d->m_listener->setObjectName("qt_pyside_signalmanagerdestroylistener"); + QObject::connect(app, &QCoreApplication::aboutToQuit, qAppAboutToQuit); + qAppRunning = true; } - } else { - gr = it.value(); - if (sender) - gr->incRef(sender); } - return reinterpret_cast<QObject *>(gr); + auto &globalReceivers = m_d->m_globalReceivers; + const GlobalReceiverKey key = GlobalReceiverV2::key(callback); + auto it = globalReceivers.find(key); + if (it == globalReceivers.end()) { + auto gr = std::make_shared<GlobalReceiverV2>(callback, receiver); + it = globalReceivers.insert(key, gr); + } + + if (sender != nullptr) { + it.value()->incRef(sender); // create a link reference + + // For main thread-objects, add a notification for destroy (PYSIDE-2646, 2141) + if (qAppRunning && !m_d->m_listener.isNull() && isInMainThread(sender)) { + QObject::connect(sender, &QObject::destroyed, + m_d->m_listener, &SignalManagerDestroyListener::destroyNotify, + Qt::UniqueConnection); + } + } + + return it.value().get(); } -int SignalManager::countConnectionsWith(const QObject *object) +void SignalManager::purgeEmptyGlobalReceivers() { - int count = 0; - for (GlobalReceiverV2Map::const_iterator it = m_d->m_globalReceivers->cbegin(), end = m_d->m_globalReceivers->cend(); it != end; ++it) { - if (it.value()->refCount(object)) - count++; - } - return count; + m_d->purgeEmptyGlobalReceivers(); } void SignalManager::notifyGlobalReceiver(QObject *receiver) { reinterpret_cast<GlobalReceiverV2 *>(receiver)->notify(); + purgeEmptyGlobalReceivers(); } void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiver) { - auto gr = reinterpret_cast<GlobalReceiverV2 *>(receiver); + auto gr = static_cast<GlobalReceiverV2 *>(receiver); gr->decRef(source); + if (gr->isEmpty()) + m_d->deleteGlobalReceiver(gr); +} + +void SignalManager::deleteGlobalReceiver(const QObject *gr) +{ + SignalManager::instance().m_d->deleteGlobalReceiver(gr); +} + +void SignalManager::SignalManagerPrivate::deleteGlobalReceiver(const QObject *gr) +{ + for (auto it = m_globalReceivers.begin(), end = m_globalReceivers.end(); it != end; ++it) { + if (it.value().get() == gr) { + m_globalReceivers.erase(it); + break; + } + } +} + +void SignalManager::SignalManagerPrivate::clear() +{ + // Delete receivers by always retrieving the current first element, + // because deleting a receiver can indirectly delete another one + // via ~DynamicSlotDataV2(). Using ~QHash/clear() could cause an + // iterator invalidation, and thus undefined behavior. + while (!m_globalReceivers.isEmpty()) + m_globalReceivers.erase(m_globalReceivers.cbegin()); +} + +static bool isEmptyGlobalReceiver(const GlobalReceiverV2Ptr &g) +{ + return g->isEmpty(); +} + +void SignalManager::SignalManagerPrivate::purgeEmptyGlobalReceivers() +{ + // Delete repetitively (see comment in clear()). + while (true) { + auto it = std::find_if(m_globalReceivers.cbegin(), m_globalReceivers.cend(), + isEmptyGlobalReceiver); + if (it == m_globalReceivers.cend()) + break; + m_globalReceivers.erase(it); + } } int SignalManager::globalReceiverSlotIndex(QObject *receiver, const char *signature) const { - return reinterpret_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature); + return static_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature); } bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *args) @@ -351,120 +479,159 @@ bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *ar // if the signature doesn't have a '(' it's a shor circuited signal, i.e. std::find // returned the string null terminator. bool isShortCircuit = !*std::find(signal, signal + std::strlen(signal), '('); - if (isShortCircuit) - return emitShortCircuitSignal(source, signalIndex, args); - else - return MetaFunction::call(source, signalIndex, args); + return isShortCircuit + ? emitShortCircuitSignal(source, signalIndex, args) + : MetaFunction::call(source, signalIndex, args); } return false; } -int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, void **args) +// Handle errors from meta calls. Requires GIL and PyErr_Occurred() +void SignalManager::SignalManagerPrivate::handleMetaCallError(QObject *object, int *result) +{ + // Bubbles Python exceptions up to the Javascript engine, if called from one + if (m_qmlMetaCallErrorHandler) { + auto idOpt = m_qmlMetaCallErrorHandler(object); + if (idOpt.has_value()) + *result = idOpt.value(); + } + + const int reclimit = Py_GetRecursionLimit(); + // Inspired by Python's errors.c: PyErr_GivenExceptionMatches() function. + // Temporarily bump the recursion limit, so that PyErr_Print will not raise a recursion + // error again. Don't do it when the limit is already insanely high, to avoid overflow. + if (reclimit < (1 << 30)) + Py_SetRecursionLimit(reclimit + 5); + PyErr_Print(); + Py_SetRecursionLimit(reclimit); +} + +// Handler for QMetaObject::ReadProperty/WriteProperty/ResetProperty: +int SignalManager::SignalManagerPrivate::qtPropertyMetacall(QObject *object, + QMetaObject::Call call, + int id, void **args) { const QMetaObject *metaObject = object->metaObject(); - PySideProperty *pp = nullptr; - PyObject *pp_name = nullptr; - QMetaProperty mp; - PyObject *pySelf = nullptr; - int methodCount = metaObject->methodCount(); - int propertyCount = metaObject->propertyCount(); - - if (call != QMetaObject::InvokeMetaMethod) { - mp = metaObject->property(id); - if (!mp.isValid()) { - return id - methodCount; - } + int result = id - metaObject->propertyCount(); + + const QMetaProperty mp = metaObject->property(id); + + qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ + << ' ' << metaCallName(call) << " #" << id << ' ' << mp.typeName() + << "/\"" << mp.name() << "\" " << object; + + if (!mp.isValid()) + return result; - Shiboken::GilState gil; - pySelf = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(object)); - Q_ASSERT(pySelf); - pp_name = Shiboken::String::fromCString(mp.name()); - pp = Property::getObject(pySelf, pp_name); - if (!pp) { - qWarning("Invalid property: %s.", mp.name()); - Py_XDECREF(pp_name); - return id - methodCount; + Shiboken::GilState gil; + auto *pySbkSelf = Shiboken::BindingManager::instance().retrieveWrapper(object); + Q_ASSERT(pySbkSelf); + auto *pySelf = reinterpret_cast<PyObject *>(pySbkSelf); + Shiboken::AutoDecRef pp_name(Shiboken::String::fromCString(mp.name())); + PySideProperty *pp = Property::getObject(pySelf, pp_name); + if (!pp) { + qWarning("Invalid property: %s.", mp.name()); + return false; + } + pp->d->metaCall(pySelf, call, args); + Py_DECREF(pp); + if (PyErr_Occurred()) { + // PYSIDE-2160: An unknown type was reported. Indicated by StopIteration. + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyObject *excType, *excValue, *excTraceback; + PyErr_Fetch(&excType, &excValue, &excTraceback); + bool ign = call == QMetaObject::WriteProperty; + PyErr_WarnFormat(PyExc_RuntimeWarning, 0, + ign ? "Unknown property type '%s' of QObject '%s' used in fset" + : "Unknown property type '%s' of QObject '%s' used in fget with %R", + pp->d->typeName.constData(), metaObject->className(), excValue); + if (PyErr_Occurred()) + Shiboken::Errors::storeErrorOrPrint(); + Py_DECREF(excType); + Py_DECREF(excValue); + Py_XDECREF(excTraceback); + return result; } + + qWarning().noquote().nospace() + << "An error occurred executing the property metacall " << call + << " on property \"" << mp.name() << "\" of " << object; + handleMetaCallError(object, &result); } + return result; +} - switch(call) { -#ifndef QT_NO_PROPERTIES - case QMetaObject::ReadProperty: - case QMetaObject::WriteProperty: - case QMetaObject::ResetProperty: - pp->d->metaCallHandler(pp, pySelf, call, args); - break; -#endif - case QMetaObject::InvokeMetaMethod: - id = callMethod(object, id, args); - break; +// Handler for QMetaObject::InvokeMetaMethod +int SignalManager::SignalManagerPrivate::qtMethodMetacall(QObject *object, + int id, void **args) +{ + const QMetaObject *metaObject = object->metaObject(); + const QMetaMethod method = metaObject->method(id); + int result = id - metaObject->methodCount(); - default: - qWarning("Unsupported meta invocation type."); - } + std::unique_ptr<Shiboken::GilState> gil; - // WARNING Isn't safe to call any metaObject and/or object methods beyond this point - // because the object can be deleted inside the called slot. + qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ << " #" << id + << " \"" << method.methodSignature() << '"'; - if (call == QMetaObject::InvokeMetaMethod) { - id = id - methodCount; + if (method.methodType() == QMetaMethod::Signal) { + // emit python signal + QMetaObject::activate(object, id, args); } else { - id = id - propertyCount; + gil.reset(new Shiboken::GilState); + auto *pySbkSelf = Shiboken::BindingManager::instance().retrieveWrapper(object); + Q_ASSERT(pySbkSelf); + auto *pySelf = reinterpret_cast<PyObject *>(pySbkSelf); + QByteArray methodName = method.methodSignature(); + methodName.truncate(methodName.indexOf('(')); + Shiboken::AutoDecRef pyMethod(PyObject_GetAttrString(pySelf, methodName)); + if (pyMethod.isNull()) { + PyErr_Format(PyExc_AttributeError, "Slot '%s::%s' not found.", + metaObject->className(), method.methodSignature().constData()); + } else { + SignalManager::callPythonMetaMethod(method, args, pyMethod, false); + } } + // WARNING Isn't safe to call any metaObject and/or object methods beyond this point + // because the object can be deleted inside the called slot. - if (pp || pp_name) { - Shiboken::GilState gil; - Py_XDECREF(pp); - Py_XDECREF(pp_name); - } + if (gil.get() == nullptr) + gil.reset(new Shiboken::GilState); - // Bubbles Python exceptions up to the Javascript engine, if called from one - { - Shiboken::GilState gil; - - if (PyErr_Occurred()) { - -#if PYSIDE_QML_PRIVATE_API_SUPPORT - // This JS engine grabber based off of Qt 5.5's `qjsEngine` function - QQmlData *data = QQmlData::get(object, false); - - if (data && !data->jsWrapper.isNullOrUndefined()) { - QV4::ExecutionEngine *engine = data->jsWrapper.engine(); - if (engine->currentStackFrame != nullptr) { - PyObject *errType, *errValue, *errTraceback; - PyErr_Fetch(&errType, &errValue, &errTraceback); - // PYSIDE-464: The error is only valid before PyErr_Restore, - // PYSIDE-464: therefore we take local copies. - Shiboken::AutoDecRef objStr(PyObject_Str(errValue)); - const QString errString = QLatin1String(Shiboken::String::toCString(objStr)); - const bool isSyntaxError = errType == PyExc_SyntaxError; - const bool isTypeError = errType == PyExc_TypeError; - PyErr_Restore(errType, errValue, errTraceback); - - PyErr_Print(); // Note: PyErr_Print clears the error. - - if (isSyntaxError) { - return engine->throwSyntaxError(errString); - } else if (isTypeError) { - return engine->throwTypeError(errString); - } else { - return engine->throwError(errString); - } - } - } -#endif // PYSIDE_QML_PRIVATE_API_SUPPORT - - int reclimit = Py_GetRecursionLimit(); - // Inspired by Python's errors.c: PyErr_GivenExceptionMatches() function. - // Temporarily bump the recursion limit, so that PyErr_Print will not raise a recursion - // error again. Don't do it when the limit is already insanely high, to avoid overflow. - if (reclimit < (1 << 30)) - Py_SetRecursionLimit(reclimit + 5); - PyErr_Print(); - Py_SetRecursionLimit(reclimit); - } - } + if (PyErr_Occurred()) + handleMetaCallError(object, &result); + + return result; +} +int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, void **args) +{ + switch (call) { + case QMetaObject::ReadProperty: + case QMetaObject::WriteProperty: + case QMetaObject::ResetProperty: + id = SignalManagerPrivate::qtPropertyMetacall(object, call, id, args); + break; + case QMetaObject::RegisterPropertyMetaType: + case QMetaObject::BindableProperty: + id -= object->metaObject()->propertyCount(); + break; + case QMetaObject::InvokeMetaMethod: + id = SignalManagerPrivate::qtMethodMetacall(object, id, args); + break; + case QMetaObject::CreateInstance: + case QMetaObject::IndexOfMethod: + case QMetaObject::RegisterMethodArgumentMetaType: + case QMetaObject::CustomCall: + qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ << ' ' + << metaCallName(call) << " #" << id << ' ' << object; + id -= object->metaObject()->methodCount(); + break; +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + case QMetaObject::ConstructInPlace: + break; +#endif + } return id; } @@ -473,35 +640,27 @@ int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, Q_ASSERT(pyMethod); Shiboken::GilState gil; - PyObject *pyArguments = nullptr; - - if (isShortCuit){ - pyArguments = reinterpret_cast<PyObject *>(args[1]); - } else { - pyArguments = parseArguments(method.parameterTypes(), args); - } + PyObject *pyArguments = isShortCuit + ? reinterpret_cast<PyObject *>(args[1]) : parseArguments(method, args); if (pyArguments) { - Shiboken::Conversions::SpecificConverter *retConverter = nullptr; + QScopedPointer<Shiboken::Conversions::SpecificConverter> retConverter; const char *returnType = method.typeName(); - if (returnType && std::strcmp("", returnType) && std::strcmp("void", returnType)) { - retConverter = new Shiboken::Conversions::SpecificConverter(returnType); - if (!retConverter || !*retConverter) { - PyErr_Format(PyExc_RuntimeError, "Can't find converter for '%s' to call Python meta method.", returnType); + if (returnType != nullptr && returnType[0] != 0 && std::strcmp("void", returnType) != 0) { + retConverter.reset(new Shiboken::Conversions::SpecificConverter(returnType)); + if (!retConverter->isValid()) { + PyErr_SetString(PyExc_RuntimeError, msgCannotConvertReturn(method).constData()); return -1; } } Shiboken::AutoDecRef retval(PyObject_CallObject(pyMethod, pyArguments)); - if (!isShortCuit && pyArguments){ + if (!isShortCuit && pyArguments) Py_DECREF(pyArguments); - } - if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) { + if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) retConverter->toCpp(retval, args[0]); - } - delete retConverter; } return -1; @@ -531,6 +690,57 @@ static MetaObjectBuilder *metaBuilderFromDict(PyObject *dict) return reinterpret_cast<MetaObjectBuilder *>(PyCapsule_GetPointer(pyBuilder, nullptr)); } +// Helper to format a method signature "foo(QString)" into +// Slot decorator "@Slot(str)" + +struct slotSignature +{ + explicit slotSignature(const char *signature) : m_signature(signature) {} + + const char *m_signature; +}; + +QDebug operator<<(QDebug debug, const slotSignature &sig) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "@Slot("; + QByteArrayView signature(sig.m_signature); + const auto len = signature.size(); + auto pos = signature.indexOf('('); + if (pos != -1 && pos < len - 2) { + ++pos; + while (true) { + auto nextPos = signature.indexOf(',', pos); + if (nextPos == -1) + nextPos = len - 1; + const QByteArrayView parameter = signature.sliced(pos, nextPos - pos); + if (parameter == "QString") { + debug << "str"; + } else if (parameter == "double") { + debug << "float"; + } else { + const bool hasDelimiter = parameter.contains("::"); + if (hasDelimiter) + debug << '"'; + if (!hasDelimiter && parameter.endsWith('*')) + debug << parameter.first(parameter.size() - 1); + else + debug << parameter; + if (hasDelimiter) + debug << '"'; + } + pos = nextPos + 1; + if (pos >= len) + break; + debug << ','; + } + } + debug << ')'; + return debug; +} + int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signature, QMetaMethod::MethodType type) { if (!source) { @@ -544,26 +754,32 @@ int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signa if (methodIndex == -1) { SbkObject *self = Shiboken::BindingManager::instance().retrieveWrapper(source); if (!Shiboken::Object::hasCppWrapper(self)) { - qWarning() << "Invalid Signal signature:" << signature; + qWarning().noquote().nospace() << __FUNCTION__ + << ": Cannot add dynamic method \"" << signature << "\" (" << type + << ") to " << source << ": No Wrapper found."; return -1; - } else { - auto pySelf = reinterpret_cast<PyObject *>(self); - PyObject *dict = self->ob_dict; - MetaObjectBuilder *dmo = metaBuilderFromDict(dict); - - // Create a instance meta object - if (!dmo) { - dmo = new MetaObjectBuilder(Py_TYPE(pySelf), metaObject); - PyObject *pyDmo = PyCapsule_New(dmo, nullptr, destroyMetaObject); - PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo); - Py_DECREF(pyDmo); - } + } + auto *pySelf = reinterpret_cast<PyObject *>(self); + auto *dict = SbkObject_GetDict_NoRef(pySelf); + MetaObjectBuilder *dmo = metaBuilderFromDict(dict); + + // Create a instance meta object + if (!dmo) { + dmo = new MetaObjectBuilder(Py_TYPE(pySelf), metaObject); + PyObject *pyDmo = PyCapsule_New(dmo, nullptr, destroyMetaObject); + PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo); + Py_DECREF(pyDmo); + } - if (type == QMetaMethod::Signal) - return dmo->addSignal(signature); - else - return dmo->addSlot(signature); + if (type == QMetaMethod::Slot) { + qCWarning(lcPySide).noquote().nospace() + << "Warning: Registering dynamic slot \"" + << signature << "\" on \"" << source->metaObject()->className() + << "\". Consider annotating with " << slotSignature(signature); } + + return type == QMetaMethod::Signal + ? dmo->addSignal(signature) : dmo->addSlot(signature); } return methodIndex; } @@ -580,51 +796,30 @@ const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self) // m_dirty flag is set. Q_ASSERT(self); - MetaObjectBuilder *builder = metaBuilderFromDict(reinterpret_cast<SbkObject *>(self)->ob_dict); + auto *ob_dict = SbkObject_GetDict_NoRef(self); + MetaObjectBuilder *builder = metaBuilderFromDict(ob_dict); if (!builder) builder = &(retrieveTypeUserData(self)->mo); return builder->update(); } -namespace { - -static int callMethod(QObject *object, int id, void **args) -{ - const QMetaObject *metaObject = object->metaObject(); - QMetaMethod method = metaObject->method(id); - - if (method.methodType() == QMetaMethod::Signal) { - // emit python signal - QMetaObject::activate(object, id, args); - } else { - Shiboken::GilState gil; - auto self = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(object)); - QByteArray methodName = method.methodSignature(); - methodName.truncate(methodName.indexOf('(')); - Shiboken::AutoDecRef pyMethod(PyObject_GetAttrString(self, methodName)); - return SignalManager::callPythonMetaMethod(method, args, pyMethod, false); - } - return -1; -} - - -static PyObject *parseArguments(const QList<QByteArray>& paramTypes, void **args) +static PyObject *parseArguments(const QMetaMethod &method, void **args) { - int argsSize = paramTypes.count(); + const auto ¶mTypes = method.parameterTypes(); + const qsizetype argsSize = paramTypes.size(); PyObject *preparedArgs = PyTuple_New(argsSize); - for (int i = 0, max = argsSize; i < max; ++i) { + for (qsizetype i = 0; i < argsSize; ++i) { void *data = args[i+1]; - const char *dataType = paramTypes[i].constData(); - Shiboken::Conversions::SpecificConverter converter(dataType); - if (converter) { - PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data)); - } else { - PyErr_Format(PyExc_TypeError, "Can't call meta function because I have no idea how to handle %s", dataType); + auto param = paramTypes.at(i); + Shiboken::Conversions::SpecificConverter converter(param.constData()); + if (!converter) { + PyErr_SetString(PyExc_TypeError, msgCannotConvertParameter(method, i).constData()); Py_DECREF(preparedArgs); - return 0; + return nullptr; } + PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data)); } return preparedArgs; } @@ -636,4 +831,4 @@ static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *a return true; } -} //namespace +#include "signalmanager.moc" |