xref: /openbmc/qemu/scripts/qapi/expr.py (revision 243975c0)
1# -*- coding: utf-8 -*-
2#
3# Copyright IBM, Corp. 2011
4# Copyright (c) 2013-2021 Red Hat Inc.
5#
6# Authors:
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>
12#
13# This work is licensed under the terms of the GNU GPL, version 2.
14# See the COPYING file in the top-level directory.
15
16"""
17Normalize and validate (context-free) QAPI schema expression structures.
18
19`QAPISchemaParser` parses a QAPI schema into abstract syntax trees
20consisting of dict, list, str, bool, and int nodes.  This module ensures
21that these nested structures have the correct type(s) and key(s) where
22appropriate for the QAPI context-free grammar.
23
24The QAPI schema expression language allows for certain syntactic sugar;
25this module also handles the normalization process of these nested
26structures.
27
28See `check_exprs` for the main entry point.
29
30See `schema.QAPISchema` for processing into native Python data
31structures and contextual semantic validation.
32"""
33
34import re
35from typing import (
36    Dict,
37    Iterable,
38    List,
39    Optional,
40    Union,
41    cast,
42)
43
44from .common import c_name
45from .error import QAPISemError
46from .parser import QAPIExpression
47from .source import QAPISourceInfo
48
49
50# See check_name_str(), below.
51valid_name = re.compile(r'(__[a-z0-9.-]+_)?'
52                        r'(x-)?'
53                        r'([a-z][a-z0-9_-]*)$', re.IGNORECASE)
54
55
56def check_name_is_str(name: object,
57                      info: QAPISourceInfo,
58                      source: str) -> None:
59    """
60    Ensure that ``name`` is a ``str``.
61
62    :raise QAPISemError: When ``name`` fails validation.
63    """
64    if not isinstance(name, str):
65        raise QAPISemError(info, "%s requires a string name" % source)
66
67
68def check_name_str(name: str, info: QAPISourceInfo, source: str) -> str:
69    """
70    Ensure that ``name`` is a valid QAPI name.
71
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.
76
77    A valid name cannot start with ``q_``, which is reserved.
78
79    :param name: Name to check.
80    :param info: QAPI schema source file information.
81    :param source: Error string describing what ``name`` belongs to.
82
83    :raise QAPISemError: When ``name`` fails validation.
84    :return: The stem of the valid name, with no prefixes.
85    """
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)
91    return match.group(3)
92
93
94def check_name_upper(name: str, info: QAPISourceInfo, source: str) -> None:
95    """
96    Ensure that ``name`` is a valid event name.
97
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 ``-``.
101
102    :param name: Name to check.
103    :param info: QAPI schema source file information.
104    :param source: Error string describing what ``name`` belongs to.
105
106    :raise QAPISemError: When ``name`` fails validation.
107    """
108    stem = check_name_str(name, info, source)
109    if re.search(r'[a-z-]', stem):
110        raise QAPISemError(
111            info, "name of %s must not use lowercase or '-'" % source)
112
113
114def check_name_lower(name: str, info: QAPISourceInfo, source: str,
115                     permit_upper: bool = False,
116                     permit_underscore: bool = False) -> None:
117    """
118    Ensure that ``name`` is a valid command or member name.
119
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 ``_``.
123
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 ``_``.
129
130    :raise QAPISemError: When ``name`` fails validation.
131    """
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)):
135        raise QAPISemError(
136            info, "name of %s must not use uppercase or '_'" % source)
137
138
139def check_name_camel(name: str, info: QAPISourceInfo, source: str) -> None:
140    """
141    Ensure that ``name`` is a valid user-defined type name.
142
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.
145
146    :param name: Name to check.
147    :param info: QAPI schema source file information.
148    :param source: Error string describing what ``name`` belongs to.
149
150    :raise QAPISemError: When ``name`` fails validation.
151    """
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)
155
156
157def check_defn_name_str(name: str, info: QAPISourceInfo, meta: str) -> None:
158    """
159    Ensure that ``name`` is a valid definition name.
160
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``.
166
167    :param name: Name to check.
168    :param info: QAPI schema source file information.
169    :param meta: Meta-type name of the QAPI expression.
170
171    :raise QAPISemError: When ``name`` fails validation.
172    """
173    if meta == 'event':
174        check_name_upper(name, info, meta)
175    elif meta == 'command':
176        check_name_lower(
177            name, info, meta,
178            permit_underscore=name in info.pragma.command_name_exceptions)
179    else:
180        check_name_camel(name, info, meta)
181        if name.endswith('List'):
182            raise QAPISemError(
183                info, "%s name should not end in 'List'" % meta)
184
185
186def check_keys(value: Dict[str, object],
187               info: QAPISourceInfo,
188               source: str,
189               required: List[str],
190               optional: List[str]) -> None:
191    """
192    Ensure that a dict has a specific set of keys.
193
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.
199
200    :raise QAPISemError: When unknown keys are present.
201    """
202
203    def pprint(elems: Iterable[str]) -> str:
204        return ', '.join("'" + e + "'" for e in sorted(elems))
205
206    missing = set(required) - set(value)
207    if missing:
208        raise QAPISemError(
209            info,
210            "%s misses key%s %s"
211            % (source, 's' if len(missing) > 1 else '',
212               pprint(missing)))
213    allowed = set(required) | set(optional)
214    unknown = set(value) - allowed
215    if unknown:
216        raise QAPISemError(
217            info,
218            "%s has unknown key%s %s\nValid keys are %s."
219            % (source, 's' if len(unknown) > 1 else '',
220               pprint(unknown), pprint(allowed)))
221
222
223def check_flags(expr: QAPIExpression) -> None:
224    """
225    Ensure flag members (if present) have valid values.
226
227    :param expr: The expression to validate.
228
229    :raise QAPISemError:
230        When certain flags have an invalid value, or when
231        incompatible flags are present.
232    """
233    for key in ('gen', 'success-response'):
234        if key in expr and expr[key] is not False:
235            raise QAPISemError(
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:
239            raise QAPISemError(
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
245        # a use case for it.
246        raise QAPISemError(
247            expr.info, "flags 'allow-oob' and 'coroutine' are incompatible")
248
249
250def check_if(expr: Dict[str, object],
251             info: QAPISourceInfo, source: str) -> None:
252    """
253    Validate the ``if`` member of an object.
254
255    The ``if`` member may be either a ``str`` or a dict.
256
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``.
260
261    :raise QAPISemError:
262        When the "if" member fails validation, or when there are no
263        non-empty conditions.
264    :return: None
265    """
266
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):
270                raise QAPISemError(
271                    info,
272                    "'if' condition '%s' of %s is not a valid identifier"
273                    % (cond, source))
274            return
275
276        if not isinstance(cond, dict):
277            raise QAPISemError(
278                info,
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"])
282        if len(cond) != 1:
283            raise QAPISemError(
284                info,
285                "'if' condition of %s has conflicting keys" % source)
286
287        if 'not' in cond:
288            _check_if(cond['not'])
289        elif 'all' in cond:
290            _check_infix('all', cond['all'])
291        else:
292            _check_infix('any', cond['any'])
293
294    def _check_infix(operator: str, operands: object) -> None:
295        if not isinstance(operands, list):
296            raise QAPISemError(
297                info,
298                "'%s' condition of %s must be an array"
299                % (operator, source))
300        if not operands:
301            raise QAPISemError(
302                info, "'if' condition [] of %s is useless" % source)
303        for operand in operands:
304            _check_if(operand)
305
306    ifcond = expr.get('if')
307    if ifcond is None:
308        return
309
310    _check_if(ifcond)
311
312
313def normalize_members(members: object) -> None:
314    """
315    Normalize a "members" value.
316
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
319    ``{'type': value}``.
320
321    :forms:
322      :sugared: ``Dict[str, Union[str, TypeRef]]``
323      :canonical: ``Dict[str, TypeRef]``
324
325    :param members: The members value to normalize.
326
327    :return: None, ``members`` is normalized in-place as needed.
328    """
329    if isinstance(members, dict):
330        for key, arg in members.items():
331            if isinstance(arg, dict):
332                continue
333            members[key] = {'type': arg}
334
335
336def check_type_name(value: Optional[object],
337                    info: QAPISourceInfo, source: str) -> None:
338    if value is not None and not isinstance(value, str):
339        raise QAPISemError(info, "%s should be a type name" % source)
340
341
342def check_type_name_or_array(value: Optional[object],
343                             info: QAPISourceInfo, source: str) -> None:
344    if value is None or isinstance(value, str):
345        return
346
347    if not isinstance(value, list):
348        raise QAPISemError(info,
349                           "%s should be a type name or array" % source)
350
351    if len(value) != 1 or not isinstance(value[0], str):
352        raise QAPISemError(info,
353                           "%s: array type must contain single type name" %
354                           source)
355
356
357def check_type_implicit(value: Optional[object],
358                        info: QAPISourceInfo, source: str,
359                        parent_name: Optional[str]) -> None:
360    """
361    Normalize and validate an optional implicit struct type.
362
363    Accept ``None`` or a ``dict`` defining an implicit struct type.
364    The latter is normalized in place.
365
366    :param value: The value to check.
367    :param info: QAPI schema source file information.
368    :param source: Error string describing this ``value``.
369    :param parent_name:
370        When the value of ``parent_name`` is in pragma
371        ``member-name-exceptions``, an implicit struct type may
372        violate the member naming rules.
373
374    :raise QAPISemError: When ``value`` fails validation.
375    :return: None
376    """
377    if value is None:
378        return
379
380    if not isinstance(value, dict):
381        raise QAPISemError(info,
382                           "%s should be an object or type name" % source)
383
384    permissive = parent_name in info.pragma.member_name_exceptions
385
386    for (key, arg) in value.items():
387        key_source = "%s member '%s'" % (source, key)
388        if key.startswith('*'):
389            key = key[1:]
390        check_name_lower(key, info, key_source,
391                         permit_upper=permissive,
392                         permit_underscore=permissive)
393        if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
394            raise QAPISemError(info, "%s uses reserved name" % key_source)
395        check_keys(arg, info, key_source, ['type'], ['if', 'features'])
396        check_if(arg, info, key_source)
397        check_features(arg.get('features'), info)
398        check_type_name_or_array(arg['type'], info, key_source)
399
400
401def check_type_name_or_implicit(value: Optional[object],
402                                info: QAPISourceInfo, source: str,
403                                parent_name: Optional[str]) -> None:
404    if value is None or isinstance(value, str):
405        return
406
407    check_type_implicit(value, info, source, parent_name)
408
409
410def check_features(features: Optional[object],
411                   info: QAPISourceInfo) -> None:
412    """
413    Normalize and validate the ``features`` member.
414
415    ``features`` may be a ``list`` of either ``str`` or ``dict``.
416    Any ``str`` element will be normalized to ``{'name': element}``.
417
418    :forms:
419      :sugared: ``List[Union[str, Feature]]``
420      :canonical: ``List[Feature]``
421
422    :param features: The features member value to validate.
423    :param info: QAPI schema source file information.
424
425    :raise QAPISemError: When ``features`` fails validation.
426    :return: None, ``features`` is normalized in-place as needed.
427    """
428    if features is None:
429        return
430    if not isinstance(features, list):
431        raise QAPISemError(info, "'features' must be an array")
432    features[:] = [f if isinstance(f, dict) else {'name': f}
433                   for f in features]
434    for feat in features:
435        source = "'features' member"
436        assert isinstance(feat, dict)
437        check_keys(feat, info, source, ['name'], ['if'])
438        check_name_is_str(feat['name'], info, source)
439        source = "%s '%s'" % (source, feat['name'])
440        check_name_lower(feat['name'], info, source)
441        check_if(feat, info, source)
442
443
444def check_enum(expr: QAPIExpression) -> None:
445    """
446    Normalize and validate this expression as an ``enum`` definition.
447
448    :param expr: The expression to validate.
449
450    :raise QAPISemError: When ``expr`` is not a valid ``enum``.
451    :return: None, ``expr`` is normalized in-place as needed.
452    """
453    name = expr['enum']
454    members = expr['data']
455    prefix = expr.get('prefix')
456    info = expr.info
457
458    if not isinstance(members, list):
459        raise QAPISemError(info, "'data' must be an array")
460    if prefix is not None and not isinstance(prefix, str):
461        raise QAPISemError(info, "'prefix' must be a string")
462
463    permissive = name in info.pragma.member_name_exceptions
464
465    members[:] = [m if isinstance(m, dict) else {'name': m}
466                  for m in members]
467    for member in members:
468        source = "'data' member"
469        check_keys(member, info, source, ['name'], ['if', 'features'])
470        member_name = member['name']
471        check_name_is_str(member_name, info, source)
472        source = "%s '%s'" % (source, member_name)
473        # Enum members may start with a digit
474        if member_name[0].isdigit():
475            member_name = 'd' + member_name  # Hack: hide the digit
476        check_name_lower(member_name, info, source,
477                         permit_upper=permissive,
478                         permit_underscore=permissive)
479        check_if(member, info, source)
480        check_features(member.get('features'), info)
481
482
483def check_struct(expr: QAPIExpression) -> None:
484    """
485    Normalize and validate this expression as a ``struct`` definition.
486
487    :param expr: The expression to validate.
488
489    :raise QAPISemError: When ``expr`` is not a valid ``struct``.
490    :return: None, ``expr`` is normalized in-place as needed.
491    """
492    name = cast(str, expr['struct'])  # Checked in check_exprs
493    members = expr['data']
494
495    check_type_implicit(members, expr.info, "'data'", name)
496    check_type_name(expr.get('base'), expr.info, "'base'")
497
498
499def check_union(expr: QAPIExpression) -> None:
500    """
501    Normalize and validate this expression as a ``union`` definition.
502
503    :param expr: The expression to validate.
504
505    :raise QAPISemError: when ``expr`` is not a valid ``union``.
506    :return: None, ``expr`` is normalized in-place as needed.
507    """
508    name = cast(str, expr['union'])  # Checked in check_exprs
509    base = expr['base']
510    discriminator = expr['discriminator']
511    members = expr['data']
512    info = expr.info
513
514    check_type_name_or_implicit(base, info, "'base'", name)
515    check_name_is_str(discriminator, info, "'discriminator'")
516
517    if not isinstance(members, dict):
518        raise QAPISemError(info, "'data' must be an object")
519
520    for (key, value) in members.items():
521        source = "'data' member '%s'" % key
522        check_keys(value, info, source, ['type'], ['if'])
523        check_if(value, info, source)
524        check_type_name(value['type'], info, source)
525
526
527def check_alternate(expr: QAPIExpression) -> None:
528    """
529    Normalize and validate this expression as an ``alternate`` definition.
530
531    :param expr: The expression to validate.
532
533    :raise QAPISemError: When ``expr`` is not a valid ``alternate``.
534    :return: None, ``expr`` is normalized in-place as needed.
535    """
536    members = expr['data']
537    info = expr.info
538
539    if not members:
540        raise QAPISemError(info, "'data' must not be empty")
541
542    if not isinstance(members, dict):
543        raise QAPISemError(info, "'data' must be an object")
544
545    for (key, value) in members.items():
546        source = "'data' member '%s'" % key
547        check_name_lower(key, info, source)
548        check_keys(value, info, source, ['type'], ['if'])
549        check_if(value, info, source)
550        check_type_name_or_array(value['type'], info, source)
551
552
553def check_command(expr: QAPIExpression) -> None:
554    """
555    Normalize and validate this expression as a ``command`` definition.
556
557    :param expr: The expression to validate.
558
559    :raise QAPISemError: When ``expr`` is not a valid ``command``.
560    :return: None, ``expr`` is normalized in-place as needed.
561    """
562    args = expr.get('data')
563    rets = expr.get('returns')
564    boxed = expr.get('boxed', False)
565
566    if boxed:
567        if args is None:
568            raise QAPISemError(expr.info, "'boxed': true requires 'data'")
569        check_type_name(args, expr.info, "'data'")
570    else:
571        check_type_name_or_implicit(args, expr.info, "'data'", None)
572    check_type_name_or_array(rets, expr.info, "'returns'")
573
574
575def check_event(expr: QAPIExpression) -> None:
576    """
577    Normalize and validate this expression as an ``event`` definition.
578
579    :param expr: The expression to validate.
580
581    :raise QAPISemError: When ``expr`` is not a valid ``event``.
582    :return: None, ``expr`` is normalized in-place as needed.
583    """
584    args = expr.get('data')
585    boxed = expr.get('boxed', False)
586
587    if boxed:
588        if args is None:
589            raise QAPISemError(expr.info, "'boxed': true requires 'data'")
590        check_type_name(args, expr.info, "'data'")
591    else:
592        check_type_name_or_implicit(args, expr.info, "'data'", None)
593
594
595def check_exprs(exprs: List[QAPIExpression]) -> List[QAPIExpression]:
596    """
597    Validate and normalize a list of parsed QAPI schema expressions.
598
599    This function accepts a list of expressions and metadata as returned
600    by the parser.  It destructively normalizes the expressions in-place.
601
602    :param exprs: The list of expressions to normalize and validate.
603
604    :raise QAPISemError: When any expression fails validation.
605    :return: The same list of expressions (now modified).
606    """
607    for expr in exprs:
608        info = expr.info
609        doc = expr.doc
610
611        if 'include' in expr:
612            continue
613
614        metas = expr.keys() & {'enum', 'struct', 'union', 'alternate',
615                               'command', 'event'}
616        if len(metas) != 1:
617            raise QAPISemError(
618                info,
619                "expression must have exactly one key"
620                " 'enum', 'struct', 'union', 'alternate',"
621                " 'command', 'event'")
622        meta = metas.pop()
623
624        check_name_is_str(expr[meta], info, "'%s'" % meta)
625        name = cast(str, expr[meta])
626        info.set_defn(meta, name)
627        check_defn_name_str(name, info, meta)
628
629        if doc:
630            if doc.symbol != name:
631                raise QAPISemError(
632                    info, "documentation comment is for '%s'" % doc.symbol)
633            doc.check_expr(expr)
634        elif info.pragma.doc_required:
635            raise QAPISemError(info,
636                               "documentation comment required")
637
638        if meta == 'enum':
639            check_keys(expr, info, meta,
640                       ['enum', 'data'], ['if', 'features', 'prefix'])
641            check_enum(expr)
642        elif meta == 'union':
643            check_keys(expr, info, meta,
644                       ['union', 'base', 'discriminator', 'data'],
645                       ['if', 'features'])
646            normalize_members(expr.get('base'))
647            normalize_members(expr['data'])
648            check_union(expr)
649        elif meta == 'alternate':
650            check_keys(expr, info, meta,
651                       ['alternate', 'data'], ['if', 'features'])
652            normalize_members(expr['data'])
653            check_alternate(expr)
654        elif meta == 'struct':
655            check_keys(expr, info, meta,
656                       ['struct', 'data'], ['base', 'if', 'features'])
657            normalize_members(expr['data'])
658            check_struct(expr)
659        elif meta == 'command':
660            check_keys(expr, info, meta,
661                       ['command'],
662                       ['data', 'returns', 'boxed', 'if', 'features',
663                        'gen', 'success-response', 'allow-oob',
664                        'allow-preconfig', 'coroutine'])
665            normalize_members(expr.get('data'))
666            check_command(expr)
667        elif meta == 'event':
668            check_keys(expr, info, meta,
669                       ['event'], ['data', 'boxed', 'if', 'features'])
670            normalize_members(expr.get('data'))
671            check_event(expr)
672        else:
673            assert False, 'unexpected meta type'
674
675        check_if(expr, info, meta)
676        check_features(expr.get('features'), info)
677        check_flags(expr)
678
679    return exprs
680