xref: /openbmc/qemu/scripts/qapi/expr.py (revision c3f9aa8e488db330197c9217e38555f6772e8f07)
1e6c42b96SMarkus Armbruster# -*- coding: utf-8 -*-
2e6c42b96SMarkus Armbruster#
3e6c42b96SMarkus Armbruster# Copyright IBM, Corp. 2011
4e81718c6SJohn Snow# Copyright (c) 2013-2021 Red Hat Inc.
5e6c42b96SMarkus Armbruster#
6e6c42b96SMarkus Armbruster# Authors:
7e6c42b96SMarkus Armbruster#  Anthony Liguori <aliguori@us.ibm.com>
8e6c42b96SMarkus Armbruster#  Markus Armbruster <armbru@redhat.com>
9e6c42b96SMarkus Armbruster#  Eric Blake <eblake@redhat.com>
10e6c42b96SMarkus Armbruster#  Marc-André Lureau <marcandre.lureau@redhat.com>
11e81718c6SJohn Snow#  John Snow <jsnow@redhat.com>
12e6c42b96SMarkus Armbruster#
13e6c42b96SMarkus Armbruster# This work is licensed under the terms of the GNU GPL, version 2.
14e6c42b96SMarkus Armbruster# See the COPYING file in the top-level directory.
15e6c42b96SMarkus Armbruster
16a4865363SJohn Snow"""
17a4865363SJohn SnowNormalize and validate (context-free) QAPI schema expression structures.
18a4865363SJohn Snow
19a4865363SJohn Snow`QAPISchemaParser` parses a QAPI schema into abstract syntax trees
20a4865363SJohn Snowconsisting of dict, list, str, bool, and int nodes.  This module ensures
21a4865363SJohn Snowthat these nested structures have the correct type(s) and key(s) where
22a4865363SJohn Snowappropriate for the QAPI context-free grammar.
23a4865363SJohn Snow
24a4865363SJohn SnowThe QAPI schema expression language allows for certain syntactic sugar;
25a4865363SJohn Snowthis module also handles the normalization process of these nested
26a4865363SJohn Snowstructures.
27a4865363SJohn Snow
28a4865363SJohn SnowSee `check_exprs` for the main entry point.
29a4865363SJohn Snow
30a4865363SJohn SnowSee `schema.QAPISchema` for processing into native Python data
31a4865363SJohn Snowstructures and contextual semantic validation.
32a4865363SJohn Snow"""
33a4865363SJohn Snow
3467fea575SJohn Snowimport re
35b9ad358aSJohn Snowfrom typing import (
36b9ad358aSJohn Snow    Dict,
37b9ad358aSJohn Snow    Iterable,
38b9ad358aSJohn Snow    List,
39b9ad358aSJohn Snow    Optional,
40b9ad358aSJohn Snow    Union,
41b9ad358aSJohn Snow    cast,
42b9ad358aSJohn Snow)
4367fea575SJohn Snow
447137a960SJohn Snowfrom .common import c_name
457137a960SJohn Snowfrom .error import QAPISemError
4642011059SJohn Snowfrom .parser import QAPIExpression
4759b5556cSJohn Snowfrom .source import QAPISourceInfo
4859b5556cSJohn Snow
4959b5556cSJohn Snow
50a4865363SJohn Snow# See check_name_str(), below.
51eaab06faSMarkus Armbrustervalid_name = re.compile(r'(__[a-z0-9.-]+_)?'
52eaab06faSMarkus Armbruster                        r'(x-)?'
53eaab06faSMarkus Armbruster                        r'([a-z][a-z0-9_-]*)$', re.IGNORECASE)
54e6c42b96SMarkus Armbruster
55e6c42b96SMarkus Armbruster
56b9ad358aSJohn Snowdef check_name_is_str(name: object,
57b9ad358aSJohn Snow                      info: QAPISourceInfo,
58b9ad358aSJohn Snow                      source: str) -> None:
59a4865363SJohn Snow    """
60a4865363SJohn Snow    Ensure that ``name`` is a ``str``.
61a4865363SJohn Snow
62a4865363SJohn Snow    :raise QAPISemError: When ``name`` fails validation.
63a4865363SJohn Snow    """
64e6c42b96SMarkus Armbruster    if not isinstance(name, str):
65e6c42b96SMarkus Armbruster        raise QAPISemError(info, "%s requires a string name" % source)
66e6c42b96SMarkus Armbruster
67e6c42b96SMarkus Armbruster
68b9ad358aSJohn Snowdef check_name_str(name: str, info: QAPISourceInfo, source: str) -> str:
69a4865363SJohn Snow    """
70a4865363SJohn Snow    Ensure that ``name`` is a valid QAPI name.
71a4865363SJohn Snow
72a4865363SJohn Snow    A valid name consists of ASCII letters, digits, ``-``, and ``_``,
73a4865363SJohn Snow    starting with a letter.  It may be prefixed by a downstream prefix
74a4865363SJohn Snow    of the form __RFQDN_, or the experimental prefix ``x-``.  If both
75a4865363SJohn Snow    prefixes are present, the __RFDQN_ prefix goes first.
76a4865363SJohn Snow
77a4865363SJohn Snow    A valid name cannot start with ``q_``, which is reserved.
78a4865363SJohn Snow
79a4865363SJohn Snow    :param name: Name to check.
80a4865363SJohn Snow    :param info: QAPI schema source file information.
81a4865363SJohn Snow    :param source: Error string describing what ``name`` belongs to.
82a4865363SJohn Snow
83a4865363SJohn Snow    :raise QAPISemError: When ``name`` fails validation.
84a4865363SJohn Snow    :return: The stem of the valid name, with no prefixes.
85a4865363SJohn Snow    """
86e6c42b96SMarkus Armbruster    # Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
87e6c42b96SMarkus Armbruster    # and 'q_obj_*' implicit type names.
88eaab06faSMarkus Armbruster    match = valid_name.match(name)
89eaab06faSMarkus Armbruster    if not match or c_name(name, False).startswith('q_'):
90e6c42b96SMarkus Armbruster        raise QAPISemError(info, "%s has an invalid name" % source)
91eaab06faSMarkus Armbruster    return match.group(3)
92eaab06faSMarkus Armbruster
93eaab06faSMarkus Armbruster
94b9ad358aSJohn Snowdef check_name_upper(name: str, info: QAPISourceInfo, source: str) -> None:
95a4865363SJohn Snow    """
96a4865363SJohn Snow    Ensure that ``name`` is a valid event name.
97a4865363SJohn Snow
98a4865363SJohn Snow    This means it must be a valid QAPI name as checked by
99a4865363SJohn Snow    `check_name_str()`, but where the stem prohibits lowercase
100a4865363SJohn Snow    characters and ``-``.
101a4865363SJohn Snow
102a4865363SJohn Snow    :param name: Name to check.
103a4865363SJohn Snow    :param info: QAPI schema source file information.
104a4865363SJohn Snow    :param source: Error string describing what ``name`` belongs to.
105a4865363SJohn Snow
106a4865363SJohn Snow    :raise QAPISemError: When ``name`` fails validation.
107a4865363SJohn Snow    """
108d224e0c0SMarkus Armbruster    stem = check_name_str(name, info, source)
109d4f4cae8SMarkus Armbruster    if re.search(r'[a-z-]', stem):
110d4f4cae8SMarkus Armbruster        raise QAPISemError(
111d4f4cae8SMarkus Armbruster            info, "name of %s must not use lowercase or '-'" % source)
112eaab06faSMarkus Armbruster
113eaab06faSMarkus Armbruster
114b9ad358aSJohn Snowdef check_name_lower(name: str, info: QAPISourceInfo, source: str,
115b9ad358aSJohn Snow                     permit_upper: bool = False,
116b9ad358aSJohn Snow                     permit_underscore: bool = False) -> None:
117a4865363SJohn Snow    """
118a4865363SJohn Snow    Ensure that ``name`` is a valid command or member name.
119a4865363SJohn Snow
120a4865363SJohn Snow    This means it must be a valid QAPI name as checked by
121a4865363SJohn Snow    `check_name_str()`, but where the stem prohibits uppercase
122a4865363SJohn Snow    characters and ``_``.
123a4865363SJohn Snow
124a4865363SJohn Snow    :param name: Name to check.
125a4865363SJohn Snow    :param info: QAPI schema source file information.
126a4865363SJohn Snow    :param source: Error string describing what ``name`` belongs to.
127a4865363SJohn Snow    :param permit_upper: Additionally permit uppercase.
128a4865363SJohn Snow    :param permit_underscore: Additionally permit ``_``.
129a4865363SJohn Snow
130a4865363SJohn Snow    :raise QAPISemError: When ``name`` fails validation.
131a4865363SJohn Snow    """
132d224e0c0SMarkus Armbruster    stem = check_name_str(name, info, source)
1339af4b6b9SMarkus Armbruster    if ((not permit_upper and re.search(r'[A-Z]', stem))
1349af4b6b9SMarkus Armbruster            or (not permit_underscore and '_' in stem)):
135d224e0c0SMarkus Armbruster        raise QAPISemError(
1369af4b6b9SMarkus Armbruster            info, "name of %s must not use uppercase or '_'" % source)
137eaab06faSMarkus Armbruster
138eaab06faSMarkus Armbruster
139b9ad358aSJohn Snowdef check_name_camel(name: str, info: QAPISourceInfo, source: str) -> None:
140a4865363SJohn Snow    """
141a4865363SJohn Snow    Ensure that ``name`` is a valid user-defined type name.
142a4865363SJohn Snow
143a4865363SJohn Snow    This means it must be a valid QAPI name as checked by
144a4865363SJohn Snow    `check_name_str()`, but where the stem must be in CamelCase.
145a4865363SJohn Snow
146a4865363SJohn Snow    :param name: Name to check.
147a4865363SJohn Snow    :param info: QAPI schema source file information.
148a4865363SJohn Snow    :param source: Error string describing what ``name`` belongs to.
149a4865363SJohn Snow
150a4865363SJohn Snow    :raise QAPISemError: When ``name`` fails validation.
151a4865363SJohn Snow    """
152d224e0c0SMarkus Armbruster    stem = check_name_str(name, info, source)
1533e6c8a63SMarkus Armbruster    if not re.match(r'[A-Z][A-Za-z0-9]*[a-z][A-Za-z0-9]*$', stem):
1543e6c8a63SMarkus Armbruster        raise QAPISemError(info, "name of %s must use CamelCase" % source)
155e6c42b96SMarkus Armbruster
156e6c42b96SMarkus Armbruster
157b9ad358aSJohn Snowdef check_defn_name_str(name: str, info: QAPISourceInfo, meta: str) -> None:
158a4865363SJohn Snow    """
159a4865363SJohn Snow    Ensure that ``name`` is a valid definition name.
160a4865363SJohn Snow
161a4865363SJohn Snow    Based on the value of ``meta``, this means that:
162a4865363SJohn Snow      - 'event' names adhere to `check_name_upper()`.
163a4865363SJohn Snow      - 'command' names adhere to `check_name_lower()`.
164a4865363SJohn Snow      - Else, meta is a type, and must pass `check_name_camel()`.
1658ebc3120SMarkus Armbruster        These names must not end with ``List``.
166a4865363SJohn Snow
167a4865363SJohn Snow    :param name: Name to check.
168a4865363SJohn Snow    :param info: QAPI schema source file information.
169a4865363SJohn Snow    :param meta: Meta-type name of the QAPI expression.
170a4865363SJohn Snow
171a4865363SJohn Snow    :raise QAPISemError: When ``name`` fails validation.
172a4865363SJohn Snow    """
173eaab06faSMarkus Armbruster    if meta == 'event':
174eaab06faSMarkus Armbruster        check_name_upper(name, info, meta)
175eaab06faSMarkus Armbruster    elif meta == 'command':
17605ebf841SMarkus Armbruster        check_name_lower(
17705ebf841SMarkus Armbruster            name, info, meta,
17805ebf841SMarkus Armbruster            permit_underscore=name in info.pragma.command_name_exceptions)
179eaab06faSMarkus Armbruster    else:
180eaab06faSMarkus Armbruster        check_name_camel(name, info, meta)
1818ebc3120SMarkus Armbruster        if name.endswith('List'):
182e6c42b96SMarkus Armbruster            raise QAPISemError(
1838ebc3120SMarkus Armbruster                info, "%s name should not end in 'List'" % meta)
184e6c42b96SMarkus Armbruster
185e6c42b96SMarkus Armbruster
18667a81f9fSJohn Snowdef check_keys(value: Dict[str, object],
187b9ad358aSJohn Snow               info: QAPISourceInfo,
188b9ad358aSJohn Snow               source: str,
189c60caf80SJohn Snow               required: List[str],
190c60caf80SJohn Snow               optional: List[str]) -> None:
191a4865363SJohn Snow    """
192a4865363SJohn Snow    Ensure that a dict has a specific set of keys.
193a4865363SJohn Snow
194a4865363SJohn Snow    :param value: The dict to check.
195a4865363SJohn Snow    :param info: QAPI schema source file information.
196a4865363SJohn Snow    :param source: Error string describing this ``value``.
197a4865363SJohn Snow    :param required: Keys that *must* be present.
198a4865363SJohn Snow    :param optional: Keys that *may* be present.
199a4865363SJohn Snow
200a4865363SJohn Snow    :raise QAPISemError: When unknown keys are present.
201a4865363SJohn Snow    """
202e6c42b96SMarkus Armbruster
203b9ad358aSJohn Snow    def pprint(elems: Iterable[str]) -> str:
204e6c42b96SMarkus Armbruster        return ', '.join("'" + e + "'" for e in sorted(elems))
205e6c42b96SMarkus Armbruster
206e6c42b96SMarkus Armbruster    missing = set(required) - set(value)
207e6c42b96SMarkus Armbruster    if missing:
208e6c42b96SMarkus Armbruster        raise QAPISemError(
209e6c42b96SMarkus Armbruster            info,
210e6c42b96SMarkus Armbruster            "%s misses key%s %s"
211e6c42b96SMarkus Armbruster            % (source, 's' if len(missing) > 1 else '',
212e6c42b96SMarkus Armbruster               pprint(missing)))
213538cd410SJohn Snow    allowed = set(required) | set(optional)
214e6c42b96SMarkus Armbruster    unknown = set(value) - allowed
215e6c42b96SMarkus Armbruster    if unknown:
216e6c42b96SMarkus Armbruster        raise QAPISemError(
217e6c42b96SMarkus Armbruster            info,
218e6c42b96SMarkus Armbruster            "%s has unknown key%s %s\nValid keys are %s."
219e6c42b96SMarkus Armbruster            % (source, 's' if len(unknown) > 1 else '',
220e6c42b96SMarkus Armbruster               pprint(unknown), pprint(allowed)))
221e6c42b96SMarkus Armbruster
222e6c42b96SMarkus Armbruster
22342011059SJohn Snowdef check_flags(expr: QAPIExpression) -> None:
224a4865363SJohn Snow    """
225a4865363SJohn Snow    Ensure flag members (if present) have valid values.
226a4865363SJohn Snow
227a4865363SJohn Snow    :param expr: The expression to validate.
228a4865363SJohn Snow
229a4865363SJohn Snow    :raise QAPISemError:
230a4865363SJohn Snow        When certain flags have an invalid value, or when
231a4865363SJohn Snow        incompatible flags are present.
232a4865363SJohn Snow    """
233eab99939SJohn Snow    for key in ('gen', 'success-response'):
234e6c42b96SMarkus Armbruster        if key in expr and expr[key] is not False:
235e6c42b96SMarkus Armbruster            raise QAPISemError(
23642011059SJohn Snow                expr.info, "flag '%s' may only use false value" % key)
237eab99939SJohn Snow    for key in ('boxed', 'allow-oob', 'allow-preconfig', 'coroutine'):
238e6c42b96SMarkus Armbruster        if key in expr and expr[key] is not True:
239e6c42b96SMarkus Armbruster            raise QAPISemError(
24042011059SJohn Snow                expr.info, "flag '%s' may only use true value" % key)
24104f22362SKevin Wolf    if 'allow-oob' in expr and 'coroutine' in expr:
24204f22362SKevin Wolf        # This is not necessarily a fundamental incompatibility, but
24304f22362SKevin Wolf        # we don't have a use case and the desired semantics isn't
24404f22362SKevin Wolf        # obvious.  The simplest solution is to forbid it until we get
24504f22362SKevin Wolf        # a use case for it.
24642011059SJohn Snow        raise QAPISemError(
24742011059SJohn Snow            expr.info, "flags 'allow-oob' and 'coroutine' are incompatible")
248e6c42b96SMarkus Armbruster
249e6c42b96SMarkus Armbruster
25067a81f9fSJohn Snowdef check_if(expr: Dict[str, object],
25167a81f9fSJohn Snow             info: QAPISourceInfo, source: str) -> None:
252a4865363SJohn Snow    """
2535d83b9a1SMarc-André Lureau    Validate the ``if`` member of an object.
254e6c42b96SMarkus Armbruster
2555d83b9a1SMarc-André Lureau    The ``if`` member may be either a ``str`` or a dict.
256a4865363SJohn Snow
257a4865363SJohn Snow    :param expr: The expression containing the ``if`` member to validate.
258a4865363SJohn Snow    :param info: QAPI schema source file information.
259a4865363SJohn Snow    :param source: Error string describing ``expr``.
260a4865363SJohn Snow
261a4865363SJohn Snow    :raise QAPISemError:
262a4865363SJohn Snow        When the "if" member fails validation, or when there are no
263a4865363SJohn Snow        non-empty conditions.
2645d83b9a1SMarc-André Lureau    :return: None
265a4865363SJohn Snow    """
2665d83b9a1SMarc-André Lureau
2675d83b9a1SMarc-André Lureau    def _check_if(cond: Union[str, object]) -> None:
2685d83b9a1SMarc-André Lureau        if isinstance(cond, str):
269555dd1aaSMarkus Armbruster            if not re.fullmatch(r'[A-Z][A-Z0-9_]*', cond):
2705d83b9a1SMarc-André Lureau                raise QAPISemError(
2715d83b9a1SMarc-André Lureau                    info,
2728a9f1e1dSMarc-André Lureau                    "'if' condition '%s' of %s is not a valid identifier"
2735d83b9a1SMarc-André Lureau                    % (cond, source))
2745d83b9a1SMarc-André Lureau            return
2755d83b9a1SMarc-André Lureau
2765d83b9a1SMarc-André Lureau        if not isinstance(cond, dict):
2775d83b9a1SMarc-André Lureau            raise QAPISemError(
2785d83b9a1SMarc-André Lureau                info,
2795d83b9a1SMarc-André Lureau                "'if' condition of %s must be a string or an object" % source)
28034f7b25eSMarkus Armbruster        check_keys(cond, info, "'if' condition of %s" % source, [],
28134f7b25eSMarkus Armbruster                   ["all", "any", "not"])
2825d83b9a1SMarc-André Lureau        if len(cond) != 1:
2835d83b9a1SMarc-André Lureau            raise QAPISemError(
2845d83b9a1SMarc-André Lureau                info,
28534f7b25eSMarkus Armbruster                "'if' condition of %s has conflicting keys" % source)
2865d83b9a1SMarc-André Lureau
28762f27589SMarkus Armbruster        if 'not' in cond:
28862f27589SMarkus Armbruster            _check_if(cond['not'])
28962f27589SMarkus Armbruster        elif 'all' in cond:
29062f27589SMarkus Armbruster            _check_infix('all', cond['all'])
29162f27589SMarkus Armbruster        else:
29262f27589SMarkus Armbruster            _check_infix('any', cond['any'])
29362f27589SMarkus Armbruster
29462f27589SMarkus Armbruster    def _check_infix(operator: str, operands: object) -> None:
29562f27589SMarkus Armbruster        if not isinstance(operands, list):
29662f27589SMarkus Armbruster            raise QAPISemError(
29762f27589SMarkus Armbruster                info,
29862f27589SMarkus Armbruster                "'%s' condition of %s must be an array"
29962f27589SMarkus Armbruster                % (operator, source))
3005d83b9a1SMarc-André Lureau        if not operands:
3015d83b9a1SMarc-André Lureau            raise QAPISemError(
3025d83b9a1SMarc-André Lureau                info, "'if' condition [] of %s is useless" % source)
3035d83b9a1SMarc-André Lureau        for operand in operands:
3045d83b9a1SMarc-André Lureau            _check_if(operand)
3055d83b9a1SMarc-André Lureau
306210fd631SJohn Snow    ifcond = expr.get('if')
307210fd631SJohn Snow    if ifcond is None:
308210fd631SJohn Snow        return
309210fd631SJohn Snow
3105d83b9a1SMarc-André Lureau    _check_if(ifcond)
311e6c42b96SMarkus Armbruster
312e6c42b96SMarkus Armbruster
313b9ad358aSJohn Snowdef normalize_members(members: object) -> None:
314a4865363SJohn Snow    """
315a4865363SJohn Snow    Normalize a "members" value.
316a4865363SJohn Snow
317a4865363SJohn Snow    If ``members`` is a dict, for every value in that dict, if that
318a4865363SJohn Snow    value is not itself already a dict, normalize it to
319a4865363SJohn Snow    ``{'type': value}``.
320a4865363SJohn Snow
321a4865363SJohn Snow    :forms:
322a4865363SJohn Snow      :sugared: ``Dict[str, Union[str, TypeRef]]``
323a4865363SJohn Snow      :canonical: ``Dict[str, TypeRef]``
324a4865363SJohn Snow
325a4865363SJohn Snow    :param members: The members value to normalize.
326a4865363SJohn Snow
327a4865363SJohn Snow    :return: None, ``members`` is normalized in-place as needed.
328a4865363SJohn Snow    """
3290f231dcfSJohn Snow    if isinstance(members, dict):
330e6c42b96SMarkus Armbruster        for key, arg in members.items():
331e6c42b96SMarkus Armbruster            if isinstance(arg, dict):
332e6c42b96SMarkus Armbruster                continue
333e6c42b96SMarkus Armbruster            members[key] = {'type': arg}
334e6c42b96SMarkus Armbruster
335e6c42b96SMarkus Armbruster
33606cc46eeSMarkus Armbrusterdef check_type_name(value: Optional[object],
33706cc46eeSMarkus Armbruster                    info: QAPISourceInfo, source: str) -> None:
3387c407519SMarkus Armbruster    if value is not None and not isinstance(value, str):
33906cc46eeSMarkus Armbruster        raise QAPISemError(info, "%s should be a type name" % source)
34006cc46eeSMarkus Armbruster
34106cc46eeSMarkus Armbruster
34206cc46eeSMarkus Armbrusterdef check_type_name_or_array(value: Optional[object],
34306cc46eeSMarkus Armbruster                             info: QAPISourceInfo, source: str) -> None:
3447c407519SMarkus Armbruster    if value is None or isinstance(value, str):
34506cc46eeSMarkus Armbruster        return
34606cc46eeSMarkus Armbruster
3476f2ab609SMarkus Armbruster    if not isinstance(value, list):
3486f2ab609SMarkus Armbruster        raise QAPISemError(info,
3496f2ab609SMarkus Armbruster                           "%s should be a type name or array" % source)
3506f2ab609SMarkus Armbruster
351e6c42b96SMarkus Armbruster    if len(value) != 1 or not isinstance(value[0], str):
352e6c42b96SMarkus Armbruster        raise QAPISemError(info,
353e6c42b96SMarkus Armbruster                           "%s: array type must contain single type name" %
354e6c42b96SMarkus Armbruster                           source)
355e6c42b96SMarkus Armbruster
35606cc46eeSMarkus Armbruster
357*e2050ef6SMarkus Armbrusterdef check_type_implicit(value: Optional[object],
35806cc46eeSMarkus Armbruster                        info: QAPISourceInfo, source: str,
35906cc46eeSMarkus Armbruster                        parent_name: Optional[str]) -> None:
36006cc46eeSMarkus Armbruster    """
36106cc46eeSMarkus Armbruster    Normalize and validate an optional implicit struct type.
36206cc46eeSMarkus Armbruster
363*e2050ef6SMarkus Armbruster    Accept ``None`` or a ``dict`` defining an implicit struct type.
364*e2050ef6SMarkus Armbruster    The latter is normalized in place.
36506cc46eeSMarkus Armbruster
36606cc46eeSMarkus Armbruster    :param value: The value to check.
36706cc46eeSMarkus Armbruster    :param info: QAPI schema source file information.
36806cc46eeSMarkus Armbruster    :param source: Error string describing this ``value``.
36906cc46eeSMarkus Armbruster    :param parent_name:
37006cc46eeSMarkus Armbruster        When the value of ``parent_name`` is in pragma
37106cc46eeSMarkus Armbruster        ``member-name-exceptions``, an implicit struct type may
37206cc46eeSMarkus Armbruster        violate the member naming rules.
37306cc46eeSMarkus Armbruster
37406cc46eeSMarkus Armbruster    :raise QAPISemError: When ``value`` fails validation.
37506cc46eeSMarkus Armbruster    :return: None
37606cc46eeSMarkus Armbruster    """
37706cc46eeSMarkus Armbruster    if value is None:
37806cc46eeSMarkus Armbruster        return
37906cc46eeSMarkus Armbruster
3800f231dcfSJohn Snow    if not isinstance(value, dict):
381e6c42b96SMarkus Armbruster        raise QAPISemError(info,
382e6c42b96SMarkus Armbruster                           "%s should be an object or type name" % source)
383e6c42b96SMarkus Armbruster
38406cc46eeSMarkus Armbruster    permissive = parent_name in info.pragma.member_name_exceptions
385e6c42b96SMarkus Armbruster
386e6c42b96SMarkus Armbruster    for (key, arg) in value.items():
387e6c42b96SMarkus Armbruster        key_source = "%s member '%s'" % (source, key)
388dbfe3c7cSMarkus Armbruster        if key.startswith('*'):
389dbfe3c7cSMarkus Armbruster            key = key[1:]
3909af4b6b9SMarkus Armbruster        check_name_lower(key, info, key_source,
3915aceeac0SMarkus Armbruster                         permit_upper=permissive,
3925aceeac0SMarkus Armbruster                         permit_underscore=permissive)
393e6c42b96SMarkus Armbruster        if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
394e6c42b96SMarkus Armbruster            raise QAPISemError(info, "%s uses reserved name" % key_source)
39584ab0086SMarkus Armbruster        check_keys(arg, info, key_source, ['type'], ['if', 'features'])
396e6c42b96SMarkus Armbruster        check_if(arg, info, key_source)
39784ab0086SMarkus Armbruster        check_features(arg.get('features'), info)
39806cc46eeSMarkus Armbruster        check_type_name_or_array(arg['type'], info, key_source)
399e6c42b96SMarkus Armbruster
400e6c42b96SMarkus Armbruster
401*e2050ef6SMarkus Armbrusterdef check_type_name_or_implicit(value: Optional[object],
402*e2050ef6SMarkus Armbruster                                info: QAPISourceInfo, source: str,
403*e2050ef6SMarkus Armbruster                                parent_name: Optional[str]) -> None:
404*e2050ef6SMarkus Armbruster    if value is None or isinstance(value, str):
405*e2050ef6SMarkus Armbruster        return
406*e2050ef6SMarkus Armbruster
407*e2050ef6SMarkus Armbruster    check_type_implicit(value, info, source, parent_name)
408*e2050ef6SMarkus Armbruster
409*e2050ef6SMarkus Armbruster
410b9ad358aSJohn Snowdef check_features(features: Optional[object],
411b9ad358aSJohn Snow                   info: QAPISourceInfo) -> None:
412a4865363SJohn Snow    """
413a4865363SJohn Snow    Normalize and validate the ``features`` member.
414a4865363SJohn Snow
415a4865363SJohn Snow    ``features`` may be a ``list`` of either ``str`` or ``dict``.
416a4865363SJohn Snow    Any ``str`` element will be normalized to ``{'name': element}``.
417a4865363SJohn Snow
418a4865363SJohn Snow    :forms:
419a4865363SJohn Snow      :sugared: ``List[Union[str, Feature]]``
420a4865363SJohn Snow      :canonical: ``List[Feature]``
421a4865363SJohn Snow
422a4865363SJohn Snow    :param features: The features member value to validate.
423a4865363SJohn Snow    :param info: QAPI schema source file information.
424a4865363SJohn Snow
425a4865363SJohn Snow    :raise QAPISemError: When ``features`` fails validation.
426a4865363SJohn Snow    :return: None, ``features`` is normalized in-place as needed.
427a4865363SJohn Snow    """
42823394b4cSPeter Krempa    if features is None:
42923394b4cSPeter Krempa        return
43023394b4cSPeter Krempa    if not isinstance(features, list):
43123394b4cSPeter Krempa        raise QAPISemError(info, "'features' must be an array")
4322ce51ef6SMarkus Armbruster    features[:] = [f if isinstance(f, dict) else {'name': f}
4332ce51ef6SMarkus Armbruster                   for f in features]
434e42648dcSJohn Snow    for feat in features:
43523394b4cSPeter Krempa        source = "'features' member"
436e42648dcSJohn Snow        assert isinstance(feat, dict)
437e42648dcSJohn Snow        check_keys(feat, info, source, ['name'], ['if'])
438e42648dcSJohn Snow        check_name_is_str(feat['name'], info, source)
439e42648dcSJohn Snow        source = "%s '%s'" % (source, feat['name'])
4409e191d33SMarkus Armbruster        check_name_lower(feat['name'], info, source)
441e42648dcSJohn Snow        check_if(feat, info, source)
44223394b4cSPeter Krempa
44323394b4cSPeter Krempa
44442011059SJohn Snowdef check_enum(expr: QAPIExpression) -> None:
445a4865363SJohn Snow    """
446a4865363SJohn Snow    Normalize and validate this expression as an ``enum`` definition.
447a4865363SJohn Snow
448a4865363SJohn Snow    :param expr: The expression to validate.
449a4865363SJohn Snow
450a4865363SJohn Snow    :raise QAPISemError: When ``expr`` is not a valid ``enum``.
451a4865363SJohn Snow    :return: None, ``expr`` is normalized in-place as needed.
452a4865363SJohn Snow    """
453e6c42b96SMarkus Armbruster    name = expr['enum']
454e6c42b96SMarkus Armbruster    members = expr['data']
455e6c42b96SMarkus Armbruster    prefix = expr.get('prefix')
45642011059SJohn Snow    info = expr.info
457e6c42b96SMarkus Armbruster
458e6c42b96SMarkus Armbruster    if not isinstance(members, list):
459e6c42b96SMarkus Armbruster        raise QAPISemError(info, "'data' must be an array")
460e6c42b96SMarkus Armbruster    if prefix is not None and not isinstance(prefix, str):
461e6c42b96SMarkus Armbruster        raise QAPISemError(info, "'prefix' must be a string")
462e6c42b96SMarkus Armbruster
463407efbf9SMarkus Armbruster    permissive = name in info.pragma.member_name_exceptions
464e6c42b96SMarkus Armbruster
465ad1ecfc6SMarkus Armbruster    members[:] = [m if isinstance(m, dict) else {'name': m}
466ad1ecfc6SMarkus Armbruster                  for m in members]
467e6c42b96SMarkus Armbruster    for member in members:
468e6c42b96SMarkus Armbruster        source = "'data' member"
469b6c18755SMarkus Armbruster        check_keys(member, info, source, ['name'], ['if', 'features'])
470a0c7b99bSMarkus Armbruster        member_name = member['name']
4710825f62cSMarkus Armbruster        check_name_is_str(member_name, info, source)
4720825f62cSMarkus Armbruster        source = "%s '%s'" % (source, member_name)
4730825f62cSMarkus Armbruster        # Enum members may start with a digit
4740825f62cSMarkus Armbruster        if member_name[0].isdigit():
4750825f62cSMarkus Armbruster            member_name = 'd' + member_name  # Hack: hide the digit
4769af4b6b9SMarkus Armbruster        check_name_lower(member_name, info, source,
477407efbf9SMarkus Armbruster                         permit_upper=permissive,
478407efbf9SMarkus Armbruster                         permit_underscore=permissive)
479e6c42b96SMarkus Armbruster        check_if(member, info, source)
480b6c18755SMarkus Armbruster        check_features(member.get('features'), info)
481e6c42b96SMarkus Armbruster
482e6c42b96SMarkus Armbruster
48342011059SJohn Snowdef check_struct(expr: QAPIExpression) -> None:
484a4865363SJohn Snow    """
485a4865363SJohn Snow    Normalize and validate this expression as a ``struct`` definition.
486a4865363SJohn Snow
487a4865363SJohn Snow    :param expr: The expression to validate.
488a4865363SJohn Snow
489a4865363SJohn Snow    :raise QAPISemError: When ``expr`` is not a valid ``struct``.
490a4865363SJohn Snow    :return: None, ``expr`` is normalized in-place as needed.
491a4865363SJohn Snow    """
4927a783ce5SJohn Snow    name = cast(str, expr['struct'])  # Checked in check_exprs
493e6c42b96SMarkus Armbruster    members = expr['data']
494e6c42b96SMarkus Armbruster
495*e2050ef6SMarkus Armbruster    check_type_implicit(members, expr.info, "'data'", name)
49606cc46eeSMarkus Armbruster    check_type_name(expr.get('base'), expr.info, "'base'")
497e6c42b96SMarkus Armbruster
498e6c42b96SMarkus Armbruster
49942011059SJohn Snowdef check_union(expr: QAPIExpression) -> None:
500a4865363SJohn Snow    """
501a4865363SJohn Snow    Normalize and validate this expression as a ``union`` definition.
502a4865363SJohn Snow
503a4865363SJohn Snow    :param expr: The expression to validate.
504a4865363SJohn Snow
505a4865363SJohn Snow    :raise QAPISemError: when ``expr`` is not a valid ``union``.
506a4865363SJohn Snow    :return: None, ``expr`` is normalized in-place as needed.
507a4865363SJohn Snow    """
5087a783ce5SJohn Snow    name = cast(str, expr['union'])  # Checked in check_exprs
5094e99f4b1SMarkus Armbruster    base = expr['base']
5104e99f4b1SMarkus Armbruster    discriminator = expr['discriminator']
511e6c42b96SMarkus Armbruster    members = expr['data']
51242011059SJohn Snow    info = expr.info
513e6c42b96SMarkus Armbruster
51406cc46eeSMarkus Armbruster    check_type_name_or_implicit(base, info, "'base'", name)
515e6c42b96SMarkus Armbruster    check_name_is_str(discriminator, info, "'discriminator'")
516e6c42b96SMarkus Armbruster
5174918bb7dSJohn Snow    if not isinstance(members, dict):
5184918bb7dSJohn Snow        raise QAPISemError(info, "'data' must be an object")
5194918bb7dSJohn Snow
520e6c42b96SMarkus Armbruster    for (key, value) in members.items():
521e6c42b96SMarkus Armbruster        source = "'data' member '%s'" % key
522e6c42b96SMarkus Armbruster        check_keys(value, info, source, ['type'], ['if'])
523e6c42b96SMarkus Armbruster        check_if(value, info, source)
52406cc46eeSMarkus Armbruster        check_type_name(value['type'], info, source)
525e6c42b96SMarkus Armbruster
526e6c42b96SMarkus Armbruster
52742011059SJohn Snowdef check_alternate(expr: QAPIExpression) -> None:
528a4865363SJohn Snow    """
529a4865363SJohn Snow    Normalize and validate this expression as an ``alternate`` definition.
530a4865363SJohn Snow
531a4865363SJohn Snow    :param expr: The expression to validate.
532a4865363SJohn Snow
533a4865363SJohn Snow    :raise QAPISemError: When ``expr`` is not a valid ``alternate``.
534a4865363SJohn Snow    :return: None, ``expr`` is normalized in-place as needed.
535a4865363SJohn Snow    """
536e6c42b96SMarkus Armbruster    members = expr['data']
53742011059SJohn Snow    info = expr.info
538e6c42b96SMarkus Armbruster
5398ec0e1a4SMarkus Armbruster    if not members:
540e6c42b96SMarkus Armbruster        raise QAPISemError(info, "'data' must not be empty")
5414918bb7dSJohn Snow
5424918bb7dSJohn Snow    if not isinstance(members, dict):
5434918bb7dSJohn Snow        raise QAPISemError(info, "'data' must be an object")
5444918bb7dSJohn Snow
545e6c42b96SMarkus Armbruster    for (key, value) in members.items():
546e6c42b96SMarkus Armbruster        source = "'data' member '%s'" % key
547d83b4764SMarkus Armbruster        check_name_lower(key, info, source)
548e6c42b96SMarkus Armbruster        check_keys(value, info, source, ['type'], ['if'])
549e6c42b96SMarkus Armbruster        check_if(value, info, source)
55006cc46eeSMarkus Armbruster        check_type_name_or_array(value['type'], info, source)
551e6c42b96SMarkus Armbruster
552e6c42b96SMarkus Armbruster
55342011059SJohn Snowdef check_command(expr: QAPIExpression) -> None:
554a4865363SJohn Snow    """
555a4865363SJohn Snow    Normalize and validate this expression as a ``command`` definition.
556a4865363SJohn Snow
557a4865363SJohn Snow    :param expr: The expression to validate.
558a4865363SJohn Snow
559a4865363SJohn Snow    :raise QAPISemError: When ``expr`` is not a valid ``command``.
560a4865363SJohn Snow    :return: None, ``expr`` is normalized in-place as needed.
561a4865363SJohn Snow    """
562e6c42b96SMarkus Armbruster    args = expr.get('data')
563e6c42b96SMarkus Armbruster    rets = expr.get('returns')
564e6c42b96SMarkus Armbruster    boxed = expr.get('boxed', False)
565e6c42b96SMarkus Armbruster
56606cc46eeSMarkus Armbruster    if boxed:
56706cc46eeSMarkus Armbruster        if args is None:
56842011059SJohn Snow            raise QAPISemError(expr.info, "'boxed': true requires 'data'")
56906cc46eeSMarkus Armbruster        check_type_name(args, expr.info, "'data'")
57006cc46eeSMarkus Armbruster    else:
57106cc46eeSMarkus Armbruster        check_type_name_or_implicit(args, expr.info, "'data'", None)
57206cc46eeSMarkus Armbruster    check_type_name_or_array(rets, expr.info, "'returns'")
573e6c42b96SMarkus Armbruster
574e6c42b96SMarkus Armbruster
57542011059SJohn Snowdef check_event(expr: QAPIExpression) -> None:
576a4865363SJohn Snow    """
577a4865363SJohn Snow    Normalize and validate this expression as an ``event`` definition.
578a4865363SJohn Snow
579a4865363SJohn Snow    :param expr: The expression to validate.
580a4865363SJohn Snow
581a4865363SJohn Snow    :raise QAPISemError: When ``expr`` is not a valid ``event``.
582a4865363SJohn Snow    :return: None, ``expr`` is normalized in-place as needed.
583a4865363SJohn Snow    """
584e6c42b96SMarkus Armbruster    args = expr.get('data')
585e6c42b96SMarkus Armbruster    boxed = expr.get('boxed', False)
586e6c42b96SMarkus Armbruster
58706cc46eeSMarkus Armbruster    if boxed:
58806cc46eeSMarkus Armbruster        if args is None:
58942011059SJohn Snow            raise QAPISemError(expr.info, "'boxed': true requires 'data'")
59006cc46eeSMarkus Armbruster        check_type_name(args, expr.info, "'data'")
59106cc46eeSMarkus Armbruster    else:
59206cc46eeSMarkus Armbruster        check_type_name_or_implicit(args, expr.info, "'data'", None)
593e6c42b96SMarkus Armbruster
594e6c42b96SMarkus Armbruster
59542011059SJohn Snowdef check_exprs(exprs: List[QAPIExpression]) -> List[QAPIExpression]:
596a4865363SJohn Snow    """
597a4865363SJohn Snow    Validate and normalize a list of parsed QAPI schema expressions.
598a4865363SJohn Snow
599a4865363SJohn Snow    This function accepts a list of expressions and metadata as returned
600a4865363SJohn Snow    by the parser.  It destructively normalizes the expressions in-place.
601a4865363SJohn Snow
602a4865363SJohn Snow    :param exprs: The list of expressions to normalize and validate.
603a4865363SJohn Snow
604a4865363SJohn Snow    :raise QAPISemError: When any expression fails validation.
605a4865363SJohn Snow    :return: The same list of expressions (now modified).
606a4865363SJohn Snow    """
60742011059SJohn Snow    for expr in exprs:
60842011059SJohn Snow        info = expr.info
60942011059SJohn Snow        doc = expr.doc
610e6c42b96SMarkus Armbruster
611e6c42b96SMarkus Armbruster        if 'include' in expr:
612e6c42b96SMarkus Armbruster            continue
613e6c42b96SMarkus Armbruster
6146dcf0371SMarkus Armbruster        metas = expr.keys() & {'enum', 'struct', 'union', 'alternate',
6156dcf0371SMarkus Armbruster                               'command', 'event'}
6166dcf0371SMarkus Armbruster        if len(metas) != 1:
6176dcf0371SMarkus Armbruster            raise QAPISemError(
6186dcf0371SMarkus Armbruster                info,
6196dcf0371SMarkus Armbruster                "expression must have exactly one key"
6206dcf0371SMarkus Armbruster                " 'enum', 'struct', 'union', 'alternate',"
6216dcf0371SMarkus Armbruster                " 'command', 'event'")
6226dcf0371SMarkus Armbruster        meta = metas.pop()
623e6c42b96SMarkus Armbruster
6247a783ce5SJohn Snow        check_name_is_str(expr[meta], info, "'%s'" % meta)
6257a783ce5SJohn Snow        name = cast(str, expr[meta])
626e6c42b96SMarkus Armbruster        info.set_defn(meta, name)
627e6c42b96SMarkus Armbruster        check_defn_name_str(name, info, meta)
628e6c42b96SMarkus Armbruster
629e6c42b96SMarkus Armbruster        if doc:
630e6c42b96SMarkus Armbruster            if doc.symbol != name:
631e6c42b96SMarkus Armbruster                raise QAPISemError(
632e6c42b96SMarkus Armbruster                    info, "documentation comment is for '%s'" % doc.symbol)
633e6c42b96SMarkus Armbruster            doc.check_expr(expr)
634e6c42b96SMarkus Armbruster        elif info.pragma.doc_required:
635e6c42b96SMarkus Armbruster            raise QAPISemError(info,
636e6c42b96SMarkus Armbruster                               "documentation comment required")
637e6c42b96SMarkus Armbruster
638e6c42b96SMarkus Armbruster        if meta == 'enum':
639e6c42b96SMarkus Armbruster            check_keys(expr, info, meta,
640013b4efcSMarkus Armbruster                       ['enum', 'data'], ['if', 'features', 'prefix'])
64142011059SJohn Snow            check_enum(expr)
642e6c42b96SMarkus Armbruster        elif meta == 'union':
643e6c42b96SMarkus Armbruster            check_keys(expr, info, meta,
6444e99f4b1SMarkus Armbruster                       ['union', 'base', 'discriminator', 'data'],
6454e99f4b1SMarkus Armbruster                       ['if', 'features'])
646e6c42b96SMarkus Armbruster            normalize_members(expr.get('base'))
647e6c42b96SMarkus Armbruster            normalize_members(expr['data'])
64842011059SJohn Snow            check_union(expr)
649e6c42b96SMarkus Armbruster        elif meta == 'alternate':
650e6c42b96SMarkus Armbruster            check_keys(expr, info, meta,
651013b4efcSMarkus Armbruster                       ['alternate', 'data'], ['if', 'features'])
652e6c42b96SMarkus Armbruster            normalize_members(expr['data'])
65342011059SJohn Snow            check_alternate(expr)
654e6c42b96SMarkus Armbruster        elif meta == 'struct':
655e6c42b96SMarkus Armbruster            check_keys(expr, info, meta,
656e6c42b96SMarkus Armbruster                       ['struct', 'data'], ['base', 'if', 'features'])
657e6c42b96SMarkus Armbruster            normalize_members(expr['data'])
65842011059SJohn Snow            check_struct(expr)
659e6c42b96SMarkus Armbruster        elif meta == 'command':
660e6c42b96SMarkus Armbruster            check_keys(expr, info, meta,
661e6c42b96SMarkus Armbruster                       ['command'],
66223394b4cSPeter Krempa                       ['data', 'returns', 'boxed', 'if', 'features',
663e6c42b96SMarkus Armbruster                        'gen', 'success-response', 'allow-oob',
66404f22362SKevin Wolf                        'allow-preconfig', 'coroutine'])
665e6c42b96SMarkus Armbruster            normalize_members(expr.get('data'))
66642011059SJohn Snow            check_command(expr)
667e6c42b96SMarkus Armbruster        elif meta == 'event':
668e6c42b96SMarkus Armbruster            check_keys(expr, info, meta,
669013b4efcSMarkus Armbruster                       ['event'], ['data', 'boxed', 'if', 'features'])
670e6c42b96SMarkus Armbruster            normalize_members(expr.get('data'))
67142011059SJohn Snow            check_event(expr)
672e6c42b96SMarkus Armbruster        else:
673e6c42b96SMarkus Armbruster            assert False, 'unexpected meta type'
674e6c42b96SMarkus Armbruster
675e6c42b96SMarkus Armbruster        check_if(expr, info, meta)
676013b4efcSMarkus Armbruster        check_features(expr.get('features'), info)
67742011059SJohn Snow        check_flags(expr)
678e6c42b96SMarkus Armbruster
679e6c42b96SMarkus Armbruster    return exprs
680