1 // This contains the implementation of the PyQtSlot class.
2 //
3 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
4 //
5 // This file is part of PyQt5.
6 //
7 // This file may be used under the terms of the GNU General Public License
8 // version 3.0 as published by the Free Software Foundation and appearing in
9 // the file LICENSE included in the packaging of this file. Please review the
10 // following information to ensure the GNU General Public License version 3.0
11 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 //
13 // If you do not wish to use this file under the terms of the GPL version 3.0
14 // then you may purchase a commercial license. For more information contact
15 // info@riverbankcomputing.com.
16 //
17 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19
20
21 #include <Python.h>
22
23 #include "qpycore_chimera.h"
24 #include "qpycore_pyqtslot.h"
25
26
27 // Create the slot for an unbound method.
PyQtSlot(PyObject * method,PyObject * type,const Chimera::Signature * slot_signature)28 PyQtSlot::PyQtSlot(PyObject *method, PyObject *type,
29 const Chimera::Signature *slot_signature)
30 : mfunc(method), mself(0), mself_wr(0), other(0), signature(slot_signature)
31 {
32 #if PY_MAJOR_VERSION < 3
33 mclass = type;
34 #else
35 Q_UNUSED(type)
36 #endif
37 }
38
39
40 // Create the slot for a callable.
PyQtSlot(PyObject * callable,const Chimera::Signature * slot_signature)41 PyQtSlot::PyQtSlot(PyObject *callable, const Chimera::Signature *slot_signature)
42 : mfunc(0), mself(0), mself_wr(0), other(0), signature(slot_signature)
43 {
44 sipMethodDef callable_m;
45
46 if (sipGetMethod(callable, &callable_m))
47 {
48 // Save the component parts.
49 mfunc = callable_m.pm_function;
50 mself = callable_m.pm_self;
51 #if PY_MAJOR_VERSION < 3
52 mclass = callable_m.pm_class;
53 #endif
54
55 // Try and create a weak reference to the instance object.
56 mself_wr = PyWeakref_NewRef(mself, 0);
57 }
58 else
59 {
60 // Give the slot an extra reference to keep it alive.
61 Py_INCREF(callable);
62 other = callable;
63 }
64 }
65
66
67 // Destroy the slot.
~PyQtSlot()68 PyQtSlot::~PyQtSlot()
69 {
70 Py_XDECREF(mself_wr);
71 Py_XDECREF(other);
72 }
73
74
75 // Invoke the slot on behalf of C++.
invoke(void ** qargs,bool no_receiver_check) const76 PyQtSlot::Result PyQtSlot::invoke(void **qargs, bool no_receiver_check) const
77 {
78 return invoke(qargs, 0, 0, no_receiver_check);
79 }
80
81
82 // Invoke the slot on behalf of C++.
invoke(void ** qargs,PyObject * self,void * result) const83 bool PyQtSlot::invoke(void **qargs, PyObject *self, void *result) const
84 {
85 return (invoke(qargs, self, result, false) != PyQtSlot::Failed);
86 }
87
88
89 // Invoke the slot on behalf of C++.
invoke(void ** qargs,PyObject * self,void * result,bool no_receiver_check) const90 PyQtSlot::Result PyQtSlot::invoke(void **qargs, PyObject *self, void *result,
91 bool no_receiver_check) const
92 {
93 // Get the callable.
94 PyObject *callable;
95
96 if (other)
97 {
98 callable = other;
99 Py_INCREF(callable);
100 }
101 else
102 {
103 // Use the value we have if one wasn't supplied.
104 if (!self)
105 self = instance();
106
107 // If self is NULL then we didn't have a method in the first place.
108 // Instead we had a callable that has been cleared during garbage
109 // collection - so we can simply ignore the invocation.
110 if (!self)
111 return PyQtSlot::Ignored;
112
113 // See if the instance has gone (which isn't an error).
114 if (self == Py_None)
115 return PyQtSlot::Ignored;
116
117 // If the receiver wraps a C++ object then ignore the call if it no
118 // longer exists.
119 if (!no_receiver_check && PyObject_TypeCheck(self, sipSimpleWrapper_Type) && !sipGetAddress((sipSimpleWrapper *)self))
120 return PyQtSlot::Ignored;
121
122 sipMethodDef callable_m;
123
124 callable_m.pm_function = mfunc;
125 callable_m.pm_self = self;
126 #if PY_MAJOR_VERSION < 3
127 callable_m.pm_class = mclass;
128 #endif
129
130 callable = sipFromMethod(&callable_m);
131 }
132
133 // Convert the C++ arguments to Python objects.
134 const QList<const Chimera *> &args = signature->parsed_arguments;
135
136 PyObject *argtup = PyTuple_New(args.size());
137
138 if (!argtup)
139 return PyQtSlot::Failed;
140
141 QList<const Chimera *>::const_iterator it = args.constBegin();
142
143 for (int a = 0; it != args.constEnd(); ++a)
144 {
145 PyObject *arg = (*it)->toPyObject(*++qargs);
146
147 if (!arg)
148 {
149 Py_DECREF(argtup);
150 return PyQtSlot::Failed;
151 }
152
153 PyTuple_SetItem(argtup, a, arg);
154
155 ++it;
156 }
157
158 // Dispatch to the real slot.
159 PyObject *res = call(callable, argtup);
160
161 Py_DECREF(argtup);
162 Py_DECREF(callable);
163
164 if (!res)
165 return PyQtSlot::Failed;
166
167 // Handle any result if required.
168 bool ok;
169
170 if (result && signature->result)
171 ok = signature->result->fromPyObject(res, result);
172 else
173 ok = true;
174
175 Py_DECREF(res);
176
177 return (ok ? PyQtSlot::Succeeded : PyQtSlot::Failed);
178 }
179
180
181 // See if this slot corresponds to the given callable.
operator ==(PyObject * callable) const182 bool PyQtSlot::operator==(PyObject *callable) const
183 {
184 sipMethodDef callable_m;
185
186 if (sipGetMethod(callable, &callable_m))
187 {
188 if (other)
189 return false;
190
191 return (mfunc == callable_m.pm_function
192 && instance() == callable_m.pm_self
193 #if PY_MAJOR_VERSION < 3
194 && mclass == callable_m.pm_class
195 #endif
196 );
197 }
198
199 if (!other)
200 return false;
201
202 // See if it is a wrapped C++ method. Note that the PyQt4 behaviour is to
203 // not save a reference but to save the components (as we do with methods).
204 // Hopefully it won't make a difference. However it begs the question as
205 // to whether we should do the same with methods and rely on the garbage
206 // collector - is the current way of handling methods purely historical?
207 sipCFunctionDef other_cf, callable_cf;
208
209 if (sipGetCFunction(other, &other_cf) && sipGetCFunction(callable, &callable_cf))
210 return (other_cf.cf_self == callable_cf.cf_self &&
211 other_cf.cf_function->ml_meth == callable_cf.cf_function->ml_meth);
212
213 return (other == callable);
214 }
215
216
217 // Get the instance object.
instance() const218 PyObject *PyQtSlot::instance() const
219 {
220 // Use the weak reference if possible.
221 if (mself_wr)
222 return PyWeakref_GetObject(mself_wr);
223
224 return mself;
225 }
226
227
228 // Call a single slot and return the result.
call(PyObject * callable,PyObject * args) const229 PyObject *PyQtSlot::call(PyObject *callable, PyObject *args) const
230 {
231 PyObject *sa, *oxtype, *oxvalue, *oxtb;
232
233 // Keep some compilers quiet.
234 oxtype = oxvalue = oxtb = 0;
235
236 // We make repeated attempts to call a slot. If we work out that it failed
237 // because of an immediate type error we try again with one less argument.
238 // We keep going until we run out of arguments to drop. This emulates the
239 // Qt ability of the slot to accept fewer arguments than a signal provides.
240 sa = args;
241 Py_INCREF(sa);
242
243 for (;;)
244 {
245 PyObject *nsa, *xtype, *xvalue, *xtb, *res;
246
247 if ((res = PyObject_Call(callable, sa, NULL)) != NULL)
248 {
249 // Remove any previous exception.
250
251 if (sa != args)
252 {
253 Py_XDECREF(oxtype);
254 Py_XDECREF(oxvalue);
255 Py_XDECREF(oxtb);
256 PyErr_Clear();
257 }
258
259 Py_DECREF(sa);
260
261 return res;
262 }
263
264 // Get the exception.
265 PyErr_Fetch(&xtype, &xvalue, &xtb);
266
267 // See if it is unacceptable. An acceptable failure is a type error
268 // with no traceback - so long as we can still reduce the number of
269 // arguments and try again.
270 if (!PyErr_GivenExceptionMatches(xtype, PyExc_TypeError) || xtb ||
271 PyTuple_Size(sa) == 0)
272 {
273 // If there is a traceback then we must have called the slot and
274 // the exception was later on - so report the exception as is.
275 if (xtb)
276 {
277 if (sa != args)
278 {
279 Py_XDECREF(oxtype);
280 Py_XDECREF(oxvalue);
281 Py_XDECREF(oxtb);
282 }
283
284 PyErr_Restore(xtype,xvalue,xtb);
285 }
286 else if (sa == args)
287 {
288 PyErr_Restore(xtype, xvalue, xtb);
289 }
290 else
291 {
292 // Discard the latest exception and restore the original one.
293 Py_XDECREF(xtype);
294 Py_XDECREF(xvalue);
295 Py_XDECREF(xtb);
296
297 PyErr_Restore(oxtype, oxvalue, oxtb);
298 }
299
300 break;
301 }
302
303 // If this is the first attempt, save the exception.
304 if (sa == args)
305 {
306 oxtype = xtype;
307 oxvalue = xvalue;
308 oxtb = xtb;
309 }
310 else
311 {
312 Py_XDECREF(xtype);
313 Py_XDECREF(xvalue);
314 Py_XDECREF(xtb);
315 }
316
317 // Create the new argument tuple.
318 if ((nsa = PyTuple_GetSlice(sa, 0, PyTuple_Size(sa) - 1)) == NULL)
319 {
320 // Tidy up.
321 Py_XDECREF(oxtype);
322 Py_XDECREF(oxvalue);
323 Py_XDECREF(oxtb);
324
325 break;
326 }
327
328 Py_DECREF(sa);
329 sa = nsa;
330 }
331
332 Py_DECREF(sa);
333
334 return 0;
335 }
336
337
338 // Clear the slot if it has an extra reference.
clearOther()339 void PyQtSlot::clearOther()
340 {
341 Py_CLEAR(other);
342 }
343
344
345 // Visit the slot if it has an extra reference.
visitOther(visitproc visit,void * arg)346 int PyQtSlot::visitOther(visitproc visit, void *arg)
347 {
348 Py_VISIT(other);
349
350 return 0;
351 }
352