]>
Commit | Line | Data |
---|---|---|
e6c42b96 MA |
1 | # -*- coding: utf-8 -*- |
2 | # | |
3 | # Check (context-free) QAPI schema expression structure | |
4 | # | |
5 | # Copyright IBM, Corp. 2011 | |
6 | # Copyright (c) 2013-2019 Red Hat Inc. | |
7 | # | |
8 | # Authors: | |
9 | # Anthony Liguori <aliguori@us.ibm.com> | |
10 | # Markus Armbruster <armbru@redhat.com> | |
11 | # Eric Blake <eblake@redhat.com> | |
12 | # Marc-André Lureau <marcandre.lureau@redhat.com> | |
13 | # | |
14 | # This work is licensed under the terms of the GNU GPL, version 2. | |
15 | # See the COPYING file in the top-level directory. | |
16 | ||
17 | import re | |
18 | from collections import OrderedDict | |
19 | from qapi.common import c_name | |
20 | from qapi.error import QAPISemError | |
21 | ||
22 | ||
23 | # Names must be letters, numbers, -, and _. They must start with letter, | |
24 | # except for downstream extensions which must start with __RFQDN_. | |
25 | # Dots are only valid in the downstream extension prefix. | |
26 | valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?' | |
27 | '[a-zA-Z][a-zA-Z0-9_-]*$') | |
28 | ||
29 | ||
30 | def check_name_is_str(name, info, source): | |
31 | if not isinstance(name, str): | |
32 | raise QAPISemError(info, "%s requires a string name" % source) | |
33 | ||
34 | ||
35 | def check_name_str(name, info, source, | |
36 | allow_optional=False, enum_member=False, | |
37 | permit_upper=False): | |
38 | global valid_name | |
39 | membername = name | |
40 | ||
41 | if allow_optional and name.startswith('*'): | |
42 | membername = name[1:] | |
43 | # Enum members can start with a digit, because the generated C | |
44 | # code always prefixes it with the enum name | |
45 | if enum_member and membername[0].isdigit(): | |
46 | membername = 'D' + membername | |
47 | # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty' | |
48 | # and 'q_obj_*' implicit type names. | |
49 | if not valid_name.match(membername) or \ | |
50 | c_name(membername, False).startswith('q_'): | |
51 | raise QAPISemError(info, "%s has an invalid name" % source) | |
52 | if not permit_upper and name.lower() != name: | |
53 | raise QAPISemError( | |
54 | info, "%s uses uppercase in name" % source) | |
55 | assert not membername.startswith('*') | |
56 | ||
57 | ||
58 | def check_defn_name_str(name, info, meta): | |
59 | check_name_str(name, info, meta, permit_upper=True) | |
60 | if name.endswith('Kind') or name.endswith('List'): | |
61 | raise QAPISemError( | |
62 | info, "%s name should not end in '%s'" % (meta, name[-4:])) | |
63 | ||
64 | ||
65 | def check_keys(value, info, source, required, optional): | |
66 | ||
67 | def pprint(elems): | |
68 | return ', '.join("'" + e + "'" for e in sorted(elems)) | |
69 | ||
70 | missing = set(required) - set(value) | |
71 | if missing: | |
72 | raise QAPISemError( | |
73 | info, | |
74 | "%s misses key%s %s" | |
75 | % (source, 's' if len(missing) > 1 else '', | |
76 | pprint(missing))) | |
77 | allowed = set(required + optional) | |
78 | unknown = set(value) - allowed | |
79 | if unknown: | |
80 | raise QAPISemError( | |
81 | info, | |
82 | "%s has unknown key%s %s\nValid keys are %s." | |
83 | % (source, 's' if len(unknown) > 1 else '', | |
84 | pprint(unknown), pprint(allowed))) | |
85 | ||
86 | ||
87 | def check_flags(expr, info): | |
88 | for key in ['gen', 'success-response']: | |
89 | if key in expr and expr[key] is not False: | |
90 | raise QAPISemError( | |
91 | info, "flag '%s' may only use false value" % key) | |
92 | for key in ['boxed', 'allow-oob', 'allow-preconfig']: | |
93 | if key in expr and expr[key] is not True: | |
94 | raise QAPISemError( | |
95 | info, "flag '%s' may only use true value" % key) | |
96 | ||
97 | ||
e6c42b96 MA |
98 | def check_if(expr, info, source): |
99 | ||
100 | def check_if_str(ifcond, info): | |
101 | if not isinstance(ifcond, str): | |
102 | raise QAPISemError( | |
103 | info, | |
104 | "'if' condition of %s must be a string or a list of strings" | |
105 | % source) | |
106 | if ifcond.strip() == '': | |
107 | raise QAPISemError( | |
108 | info, | |
109 | "'if' condition '%s' of %s makes no sense" | |
110 | % (ifcond, source)) | |
111 | ||
112 | ifcond = expr.get('if') | |
113 | if ifcond is None: | |
114 | return | |
115 | if isinstance(ifcond, list): | |
116 | if ifcond == []: | |
117 | raise QAPISemError( | |
118 | info, "'if' condition [] of %s is useless" % source) | |
119 | for elt in ifcond: | |
120 | check_if_str(elt, info) | |
121 | else: | |
122 | check_if_str(ifcond, info) | |
c145bfda | 123 | expr['if'] = [ifcond] |
e6c42b96 MA |
124 | |
125 | ||
126 | def normalize_members(members): | |
127 | if isinstance(members, OrderedDict): | |
128 | for key, arg in members.items(): | |
129 | if isinstance(arg, dict): | |
130 | continue | |
131 | members[key] = {'type': arg} | |
132 | ||
133 | ||
134 | def check_type(value, info, source, | |
135 | allow_array=False, allow_dict=False): | |
136 | if value is None: | |
137 | return | |
138 | ||
139 | # Array type | |
140 | if isinstance(value, list): | |
141 | if not allow_array: | |
142 | raise QAPISemError(info, "%s cannot be an array" % source) | |
143 | if len(value) != 1 or not isinstance(value[0], str): | |
144 | raise QAPISemError(info, | |
145 | "%s: array type must contain single type name" % | |
146 | source) | |
147 | return | |
148 | ||
149 | # Type name | |
150 | if isinstance(value, str): | |
151 | return | |
152 | ||
153 | # Anonymous type | |
154 | ||
155 | if not allow_dict: | |
156 | raise QAPISemError(info, "%s should be a type name" % source) | |
157 | ||
158 | if not isinstance(value, OrderedDict): | |
159 | raise QAPISemError(info, | |
160 | "%s should be an object or type name" % source) | |
161 | ||
162 | permit_upper = allow_dict in info.pragma.name_case_whitelist | |
163 | ||
164 | # value is a dictionary, check that each member is okay | |
165 | for (key, arg) in value.items(): | |
166 | key_source = "%s member '%s'" % (source, key) | |
167 | check_name_str(key, info, key_source, | |
168 | allow_optional=True, permit_upper=permit_upper) | |
169 | if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'): | |
170 | raise QAPISemError(info, "%s uses reserved name" % key_source) | |
171 | check_keys(arg, info, key_source, ['type'], ['if']) | |
172 | check_if(arg, info, key_source) | |
e6c42b96 MA |
173 | check_type(arg['type'], info, key_source, allow_array=True) |
174 | ||
175 | ||
176 | def normalize_features(features): | |
177 | if isinstance(features, list): | |
178 | features[:] = [f if isinstance(f, dict) else {'name': f} | |
179 | for f in features] | |
180 | ||
181 | ||
23394b4c PK |
182 | def check_features(features, info): |
183 | if features is None: | |
184 | return | |
185 | if not isinstance(features, list): | |
186 | raise QAPISemError(info, "'features' must be an array") | |
187 | for f in features: | |
188 | source = "'features' member" | |
189 | assert isinstance(f, dict) | |
190 | check_keys(f, info, source, ['name'], ['if']) | |
191 | check_name_is_str(f['name'], info, source) | |
192 | source = "%s '%s'" % (source, f['name']) | |
193 | check_name_str(f['name'], info, source) | |
194 | check_if(f, info, source) | |
23394b4c PK |
195 | |
196 | ||
e6c42b96 MA |
197 | def normalize_enum(expr): |
198 | if isinstance(expr['data'], list): | |
199 | expr['data'] = [m if isinstance(m, dict) else {'name': m} | |
200 | for m in expr['data']] | |
201 | ||
202 | ||
203 | def check_enum(expr, info): | |
204 | name = expr['enum'] | |
205 | members = expr['data'] | |
206 | prefix = expr.get('prefix') | |
207 | ||
208 | if not isinstance(members, list): | |
209 | raise QAPISemError(info, "'data' must be an array") | |
210 | if prefix is not None and not isinstance(prefix, str): | |
211 | raise QAPISemError(info, "'prefix' must be a string") | |
212 | ||
213 | permit_upper = name in info.pragma.name_case_whitelist | |
214 | ||
215 | for member in members: | |
216 | source = "'data' member" | |
217 | check_keys(member, info, source, ['name'], ['if']) | |
218 | check_name_is_str(member['name'], info, source) | |
219 | source = "%s '%s'" % (source, member['name']) | |
220 | check_name_str(member['name'], info, source, | |
221 | enum_member=True, permit_upper=permit_upper) | |
222 | check_if(member, info, source) | |
e6c42b96 MA |
223 | |
224 | ||
225 | def check_struct(expr, info): | |
226 | name = expr['struct'] | |
227 | members = expr['data'] | |
e6c42b96 MA |
228 | |
229 | check_type(members, info, "'data'", allow_dict=name) | |
230 | check_type(expr.get('base'), info, "'base'") | |
23394b4c | 231 | check_features(expr.get('features'), info) |
e6c42b96 MA |
232 | |
233 | ||
234 | def check_union(expr, info): | |
235 | name = expr['union'] | |
236 | base = expr.get('base') | |
237 | discriminator = expr.get('discriminator') | |
238 | members = expr['data'] | |
239 | ||
240 | if discriminator is None: # simple union | |
241 | if base is not None: | |
242 | raise QAPISemError(info, "'base' requires 'discriminator'") | |
243 | else: # flat union | |
244 | check_type(base, info, "'base'", allow_dict=name) | |
245 | if not base: | |
246 | raise QAPISemError(info, "'discriminator' requires 'base'") | |
247 | check_name_is_str(discriminator, info, "'discriminator'") | |
248 | ||
249 | for (key, value) in members.items(): | |
250 | source = "'data' member '%s'" % key | |
251 | check_name_str(key, info, source) | |
252 | check_keys(value, info, source, ['type'], ['if']) | |
253 | check_if(value, info, source) | |
e6c42b96 MA |
254 | check_type(value['type'], info, source, allow_array=not base) |
255 | ||
256 | ||
257 | def check_alternate(expr, info): | |
258 | members = expr['data'] | |
259 | ||
260 | if len(members) == 0: | |
261 | raise QAPISemError(info, "'data' must not be empty") | |
262 | for (key, value) in members.items(): | |
263 | source = "'data' member '%s'" % key | |
264 | check_name_str(key, info, source) | |
265 | check_keys(value, info, source, ['type'], ['if']) | |
266 | check_if(value, info, source) | |
e6c42b96 MA |
267 | check_type(value['type'], info, source) |
268 | ||
269 | ||
270 | def check_command(expr, info): | |
271 | args = expr.get('data') | |
272 | rets = expr.get('returns') | |
273 | boxed = expr.get('boxed', False) | |
274 | ||
275 | if boxed and args is None: | |
276 | raise QAPISemError(info, "'boxed': true requires 'data'") | |
277 | check_type(args, info, "'data'", allow_dict=not boxed) | |
278 | check_type(rets, info, "'returns'", allow_array=True) | |
23394b4c | 279 | check_features(expr.get('features'), info) |
e6c42b96 MA |
280 | |
281 | ||
282 | def check_event(expr, info): | |
283 | args = expr.get('data') | |
284 | boxed = expr.get('boxed', False) | |
285 | ||
286 | if boxed and args is None: | |
287 | raise QAPISemError(info, "'boxed': true requires 'data'") | |
288 | check_type(args, info, "'data'", allow_dict=not boxed) | |
289 | ||
290 | ||
291 | def check_exprs(exprs): | |
292 | for expr_elem in exprs: | |
293 | expr = expr_elem['expr'] | |
294 | info = expr_elem['info'] | |
295 | doc = expr_elem.get('doc') | |
296 | ||
297 | if 'include' in expr: | |
298 | continue | |
299 | ||
300 | if 'enum' in expr: | |
301 | meta = 'enum' | |
302 | elif 'union' in expr: | |
303 | meta = 'union' | |
304 | elif 'alternate' in expr: | |
305 | meta = 'alternate' | |
306 | elif 'struct' in expr: | |
307 | meta = 'struct' | |
308 | elif 'command' in expr: | |
309 | meta = 'command' | |
310 | elif 'event' in expr: | |
311 | meta = 'event' | |
312 | else: | |
313 | raise QAPISemError(info, "expression is missing metatype") | |
314 | ||
315 | name = expr[meta] | |
316 | check_name_is_str(name, info, "'%s'" % meta) | |
317 | info.set_defn(meta, name) | |
318 | check_defn_name_str(name, info, meta) | |
319 | ||
320 | if doc: | |
321 | if doc.symbol != name: | |
322 | raise QAPISemError( | |
323 | info, "documentation comment is for '%s'" % doc.symbol) | |
324 | doc.check_expr(expr) | |
325 | elif info.pragma.doc_required: | |
326 | raise QAPISemError(info, | |
327 | "documentation comment required") | |
328 | ||
329 | if meta == 'enum': | |
330 | check_keys(expr, info, meta, | |
331 | ['enum', 'data'], ['if', 'prefix']) | |
332 | normalize_enum(expr) | |
333 | check_enum(expr, info) | |
334 | elif meta == 'union': | |
335 | check_keys(expr, info, meta, | |
336 | ['union', 'data'], | |
337 | ['base', 'discriminator', 'if']) | |
338 | normalize_members(expr.get('base')) | |
339 | normalize_members(expr['data']) | |
340 | check_union(expr, info) | |
341 | elif meta == 'alternate': | |
342 | check_keys(expr, info, meta, | |
343 | ['alternate', 'data'], ['if']) | |
344 | normalize_members(expr['data']) | |
345 | check_alternate(expr, info) | |
346 | elif meta == 'struct': | |
347 | check_keys(expr, info, meta, | |
348 | ['struct', 'data'], ['base', 'if', 'features']) | |
349 | normalize_members(expr['data']) | |
350 | normalize_features(expr.get('features')) | |
351 | check_struct(expr, info) | |
352 | elif meta == 'command': | |
353 | check_keys(expr, info, meta, | |
354 | ['command'], | |
23394b4c | 355 | ['data', 'returns', 'boxed', 'if', 'features', |
e6c42b96 MA |
356 | 'gen', 'success-response', 'allow-oob', |
357 | 'allow-preconfig']) | |
358 | normalize_members(expr.get('data')) | |
23394b4c | 359 | normalize_features(expr.get('features')) |
e6c42b96 MA |
360 | check_command(expr, info) |
361 | elif meta == 'event': | |
362 | check_keys(expr, info, meta, | |
363 | ['event'], ['data', 'boxed', 'if']) | |
364 | normalize_members(expr.get('data')) | |
365 | check_event(expr, info) | |
366 | else: | |
367 | assert False, 'unexpected meta type' | |
368 | ||
e6c42b96 MA |
369 | check_if(expr, info, meta) |
370 | check_flags(expr, info) | |
371 | ||
372 | return exprs |