1 # -*- coding: utf-8 -*-
3 # Copyright IBM, Corp. 2011
4 # Copyright (c) 2013-2021 Red Hat Inc.
7 # Anthony Liguori <aliguori@us.ibm.com>
8 # Markus Armbruster <armbru@redhat.com>
9 # Eric Blake <eblake@redhat.com>
10 # Marc-André Lureau <marcandre.lureau@redhat.com>
11 # John Snow <jsnow@redhat.com>
13 # This work is licensed under the terms of the GNU GPL, version 2.
14 # See the COPYING file in the top-level directory.
17 Normalize and validate (context-free) QAPI schema expression structures.
19 `QAPISchemaParser` parses a QAPI schema into abstract syntax trees
20 consisting of dict, list, str, bool, and int nodes. This module ensures
21 that these nested structures have the correct type(s) and key(s) where
22 appropriate for the QAPI context-free grammar.
24 The QAPI schema expression language allows for certain syntactic sugar;
25 this module also handles the normalization process of these nested
28 See `check_exprs` for the main entry point.
30 See `schema.QAPISchema` for processing into native Python data
31 structures and contextual semantic validation.
44 from .common
import c_name
45 from .error
import QAPISemError
46 from .parser
import QAPIExpression
47 from .source
import QAPISourceInfo
50 # See check_name_str(), below.
51 valid_name
= re
.compile(r
'(__[a-z0-9.-]+_)?'
53 r
'([a-z][a-z0-9_-]*)$', re
.IGNORECASE
)
56 def check_name_is_str(name
: object,
60 Ensure that ``name`` is a ``str``.
62 :raise QAPISemError: When ``name`` fails validation.
64 if not isinstance(name
, str):
65 raise QAPISemError(info
, "%s requires a string name" % source
)
68 def check_name_str(name
: str, info
: QAPISourceInfo
, source
: str) -> str:
70 Ensure that ``name`` is a valid QAPI name.
72 A valid name consists of ASCII letters, digits, ``-``, and ``_``,
73 starting with a letter. It may be prefixed by a downstream prefix
74 of the form __RFQDN_, or the experimental prefix ``x-``. If both
75 prefixes are present, the __RFDQN_ prefix goes first.
77 A valid name cannot start with ``q_``, which is reserved.
79 :param name: Name to check.
80 :param info: QAPI schema source file information.
81 :param source: Error string describing what ``name`` belongs to.
83 :raise QAPISemError: When ``name`` fails validation.
84 :return: The stem of the valid name, with no prefixes.
86 # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
87 # and 'q_obj_*' implicit type names.
88 match
= valid_name
.match(name
)
89 if not match
or c_name(name
, False).startswith('q_'):
90 raise QAPISemError(info
, "%s has an invalid name" % source
)
94 def check_name_upper(name
: str, info
: QAPISourceInfo
, source
: str) -> None:
96 Ensure that ``name`` is a valid event name.
98 This means it must be a valid QAPI name as checked by
99 `check_name_str()`, but where the stem prohibits lowercase
100 characters and ``-``.
102 :param name: Name to check.
103 :param info: QAPI schema source file information.
104 :param source: Error string describing what ``name`` belongs to.
106 :raise QAPISemError: When ``name`` fails validation.
108 stem
= check_name_str(name
, info
, source
)
109 if re
.search(r
'[a-z-]', stem
):
111 info
, "name of %s must not use lowercase or '-'" % source
)
114 def check_name_lower(name
: str, info
: QAPISourceInfo
, source
: str,
115 permit_upper
: bool = False,
116 permit_underscore
: bool = False) -> None:
118 Ensure that ``name`` is a valid command or member name.
120 This means it must be a valid QAPI name as checked by
121 `check_name_str()`, but where the stem prohibits uppercase
122 characters and ``_``.
124 :param name: Name to check.
125 :param info: QAPI schema source file information.
126 :param source: Error string describing what ``name`` belongs to.
127 :param permit_upper: Additionally permit uppercase.
128 :param permit_underscore: Additionally permit ``_``.
130 :raise QAPISemError: When ``name`` fails validation.
132 stem
= check_name_str(name
, info
, source
)
133 if ((not permit_upper
and re
.search(r
'[A-Z]', stem
))
134 or (not permit_underscore
and '_' in stem
)):
136 info
, "name of %s must not use uppercase or '_'" % source
)
139 def check_name_camel(name
: str, info
: QAPISourceInfo
, source
: str) -> None:
141 Ensure that ``name`` is a valid user-defined type name.
143 This means it must be a valid QAPI name as checked by
144 `check_name_str()`, but where the stem must be in CamelCase.
146 :param name: Name to check.
147 :param info: QAPI schema source file information.
148 :param source: Error string describing what ``name`` belongs to.
150 :raise QAPISemError: When ``name`` fails validation.
152 stem
= check_name_str(name
, info
, source
)
153 if not re
.match(r
'[A-Z][A-Za-z0-9]*[a-z][A-Za-z0-9]*$', stem
):
154 raise QAPISemError(info
, "name of %s must use CamelCase" % source
)
157 def check_defn_name_str(name
: str, info
: QAPISourceInfo
, meta
: str) -> None:
159 Ensure that ``name`` is a valid definition name.
161 Based on the value of ``meta``, this means that:
162 - 'event' names adhere to `check_name_upper()`.
163 - 'command' names adhere to `check_name_lower()`.
164 - Else, meta is a type, and must pass `check_name_camel()`.
165 These names must not end with ``List``.
167 :param name: Name to check.
168 :param info: QAPI schema source file information.
169 :param meta: Meta-type name of the QAPI expression.
171 :raise QAPISemError: When ``name`` fails validation.
174 check_name_upper(name
, info
, meta
)
175 elif meta
== 'command':
178 permit_underscore
=name
in info
.pragma
.command_name_exceptions
)
180 check_name_camel(name
, info
, meta
)
181 if name
.endswith('List'):
183 info
, "%s name should not end in 'List'" % meta
)
186 def check_keys(value
: Dict
[str, object],
187 info
: QAPISourceInfo
,
190 optional
: List
[str]) -> None:
192 Ensure that a dict has a specific set of keys.
194 :param value: The dict to check.
195 :param info: QAPI schema source file information.
196 :param source: Error string describing this ``value``.
197 :param required: Keys that *must* be present.
198 :param optional: Keys that *may* be present.
200 :raise QAPISemError: When unknown keys are present.
203 def pprint(elems
: Iterable
[str]) -> str:
204 return ', '.join("'" + e
+ "'" for e
in sorted(elems
))
206 missing
= set(required
) - set(value
)
211 % (source
, 's' if len(missing
) > 1 else '',
213 allowed
= set(required
) |
set(optional
)
214 unknown
= set(value
) - allowed
218 "%s has unknown key%s %s\nValid keys are %s."
219 % (source
, 's' if len(unknown
) > 1 else '',
220 pprint(unknown
), pprint(allowed
)))
223 def check_flags(expr
: QAPIExpression
) -> None:
225 Ensure flag members (if present) have valid values.
227 :param expr: The expression to validate.
230 When certain flags have an invalid value, or when
231 incompatible flags are present.
233 for key
in ('gen', 'success-response'):
234 if key
in expr
and expr
[key
] is not False:
236 expr
.info
, "flag '%s' may only use false value" % key
)
237 for key
in ('boxed', 'allow-oob', 'allow-preconfig', 'coroutine'):
238 if key
in expr
and expr
[key
] is not True:
240 expr
.info
, "flag '%s' may only use true value" % key
)
241 if 'allow-oob' in expr
and 'coroutine' in expr
:
242 # This is not necessarily a fundamental incompatibility, but
243 # we don't have a use case and the desired semantics isn't
244 # obvious. The simplest solution is to forbid it until we get
247 expr
.info
, "flags 'allow-oob' and 'coroutine' are incompatible")
250 def check_if(expr
: Dict
[str, object],
251 info
: QAPISourceInfo
, source
: str) -> None:
253 Validate the ``if`` member of an object.
255 The ``if`` member may be either a ``str`` or a dict.
257 :param expr: The expression containing the ``if`` member to validate.
258 :param info: QAPI schema source file information.
259 :param source: Error string describing ``expr``.
262 When the "if" member fails validation, or when there are no
263 non-empty conditions.
267 def _check_if(cond
: Union
[str, object]) -> None:
268 if isinstance(cond
, str):
269 if not re
.fullmatch(r
'[A-Z][A-Z0-9_]*', cond
):
272 "'if' condition '%s' of %s is not a valid identifier"
276 if not isinstance(cond
, dict):
279 "'if' condition of %s must be a string or an object" % source
)
280 check_keys(cond
, info
, "'if' condition of %s" % source
, [],
281 ["all", "any", "not"])
285 "'if' condition of %s has conflicting keys" % source
)
288 _check_if(cond
['not'])
290 _check_infix('all', cond
['all'])
292 _check_infix('any', cond
['any'])
294 def _check_infix(operator
: str, operands
: object) -> None:
295 if not isinstance(operands
, list):
298 "'%s' condition of %s must be an array"
299 % (operator
, source
))
302 info
, "'if' condition [] of %s is useless" % source
)
303 for operand
in operands
:
306 ifcond
= expr
.get('if')
313 def normalize_members(members
: object) -> None:
315 Normalize a "members" value.
317 If ``members`` is a dict, for every value in that dict, if that
318 value is not itself already a dict, normalize it to
322 :sugared: ``Dict[str, Union[str, TypeRef]]``
323 :canonical: ``Dict[str, TypeRef]``
325 :param members: The members value to normalize.
327 :return: None, ``members`` is normalized in-place as needed.
329 if isinstance(members
, dict):
330 for key
, arg
in members
.items():
331 if isinstance(arg
, dict):
333 members
[key
] = {'type': arg
}
336 def check_type_name(value
: Optional
[object],
337 info
: QAPISourceInfo
, source
: str) -> None:
341 if isinstance(value
, str):
344 if isinstance(value
, list):
345 raise QAPISemError(info
, "%s cannot be an array" % source
)
347 raise QAPISemError(info
, "%s should be a type name" % source
)
350 def check_type_name_or_array(value
: Optional
[object],
351 info
: QAPISourceInfo
, source
: str) -> None:
355 if isinstance(value
, str):
358 if isinstance(value
, list):
359 if len(value
) != 1 or not isinstance(value
[0], str):
360 raise QAPISemError(info
,
361 "%s: array type must contain single type name" %
365 raise QAPISemError(info
,
366 "%s should be a type name" % source
)
369 def check_type_name_or_implicit(value
: Optional
[object],
370 info
: QAPISourceInfo
, source
: str,
371 parent_name
: Optional
[str]) -> None:
373 Normalize and validate an optional implicit struct type.
375 Accept ``None``, ``str``, or a ``dict`` defining an implicit
376 struct type. The latter is normalized in place.
378 :param value: The value to check.
379 :param info: QAPI schema source file information.
380 :param source: Error string describing this ``value``.
382 When the value of ``parent_name`` is in pragma
383 ``member-name-exceptions``, an implicit struct type may
384 violate the member naming rules.
386 :raise QAPISemError: When ``value`` fails validation.
392 if isinstance(value
, str):
395 if isinstance(value
, list):
396 raise QAPISemError(info
, "%s cannot be an array" % source
)
398 if not isinstance(value
, dict):
399 raise QAPISemError(info
,
400 "%s should be an object or type name" % source
)
402 permissive
= parent_name
in info
.pragma
.member_name_exceptions
404 for (key
, arg
) in value
.items():
405 key_source
= "%s member '%s'" % (source
, key
)
406 if key
.startswith('*'):
408 check_name_lower(key
, info
, key_source
,
409 permit_upper
=permissive
,
410 permit_underscore
=permissive
)
411 if c_name(key
, False) == 'u' or c_name(key
, False).startswith('has_'):
412 raise QAPISemError(info
, "%s uses reserved name" % key_source
)
413 check_keys(arg
, info
, key_source
, ['type'], ['if', 'features'])
414 check_if(arg
, info
, key_source
)
415 check_features(arg
.get('features'), info
)
416 check_type_name_or_array(arg
['type'], info
, key_source
)
419 def check_features(features
: Optional
[object],
420 info
: QAPISourceInfo
) -> None:
422 Normalize and validate the ``features`` member.
424 ``features`` may be a ``list`` of either ``str`` or ``dict``.
425 Any ``str`` element will be normalized to ``{'name': element}``.
428 :sugared: ``List[Union[str, Feature]]``
429 :canonical: ``List[Feature]``
431 :param features: The features member value to validate.
432 :param info: QAPI schema source file information.
434 :raise QAPISemError: When ``features`` fails validation.
435 :return: None, ``features`` is normalized in-place as needed.
439 if not isinstance(features
, list):
440 raise QAPISemError(info
, "'features' must be an array")
441 features
[:] = [f
if isinstance(f
, dict) else {'name': f
}
443 for feat
in features
:
444 source
= "'features' member"
445 assert isinstance(feat
, dict)
446 check_keys(feat
, info
, source
, ['name'], ['if'])
447 check_name_is_str(feat
['name'], info
, source
)
448 source
= "%s '%s'" % (source
, feat
['name'])
449 check_name_lower(feat
['name'], info
, source
)
450 check_if(feat
, info
, source
)
453 def check_enum(expr
: QAPIExpression
) -> None:
455 Normalize and validate this expression as an ``enum`` definition.
457 :param expr: The expression to validate.
459 :raise QAPISemError: When ``expr`` is not a valid ``enum``.
460 :return: None, ``expr`` is normalized in-place as needed.
463 members
= expr
['data']
464 prefix
= expr
.get('prefix')
467 if not isinstance(members
, list):
468 raise QAPISemError(info
, "'data' must be an array")
469 if prefix
is not None and not isinstance(prefix
, str):
470 raise QAPISemError(info
, "'prefix' must be a string")
472 permissive
= name
in info
.pragma
.member_name_exceptions
474 members
[:] = [m
if isinstance(m
, dict) else {'name': m
}
476 for member
in members
:
477 source
= "'data' member"
478 check_keys(member
, info
, source
, ['name'], ['if', 'features'])
479 member_name
= member
['name']
480 check_name_is_str(member_name
, info
, source
)
481 source
= "%s '%s'" % (source
, member_name
)
482 # Enum members may start with a digit
483 if member_name
[0].isdigit():
484 member_name
= 'd' + member_name
# Hack: hide the digit
485 check_name_lower(member_name
, info
, source
,
486 permit_upper
=permissive
,
487 permit_underscore
=permissive
)
488 check_if(member
, info
, source
)
489 check_features(member
.get('features'), info
)
492 def check_struct(expr
: QAPIExpression
) -> None:
494 Normalize and validate this expression as a ``struct`` definition.
496 :param expr: The expression to validate.
498 :raise QAPISemError: When ``expr`` is not a valid ``struct``.
499 :return: None, ``expr`` is normalized in-place as needed.
501 name
= cast(str, expr
['struct']) # Checked in check_exprs
502 members
= expr
['data']
504 check_type_name_or_implicit(members
, expr
.info
, "'data'", name
)
505 check_type_name(expr
.get('base'), expr
.info
, "'base'")
508 def check_union(expr
: QAPIExpression
) -> None:
510 Normalize and validate this expression as a ``union`` definition.
512 :param expr: The expression to validate.
514 :raise QAPISemError: when ``expr`` is not a valid ``union``.
515 :return: None, ``expr`` is normalized in-place as needed.
517 name
= cast(str, expr
['union']) # Checked in check_exprs
519 discriminator
= expr
['discriminator']
520 members
= expr
['data']
523 check_type_name_or_implicit(base
, info
, "'base'", name
)
524 check_name_is_str(discriminator
, info
, "'discriminator'")
526 if not isinstance(members
, dict):
527 raise QAPISemError(info
, "'data' must be an object")
529 for (key
, value
) in members
.items():
530 source
= "'data' member '%s'" % key
531 check_keys(value
, info
, source
, ['type'], ['if'])
532 check_if(value
, info
, source
)
533 check_type_name(value
['type'], info
, source
)
536 def check_alternate(expr
: QAPIExpression
) -> None:
538 Normalize and validate this expression as an ``alternate`` definition.
540 :param expr: The expression to validate.
542 :raise QAPISemError: When ``expr`` is not a valid ``alternate``.
543 :return: None, ``expr`` is normalized in-place as needed.
545 members
= expr
['data']
549 raise QAPISemError(info
, "'data' must not be empty")
551 if not isinstance(members
, dict):
552 raise QAPISemError(info
, "'data' must be an object")
554 for (key
, value
) in members
.items():
555 source
= "'data' member '%s'" % key
556 check_name_lower(key
, info
, source
)
557 check_keys(value
, info
, source
, ['type'], ['if'])
558 check_if(value
, info
, source
)
559 check_type_name_or_array(value
['type'], info
, source
)
562 def check_command(expr
: QAPIExpression
) -> None:
564 Normalize and validate this expression as a ``command`` definition.
566 :param expr: The expression to validate.
568 :raise QAPISemError: When ``expr`` is not a valid ``command``.
569 :return: None, ``expr`` is normalized in-place as needed.
571 args
= expr
.get('data')
572 rets
= expr
.get('returns')
573 boxed
= expr
.get('boxed', False)
577 raise QAPISemError(expr
.info
, "'boxed': true requires 'data'")
578 check_type_name(args
, expr
.info
, "'data'")
580 check_type_name_or_implicit(args
, expr
.info
, "'data'", None)
581 check_type_name_or_array(rets
, expr
.info
, "'returns'")
584 def check_event(expr
: QAPIExpression
) -> None:
586 Normalize and validate this expression as an ``event`` definition.
588 :param expr: The expression to validate.
590 :raise QAPISemError: When ``expr`` is not a valid ``event``.
591 :return: None, ``expr`` is normalized in-place as needed.
593 args
= expr
.get('data')
594 boxed
= expr
.get('boxed', False)
598 raise QAPISemError(expr
.info
, "'boxed': true requires 'data'")
599 check_type_name(args
, expr
.info
, "'data'")
601 check_type_name_or_implicit(args
, expr
.info
, "'data'", None)
604 def check_exprs(exprs
: List
[QAPIExpression
]) -> List
[QAPIExpression
]:
606 Validate and normalize a list of parsed QAPI schema expressions.
608 This function accepts a list of expressions and metadata as returned
609 by the parser. It destructively normalizes the expressions in-place.
611 :param exprs: The list of expressions to normalize and validate.
613 :raise QAPISemError: When any expression fails validation.
614 :return: The same list of expressions (now modified).
620 if 'include' in expr
:
623 metas
= expr
.keys() & {'enum', 'struct', 'union', 'alternate',
628 "expression must have exactly one key"
629 " 'enum', 'struct', 'union', 'alternate',"
630 " 'command', 'event'")
633 check_name_is_str(expr
[meta
], info
, "'%s'" % meta
)
634 name
= cast(str, expr
[meta
])
635 info
.set_defn(meta
, name
)
636 check_defn_name_str(name
, info
, meta
)
639 if doc
.symbol
!= name
:
641 info
, "documentation comment is for '%s'" % doc
.symbol
)
643 elif info
.pragma
.doc_required
:
644 raise QAPISemError(info
,
645 "documentation comment required")
648 check_keys(expr
, info
, meta
,
649 ['enum', 'data'], ['if', 'features', 'prefix'])
651 elif meta
== 'union':
652 check_keys(expr
, info
, meta
,
653 ['union', 'base', 'discriminator', 'data'],
655 normalize_members(expr
.get('base'))
656 normalize_members(expr
['data'])
658 elif meta
== 'alternate':
659 check_keys(expr
, info
, meta
,
660 ['alternate', 'data'], ['if', 'features'])
661 normalize_members(expr
['data'])
662 check_alternate(expr
)
663 elif meta
== 'struct':
664 check_keys(expr
, info
, meta
,
665 ['struct', 'data'], ['base', 'if', 'features'])
666 normalize_members(expr
['data'])
668 elif meta
== 'command':
669 check_keys(expr
, info
, meta
,
671 ['data', 'returns', 'boxed', 'if', 'features',
672 'gen', 'success-response', 'allow-oob',
673 'allow-preconfig', 'coroutine'])
674 normalize_members(expr
.get('data'))
676 elif meta
== 'event':
677 check_keys(expr
, info
, meta
,
678 ['event'], ['data', 'boxed', 'if', 'features'])
679 normalize_members(expr
.get('data'))
682 assert False, 'unexpected meta type'
684 check_if(expr
, info
, meta
)
685 check_features(expr
.get('features'), info
)