qapi/expr.py: move string check upwards in check_type
[qemu/armbru.git] / scripts / qapi / expr.py
blobc0d18dcc0184e34bb30de797ad60f006fd07590d
1 # -*- coding: utf-8 -*-
3 # Check (context-free) QAPI schema expression structure
5 # Copyright IBM, Corp. 2011
6 # Copyright (c) 2013-2019 Red Hat Inc.
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>
14 # This work is licensed under the terms of the GNU GPL, version 2.
15 # See the COPYING file in the top-level directory.
17 import re
18 from typing import Dict, Optional
20 from .common import c_name
21 from .error import QAPISemError
22 from .parser import QAPIDoc
23 from .source import QAPISourceInfo
26 # Deserialized JSON objects as returned by the parser.
27 # The values of this mapping are not necessary to exhaustively type
28 # here (and also not practical as long as mypy lacks recursive
29 # types), because the purpose of this module is to interrogate that
30 # type.
31 _JSONObject = Dict[str, object]
34 # Names consist of letters, digits, -, and _, starting with a letter.
35 # An experimental name is prefixed with x-. A name of a downstream
36 # extension is prefixed with __RFQDN_. The latter prefix goes first.
37 valid_name = re.compile(r'(__[a-z0-9.-]+_)?'
38 r'(x-)?'
39 r'([a-z][a-z0-9_-]*)$', re.IGNORECASE)
42 def check_name_is_str(name, info, source):
43 if not isinstance(name, str):
44 raise QAPISemError(info, "%s requires a string name" % source)
47 def check_name_str(name, info, source):
48 # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
49 # and 'q_obj_*' implicit type names.
50 match = valid_name.match(name)
51 if not match or c_name(name, False).startswith('q_'):
52 raise QAPISemError(info, "%s has an invalid name" % source)
53 return match.group(3)
56 def check_name_upper(name, info, source):
57 stem = check_name_str(name, info, source)
58 if re.search(r'[a-z-]', stem):
59 raise QAPISemError(
60 info, "name of %s must not use lowercase or '-'" % source)
63 def check_name_lower(name, info, source,
64 permit_upper=False,
65 permit_underscore=False):
66 stem = check_name_str(name, info, source)
67 if ((not permit_upper and re.search(r'[A-Z]', stem))
68 or (not permit_underscore and '_' in stem)):
69 raise QAPISemError(
70 info, "name of %s must not use uppercase or '_'" % source)
73 def check_name_camel(name, info, source):
74 stem = check_name_str(name, info, source)
75 if not re.match(r'[A-Z][A-Za-z0-9]*[a-z][A-Za-z0-9]*$', stem):
76 raise QAPISemError(info, "name of %s must use CamelCase" % source)
79 def check_defn_name_str(name, info, meta):
80 if meta == 'event':
81 check_name_upper(name, info, meta)
82 elif meta == 'command':
83 check_name_lower(
84 name, info, meta,
85 permit_underscore=name in info.pragma.command_name_exceptions)
86 else:
87 check_name_camel(name, info, meta)
88 if name.endswith('Kind') or name.endswith('List'):
89 raise QAPISemError(
90 info, "%s name should not end in '%s'" % (meta, name[-4:]))
93 def check_keys(value, info, source, required, optional):
95 def pprint(elems):
96 return ', '.join("'" + e + "'" for e in sorted(elems))
98 missing = set(required) - set(value)
99 if missing:
100 raise QAPISemError(
101 info,
102 "%s misses key%s %s"
103 % (source, 's' if len(missing) > 1 else '',
104 pprint(missing)))
105 allowed = set(required + optional)
106 unknown = set(value) - allowed
107 if unknown:
108 raise QAPISemError(
109 info,
110 "%s has unknown key%s %s\nValid keys are %s."
111 % (source, 's' if len(unknown) > 1 else '',
112 pprint(unknown), pprint(allowed)))
115 def check_flags(expr, info):
116 for key in ['gen', 'success-response']:
117 if key in expr and expr[key] is not False:
118 raise QAPISemError(
119 info, "flag '%s' may only use false value" % key)
120 for key in ['boxed', 'allow-oob', 'allow-preconfig', 'coroutine']:
121 if key in expr and expr[key] is not True:
122 raise QAPISemError(
123 info, "flag '%s' may only use true value" % key)
124 if 'allow-oob' in expr and 'coroutine' in expr:
125 # This is not necessarily a fundamental incompatibility, but
126 # we don't have a use case and the desired semantics isn't
127 # obvious. The simplest solution is to forbid it until we get
128 # a use case for it.
129 raise QAPISemError(info, "flags 'allow-oob' and 'coroutine' "
130 "are incompatible")
133 def check_if(expr, info, source):
135 def check_if_str(ifcond):
136 if not isinstance(ifcond, str):
137 raise QAPISemError(
138 info,
139 "'if' condition of %s must be a string or a list of strings"
140 % source)
141 if ifcond.strip() == '':
142 raise QAPISemError(
143 info,
144 "'if' condition '%s' of %s makes no sense"
145 % (ifcond, source))
147 ifcond = expr.get('if')
148 if ifcond is None:
149 return
150 if isinstance(ifcond, list):
151 if ifcond == []:
152 raise QAPISemError(
153 info, "'if' condition [] of %s is useless" % source)
154 for elt in ifcond:
155 check_if_str(elt)
156 else:
157 check_if_str(ifcond)
158 expr['if'] = [ifcond]
161 def normalize_members(members):
162 if isinstance(members, dict):
163 for key, arg in members.items():
164 if isinstance(arg, dict):
165 continue
166 members[key] = {'type': arg}
169 def check_type(value, info, source,
170 allow_array=False, allow_dict=False):
171 if value is None:
172 return
174 # Type name
175 if isinstance(value, str):
176 return
178 # Array type
179 if isinstance(value, list):
180 if not allow_array:
181 raise QAPISemError(info, "%s cannot be an array" % source)
182 if len(value) != 1 or not isinstance(value[0], str):
183 raise QAPISemError(info,
184 "%s: array type must contain single type name" %
185 source)
186 return
188 # Anonymous type
190 if not allow_dict:
191 raise QAPISemError(info, "%s should be a type name" % source)
193 if not isinstance(value, dict):
194 raise QAPISemError(info,
195 "%s should be an object or type name" % source)
197 permissive = False
198 if isinstance(allow_dict, str):
199 permissive = allow_dict in info.pragma.member_name_exceptions
201 # value is a dictionary, check that each member is okay
202 for (key, arg) in value.items():
203 key_source = "%s member '%s'" % (source, key)
204 if key.startswith('*'):
205 key = key[1:]
206 check_name_lower(key, info, key_source,
207 permit_upper=permissive,
208 permit_underscore=permissive)
209 if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
210 raise QAPISemError(info, "%s uses reserved name" % key_source)
211 check_keys(arg, info, key_source, ['type'], ['if', 'features'])
212 check_if(arg, info, key_source)
213 check_features(arg.get('features'), info)
214 check_type(arg['type'], info, key_source, allow_array=True)
217 def check_features(features, info):
218 if features is None:
219 return
220 if not isinstance(features, list):
221 raise QAPISemError(info, "'features' must be an array")
222 features[:] = [f if isinstance(f, dict) else {'name': f}
223 for f in features]
224 for f in features:
225 source = "'features' member"
226 assert isinstance(f, dict)
227 check_keys(f, info, source, ['name'], ['if'])
228 check_name_is_str(f['name'], info, source)
229 source = "%s '%s'" % (source, f['name'])
230 check_name_lower(f['name'], info, source)
231 check_if(f, info, source)
234 def check_enum(expr, info):
235 name = expr['enum']
236 members = expr['data']
237 prefix = expr.get('prefix')
239 if not isinstance(members, list):
240 raise QAPISemError(info, "'data' must be an array")
241 if prefix is not None and not isinstance(prefix, str):
242 raise QAPISemError(info, "'prefix' must be a string")
244 permissive = name in info.pragma.member_name_exceptions
246 members[:] = [m if isinstance(m, dict) else {'name': m}
247 for m in members]
248 for member in members:
249 source = "'data' member"
250 member_name = member['name']
251 check_keys(member, info, source, ['name'], ['if'])
252 check_name_is_str(member_name, info, source)
253 source = "%s '%s'" % (source, member_name)
254 # Enum members may start with a digit
255 if member_name[0].isdigit():
256 member_name = 'd' + member_name # Hack: hide the digit
257 check_name_lower(member_name, info, source,
258 permit_upper=permissive,
259 permit_underscore=permissive)
260 check_if(member, info, source)
263 def check_struct(expr, info):
264 name = expr['struct']
265 members = expr['data']
267 check_type(members, info, "'data'", allow_dict=name)
268 check_type(expr.get('base'), info, "'base'")
271 def check_union(expr, info):
272 name = expr['union']
273 base = expr.get('base')
274 discriminator = expr.get('discriminator')
275 members = expr['data']
277 if discriminator is None: # simple union
278 if base is not None:
279 raise QAPISemError(info, "'base' requires 'discriminator'")
280 else: # flat union
281 check_type(base, info, "'base'", allow_dict=name)
282 if not base:
283 raise QAPISemError(info, "'discriminator' requires 'base'")
284 check_name_is_str(discriminator, info, "'discriminator'")
286 for (key, value) in members.items():
287 source = "'data' member '%s'" % key
288 if discriminator is None:
289 check_name_lower(key, info, source)
290 # else: name is in discriminator enum, which gets checked
291 check_keys(value, info, source, ['type'], ['if'])
292 check_if(value, info, source)
293 check_type(value['type'], info, source, allow_array=not base)
296 def check_alternate(expr, info):
297 members = expr['data']
299 if not members:
300 raise QAPISemError(info, "'data' must not be empty")
301 for (key, value) in members.items():
302 source = "'data' member '%s'" % key
303 check_name_lower(key, info, source)
304 check_keys(value, info, source, ['type'], ['if'])
305 check_if(value, info, source)
306 check_type(value['type'], info, source)
309 def check_command(expr, info):
310 args = expr.get('data')
311 rets = expr.get('returns')
312 boxed = expr.get('boxed', False)
314 if boxed and args is None:
315 raise QAPISemError(info, "'boxed': true requires 'data'")
316 check_type(args, info, "'data'", allow_dict=not boxed)
317 check_type(rets, info, "'returns'", allow_array=True)
320 def check_event(expr, info):
321 args = expr.get('data')
322 boxed = expr.get('boxed', False)
324 if boxed and args is None:
325 raise QAPISemError(info, "'boxed': true requires 'data'")
326 check_type(args, info, "'data'", allow_dict=not boxed)
329 def check_exprs(exprs):
330 for expr_elem in exprs:
331 # Expression
332 assert isinstance(expr_elem['expr'], dict)
333 for key in expr_elem['expr'].keys():
334 assert isinstance(key, str)
335 expr: _JSONObject = expr_elem['expr']
337 # QAPISourceInfo
338 assert isinstance(expr_elem['info'], QAPISourceInfo)
339 info: QAPISourceInfo = expr_elem['info']
341 # Optional[QAPIDoc]
342 tmp = expr_elem.get('doc')
343 assert tmp is None or isinstance(tmp, QAPIDoc)
344 doc: Optional[QAPIDoc] = tmp
346 if 'include' in expr:
347 continue
349 if 'enum' in expr:
350 meta = 'enum'
351 elif 'union' in expr:
352 meta = 'union'
353 elif 'alternate' in expr:
354 meta = 'alternate'
355 elif 'struct' in expr:
356 meta = 'struct'
357 elif 'command' in expr:
358 meta = 'command'
359 elif 'event' in expr:
360 meta = 'event'
361 else:
362 raise QAPISemError(info, "expression is missing metatype")
364 name = expr[meta]
365 check_name_is_str(name, info, "'%s'" % meta)
366 info.set_defn(meta, name)
367 check_defn_name_str(name, info, meta)
369 if doc:
370 if doc.symbol != name:
371 raise QAPISemError(
372 info, "documentation comment is for '%s'" % doc.symbol)
373 doc.check_expr(expr)
374 elif info.pragma.doc_required:
375 raise QAPISemError(info,
376 "documentation comment required")
378 if meta == 'enum':
379 check_keys(expr, info, meta,
380 ['enum', 'data'], ['if', 'features', 'prefix'])
381 check_enum(expr, info)
382 elif meta == 'union':
383 check_keys(expr, info, meta,
384 ['union', 'data'],
385 ['base', 'discriminator', 'if', 'features'])
386 normalize_members(expr.get('base'))
387 normalize_members(expr['data'])
388 check_union(expr, info)
389 elif meta == 'alternate':
390 check_keys(expr, info, meta,
391 ['alternate', 'data'], ['if', 'features'])
392 normalize_members(expr['data'])
393 check_alternate(expr, info)
394 elif meta == 'struct':
395 check_keys(expr, info, meta,
396 ['struct', 'data'], ['base', 'if', 'features'])
397 normalize_members(expr['data'])
398 check_struct(expr, info)
399 elif meta == 'command':
400 check_keys(expr, info, meta,
401 ['command'],
402 ['data', 'returns', 'boxed', 'if', 'features',
403 'gen', 'success-response', 'allow-oob',
404 'allow-preconfig', 'coroutine'])
405 normalize_members(expr.get('data'))
406 check_command(expr, info)
407 elif meta == 'event':
408 check_keys(expr, info, meta,
409 ['event'], ['data', 'boxed', 'if', 'features'])
410 normalize_members(expr.get('data'))
411 check_event(expr, info)
412 else:
413 assert False, 'unexpected meta type'
415 check_if(expr, info, meta)
416 check_features(expr.get('features'), info)
417 check_flags(expr, info)
419 return exprs