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