Skip to content

Commit 62e72aa

Browse files
Fix crash retrieving a PyObject type property via QVariant<PyObjectWrapper>
The old code registered a Shiboken converter for PyObjectWrapper by pointer conversion. This resulted in the Python to C++ converter falling back to plain pointer passthrough since it only works for SbkObjects. The C++ to Python conversion worked by coincidence for either raw PyObject * pointers used in meta call handling or pointers obtained from calling QVariant<PyObjectWrapper>.data(), but without handling reference counts. To fix this, remove the Python to C++ conversion entirely and do this manually via QVariant. Change the C++ to Python to be by value and use PyObjectWrapper. Fixes: PYSIDE-2193 Pick-to: 6.9 Change-Id: I00898894651f220d7b8fe60608e93233ef3e6493 Reviewed-by: Shyamnath Premnadh <[email protected]>
1 parent 37450c8 commit 62e72aa

File tree

5 files changed

+78
-25
lines changed

5 files changed

+78
-25
lines changed

sources/pyside6/libpyside/pysidemetafunction.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "pysidemetafunction.h"
55
#include "pysidemetafunction_p.h"
66

7+
#include <signalmanager.h>
8+
79
#include <autodecref.h>
810
#include <basewrapper.h>
911
#include <sbkconverter.h>
@@ -12,6 +14,8 @@
1214

1315
#include <QtCore/qmetaobject.h>
1416

17+
using namespace Qt::StringLiterals;
18+
1519
extern "C"
1620
{
1721

@@ -164,6 +168,10 @@ bool call(QObject *self, int methodIndex, PyObject *args, PyObject **retVal)
164168
QString tmp;
165169
converter.toCpp(obj, &tmp);
166170
methValues[i] = tmp;
171+
} else if (metaType.id() == PyObjectWrapper::metaTypeId()) {
172+
// Manual conversion, see PyObjectWrapper converter registration
173+
methValues[i] = QVariant::fromValue(PyObjectWrapper(obj.object()));
174+
methArgs[i] = methValues[i].data();
167175
} else {
168176
converter.toCpp(obj, methArgs[i]);
169177
}

sources/pyside6/libpyside/pysideproperty.cpp

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "pysideproperty_p.h"
77
#include "pysidesignal.h"
88
#include "pysidesignal_p.h"
9+
#include "signalmanager.h"
910

1011
#include <autodecref.h>
1112
#include <pep384ext.h>
@@ -17,6 +18,8 @@
1718

1819
using namespace Shiboken;
1920

