1 /*
2  * The transitional Python bindings for the C parts of the sip code generator.
3  *
4  * Copyright (c) 2019 Riverbank Computing Limited <info@riverbankcomputing.com>
5  *
6  * This file is part of SIP.
7  *
8  * This copy of SIP is licensed for use under the terms of the SIP License
9  * Agreement.  See the file LICENSE for more details.
10  *
11  * This copy of SIP may also used under the terms of the GNU General Public
12  * License v2 or v3 as published by the Free Software Foundation which can be
13  * found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
14  *
15  * SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17  */
18 
19 
20 #include <assert.h>
21 #include <setjmp.h>
22 #include <stdarg.h>
23 #include <stdio.h>
24 #include <string.h>
25 
26 #include <Python.h>
27 
28 #include "sip.h"
29 
30 
31 /* Globals - see sip.h for their meanings. */
32 unsigned sipVersion;
33 const char *sipVersionStr;
34 unsigned abiMajor;
35 unsigned abiMinor;
36 stringList *includeDirList;
37 
38 /* Support for fatal error handling. */
39 #define NO_EXCEPTION        0           /* No exception has been set. */
40 #define EXCEPTION_SET       1           /* An exception has been set. */
41 #define EXCEPTION_NEEDED    2           /* An exception needs to be set. */
42 
43 static char error_text[1000];
44 static jmp_buf on_fatal_error;
45 static PyObject *exception_type;
46 static void raise_exception(int action);
47 
48 /* Forward declarations. */
49 static PyObject *py_set_globals(PyObject *self, PyObject *args);
50 static PyObject *py_parse(PyObject *self, PyObject *args);
51 static PyObject *py_generateCode(PyObject *self, PyObject *args);
52 static PyObject *py_generateExtracts(PyObject *self, PyObject *args);
53 static PyObject *py_generateAPI(PyObject *self, PyObject *args);
54 static PyObject *py_generateXML(PyObject *self, PyObject *args);
55 static PyObject *py_generateTypeHints(PyObject *self, PyObject *args);
56 
57 static int fs_convertor(PyObject *obj, char **fsp);
58 static int sipSpec_convertor(PyObject *obj, sipSpec **ptp);
59 static int stringList_convertor(PyObject *obj, stringList **slp);
60 static PyObject *stringList_convert_from(stringList *sl);
61 static int extend_stringList(stringList **slp, PyObject *py_list);
62 static void exception_set(void);
63 
64 
65 /*
66  * The sipbuild.code_generator module initialisation function.
67  */
PyInit_code_generator(void)68 PyMODINIT_FUNC PyInit_code_generator(void)
69 {
70     static PyMethodDef methods[] = {
71         {"set_globals", py_set_globals, METH_VARARGS, NULL},
72         {"parse", py_parse, METH_VARARGS, NULL},
73         {"generateCode", py_generateCode, METH_VARARGS, NULL},
74         {"generateExtracts", py_generateExtracts, METH_VARARGS, NULL},
75         {"generateAPI", py_generateAPI, METH_VARARGS, NULL},
76         {"generateXML", py_generateXML, METH_VARARGS, NULL},
77         {"generateTypeHints", py_generateTypeHints, METH_VARARGS, NULL},
78         {NULL, NULL, 0, NULL},
79     };
80 
81     static PyModuleDef module_def = {
82         PyModuleDef_HEAD_INIT,
83         "sipbuild.code_generator",  /* m_name */
84         NULL,                       /* m_doc */
85         -1,                         /* m_size */
86         methods,                    /* m_methods */
87         NULL,                       /* m_reload */
88         NULL,                       /* m_traverse */
89         NULL,                       /* m_clear */
90         NULL,                       /* m_free */
91     };
92 
93     return PyModule_Create(&module_def);
94 }
95 
96 
97 /*
98  * Set the globals.
99  */
py_set_globals(PyObject * self,PyObject * args)100 static PyObject *py_set_globals(PyObject *self, PyObject *args)
101 {
102     if (!PyArg_ParseTuple(args, "IsIIOO&",
103             &sipVersion,
104             &sipVersionStr,
105             &abiMajor,
106             &abiMinor,
107             &exception_type,
108             stringList_convertor, &includeDirList))
109         return NULL;
110 
111     Py_INCREF(exception_type);
112 
113     Py_RETURN_NONE;
114 }
115 
116 
117 /*
118  * Wrapper around parse().
119  */
py_parse(PyObject * self,PyObject * args)120 static PyObject *py_parse(PyObject *self, PyObject *args)
121 {
122     sipSpec *pt;
123     FILE *file;
124     char *filename;
125     stringList *versions, *backstops, *xfeatures, *sip_files;
126     int strict, protHack, action;
127 
128     if (!PyArg_ParseTuple(args, "O&pO&O&O&p",
129             fs_convertor, &filename,
130             &strict,
131             stringList_convertor, &versions,
132             stringList_convertor, &backstops,
133             stringList_convertor, &xfeatures,
134             &protHack))
135         return NULL;
136 
137     pt = sipMalloc(sizeof (sipSpec));
138 
139     if (filename != NULL)
140     {
141         file = NULL;
142     }
143     else
144     {
145         file = stdin;
146         filename = "stdin";
147     }
148 
149     if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
150     {
151         raise_exception(action);
152         return NULL;
153     }
154 
155     sip_files = NULL;
156 
157     parse(pt, file, filename, strict, &versions, backstops, &xfeatures,
158             protHack, &sip_files);
159 
160     transform(pt, strict);
161 
162     return Py_BuildValue("(NsiNNN)", PyCapsule_New(pt, NULL, NULL),
163             pt->module->fullname->text,
164             (isComposite(pt->module) || useLimitedAPI(pt->module)),
165             stringList_convert_from(sip_files),
166             stringList_convert_from(versions),
167             stringList_convert_from(xfeatures));
168 }
169 
170 
171 /*
172  * Wrapper around generateCode().
173  */
py_generateCode(PyObject * self,PyObject * args)174 static PyObject *py_generateCode(PyObject *self, PyObject *args)
175 {
176     sipSpec *pt;
177     char *codeDir, *srcSuffix, *sipName;
178     const char *api_header;
179     int exceptions, tracing, releaseGIL, parts, docs, py_debug, action;
180     stringList *versions, *xfeatures, *sources;
181 
182     if (!PyArg_ParseTuple(args, "O&O&O&pppiO&O&ppz",
183             sipSpec_convertor, &pt,
184             fs_convertor, &codeDir,
185             fs_convertor, &srcSuffix,
186             &exceptions,
187             &tracing,
188             &releaseGIL,
189             &parts,
190             stringList_convertor, &versions,
191             stringList_convertor, &xfeatures,
192             &docs,
193             &py_debug,
194             &sipName))
195         return NULL;
196 
197     if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
198     {
199         raise_exception(action);
200         return NULL;
201     }
202 
203     if (sipName != NULL && sipName[0] == '\0')
204         sipName = NULL;
205 
206     sources = generateCode(pt, codeDir, srcSuffix, exceptions, tracing,
207             releaseGIL, parts, versions, xfeatures, docs, py_debug, sipName,
208             &api_header);
209 
210     return Py_BuildValue("(sN)", api_header, stringList_convert_from(sources));
211 }
212 
213 
214 /*
215  * Wrapper around generateExtracts().
216  */
py_generateExtracts(PyObject * self,PyObject * args)217 static PyObject *py_generateExtracts(PyObject *self, PyObject *args)
218 {
219     sipSpec *pt;
220     stringList *extracts;
221     int action;
222 
223     if (!PyArg_ParseTuple(args, "O&O&",
224             sipSpec_convertor, &pt,
225             stringList_convertor, &extracts))
226         return NULL;
227 
228     if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
229     {
230         raise_exception(action);
231         return NULL;
232     }
233 
234     generateExtracts(pt, extracts);
235 
236     Py_RETURN_NONE;
237 }
238 
239 
240 /*
241  * Wrapper around generateAPI().
242  */
py_generateAPI(PyObject * self,PyObject * args)243 static PyObject *py_generateAPI(PyObject *self, PyObject *args)
244 {
245     sipSpec *pt;
246     char *apiFile;
247     int action;
248 
249     if (!PyArg_ParseTuple(args, "O&O&",
250             sipSpec_convertor, &pt,
251             fs_convertor, &apiFile))
252         return NULL;
253 
254     if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
255     {
256         raise_exception(action);
257         return NULL;
258     }
259 
260     generateAPI(pt, pt->module, apiFile);
261 
262     Py_RETURN_NONE;
263 }
264 
265 
266 /*
267  * Wrapper around generateXML().
268  */
py_generateXML(PyObject * self,PyObject * args)269 static PyObject *py_generateXML(PyObject *self, PyObject *args)
270 {
271     sipSpec *pt;
272     char *xmlFile;
273     int action;
274 
275     if (!PyArg_ParseTuple(args, "O&O&",
276             sipSpec_convertor, &pt,
277             fs_convertor, &xmlFile))
278         return NULL;
279 
280     if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
281     {
282         raise_exception(action);
283         return NULL;
284     }
285 
286     generateXML(pt, pt->module, xmlFile);
287 
288     Py_RETURN_NONE;
289 }
290 
291 
292 /*
293  * Wrapper around generateTypeHints().
294  */
py_generateTypeHints(PyObject * self,PyObject * args)295 static PyObject *py_generateTypeHints(PyObject *self, PyObject *args)
296 {
297     sipSpec *pt;
298     char *pyiFile;
299     int action;
300 
301     if (!PyArg_ParseTuple(args, "O&O&",
302             sipSpec_convertor, &pt,
303             fs_convertor, &pyiFile))
304         return NULL;
305 
306     if ((action = setjmp(on_fatal_error)) != NO_EXCEPTION)
307     {
308         raise_exception(action);
309         return NULL;
310     }
311 
312     generateTypeHints(pt, pt->module, pyiFile);
313 
314     Py_RETURN_NONE;
315 }
316 
317 
318 /*
319  * Convert a callable argument to a filesystem name.
320  */
fs_convertor(PyObject * obj,char ** fsp)321 static int fs_convertor(PyObject *obj, char **fsp)
322 {
323     PyObject *bytes;
324 
325     if (obj == Py_None)
326     {
327         *fsp = NULL;
328         return 1;
329     }
330 
331     if ((bytes = PyUnicode_EncodeFSDefault(obj)) == NULL)
332         return 0;
333 
334     /* Leak the bytes object rather than strdup() its contents. */
335     *fsp = PyBytes_AS_STRING(bytes);
336 
337     return 1;
338 }
339 
340 
341 /*
342  * Convert a callable argument to a sipSpec.
343  */
sipSpec_convertor(PyObject * obj,sipSpec ** ptp)344 static int sipSpec_convertor(PyObject *obj, sipSpec **ptp)
345 {
346     if (!PyCapsule_CheckExact(obj))
347     {
348         PyErr_SetString(PyExc_TypeError, "parse tree expected");
349         return 0;
350     }
351 
352     if ((*ptp = (sipSpec *)PyCapsule_GetPointer(obj, NULL)) == NULL)
353         return 0;
354 
355     return 1;
356 }
357 
358 
359 /*
360  * Convert a callable argument to a stringList.
361  */
stringList_convertor(PyObject * obj,stringList ** slp)362 static int stringList_convertor(PyObject *obj, stringList **slp)
363 {
364     *slp = NULL;
365 
366     if (obj == Py_None)
367         return 1;
368 
369     if (!PyList_Check(obj))
370     {
371         PyErr_SetString(PyExc_TypeError, "list of str expected");
372         return 0;
373     }
374 
375     return extend_stringList(slp, obj);
376 }
377 
378 
379 /*
380  * Convert a stringList to a Python list of strings.
381  */
stringList_convert_from(stringList * sl)382 static PyObject *stringList_convert_from(stringList *sl)
383 {
384     PyObject *pyl;
385 
386     if ((pyl = PyList_New(0)) == NULL)
387         return NULL;
388 
389     while (sl != NULL)
390     {
391         PyObject *s;
392 
393         if ((s = PyUnicode_DecodeLocale(sl->s, NULL)) == NULL)
394         {
395             Py_DECREF(pyl);
396             return NULL;
397         }
398 
399         if (PyList_Append(pyl, s) < 0)
400         {
401             Py_DECREF(s);
402             Py_DECREF(pyl);
403             return NULL;
404         }
405 
406         Py_DECREF(s);
407 
408         sl = sl->next;
409     }
410 
411     return pyl;
412 }
413 
414 
415 /*
416  * Extend a stringList by the contents fo a Python list of strings.
417  */
extend_stringList(stringList ** slp,PyObject * py_list)418 static int extend_stringList(stringList **slp, PyObject *py_list)
419 {
420     Py_ssize_t i;
421 
422     for (i = 0; i < PyList_GET_SIZE(py_list); ++i)
423     {
424         PyObject *el = PyUnicode_EncodeLocale(PyList_GET_ITEM(py_list, i),
425                 NULL);
426 
427         if (el == NULL)
428             return 0;
429 
430         appendString(slp, sipStrdup(PyBytes_AS_STRING(el)));
431     }
432 
433     return 1;
434 }
435 
436 
437 /*
438  * Display a one line error message describing a fatal error.  This does not
439  * return.
440  */
fatal(const char * fmt,...)441 void fatal(const char *fmt, ...)
442 {
443     va_list ap;
444     size_t used = strlen(error_text);
445     size_t room = sizeof (error_text) - used - 1;
446 
447     va_start(ap,fmt);
448     vsnprintf(&error_text[used], room, fmt, ap);
449     va_end(ap);
450 
451     /* Raise an exception. */
452     longjmp(on_fatal_error, EXCEPTION_NEEDED);
453 }
454 
455 
456 /*
457  * Append to the current error message.
458  */
fatalAppend(const char * fmt,...)459 void fatalAppend(const char *fmt, ...)
460 {
461     va_list ap;
462     size_t used = strlen(error_text);
463     size_t room = sizeof (error_text) - used - 1;
464 
465     va_start(ap, fmt);
466     vsnprintf(&error_text[used], room, fmt, ap);
467     va_end(ap);
468 }
469 
470 
471 /*
472  * Raise an exception if needed.
473  */
raise_exception(int action)474 static void raise_exception(int action)
475 {
476     if (action == EXCEPTION_NEEDED)
477         PyErr_SetString(exception_type, error_text);
478 
479     /*
480      * The error text buffer may be used more than once as the Python exception
481      * could be ignored.
482      */
483     error_text[0] = '\0';
484 }
485 
486 
487 /*
488  * Return to the Python interpreter after an exception has been set.
489  */
exception_set(void)490 static void exception_set(void)
491 {
492     longjmp(on_fatal_error, EXCEPTION_SET);
493 }
494 
495 
496 /*
497  * Display a warning message.
498  */
warning(Warning w,const char * fmt,...)499 void warning(Warning w, const char *fmt, ...)
500 {
501     static char warning_text[1000];
502 
503     va_list ap;
504     size_t used = strlen(warning_text);
505     size_t room = sizeof (warning_text) - used - 1;
506 
507     va_start(ap, fmt);
508     vsnprintf(&warning_text[used], room, fmt, ap);
509     va_end(ap);
510 
511     if (strchr(fmt, '\n') != NULL)
512     {
513         int ret;
514 
515         ret = PyErr_WarnEx(
516                 (w == DeprecationWarning ? PyExc_FutureWarning :
517                         PyExc_UserWarning),
518                 warning_text, 1);
519 
520         warning_text[0] = '\0';
521 
522         if (ret < 0)
523             exception_set();
524     }
525 }
526 
527 
528 /*
529  * Get the configuration of a set of bindings and update the list of tags and
530  * disabled features.
531  */
get_bindings_configuration(const char * sip_file,stringList ** tags,stringList ** disabled)532 void get_bindings_configuration(const char *sip_file, stringList **tags,
533         stringList **disabled)
534 {
535     static PyObject *get_bindings_configuration = NULL;
536 
537     PyObject *res, *py_tags, *py_disabled;
538 
539     /* Get the Python helper. */
540     if (get_bindings_configuration == NULL)
541     {
542         PyObject *mod;
543 
544         if ((mod = PyImport_ImportModule("sipbuild.helpers")) == NULL)
545             exception_set();
546 
547         get_bindings_configuration = PyObject_GetAttrString(mod,
548                 "get_bindings_configuration");
549         Py_DECREF(mod);
550 
551         if (get_bindings_configuration == NULL)
552             exception_set();
553     }
554 
555     /* Call the helper. */
556     res = PyObject_CallFunction(get_bindings_configuration, "IIsN", abiMajor,
557             abiMinor, sip_file, stringList_convert_from(includeDirList));
558 
559     if (res == NULL)
560         exception_set();
561 
562     /* The result should be a 2-tuple of lists of strings. */
563     assert(PyTuple_Check(res));
564     assert(PyTuple_GET_SIZE(res) == 2);
565 
566     py_tags = PyTuple_GET_ITEM(res, 0);
567     assert(PyList_Check(py_tags));
568 
569     if (!extend_stringList(tags, py_tags))
570     {
571         Py_DECREF(res);
572         exception_set();
573     }
574 
575     py_disabled = PyTuple_GET_ITEM(res, 1);
576     assert(PyList_Check(py_disabled));
577 
578     if (!extend_stringList(disabled, py_disabled))
579     {
580         Py_DECREF(res);
581         exception_set();
582     }
583 
584     /* Tidy up. */
585     Py_DECREF(res);
586 }
587