diff options
Diffstat (limited to 'sources/pyside6/libpyside/pyside.cpp')
-rw-r--r-- | sources/pyside6/libpyside/pyside.cpp | 344 |
1 files changed, 300 insertions, 44 deletions
diff --git a/sources/pyside6/libpyside/pyside.cpp b/sources/pyside6/libpyside/pyside.cpp index a60b7131a..9e12e3cd7 100644 --- a/sources/pyside6/libpyside/pyside.cpp +++ b/sources/pyside6/libpyside/pyside.cpp @@ -32,21 +32,28 @@ #include <sbkstring.h> #include <sbkstaticstrings.h> #include <sbkfeature_base.h> +#include <sbkmodule.h> #include <QtCore/QByteArray> #include <QtCore/QCoreApplication> +#include <QtCore/QDebug> #include <QtCore/QDir> #include <QtCore/QFileInfo> +#include <QtCore/QMetaMethod> #include <QtCore/QMutex> #include <QtCore/QStack> #include <QtCore/QThread> +#include <QtCore/private/qobject_p.h> #include <algorithm> #include <cstring> #include <cctype> #include <memory> +#include <optional> #include <typeinfo> +using namespace Qt::StringLiterals; + static QStack<PySide::CleanupFunction> cleanupFunctionList; static void *qobjectNextAddr; @@ -57,6 +64,20 @@ QT_END_NAMESPACE Q_LOGGING_CATEGORY(lcPySide, "qt.pyside.libpyside", QtCriticalMsg) +static QObjectData *qt_object_private(const QObject *o) +{ + class FriendlyQObject : public QObject { + public: + using QObject::d_ptr; + }; + return static_cast<const FriendlyQObject *>(o)->d_ptr.data(); +} + +static bool hasDynamicMetaObject(const QObject *o) +{ + return qt_object_private(o)->metaObject != nullptr; +} + namespace PySide { @@ -198,6 +219,8 @@ static QByteArrayList _SbkType_LookupProperty(PyTypeObject *type, auto len = std::strlen(origName); for (Py_ssize_t idx = 0; idx < n; idx++) { PyTypeObject *base = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, idx)); + if (!SbkObjectType_Check(base)) + continue; auto props = SbkObjectType_GetPropertyStrings(base); if (props == nullptr || *props == nullptr) continue; @@ -297,7 +320,23 @@ static bool _setProperty(PyObject *qObj, PyObject *name, PyObject *value, bool * return true; } -bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds) +// PYSIDE-2329: Search a signal by name (Note: QMetaObject::indexOfSignal() +// searches by signature). +static std::optional<QMetaMethod> findSignal(const QMetaObject *mo, + const QByteArray &name) +{ + const auto count = mo->methodCount(); + for (int i = mo->methodOffset(); i < count; ++i) { + const auto method = mo->method(i); + if (method.methodType() == QMetaMethod::Signal && method.name() == name) + return method; + } + auto *base = mo->superClass(); + return base != nullptr ? findSignal(base, name) : std::nullopt; +} + +bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, + PyObject *kwds, bool allowErrors) { PyObject *key, *value; @@ -306,7 +345,7 @@ bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds int snake_flag = flags & 0x01; while (PyDict_Next(kwds, &pos, &key, &value)) { - QByteArray propName(Shiboken::String::toCString(key)); + const QByteArray propName = Shiboken::String::toCString(key); QByteArray unmangledName = _sigWithOrigName(propName, snake_flag); bool accept = false; // PYSIDE-1705: Make sure that un-mangled names are not recognized in snake_case mode. @@ -315,11 +354,11 @@ bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds if (!_setProperty(qObj, key, value, &accept)) return false; } else { - propName.append("()"); - if (metaObj->indexOfSignal(propName) != -1) { + const auto methodO = findSignal(metaObj, propName); + if (methodO.has_value()) { + const auto signature = "2"_ba + methodO->methodSignature(); accept = true; - propName.prepend('2'); - if (!PySide::Signal::connect(qObj, propName, value)) + if (!PySide::Signal::connect(qObj, signature, value)) return false; } } @@ -329,6 +368,10 @@ bool fillQtProperties(PyObject *qObj, const QMetaObject *metaObj, PyObject *kwds return false; } } + if (allowErrors) { + PyErr_Clear(); + continue; + } if (!accept) { PyErr_Format(PyExc_AttributeError, "'%s' is not a Qt property or a signal", propName.constData()); @@ -419,6 +462,8 @@ void initDynamicMetaObject(PyTypeObject *type, const QMetaObject *base, std::siz TypeUserData *retrieveTypeUserData(PyTypeObject *pyTypeObj) { + if (!SbkObjectType_Check(pyTypeObj)) + return nullptr; return reinterpret_cast<TypeUserData *>(Shiboken::ObjectType::getTypeUserData(pyTypeObj)); } @@ -445,7 +490,6 @@ const QMetaObject *retrieveMetaObject(PyObject *pyObj) void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject * /* kwds */) { PyTypeObject *qObjType = Shiboken::Conversions::getPythonTypeObject("QObject*"); - QByteArray className(Shiboken::String::toCString(PyTuple_GET_ITEM(args, 0))); PyObject *bases = PyTuple_GET_ITEM(args, 1); int numBases = PyTuple_GET_SIZE(bases); @@ -460,7 +504,9 @@ void initQObjectSubType(PyTypeObject *type, PyObject *args, PyObject * /* kwds * } } if (!userData) { - qWarning("Sub class of QObject not inheriting QObject!? Crash will happen when using %s.", className.constData()); + const char *className = Shiboken::String::toCString(PyTuple_GET_ITEM(args, 0)); + qWarning("Sub class of QObject not inheriting QObject!? Crash will happen when using %s.", + className); return; } // PYSIDE-1463: Don't change feature selection durin subtype initialization. @@ -494,6 +540,7 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n { using Shiboken::AutoDecRef; + // PYSIDE-68-bis: This getattr finds signals early by `signalDescrGet`. PyObject *attr = PyObject_GenericGetAttr(self, name); if (!Shiboken::Object::isValid(reinterpret_cast<SbkObject *>(self), false)) return attr; @@ -506,15 +553,6 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n attr = value; } - // Mutate native signals to signal instance type - // Caution: This inserts the signal instance into the instance dict. - if (attr && PyObject_TypeCheck(attr, PySideSignal_TypeF())) { - auto *inst = Signal::initialize(reinterpret_cast<PySideSignal *>(attr), name, self); - PyObject *signalInst = reinterpret_cast<PyObject *>(inst); - PyObject_SetAttr(self, name, signalInst); - return signalInst; - } - // Search on metaobject (avoid internal attributes started with '__') if (!attr) { PyObject *type, *value, *traceback; @@ -556,7 +594,7 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n const char *cname = Shiboken::String::toCString(name); uint cnameLen = qstrlen(cname); - if (std::strncmp("__", cname, 2)) { + if (std::strncmp("__", cname, 2) != 0) { const QMetaObject *metaObject = cppSelf->metaObject(); QList<QMetaMethod> signalList; // Caution: This inserts a meta function or a signal into the instance dict. @@ -596,12 +634,6 @@ PyObject *getHiddenDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *n return attr; } -// PYSIDE-1889: Keeping the old, misleading API for a while. -PyObject *getMetaDataFromQObject(QObject *cppSelf, PyObject *self, PyObject *name) -{ - return getHiddenDataFromQObject(cppSelf, self, name); -} - bool inherits(PyTypeObject *objType, const char *class_name) { if (strcmp(objType->tp_name, class_name) == 0) @@ -635,7 +667,7 @@ void setNextQObjectMemoryAddr(void *addr) // A std::shared_ptr is used with a deletion function to invalidate a pointer // when the property value is cleared. This should be a QSharedPointer with // a void *pointer, but that isn't allowed -typedef char any_t; +using any_t = char; Q_DECLARE_METATYPE(std::shared_ptr<any_t>); @@ -644,6 +676,11 @@ namespace PySide static void invalidatePtr(any_t *object) { + // PYSIDE-2254: Guard against QObjects outliving Python, for example the + // adopted main thread as returned by QObjects::thread(). + if (Py_IsInitialized() == 0) + return; + Shiboken::GilState state; SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(object); @@ -653,6 +690,32 @@ static void invalidatePtr(any_t *object) static const char invalidatePropertyName[] = "_PySideInvalidatePtr"; +// PYSIDE-2749: Skip over internal QML classes and classes +// with dynamic meta objects when looking for the best matching +// type to avoid unnessarily triggering the lazy load mechanism +// for classes that do not have a binding from things like eventFilter(). +static inline bool isInternalObject(const char *name) +{ + return std::strstr(name, "QMLTYPE") != nullptr || std::strstr(name, "QQmlPrivate") != nullptr; +} + +static const QMetaObject *metaObjectCandidate(const QObject *o) +{ + auto *metaObject = o->metaObject(); + // Skip QML helper types and Python objects + if (hasDynamicMetaObject(o)) { + if (auto *super = metaObject->superClass()) + metaObject = super; + } + for (auto *candidate = metaObject; candidate != nullptr; candidate = candidate->superClass()) { + if (!isInternalObject(candidate->className())) { + metaObject = candidate; + break; + } + } + return metaObject; +} + // PYSIDE-1214, when creating new wrappers for classes inheriting QObject but // not exposed to Python, try to find the best-matching (most-derived) Qt // class by walking up the meta objects. @@ -660,7 +723,8 @@ static const char *typeName(const QObject *cppSelf) { const char *typeName = typeid(*cppSelf).name(); if (!Shiboken::Conversions::getConverter(typeName)) { - for (auto metaObject = cppSelf->metaObject(); metaObject; metaObject = metaObject->superClass()) { + auto *metaObject = metaObjectCandidate(cppSelf); + for (; metaObject != nullptr; metaObject = metaObject->superClass()) { const char *name = metaObject->className(); if (Shiboken::Conversions::getConverter(name)) { typeName = name; @@ -709,7 +773,7 @@ PyObject *getWrapperForQObject(QObject *cppSelf, PyTypeObject *sbk_type) } } - pyOut = Shiboken::Object::newObject(sbk_type, cppSelf, false, false, typeName(cppSelf)); + pyOut = Shiboken::Object::newObjectWithHeuristics(sbk_type, cppSelf, false, typeName(cppSelf)); return pyOut; } @@ -813,9 +877,9 @@ bool registerInternalQtConf() { // Guard to ensure single registration. #ifdef PYSIDE_QT_CONF_PREFIX - static bool registrationAttempted = false; + static bool registrationAttempted = false; #else - static bool registrationAttempted = true; + static bool registrationAttempted = true; #endif static bool isRegistered = false; if (registrationAttempted) @@ -827,23 +891,32 @@ bool registerInternalQtConf() // This will disable the internal qt.conf which points to the PySide6 subdirectory (due to the // subdirectory not existing anymore). #ifndef PYPY_VERSION - QString executablePath = - QString::fromWCharArray(Py_GetProgramFullPath()); + QString executablePath = QString::fromWCharArray(Py_GetProgramFullPath()); #else // PYSIDE-535: FIXME: Add this function when available. - QString executablePath = QLatin1String("missing Py_GetProgramFullPath"); + QString executablePath = QLatin1StringView("missing Py_GetProgramFullPath"); #endif // PYPY_VERSION + QString appDirPath = QFileInfo(executablePath).absolutePath(); - QString maybeQtConfPath = QDir(appDirPath).filePath(QStringLiteral("qt.conf")); - bool executableQtConfAvailable = QFileInfo::exists(maybeQtConfPath); + + QString maybeQtConfPath = QDir(appDirPath).filePath(u"qt.conf"_s); maybeQtConfPath = QDir::toNativeSeparators(maybeQtConfPath); + bool executableQtConfAvailable = QFileInfo::exists(maybeQtConfPath); + + QString maybeQt6ConfPath = QDir(appDirPath).filePath(u"qt6.conf"_s); + maybeQt6ConfPath = QDir::toNativeSeparators(maybeQt6ConfPath); + bool executableQt6ConfAvailable = QFileInfo::exists(maybeQt6ConfPath); // Allow disabling the usage of the internal qt.conf. This is necessary for tests to work, // because tests are executed before the package is installed, and thus the Prefix specified // in qt.conf would point to a not yet existing location. bool disableInternalQtConf = - qEnvironmentVariableIntValue("PYSIDE_DISABLE_INTERNAL_QT_CONF") > 0; - if (disableInternalQtConf || executableQtConfAvailable) { + qEnvironmentVariableIntValue("PYSIDE_DISABLE_INTERNAL_QT_CONF") > 0; + bool runsInConda = + qEnvironmentVariableIsSet("CONDA_DEFAULT_ENV") || qEnvironmentVariableIsSet("CONDA_PREFIX"); + + if ((!runsInConda && (disableInternalQtConf || executableQtConfAvailable)) + || (runsInConda && executableQt6ConfAvailable)) { registrationAttempted = true; return false; } @@ -877,15 +950,13 @@ bool registerInternalQtConf() // rccData needs to be static, otherwise when it goes out of scope, the Qt resource system // will point to invalid memory. - static QByteArray rccData = QByteArrayLiteral("[Paths]\nPrefix = ") + prefixPath + static QByteArray rccData = QByteArrayLiteral("[Paths]\nPrefix = ") + prefixPath + "\n"; #ifdef Q_OS_WIN - // LibraryExecutables needs to point to Prefix instead of ./bin because we don't - // currently conform to the Qt default directory layout on Windows. This is necessary - // for QtWebEngineCore to find the location of QtWebEngineProcess.exe. - + QByteArray("\nLibraryExecutables = ") + prefixPath + // LibraryExecutables needs to point to Prefix instead of ./bin because we don't + // currently conform to the Qt default directory layout on Windows. This is necessary + // for QtWebEngineCore to find the location of QtWebEngineProcess.exe. + rccData += QByteArrayLiteral("LibraryExecutables = ") + prefixPath + "\n"; #endif - ; - rccData.append('\n'); // The RCC data structure expects a 4-byte size value representing the actual data. qsizetype size = rccData.size(); @@ -955,5 +1026,190 @@ QMetaType qMetaTypeFromPyType(PyTypeObject *pyType) return QMetaType::fromName(pyType->tp_name); } -} //namespace PySide +debugPyTypeObject::debugPyTypeObject(const PyTypeObject *o) noexcept + : m_object(o) +{ +} + +QDebug operator<<(QDebug debug, const debugPyTypeObject &o) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "PyTypeObject("; + if (o.m_object) + debug << '"' << o.m_object->tp_name << '"'; + else + debug << '0'; + debug << ')'; + return debug; +} + +static void formatPyObject(PyObject *obj, QDebug &debug); + +static void formatPySequence(PyObject *obj, QDebug &debug) +{ + const Py_ssize_t size = PySequence_Size(obj); + debug << size << " ["; + for (Py_ssize_t i = 0; i < size; ++i) { + if (i) + debug << ", "; + Shiboken::AutoDecRef item(PySequence_GetItem(obj, i)); + formatPyObject(item.object(), debug); + } + debug << ']'; +} +static void formatPyDict(PyObject *obj, QDebug &debug) +{ + PyObject *key; + PyObject *value; + Py_ssize_t pos = 0; + bool first = true; + debug << '{'; + while (PyDict_Next(obj, &pos, &key, &value) != 0) { + if (first) + first = false; + else + debug << ", "; + formatPyObject(key, debug); + debug << '='; + formatPyObject(value, debug); + } + debug << '}'; +} + +static inline const char *pyTypeName(PyObject *obj) +{ + return Py_TYPE(obj)->tp_name; +} + +static QString getQualName(PyObject *obj) +{ + Shiboken::AutoDecRef result(PyObject_GetAttr(obj, Shiboken::PyMagicName::qualname())); + return result.object() != nullptr + ? pyStringToQString(result.object()) : QString{}; +} + +static void formatPyFunction(PyObject *obj, QDebug &debug) +{ + debug << '"' << getQualName(obj) << "()\""; +} + +static void formatPyMethod(PyObject *obj, QDebug &debug) +{ + if (auto *func = PyMethod_Function(obj)) + formatPyFunction(func, debug); + debug << ", instance=" << PyMethod_Self(obj); +} + +static void formatPyObjectValue(PyObject *obj, QDebug &debug) +{ + if (PyType_Check(obj) != 0) + debug << "type: \"" << pyTypeName(obj) << '"'; + else if (PyLong_Check(obj) != 0) { + const auto llv = PyLong_AsLongLong(obj); + if (PyErr_Occurred() != PyExc_OverflowError) { + debug << llv; + } else { + PyErr_Clear(); + debug << "0x" << Qt::hex << PyLong_AsUnsignedLongLong(obj) << Qt::dec; + } + } else if (PyFloat_Check(obj) != 0) + debug << PyFloat_AsDouble(obj); + else if (PyUnicode_Check(obj) != 0) + debug << '"' << pyStringToQString(obj) << '"'; + else if (PyFunction_Check(obj) != 0) + formatPyFunction(obj, debug); + else if (PyMethod_Check(obj) != 0) + formatPyMethod(obj, debug); + else if (PySequence_Check(obj) != 0) + formatPySequence(obj, debug); + else if (PyDict_Check(obj) != 0) + formatPyDict(obj, debug); + else + debug << obj; +} + +static void formatPyObject(PyObject *obj, QDebug &debug) +{ + if (obj == nullptr) { + debug << '0'; + return; + } + if (obj == Py_None) { + debug << "None"; + return; + } + if (obj == Py_True) { + debug << "True"; + return; + } + if (obj == Py_False) { + debug << "False"; + return; + } + if (PyType_Check(obj) == 0) + debug << pyTypeName(obj) << ": "; + formatPyObjectValue(obj, debug); +} + +debugPyObject::debugPyObject(PyObject *o) noexcept : m_object(o) +{ +} + +QDebug operator<<(QDebug debug, const debugPyObject &o) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "PyObject("; + formatPyObject(o.m_object, debug); + debug << ')'; + return debug; +} + +debugPyBuffer::debugPyBuffer(Py_buffer *b) noexcept : m_buffer(b) +{ +} + +static void formatPy_ssizeArray(QDebug &debug, const char *name, const Py_ssize_t *array, int len) +{ + debug << ", " << name << '='; + if (array != nullptr) { + debug << '['; + for (int i = 0; i < len; ++i) + debug << array[i] << ' '; + debug << ']'; + } else { + debug << '0'; + } +} + +PYSIDE_API QDebug operator<<(QDebug debug, const debugPyBuffer &b) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Py_buffer("; + if (b.m_buffer != nullptr) { + debug << "obj=" << b.m_buffer->obj + << ", buf=" << b.m_buffer->buf << ", len=" << b.m_buffer->len + << ", readonly=" << b.m_buffer->readonly + << ", itemsize=" << b.m_buffer->itemsize << ", format="; + if (b.m_buffer->format != nullptr) + debug << '"' << b.m_buffer->format << '"'; + else + debug << '0'; + debug << ", ndim=" << b.m_buffer->ndim; + formatPy_ssizeArray(debug, "shape", b.m_buffer->shape, b.m_buffer->ndim); + formatPy_ssizeArray(debug, "strides", b.m_buffer->strides, b.m_buffer->ndim); + formatPy_ssizeArray(debug, "suboffsets", b.m_buffer->suboffsets, b.m_buffer->ndim); + } else { + debug << '0'; + } + debug << ')'; + return debug; +} + +} // namespace PySide |