diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2025-04-25 09:25:13 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2025-05-09 06:54:12 +0200 |
commit | f81fb9ee887d088e8988d743bb7cac4f781fff82 (patch) | |
tree | e2d1989c78bd4914f9f4c83c853113e34a980ec4 | |
parent | 7cd0aee8e7b7d49ace2e28f204ea469bc9e60271 (diff) |
Fix crash retrieving a QGraphicsProxyObject from QVariant
Remove the default-superclass specification QGraphicsItem for
QGraphicsObject since it causes the type discovery to directly cast
from QGraphicsObject* to QGraphicsItem*. This crashes
since QGraphicsObject inherits from QObject as first base class.
The intention of the class attribute was to ensure that any
QGraphicsObject class is stored as a QGraphicsItem. To preserve this,
hardcode it in QVariant_resolveMetaType().
Pick-to: 6.9
Fixes: PYSIDE-3069
Task-number: PYSIDE-86
Task-number: PYSIDE-1887
Change-Id: I3704988f105b118b1e4ef8d078b68c01ba89386c
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
5 files changed, 62 insertions, 2 deletions
diff --git a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp index c073c8bc1..e58d54998 100644 --- a/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp +++ b/sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp @@ -45,7 +45,14 @@ QMetaType QVariant_resolveMetaType(PyTypeObject *type) // that has added any python fields or slots to its object layout. // See https://mail.python.org/pipermail/python-list/2009-January/520733.html if (type->tp_bases) { - for (Py_ssize_t i = 0, size = PyTuple_Size(type->tp_bases); i < size; ++i) { + const auto size = PyTuple_Size(type->tp_bases); + Py_ssize_t i = 0; + // PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject; + // it needs to use always QGraphicsItem as a QVariant type for + // QGraphicsItem::itemChange() to work. + if (qstrcmp(typeName, "QGraphicsObject*") == 0) + ++i; + for ( ; i < size; ++i) { auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i)); const QMetaType derived = QVariant_resolveMetaType(baseType); if (derived.isValid()) diff --git a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml index 509459ec6..a15527c03 100644 --- a/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml @@ -3358,7 +3358,7 @@ <enum-type name="PixmapPadMode"/> </object-type> - <object-type name="QGraphicsObject" default-superclass="QGraphicsItem"/> + <object-type name="QGraphicsObject"/> <object-type name="QGraphicsOpacityEffect"/> <object-type name="QGraphicsRotation"/> <object-type name="QGraphicsScale"/> diff --git a/sources/pyside6/tests/QtWidgets/CMakeLists.txt b/sources/pyside6/tests/QtWidgets/CMakeLists.txt index 01b7d08ea..9bb2fad67 100644 --- a/sources/pyside6/tests/QtWidgets/CMakeLists.txt +++ b/sources/pyside6/tests/QtWidgets/CMakeLists.txt @@ -84,6 +84,7 @@ PYSIDE_TEST(qapp_issue_585.py) PYSIDE_TEST(qapp_test.py) PYSIDE_TEST(qapplication_test.py) PYSIDE_TEST(qapplication_exit_segfault_test.py) +PYSIDE_TEST(pyside3069.py) PYSIDE_TEST(qdialog_test.py) PYSIDE_TEST(qdynamic_signal.py) # TODO: This passes, but requires manual button clicking (at least on mac) diff --git a/sources/pyside6/tests/QtWidgets/pyside3069.py b/sources/pyside6/tests/QtWidgets/pyside3069.py new file mode 100644 index 000000000..62ad73038 --- /dev/null +++ b/sources/pyside6/tests/QtWidgets/pyside3069.py @@ -0,0 +1,51 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +from __future__ import annotations + +import os +import sys +import unittest + +from pathlib import Path +sys.path.append(os.fspath(Path(__file__).resolve().parents[1])) +from init_paths import init_test_paths # noqa: E402 +init_test_paths(False) + +from PySide6.QtCore import Qt # noqa: E402 +from PySide6.QtWidgets import QApplication, QComboBox, QGraphicsScene, QGraphicsView # noqa: E402 + +from helper.usesqapplication import UsesQApplication # noqa: E402 + + +class BugTest(UsesQApplication): + """PYSIDE-3069: Test that the conversion of an element of a list + QGraphicsItem* to QGraphicsProxyWidget* (inheriting QObject/QGraphicsItem) + works correctly without crash. + + For this, we need a QGraphicsProxyWidget for which no wrapper exists, + created in C++. So, we populate a combo, add it to the scene and show its + popup, which creates a top level that is automatically wrapped by + another QGraphicsProxyWidget. This, we print from the list of items(). + + See also PYSIDE-86, PYSIDE-1887.""" + def test(self): + qApp.setEffectEnabled(Qt.UI_AnimateCombo, False) # noqa: F821 + cb = QComboBox() + cb.addItem("i1") + cb.addItem("i2") + scene = QGraphicsScene() + scene.addWidget(cb) + view = QGraphicsView(scene) + view.show() + cb.showPopup() + while not view.windowHandle().isExposed(): + QApplication.processEvents() + items = scene.items() + self.assertEqual(len(items), 2) # Combo and its popup, created in C++ + for i in items: + print(i) + view.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py b/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py index 71aba9941..91b405aaf 100644 --- a/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py +++ b/sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py @@ -50,6 +50,7 @@ class QGraphicsObjectReimpl(UsesQApplication): # and then the QVariant was not associated with # a QGraphicsItem but a QObjectItem because the base # class was a QObject. + # See also PYSIDE-1887, PYSIDE-3069 gobjA = GObjA() gobjA.setParentItem(w) self.assertIs(type(w), type(gobjA.parentItem())) |