1 // This implements the public API provided by PyQt to external packages.
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 <QByteArray>
22 #include <QHash>
23 
24 #include "qpycore_chimera.h"
25 #include "qpycore_event_handlers.h"
26 #include "qpycore_objectified_strings.h"
27 #include "qpycore_public_api.h"
28 #include "qpycore_pyqtboundsignal.h"
29 #include "qpycore_pyqtsignal.h"
30 #include "qpycore_types.h"
31 
32 #include <QCoreApplication>
33 #include <QThread>
34 
35 
36 // The visitor called for each wrapper to clean up if it is a QObject.
cleanup_qobject(sipSimpleWrapper * sw,void * closure)37 static void cleanup_qobject(sipSimpleWrapper *sw, void *closure)
38 {
39     // Ignore any wrapper for QCoreApplication (or sub-class) instances or that
40     // have already been destroyed.
41     void *addr = sipGetAddress(sw);
42 
43     if (!addr || addr == closure)
44         return;
45 
46     // Ignore anything not owned by Python.
47     if (!sipIsOwnedByPython(sw))
48         return;
49 
50     // Ignore non-QObjects.
51     if (!PyObject_TypeCheck((PyObject *)sw, sipTypeAsPyTypeObject(sipType_QObject)))
52         return;
53 
54     // Ignore running threads.
55     if (PyObject_TypeCheck((PyObject *)sw, sipTypeAsPyTypeObject(sipType_QThread)))
56     {
57         QThread *thr = reinterpret_cast<QThread *>(addr);
58 
59         if (thr->isRunning())
60             return;
61     }
62 
63     sipTransferTo((PyObject *)sw, SIP_NULLPTR);
64 
65     Py_BEGIN_ALLOW_THREADS
66     delete reinterpret_cast<QObject *>(addr);
67     Py_END_ALLOW_THREADS
68 }
69 
70 
71 // Call the C++ dtors of all QObject instances (except for QCoreApplication
72 // instances) owned by Python.
pyqt5_cleanup_qobjects()73 void pyqt5_cleanup_qobjects()
74 {
75     // Disable any monitoring.
76     PyQtMonitor::enabled = false;
77 
78     sipVisitWrappers(cleanup_qobject, QCoreApplication::instance());
79 }
80 
81 
82 // A replacement for PyErr_Print() that passes the exception to qFatal().
pyqt5_err_print()83 void pyqt5_err_print()
84 {
85 #if PY_MAJOR_VERSION >= 3
86 #define CONST_CAST(s)   s
87 #else
88 #define CONST_CAST(s)   const_cast<char *>(s)
89 #endif
90 
91     static bool recursing = false;
92 
93     if (recursing)
94         return;
95 
96     recursing = true;
97 
98     // Save the exception in case of new exceptions raised here.
99     PyObject *exception, *value, *traceback;
100 
101     PyErr_Fetch(&exception, &value, &traceback);
102 
103     // Get the standard exception hook.
104     static PyObject *original_hook = 0;
105 
106     if (!original_hook)
107         original_hook = PySys_GetObject(CONST_CAST("__excepthook__"));
108 
109     // See if the application has installed its own hook.
110     PyObject *hook = PySys_GetObject(CONST_CAST("excepthook"));
111 
112     if (hook != original_hook)
113     {
114         // This will invoke the application's hook.
115         PyErr_Restore(exception, value, traceback);
116         PyErr_Print();
117     }
118     else
119     {
120         // Make sure we have the StringIO ctor.
121         static PyObject *stringio_ctor = 0;
122 
123         if (!stringio_ctor)
124         {
125             PyObject *io_module;
126 
127 #if PY_MAJOR_VERSION >= 3
128             io_module = PyImport_ImportModule("io");
129 #else
130             PyErr_Clear();
131             io_module = PyImport_ImportModule("cStringIO");
132 
133             if (!io_module)
134             {
135                 PyErr_Clear();
136                 io_module = PyImport_ImportModule("StringIO");
137             }
138 #endif
139 
140             if (io_module)
141             {
142                 stringio_ctor = PyObject_GetAttrString(io_module, "StringIO");
143                 Py_DECREF(io_module);
144             }
145         }
146 
147         // Create a StringIO object and replace sys.stderr with it.
148         PyObject *new_stderr = 0, *old_stderr;
149 
150         if (stringio_ctor)
151         {
152             old_stderr = PySys_GetObject(CONST_CAST("stderr"));
153 
154             if (old_stderr)
155             {
156                 new_stderr = PyObject_CallObject(stringio_ctor, NULL);
157 
158                 if (new_stderr)
159                 {
160                     // Make sure the old stderr doesn't get garbage collected
161                     // when it is replaced.
162                     Py_INCREF(old_stderr);
163 
164                     if (PySys_SetObject(CONST_CAST("stderr"), new_stderr) < 0)
165                     {
166                         Py_DECREF(old_stderr);
167                         Py_DECREF(new_stderr);
168                         new_stderr = 0;
169                     }
170                 }
171             }
172         }
173 
174         // Restore the exception and print it.
175         PyErr_Restore(exception, value, traceback);
176         PyErr_Print();
177 
178         // This will be passed to qFatal() if we can't get the detailed text.
179         QByteArray message("Unhandled Python exception");
180 
181         // Extract the detailed text if it was redirected.
182         if (new_stderr)
183         {
184             // Restore sys.stderr.
185             PySys_SetObject(CONST_CAST("stderr"), old_stderr);
186             Py_DECREF(old_stderr);
187 
188             // Extract the text.
189             PyObject *text = PyObject_CallMethod(new_stderr,
190                     CONST_CAST("getvalue"), NULL);
191 
192             if (text)
193             {
194                 // Strip the text as qFatal() likes to add a newline.
195                 PyObject *stripped = PyObject_CallMethod(text,
196                         CONST_CAST("strip"), NULL);
197 
198                 if (stripped)
199                 {
200                     Py_DECREF(text);
201                     text = stripped;
202                 }
203 
204                 // Encode the text using the encoding of the original
205                 // sys.stderr.
206 
207 #if PY_MAJOR_VERSION >= 3
208                 PyObject *encoding = PyObject_GetAttrString(old_stderr,
209                         "encoding");
210 
211                 if (encoding)
212                 {
213                     PyObject *encoding_bytes = PyUnicode_AsUTF8String(
214                             encoding);
215 
216                     if (encoding_bytes)
217                     {
218                         Q_ASSERT(PyBytes_Check(encoding_bytes));
219 
220                         PyObject *bytes = PyUnicode_AsEncodedString(text,
221                                 PyBytes_AsString(encoding_bytes), "strict");
222 
223                         if (bytes)
224                         {
225                             Q_ASSERT(PyBytes_Check(bytes));
226 
227                             message = QByteArray(PyBytes_AsString(bytes),
228                                     PyBytes_Size(bytes));
229 
230                             Py_DECREF(bytes);
231                         }
232 
233                         Py_DECREF(encoding_bytes);
234                     }
235 
236                     Py_DECREF(encoding);
237                 }
238 #else
239                 char *buffer;
240                 Py_ssize_t length;
241 
242                 if (PyString_AsStringAndSize(text, &buffer, &length) == 0)
243                     message = QByteArray(buffer, length);
244 #endif
245 
246                 Py_DECREF(text);
247             }
248 
249             Py_DECREF(new_stderr);
250         }
251 
252         // qFatal() may not call abort.
253         Py_BEGIN_ALLOW_THREADS
254         qFatal("%s", message.data());
255         Py_END_ALLOW_THREADS
256     }
257 
258     recursing = false;
259 }
260 
261 
262 // Convert a Python argv list to a conventional C argc count and argv array.
pyqt5_from_argv_list(PyObject * argv_list,int & argc)263 char **pyqt5_from_argv_list(PyObject *argv_list, int &argc)
264 {
265     argc = PyList_Size(argv_list);
266 
267     // Allocate space for two copies of the argument pointers, plus the
268     // terminating NULL.
269     char **argv = new char *[2 * (argc + 1)];
270 
271     // Convert the list.
272     for (int a = 0; a < argc; ++a)
273     {
274         PyObject *arg_obj = PyList_GetItem(argv_list, a);
275         char *arg;
276 
277         if (PyUnicode_Check(arg_obj))
278         {
279             QByteArray ba_arg = qpycore_PyObject_AsQString(arg_obj).toLocal8Bit();
280             arg = qstrdup(ba_arg.constData());
281         }
282         else if (SIPBytes_Check(arg_obj))
283         {
284             arg = qstrdup(SIPBytes_AsString(arg_obj));
285         }
286         else
287         {
288             arg = const_cast<char *>("invalid");
289         }
290 
291         argv[a] = argv[a + argc + 1] = arg;
292     }
293 
294     argv[argc + argc + 1] = argv[argc] = NULL;
295 
296     return argv;
297 }
298 
299 
300 // Get the receiver object and slot signature for a connection.
pyqt5_get_connection_parts(PyObject * slot,QObject * transmitter,const char * signal_signature,bool single_shot,QObject ** receiver,QByteArray & slot_signature)301 sipErrorState pyqt5_get_connection_parts(PyObject *slot, QObject *transmitter,
302         const char *signal_signature, bool single_shot, QObject **receiver,
303         QByteArray &slot_signature)
304 {
305     static QHash<QByteArray, const Chimera::Signature *> cache;
306 
307     QByteArray key(signal_signature);
308     const Chimera::Signature *parsed_signal_signature = cache.value(key);
309 
310     if (!parsed_signal_signature)
311     {
312         parsed_signal_signature = Chimera::parse(key, "a signal argument");
313 
314         if (!parsed_signal_signature)
315             return sipErrorFail;
316 
317         cache.insert(key, parsed_signal_signature);
318     }
319 
320     return qpycore_get_receiver_slot_signature(slot, transmitter,
321             parsed_signal_signature, single_shot, receiver, slot_signature);
322 }
323 
324 
325 
326 // Get the transmitter object and signal signature from a bound signal.
pyqt5_get_pyqtsignal_parts(PyObject * signal,QObject ** transmitter,QByteArray & signal_signature)327 sipErrorState pyqt5_get_pyqtsignal_parts(PyObject *signal,
328         QObject **transmitter, QByteArray &signal_signature)
329 {
330     if (PyObject_TypeCheck(signal, qpycore_pyqtBoundSignal_TypeObject))
331     {
332         qpycore_pyqtBoundSignal *bs = (qpycore_pyqtBoundSignal *)signal;
333 
334         *transmitter = bs->bound_qobject;
335         signal_signature = bs->unbound_signal->parsed_signature->signature;
336 
337         return sipErrorNone;
338     }
339 
340     return sipErrorContinue;
341 }
342 
343 
344 // Get the receiver object and slot signature from a decorated callable.
pyqt5_get_pyqtslot_parts(PyObject * slot,QObject ** receiver,QByteArray & slot_signature)345 sipErrorState pyqt5_get_pyqtslot_parts(PyObject *slot, QObject **receiver,
346         QByteArray &slot_signature)
347 {
348     PyObject *py_receiver, *decorations;
349     int is_err;
350     void *qobj;
351     Chimera::Signature *sig;
352     sipMethodDef slot_m;
353 
354     // Get the QObject.
355     if (!sipGetMethod(slot, &slot_m))
356         goto bad_callable;
357 
358     py_receiver = slot_m.pm_self;
359     if (!py_receiver)
360         goto bad_callable;
361 
362     is_err = 0;
363 
364     qobj = sipForceConvertToType(py_receiver, sipType_QObject, 0,
365             SIP_NO_CONVERTORS, 0, &is_err);
366 
367     if (is_err)
368         goto bad_callable;
369 
370     *receiver = reinterpret_cast<QObject *>(qobj);
371 
372     // Get the decoration.
373     decorations = PyObject_GetAttr(slot, qpycore_dunder_pyqtsignature);
374 
375     if (!decorations)
376         goto bad_callable;
377 
378     // Use the first one ignoring any others.
379     sig = Chimera::Signature::fromPyObject(PyList_GetItem(decorations, 0));
380     Py_DECREF(decorations);
381 
382     slot_signature = sig->signature;
383     slot_signature.prepend('1');
384 
385     return sipErrorNone;
386 
387 bad_callable:
388     PyErr_SetString(PyExc_TypeError,
389             "callable must be a method of a QtCore.QObject instance decorated "
390             "by QtCore.pyqtSlot");
391 
392     return sipErrorFail;
393 }
394 
395 
396 // Get the signature string for a bound or unbound signal.
pyqt5_get_signal_signature(PyObject * signal,const QObject * transmitter,QByteArray & signal_signature)397 sipErrorState pyqt5_get_signal_signature(PyObject *signal,
398         const QObject *transmitter, QByteArray &signal_signature)
399 {
400     qpycore_pyqtSignal *ps;
401 
402     if (PyObject_TypeCheck(signal, qpycore_pyqtBoundSignal_TypeObject))
403     {
404         qpycore_pyqtBoundSignal *bs = (qpycore_pyqtBoundSignal *)signal;
405 
406         if (transmitter && bs->bound_qobject != transmitter)
407         {
408             PyErr_SetString(PyExc_ValueError,
409                     "signal is bound to a different QObject");
410             return sipErrorFail;
411         }
412 
413         ps = bs->unbound_signal;
414     }
415     else if (PyObject_TypeCheck(signal, qpycore_pyqtSignal_TypeObject))
416     {
417         ps = (qpycore_pyqtSignal *)signal;
418     }
419     else
420     {
421         return sipErrorContinue;
422     }
423 
424     signal_signature = ps->parsed_signature->signature;
425 
426     return sipErrorNone;
427 }
428 
429 
430 // Register a convertor function that converts a QVariant to a Python object.
pyqt5_register_from_qvariant_convertor(bool (* convertor)(const QVariant &,PyObject **))431 void pyqt5_register_from_qvariant_convertor(
432         bool (*convertor)(const QVariant &, PyObject **))
433 {
434     Chimera::registeredFromQVariantConvertors.append(convertor);
435 }
436 
437 
438 // Register a convertor function that converts a Python object to a QVariant.
pyqt5_register_to_qvariant_convertor(bool (* convertor)(PyObject *,QVariant &,bool *))439 void pyqt5_register_to_qvariant_convertor(
440         bool (*convertor)(PyObject *, QVariant &, bool *))
441 {
442     Chimera::registeredToQVariantConvertors.append(convertor);
443 }
444 
445 
446 // Register a convertor function that converts a Python object to the
447 // pre-allocated data of a QVariant with a specific meta-type.
pyqt5_register_to_qvariant_data_convertor(bool (* convertor)(PyObject *,void *,int,bool *))448 void pyqt5_register_to_qvariant_data_convertor(
449         bool (*convertor)(PyObject *, void *, int, bool *))
450 {
451     Chimera::registeredToQVariantDataConvertors.append(convertor);
452 }
453 
454 
455 // Remove arguments from the Python argv list that have been removed from the
456 // C argv array.
pyqt5_update_argv_list(PyObject * argv_list,int argc,char ** argv)457 void pyqt5_update_argv_list(PyObject *argv_list, int argc, char **argv)
458 {
459     for (int a = 0, na = 0; a < argc; ++a)
460     {
461         // See if it was removed.
462         if (argv[na] == argv[a + argc + 1])
463             ++na;
464         else
465             PyList_SetSlice(argv_list, na, na + 1, 0);
466     }
467 
468     // Assume that we have created a QCoreApplication instance.
469     qpycore_created_qapp = true;
470 }
471 
472 
473 // Get the QMetaObject instance for a Python type.
pyqt5_get_qmetaobject(PyTypeObject * type)474 const QMetaObject *pyqt5_get_qmetaobject(PyTypeObject *type)
475 {
476     return qpycore_get_qmetaobject((sipWrapperType *)type);
477 }
478