1 // This contains the implementation of the PyQtSlotProxy 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 <QByteArray>
24 #include <QMetaObject>
25 #include <QMutex>
26 #include <QObject>
27 
28 #include "qpycore_api.h"
29 #include "qpycore_chimera.h"
30 #include "qpycore_qmetaobjectbuilder.h"
31 #include "qpycore_pyqtslot.h"
32 #include "qpycore_pyqtslotproxy.h"
33 
34 #include "sipAPIQtCore.h"
35 
36 
37 // Proxy flags.
38 #define PROXY_SINGLE_SHOT   0x01    // The slot will only be called once.
39 #define PROXY_SLOT_INVOKED  0x02    // The proxied slot is executing.
40 #define PROXY_SLOT_DISABLED 0x04    // The proxy deleteLater() has been called
41                                     // or should be called after the slot has
42                                     // finished executing.
43 #define PROXY_NO_RCVR_CHECK 0x08    // The existence of the receiver C++ object
44                                     // should not be checked.
45 
46 
47 // The last QObject sender.
48 QObject *PyQtSlotProxy::last_sender = 0;
49 
50 
51 // Create a universal proxy used as a slot.  Note that this will leak if there
52 // is no transmitter and this is not a single shot slot.  There will be no
53 // meta-object if there was a problem creating the proxy.  This is called
54 // without the GIL.
PyQtSlotProxy(PyObject * slot,QObject * q_tx,const Chimera::Signature * slot_signature,bool single_shot)55 PyQtSlotProxy::PyQtSlotProxy(PyObject *slot, QObject *q_tx,
56         const Chimera::Signature *slot_signature, bool single_shot)
57     : QObject(), proxy_flags(single_shot ? PROXY_SINGLE_SHOT : 0),
58         signature(slot_signature->signature), transmitter(q_tx)
59 {
60     SIP_BLOCK_THREADS
61     real_slot = new PyQtSlot(slot, slot_signature);
62     SIP_UNBLOCK_THREADS
63 
64     // Create a new meta-object on the heap so that it looks like it has a slot
65     // of the right name and signature.
66     QMetaObjectBuilder builder;
67 
68     builder.setClassName("PyQtSlotProxy");
69     builder.setSuperClass(&QObject::staticMetaObject);
70 
71     builder.addSlot("unislot()");
72     builder.addSlot("disable()");
73 
74     meta_object = builder.toMetaObject();
75 
76     // Detect when any transmitter is destroyed.  (Note that we used to do this
77     // by making the proxy a child of the transmitter.  This doesn't work as
78     // expected because QWidget destroys its children before emitting the
79     // destroyed signal.)  We use a queued connection in case the proxy is also
80     // connected to the same signal and we want to make sure it has a chance to
81     // invoke the slot before being destroyed.
82     if (transmitter)
83     {
84         // Add this one to the global hash.
85         mutex->lock();
86         proxy_slots.insert(transmitter, this);
87         mutex->unlock();
88 
89         connect(transmitter, SIGNAL(destroyed(QObject *)), SLOT(disable()),
90                 Qt::QueuedConnection);
91     }
92 }
93 
94 
95 // Destroy a universal proxy.  This is called without the GIL.
~PyQtSlotProxy()96 PyQtSlotProxy::~PyQtSlotProxy()
97 {
98     Q_ASSERT((proxy_flags & PROXY_SLOT_INVOKED) == 0);
99 
100     if (transmitter)
101     {
102         mutex->lock();
103 
104         ProxyHash::iterator it(proxy_slots.find(transmitter));
105         ProxyHash::iterator end(proxy_slots.end());
106 
107         while (it != end && it.key() == transmitter)
108         {
109             if (it.value() == this)
110                 it = proxy_slots.erase(it);
111             else
112                 ++it;
113         }
114 
115         mutex->unlock();
116     }
117 
118     // Qt can still be tidying up after Python has gone so make sure that it
119     // hasn't.
120     if (Py_IsInitialized())
121     {
122         SIP_BLOCK_THREADS
123         delete real_slot;
124         SIP_UNBLOCK_THREADS
125     }
126 
127     if (meta_object)
128     {
129         free(const_cast<QMetaObject *>(meta_object));
130     }
131 }
132 
133 
134 // The static members of PyQtSlotProxy.
135 const QByteArray PyQtSlotProxy::proxy_slot_signature(SLOT(unislot()));
136 QMutex *PyQtSlotProxy::mutex;
137 PyQtSlotProxy::ProxyHash PyQtSlotProxy::proxy_slots;
138 
139 
metaObject() const140 const QMetaObject *PyQtSlotProxy::metaObject() const
141 {
142     return meta_object;
143 }
144 
145 
qt_metacast(const char * _clname)146 void *PyQtSlotProxy::qt_metacast(const char *_clname)
147 {
148     if (!_clname)
149         return 0;
150 
151     if (qstrcmp(_clname, "PyQtSlotProxy") == 0)
152         return static_cast<void *>(const_cast<PyQtSlotProxy *>(this));
153 
154     return QObject::qt_metacast(_clname);
155 }
156 
157 
qt_metacall(QMetaObject::Call _c,int _id,void ** _a)158 int PyQtSlotProxy::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
159 {
160     _id = QObject::qt_metacall(_c, _id, _a);
161 
162     if (_id < 0)
163         return _id;
164 
165     if (_c == QMetaObject::InvokeMetaMethod)
166     {
167         switch (_id)
168         {
169         case 0:
170             unislot(_a);
171             break;
172 
173         case 1:
174             disable();
175             break;
176         }
177 
178         _id -= 2;
179     }
180 
181     return _id;
182 }
183 
184 
185 // This is the universal slot itself that dispatches to the real slot.
unislot(void ** qargs)186 void PyQtSlotProxy::unislot(void **qargs)
187 {
188     // If we are marked as disabled (possible if a queued signal has been
189     // disconnected but there is still a signal in the event queue) then just
190     // ignore the call.
191     if (proxy_flags & PROXY_SLOT_DISABLED)
192         return;
193 
194     // sender() must be called without the GIL to avoid possible deadlocks
195     // between the GIL and Qt's internal thread data mutex.
196     QObject *new_last_sender = sender();
197 
198     SIP_BLOCK_THREADS
199 
200     QObject *saved_last_sender = last_sender;
201     last_sender = new_last_sender;
202 
203     proxy_flags |= PROXY_SLOT_INVOKED;
204 
205     switch (real_slot->invoke(qargs, (proxy_flags & PROXY_NO_RCVR_CHECK)))
206     {
207     case PyQtSlot::Succeeded:
208         break;
209 
210     case PyQtSlot::Failed:
211         pyqt5_err_print();
212         break;
213 
214     case PyQtSlot::Ignored:
215         proxy_flags |= PROXY_SLOT_DISABLED;
216         break;
217     }
218 
219     proxy_flags &= ~PROXY_SLOT_INVOKED;
220 
221     // Self destruct if we are a single shot or disabled.
222     if (proxy_flags & (PROXY_SINGLE_SHOT|PROXY_SLOT_DISABLED))
223     {
224         // See the comment in disable() for why we deleteLater().
225         deleteLater();
226     }
227 
228     last_sender = saved_last_sender;
229 
230     SIP_UNBLOCK_THREADS
231 }
232 
233 
234 // Disable the slot by destroying it if possible, or delaying its destruction
235 // until the proxied slot returns.
disable()236 void PyQtSlotProxy::disable()
237 {
238     proxy_flags |= PROXY_SLOT_DISABLED;
239 
240     // Delete it if the slot isn't currently executing, otherwise it will be
241     // done after the slot returns.  Note that we don't rely on deleteLater()
242     // providing the necessary delay because the slot could process the event
243     // loop and the proxy would be deleted too soon.
244     if ((proxy_flags & PROXY_SLOT_INVOKED) == 0)
245     {
246         // Despite what the Qt documentation suggests, if there are outstanding
247         // queued signals then we may crash so we always delete later when the
248         // event queue will have been flushed.
249         deleteLater();
250     }
251 }
252 
253 
254 // Find a slot proxy connected to a transmitter.
findSlotProxy(const QObject * transmitter,const QByteArray & signal_signature,PyObject * slot)255 PyQtSlotProxy *PyQtSlotProxy::findSlotProxy(const QObject *transmitter,
256         const QByteArray &signal_signature, PyObject *slot)
257 {
258     PyQtSlotProxy *proxy = 0;
259 
260     mutex->lock();
261 
262     ProxyHash::const_iterator it(proxy_slots.find(transmitter));
263     ProxyHash::const_iterator end(proxy_slots.end());
264 
265     while (it != end && it.key() == transmitter)
266     {
267         PyQtSlotProxy *sp = it.value();
268 
269         if (!(sp->proxy_flags & PROXY_SLOT_DISABLED) && sp->signature == signal_signature && *(sp->real_slot) == slot)
270         {
271             proxy = sp;
272             break;
273         }
274 
275         ++it;
276     }
277 
278     mutex->unlock();
279 
280     return proxy;
281 }
282 
283 
284 // Delete any slot proxy for a particular connection.
deleteSlotProxy(const QMetaObject::Connection * connection)285 void PyQtSlotProxy::deleteSlotProxy(const QMetaObject::Connection *connection)
286 {
287     mutex->lock();
288 
289     ProxyHash::iterator it(proxy_slots.begin());
290     ProxyHash::iterator end(proxy_slots.end());
291 
292     while (it != end)
293     {
294         PyQtSlotProxy *sp = it.value();
295 
296         if (sp->connection == *connection)
297         {
298             proxy_slots.erase(it);
299             sp->disable();
300 
301             break;
302         }
303 
304         ++it;
305     }
306 
307     mutex->unlock();
308 }
309 
310 
311 // Delete any slot proxies for a particular transmitter and optional signal
312 // signature.
deleteSlotProxies(const QObject * transmitter,const QByteArray & signal_signature)313 void PyQtSlotProxy::deleteSlotProxies(const QObject *transmitter,
314         const QByteArray &signal_signature)
315 {
316     mutex->lock();
317 
318     ProxyHash::iterator it(proxy_slots.find(transmitter));
319     ProxyHash::iterator end(proxy_slots.end());
320 
321     while (it != end && it.key() == transmitter)
322     {
323         PyQtSlotProxy *sp = it.value();
324 
325         if (signal_signature.isEmpty() || signal_signature == sp->signature)
326         {
327             it = proxy_slots.erase(it);
328             sp->disable();
329         }
330         else
331         {
332             ++it;
333         }
334     }
335 
336     mutex->unlock();
337 }
338 
339 
340 // Clear the extra references of any slots connected to a transmitter.  This is
341 // called with the GIL.
clearSlotProxies(const QObject * transmitter)342 int PyQtSlotProxy::clearSlotProxies(const QObject *transmitter)
343 {
344     ProxyHash::iterator it(proxy_slots.find(transmitter));
345     ProxyHash::iterator end(proxy_slots.end());
346 
347     while (it != end && it.key() == transmitter)
348     {
349         it.value()->real_slot->clearOther();
350 
351         ++it;
352     }
353 
354     return 0;
355 }
356 
357 
358 // A thing wrapper available to the generated code.
qpycore_clearSlotProxies(const QObject * transmitter)359 int qpycore_clearSlotProxies(const QObject *transmitter)
360 {
361     return PyQtSlotProxy::clearSlotProxies(transmitter);
362 }
363 
364 
365 // Visit the extra references of any slots connected to a transmitter.  This is
366 // called with the GIL.
visitSlotProxies(const QObject * transmitter,visitproc visit,void * arg)367 int PyQtSlotProxy::visitSlotProxies(const QObject *transmitter,
368         visitproc visit, void *arg)
369 {
370     int vret = 0;
371 
372     ProxyHash::iterator it(proxy_slots.find(transmitter));
373     ProxyHash::iterator end(proxy_slots.end());
374 
375     while (it != end && it.key() == transmitter)
376     {
377         if ((vret = it.value()->real_slot->visitOther(visit, arg)) != 0)
378             break;
379 
380         ++it;
381     }
382 
383     return vret;
384 }
385 
386 
387 // A thing wrapper available to the generated code.
qpycore_visitSlotProxies(const QObject * transmitter,visitproc visit,void * arg)388 int qpycore_visitSlotProxies(const QObject *transmitter, visitproc visit,
389         void *arg)
390 {
391     return PyQtSlotProxy::visitSlotProxies(transmitter, visit, arg);
392 }
393 
394 
395 // Get the last sender.
lastSender()396 QObject *PyQtSlotProxy::lastSender()
397 {
398     return last_sender;
399 }
400 
401 
402 // Disable the check that the receiver C++ object exists before invoking a
403 // slot.
disableReceiverCheck()404 void PyQtSlotProxy::disableReceiverCheck()
405 {
406     proxy_flags |= PROXY_NO_RCVR_CHECK;
407 }
408