1 // This contains the implementation of the Q_ENUM, Q_ENUMS, Q_FLAG and Q_FLAGS
2 // support.
3 //
4 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
5 //
6 // This file is part of PyQt5.
7 //
8 // This file may be used under the terms of the GNU General Public License
9 // version 3.0 as published by the Free Software Foundation and appearing in
10 // the file LICENSE included in the packaging of this file.  Please review the
11 // following information to ensure the GNU General Public License version 3.0
12 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
13 //
14 // If you do not wish to use this file under the terms of the GPL version 3.0
15 // then you may purchase a commercial license.  For more information contact
16 // info@riverbankcomputing.com.
17 //
18 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
19 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 
21 
22 #include <Python.h>
23 
24 #include <QMultiHash>
25 
26 #include "qpycore_chimera.h"
27 #include "qpycore_enums_flags.h"
28 
29 #include "sipAPIQtCore.h"
30 
31 
32 // Forward declarations.
33 static bool add_enum_flag(PyObject *arg, bool flag, const char *context,
34         struct _frame *frame);
35 static void add_key_value(EnumFlag &enum_flag, PyObject *key, PyObject *value);
36 static struct _frame *get_calling_frame();
37 static bool objectify(const char *s, PyObject **objp);
38 static PyObject *parse_enum_flag(PyObject *arg, bool flag,
39         const char *context);
40 static PyObject *parse_enums_flags(PyObject *args, bool flags,
41         const char *context);
42 static bool trawl_members(PyObject *members, EnumFlag &enum_flag);
43 static void trawl_type_dict(PyObject *arg, EnumFlag &enum_flag);
44 
45 
46 // The enums and flags defined in each frame.
47 static QMultiHash<const struct _frame *, EnumFlag> enums_flags_hash;
48 
49 
50 // Add the given Q_ENUM() argument to the current enums/flags hash.
qpycore_Enum(PyObject * arg)51 PyObject *qpycore_Enum(PyObject *arg)
52 {
53     return parse_enum_flag(arg, false, "Q_ENUM");
54 }
55 
56 
57 // Add the given Q_ENUMS() arguments to the current enums/flags hash.
qpycore_Enums(PyObject * args)58 PyObject *qpycore_Enums(PyObject *args)
59 {
60     return parse_enums_flags(args, false, "Q_ENUMS");
61 }
62 
63 
64 // Add the given Q_FLAG() arguments to the current enums/flags hash.
qpycore_Flag(PyObject * arg)65 PyObject *qpycore_Flag(PyObject *arg)
66 {
67     return parse_enum_flag(arg, true, "Q_FLAG");
68 }
69 
70 
71 // Add the given Q_FLAGS() arguments to the current enums/flags hash.
qpycore_Flags(PyObject * args)72 PyObject *qpycore_Flags(PyObject *args)
73 {
74     return parse_enums_flags(args, true, "Q_FLAGS");
75 }
76 
77 
78 // Return the calling frame.
get_calling_frame()79 static struct _frame *get_calling_frame()
80 {
81     struct _frame *frame = sipGetFrame(1);
82 
83     if (!frame)
84     {
85         PyErr_SetString(PyExc_RuntimeError, "no previous frame");
86         return 0;
87     }
88 
89     return frame;
90 }
91 
92 
93 // Add the given Q_ENUM() or Q_FLAG() argument to the current enums/flags hash.
parse_enum_flag(PyObject * arg,bool flag,const char * context)94 static PyObject *parse_enum_flag(PyObject *arg, bool flag, const char *context)
95 {
96 #if defined(PYPY_VERSION)
97     PyErr_Format(PyExc_AttributeError, "%s is not supported on PyPy", context);
98     return 0;
99 #else
100     struct _frame *frame = get_calling_frame();
101 
102     if (!frame)
103         return 0;
104 
105     if (!add_enum_flag(arg, flag, context, frame))
106         return 0;
107 
108     Py_INCREF(Py_None);
109     return Py_None;
110 #endif
111 }
112 
113 
114 // Add the given Q_ENUMS() or Q_FLAGS() arguments to the current enums/flags
115 // hash.
parse_enums_flags(PyObject * args,bool flags,const char * context)116 static PyObject *parse_enums_flags(PyObject *args, bool flags,
117         const char *context)
118 {
119 #if defined(PYPY_VERSION)
120     PyErr_Format(PyExc_AttributeError, "%s is not supported on PyPy", context);
121     return 0;
122 #else
123     struct _frame *frame = get_calling_frame();
124 
125     if (!frame)
126         return 0;
127 
128     // Each argument is a separate enum/flag.
129     for (Py_ssize_t i = 0; i < PyTuple_Size(args); ++i)
130     {
131         PyObject *arg = PyTuple_GetItem(args, i);
132 
133         if (!add_enum_flag(arg, flags, context, frame))
134             return 0;
135     }
136 
137     Py_INCREF(Py_None);
138     return Py_None;
139 #endif
140 }
141 
142 
143 // Add the given enum/flag to the current hash.  Return true if successfull.
add_enum_flag(PyObject * arg,bool flag,const char * context,struct _frame * frame)144 static bool add_enum_flag(PyObject *arg, bool flag, const char *context,
145         struct _frame *frame)
146 {
147     // Check the argument's type is type.  This will also pass Enums.
148     if (!PyType_Check(arg))
149     {
150         PyErr_Format(PyExc_TypeError,
151                 "arguments to %s() must be type or enum.Enum objects",
152                 context);
153 
154         return false;
155     }
156 
157     // Create the basic enum/flag.
158     EnumFlag enum_flag(sipPyTypeName((PyTypeObject *)arg), flag);
159 
160     // See if it an Enum.  We assume it is if it has a __members__ attribute.
161     static PyObject *members_s = 0;
162 
163     if (!objectify("__members__", &members_s))
164         return false;
165 
166     PyObject *members = PyObject_GetAttr(arg, members_s);
167 
168     if (members)
169     {
170         bool ok = trawl_members(members, enum_flag);
171 
172         Py_DECREF(members);
173 
174         if (!ok)
175             return false;
176 
177         enum_flag.isScoped = true;
178     }
179     else
180     {
181         trawl_type_dict(arg, enum_flag);
182     }
183 
184     enums_flags_hash.insert(frame, enum_flag);
185 
186     Chimera::registerPyEnum(arg);
187 
188     // Make sure there are no exceptions left after failed value conversions.
189     PyErr_Clear();
190 
191     return true;
192 }
193 
194 
195 // Trawl a __members__ mapping for int keys.
trawl_members(PyObject * members,EnumFlag & enum_flag)196 static bool trawl_members(PyObject *members, EnumFlag &enum_flag)
197 {
198     static PyObject *value_s = 0;
199 
200     if (!objectify("value", &value_s))
201         return false;
202 
203     PyObject *items;
204     Py_ssize_t nr_items;
205 
206     // Get the contents of __members__.
207 #if PY_MAJOR_VERSION >= 3
208     items = PyMapping_Items(members);
209 #else
210     // Python v2 implements PyMapping_Items() as a macro that expands to the
211     // following - but without the const_cast.  It isn't a problem for the
212     // version of MSVC supported by Python v2 but the const_cast is needed by
213     // later versions of MSVC and people will want to use them even though they
214     // are not supported.
215     items = PyObject_CallMethod(members, const_cast<char *>("items"), NULL);
216 #endif
217 
218     if (!items)
219         goto return_error;
220 
221     nr_items = PySequence_Length(items);
222     if (nr_items < 0)
223         goto release_items;
224 
225     for (Py_ssize_t i = 0; i < nr_items; ++i)
226     {
227         PyObject *item, *key, *member, *value;
228 
229         item = PySequence_GetItem(items, i);
230         if (!item)
231             goto release_items;
232 
233         // The item should be a 2-element sequence of the key name and an
234         // object containing the value.
235         key = PySequence_GetItem(item, 0);
236         member = PySequence_GetItem(item, 1);
237 
238         Py_DECREF(item);
239 
240         if (!key || !member)
241         {
242             Py_XDECREF(key);
243             Py_XDECREF(member);
244 
245             goto release_items;
246         }
247 
248         // Get the value.
249         value = PyObject_GetAttr(member, value_s);
250 
251         Py_DECREF(member);
252 
253         if (!value)
254         {
255             Py_DECREF(key);
256 
257             goto release_items;
258         }
259 
260         add_key_value(enum_flag, key, value);
261 
262         Py_DECREF(key);
263         Py_DECREF(value);
264     }
265 
266     Py_DECREF(items);
267 
268     return true;
269 
270 release_items:
271     Py_DECREF(items);
272 
273 return_error:
274     return false;
275 }
276 
277 
278 // Trawl the dictionary of a type looking for int attributes.
trawl_type_dict(PyObject * arg,EnumFlag & enum_flag)279 static void trawl_type_dict(PyObject *arg, EnumFlag &enum_flag)
280 {
281     Py_ssize_t pos = 0;
282     PyObject *key, *value, *dict;
283 
284     dict = sipPyTypeDict((PyTypeObject *)arg);
285 
286     while (PyDict_Next(dict, &pos, &key, &value))
287         add_key_value(enum_flag, key, value);
288 }
289 
290 
291 // Add a key/value to an enum/flag.
add_key_value(EnumFlag & enum_flag,PyObject * key,PyObject * value)292 static void add_key_value(EnumFlag &enum_flag, PyObject *key, PyObject *value)
293 {
294     PyErr_Clear();
295 
296     int i_value = sipLong_AsInt(value);
297 
298     if (!PyErr_Occurred())
299     {
300         const char *s_key = sipString_AsASCIIString(&key);
301 
302         if (s_key)
303         {
304             enum_flag.keys.insert(s_key, i_value);
305             Py_DECREF(key);
306         }
307     }
308 }
309 
310 
311 // Convert an ASCII string to a Python object if it hasn't already been done.
objectify(const char * s,PyObject ** objp)312 static bool objectify(const char *s, PyObject **objp)
313 {
314     if (*objp == NULL)
315     {
316 #if PY_MAJOR_VERSION >= 3
317         *objp = PyUnicode_FromString(s);
318 #else
319         *objp = PyString_FromString(s);
320 #endif
321 
322         if (*objp == NULL)
323             return false;
324     }
325 
326     return true;
327 }
328 
329 
330 // Return the current enums/flags list.
qpycore_get_enums_flags_list()331 QList<EnumFlag> qpycore_get_enums_flags_list()
332 {
333     QList<EnumFlag> enums_flags_list;
334 
335 #if !defined(PYPY_VERSION)
336     struct _frame *frame = sipGetFrame(0);
337 
338     enums_flags_list = enums_flags_hash.values(frame);
339     enums_flags_hash.remove(frame);
340 #endif
341 
342     return enums_flags_list;
343 }
344