21+
using namespace Qt::StringLiterals;
22+
2023
extern "C"
2124
{
2225

@@ -148,16 +151,20 @@ void PySidePropertyPrivate::metaCall(PyObject *source, QMetaObject::Call call, v
148151
switch (call) {
149152
case QMetaObject::ReadProperty: {
150153
AutoDecRef value(getValue(source));
151-
auto *obValue = value.object();
152-
if (obValue) {
153-
Conversions::SpecificConverter converter(typeName);
154-
if (converter) {
155-
converter.toCpp(obValue, args[0]);
156-
} else {
157-
// PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
158-
PyErr_SetObject(PyExc_StopIteration, obValue);
159-
}
154+
if (value.isNull())
155+
return;
156+
if (typeName == "PyObject"_ba) {
157+
// Manual conversion, see PyObjectWrapper converter registration
158+
auto *pw = reinterpret_cast<PySide::PyObjectWrapper *>(args[0]);
159+
pw->reset(value.object());
160+
return;
161+
}
162+
if (Conversions::SpecificConverter converter(typeName); converter) {
163+
converter.toCpp(value.object(), args[0]);
164+
return;
160165
}
166+
// PYSIDE-2160: Report an unknown type name to the caller `qtPropertyMetacall`.
167+
PyErr_SetObject(PyExc_StopIteration, value.object());
161168
}
162169
break;
163170

sources/pyside6/libpyside/signalmanager.cpp

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <QtCore/qcoreevent.h>
2525
#include <QtCore/qdebug.h>
2626
#include <QtCore/qhash.h>
27+
#include <QtCore/qmetatype.h>
2728
#include <QtCore/qscopedpointer.h>
2829

2930
#include <climits>
@@ -38,6 +39,8 @@ using namespace Qt::StringLiterals;
3839

3940
static PyObject *metaObjectAttr = nullptr;
4041

42+
static int pyObjectWrapperMetaTypeId = QMetaType::UnknownType;
43+
4144
static void destroyMetaObject(PyObject *obj)
4245
{
4346
void *ptr = PyCapsule_GetPointer(obj, nullptr);
@@ -169,6 +172,10 @@ PyObjectWrapper::operator PyObject *() const
169172
return m_me;
170173
}
171174

175+
int PyObjectWrapper::metaTypeId()
176+
{
177+
return pyObjectWrapperMetaTypeId;
178+
}
172179

173180
int PyObjectWrapper::toInt() const
174181
{
@@ -273,19 +280,11 @@ struct SignalManagerPrivate
273280
SignalManager::QmlMetaCallErrorHandler
274281
SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr;
275282

276-
static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut)
277-
{
278-
*reinterpret_cast<PyObject **>(cppOut) = pyIn;
279-
}
280-
static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */)
281-
{
282-
return PyObject_PythonToCpp_PyObject_PTR;
283-
}
284-
static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn)
283+
static PyObject *CopyCppToPythonPyObject(const void *cppIn)
285284
{
286-
auto *pyOut = reinterpret_cast<PyObject *>(const_cast<void *>(cppIn));
287-
if (pyOut)
288-
Py_INCREF(pyOut);
285+
const auto *wrapper = reinterpret_cast<const PyObjectWrapper *>(cppIn);
286+
PyObject *pyOut = *wrapper;
287+
Py_XINCREF(pyOut);
289288
return pyOut;
290289
}
291290

@@ -295,13 +294,16 @@ void SignalManager::init()
295294
using namespace Shiboken;
296295

297296
// Register PyObject type to use in queued signal and slot connections
298-
qRegisterMetaType<PyObjectWrapper>("PyObject");
297+
pyObjectWrapperMetaTypeId = qRegisterMetaType<PyObjectWrapper>("PyObject");
299298
// Register QVariant(enum) conversion to QVariant(int)
300299
QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt);
301300

302-
SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr);
303-
Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject);
304-
Shiboken::Conversions::setPythonToCppPointerFunctions(converter, PyObject_PythonToCpp_PyObject_PTR, is_PyObject_PythonToCpp_PyObject_PTR_Convertible);
301+
// Register a shiboken converter for PyObjectWrapper->Python (value conversion).
302+
// Python->PyObjectWrapper is not registered since the converters do not work for
303+
// non-SbkObject types (falling back to plain pointer pass through).
304+
// This conversion needs to be done manually via QVariant.
305+
SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type,
306+
CopyCppToPythonPyObject);
305307
Shiboken::Conversions::registerConverterName(converter, "PyObject");
306308
Shiboken::Conversions::registerConverterName(converter, "object");
307309
Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper");

sources/pyside6/libpyside/signalmanager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class PYSIDE_API PyObjectWrapper
4444
// The proper fix would be to associate PyObjectWrapper to the corresponding C++ Enum.
4545
int toInt() const;
4646

47+
static int metaTypeId();
48+
4749
private:
4850
PyObject* m_me;
4951
};

sources/pyside6/tests/QtCore/qobject_property_test.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,26 @@ def writeP(self, v):
3232
myProperty = Property(int, readP, fset=writeP, notify=notifyP)
3333

3434

35+
class OtherClass:
36+
"""Helper for QObjectWithOtherClassPropertyTest."""
37+
pass
38+
39+
40+
class MyObjectWithOtherClassProperty(QObject):
41+
"""Helper for QObjectWithOtherClassPropertyTest."""
42+
def __init__(self, parent=None):
43+
super().__init__(parent)
44+
self._otherclass = None
45+
46+
def _get_otherclass(self):
47+
return self._otherclass
48+
49+
def _set_otherclass(self, o):
50+
self._otherclass = o
51+
52+
otherclass = Property(OtherClass, fget=_get_otherclass, fset=_set_otherclass)
53+
54+
3555
class PropertyWithNotify(unittest.TestCase):
3656
def called(self):
3757
self.called_ = True
@@ -50,5 +70,19 @@ def testHasProperty(self):
5070
self.assertEqual(o.property("myProperty"), 10)
5171

5272

73+
class QObjectWithOtherClassPropertyTest(unittest.TestCase):
74+
"""PYSIDE-2193: For properties of custom classes not wrapped by shiboken,
75+
QVariant<PyObjectWrapper> is used, which had refcount issues causing crashes.
76+
Exercise the QVariant conversion by setting and retrieving via the
77+
QVariant-based property()/setProperty() API."""
78+
def testNotify(self):
79+
obj = MyObjectWithOtherClassProperty()
80+
obj.setProperty("otherclass", OtherClass())
81+
for i in range(10):
82+
pv = obj.property("otherclass")
83+
print(pv) # Exercise repr
84+
self.assertTrue(type(pv) is OtherClass)
85+
86+
5387
if __name__ == '__main__':
5488
unittest.main()

0 commit comments

Comments
 (0)