aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/libpyside/pyside.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/libpyside/pyside.cpp')
-rw-r--r--sources/pyside6/libpyside/pyside.cpp344
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