Skip to content

Commit f81fb9e

Browse files
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 <[email protected]>
1 parent 7cd0aee commit f81fb9e

File tree

5 files changed

+62
-2
lines changed

5 files changed

+62
-2
lines changed

sources/pyside6/PySide6/QtCore/glue/core_snippets.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ QMetaType QVariant_resolveMetaType(PyTypeObject *type)
4545
// that has added any python fields or slots to its object layout.
4646
// See https://mail.python.org/pipermail/python-list/2009-January/520733.html
4747
if (type->tp_bases) {
48-
for (Py_ssize_t i = 0, size = PyTuple_Size(type->tp_bases); i < size; ++i) {
48+
const auto size = PyTuple_Size(type->tp_bases);
49+
Py_ssize_t i = 0;
50+
// PYSIDE-1887, PYSIDE-86: Skip QObject base class of QGraphicsObject;
51+
// it needs to use always QGraphicsItem as a QVariant type for
52+
// QGraphicsItem::itemChange() to work.
53+
if (qstrcmp(typeName, "QGraphicsObject*") == 0)
54+
++i;
55+
for ( ; i < size; ++i) {
4956
auto baseType = reinterpret_cast<PyTypeObject *>(PyTuple_GetItem(type->tp_bases, i));
5057
const QMetaType derived = QVariant_resolveMetaType(baseType);
5158
if (derived.isValid())

sources/pyside6/PySide6/QtWidgets/typesystem_widgets_common.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3358,7 +3358,7 @@
33583358
<enum-type name="PixmapPadMode"/>
33593359
</object-type>
33603360

3361-
<object-type name="QGraphicsObject" default-superclass="QGraphicsItem"/>
3361+
<object-type name="QGraphicsObject"/>
33623362
<object-type name="QGraphicsOpacityEffect"/>
33633363
<object-type name="QGraphicsRotation"/>
33643364
<object-type name="QGraphicsScale"/>

sources/pyside6/tests/QtWidgets/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ PYSIDE_TEST(qapp_issue_585.py)
8484
PYSIDE_TEST(qapp_test.py)
8585
PYSIDE_TEST(qapplication_test.py)
8686
PYSIDE_TEST(qapplication_exit_segfault_test.py)
87+
PYSIDE_TEST(pyside3069.py)
8788
PYSIDE_TEST(qdialog_test.py)
8889
PYSIDE_TEST(qdynamic_signal.py)
8990
# TODO: This passes, but requires manual button clicking (at least on mac)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (C) 2025 The Qt Company Ltd.
2+
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3+
from __future__ import annotations
4+
5+
import os
6+
import sys
7+
import unittest
8+
9+
from pathlib import Path
10+
sys.path.append(os.fspath(Path(__file__).resolve().parents[1]))
11+
from init_paths import init_test_paths # noqa: E402
12+
init_test_paths(False)
13+
14+
from PySide6.QtCore import Qt # noqa: E402
15+
from PySide6.QtWidgets import QApplication, QComboBox, QGraphicsScene, QGraphicsView # noqa: E402
16+
17+
from helper.usesqapplication import UsesQApplication # noqa: E402
18+
19+
20+
class BugTest(UsesQApplication):
21+
"""PYSIDE-3069: Test that the conversion of an element of a list
22+
QGraphicsItem* to QGraphicsProxyWidget* (inheriting QObject/QGraphicsItem)
23+
works correctly without crash.
24+
25+
For this, we need a QGraphicsProxyWidget for which no wrapper exists,
26+
created in C++. So, we populate a combo, add it to the scene and show its
27+
popup, which creates a top level that is automatically wrapped by
28+
another QGraphicsProxyWidget. This, we print from the list of items().
29+
30+
See also PYSIDE-86, PYSIDE-1887."""
31+
def test(self):
32+
qApp.setEffectEnabled(Qt.UI_AnimateCombo, False) # noqa: F821
33+
cb = QComboBox()
34+
cb.addItem("i1")
35+
cb.addItem("i2")
36+
scene = QGraphicsScene()
37+
scene.addWidget(cb)
38+
view = QGraphicsView(scene)
39+
view.show()
40+
cb.showPopup()
41+
while not view.windowHandle().isExposed():
42+
QApplication.processEvents()
43+
items = scene.items()
44+
self.assertEqual(len(items), 2) # Combo and its popup, created in C++
45+
for i in items:
46+
print(i)
47+
view.close()
48+
49+
50+
if __name__ == "__main__":
51+
unittest.main()

sources/pyside6/tests/QtWidgets/qgraphicsobjectreimpl_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def testReimplementationTypes(self):
5050
# and then the QVariant was not associated with
5151
# a QGraphicsItem but a QObjectItem because the base
5252
# class was a QObject.
53+
# See also PYSIDE-1887, PYSIDE-3069
5354
gobjA = GObjA()
5455
gobjA.setParentItem(w)
5556
self.assertIs(type(w), type(gobjA.parentItem()))

0 commit comments

Comments
 (0)