2015-08-21 07:04:50 +00:00
|
|
|
#
|
|
|
|
# QAPI helper library
|
|
|
|
#
|
|
|
|
# Copyright IBM, Corp. 2011
|
2018-03-09 12:53:43 +00:00
|
|
|
# Copyright (c) 2013-2018 Red Hat Inc.
|
2015-08-21 07:04:50 +00:00
|
|
|
#
|
|
|
|
# Authors:
|
|
|
|
# Anthony Liguori <aliguori@us.ibm.com>
|
|
|
|
# Markus Armbruster <armbru@redhat.com>
|
|
|
|
#
|
|
|
|
# This work is licensed under the terms of the GNU GPL, version 2.
|
|
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
|
2018-03-06 16:26:57 +00:00
|
|
|
from __future__ import print_function
|
2018-07-05 16:07:12 +00:00
|
|
|
from contextlib import contextmanager
|
2018-02-19 20:31:47 +00:00
|
|
|
import errno
|
2015-08-21 07:04:50 +00:00
|
|
|
import os
|
2018-03-03 23:23:47 +00:00
|
|
|
import re
|
2018-02-19 19:40:55 +00:00
|
|
|
import string
|
2018-07-03 03:01:21 +00:00
|
|
|
import sys
|
2018-03-06 16:28:54 +00:00
|
|
|
try:
|
|
|
|
from collections import OrderedDict
|
|
|
|
except:
|
|
|
|
from ordereddict import OrderedDict
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 18:14:35 +00:00
|
|
|
builtin_types = {
|
2018-03-07 21:52:39 +00:00
|
|
|
'null': 'QTYPE_QNULL',
|
2015-08-21 07:04:50 +00:00
|
|
|
'str': 'QTYPE_QSTRING',
|
2018-03-03 22:43:24 +00:00
|
|
|
'int': 'QTYPE_QNUM',
|
|
|
|
'number': 'QTYPE_QNUM',
|
2015-08-21 07:04:50 +00:00
|
|
|
'bool': 'QTYPE_QBOOL',
|
2018-03-03 22:43:24 +00:00
|
|
|
'int8': 'QTYPE_QNUM',
|
|
|
|
'int16': 'QTYPE_QNUM',
|
|
|
|
'int32': 'QTYPE_QNUM',
|
|
|
|
'int64': 'QTYPE_QNUM',
|
|
|
|
'uint8': 'QTYPE_QNUM',
|
|
|
|
'uint16': 'QTYPE_QNUM',
|
|
|
|
'uint32': 'QTYPE_QNUM',
|
|
|
|
'uint64': 'QTYPE_QNUM',
|
|
|
|
'size': 'QTYPE_QNUM',
|
2018-02-20 02:41:48 +00:00
|
|
|
'any': None, # any QType possible, actually
|
qapi: Convert QType into QAPI built-in enum type
What's more meta than using qapi to define qapi? :)
Convert QType into a full-fledged[*] builtin qapi enum type, so
that a subsequent patch can then use it as the discriminator
type of qapi alternate types. Fortunately, the judicious use of
'prefix' in the qapi definition avoids churn to the spelling of
the enum constants.
To avoid circular definitions, we have to flip the order of
inclusion between "qobject.h" vs. "qapi-types.h". Back in commit
28770e0, we had the latter include the former, so that we could
use 'QObject *' for our implementation of 'any'. But that usage
also works with only a forward declaration, whereas the
definition of QObject requires QType to be a complete type.
[*] The type has to be builtin, rather than declared in
qapi/common.json, because we want to use it for alternates even
when common.json is not included. But since it is the first
builtin enum type, we have to add special cases to qapi-types
and qapi-visit to only emit definitions once, even when two
qapi files are being compiled into the same binary (the way we
already handled builtin list types like 'intList'). We may
need to revisit how multiple qapi files share common types,
but that's a project for another day.
Backports commit 7264f5c50cc1be0f1406e3ebb45aedcca02f603a from qemu
2018-02-20 02:45:54 +00:00
|
|
|
'QType': 'QTYPE_QSTRING',
|
2015-08-21 07:04:50 +00:00
|
|
|
}
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
# Are documentation comments required?
|
|
|
|
doc_required = False
|
|
|
|
|
2018-02-19 19:06:22 +00:00
|
|
|
# Whitelist of commands allowed to return a non-dictionary
|
2018-03-03 23:23:47 +00:00
|
|
|
returns_whitelist = []
|
2018-02-19 19:06:22 +00:00
|
|
|
|
qapi: Enforce (or whitelist) case conventions on qapi members
We document that members of enums and objects should be
'lower-case', although we were not enforcing it. We have to
whitelist a few pre-existing entities that violate the norms.
Add three new tests to expose the new error message, each of
which first uses the whitelisted name 'UuidInfo' to prove the
whitelist works, then triggers the failure (this is the same
pattern used in the existing returns-whitelist.json test).
Note that by adding this check, we have effectively forbidden
an entity with a case-insensitive clash of member names, for
any entity that is not on the whitelist (although there is
still the possibility to clash via '-' vs. '_').
Not done here: a future patch should also add naming convention
support and whitelist exceptions for command, event, and type
names.
The additions to QAPISchemaMember.check_clash() check whether
info['name'] is in the whitelist (the top-most entity name at
the point 'info' tracks), rather than self.owner (the type,
possibly implicit, that directly owns the member), because it
is easier to maintain the whitelist by the names actually in
the user's .json file, rather than worrying about the names
of implicit types.
Backports commit 893e1f2c5170d54316f1dcf3fefae679175622fc from qemu
2018-02-20 03:08:24 +00:00
|
|
|
# Whitelist of entities allowed to violate case conventions
|
2018-03-03 23:23:47 +00:00
|
|
|
name_case_whitelist = []
|
|
|
|
|
|
|
|
enum_types = {}
|
|
|
|
struct_types = {}
|
|
|
|
union_types = {}
|
2018-02-19 18:56:02 +00:00
|
|
|
all_names = {}
|
|
|
|
|
2018-02-19 20:47:16 +00:00
|
|
|
#
|
|
|
|
# Parsing the schema into expressions
|
|
|
|
#
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def error_path(parent):
|
2018-03-03 23:23:47 +00:00
|
|
|
res = ''
|
2015-08-21 07:04:50 +00:00
|
|
|
while parent:
|
2018-03-03 23:23:47 +00:00
|
|
|
res = ('In file included from %s:%d:\n' % (parent['file'],
|
2015-08-21 07:04:50 +00:00
|
|
|
parent['line'])) + res
|
|
|
|
parent = parent['parent']
|
|
|
|
return res
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
class QAPIError(Exception):
|
|
|
|
def __init__(self, fname, line, col, incl_info, msg):
|
2018-02-19 22:57:18 +00:00
|
|
|
Exception.__init__(self)
|
2018-03-03 23:23:47 +00:00
|
|
|
self.fname = fname
|
|
|
|
self.line = line
|
|
|
|
self.col = col
|
|
|
|
self.info = incl_info
|
2015-08-21 07:04:50 +00:00
|
|
|
self.msg = msg
|
2018-03-03 23:23:47 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
loc = '%s:%d' % (self.fname, self.line)
|
|
|
|
if self.col is not None:
|
|
|
|
loc += ':%s' % self.col
|
|
|
|
return error_path(self.info) + '%s: %s' % (loc, self.msg)
|
|
|
|
|
|
|
|
|
|
|
|
class QAPIParseError(QAPIError):
|
|
|
|
def __init__(self, parser, msg):
|
|
|
|
col = 1
|
|
|
|
for ch in parser.src[parser.line_pos:parser.pos]:
|
2015-08-21 07:04:50 +00:00
|
|
|
if ch == '\t':
|
2018-03-03 23:23:47 +00:00
|
|
|
col = (col + 7) % 8 + 1
|
2015-08-21 07:04:50 +00:00
|
|
|
else:
|
2018-03-03 23:23:47 +00:00
|
|
|
col += 1
|
|
|
|
QAPIError.__init__(self, parser.fname, parser.line, col,
|
|
|
|
parser.incl_info, msg)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
class QAPISemError(QAPIError):
|
|
|
|
def __init__(self, info, msg):
|
|
|
|
QAPIError.__init__(self, info['file'], info['line'], None,
|
|
|
|
info['parent'], msg)
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
class QAPIDoc(object):
|
|
|
|
class Section(object):
|
|
|
|
def __init__(self, name=None):
|
|
|
|
# optional section name (argument/member or section name)
|
|
|
|
self.name = name
|
|
|
|
# the list of lines for this section
|
|
|
|
self.content = []
|
|
|
|
|
|
|
|
def append(self, line):
|
|
|
|
self.content.append(line)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return '\n'.join(self.content).strip()
|
|
|
|
|
|
|
|
class ArgSection(Section):
|
|
|
|
def __init__(self, name):
|
|
|
|
QAPIDoc.Section.__init__(self, name)
|
|
|
|
self.member = None
|
|
|
|
|
|
|
|
def connect(self, member):
|
|
|
|
self.member = member
|
|
|
|
|
|
|
|
def __init__(self, parser, info):
|
|
|
|
# self.parser is used to report errors with QAPIParseError. The
|
|
|
|
# resulting error position depends on the state of the parser.
|
|
|
|
# It happens to be the beginning of the comment. More or less
|
|
|
|
# servicable, but action at a distance.
|
|
|
|
self.parser = parser
|
|
|
|
self.info = info
|
|
|
|
self.symbol = None
|
|
|
|
self.body = QAPIDoc.Section()
|
|
|
|
# dict mapping parameter name to ArgSection
|
|
|
|
self.args = OrderedDict()
|
|
|
|
# a list of Section
|
|
|
|
self.sections = []
|
|
|
|
# the current section
|
|
|
|
self.section = self.body
|
|
|
|
|
|
|
|
def has_section(self, name):
|
|
|
|
"""Return True if we have a section with this name."""
|
|
|
|
for i in self.sections:
|
|
|
|
if i.name == name:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def append(self, line):
|
|
|
|
"""Parse a comment line and add it to the documentation."""
|
|
|
|
line = line[1:]
|
|
|
|
if not line:
|
|
|
|
self._append_freeform(line)
|
|
|
|
return
|
|
|
|
|
|
|
|
if line[0] != ' ':
|
|
|
|
raise QAPIParseError(self.parser, "Missing space after #")
|
|
|
|
line = line[1:]
|
|
|
|
|
|
|
|
# FIXME not nice: things like '# @foo:' and '# @foo: ' aren't
|
|
|
|
# recognized, and get silently treated as ordinary text
|
|
|
|
if self.symbol:
|
|
|
|
self._append_symbol_line(line)
|
|
|
|
elif not self.body.content and line.startswith('@'):
|
|
|
|
if not line.endswith(':'):
|
|
|
|
raise QAPIParseError(self.parser, "Line should end with :")
|
|
|
|
self.symbol = line[1:-1]
|
|
|
|
# FIXME invalid names other than the empty string aren't flagged
|
|
|
|
if not self.symbol:
|
|
|
|
raise QAPIParseError(self.parser, "Invalid name")
|
|
|
|
else:
|
|
|
|
self._append_freeform(line)
|
|
|
|
|
|
|
|
def end_comment(self):
|
|
|
|
self._end_section()
|
|
|
|
|
|
|
|
def _append_symbol_line(self, line):
|
|
|
|
name = line.split(' ', 1)[0]
|
|
|
|
|
|
|
|
if name.startswith('@') and name.endswith(':'):
|
|
|
|
line = line[len(name)+1:]
|
|
|
|
self._start_args_section(name[1:-1])
|
|
|
|
elif name in ('Returns:', 'Since:',
|
|
|
|
# those are often singular or plural
|
|
|
|
'Note:', 'Notes:',
|
|
|
|
'Example:', 'Examples:',
|
|
|
|
'TODO:'):
|
|
|
|
line = line[len(name)+1:]
|
|
|
|
self._start_section(name[:-1])
|
|
|
|
|
|
|
|
self._append_freeform(line)
|
|
|
|
|
|
|
|
def _start_args_section(self, name):
|
|
|
|
# FIXME invalid names other than the empty string aren't flagged
|
|
|
|
if not name:
|
|
|
|
raise QAPIParseError(self.parser, "Invalid parameter name")
|
|
|
|
if name in self.args:
|
|
|
|
raise QAPIParseError(self.parser,
|
|
|
|
"'%s' parameter name duplicated" % name)
|
|
|
|
if self.sections:
|
|
|
|
raise QAPIParseError(self.parser,
|
|
|
|
"'@%s:' can't follow '%s' section"
|
|
|
|
% (name, self.sections[0].name))
|
|
|
|
self._end_section()
|
|
|
|
self.section = QAPIDoc.ArgSection(name)
|
|
|
|
self.args[name] = self.section
|
|
|
|
|
|
|
|
def _start_section(self, name=''):
|
|
|
|
if name in ('Returns', 'Since') and self.has_section(name):
|
|
|
|
raise QAPIParseError(self.parser,
|
|
|
|
"Duplicated '%s' section" % name)
|
|
|
|
self._end_section()
|
|
|
|
self.section = QAPIDoc.Section(name)
|
|
|
|
self.sections.append(self.section)
|
|
|
|
|
|
|
|
def _end_section(self):
|
|
|
|
if self.section:
|
|
|
|
contents = str(self.section)
|
|
|
|
if self.section.name and (not contents or contents.isspace()):
|
|
|
|
raise QAPIParseError(self.parser, "Empty doc section '%s'"
|
|
|
|
% self.section.name)
|
|
|
|
self.section = None
|
|
|
|
|
|
|
|
def _append_freeform(self, line):
|
|
|
|
in_arg = isinstance(self.section, QAPIDoc.ArgSection)
|
|
|
|
if (in_arg and self.section.content
|
|
|
|
and not self.section.content[-1]
|
|
|
|
and line and not line[0].isspace()):
|
|
|
|
self._start_section()
|
|
|
|
if (in_arg or not self.section.name
|
|
|
|
or not self.section.name.startswith('Example')):
|
|
|
|
line = line.strip()
|
|
|
|
match = re.match(r'(@\S+:)', line)
|
|
|
|
if match:
|
|
|
|
raise QAPIParseError(self.parser,
|
|
|
|
"'%s' not allowed in free-form documentation"
|
|
|
|
% match.group(1))
|
|
|
|
# TODO Drop this once the dust has settled
|
|
|
|
if (isinstance(self.section, QAPIDoc.ArgSection)
|
|
|
|
and '#optional' in line):
|
|
|
|
raise QAPISemError(self.info, "Please drop the #optional tag")
|
|
|
|
self.section.append(line)
|
|
|
|
|
|
|
|
def connect_member(self, member):
|
|
|
|
if member.name not in self.args:
|
|
|
|
# Undocumented TODO outlaw
|
|
|
|
self.args[member.name] = QAPIDoc.ArgSection(member.name)
|
|
|
|
self.args[member.name].connect(member)
|
|
|
|
|
|
|
|
def check_expr(self, expr):
|
|
|
|
if self.has_section('Returns') and 'command' not in expr:
|
|
|
|
raise QAPISemError(self.info,
|
|
|
|
"'Returns:' is only valid for commands")
|
|
|
|
|
|
|
|
def check(self):
|
2018-03-06 16:28:20 +00:00
|
|
|
bogus = [name for name, section in self.args.items()
|
2018-03-03 23:23:47 +00:00
|
|
|
if not section.member]
|
|
|
|
if bogus:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"The following documented members are not in "
|
|
|
|
"the declaration: %s" % ", ".join(bogus))
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-19 21:40:55 +00:00
|
|
|
class QAPISchemaParser(object):
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
def __init__(self, fp, previously_included=[], incl_info=None):
|
2018-02-19 20:39:05 +00:00
|
|
|
fname = fp.name
|
2018-02-19 20:34:51 +00:00
|
|
|
self.fname = fname
|
2018-03-09 13:52:59 +00:00
|
|
|
previously_included.append(os.path.abspath(fp.name))
|
2018-02-19 20:34:51 +00:00
|
|
|
self.incl_info = incl_info
|
2015-08-21 07:04:50 +00:00
|
|
|
self.src = fp.read()
|
|
|
|
if self.src == '' or self.src[-1] != '\n':
|
|
|
|
self.src += '\n'
|
|
|
|
self.cursor = 0
|
|
|
|
self.line = 1
|
|
|
|
self.line_pos = 0
|
|
|
|
self.exprs = []
|
2018-03-03 23:23:47 +00:00
|
|
|
self.docs = []
|
|
|
|
self.cur_doc = None
|
2015-08-21 07:04:50 +00:00
|
|
|
self.accept()
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
while self.tok is not None:
|
2018-03-03 23:23:47 +00:00
|
|
|
info = {'file': fname, 'line': self.line,
|
|
|
|
'parent': self.incl_info}
|
|
|
|
if self.tok == '#':
|
|
|
|
self.reject_expr_doc()
|
|
|
|
self.cur_doc = self.get_doc(info)
|
|
|
|
self.docs.append(self.cur_doc)
|
|
|
|
continue
|
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
expr = self.get_expr(False)
|
2018-03-03 23:23:47 +00:00
|
|
|
if 'include' in expr:
|
|
|
|
self.reject_expr_doc()
|
2015-08-21 07:04:50 +00:00
|
|
|
if len(expr) != 1:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Invalid 'include' directive")
|
|
|
|
include = expr['include']
|
2015-08-21 07:04:50 +00:00
|
|
|
if not isinstance(include, str):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Value of 'include' must be a string")
|
2018-03-09 13:58:47 +00:00
|
|
|
incl_fname = os.path.join(os.path.dirname(self.fname),
|
|
|
|
include)
|
|
|
|
self.exprs.append({'expr': {'include': incl_fname},
|
|
|
|
'info': info})
|
|
|
|
exprs_include = self._include(include, info, incl_fname,
|
2018-03-09 13:56:44 +00:00
|
|
|
previously_included)
|
|
|
|
if exprs_include:
|
|
|
|
self.exprs.extend(exprs_include.exprs)
|
|
|
|
self.docs.extend(exprs_include.docs)
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
elif "pragma" in expr:
|
|
|
|
self.reject_expr_doc()
|
|
|
|
if len(expr) != 1:
|
|
|
|
raise QAPISemError(info, "Invalid 'pragma' directive")
|
|
|
|
pragma = expr['pragma']
|
|
|
|
if not isinstance(pragma, dict):
|
|
|
|
raise QAPISemError(
|
|
|
|
info, "Value of 'pragma' must be a dictionary")
|
2018-03-06 16:28:20 +00:00
|
|
|
for name, value in pragma.items():
|
2018-03-03 23:23:47 +00:00
|
|
|
self._pragma(name, value, info)
|
2015-08-21 07:04:50 +00:00
|
|
|
else:
|
|
|
|
expr_elem = {'expr': expr,
|
2018-03-03 23:23:47 +00:00
|
|
|
'info': info}
|
|
|
|
if self.cur_doc:
|
|
|
|
if not self.cur_doc.symbol:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.cur_doc.info,
|
|
|
|
"Expression documentation required")
|
|
|
|
expr_elem['doc'] = self.cur_doc
|
2015-08-21 07:04:50 +00:00
|
|
|
self.exprs.append(expr_elem)
|
2018-03-03 23:23:47 +00:00
|
|
|
self.cur_doc = None
|
|
|
|
self.reject_expr_doc()
|
|
|
|
|
|
|
|
def reject_expr_doc(self):
|
|
|
|
if self.cur_doc and self.cur_doc.symbol:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.cur_doc.info,
|
|
|
|
"Documentation for '%s' is not followed by the definition"
|
|
|
|
% self.cur_doc.symbol)
|
|
|
|
|
2018-03-09 13:58:47 +00:00
|
|
|
def _include(self, include, info, incl_fname, previously_included):
|
2018-03-09 13:52:59 +00:00
|
|
|
incl_abs_fname = os.path.abspath(incl_fname)
|
2018-03-03 23:23:47 +00:00
|
|
|
# catch inclusion cycle
|
|
|
|
inf = info
|
|
|
|
while inf:
|
|
|
|
if incl_abs_fname == os.path.abspath(inf['file']):
|
|
|
|
raise QAPISemError(info, "Inclusion loop for %s" % include)
|
|
|
|
inf = inf['parent']
|
|
|
|
|
|
|
|
# skip multiple include of the same file
|
|
|
|
if incl_abs_fname in previously_included:
|
2018-03-09 13:56:44 +00:00
|
|
|
return None
|
2018-03-03 23:23:47 +00:00
|
|
|
try:
|
2018-07-03 03:01:21 +00:00
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
fobj = open(incl_fname, 'r', encoding='utf-8')
|
|
|
|
else:
|
|
|
|
fobj = open(incl_fname, 'r')
|
2018-03-03 23:23:47 +00:00
|
|
|
except IOError as e:
|
2018-03-09 13:52:59 +00:00
|
|
|
raise QAPISemError(info, '%s: %s' % (e.strerror, incl_fname))
|
2018-03-09 13:56:44 +00:00
|
|
|
return QAPISchemaParser(fobj, previously_included, info)
|
2018-03-03 23:23:47 +00:00
|
|
|
|
|
|
|
def _pragma(self, name, value, info):
|
|
|
|
global doc_required, returns_whitelist, name_case_whitelist
|
|
|
|
if name == 'doc-required':
|
|
|
|
if not isinstance(value, bool):
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Pragma 'doc-required' must be boolean")
|
|
|
|
doc_required = value
|
|
|
|
elif name == 'returns-whitelist':
|
|
|
|
if (not isinstance(value, list)
|
|
|
|
or any([not isinstance(elt, str) for elt in value])):
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Pragma returns-whitelist must be"
|
|
|
|
" a list of strings")
|
|
|
|
returns_whitelist = value
|
|
|
|
elif name == 'name-case-whitelist':
|
|
|
|
if (not isinstance(value, list)
|
|
|
|
or any([not isinstance(elt, str) for elt in value])):
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Pragma name-case-whitelist must be"
|
|
|
|
" a list of strings")
|
|
|
|
name_case_whitelist = value
|
|
|
|
else:
|
|
|
|
raise QAPISemError(info, "Unknown pragma '%s'" % name)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def accept(self, skip_comment=True):
|
2015-08-21 07:04:50 +00:00
|
|
|
while True:
|
|
|
|
self.tok = self.src[self.cursor]
|
|
|
|
self.pos = self.cursor
|
|
|
|
self.cursor += 1
|
|
|
|
self.val = None
|
|
|
|
|
|
|
|
if self.tok == '#':
|
2018-03-03 23:23:47 +00:00
|
|
|
if self.src[self.cursor] == '#':
|
|
|
|
# Start of doc comment
|
|
|
|
skip_comment = False
|
2015-08-21 07:04:50 +00:00
|
|
|
self.cursor = self.src.find('\n', self.cursor)
|
2018-03-03 23:23:47 +00:00
|
|
|
if not skip_comment:
|
|
|
|
self.val = self.src[self.pos:self.cursor]
|
|
|
|
return
|
|
|
|
elif self.tok in '{}:,[]':
|
2015-08-21 07:04:50 +00:00
|
|
|
return
|
|
|
|
elif self.tok == "'":
|
|
|
|
string = ''
|
|
|
|
esc = False
|
|
|
|
while True:
|
|
|
|
ch = self.src[self.cursor]
|
|
|
|
self.cursor += 1
|
|
|
|
if ch == '\n':
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Missing terminating "\'"')
|
2015-08-21 07:04:50 +00:00
|
|
|
if esc:
|
qapi: Support (subset of) \u escapes in strings
The handling of \ inside QAPI strings was less than ideal, and
really only worked JSON's \/, \\, \", and our extension of \'
(an obvious extension, when you realize we use '' instead of ""
for strings). For other things, like '\n', it resulted in a
literal 'n' instead of a newline.
Of course, at the moment, we really have no use for escaped
characters, as QAPI has to map to C identifiers, and we currently
support ASCII only for that. But down the road, we may add
support for default values for string parameters to a command
or struct; if that happens, it would be nice to correctly support
all JSON escape sequences, such as \n or \uXXXX. This gets us
closer, by supporting Unicode escapes in the ASCII range.
Since JSON does not require \OCTAL or \xXX escapes, and our QMP
implementation does not understand them either, I intentionally
reject it here, but it would be an easy addition if we desired it.
Likewise, intentionally refusing the NUL byte means we don't have
to worry about C strings being shorter than the qapi input.
Backports commit a7f5966b297330f6492020019544ae87c45d699b from qemu
2018-02-19 19:36:46 +00:00
|
|
|
if ch == 'b':
|
|
|
|
string += '\b'
|
|
|
|
elif ch == 'f':
|
|
|
|
string += '\f'
|
|
|
|
elif ch == 'n':
|
|
|
|
string += '\n'
|
|
|
|
elif ch == 'r':
|
|
|
|
string += '\r'
|
|
|
|
elif ch == 't':
|
|
|
|
string += '\t'
|
|
|
|
elif ch == 'u':
|
|
|
|
value = 0
|
2018-02-19 23:07:49 +00:00
|
|
|
for _ in range(0, 4):
|
qapi: Support (subset of) \u escapes in strings
The handling of \ inside QAPI strings was less than ideal, and
really only worked JSON's \/, \\, \", and our extension of \'
(an obvious extension, when you realize we use '' instead of ""
for strings). For other things, like '\n', it resulted in a
literal 'n' instead of a newline.
Of course, at the moment, we really have no use for escaped
characters, as QAPI has to map to C identifiers, and we currently
support ASCII only for that. But down the road, we may add
support for default values for string parameters to a command
or struct; if that happens, it would be nice to correctly support
all JSON escape sequences, such as \n or \uXXXX. This gets us
closer, by supporting Unicode escapes in the ASCII range.
Since JSON does not require \OCTAL or \xXX escapes, and our QMP
implementation does not understand them either, I intentionally
reject it here, but it would be an easy addition if we desired it.
Likewise, intentionally refusing the NUL byte means we don't have
to worry about C strings being shorter than the qapi input.
Backports commit a7f5966b297330f6492020019544ae87c45d699b from qemu
2018-02-19 19:36:46 +00:00
|
|
|
ch = self.src[self.cursor]
|
|
|
|
self.cursor += 1
|
2018-03-03 23:23:47 +00:00
|
|
|
if ch not in '0123456789abcdefABCDEF':
|
|
|
|
raise QAPIParseError(self,
|
|
|
|
'\\u escape needs 4 '
|
|
|
|
'hex digits')
|
qapi: Support (subset of) \u escapes in strings
The handling of \ inside QAPI strings was less than ideal, and
really only worked JSON's \/, \\, \", and our extension of \'
(an obvious extension, when you realize we use '' instead of ""
for strings). For other things, like '\n', it resulted in a
literal 'n' instead of a newline.
Of course, at the moment, we really have no use for escaped
characters, as QAPI has to map to C identifiers, and we currently
support ASCII only for that. But down the road, we may add
support for default values for string parameters to a command
or struct; if that happens, it would be nice to correctly support
all JSON escape sequences, such as \n or \uXXXX. This gets us
closer, by supporting Unicode escapes in the ASCII range.
Since JSON does not require \OCTAL or \xXX escapes, and our QMP
implementation does not understand them either, I intentionally
reject it here, but it would be an easy addition if we desired it.
Likewise, intentionally refusing the NUL byte means we don't have
to worry about C strings being shorter than the qapi input.
Backports commit a7f5966b297330f6492020019544ae87c45d699b from qemu
2018-02-19 19:36:46 +00:00
|
|
|
value = (value << 4) + int(ch, 16)
|
|
|
|
# If Python 2 and 3 didn't disagree so much on
|
|
|
|
# how to handle Unicode, then we could allow
|
|
|
|
# Unicode string defaults. But most of QAPI is
|
|
|
|
# ASCII-only, so we aren't losing much for now.
|
|
|
|
if not value or value > 0x7f:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self,
|
|
|
|
'For now, \\u escape '
|
|
|
|
'only supports non-zero '
|
|
|
|
'values up to \\u007f')
|
qapi: Support (subset of) \u escapes in strings
The handling of \ inside QAPI strings was less than ideal, and
really only worked JSON's \/, \\, \", and our extension of \'
(an obvious extension, when you realize we use '' instead of ""
for strings). For other things, like '\n', it resulted in a
literal 'n' instead of a newline.
Of course, at the moment, we really have no use for escaped
characters, as QAPI has to map to C identifiers, and we currently
support ASCII only for that. But down the road, we may add
support for default values for string parameters to a command
or struct; if that happens, it would be nice to correctly support
all JSON escape sequences, such as \n or \uXXXX. This gets us
closer, by supporting Unicode escapes in the ASCII range.
Since JSON does not require \OCTAL or \xXX escapes, and our QMP
implementation does not understand them either, I intentionally
reject it here, but it would be an easy addition if we desired it.
Likewise, intentionally refusing the NUL byte means we don't have
to worry about C strings being shorter than the qapi input.
Backports commit a7f5966b297330f6492020019544ae87c45d699b from qemu
2018-02-19 19:36:46 +00:00
|
|
|
string += chr(value)
|
2018-03-03 23:23:47 +00:00
|
|
|
elif ch in '\\/\'"':
|
qapi: Support (subset of) \u escapes in strings
The handling of \ inside QAPI strings was less than ideal, and
really only worked JSON's \/, \\, \", and our extension of \'
(an obvious extension, when you realize we use '' instead of ""
for strings). For other things, like '\n', it resulted in a
literal 'n' instead of a newline.
Of course, at the moment, we really have no use for escaped
characters, as QAPI has to map to C identifiers, and we currently
support ASCII only for that. But down the road, we may add
support for default values for string parameters to a command
or struct; if that happens, it would be nice to correctly support
all JSON escape sequences, such as \n or \uXXXX. This gets us
closer, by supporting Unicode escapes in the ASCII range.
Since JSON does not require \OCTAL or \xXX escapes, and our QMP
implementation does not understand them either, I intentionally
reject it here, but it would be an easy addition if we desired it.
Likewise, intentionally refusing the NUL byte means we don't have
to worry about C strings being shorter than the qapi input.
Backports commit a7f5966b297330f6492020019544ae87c45d699b from qemu
2018-02-19 19:36:46 +00:00
|
|
|
string += ch
|
|
|
|
else:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self,
|
|
|
|
"Unknown escape \\%s" % ch)
|
2015-08-21 07:04:50 +00:00
|
|
|
esc = False
|
2018-03-03 23:23:47 +00:00
|
|
|
elif ch == '\\':
|
2015-08-21 07:04:50 +00:00
|
|
|
esc = True
|
|
|
|
elif ch == "'":
|
|
|
|
self.val = string
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
string += ch
|
2018-03-03 23:23:47 +00:00
|
|
|
elif self.src.startswith('true', self.pos):
|
2018-02-19 20:41:36 +00:00
|
|
|
self.val = True
|
|
|
|
self.cursor += 3
|
|
|
|
return
|
2018-03-03 23:23:47 +00:00
|
|
|
elif self.src.startswith('false', self.pos):
|
2018-02-19 20:41:36 +00:00
|
|
|
self.val = False
|
|
|
|
self.cursor += 4
|
|
|
|
return
|
2018-03-03 23:23:47 +00:00
|
|
|
elif self.src.startswith('null', self.pos):
|
2018-02-19 20:41:36 +00:00
|
|
|
self.val = None
|
|
|
|
self.cursor += 3
|
|
|
|
return
|
2015-08-21 07:04:50 +00:00
|
|
|
elif self.tok == '\n':
|
|
|
|
if self.cursor == len(self.src):
|
|
|
|
self.tok = None
|
|
|
|
return
|
|
|
|
self.line += 1
|
|
|
|
self.line_pos = self.cursor
|
|
|
|
elif not self.tok.isspace():
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Stray "%s"' % self.tok)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
|
|
|
def get_members(self):
|
|
|
|
expr = OrderedDict()
|
|
|
|
if self.tok == '}':
|
|
|
|
self.accept()
|
|
|
|
return expr
|
|
|
|
if self.tok != "'":
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected string or "}"')
|
2015-08-21 07:04:50 +00:00
|
|
|
while True:
|
|
|
|
key = self.val
|
|
|
|
self.accept()
|
|
|
|
if self.tok != ':':
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected ":"')
|
2015-08-21 07:04:50 +00:00
|
|
|
self.accept()
|
|
|
|
if key in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Duplicate key "%s"' % key)
|
2015-08-21 07:04:50 +00:00
|
|
|
expr[key] = self.get_expr(True)
|
|
|
|
if self.tok == '}':
|
|
|
|
self.accept()
|
|
|
|
return expr
|
|
|
|
if self.tok != ',':
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected "," or "}"')
|
2015-08-21 07:04:50 +00:00
|
|
|
self.accept()
|
|
|
|
if self.tok != "'":
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected string')
|
2015-08-21 07:04:50 +00:00
|
|
|
|
|
|
|
def get_values(self):
|
|
|
|
expr = []
|
|
|
|
if self.tok == ']':
|
|
|
|
self.accept()
|
|
|
|
return expr
|
2018-02-19 23:07:49 +00:00
|
|
|
if self.tok not in "{['tfn":
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected "{", "[", "]", string, '
|
|
|
|
'boolean or "null"')
|
2015-08-21 07:04:50 +00:00
|
|
|
while True:
|
|
|
|
expr.append(self.get_expr(True))
|
|
|
|
if self.tok == ']':
|
|
|
|
self.accept()
|
|
|
|
return expr
|
|
|
|
if self.tok != ',':
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected "," or "]"')
|
2015-08-21 07:04:50 +00:00
|
|
|
self.accept()
|
|
|
|
|
|
|
|
def get_expr(self, nested):
|
|
|
|
if self.tok != '{' and not nested:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected "{"')
|
2015-08-21 07:04:50 +00:00
|
|
|
if self.tok == '{':
|
|
|
|
self.accept()
|
|
|
|
expr = self.get_members()
|
|
|
|
elif self.tok == '[':
|
|
|
|
self.accept()
|
|
|
|
expr = self.get_values()
|
qapi: Allow true, false and null in schema json
In the near term, we will use it for a sensible-looking
'gen':false inside command declarations, instead of the
current ugly 'gen':'no'.
In the long term, it will allow conversion from shorthand
with defaults mentioned only in side-band documentation:
'data':{'*flag':'bool', '*string':'str'}
into an explicit default value documentation, as in:
'data':{'flag':{'type':'bool', 'optional':true, 'default':true},
'string':{'type':'str', 'optional':true, 'default':null}}
We still don't parse integer values (also necessary before
we can allow explicit defaults), but that can come in a later
series.
Backports commit e53188ada516c814a729551be2448684d6d8ce08 from qemu
2018-02-19 18:57:43 +00:00
|
|
|
elif self.tok in "'tfn":
|
2015-08-21 07:04:50 +00:00
|
|
|
expr = self.val
|
|
|
|
self.accept()
|
|
|
|
else:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPIParseError(self, 'Expected "{", "[", string, '
|
|
|
|
'boolean or "null"')
|
2015-08-21 07:04:50 +00:00
|
|
|
return expr
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def get_doc(self, info):
|
|
|
|
if self.val != '##':
|
|
|
|
raise QAPIParseError(self, "Junk after '##' at start of "
|
|
|
|
"documentation comment")
|
|
|
|
|
|
|
|
doc = QAPIDoc(self, info)
|
|
|
|
self.accept(False)
|
|
|
|
while self.tok == '#':
|
|
|
|
if self.val.startswith('##'):
|
|
|
|
# End of doc comment
|
|
|
|
if self.val != '##':
|
|
|
|
raise QAPIParseError(self, "Junk after '##' at end of "
|
|
|
|
"documentation comment")
|
|
|
|
doc.end_comment()
|
|
|
|
self.accept()
|
|
|
|
return doc
|
|
|
|
else:
|
|
|
|
doc.append(self.val)
|
|
|
|
self.accept(False)
|
|
|
|
|
|
|
|
raise QAPIParseError(self, "Documentation comment must end with '##'")
|
|
|
|
|
|
|
|
|
2018-02-19 20:47:16 +00:00
|
|
|
#
|
|
|
|
# Semantic analysis of schema expressions
|
2018-02-19 21:43:28 +00:00
|
|
|
# TODO fold into QAPISchema
|
|
|
|
# TODO catching name collisions in generated code would be nice
|
2018-02-19 20:47:16 +00:00
|
|
|
#
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-22 03:19:55 +00:00
|
|
|
def find_base_members(base):
|
2018-02-22 03:54:15 +00:00
|
|
|
if isinstance(base, dict):
|
|
|
|
return base
|
2018-03-03 23:23:47 +00:00
|
|
|
base_struct_define = struct_types.get(base)
|
2015-08-21 07:04:50 +00:00
|
|
|
if not base_struct_define:
|
|
|
|
return None
|
|
|
|
return base_struct_define['data']
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-19 18:44:13 +00:00
|
|
|
# Return the qtype of an alternate branch, or None on error.
|
|
|
|
def find_alternate_member_qtype(qapi_type):
|
2018-02-19 23:07:49 +00:00
|
|
|
if qapi_type in builtin_types:
|
2018-02-19 18:34:18 +00:00
|
|
|
return builtin_types[qapi_type]
|
2018-03-03 23:23:47 +00:00
|
|
|
elif qapi_type in struct_types:
|
|
|
|
return 'QTYPE_QDICT'
|
|
|
|
elif qapi_type in enum_types:
|
|
|
|
return 'QTYPE_QSTRING'
|
|
|
|
elif qapi_type in union_types:
|
|
|
|
return 'QTYPE_QDICT'
|
2018-02-19 18:34:18 +00:00
|
|
|
return None
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
# Return the discriminator enum define if discriminator is specified as an
|
|
|
|
# enum type, otherwise return None.
|
|
|
|
def discriminator_find_enum_define(expr):
|
|
|
|
base = expr.get('base')
|
|
|
|
discriminator = expr.get('discriminator')
|
|
|
|
|
|
|
|
if not (discriminator and base):
|
|
|
|
return None
|
|
|
|
|
2018-02-22 03:19:55 +00:00
|
|
|
base_members = find_base_members(base)
|
|
|
|
if not base_members:
|
2015-08-21 07:04:50 +00:00
|
|
|
return None
|
|
|
|
|
2018-02-22 03:19:55 +00:00
|
|
|
discriminator_type = base_members.get(discriminator)
|
2015-08-21 07:04:50 +00:00
|
|
|
if not discriminator_type:
|
|
|
|
return None
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
return enum_types.get(discriminator_type)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-20 01:33:16 +00:00
|
|
|
# Names must be letters, numbers, -, and _. They must start with letter,
|
|
|
|
# except for downstream extensions which must start with __RFQDN_.
|
|
|
|
# Dots are only valid in the downstream extension prefix.
|
2018-03-03 23:23:47 +00:00
|
|
|
valid_name = re.compile(r'^(__[a-zA-Z0-9.-]+_)?'
|
2018-02-20 01:33:16 +00:00
|
|
|
'[a-zA-Z][a-zA-Z0-9_-]*$')
|
|
|
|
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_name(info, source, name, allow_optional=False,
|
2018-02-19 23:07:49 +00:00
|
|
|
enum_member=False):
|
2018-02-19 19:04:46 +00:00
|
|
|
global valid_name
|
|
|
|
membername = name
|
|
|
|
|
|
|
|
if not isinstance(name, str):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s requires a string name" % source)
|
2018-02-19 19:04:46 +00:00
|
|
|
if name.startswith('*'):
|
|
|
|
membername = name[1:]
|
|
|
|
if not allow_optional:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s does not allow optional name '%s'"
|
|
|
|
% (source, name))
|
2018-02-19 19:04:46 +00:00
|
|
|
# Enum members can start with a digit, because the generated C
|
|
|
|
# code always prefixes it with the enum name
|
2018-02-20 01:33:16 +00:00
|
|
|
if enum_member and membername[0].isdigit():
|
|
|
|
membername = 'D' + membername
|
2018-02-22 03:35:11 +00:00
|
|
|
# Reserve the entire 'q_' namespace for c_name(), and for 'q_empty'
|
|
|
|
# and 'q_obj_*' implicit type names.
|
qapi: Reserve 'q_*' and 'has_*' member names
c_name() produces names starting with 'q_' when protecting a
dictionary member name that would fail to directly compile, but
in doing so can cause clashes with any member name already
beginning with 'q-' or 'q_'. Likewise, we create a C name 'has_'
for any optional member that can clash with any member name
beginning with 'has-' or 'has_'.
Technically, rather than blindly reserving the namespace,
we could try to complain about user names only when an actual
collision occurs, or even teach c_name() how to munge names
to avoid collisions. But it is not trivial, especially when
collisions can occur across multiple types (such as via
inheritance or flat unions). Besides, no existing .json
files are trying to use these names. So it's easier to just
outright forbid the potential for collision. We can always
relax things in the future if a real need arises for QMP to
express member names that have been forbidden here.
'has_' only has to be reserved for struct/union member names,
while 'q_' is reserved everywhere (matching the fact that
only members can be optional, while we use c_name() for munging
both members and entities). Note that we could relax 'q_'
restrictions on entities independently from member names; for
example, c_name('qmp_' + 'unix') would result in a different
function name than our current 'qmp_' + c_name('unix').
Update and add tests to cover the new error messages.
Backports commit 9fb081e0b98409556d023c7193eeb68947cd1211 from qemu
2018-02-20 00:09:15 +00:00
|
|
|
if not valid_name.match(membername) or \
|
|
|
|
c_name(membername, False).startswith('q_'):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
|
2018-02-19 19:04:46 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
|
|
|
def add_name(name, info, meta, implicit=False):
|
2018-02-19 20:47:16 +00:00
|
|
|
global all_names
|
|
|
|
check_name(info, "'%s'" % meta, name)
|
2018-02-19 21:22:16 +00:00
|
|
|
# FIXME should reject names that differ only in '_' vs. '.'
|
|
|
|
# vs. '-', because they're liable to clash in generated C.
|
2018-02-19 20:47:16 +00:00
|
|
|
if name in all_names:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s '%s' is already defined"
|
|
|
|
% (all_names[name], name))
|
2018-02-20 00:08:11 +00:00
|
|
|
if not implicit and (name.endswith('Kind') or name.endswith('List')):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s '%s' should not end in '%s'"
|
|
|
|
% (meta, name, name[-4:]))
|
2018-02-19 20:47:16 +00:00
|
|
|
all_names[name] = meta
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-07-05 15:41:33 +00:00
|
|
|
def check_if(expr, info):
|
|
|
|
|
|
|
|
def check_if_str(ifcond, info):
|
|
|
|
if not isinstance(ifcond, str):
|
|
|
|
raise QAPISemError(
|
|
|
|
info, "'if' condition must be a string or a list of strings")
|
|
|
|
if ifcond == '':
|
|
|
|
raise QAPISemError(info, "'if' condition '' makes no sense")
|
|
|
|
|
|
|
|
ifcond = expr.get('if')
|
|
|
|
if ifcond is None:
|
|
|
|
return
|
|
|
|
if isinstance(ifcond, list):
|
|
|
|
if ifcond == []:
|
|
|
|
raise QAPISemError(info, "'if' condition [] is useless")
|
|
|
|
for elt in ifcond:
|
|
|
|
check_if_str(elt, info)
|
|
|
|
else:
|
|
|
|
check_if_str(ifcond, info)
|
|
|
|
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_type(info, source, value, allow_array=False,
|
2018-02-19 23:07:49 +00:00
|
|
|
allow_dict=False, allow_optional=False,
|
|
|
|
allow_metas=[]):
|
2018-02-19 19:01:11 +00:00
|
|
|
global all_names
|
|
|
|
|
|
|
|
if value is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
# Check if array type for value is okay
|
|
|
|
if isinstance(value, list):
|
|
|
|
if not allow_array:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s cannot be an array" % source)
|
2018-02-19 19:01:11 +00:00
|
|
|
if len(value) != 1 or not isinstance(value[0], str):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"%s: array type must contain single type name" %
|
|
|
|
source)
|
2018-02-19 19:01:11 +00:00
|
|
|
value = value[0]
|
|
|
|
|
|
|
|
# Check if type name for value is okay
|
|
|
|
if isinstance(value, str):
|
2018-02-19 23:07:49 +00:00
|
|
|
if value not in all_names:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s uses unknown type '%s'"
|
|
|
|
% (source, value))
|
2018-02-19 19:01:11 +00:00
|
|
|
if not all_names[value] in allow_metas:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s cannot use %s type '%s'" %
|
|
|
|
(source, all_names[value], value))
|
2018-02-19 19:01:11 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
if not allow_dict:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s should be a type name" % source)
|
|
|
|
|
2018-02-19 21:30:47 +00:00
|
|
|
if not isinstance(value, OrderedDict):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"%s should be a dictionary or type name" % source)
|
2018-02-19 21:30:47 +00:00
|
|
|
|
|
|
|
# value is a dictionary, check that each member is okay
|
2018-02-19 19:01:11 +00:00
|
|
|
for (key, arg) in value.items():
|
2018-03-03 23:23:47 +00:00
|
|
|
check_name(info, "Member of %s" % source, key,
|
2018-02-19 19:04:46 +00:00
|
|
|
allow_optional=allow_optional)
|
2018-02-20 00:28:46 +00:00
|
|
|
if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Member of %s uses reserved name '%s'"
|
|
|
|
% (source, key))
|
2018-02-19 19:28:44 +00:00
|
|
|
# Todo: allow dictionaries to represent default values of
|
|
|
|
# an optional argument.
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "Member '%s' of %s" % (key, source), arg,
|
2018-02-19 22:49:43 +00:00
|
|
|
allow_array=True,
|
2018-02-19 19:01:11 +00:00
|
|
|
allow_metas=['built-in', 'union', 'alternate', 'struct',
|
2018-02-19 19:28:44 +00:00
|
|
|
'enum'])
|
2018-02-19 19:01:11 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_command(expr, info):
|
2018-02-19 19:01:11 +00:00
|
|
|
name = expr['command']
|
2018-02-26 01:22:00 +00:00
|
|
|
boxed = expr.get('boxed', False)
|
2018-02-19 19:09:01 +00:00
|
|
|
|
2018-02-26 01:22:00 +00:00
|
|
|
args_meta = ['struct']
|
|
|
|
if boxed:
|
|
|
|
args_meta += ['union', 'alternate']
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "'data' for command '%s'" % name,
|
2018-02-26 01:22:00 +00:00
|
|
|
expr.get('data'), allow_dict=not boxed, allow_optional=True,
|
|
|
|
allow_metas=args_meta)
|
2018-02-19 19:06:22 +00:00
|
|
|
returns_meta = ['union', 'struct']
|
|
|
|
if name in returns_whitelist:
|
|
|
|
returns_meta += ['built-in', 'alternate', 'enum']
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "'returns' for command '%s'" % name,
|
qapi: Command returning anonymous type doesn't work, outlaw
Reproducer: with
{ 'command': 'user_def_cmd4', 'returns': { 'a': 'int' } }
added to qapi-schema-test.json, qapi-commands.py dies when it tries to
generate the command handler function
Traceback (most recent call last):
File "/work/armbru/qemu/scripts/qapi-commands.py", line 359, in <module>
ret = generate_command_decl(cmd['command'], arglist, ret_type) + "\n"
File "/work/armbru/qemu/scripts/qapi-commands.py", line 29, in generate_command_decl
ret_type=c_type(ret_type), name=c_name(name),
File "/work/armbru/qemu/scripts/qapi.py", line 927, in c_type
assert isinstance(value, str) and value != ""
AssertionError
because the return type doesn't exist.
Simply outlaw this usage, and drop or dumb down test cases accordingly.
Backports commit 9b090d42aea9a0abbf39a1d75561a186057b5fe6 from qemu
2018-02-19 21:24:11 +00:00
|
|
|
expr.get('returns'), allow_array=True,
|
2018-02-19 22:49:43 +00:00
|
|
|
allow_optional=True, allow_metas=returns_meta)
|
2018-02-19 19:01:11 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_event(expr, info):
|
2018-02-19 18:56:02 +00:00
|
|
|
name = expr['event']
|
2018-02-26 01:22:00 +00:00
|
|
|
boxed = expr.get('boxed', False)
|
2018-02-19 18:56:02 +00:00
|
|
|
|
2018-02-26 01:22:00 +00:00
|
|
|
meta = ['struct']
|
|
|
|
if boxed:
|
|
|
|
meta += ['union', 'alternate']
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "'data' for event '%s'" % name,
|
2018-02-26 01:22:00 +00:00
|
|
|
expr.get('data'), allow_dict=not boxed, allow_optional=True,
|
|
|
|
allow_metas=meta)
|
2018-02-19 18:56:02 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_union(expr, info):
|
2015-08-21 07:04:50 +00:00
|
|
|
name = expr['union']
|
|
|
|
base = expr.get('base')
|
|
|
|
discriminator = expr.get('discriminator')
|
|
|
|
members = expr['data']
|
|
|
|
|
2018-02-19 18:44:13 +00:00
|
|
|
# Two types of unions, determined by discriminator.
|
|
|
|
|
|
|
|
# With no discriminator it is a simple union.
|
|
|
|
if discriminator is None:
|
2015-08-21 07:04:50 +00:00
|
|
|
enum_define = None
|
2018-02-19 23:07:49 +00:00
|
|
|
allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
|
2018-02-19 18:34:18 +00:00
|
|
|
if base is not None:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Simple union '%s' must not have a base" %
|
|
|
|
name)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
|
|
|
# Else, it's a flat union.
|
|
|
|
else:
|
2018-02-22 03:54:15 +00:00
|
|
|
# The object must have a string or dictionary 'base'.
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "'base' for union '%s'" % name,
|
2018-02-22 03:54:15 +00:00
|
|
|
base, allow_dict=True, allow_optional=True,
|
|
|
|
allow_metas=['struct'])
|
2018-02-19 23:10:23 +00:00
|
|
|
if not base:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Flat union '%s' must have a base"
|
|
|
|
% name)
|
|
|
|
base_members = find_base_members(base)
|
|
|
|
assert base_members is not None
|
2018-02-19 23:10:23 +00:00
|
|
|
|
2018-02-19 19:04:46 +00:00
|
|
|
# The value of member 'discriminator' must name a non-optional
|
2018-02-19 19:13:27 +00:00
|
|
|
# member of the base struct.
|
2018-03-03 23:23:47 +00:00
|
|
|
check_name(info, "Discriminator of flat union '%s'" % name,
|
2018-02-19 19:04:46 +00:00
|
|
|
discriminator)
|
2018-02-22 03:19:55 +00:00
|
|
|
discriminator_type = base_members.get(discriminator)
|
2015-08-21 07:04:50 +00:00
|
|
|
if not discriminator_type:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Discriminator '%s' is not a member of base "
|
|
|
|
"struct '%s'"
|
|
|
|
% (discriminator, base))
|
|
|
|
enum_define = enum_types.get(discriminator_type)
|
2018-02-19 23:07:49 +00:00
|
|
|
allow_metas = ['struct']
|
2015-08-21 07:04:50 +00:00
|
|
|
# Do not allow string discriminator
|
|
|
|
if not enum_define:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Discriminator '%s' must be of enumeration "
|
|
|
|
"type" % discriminator)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-20 20:45:21 +00:00
|
|
|
# Check every branch; don't allow an empty union
|
|
|
|
if len(members) == 0:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
|
2015-08-21 07:04:50 +00:00
|
|
|
for (key, value) in members.items():
|
2018-03-03 23:23:47 +00:00
|
|
|
check_name(info, "Member of union '%s'" % name, key)
|
2018-02-19 19:04:46 +00:00
|
|
|
|
2018-02-20 03:11:24 +00:00
|
|
|
# Each value must name a known type
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "Member '%s' of union '%s'" % (key, name),
|
2018-02-19 20:48:10 +00:00
|
|
|
value, allow_array=not base, allow_metas=allow_metas)
|
2018-02-19 19:01:11 +00:00
|
|
|
|
2018-02-19 18:34:18 +00:00
|
|
|
# If the discriminator names an enum type, then all members
|
2018-02-20 01:24:24 +00:00
|
|
|
# of 'data' must also be members of the enum type.
|
2018-02-19 18:34:18 +00:00
|
|
|
if enum_define:
|
2018-03-03 23:23:47 +00:00
|
|
|
if key not in enum_define['data']:
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Discriminator value '%s' is not found in "
|
|
|
|
"enum '%s'"
|
|
|
|
% (key, enum_define['enum']))
|
2018-02-19 18:34:18 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_alternate(expr, info):
|
2018-02-19 18:49:54 +00:00
|
|
|
name = expr['alternate']
|
2018-02-19 18:44:13 +00:00
|
|
|
members = expr['data']
|
|
|
|
types_seen = {}
|
|
|
|
|
2018-02-20 20:45:21 +00:00
|
|
|
# Check every branch; require at least two branches
|
|
|
|
if len(members) < 2:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Alternate '%s' should have at least two branches "
|
|
|
|
"in 'data'" % name)
|
2018-02-19 18:44:13 +00:00
|
|
|
for (key, value) in members.items():
|
2018-03-03 23:23:47 +00:00
|
|
|
check_name(info, "Member of alternate '%s'" % name, key)
|
2018-02-19 19:04:46 +00:00
|
|
|
|
2018-02-19 18:44:13 +00:00
|
|
|
# Ensure alternates have no type conflicts.
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "Member '%s' of alternate '%s'" % (key, name),
|
2018-02-19 19:01:11 +00:00
|
|
|
value,
|
|
|
|
allow_metas=['built-in', 'union', 'struct', 'enum'])
|
2018-02-19 18:44:13 +00:00
|
|
|
qtype = find_alternate_member_qtype(value)
|
2018-02-20 20:47:11 +00:00
|
|
|
if not qtype:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
|
|
|
|
"type '%s'" % (name, key, value))
|
|
|
|
conflicting = set([qtype])
|
|
|
|
if qtype == 'QTYPE_QSTRING':
|
|
|
|
enum_expr = enum_types.get(value)
|
|
|
|
if enum_expr:
|
|
|
|
for v in enum_expr['data']:
|
|
|
|
if v in ['on', 'off']:
|
|
|
|
conflicting.add('QTYPE_QBOOL')
|
|
|
|
if re.match(r'[-+0-9.]', v): # lazy, could be tightened
|
|
|
|
conflicting.add('QTYPE_QNUM')
|
|
|
|
else:
|
|
|
|
conflicting.add('QTYPE_QNUM')
|
|
|
|
conflicting.add('QTYPE_QBOOL')
|
|
|
|
for qt in conflicting:
|
qapi: Fix error handling code on alternate conflict
The conflict check added by commit c0644771 ("qapi: Reject
alternates that can't work with keyval_parse()") doesn't work
with the following declaration:
{ 'alternate': 'Alt',
'data': { 'one': 'bool',
'two': 'str' } }
It crashes with:
Traceback (most recent call last):
File "./scripts/qapi-types.py", line 295, in <module>
schema = QAPISchema(input_file)
File "/home/ehabkost/rh/proj/virt/qemu/scripts/qapi.py", line 1468, in __init__
self.exprs = check_exprs(parser.exprs)
File "/home/ehabkost/rh/proj/virt/qemu/scripts/qapi.py", line 958, in check_exprs
check_alternate(expr, info)
File "/home/ehabkost/rh/proj/virt/qemu/scripts/qapi.py", line 830, in check_alternate
% (name, key, types_seen[qtype]))
KeyError: 'QTYPE_QSTRING'
This happens because the previously-seen conflicting member
('one') can't be found at types_seen[qtype], but at
types_seen['QTYPE_BOOL'].
Fix the bug by moving the error check to the same loop that adds
new items to types_seen, raising an exception if types_seen[qt]
is already set.
Backports commit fda72ab4510bcc680a3c4fe55997aa29589884f7 from qemu
2018-03-07 21:57:53 +00:00
|
|
|
if qt in types_seen:
|
|
|
|
raise QAPISemError(info, "Alternate '%s' member '%s' can't "
|
|
|
|
"be distinguished from member '%s'"
|
|
|
|
% (name, key, types_seen[qt]))
|
2018-03-03 23:23:47 +00:00
|
|
|
types_seen[qt] = key
|
|
|
|
|
|
|
|
|
|
|
|
def check_enum(expr, info):
|
2018-02-19 18:23:52 +00:00
|
|
|
name = expr['enum']
|
|
|
|
members = expr.get('data')
|
2018-02-19 21:38:38 +00:00
|
|
|
prefix = expr.get('prefix')
|
2018-02-19 18:23:52 +00:00
|
|
|
|
|
|
|
if not isinstance(members, list):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Enum '%s' requires an array for 'data'" % name)
|
2018-02-19 21:38:38 +00:00
|
|
|
if prefix is not None and not isinstance(prefix, str):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"Enum '%s' requires a string for 'prefix'" % name)
|
2018-02-19 18:23:52 +00:00
|
|
|
for member in members:
|
2018-03-03 23:23:47 +00:00
|
|
|
check_name(info, "Member of enum '%s'" % name, member,
|
2018-02-19 19:04:46 +00:00
|
|
|
enum_member=True)
|
2018-02-19 18:23:52 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_struct(expr, info):
|
2018-02-19 19:13:27 +00:00
|
|
|
name = expr['struct']
|
2018-02-19 19:01:11 +00:00
|
|
|
members = expr['data']
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "'data' for struct '%s'" % name, members,
|
2018-02-19 19:04:46 +00:00
|
|
|
allow_dict=True, allow_optional=True)
|
2018-03-03 23:23:47 +00:00
|
|
|
check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
|
2018-02-19 19:01:11 +00:00
|
|
|
allow_metas=['struct'])
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-19 18:52:10 +00:00
|
|
|
def check_keys(expr_elem, meta, required, optional=[]):
|
|
|
|
expr = expr_elem['expr']
|
|
|
|
info = expr_elem['info']
|
|
|
|
name = expr[meta]
|
|
|
|
if not isinstance(name, str):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "'%s' key must have a string value" % meta)
|
2018-02-19 23:07:49 +00:00
|
|
|
required = required + [meta]
|
2018-02-19 18:52:10 +00:00
|
|
|
for (key, value) in expr.items():
|
2018-02-19 23:07:49 +00:00
|
|
|
if key not in required and key not in optional:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Unknown key '%s' in %s '%s'"
|
|
|
|
% (key, meta, name))
|
2018-08-16 11:14:31 +00:00
|
|
|
if key in ['gen', 'success-response'] and value is not False:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"'%s' of %s '%s' should only use false value"
|
|
|
|
% (key, meta, name))
|
2018-08-16 11:14:31 +00:00
|
|
|
if (key in ['boxed', 'allow-oob', 'allow-preconfig']
|
|
|
|
and value is not True):
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info,
|
|
|
|
"'%s' of %s '%s' should only use true value"
|
|
|
|
% (key, meta, name))
|
2018-07-05 15:41:33 +00:00
|
|
|
if key == 'if':
|
|
|
|
check_if(expr, info)
|
2018-02-19 18:52:10 +00:00
|
|
|
for key in required:
|
2018-02-19 23:07:49 +00:00
|
|
|
if key not in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "Key '%s' is missing from %s '%s'"
|
|
|
|
% (key, meta, name))
|
2018-02-19 18:52:10 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-19 20:44:18 +00:00
|
|
|
def check_exprs(exprs):
|
2018-02-19 18:56:02 +00:00
|
|
|
global all_names
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
# Populate name table with names of built-in types
|
2018-02-19 20:44:18 +00:00
|
|
|
for builtin in builtin_types.keys():
|
|
|
|
all_names[builtin] = 'built-in'
|
2018-03-03 23:23:47 +00:00
|
|
|
|
|
|
|
# Learn the types and check for valid expression keys
|
2018-02-19 20:44:18 +00:00
|
|
|
for expr_elem in exprs:
|
|
|
|
expr = expr_elem['expr']
|
|
|
|
info = expr_elem['info']
|
2018-03-03 23:23:47 +00:00
|
|
|
doc = expr_elem.get('doc')
|
|
|
|
|
2018-03-09 13:58:47 +00:00
|
|
|
if 'include' in expr:
|
|
|
|
continue
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
if not doc and doc_required:
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"Expression missing documentation comment")
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
if 'enum' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
meta = 'enum'
|
2018-07-05 15:41:33 +00:00
|
|
|
check_keys(expr_elem, 'enum', ['data'], ['if', 'prefix'])
|
2018-03-03 23:23:47 +00:00
|
|
|
enum_types[expr[meta]] = expr
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'union' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
meta = 'union'
|
2018-02-19 20:44:18 +00:00
|
|
|
check_keys(expr_elem, 'union', ['data'],
|
2018-07-05 15:41:33 +00:00
|
|
|
['base', 'discriminator', 'if'])
|
2018-03-03 23:23:47 +00:00
|
|
|
union_types[expr[meta]] = expr
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'alternate' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
meta = 'alternate'
|
2018-07-05 15:41:33 +00:00
|
|
|
check_keys(expr_elem, 'alternate', ['data'], ['if'])
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'struct' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
meta = 'struct'
|
2018-07-05 15:41:33 +00:00
|
|
|
check_keys(expr_elem, 'struct', ['data'], ['base', 'if'])
|
2018-03-03 23:23:47 +00:00
|
|
|
struct_types[expr[meta]] = expr
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'command' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
meta = 'command'
|
2018-02-19 20:44:18 +00:00
|
|
|
check_keys(expr_elem, 'command', [],
|
2018-07-05 15:34:35 +00:00
|
|
|
['data', 'returns', 'gen', 'success-response',
|
2018-07-05 15:41:33 +00:00
|
|
|
'boxed', 'allow-oob', 'allow-preconfig', 'if'])
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'event' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
meta = 'event'
|
2018-07-05 15:41:33 +00:00
|
|
|
check_keys(expr_elem, 'event', [], ['data', 'boxed', 'if'])
|
2018-02-19 20:44:18 +00:00
|
|
|
else:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(expr_elem['info'],
|
|
|
|
"Expression is missing metatype")
|
|
|
|
name = expr[meta]
|
|
|
|
add_name(name, info, meta)
|
|
|
|
if doc and doc.symbol != name:
|
|
|
|
raise QAPISemError(info, "Definition of '%s' follows documentation"
|
|
|
|
" for '%s'" % (name, doc.symbol))
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 20:44:18 +00:00
|
|
|
# Try again for hidden UnionKind enum
|
|
|
|
for expr_elem in exprs:
|
|
|
|
expr = expr_elem['expr']
|
2018-03-09 13:58:47 +00:00
|
|
|
|
|
|
|
if 'include' in expr:
|
|
|
|
continue
|
2018-03-03 23:23:47 +00:00
|
|
|
if 'union' in expr and not discriminator_find_enum_define(expr):
|
|
|
|
name = '%sKind' % expr['union']
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'alternate' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
name = '%sKind' % expr['alternate']
|
|
|
|
else:
|
|
|
|
continue
|
|
|
|
enum_types[name] = {'enum': name}
|
|
|
|
add_name(name, info, 'enum', implicit=True)
|
2018-02-19 20:44:18 +00:00
|
|
|
|
|
|
|
# Validate that exprs make sense
|
|
|
|
for expr_elem in exprs:
|
|
|
|
expr = expr_elem['expr']
|
|
|
|
info = expr_elem['info']
|
2018-03-03 23:23:47 +00:00
|
|
|
doc = expr_elem.get('doc')
|
2018-02-19 18:37:31 +00:00
|
|
|
|
2018-03-09 13:58:47 +00:00
|
|
|
if 'include' in expr:
|
|
|
|
continue
|
2018-02-19 23:07:49 +00:00
|
|
|
if 'enum' in expr:
|
2018-02-19 20:44:18 +00:00
|
|
|
check_enum(expr, info)
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'union' in expr:
|
2018-02-19 20:44:18 +00:00
|
|
|
check_union(expr, info)
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'alternate' in expr:
|
2018-02-19 20:44:18 +00:00
|
|
|
check_alternate(expr, info)
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'struct' in expr:
|
2018-02-19 20:44:18 +00:00
|
|
|
check_struct(expr, info)
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'command' in expr:
|
2018-02-19 20:44:18 +00:00
|
|
|
check_command(expr, info)
|
2018-02-19 23:07:49 +00:00
|
|
|
elif 'event' in expr:
|
2018-02-19 20:44:18 +00:00
|
|
|
check_event(expr, info)
|
|
|
|
else:
|
|
|
|
assert False, 'unexpected meta type'
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
if doc:
|
|
|
|
doc.check_expr(expr)
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
return exprs
|
2018-02-19 20:44:18 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
#
|
|
|
|
# Schema compiler frontend
|
|
|
|
#
|
|
|
|
|
2018-07-05 15:47:52 +00:00
|
|
|
def listify_cond(ifcond):
|
|
|
|
if not ifcond:
|
|
|
|
return []
|
|
|
|
if not isinstance(ifcond, list):
|
|
|
|
return [ifcond]
|
|
|
|
return ifcond
|
|
|
|
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
class QAPISchemaEntity(object):
|
2018-07-05 15:47:52 +00:00
|
|
|
def __init__(self, name, info, doc, ifcond=None):
|
2018-03-09 14:03:14 +00:00
|
|
|
assert name is None or isinstance(name, str)
|
2018-02-19 21:43:28 +00:00
|
|
|
self.name = name
|
2018-03-09 14:03:14 +00:00
|
|
|
self.module = None
|
2018-02-20 00:04:36 +00:00
|
|
|
# For explicitly defined entities, info points to the (explicit)
|
|
|
|
# definition. For builtins (and their arrays), info is None.
|
|
|
|
# For implicitly defined entities, info points to a place that
|
|
|
|
# triggered the implicit definition (there may be more than one
|
|
|
|
# such place).
|
2018-02-19 21:43:28 +00:00
|
|
|
self.info = info
|
2018-03-03 23:23:47 +00:00
|
|
|
self.doc = doc
|
2018-07-05 16:00:03 +00:00
|
|
|
self._ifcond = ifcond # self.ifcond is set only after .check()
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-19 21:47:32 +00:00
|
|
|
def c_name(self):
|
|
|
|
return c_name(self.name)
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
def check(self, schema):
|
2018-07-05 16:00:03 +00:00
|
|
|
if isinstance(self._ifcond, QAPISchemaType):
|
|
|
|
# inherit the condition from a type
|
|
|
|
typ = self._ifcond
|
|
|
|
typ.check(schema)
|
|
|
|
self.ifcond = typ.ifcond
|
|
|
|
else:
|
|
|
|
self.ifcond = listify_cond(self._ifcond)
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-19 23:53:03 +00:00
|
|
|
def is_implicit(self):
|
|
|
|
return not self.info
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaVisitor(object):
|
|
|
|
def visit_begin(self, schema):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_end(self):
|
|
|
|
pass
|
|
|
|
|
2018-03-09 14:03:14 +00:00
|
|
|
def visit_module(self, fname):
|
|
|
|
pass
|
|
|
|
|
2018-02-19 23:49:02 +00:00
|
|
|
def visit_needed(self, entity):
|
|
|
|
# Default to visiting everything
|
|
|
|
return True
|
|
|
|
|
2018-03-09 14:03:14 +00:00
|
|
|
def visit_include(self, fname, info):
|
|
|
|
pass
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit_builtin_type(self, name, info, json_type):
|
|
|
|
pass
|
|
|
|
|
2018-07-05 16:04:06 +00:00
|
|
|
def visit_enum_type(self, name, info, ifcond, values, prefix):
|
2018-02-19 21:49:57 +00:00
|
|
|
pass
|
|
|
|
|
2018-07-05 16:04:06 +00:00
|
|
|
def visit_array_type(self, name, info, ifcond, element_type):
|
2018-02-19 21:49:57 +00:00
|
|
|
pass
|
|
|
|
|
2018-07-05 16:04:06 +00:00
|
|
|
def visit_object_type(self, name, info, ifcond, base, members, variants):
|
2018-02-19 21:49:57 +00:00
|
|
|
pass
|
|
|
|
|
2018-07-05 16:04:06 +00:00
|
|
|
def visit_object_type_flat(self, name, info, ifcond, members, variants):
|
qapi: New QMP command query-qmp-schema for QMP introspection
qapi/introspect.json defines the introspection schema. It's designed
for QMP introspection, but should do for similar uses, such as QGA.
The introspection schema does not reflect all the rules and
restrictions that apply to QAPI schemata. A valid QAPI schema has an
introspection value conforming to the introspection schema, but the
converse is not true.
Introspection lowers away a number of schema details, and makes
implicit things explicit:
* The built-in types are declared with their JSON type.
All integer types are mapped to 'int', because how many bits we use
internally is an implementation detail. It could be pressed into
external interface service as very approximate range information,
but that's a bad idea. If we need range information, we better do
it properly.
* Implicit type definitions are made explicit, and given
auto-generated names:
- Array types, named by appending "List" to the name of their
element type, like in generated C.
- The enumeration types implicitly defined by simple union types,
named by appending "Kind" to the name of their simple union type,
like in generated C.
- Types that don't occur in generated C. Their names start with ':'
so they don't clash with the user's names.
* All type references are by name.
* The struct and union types are generalized into an object type.
* Base types are flattened.
* Commands take a single argument and return a single result.
Dictionary argument or list result is an implicit type definition.
The empty object type is used when a command takes no arguments or
produces no results.
The argument is always of object type, but the introspection schema
doesn't reflect that.
The 'gen': false directive is omitted as implementation detail.
The 'success-response' directive is omitted as well for now, even
though it's not an implementation detail, because it's not used by
QMP.
* Events carry a single data value.
Implicit type definition and empty object type use, just like for
commands.
The value is of object type, but the introspection schema doesn't
reflect that.
* Types not used by commands or events are omitted.
Indirect use counts as use.
* Optional members have a default, which can only be null right now
Instead of a mandatory "optional" flag, we have an optional default.
No default means mandatory, default null means optional without
default value. Non-null is available for optional with default
(possible future extension).
* Clients should *not* look up types by name, because type names are
not ABI. Look up the command or event you're interested in, then
follow the references.
TODO Should we hide the type names to eliminate the temptation?
New generator scripts/qapi-introspect.py computes an introspection
value for its input, and generates a C variable holding it.
It can generate awfully long lines. Marked TODO.
A new test-qmp-input-visitor test case feeds its result for both
tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a
QmpInputVisitor to verify it actually conforms to the schema.
New QMP command query-qmp-schema takes its return value from that
variable. Its reply is some 85KiBytes for me right now.
If this turns out to be too much, we have a couple of options:
* We can use shorter names in the JSON. Not the QMP style.
* Optionally return the sub-schema for commands and events given as
arguments.
Right now qmp_query_schema() sends the string literal computed by
qmp-introspect.py. To compute sub-schema at run time, we'd have to
duplicate parts of qapi-introspect.py in C. Unattractive.
* Let clients cache the output of query-qmp-schema.
It changes only on QEMU upgrades, i.e. rarely. Provide a command
query-qmp-schema-hash. Clients can have a cache indexed by hash,
and re-query the schema only when they don't have it cached. Even
simpler: put the hash in the QMP greeting.
Backports commit 39a181581650f4d50f4445bc6276d9716cece050 from qemu
2018-02-19 22:54:00 +00:00
|
|
|
pass
|
|
|
|
|
2018-07-05 16:04:06 +00:00
|
|
|
def visit_alternate_type(self, name, info, ifcond, variants):
|
2018-02-19 21:49:57 +00:00
|
|
|
pass
|
|
|
|
|
2018-07-05 16:04:06 +00:00
|
|
|
def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
|
2018-07-05 15:39:30 +00:00
|
|
|
success_response, boxed, allow_oob, allow_preconfig):
|
2018-02-19 21:49:57 +00:00
|
|
|
pass
|
|
|
|
|
2018-07-05 16:04:06 +00:00
|
|
|
def visit_event(self, name, info, ifcond, arg_type, boxed):
|
2018-02-19 21:49:57 +00:00
|
|
|
pass
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-03-09 14:03:14 +00:00
|
|
|
class QAPISchemaInclude(QAPISchemaEntity):
|
|
|
|
|
|
|
|
def __init__(self, fname, info):
|
|
|
|
QAPISchemaEntity.__init__(self, None, info, None)
|
|
|
|
self.fname = fname
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
|
|
|
visitor.visit_include(self.fname, self.info)
|
|
|
|
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
class QAPISchemaType(QAPISchemaEntity):
|
2018-02-22 03:01:07 +00:00
|
|
|
# Return the C type for common use.
|
|
|
|
# For the types we commonly box, this is a pointer type.
|
|
|
|
def c_type(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Return the C type to be used in a parameter list.
|
|
|
|
def c_param_type(self):
|
|
|
|
return self.c_type()
|
|
|
|
|
|
|
|
# Return the C type to be used where we suppress boxing.
|
|
|
|
def c_unboxed_type(self):
|
|
|
|
return self.c_type()
|
2018-02-19 21:47:32 +00:00
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def alternate_qtype(self):
|
|
|
|
json2qtype = {
|
2018-03-07 21:52:39 +00:00
|
|
|
'null': 'QTYPE_QNULL',
|
2018-02-19 21:47:32 +00:00
|
|
|
'string': 'QTYPE_QSTRING',
|
2018-03-03 22:43:24 +00:00
|
|
|
'number': 'QTYPE_QNUM',
|
|
|
|
'int': 'QTYPE_QNUM',
|
2018-02-19 21:47:32 +00:00
|
|
|
'boolean': 'QTYPE_QBOOL',
|
|
|
|
'object': 'QTYPE_QDICT'
|
|
|
|
}
|
|
|
|
return json2qtype.get(self.json_type())
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def doc_type(self):
|
|
|
|
if self.is_implicit():
|
|
|
|
return None
|
|
|
|
return self.name
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchemaBuiltinType(QAPISchemaType):
|
2018-02-22 03:49:31 +00:00
|
|
|
def __init__(self, name, json_type, c_type):
|
2018-03-03 23:23:47 +00:00
|
|
|
QAPISchemaType.__init__(self, name, None, None)
|
2018-02-19 21:47:32 +00:00
|
|
|
assert not c_type or isinstance(c_type, str)
|
|
|
|
assert json_type in ('string', 'number', 'int', 'boolean', 'null',
|
|
|
|
'value')
|
|
|
|
self._json_type_name = json_type
|
|
|
|
self._c_type_name = c_type
|
|
|
|
|
|
|
|
def c_name(self):
|
|
|
|
return self.name
|
|
|
|
|
2018-02-22 03:01:07 +00:00
|
|
|
def c_type(self):
|
|
|
|
return self._c_type_name
|
|
|
|
|
|
|
|
def c_param_type(self):
|
|
|
|
if self.name == 'str':
|
2018-02-19 21:47:32 +00:00
|
|
|
return 'const ' + self._c_type_name
|
|
|
|
return self._c_type_name
|
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
return self._json_type_name
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def doc_type(self):
|
|
|
|
return self.json_type()
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
|
|
|
visitor.visit_builtin_type(self.name, self.info, self.json_type())
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchemaEnumType(QAPISchemaType):
|
2018-12-18 10:00:25 +00:00
|
|
|
def __init__(self, name, info, doc, ifcond, members, prefix):
|
2018-07-05 15:47:52 +00:00
|
|
|
QAPISchemaType.__init__(self, name, info, doc, ifcond)
|
2018-12-18 10:00:25 +00:00
|
|
|
for m in members:
|
|
|
|
assert isinstance(m, QAPISchemaMember)
|
|
|
|
m.set_owner(name)
|
2018-02-19 21:43:28 +00:00
|
|
|
assert prefix is None or isinstance(prefix, str)
|
2018-12-18 10:00:25 +00:00
|
|
|
self.members = members
|
2018-02-19 21:43:28 +00:00
|
|
|
self.prefix = prefix
|
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-05 16:00:03 +00:00
|
|
|
QAPISchemaType.check(self, schema)
|
2018-02-20 03:07:15 +00:00
|
|
|
seen = {}
|
2018-12-18 10:00:25 +00:00
|
|
|
for m in self.members:
|
|
|
|
m.check_clash(self.info, seen)
|
2018-03-03 23:23:47 +00:00
|
|
|
if self.doc:
|
2018-12-18 10:00:25 +00:00
|
|
|
self.doc.connect_member(m)
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-20 00:04:36 +00:00
|
|
|
def is_implicit(self):
|
2018-03-03 23:23:47 +00:00
|
|
|
# See QAPISchema._make_implicit_enum_type() and ._def_predefineds()
|
|
|
|
return self.name.endswith('Kind') or self.name == 'QType'
|
2018-02-20 00:04:36 +00:00
|
|
|
|
2018-02-22 03:01:07 +00:00
|
|
|
def c_type(self):
|
2018-02-19 21:47:32 +00:00
|
|
|
return c_name(self.name)
|
|
|
|
|
2018-02-20 03:07:15 +00:00
|
|
|
def member_names(self):
|
2018-12-18 10:00:25 +00:00
|
|
|
return [m.name for m in self.members]
|
2018-02-20 03:07:15 +00:00
|
|
|
|
2018-02-19 21:47:32 +00:00
|
|
|
def json_type(self):
|
|
|
|
return 'string'
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
2018-07-05 16:04:06 +00:00
|
|
|
visitor.visit_enum_type(self.name, self.info, self.ifcond,
|
2018-02-20 03:07:15 +00:00
|
|
|
self.member_names(), self.prefix)
|
2018-02-19 21:49:57 +00:00
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchemaArrayType(QAPISchemaType):
|
|
|
|
def __init__(self, name, info, element_type):
|
2018-07-05 15:47:52 +00:00
|
|
|
QAPISchemaType.__init__(self, name, info, None, None)
|
2018-02-19 21:43:28 +00:00
|
|
|
assert isinstance(element_type, str)
|
|
|
|
self._element_type_name = element_type
|
|
|
|
self.element_type = None
|
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-05 16:00:03 +00:00
|
|
|
QAPISchemaType.check(self, schema)
|
2018-02-19 21:43:28 +00:00
|
|
|
self.element_type = schema.lookup_type(self._element_type_name)
|
|
|
|
assert self.element_type
|
2018-07-05 16:00:03 +00:00
|
|
|
self.element_type.check(schema)
|
2018-07-05 15:47:52 +00:00
|
|
|
self.ifcond = self.element_type.ifcond
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-20 00:04:36 +00:00
|
|
|
def is_implicit(self):
|
|
|
|
return True
|
|
|
|
|
2018-02-22 03:01:07 +00:00
|
|
|
def c_type(self):
|
|
|
|
return c_name(self.name) + pointer_suffix
|
|
|
|
|
2018-02-19 21:47:32 +00:00
|
|
|
def json_type(self):
|
|
|
|
return 'array'
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def doc_type(self):
|
|
|
|
elt_doc_type = self.element_type.doc_type()
|
|
|
|
if not elt_doc_type:
|
|
|
|
return None
|
|
|
|
return 'array of ' + elt_doc_type
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
2018-07-05 16:04:06 +00:00
|
|
|
visitor.visit_array_type(self.name, self.info, self.ifcond,
|
|
|
|
self.element_type)
|
2018-02-19 21:49:57 +00:00
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchemaObjectType(QAPISchemaType):
|
2018-07-05 15:47:52 +00:00
|
|
|
def __init__(self, name, info, doc, ifcond,
|
|
|
|
base, local_members, variants):
|
qapi: Track simple union tag in object.local_members
We were previously creating all unions with an empty list for
local_members. However, it will make it easier to unify struct
and union generation if we include the generated tag member in
local_members. That way, we can have a common code pattern:
visit the base (if any), visit the local members (if any), visit
the variants (if any). The local_members of a flat union
remains empty (because the discriminator is already visited as
part of the base). Then, by visiting tag_member.check() during
AlternateType.check(), we no longer need to call it during
Variants.check().
The various front end entities now exist as follows:
struct: optional base, optional local_members, no variants
simple union: no base, one-element local_members, variants with tag_member
from local_members
flat union: base, no local_members, variants with tag_member from base
alternate: no base, no local_members, variants
With the new local members, we require a bit of finesse to
avoid assertions in the clients.
No change to generated code.
Backports commit da34a9bd999d1be13954c699bbae0295cdaa3200 from qemu
2018-02-20 01:02:00 +00:00
|
|
|
# struct has local_members, optional base, and no variants
|
|
|
|
# flat union has base, variants, and no local_members
|
|
|
|
# simple union has local_members, variants, and no base
|
2018-07-05 15:47:52 +00:00
|
|
|
QAPISchemaType.__init__(self, name, info, doc, ifcond)
|
2018-02-19 21:43:28 +00:00
|
|
|
assert base is None or isinstance(base, str)
|
|
|
|
for m in local_members:
|
|
|
|
assert isinstance(m, QAPISchemaObjectTypeMember)
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
m.set_owner(name)
|
|
|
|
if variants is not None:
|
|
|
|
assert isinstance(variants, QAPISchemaObjectTypeVariants)
|
|
|
|
variants.set_owner(name)
|
|
|
|
self._base_name = base
|
2018-02-19 21:43:28 +00:00
|
|
|
self.base = None
|
|
|
|
self.local_members = local_members
|
|
|
|
self.variants = variants
|
|
|
|
self.members = None
|
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-05 16:00:03 +00:00
|
|
|
QAPISchemaType.check(self, schema)
|
qapi: Detect base class loops
It should be fairly obvious that qapi base classes need to
form an acyclic graph, since QMP cannot specify the same
key more than once, while base classes are included as flat
members alongside other members added by the child. But the
old check_member_clash() parser function was not prepared to
check for this, and entered an infinite recursion (at least
until Python gives up, complaining about nesting too deep).
Now that check_member_clash() has been recently removed,
attempts at self-inheritance trigger an assertion failure
introduced by commit ac88219a. The obvious fix is to turn
the assertion into a conditional.
This patch includes both the tests (base-cycle-direct and
base-cycle-indirect) and the fix, since the .err file output
for the unfixed case is not useful (particularly when it was
warning about unbounded recursion, as that limit may be
platform-specific).
We don't need to worry about cycles in flat unions (neither
the base type nor the type of a variant can be a union) nor
in alternates (alternate branches cannot themselves be an
alternate). But if we later allow a union type as a variant,
we will still be okay, as QAPISchemaObjectTypeVariants.check()
triggers the same QAPISchemaObjectType.check() that will
detect any loops.
Likewise, we need not worry about the case of diamond
inheritance where the same class is used for a flat union base
class and one of its variants; either both uses will introduce
a collision in trying to insert the same member name twice, or
the shared type is empty and changes nothing.
Backports commit bac5429ccb4f41d421ec641b11f1852c8420fdb7 from qemu
2018-02-20 03:12:14 +00:00
|
|
|
if self.members is False: # check for cycles
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(self.info,
|
|
|
|
"Object %s contains itself" % self.name)
|
2018-02-19 21:43:28 +00:00
|
|
|
if self.members:
|
|
|
|
return
|
|
|
|
self.members = False # mark as being checked
|
2018-02-20 01:15:43 +00:00
|
|
|
seen = OrderedDict()
|
2018-02-19 21:43:28 +00:00
|
|
|
if self._base_name:
|
|
|
|
self.base = schema.lookup_type(self._base_name)
|
|
|
|
assert isinstance(self.base, QAPISchemaObjectType)
|
|
|
|
self.base.check(schema)
|
2018-03-03 23:23:47 +00:00
|
|
|
self.base.check_clash(self.info, seen)
|
2018-02-19 21:43:28 +00:00
|
|
|
for m in self.local_members:
|
2018-02-20 01:09:54 +00:00
|
|
|
m.check(schema)
|
2018-02-20 01:30:18 +00:00
|
|
|
m.check_clash(self.info, seen)
|
2018-03-03 23:23:47 +00:00
|
|
|
if self.doc:
|
|
|
|
self.doc.connect_member(m)
|
|
|
|
self.members = seen.values()
|
2018-02-19 21:43:28 +00:00
|
|
|
if self.variants:
|
2018-02-20 01:13:21 +00:00
|
|
|
self.variants.check(schema, seen)
|
2018-02-20 01:18:57 +00:00
|
|
|
assert self.variants.tag_member in self.members
|
2018-03-03 23:23:47 +00:00
|
|
|
self.variants.check_clash(self.info, seen)
|
|
|
|
if self.doc:
|
|
|
|
self.doc.check()
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-22 03:19:55 +00:00
|
|
|
# Check that the members of this type do not cause duplicate JSON members,
|
2018-02-20 01:30:18 +00:00
|
|
|
# and update seen to track the members seen so far. Report any errors
|
|
|
|
# on behalf of info, which is not necessarily self.info
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_clash(self, info, seen):
|
2018-02-20 01:23:33 +00:00
|
|
|
assert not self.variants # not implemented
|
|
|
|
for m in self.members:
|
2018-02-20 01:30:18 +00:00
|
|
|
m.check_clash(info, seen)
|
2018-02-20 01:23:33 +00:00
|
|
|
|
2018-02-20 00:04:36 +00:00
|
|
|
def is_implicit(self):
|
2018-02-22 03:35:11 +00:00
|
|
|
# See QAPISchema._make_implicit_object_type(), as well as
|
|
|
|
# _def_predefineds()
|
|
|
|
return self.name.startswith('q_')
|
2018-02-20 00:04:36 +00:00
|
|
|
|
2018-02-26 01:07:41 +00:00
|
|
|
def is_empty(self):
|
|
|
|
assert self.members is not None
|
|
|
|
return not self.members and not self.variants
|
|
|
|
|
2018-02-19 21:47:32 +00:00
|
|
|
def c_name(self):
|
2018-02-26 01:05:13 +00:00
|
|
|
assert self.name != 'q_empty'
|
2018-02-19 21:47:32 +00:00
|
|
|
return QAPISchemaType.c_name(self)
|
|
|
|
|
2018-02-22 03:01:07 +00:00
|
|
|
def c_type(self):
|
2018-02-19 23:53:03 +00:00
|
|
|
assert not self.is_implicit()
|
qapi: Don't box struct branch of alternate
There's no reason to do two malloc's for an alternate type visiting
a QAPI struct; let's just inline the struct directly as the C union
branch of the struct.
Surprisingly, no clients were actually using the struct member prior
to this patch outside of the testsuite; an earlier patch in the series
added some testsuite coverage to make the effect of this patch more
obvious.
In qapi.py, c_type() gains a new is_unboxed flag to control when we
are emitting a C struct unboxed within the context of an outer
struct (different from our other two modes of usage with no flags
for normal local variable declarations, and with is_param for adding
'const' in a parameter list). I don't know if there is any more
pythonic way of collapsing the two flags into a single parameter,
as we never have a caller setting both flags at once.
Ultimately, we want to also unbox branches for QAPI unions, but as
that touches a lot more client code, it is better as separate
patches. But since unions and alternates share gen_variants(), I
had to hack in a way to test if we are visiting an alternate type
for setting the is_unboxed flag: look for a non-object branch.
This works because alternates have at least two branches, with at
most one object branch, while unions have only object branches.
The hack will go away in a later patch.
The generated code difference to qapi-types.h is relatively small:
| struct BlockdevRef {
| QType type;
| union { /* union tag is @type */
| void *data;
|- BlockdevOptions *definition;
|+ BlockdevOptions definition;
| char *reference;
| } u;
| };
The corresponding spot in qapi-visit.c calls visit_type_FOO(), which
first calls visit_start_struct() to allocate or deallocate the member
and handle a layer of {} from the JSON stream, then visits the
members. To peel off the indirection and the memory management that
comes with it, we inline this call, then suppress allocation /
deallocation by passing NULL to visit_start_struct(), and adjust the
member visit:
| switch ((*obj)->type) {
| case QTYPE_QDICT:
|- visit_type_BlockdevOptions(v, name, &(*obj)->u.definition, &err);
|+ visit_start_struct(v, name, NULL, 0, &err);
|+ if (err) {
|+ break;
|+ }
|+ visit_type_BlockdevOptions_fields(v, &(*obj)->u.definition, &err);
|+ error_propagate(errp, err);
|+ err = NULL;
|+ visit_end_struct(v, &err);
| break;
| case QTYPE_QSTRING:
| visit_type_str(v, name, &(*obj)->u.reference, &err);
The visit of non-object fields is unchanged.
Backports commit becceedc4d9bc1435099c90a0514945a89844d3a from qemu
2018-02-20 21:30:59 +00:00
|
|
|
return c_name(self.name) + pointer_suffix
|
2018-02-19 21:47:32 +00:00
|
|
|
|
2018-02-22 03:01:07 +00:00
|
|
|
def c_unboxed_type(self):
|
|
|
|
return c_name(self.name)
|
|
|
|
|
2018-02-19 21:47:32 +00:00
|
|
|
def json_type(self):
|
|
|
|
return 'object'
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
2018-07-05 16:04:06 +00:00
|
|
|
visitor.visit_object_type(self.name, self.info, self.ifcond,
|
2018-02-19 21:49:57 +00:00
|
|
|
self.base, self.local_members, self.variants)
|
2018-07-05 16:04:06 +00:00
|
|
|
visitor.visit_object_type_flat(self.name, self.info, self.ifcond,
|
qapi: New QMP command query-qmp-schema for QMP introspection
qapi/introspect.json defines the introspection schema. It's designed
for QMP introspection, but should do for similar uses, such as QGA.
The introspection schema does not reflect all the rules and
restrictions that apply to QAPI schemata. A valid QAPI schema has an
introspection value conforming to the introspection schema, but the
converse is not true.
Introspection lowers away a number of schema details, and makes
implicit things explicit:
* The built-in types are declared with their JSON type.
All integer types are mapped to 'int', because how many bits we use
internally is an implementation detail. It could be pressed into
external interface service as very approximate range information,
but that's a bad idea. If we need range information, we better do
it properly.
* Implicit type definitions are made explicit, and given
auto-generated names:
- Array types, named by appending "List" to the name of their
element type, like in generated C.
- The enumeration types implicitly defined by simple union types,
named by appending "Kind" to the name of their simple union type,
like in generated C.
- Types that don't occur in generated C. Their names start with ':'
so they don't clash with the user's names.
* All type references are by name.
* The struct and union types are generalized into an object type.
* Base types are flattened.
* Commands take a single argument and return a single result.
Dictionary argument or list result is an implicit type definition.
The empty object type is used when a command takes no arguments or
produces no results.
The argument is always of object type, but the introspection schema
doesn't reflect that.
The 'gen': false directive is omitted as implementation detail.
The 'success-response' directive is omitted as well for now, even
though it's not an implementation detail, because it's not used by
QMP.
* Events carry a single data value.
Implicit type definition and empty object type use, just like for
commands.
The value is of object type, but the introspection schema doesn't
reflect that.
* Types not used by commands or events are omitted.
Indirect use counts as use.
* Optional members have a default, which can only be null right now
Instead of a mandatory "optional" flag, we have an optional default.
No default means mandatory, default null means optional without
default value. Non-null is available for optional with default
(possible future extension).
* Clients should *not* look up types by name, because type names are
not ABI. Look up the command or event you're interested in, then
follow the references.
TODO Should we hide the type names to eliminate the temptation?
New generator scripts/qapi-introspect.py computes an introspection
value for its input, and generates a C variable holding it.
It can generate awfully long lines. Marked TODO.
A new test-qmp-input-visitor test case feeds its result for both
tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a
QmpInputVisitor to verify it actually conforms to the schema.
New QMP command query-qmp-schema takes its return value from that
variable. Its reply is some 85KiBytes for me right now.
If this turns out to be too much, we have a couple of options:
* We can use shorter names in the JSON. Not the QMP style.
* Optionally return the sub-schema for commands and events given as
arguments.
Right now qmp_query_schema() sends the string literal computed by
qmp-introspect.py. To compute sub-schema at run time, we'd have to
duplicate parts of qapi-introspect.py in C. Unattractive.
* Let clients cache the output of query-qmp-schema.
It changes only on QEMU upgrades, i.e. rarely. Provide a command
query-qmp-schema-hash. Clients can have a cache indexed by hash,
and re-query the schema only when they don't have it cached. Even
simpler: put the hash in the QMP greeting.
Backports commit 39a181581650f4d50f4445bc6276d9716cece050 from qemu
2018-02-19 22:54:00 +00:00
|
|
|
self.members, self.variants)
|
2018-02-19 21:49:57 +00:00
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-20 03:04:36 +00:00
|
|
|
class QAPISchemaMember(object):
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
role = 'member'
|
|
|
|
|
2018-02-20 03:04:36 +00:00
|
|
|
def __init__(self, name):
|
2018-02-19 21:43:28 +00:00
|
|
|
assert isinstance(name, str)
|
|
|
|
self.name = name
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
self.owner = None
|
|
|
|
|
|
|
|
def set_owner(self, name):
|
|
|
|
assert not self.owner
|
|
|
|
self.owner = name
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-20 01:30:18 +00:00
|
|
|
def check_clash(self, info, seen):
|
|
|
|
cname = c_name(self.name)
|
2018-03-03 23:23:47 +00:00
|
|
|
if cname.lower() != cname and self.owner not in name_case_whitelist:
|
|
|
|
raise QAPISemError(info,
|
|
|
|
"%s should not use uppercase" % self.describe())
|
2018-02-20 01:30:18 +00:00
|
|
|
if cname in seen:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(info, "%s collides with %s" %
|
|
|
|
(self.describe(), seen[cname].describe()))
|
2018-02-20 01:30:18 +00:00
|
|
|
seen[cname] = self
|
2018-02-20 01:17:36 +00:00
|
|
|
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
def _pretty_owner(self):
|
|
|
|
owner = self.owner
|
2018-02-22 03:35:11 +00:00
|
|
|
if owner.startswith('q_obj_'):
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
# See QAPISchema._make_implicit_object_type() - reverse the
|
|
|
|
# mapping there to create a nice human-readable description
|
2018-02-22 03:35:11 +00:00
|
|
|
owner = owner[6:]
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
if owner.endswith('-arg'):
|
|
|
|
return '(parameter of %s)' % owner[:-4]
|
2018-02-22 03:54:15 +00:00
|
|
|
elif owner.endswith('-base'):
|
|
|
|
return '(base of %s)' % owner[:-5]
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
else:
|
|
|
|
assert owner.endswith('-wrapper')
|
|
|
|
# Unreachable and not implemented
|
|
|
|
assert False
|
2018-02-20 03:07:15 +00:00
|
|
|
if owner.endswith('Kind'):
|
|
|
|
# See QAPISchema._make_implicit_enum_type()
|
|
|
|
return '(branch of %s)' % owner[:-4]
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
return '(%s of %s)' % (self.role, owner)
|
|
|
|
|
|
|
|
def describe(self):
|
|
|
|
return "'%s' %s" % (self.name, self._pretty_owner())
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-20 03:04:36 +00:00
|
|
|
class QAPISchemaObjectTypeMember(QAPISchemaMember):
|
|
|
|
def __init__(self, name, typ, optional):
|
|
|
|
QAPISchemaMember.__init__(self, name)
|
|
|
|
assert isinstance(typ, str)
|
|
|
|
assert isinstance(optional, bool)
|
|
|
|
self._type_name = typ
|
|
|
|
self.type = None
|
|
|
|
self.optional = optional
|
|
|
|
|
|
|
|
def check(self, schema):
|
|
|
|
assert self.owner
|
|
|
|
self.type = schema.lookup_type(self._type_name)
|
|
|
|
assert self.type
|
|
|
|
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
class QAPISchemaObjectTypeVariants(object):
|
2018-02-19 23:57:18 +00:00
|
|
|
def __init__(self, tag_name, tag_member, variants):
|
|
|
|
# Flat unions pass tag_name but not tag_member.
|
|
|
|
# Simple unions and alternates pass tag_member but not tag_name.
|
|
|
|
# After check(), tag_member is always set, and tag_name remains
|
|
|
|
# a reliable witness of being used by a flat union.
|
|
|
|
assert bool(tag_member) != bool(tag_name)
|
|
|
|
assert (isinstance(tag_name, str) or
|
|
|
|
isinstance(tag_member, QAPISchemaObjectTypeMember))
|
2018-02-20 20:45:21 +00:00
|
|
|
assert len(variants) > 0
|
2018-02-19 21:43:28 +00:00
|
|
|
for v in variants:
|
|
|
|
assert isinstance(v, QAPISchemaObjectTypeVariant)
|
2018-02-26 01:06:12 +00:00
|
|
|
self._tag_name = tag_name
|
2018-02-19 23:57:18 +00:00
|
|
|
self.tag_member = tag_member
|
2018-02-19 21:43:28 +00:00
|
|
|
self.variants = variants
|
|
|
|
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
def set_owner(self, name):
|
|
|
|
for v in self.variants:
|
|
|
|
v.set_owner(name)
|
|
|
|
|
2018-02-20 01:13:21 +00:00
|
|
|
def check(self, schema, seen):
|
2018-02-20 01:18:57 +00:00
|
|
|
if not self.tag_member: # flat union
|
2018-02-26 01:06:12 +00:00
|
|
|
self.tag_member = seen[c_name(self._tag_name)]
|
|
|
|
assert self._tag_name == self.tag_member.name
|
2018-02-19 21:43:28 +00:00
|
|
|
assert isinstance(self.tag_member.type, QAPISchemaEnumType)
|
2018-07-03 02:59:31 +00:00
|
|
|
if self._tag_name: # flat union
|
|
|
|
# branches that are not explicitly covered get an empty type
|
|
|
|
cases = set([v.name for v in self.variants])
|
2018-12-18 10:00:25 +00:00
|
|
|
for m in self.tag_member.type.members:
|
|
|
|
if m.name not in cases:
|
|
|
|
v = QAPISchemaObjectTypeVariant(m.name, 'q_empty')
|
2018-07-03 02:59:31 +00:00
|
|
|
v.set_owner(self.tag_member.owner)
|
|
|
|
self.variants.append(v)
|
2018-02-19 21:43:28 +00:00
|
|
|
for v in self.variants:
|
2018-02-20 01:24:24 +00:00
|
|
|
v.check(schema)
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Backports commit 0426d53c6530606bf7641b83f2b755fe61c280ee from qemu
2018-02-20 02:51:35 +00:00
|
|
|
# Union names must match enum values; alternate names are
|
|
|
|
# checked separately. Use 'seen' to tell the two apart.
|
|
|
|
if seen:
|
2018-02-20 03:07:15 +00:00
|
|
|
assert v.name in self.tag_member.type.member_names()
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Backports commit 0426d53c6530606bf7641b83f2b755fe61c280ee from qemu
2018-02-20 02:51:35 +00:00
|
|
|
assert isinstance(v.type, QAPISchemaObjectType)
|
qapi: Check for QAPI collisions involving variant members
Right now, our ad hoc parser ensures that we cannot have a
flat union that introduces any members that would clash with
non-variant members inherited from the union's base type (see
flat-union-clash-member.json). We want QAPISchemaObjectType.check()
to make the same check, so we can later reduce some of the ad
hoc checks.
We already have a map 'seen' of all non-variant members. We
still need to check for collisions between each variant type's
members and the non-variant ones.
To know the variant type's members, we need to call
variant.type.check(). This also detects when a type contains
itself in a variant, exactly like the existing base.check()
detects when a type contains itself as a base. (Except that
we currently forbid anything but a struct as the type of a
variant, so we can't actually trigger this type of loop yet.)
Slight complication: an alternate's variant can have arbitrary
type, but only an object type's check() may be called outside
QAPISchema.check(). We could either skip the call for variants
of alternates, or skip it for non-object types. For now, do
the latter, because it's easier.
Then we call each variant member's check_clash() with the
appropriate 'seen' map. Since members of different variants
can't clash, we have to clone a fresh seen for each variant.
Wrap this in a new helper method
QAPISchemaObjectTypeVariants.check_clash().
Note that cloning 'seen' inside .check_clash() resembles
the one we just removed from .check() in 'qapi: Drop
obsolete tag value collision assertions'; the difference here is
that we are now checking for clashes among the qapi members of
the variant type, rather than for a single clash with the variant
tag name itself.
Note that, by construction, collisions can't actually happen for
simple unions: each variant's type is a wrapper with a single
member 'data', which will never collide with the only non-variant
member 'type'.
For alternates, there's nothing for a variant object type's
members to clash with, and therefore no need to call the new
variants.check_clash().
No change to generated code.
Backports commit b807a1e1e3796adaf3ece2f7b69ea5ee28468ff4 from qemu
2018-02-20 01:20:02 +00:00
|
|
|
v.type.check(schema)
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def check_clash(self, info, seen):
|
qapi: Check for QAPI collisions involving variant members
Right now, our ad hoc parser ensures that we cannot have a
flat union that introduces any members that would clash with
non-variant members inherited from the union's base type (see
flat-union-clash-member.json). We want QAPISchemaObjectType.check()
to make the same check, so we can later reduce some of the ad
hoc checks.
We already have a map 'seen' of all non-variant members. We
still need to check for collisions between each variant type's
members and the non-variant ones.
To know the variant type's members, we need to call
variant.type.check(). This also detects when a type contains
itself in a variant, exactly like the existing base.check()
detects when a type contains itself as a base. (Except that
we currently forbid anything but a struct as the type of a
variant, so we can't actually trigger this type of loop yet.)
Slight complication: an alternate's variant can have arbitrary
type, but only an object type's check() may be called outside
QAPISchema.check(). We could either skip the call for variants
of alternates, or skip it for non-object types. For now, do
the latter, because it's easier.
Then we call each variant member's check_clash() with the
appropriate 'seen' map. Since members of different variants
can't clash, we have to clone a fresh seen for each variant.
Wrap this in a new helper method
QAPISchemaObjectTypeVariants.check_clash().
Note that cloning 'seen' inside .check_clash() resembles
the one we just removed from .check() in 'qapi: Drop
obsolete tag value collision assertions'; the difference here is
that we are now checking for clashes among the qapi members of
the variant type, rather than for a single clash with the variant
tag name itself.
Note that, by construction, collisions can't actually happen for
simple unions: each variant's type is a wrapper with a single
member 'data', which will never collide with the only non-variant
member 'type'.
For alternates, there's nothing for a variant object type's
members to clash with, and therefore no need to call the new
variants.check_clash().
No change to generated code.
Backports commit b807a1e1e3796adaf3ece2f7b69ea5ee28468ff4 from qemu
2018-02-20 01:20:02 +00:00
|
|
|
for v in self.variants:
|
|
|
|
# Reset seen map for each variant, since qapi names from one
|
|
|
|
# branch do not affect another branch
|
|
|
|
assert isinstance(v.type, QAPISchemaObjectType)
|
2018-03-03 23:23:47 +00:00
|
|
|
v.type.check_clash(info, dict(seen))
|
qapi: Check for QAPI collisions involving variant members
Right now, our ad hoc parser ensures that we cannot have a
flat union that introduces any members that would clash with
non-variant members inherited from the union's base type (see
flat-union-clash-member.json). We want QAPISchemaObjectType.check()
to make the same check, so we can later reduce some of the ad
hoc checks.
We already have a map 'seen' of all non-variant members. We
still need to check for collisions between each variant type's
members and the non-variant ones.
To know the variant type's members, we need to call
variant.type.check(). This also detects when a type contains
itself in a variant, exactly like the existing base.check()
detects when a type contains itself as a base. (Except that
we currently forbid anything but a struct as the type of a
variant, so we can't actually trigger this type of loop yet.)
Slight complication: an alternate's variant can have arbitrary
type, but only an object type's check() may be called outside
QAPISchema.check(). We could either skip the call for variants
of alternates, or skip it for non-object types. For now, do
the latter, because it's easier.
Then we call each variant member's check_clash() with the
appropriate 'seen' map. Since members of different variants
can't clash, we have to clone a fresh seen for each variant.
Wrap this in a new helper method
QAPISchemaObjectTypeVariants.check_clash().
Note that cloning 'seen' inside .check_clash() resembles
the one we just removed from .check() in 'qapi: Drop
obsolete tag value collision assertions'; the difference here is
that we are now checking for clashes among the qapi members of
the variant type, rather than for a single clash with the variant
tag name itself.
Note that, by construction, collisions can't actually happen for
simple unions: each variant's type is a wrapper with a single
member 'data', which will never collide with the only non-variant
member 'type'.
For alternates, there's nothing for a variant object type's
members to clash with, and therefore no need to call the new
variants.check_clash().
No change to generated code.
Backports commit b807a1e1e3796adaf3ece2f7b69ea5ee28468ff4 from qemu
2018-02-20 01:20:02 +00:00
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember):
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
role = 'branch'
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
def __init__(self, name, typ):
|
|
|
|
QAPISchemaObjectTypeMember.__init__(self, name, typ, False)
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaAlternateType(QAPISchemaType):
|
2018-07-05 15:47:52 +00:00
|
|
|
def __init__(self, name, info, doc, ifcond, variants):
|
|
|
|
QAPISchemaType.__init__(self, name, info, doc, ifcond)
|
2018-02-19 21:43:28 +00:00
|
|
|
assert isinstance(variants, QAPISchemaObjectTypeVariants)
|
2018-02-26 01:06:12 +00:00
|
|
|
assert variants.tag_member
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
variants.set_owner(name)
|
|
|
|
variants.tag_member.set_owner(self.name)
|
2018-02-19 21:43:28 +00:00
|
|
|
self.variants = variants
|
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-05 16:00:03 +00:00
|
|
|
QAPISchemaType.check(self, schema)
|
2018-02-20 01:09:54 +00:00
|
|
|
self.variants.tag_member.check(schema)
|
2018-03-03 23:23:47 +00:00
|
|
|
# Not calling self.variants.check_clash(), because there's nothing
|
|
|
|
# to clash with
|
2018-02-20 01:13:21 +00:00
|
|
|
self.variants.check(schema, {})
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Backports commit 0426d53c6530606bf7641b83f2b755fe61c280ee from qemu
2018-02-20 02:51:35 +00:00
|
|
|
# Alternate branch names have no relation to the tag enum values;
|
|
|
|
# so we have to check for potential name collisions ourselves.
|
|
|
|
seen = {}
|
|
|
|
for v in self.variants.variants:
|
|
|
|
v.check_clash(self.info, seen)
|
2018-03-03 23:23:47 +00:00
|
|
|
if self.doc:
|
|
|
|
self.doc.connect_member(v)
|
|
|
|
if self.doc:
|
|
|
|
self.doc.check()
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-22 03:01:07 +00:00
|
|
|
def c_type(self):
|
|
|
|
return c_name(self.name) + pointer_suffix
|
|
|
|
|
2018-02-19 21:47:32 +00:00
|
|
|
def json_type(self):
|
|
|
|
return 'value'
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
2018-07-05 16:04:06 +00:00
|
|
|
visitor.visit_alternate_type(self.name, self.info, self.ifcond,
|
|
|
|
self.variants)
|
2018-02-19 21:49:57 +00:00
|
|
|
|
2018-02-26 01:22:00 +00:00
|
|
|
def is_empty(self):
|
|
|
|
return False
|
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchemaCommand(QAPISchemaEntity):
|
2018-07-05 15:47:52 +00:00
|
|
|
def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
|
2018-07-05 15:39:30 +00:00
|
|
|
gen, success_response, boxed, allow_oob, allow_preconfig):
|
2018-07-05 15:47:52 +00:00
|
|
|
QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
|
2018-02-19 21:43:28 +00:00
|
|
|
assert not arg_type or isinstance(arg_type, str)
|
|
|
|
assert not ret_type or isinstance(ret_type, str)
|
|
|
|
self._arg_type_name = arg_type
|
|
|
|
self.arg_type = None
|
|
|
|
self._ret_type_name = ret_type
|
|
|
|
self.ret_type = None
|
|
|
|
self.gen = gen
|
|
|
|
self.success_response = success_response
|
2018-02-26 01:16:58 +00:00
|
|
|
self.boxed = boxed
|
2018-07-05 15:34:35 +00:00
|
|
|
self.allow_oob = allow_oob
|
2018-07-05 15:39:30 +00:00
|
|
|
self.allow_preconfig = allow_preconfig
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-05 16:00:03 +00:00
|
|
|
QAPISchemaEntity.check(self, schema)
|
2018-02-19 21:43:28 +00:00
|
|
|
if self._arg_type_name:
|
|
|
|
self.arg_type = schema.lookup_type(self._arg_type_name)
|
2018-02-26 01:22:00 +00:00
|
|
|
assert (isinstance(self.arg_type, QAPISchemaObjectType) or
|
|
|
|
isinstance(self.arg_type, QAPISchemaAlternateType))
|
|
|
|
self.arg_type.check(schema)
|
|
|
|
if self.boxed:
|
|
|
|
if self.arg_type.is_empty():
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(self.info,
|
|
|
|
"Cannot use 'boxed' with empty type")
|
2018-02-26 01:22:00 +00:00
|
|
|
else:
|
|
|
|
assert not isinstance(self.arg_type, QAPISchemaAlternateType)
|
|
|
|
assert not self.arg_type.variants
|
|
|
|
elif self.boxed:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
|
2018-02-19 21:43:28 +00:00
|
|
|
if self._ret_type_name:
|
|
|
|
self.ret_type = schema.lookup_type(self._ret_type_name)
|
|
|
|
assert isinstance(self.ret_type, QAPISchemaType)
|
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
2018-07-05 16:04:06 +00:00
|
|
|
visitor.visit_command(self.name, self.info, self.ifcond,
|
2018-02-19 21:49:57 +00:00
|
|
|
self.arg_type, self.ret_type,
|
2018-07-05 15:34:35 +00:00
|
|
|
self.gen, self.success_response,
|
2018-07-05 15:39:30 +00:00
|
|
|
self.boxed, self.allow_oob,
|
|
|
|
self.allow_preconfig)
|
2018-02-19 21:49:57 +00:00
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchemaEvent(QAPISchemaEntity):
|
2018-07-05 15:47:52 +00:00
|
|
|
def __init__(self, name, info, doc, ifcond, arg_type, boxed):
|
|
|
|
QAPISchemaEntity.__init__(self, name, info, doc, ifcond)
|
2018-02-19 21:43:28 +00:00
|
|
|
assert not arg_type or isinstance(arg_type, str)
|
|
|
|
self._arg_type_name = arg_type
|
|
|
|
self.arg_type = None
|
2018-02-26 01:16:58 +00:00
|
|
|
self.boxed = boxed
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
def check(self, schema):
|
2018-07-05 16:00:03 +00:00
|
|
|
QAPISchemaEntity.check(self, schema)
|
2018-02-19 21:43:28 +00:00
|
|
|
if self._arg_type_name:
|
|
|
|
self.arg_type = schema.lookup_type(self._arg_type_name)
|
2018-02-26 01:22:00 +00:00
|
|
|
assert (isinstance(self.arg_type, QAPISchemaObjectType) or
|
|
|
|
isinstance(self.arg_type, QAPISchemaAlternateType))
|
|
|
|
self.arg_type.check(schema)
|
|
|
|
if self.boxed:
|
|
|
|
if self.arg_type.is_empty():
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(self.info,
|
|
|
|
"Cannot use 'boxed' with empty type")
|
2018-02-26 01:22:00 +00:00
|
|
|
else:
|
|
|
|
assert not isinstance(self.arg_type, QAPISchemaAlternateType)
|
|
|
|
assert not self.arg_type.variants
|
|
|
|
elif self.boxed:
|
2018-03-03 23:23:47 +00:00
|
|
|
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
2018-07-05 16:04:06 +00:00
|
|
|
visitor.visit_event(self.name, self.info, self.ifcond,
|
|
|
|
self.arg_type, self.boxed)
|
2018-02-19 21:49:57 +00:00
|
|
|
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
class QAPISchema(object):
|
|
|
|
def __init__(self, fname):
|
2018-03-09 14:03:14 +00:00
|
|
|
self._fname = fname
|
2018-07-03 03:01:21 +00:00
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
f = open(fname, 'r', encoding='utf-8')
|
|
|
|
else:
|
|
|
|
f = open(fname, 'r')
|
|
|
|
parser = QAPISchemaParser(f)
|
2018-03-09 13:55:43 +00:00
|
|
|
exprs = check_exprs(parser.exprs)
|
|
|
|
self.docs = parser.docs
|
2018-03-09 14:00:10 +00:00
|
|
|
self._entity_list = []
|
2018-03-09 13:55:43 +00:00
|
|
|
self._entity_dict = {}
|
|
|
|
self._predefining = True
|
|
|
|
self._def_predefineds()
|
|
|
|
self._predefining = False
|
|
|
|
self._def_exprs(exprs)
|
|
|
|
self.check()
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
def _def_entity(self, ent):
|
2018-02-20 00:04:36 +00:00
|
|
|
# Only the predefined types are allowed to not have info
|
|
|
|
assert ent.info or self._predefining
|
2018-03-09 14:03:14 +00:00
|
|
|
assert ent.name is None or ent.name not in self._entity_dict
|
2018-03-09 14:00:10 +00:00
|
|
|
self._entity_list.append(ent)
|
2018-03-09 14:03:14 +00:00
|
|
|
if ent.name is not None:
|
|
|
|
self._entity_dict[ent.name] = ent
|
|
|
|
if ent.info:
|
|
|
|
ent.module = os.path.relpath(ent.info['file'],
|
|
|
|
os.path.dirname(self._fname))
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
def lookup_entity(self, name, typ=None):
|
|
|
|
ent = self._entity_dict.get(name)
|
|
|
|
if typ and not isinstance(ent, typ):
|
|
|
|
return None
|
|
|
|
return ent
|
|
|
|
|
|
|
|
def lookup_type(self, name):
|
|
|
|
return self.lookup_entity(name, QAPISchemaType)
|
|
|
|
|
2018-03-09 14:03:14 +00:00
|
|
|
def _def_include(self, expr, info, doc):
|
|
|
|
include = expr['include']
|
|
|
|
assert doc is None
|
|
|
|
main_info = info
|
|
|
|
while main_info['parent']:
|
|
|
|
main_info = main_info['parent']
|
|
|
|
fname = os.path.relpath(include, os.path.dirname(main_info['file']))
|
|
|
|
self._def_entity(QAPISchemaInclude(fname, info))
|
|
|
|
|
2018-02-22 03:49:31 +00:00
|
|
|
def _def_builtin_type(self, name, json_type, c_type):
|
|
|
|
self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
# Instantiating only the arrays that are actually used would
|
|
|
|
# be nice, but we can't as long as their generated code
|
|
|
|
# (qapi-builtin-types.[ch]) may be shared by some other
|
|
|
|
# schema.
|
2018-02-20 00:04:36 +00:00
|
|
|
self._make_array_type(name, None)
|
2018-02-19 21:43:28 +00:00
|
|
|
|
|
|
|
def _def_predefineds(self):
|
2018-02-22 03:49:31 +00:00
|
|
|
for t in [('str', 'string', 'char' + pointer_suffix),
|
|
|
|
('number', 'number', 'double'),
|
|
|
|
('int', 'int', 'int64_t'),
|
|
|
|
('int8', 'int', 'int8_t'),
|
|
|
|
('int16', 'int', 'int16_t'),
|
|
|
|
('int32', 'int', 'int32_t'),
|
|
|
|
('int64', 'int', 'int64_t'),
|
|
|
|
('uint8', 'int', 'uint8_t'),
|
|
|
|
('uint16', 'int', 'uint16_t'),
|
|
|
|
('uint32', 'int', 'uint32_t'),
|
|
|
|
('uint64', 'int', 'uint64_t'),
|
|
|
|
('size', 'int', 'uint64_t'),
|
|
|
|
('bool', 'boolean', 'bool'),
|
2018-03-07 21:52:39 +00:00
|
|
|
('any', 'value', 'QObject' + pointer_suffix),
|
|
|
|
('null', 'null', 'QNull' + pointer_suffix)]:
|
2018-02-19 21:47:32 +00:00
|
|
|
self._def_builtin_type(*t)
|
2018-03-03 23:23:47 +00:00
|
|
|
self.the_empty_object_type = QAPISchemaObjectType(
|
2018-07-05 15:47:52 +00:00
|
|
|
'q_empty', None, None, None, None, [], None)
|
qapi: New QMP command query-qmp-schema for QMP introspection
qapi/introspect.json defines the introspection schema. It's designed
for QMP introspection, but should do for similar uses, such as QGA.
The introspection schema does not reflect all the rules and
restrictions that apply to QAPI schemata. A valid QAPI schema has an
introspection value conforming to the introspection schema, but the
converse is not true.
Introspection lowers away a number of schema details, and makes
implicit things explicit:
* The built-in types are declared with their JSON type.
All integer types are mapped to 'int', because how many bits we use
internally is an implementation detail. It could be pressed into
external interface service as very approximate range information,
but that's a bad idea. If we need range information, we better do
it properly.
* Implicit type definitions are made explicit, and given
auto-generated names:
- Array types, named by appending "List" to the name of their
element type, like in generated C.
- The enumeration types implicitly defined by simple union types,
named by appending "Kind" to the name of their simple union type,
like in generated C.
- Types that don't occur in generated C. Their names start with ':'
so they don't clash with the user's names.
* All type references are by name.
* The struct and union types are generalized into an object type.
* Base types are flattened.
* Commands take a single argument and return a single result.
Dictionary argument or list result is an implicit type definition.
The empty object type is used when a command takes no arguments or
produces no results.
The argument is always of object type, but the introspection schema
doesn't reflect that.
The 'gen': false directive is omitted as implementation detail.
The 'success-response' directive is omitted as well for now, even
though it's not an implementation detail, because it's not used by
QMP.
* Events carry a single data value.
Implicit type definition and empty object type use, just like for
commands.
The value is of object type, but the introspection schema doesn't
reflect that.
* Types not used by commands or events are omitted.
Indirect use counts as use.
* Optional members have a default, which can only be null right now
Instead of a mandatory "optional" flag, we have an optional default.
No default means mandatory, default null means optional without
default value. Non-null is available for optional with default
(possible future extension).
* Clients should *not* look up types by name, because type names are
not ABI. Look up the command or event you're interested in, then
follow the references.
TODO Should we hide the type names to eliminate the temptation?
New generator scripts/qapi-introspect.py computes an introspection
value for its input, and generates a C variable holding it.
It can generate awfully long lines. Marked TODO.
A new test-qmp-input-visitor test case feeds its result for both
tests/qapi-schema/qapi-schema-test.json and qapi-schema.json to a
QmpInputVisitor to verify it actually conforms to the schema.
New QMP command query-qmp-schema takes its return value from that
variable. Its reply is some 85KiBytes for me right now.
If this turns out to be too much, we have a couple of options:
* We can use shorter names in the JSON. Not the QMP style.
* Optionally return the sub-schema for commands and events given as
arguments.
Right now qmp_query_schema() sends the string literal computed by
qmp-introspect.py. To compute sub-schema at run time, we'd have to
duplicate parts of qapi-introspect.py in C. Unattractive.
* Let clients cache the output of query-qmp-schema.
It changes only on QEMU upgrades, i.e. rarely. Provide a command
query-qmp-schema-hash. Clients can have a cache indexed by hash,
and re-query the schema only when they don't have it cached. Even
simpler: put the hash in the QMP greeting.
Backports commit 39a181581650f4d50f4445bc6276d9716cece050 from qemu
2018-02-19 22:54:00 +00:00
|
|
|
self._def_entity(self.the_empty_object_type)
|
2018-03-03 22:43:24 +00:00
|
|
|
qtype_values = self._make_enum_members(['none', 'qnull', 'qnum',
|
2018-02-20 03:07:15 +00:00
|
|
|
'qstring', 'qdict', 'qlist',
|
2018-03-03 22:43:24 +00:00
|
|
|
'qbool'])
|
2018-07-05 15:47:52 +00:00
|
|
|
self._def_entity(QAPISchemaEnumType('QType', None, None, None,
|
2018-03-03 23:23:47 +00:00
|
|
|
qtype_values, 'QTYPE'))
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-20 03:07:15 +00:00
|
|
|
def _make_enum_members(self, values):
|
|
|
|
return [QAPISchemaMember(v) for v in values]
|
|
|
|
|
2018-07-05 15:47:52 +00:00
|
|
|
def _make_implicit_enum_type(self, name, info, ifcond, values):
|
2018-02-20 03:07:15 +00:00
|
|
|
# See also QAPISchemaObjectTypeMember._pretty_owner()
|
2018-02-19 23:53:03 +00:00
|
|
|
name = name + 'Kind' # Use namespace reserved by add_name()
|
2018-02-20 03:07:15 +00:00
|
|
|
self._def_entity(QAPISchemaEnumType(
|
2018-07-05 15:47:52 +00:00
|
|
|
name, info, None, ifcond, self._make_enum_members(values), None))
|
2018-02-19 21:43:28 +00:00
|
|
|
return name
|
|
|
|
|
2018-02-20 00:04:36 +00:00
|
|
|
def _make_array_type(self, element_type, info):
|
2018-02-20 00:08:11 +00:00
|
|
|
name = element_type + 'List' # Use namespace reserved by add_name()
|
2018-02-19 21:43:28 +00:00
|
|
|
if not self.lookup_type(name):
|
2018-02-20 00:04:36 +00:00
|
|
|
self._def_entity(QAPISchemaArrayType(name, info, element_type))
|
2018-02-19 21:43:28 +00:00
|
|
|
return name
|
|
|
|
|
2018-07-05 15:47:52 +00:00
|
|
|
def _make_implicit_object_type(self, name, info, doc, ifcond,
|
|
|
|
role, members):
|
2018-02-19 21:43:28 +00:00
|
|
|
if not members:
|
|
|
|
return None
|
qapi: Track owner of each object member
Future commits will migrate semantic checking away from parsing
and over to the various QAPISchema*.check() methods. But to
report an error message about an incorrect semantic use of a
member of an object type, it helps to know which type, command,
or event owns the member. In particular, when a member is
inherited from a base type, it is desirable to associate the
member name with the base type (and not the type calling
member.check()).
Rather than packing additional information into the seen array
passed to each member.check() (as in seen[m.name] = {'member':m,
'owner':type}), it is easier to have each member track the name
of the owner type in the first place (keeping things simpler
with the existing seen[m.name] = m). The new member.owner field
is set via a new set_owner() method, called when registering
the members and variants arrays with an object or variant type.
Track only a name, and not the actual type object, to avoid
creating a circular python reference chain.
Note that Variants.set_owner() method does not set the owner
for the tag_member field; this field is set earlier either as
part of an object's non-variant members, or explicitly by
alternates.
The source information is intended for human consumption in
error messages, and a new describe() method is added to access
the resulting information. For example, given the qapi:
{ 'command': 'foo', 'data': { 'string': 'str' } }
an implementation of visit_command() that calls
arg_type.members[0].describe()
will see "'string' (parameter of foo)".
To make the human-readable name of implicit types work without
duplicating efforts, the describe() method has to reverse the
name of implicit types, via the helper _pretty_owner().
No change to generated code.
Backports commit 88d4ef8b5cbf9d3336564b1d3ac7a91cbe4aee0e from qemu
2018-02-20 01:28:17 +00:00
|
|
|
# See also QAPISchemaObjectTypeMember._pretty_owner()
|
2018-02-22 03:35:11 +00:00
|
|
|
name = 'q_obj_%s-%s' % (name, role)
|
2018-07-05 15:47:52 +00:00
|
|
|
typ = self.lookup_entity(name, QAPISchemaObjectType)
|
|
|
|
if typ:
|
|
|
|
# The implicit object type has multiple users. This can
|
|
|
|
# happen only for simple unions' implicit wrapper types.
|
|
|
|
# Its ifcond should be the disjunction of its user's
|
|
|
|
# ifconds. Not implemented. Instead, we always pass the
|
|
|
|
# wrapped type's ifcond, which is trivially the same for all
|
|
|
|
# users. It's also necessary for the wrapper to compile.
|
|
|
|
# But it's not tight: the disjunction need not imply it. We
|
|
|
|
# may end up compiling useless wrapper types.
|
|
|
|
# TODO kill simple unions or implement the disjunction
|
2018-07-05 16:00:03 +00:00
|
|
|
assert ifcond == typ._ifcond # pylint: disable=protected-access
|
2018-07-05 15:47:52 +00:00
|
|
|
else:
|
|
|
|
self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond,
|
|
|
|
None, members, None))
|
2018-02-19 21:43:28 +00:00
|
|
|
return name
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def _def_enum_type(self, expr, info, doc):
|
2018-02-19 21:43:28 +00:00
|
|
|
name = expr['enum']
|
|
|
|
data = expr['data']
|
|
|
|
prefix = expr.get('prefix')
|
2018-07-05 15:47:52 +00:00
|
|
|
ifcond = expr.get('if')
|
2018-02-20 03:07:15 +00:00
|
|
|
self._def_entity(QAPISchemaEnumType(
|
2018-07-05 15:47:52 +00:00
|
|
|
name, info, doc, ifcond,
|
|
|
|
self._make_enum_members(data), prefix))
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-02-20 00:04:36 +00:00
|
|
|
def _make_member(self, name, typ, info):
|
2018-02-19 21:43:28 +00:00
|
|
|
optional = False
|
|
|
|
if name.startswith('*'):
|
|
|
|
name = name[1:]
|
|
|
|
optional = True
|
|
|
|
if isinstance(typ, list):
|
|
|
|
assert len(typ) == 1
|
2018-02-20 00:04:36 +00:00
|
|
|
typ = self._make_array_type(typ[0], info)
|
2018-02-19 21:43:28 +00:00
|
|
|
return QAPISchemaObjectTypeMember(name, typ, optional)
|
|
|
|
|
2018-02-20 00:04:36 +00:00
|
|
|
def _make_members(self, data, info):
|
|
|
|
return [self._make_member(key, value, info)
|
2018-03-06 16:28:20 +00:00
|
|
|
for (key, value) in data.items()]
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def _def_struct_type(self, expr, info, doc):
|
2018-02-19 21:43:28 +00:00
|
|
|
name = expr['struct']
|
|
|
|
base = expr.get('base')
|
|
|
|
data = expr['data']
|
2018-07-05 15:47:52 +00:00
|
|
|
ifcond = expr.get('if')
|
|
|
|
self._def_entity(QAPISchemaObjectType(name, info, doc, ifcond, base,
|
2018-02-20 00:04:36 +00:00
|
|
|
self._make_members(data, info),
|
2018-02-19 21:43:28 +00:00
|
|
|
None))
|
|
|
|
|
|
|
|
def _make_variant(self, case, typ):
|
|
|
|
return QAPISchemaObjectTypeVariant(case, typ)
|
|
|
|
|
2018-02-20 00:04:36 +00:00
|
|
|
def _make_simple_variant(self, case, typ, info):
|
2018-02-19 21:43:28 +00:00
|
|
|
if isinstance(typ, list):
|
|
|
|
assert len(typ) == 1
|
2018-02-20 00:04:36 +00:00
|
|
|
typ = self._make_array_type(typ[0], info)
|
|
|
|
typ = self._make_implicit_object_type(
|
2018-07-05 16:00:03 +00:00
|
|
|
typ, info, None, self.lookup_type(typ),
|
2018-07-05 15:47:52 +00:00
|
|
|
'wrapper', [self._make_member('data', typ, info)])
|
2018-02-19 21:43:28 +00:00
|
|
|
return QAPISchemaObjectTypeVariant(case, typ)
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def _def_union_type(self, expr, info, doc):
|
2018-02-19 21:43:28 +00:00
|
|
|
name = expr['union']
|
|
|
|
data = expr['data']
|
|
|
|
base = expr.get('base')
|
2018-07-05 15:47:52 +00:00
|
|
|
ifcond = expr.get('if')
|
2018-02-19 21:43:28 +00:00
|
|
|
tag_name = expr.get('discriminator')
|
2018-02-19 23:57:18 +00:00
|
|
|
tag_member = None
|
2018-02-22 03:54:15 +00:00
|
|
|
if isinstance(base, dict):
|
2018-07-05 15:47:52 +00:00
|
|
|
base = self._make_implicit_object_type(
|
|
|
|
name, info, doc, ifcond,
|
|
|
|
'base', self._make_members(base, info))
|
2018-02-19 21:43:28 +00:00
|
|
|
if tag_name:
|
|
|
|
variants = [self._make_variant(key, value)
|
2018-03-06 16:28:20 +00:00
|
|
|
for (key, value) in data.items()]
|
qapi: Track simple union tag in object.local_members
We were previously creating all unions with an empty list for
local_members. However, it will make it easier to unify struct
and union generation if we include the generated tag member in
local_members. That way, we can have a common code pattern:
visit the base (if any), visit the local members (if any), visit
the variants (if any). The local_members of a flat union
remains empty (because the discriminator is already visited as
part of the base). Then, by visiting tag_member.check() during
AlternateType.check(), we no longer need to call it during
Variants.check().
The various front end entities now exist as follows:
struct: optional base, optional local_members, no variants
simple union: no base, one-element local_members, variants with tag_member
from local_members
flat union: base, no local_members, variants with tag_member from base
alternate: no base, no local_members, variants
With the new local members, we require a bit of finesse to
avoid assertions in the clients.
No change to generated code.
Backports commit da34a9bd999d1be13954c699bbae0295cdaa3200 from qemu
2018-02-20 01:02:00 +00:00
|
|
|
members = []
|
2018-02-19 21:43:28 +00:00
|
|
|
else:
|
2018-02-20 00:04:36 +00:00
|
|
|
variants = [self._make_simple_variant(key, value, info)
|
2018-03-06 16:28:20 +00:00
|
|
|
for (key, value) in data.items()]
|
2018-07-05 15:47:52 +00:00
|
|
|
typ = self._make_implicit_enum_type(name, info, ifcond,
|
2018-02-20 02:54:40 +00:00
|
|
|
[v.name for v in variants])
|
|
|
|
tag_member = QAPISchemaObjectTypeMember('type', typ, False)
|
qapi: Track simple union tag in object.local_members
We were previously creating all unions with an empty list for
local_members. However, it will make it easier to unify struct
and union generation if we include the generated tag member in
local_members. That way, we can have a common code pattern:
visit the base (if any), visit the local members (if any), visit
the variants (if any). The local_members of a flat union
remains empty (because the discriminator is already visited as
part of the base). Then, by visiting tag_member.check() during
AlternateType.check(), we no longer need to call it during
Variants.check().
The various front end entities now exist as follows:
struct: optional base, optional local_members, no variants
simple union: no base, one-element local_members, variants with tag_member
from local_members
flat union: base, no local_members, variants with tag_member from base
alternate: no base, no local_members, variants
With the new local members, we require a bit of finesse to
avoid assertions in the clients.
No change to generated code.
Backports commit da34a9bd999d1be13954c699bbae0295cdaa3200 from qemu
2018-02-20 01:02:00 +00:00
|
|
|
members = [tag_member]
|
2018-02-19 21:43:28 +00:00
|
|
|
self._def_entity(
|
2018-07-05 15:47:52 +00:00
|
|
|
QAPISchemaObjectType(name, info, doc, ifcond, base, members,
|
2018-02-19 21:43:28 +00:00
|
|
|
QAPISchemaObjectTypeVariants(tag_name,
|
2018-02-19 23:57:18 +00:00
|
|
|
tag_member,
|
2018-02-19 21:43:28 +00:00
|
|
|
variants)))
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def _def_alternate_type(self, expr, info, doc):
|
2018-02-19 21:43:28 +00:00
|
|
|
name = expr['alternate']
|
|
|
|
data = expr['data']
|
2018-07-05 15:47:52 +00:00
|
|
|
ifcond = expr.get('if')
|
2018-02-19 21:43:28 +00:00
|
|
|
variants = [self._make_variant(key, value)
|
2018-03-06 16:28:20 +00:00
|
|
|
for (key, value) in data.items()]
|
qapi: Simplify visiting of alternate types
Previously, working with alternates required two lookup arrays
and some indirection: for type Foo, we created Foo_qtypes[]
which maps each qtype to a value of the generated FooKind enum,
then look up that value in FooKind_lookup[] like we do for other
union types.
This has a couple of subtle bugs. First, the generator was
creating a call with a parameter '(int *) &(*obj)->type' where
type is an enum type; this is unsafe if the compiler chooses
to store the enum type in a different size than int, where
assigning through the wrong size pointer can corrupt data or
cause a SIGBUS.
Related bug, not not fixed in this patch: qapi-visit.py's
gen_visit_enum() generates a cast of its enum * argument to
int *. Marked FIXME.
Second, since the values of the FooKind enum start at zero, all
entries of the Foo_qtypes[] array that were not explicitly
initialized will map to the same branch of the union as the
first member of the alternate, rather than triggering a desired
failure in visit_get_next_type(). Fortunately, the bug seldom
bites; the very next thing the input visitor does is try to
parse the incoming JSON with the wrong parser, which normally
fails; the output visitor is not used with a C struct in that
state, and the dealloc visitor has nothing to clean up (so
there is no leak).
However, the second bug IS observable in one case: parsing an
integer causes unusual behavior in an alternate that contains
at least a 'number' member but no 'int' member, because the
'number' parser accepts QTYPE_QINT in addition to the expected
QTYPE_QFLOAT (that is, since 'int' is not a member, the type
QTYPE_QINT accidentally maps to FooKind 0; if this enum value
is the 'number' branch the integer parses successfully, but if
the 'number' branch is not first, some other branch tries to
parse the integer and rejects it). A later patch will worry
about fixing alternates to always parse all inputs that a
non-alternate 'number' would accept, for now this is still
marked FIXME in the updated test-qmp-input-visitor.c, to
merely point out that new undesired behavior of 'ans' matches
the existing undesired behavior of 'asn'.
This patch fixes the default-initialization bug by deleting the
indirection, and modifying get_next_type() to directly assign a
QTypeCode parameter. This in turn fixes the type-casting bug,
as we are no longer casting a pointer to enum to a questionable
size. There is no longer a need to generate an implicit FooKind
enum associated with the alternate type (since the QMP wire
format never uses the stringized counterparts of the C union
member names). Since the updated visit_get_next_type() does not
know which qtypes are expected, the generated visitor is
modified to generate an error statement if an unexpected type is
encountered.
Callers now have to know the QTYPE_* mapping when looking at the
discriminator; but so far, only the testsuite was even using the
C struct of an alternate types. I considered the possibility of
keeping the internal enum FooKind, but initialized differently
than most generated arrays, as in:
typedef enum FooKind {
FOO_KIND_A = QTYPE_QDICT,
FOO_KIND_B = QTYPE_QINT,
} FooKind;
to create nicer aliases for knowing when to use foo->a or foo->b
when inspecting foo->type; but it turned out to add too much
complexity, especially without a client.
There is a user-visible side effect to this change, but I
consider it to be an improvement. Previously,
the invalid QMP command:
{"execute":"blockdev-add", "arguments":{"options":
{"driver":"raw", "id":"a", "file":true}}}
failed with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: QDict"}}
(visit_get_next_type() succeeded, and the error comes from the
visit_type_BlockdevOptions() expecting {}; there is no mention of
the fact that a string would also work). Now it fails with:
{"error": {"class": "GenericError",
"desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
(the error when the next type doesn't match any expected types for
the overall alternate).
Backports commit 0426d53c6530606bf7641b83f2b755fe61c280ee from qemu
2018-02-20 02:51:35 +00:00
|
|
|
tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
|
2018-02-19 21:43:28 +00:00
|
|
|
self._def_entity(
|
2018-07-05 15:47:52 +00:00
|
|
|
QAPISchemaAlternateType(name, info, doc, ifcond,
|
2018-02-19 21:43:28 +00:00
|
|
|
QAPISchemaObjectTypeVariants(None,
|
2018-02-19 23:57:18 +00:00
|
|
|
tag_member,
|
2018-02-19 21:43:28 +00:00
|
|
|
variants)))
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def _def_command(self, expr, info, doc):
|
2018-02-19 21:43:28 +00:00
|
|
|
name = expr['command']
|
|
|
|
data = expr.get('data')
|
|
|
|
rets = expr.get('returns')
|
|
|
|
gen = expr.get('gen', True)
|
|
|
|
success_response = expr.get('success-response', True)
|
2018-02-26 01:16:58 +00:00
|
|
|
boxed = expr.get('boxed', False)
|
2018-07-05 15:34:35 +00:00
|
|
|
allow_oob = expr.get('allow-oob', False)
|
2018-07-05 15:39:30 +00:00
|
|
|
allow_preconfig = expr.get('allow-preconfig', False)
|
2018-07-05 15:47:52 +00:00
|
|
|
ifcond = expr.get('if')
|
2018-02-19 21:43:28 +00:00
|
|
|
if isinstance(data, OrderedDict):
|
2018-02-20 00:04:36 +00:00
|
|
|
data = self._make_implicit_object_type(
|
2018-07-05 15:47:52 +00:00
|
|
|
name, info, doc, ifcond, 'arg', self._make_members(data, info))
|
2018-02-19 21:43:28 +00:00
|
|
|
if isinstance(rets, list):
|
|
|
|
assert len(rets) == 1
|
2018-02-20 00:04:36 +00:00
|
|
|
rets = self._make_array_type(rets[0], info)
|
2018-07-05 15:47:52 +00:00
|
|
|
self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
|
2018-07-05 15:34:35 +00:00
|
|
|
gen, success_response,
|
2018-07-05 15:39:30 +00:00
|
|
|
boxed, allow_oob, allow_preconfig))
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
def _def_event(self, expr, info, doc):
|
2018-02-19 21:43:28 +00:00
|
|
|
name = expr['event']
|
|
|
|
data = expr.get('data')
|
2018-02-26 01:16:58 +00:00
|
|
|
boxed = expr.get('boxed', False)
|
2018-07-05 15:47:52 +00:00
|
|
|
ifcond = expr.get('if')
|
2018-02-19 21:43:28 +00:00
|
|
|
if isinstance(data, OrderedDict):
|
2018-02-20 00:04:36 +00:00
|
|
|
data = self._make_implicit_object_type(
|
2018-07-05 15:47:52 +00:00
|
|
|
name, info, doc, ifcond, 'arg', self._make_members(data, info))
|
|
|
|
self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
|
2018-02-19 21:43:28 +00:00
|
|
|
|
2018-03-09 13:54:18 +00:00
|
|
|
def _def_exprs(self, exprs):
|
|
|
|
for expr_elem in exprs:
|
2018-02-19 21:43:28 +00:00
|
|
|
expr = expr_elem['expr']
|
|
|
|
info = expr_elem['info']
|
2018-03-03 23:23:47 +00:00
|
|
|
doc = expr_elem.get('doc')
|
2018-02-19 21:43:28 +00:00
|
|
|
if 'enum' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
self._def_enum_type(expr, info, doc)
|
2018-02-19 21:43:28 +00:00
|
|
|
elif 'struct' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
self._def_struct_type(expr, info, doc)
|
2018-02-19 21:43:28 +00:00
|
|
|
elif 'union' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
self._def_union_type(expr, info, doc)
|
2018-02-19 21:43:28 +00:00
|
|
|
elif 'alternate' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
self._def_alternate_type(expr, info, doc)
|
2018-02-19 21:43:28 +00:00
|
|
|
elif 'command' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
self._def_command(expr, info, doc)
|
2018-02-19 21:43:28 +00:00
|
|
|
elif 'event' in expr:
|
2018-03-03 23:23:47 +00:00
|
|
|
self._def_event(expr, info, doc)
|
2018-03-09 13:58:47 +00:00
|
|
|
elif 'include' in expr:
|
2018-03-09 14:03:14 +00:00
|
|
|
self._def_include(expr, info, doc)
|
2018-02-19 21:43:28 +00:00
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
|
|
|
|
def check(self):
|
2018-03-09 14:00:10 +00:00
|
|
|
for ent in self._entity_list:
|
2018-02-19 21:43:28 +00:00
|
|
|
ent.check(self)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 21:49:57 +00:00
|
|
|
def visit(self, visitor):
|
2018-02-19 23:49:02 +00:00
|
|
|
visitor.visit_begin(self)
|
2018-03-09 14:03:14 +00:00
|
|
|
module = None
|
2018-03-09 14:00:10 +00:00
|
|
|
for entity in self._entity_list:
|
2018-02-19 23:49:02 +00:00
|
|
|
if visitor.visit_needed(entity):
|
2018-03-09 14:03:14 +00:00
|
|
|
if entity.module != module:
|
|
|
|
module = entity.module
|
|
|
|
visitor.visit_module(module)
|
2018-02-19 23:49:02 +00:00
|
|
|
entity.visit(visitor)
|
2018-02-19 21:49:57 +00:00
|
|
|
visitor.visit_end()
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
|
2018-02-19 20:47:16 +00:00
|
|
|
#
|
|
|
|
# Code generation helpers
|
|
|
|
#
|
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def camel_case(name):
|
|
|
|
new_name = ''
|
|
|
|
first = True
|
|
|
|
for ch in name:
|
|
|
|
if ch in ['_', '-']:
|
|
|
|
first = True
|
|
|
|
elif first:
|
|
|
|
new_name += ch.upper()
|
|
|
|
first = False
|
|
|
|
else:
|
|
|
|
new_name += ch.lower()
|
|
|
|
return new_name
|
|
|
|
|
2018-03-03 23:23:47 +00:00
|
|
|
|
2018-02-19 19:51:01 +00:00
|
|
|
# ENUMName -> ENUM_NAME, EnumName1 -> ENUM_NAME1
|
|
|
|
# ENUM_NAME -> ENUM_NAME, ENUM_NAME1 -> ENUM_NAME1, ENUM_Name2 -> ENUM_NAME2
|
|
|
|
# ENUM24_Name -> ENUM24_NAME
|
|
|
|
def camel_to_upper(value):
|
|
|
|
c_fun_str = c_name(value, False)
|
|
|
|
if value.isupper():
|
|
|
|
return c_fun_str
|
|
|
|
|
|
|
|
new_name = ''
|
2018-08-16 11:14:31 +00:00
|
|
|
length = len(c_fun_str)
|
|
|
|
for i in range(length):
|
2018-02-19 19:51:01 +00:00
|
|
|
c = c_fun_str[i]
|
2018-03-03 23:23:47 +00:00
|
|
|
# When c is upper and no '_' appears before, do more checks
|
|
|
|
if c.isupper() and (i > 0) and c_fun_str[i - 1] != '_':
|
2018-08-16 11:14:31 +00:00
|
|
|
if i < length - 1 and c_fun_str[i + 1].islower():
|
2018-02-19 23:07:49 +00:00
|
|
|
new_name += '_'
|
|
|
|
elif c_fun_str[i - 1].isdigit():
|
2018-02-19 19:51:01 +00:00
|
|
|
new_name += '_'
|
|
|
|
new_name += c
|
|
|
|
return new_name.lstrip('_').upper()
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-19 21:38:38 +00:00
|
|
|
def c_enum_const(type_name, const_name, prefix=None):
|
|
|
|
if prefix is not None:
|
|
|
|
type_name = prefix
|
qapi: Change munging of CamelCase enum values
When munging enum values, the fact that we were passing the entire
prefix + value through camel_to_upper() meant that enum values
spelled with CamelCase could be turned into CAMEL_CASE. However,
this provides a potential collision (both OneTwo and One-Two would
munge into ONE_TWO) for enum types, when the same two names are
valid side-by-side as QAPI member names. By changing the generation
of enum constants to always be prefix + '_' + c_name(value,
False).upper(), and ensuring that there are no case collisions (in
the next patches), we no longer have to worry about names that
would be distinct as QAPI members but collide as variant tag names,
without having to think about what munging the heuristics in
camel_to_upper() will actually perform on an enum value.
Making the change will affect enums that did not follow coding
conventions, using 'CamelCase' rather than desired 'lower-case'.
Thankfully, there are only two culprits: InputButton and ErrorClass.
We already tweaked ErrorClass to make it an alias of QapiErrorClass,
where only the alias needs changing rather than the whole tree. So
the bulk of this change is modifying INPUT_BUTTON_WHEEL_UP to the
new INPUT_BUTTON_WHEELUP (and likewise for WHEELDOWN). That part
of this commit may later need reverting if we rename the enum
constants from 'WheelUp' to 'wheel-up' as part of moving
x-input-send-event to a stable interface; but at least we have
documentation bread crumbs in place to remind us (commit 513e7cd),
and it matches the fact that SDL constants are also spelled
SDL_BUTTON_WHEELUP.
Backports commit d20a580bc0eac9d489884f6d2ed28105880532b6 from qemu
2018-02-20 01:40:12 +00:00
|
|
|
return camel_to_upper(type_name) + '_' + c_name(const_name, False).upper()
|
2018-02-19 19:51:01 +00:00
|
|
|
|
2018-08-16 11:14:31 +00:00
|
|
|
|
2018-03-06 16:30:03 +00:00
|
|
|
if hasattr(str, 'maketrans'):
|
2018-03-09 15:28:28 +00:00
|
|
|
c_name_trans = str.maketrans('.-', '__')
|
2018-03-06 16:30:03 +00:00
|
|
|
else:
|
2018-03-09 15:28:28 +00:00
|
|
|
c_name_trans = string.maketrans('.-', '__')
|
2018-02-19 19:40:55 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
qapi: Make c_type() consistently convert qapi names
Continuing the string of cleanups for supporting downstream names
containing '.', this patch focuses on ensuring c_type() can
handle a downstream name. This patch alone does not fix the
places where generator output should be calling this function
but was open-coding things instead, but it gets us a step closer.
In particular, the changes to c_list_type() and type_name() mean
that type_name(FOO) now handles the case when FOO contains '.',
'-', or is a ticklish identifier other than a builtin (builtins
are exempted because ['int'] must remain mapped to 'intList' and
not 'q_intList'). Meanwhile, ['unix'] now maps to 'q_unixList'
rather than 'unixList', to match the fact that 'unix' is ticklish;
however, our naming conventions state that complex types should
start with a capital, so no type name following conventions will
ever have the 'q_' prepended.
Likewise, changes to c_type() mean that c_type(FOO) properly
handles an enum or complex type FOO with '.' or '-' in the
name, or is a ticklish identifier (again, a ticklish identifier
as a type name violates conventions).
Backports commit c6405b54b7b09a876f2f2fba2aa6f8ac87189cb9 from qemu
2018-02-19 19:56:46 +00:00
|
|
|
# Map @name to a valid C identifier.
|
|
|
|
# If @protect, avoid returning certain ticklish identifiers (like
|
2018-03-03 23:23:47 +00:00
|
|
|
# C keywords) by prepending 'q_'.
|
qapi: Make c_type() consistently convert qapi names
Continuing the string of cleanups for supporting downstream names
containing '.', this patch focuses on ensuring c_type() can
handle a downstream name. This patch alone does not fix the
places where generator output should be calling this function
but was open-coding things instead, but it gets us a step closer.
In particular, the changes to c_list_type() and type_name() mean
that type_name(FOO) now handles the case when FOO contains '.',
'-', or is a ticklish identifier other than a builtin (builtins
are exempted because ['int'] must remain mapped to 'intList' and
not 'q_intList'). Meanwhile, ['unix'] now maps to 'q_unixList'
rather than 'unixList', to match the fact that 'unix' is ticklish;
however, our naming conventions state that complex types should
start with a capital, so no type name following conventions will
ever have the 'q_' prepended.
Likewise, changes to c_type() mean that c_type(FOO) properly
handles an enum or complex type FOO with '.' or '-' in the
name, or is a ticklish identifier (again, a ticklish identifier
as a type name violates conventions).
Backports commit c6405b54b7b09a876f2f2fba2aa6f8ac87189cb9 from qemu
2018-02-19 19:56:46 +00:00
|
|
|
#
|
|
|
|
# Used for converting 'name' from a 'name':'type' qapi definition
|
|
|
|
# into a generated struct member, as well as converting type names
|
|
|
|
# into substrings of a generated C function name.
|
|
|
|
# '__a.b_c' -> '__a_b_c', 'x-foo' -> 'x_foo'
|
|
|
|
# protect=True: 'int' -> 'q_int'; protect=False: 'int' -> 'int'
|
2018-02-19 19:44:59 +00:00
|
|
|
def c_name(name, protect=True):
|
2015-08-21 07:04:50 +00:00
|
|
|
# ANSI X3J11/88-090, 3.1.1
|
|
|
|
c89_words = set(['auto', 'break', 'case', 'char', 'const', 'continue',
|
2018-02-19 23:07:49 +00:00
|
|
|
'default', 'do', 'double', 'else', 'enum', 'extern',
|
|
|
|
'float', 'for', 'goto', 'if', 'int', 'long', 'register',
|
|
|
|
'return', 'short', 'signed', 'sizeof', 'static',
|
|
|
|
'struct', 'switch', 'typedef', 'union', 'unsigned',
|
|
|
|
'void', 'volatile', 'while'])
|
2015-08-21 07:04:50 +00:00
|
|
|
# ISO/IEC 9899:1999, 6.4.1
|
|
|
|
c99_words = set(['inline', 'restrict', '_Bool', '_Complex', '_Imaginary'])
|
|
|
|
# ISO/IEC 9899:2011, 6.4.1
|
2018-02-19 23:07:49 +00:00
|
|
|
c11_words = set(['_Alignas', '_Alignof', '_Atomic', '_Generic',
|
|
|
|
'_Noreturn', '_Static_assert', '_Thread_local'])
|
2015-08-21 07:04:50 +00:00
|
|
|
# GCC http://gcc.gnu.org/onlinedocs/gcc-4.7.1/gcc/C-Extensions.html
|
|
|
|
# excluding _.*
|
|
|
|
gcc_words = set(['asm', 'typeof'])
|
|
|
|
# C++ ISO/IEC 14882:2003 2.11
|
|
|
|
cpp_words = set(['bool', 'catch', 'class', 'const_cast', 'delete',
|
|
|
|
'dynamic_cast', 'explicit', 'false', 'friend', 'mutable',
|
|
|
|
'namespace', 'new', 'operator', 'private', 'protected',
|
|
|
|
'public', 'reinterpret_cast', 'static_cast', 'template',
|
|
|
|
'this', 'throw', 'true', 'try', 'typeid', 'typename',
|
|
|
|
'using', 'virtual', 'wchar_t',
|
|
|
|
# alternative representations
|
|
|
|
'and', 'and_eq', 'bitand', 'bitor', 'compl', 'not',
|
|
|
|
'not_eq', 'or', 'or_eq', 'xor', 'xor_eq'])
|
|
|
|
# namespace pollution:
|
2018-02-20 13:10:59 +00:00
|
|
|
polluted_words = set(['unix', 'errno', 'mips', 'sparc'])
|
2018-02-20 01:31:06 +00:00
|
|
|
name = name.translate(c_name_trans)
|
2018-02-19 23:07:49 +00:00
|
|
|
if protect and (name in c89_words | c99_words | c11_words | gcc_words
|
|
|
|
| cpp_words | polluted_words):
|
2018-03-03 23:23:47 +00:00
|
|
|
return 'q_' + name
|
2018-02-20 01:31:06 +00:00
|
|
|
return name
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-08-16 11:14:31 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
eatspace = '\033EATSPACE.'
|
2018-02-19 19:53:53 +00:00
|
|
|
pointer_suffix = ' *' + eatspace
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def genindent(count):
|
2018-03-03 23:23:47 +00:00
|
|
|
ret = ''
|
2018-02-19 23:07:49 +00:00
|
|
|
for _ in range(count):
|
2018-03-03 23:23:47 +00:00
|
|
|
ret += ' '
|
2015-08-21 07:04:50 +00:00
|
|
|
return ret
|
|
|
|
|
2018-08-16 11:14:31 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
indent_level = 0
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def push_indent(indent_amount=4):
|
|
|
|
global indent_level
|
|
|
|
indent_level += indent_amount
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def pop_indent(indent_amount=4):
|
|
|
|
global indent_level
|
|
|
|
indent_level -= indent_amount
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-19 21:04:34 +00:00
|
|
|
# Generate @code with @kwds interpolated.
|
|
|
|
# Obey indent_level, and strip eatspace.
|
2015-08-21 07:04:50 +00:00
|
|
|
def cgen(code, **kwds):
|
2018-02-19 21:04:34 +00:00
|
|
|
raw = code % kwds
|
|
|
|
if indent_level:
|
|
|
|
indent = genindent(indent_level)
|
2018-02-19 21:34:14 +00:00
|
|
|
# re.subn() lacks flags support before Python 2.7, use re.compile()
|
2018-07-05 16:04:57 +00:00
|
|
|
raw = re.subn(re.compile(r'^(?!(#|$))', re.MULTILINE),
|
|
|
|
indent, raw)
|
2018-02-19 21:04:34 +00:00
|
|
|
raw = raw[0]
|
2018-03-03 23:23:47 +00:00
|
|
|
return re.sub(re.escape(eatspace) + r' *', '', raw)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def mcgen(code, **kwds):
|
2018-02-19 21:04:34 +00:00
|
|
|
if code[0] == '\n':
|
|
|
|
code = code[1:]
|
|
|
|
return cgen(code, **kwds)
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def guardname(filename):
|
2018-03-09 14:27:09 +00:00
|
|
|
return re.sub(r'[^A-Za-z0-9_]', '_', filename).upper()
|
2015-08-21 07:04:50 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def guardstart(name):
|
|
|
|
return mcgen('''
|
|
|
|
#ifndef %(name)s
|
|
|
|
#define %(name)s
|
|
|
|
|
|
|
|
''',
|
|
|
|
name=guardname(name))
|
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2015-08-21 07:04:50 +00:00
|
|
|
def guardend(name):
|
|
|
|
return mcgen('''
|
|
|
|
|
|
|
|
#endif /* %(name)s */
|
|
|
|
''',
|
|
|
|
name=guardname(name))
|
2018-02-19 20:19:07 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-07-05 16:07:12 +00:00
|
|
|
def gen_if(ifcond):
|
|
|
|
ret = ''
|
|
|
|
for ifc in ifcond:
|
|
|
|
ret += mcgen('''
|
|
|
|
#if %(cond)s
|
|
|
|
''', cond=ifc)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def gen_endif(ifcond):
|
|
|
|
ret = ''
|
|
|
|
for ifc in reversed(ifcond):
|
|
|
|
ret += mcgen('''
|
|
|
|
#endif /* %(cond)s */
|
|
|
|
''', cond=ifc)
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
def _wrap_ifcond(ifcond, before, after):
|
|
|
|
if before == after:
|
|
|
|
return after # suppress empty #if ... #endif
|
|
|
|
|
|
|
|
assert after.startswith(before)
|
|
|
|
out = before
|
|
|
|
added = after[len(before):]
|
|
|
|
if added[0] == '\n':
|
|
|
|
out += '\n'
|
|
|
|
added = added[1:]
|
|
|
|
out += gen_if(ifcond)
|
|
|
|
out += added
|
|
|
|
out += gen_endif(ifcond)
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
def gen_enum_lookup(name, values, prefix=None):
|
2018-02-19 22:08:05 +00:00
|
|
|
ret = mcgen('''
|
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
const char *const %(c_name)s_lookup[] = {
|
2018-02-19 22:08:05 +00:00
|
|
|
''',
|
2018-02-19 22:33:32 +00:00
|
|
|
c_name=c_name(name))
|
2018-02-19 22:08:05 +00:00
|
|
|
for value in values:
|
|
|
|
index = c_enum_const(name, value, prefix)
|
|
|
|
ret += mcgen('''
|
|
|
|
"%(value)s",
|
|
|
|
''',
|
2018-03-03 23:23:47 +00:00
|
|
|
index=index, value=value)
|
2018-02-19 22:08:05 +00:00
|
|
|
|
qapi: Don't let implicit enum MAX member collide
Now that we guarantee the user doesn't have any enum values
beginning with a single underscore, we can use that for our
own purposes. Renaming ENUM_MAX to ENUM__MAX makes it obvious
that the sentinel is generated.
This patch was mostly generated by applying a temporary patch:
|diff --git a/scripts/qapi.py b/scripts/qapi.py
|index e6d014b..b862ec9 100644
|--- a/scripts/qapi.py
|+++ b/scripts/qapi.py
|@@ -1570,6 +1570,7 @@ const char *const %(c_name)s_lookup[] = {
| max_index = c_enum_const(name, 'MAX', prefix)
| ret += mcgen('''
| [%(max_index)s] = NULL,
|+// %(max_index)s
| };
| ''',
| max_index=max_index)
then running:
$ cat qapi-{types,event}.c tests/test-qapi-types.c |
sed -n 's,^// \(.*\)MAX,s|\1MAX|\1_MAX|g,p' > list
$ git grep -l _MAX | xargs sed -i -f list
The only things not generated are the changes in scripts/qapi.py.
Rejecting enum members named 'MAX' is now useless, and will be dropped
in the next patch.
Backports commit 7fb1cf1606c78c9d5b538f29176fd5a101726a9d from qemu
2018-02-20 01:34:51 +00:00
|
|
|
max_index = c_enum_const(name, '_MAX', prefix)
|
2018-02-19 22:08:05 +00:00
|
|
|
ret += mcgen('''
|
|
|
|
NULL,
|
|
|
|
};
|
2018-03-03 23:23:47 +00:00
|
|
|
''',
|
|
|
|
max_index=max_index)
|
2018-02-19 22:33:32 +00:00
|
|
|
return ret
|
2018-02-19 22:08:05 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
def gen_enum(name, values, prefix=None):
|
|
|
|
# append automatically generated _MAX value
|
qapi: Don't let implicit enum MAX member collide
Now that we guarantee the user doesn't have any enum values
beginning with a single underscore, we can use that for our
own purposes. Renaming ENUM_MAX to ENUM__MAX makes it obvious
that the sentinel is generated.
This patch was mostly generated by applying a temporary patch:
|diff --git a/scripts/qapi.py b/scripts/qapi.py
|index e6d014b..b862ec9 100644
|--- a/scripts/qapi.py
|+++ b/scripts/qapi.py
|@@ -1570,6 +1570,7 @@ const char *const %(c_name)s_lookup[] = {
| max_index = c_enum_const(name, 'MAX', prefix)
| ret += mcgen('''
| [%(max_index)s] = NULL,
|+// %(max_index)s
| };
| ''',
| max_index=max_index)
then running:
$ cat qapi-{types,event}.c tests/test-qapi-types.c |
sed -n 's,^// \(.*\)MAX,s|\1MAX|\1_MAX|g,p' > list
$ git grep -l _MAX | xargs sed -i -f list
The only things not generated are the changes in scripts/qapi.py.
Rejecting enum members named 'MAX' is now useless, and will be dropped
in the next patch.
Backports commit 7fb1cf1606c78c9d5b538f29176fd5a101726a9d from qemu
2018-02-20 01:34:51 +00:00
|
|
|
enum_values = values + ['_MAX']
|
2018-03-03 23:23:47 +00:00
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
ret = mcgen('''
|
2018-02-19 22:08:05 +00:00
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
typedef enum %(c_name)s {
|
2018-02-19 22:08:05 +00:00
|
|
|
''',
|
2018-02-19 22:33:32 +00:00
|
|
|
c_name=c_name(name))
|
2018-02-19 22:08:05 +00:00
|
|
|
|
|
|
|
for value in enum_values:
|
2018-02-19 22:33:32 +00:00
|
|
|
ret += mcgen('''
|
2018-12-18 10:03:19 +00:00
|
|
|
%(c_enum)s,
|
2018-02-19 22:08:05 +00:00
|
|
|
''',
|
2018-12-18 10:03:19 +00:00
|
|
|
c_enum=c_enum_const(name, value, prefix))
|
2018-02-19 22:08:05 +00:00
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
ret += mcgen('''
|
|
|
|
} %(c_name)s;
|
2018-02-19 22:08:05 +00:00
|
|
|
''',
|
2018-02-19 22:33:32 +00:00
|
|
|
c_name=c_name(name))
|
2018-02-19 22:08:05 +00:00
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
ret += mcgen('''
|
2018-03-03 23:23:47 +00:00
|
|
|
|
2018-02-19 22:33:32 +00:00
|
|
|
extern const char *const %(c_name)s_lookup[];
|
|
|
|
''',
|
|
|
|
c_name=c_name(name))
|
|
|
|
return ret
|
2018-02-19 22:08:05 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-09-26 01:09:31 +00:00
|
|
|
def build_params(arg_type, boxed, extra=None):
|
2018-03-03 23:23:47 +00:00
|
|
|
ret = ''
|
|
|
|
sep = ''
|
2018-02-26 01:16:58 +00:00
|
|
|
if boxed:
|
2018-09-26 01:09:31 +00:00
|
|
|
assert arg_type
|
2018-02-26 01:22:00 +00:00
|
|
|
ret += '%s arg' % arg_type.c_param_type()
|
|
|
|
sep = ', '
|
2018-09-26 01:09:31 +00:00
|
|
|
elif arg_type:
|
2018-02-26 01:16:58 +00:00
|
|
|
assert not arg_type.variants
|
|
|
|
for memb in arg_type.members:
|
|
|
|
ret += sep
|
|
|
|
sep = ', '
|
|
|
|
if memb.optional:
|
|
|
|
ret += 'bool has_%s, ' % c_name(memb.name)
|
|
|
|
ret += '%s %s' % (memb.type.c_param_type(),
|
|
|
|
c_name(memb.name))
|
2018-02-19 22:38:50 +00:00
|
|
|
if extra:
|
|
|
|
ret += sep + extra
|
2018-09-26 01:09:31 +00:00
|
|
|
return ret if ret else 'void'
|
2018-02-19 22:38:50 +00:00
|
|
|
|
2018-02-19 23:27:47 +00:00
|
|
|
|
2018-02-19 20:47:16 +00:00
|
|
|
#
|
2018-03-09 12:53:43 +00:00
|
|
|
# Accumulate and write output
|
2018-02-19 20:47:16 +00:00
|
|
|
#
|
|
|
|
|
2018-03-09 12:53:43 +00:00
|
|
|
class QAPIGen(object):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self._preamble = ''
|
|
|
|
self._body = ''
|
|
|
|
|
|
|
|
def preamble_add(self, text):
|
|
|
|
self._preamble += text
|
|
|
|
|
|
|
|
def add(self, text):
|
|
|
|
self._body += text
|
|
|
|
|
2018-07-05 16:07:12 +00:00
|
|
|
def get_content(self, fname=None):
|
|
|
|
return (self._top(fname) + self._preamble + self._body
|
|
|
|
+ self._bottom(fname))
|
|
|
|
|
2018-03-09 12:53:43 +00:00
|
|
|
def _top(self, fname):
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def _bottom(self, fname):
|
|
|
|
return ''
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-09 12:53:43 +00:00
|
|
|
def write(self, output_dir, fname):
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
pathname = os.path.join(output_dir, fname)
|
|
|
|
dir = os.path.dirname(pathname)
|
|
|
|
if dir:
|
2018-03-09 12:53:43 +00:00
|
|
|
try:
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
os.makedirs(dir)
|
2018-03-09 12:53:43 +00:00
|
|
|
except os.error as e:
|
|
|
|
if e.errno != errno.EEXIST:
|
|
|
|
raise
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
fd = os.open(pathname, os.O_RDWR | os.O_CREAT, 0o666)
|
2018-07-03 03:01:21 +00:00
|
|
|
if sys.version_info[0] >= 3:
|
|
|
|
f = open(fd, 'r+', encoding='utf-8')
|
|
|
|
else:
|
|
|
|
f = os.fdopen(fd, 'r+')
|
2018-07-05 16:07:12 +00:00
|
|
|
text = self.get_content(fname)
|
2018-03-09 13:51:12 +00:00
|
|
|
oldtext = f.read(len(text) + 1)
|
|
|
|
if text != oldtext:
|
|
|
|
f.seek(0)
|
|
|
|
f.truncate(0)
|
|
|
|
f.write(text)
|
2018-03-09 12:53:43 +00:00
|
|
|
f.close()
|
|
|
|
|
|
|
|
|
2018-07-05 16:07:12 +00:00
|
|
|
@contextmanager
|
|
|
|
def ifcontext(ifcond, *args):
|
|
|
|
"""A 'with' statement context manager to wrap with start_if()/end_if()
|
|
|
|
*args: any number of QAPIGenCCode
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
with ifcontext(ifcond, self._genh, self._genc):
|
|
|
|
modify self._genh and self._genc ...
|
|
|
|
|
|
|
|
Is equivalent to calling::
|
|
|
|
|
|
|
|
self._genh.start_if(ifcond)
|
|
|
|
self._genc.start_if(ifcond)
|
|
|
|
modify self._genh and self._genc ...
|
|
|
|
self._genh.end_if()
|
|
|
|
self._genc.end_if()
|
|
|
|
"""
|
|
|
|
for arg in args:
|
|
|
|
arg.start_if(ifcond)
|
|
|
|
yield
|
|
|
|
for arg in args:
|
|
|
|
arg.end_if()
|
|
|
|
|
|
|
|
|
|
|
|
class QAPIGenCCode(QAPIGen):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
QAPIGen.__init__(self)
|
|
|
|
self._start_if = None
|
|
|
|
|
|
|
|
def start_if(self, ifcond):
|
|
|
|
assert self._start_if is None
|
|
|
|
self._start_if = (ifcond, self._body, self._preamble)
|
|
|
|
|
|
|
|
def end_if(self):
|
|
|
|
assert self._start_if
|
|
|
|
self._wrap_ifcond()
|
|
|
|
self._start_if = None
|
|
|
|
|
|
|
|
def _wrap_ifcond(self):
|
|
|
|
self._body = _wrap_ifcond(self._start_if[0],
|
|
|
|
self._start_if[1], self._body)
|
|
|
|
self._preamble = _wrap_ifcond(self._start_if[0],
|
|
|
|
self._start_if[2], self._preamble)
|
|
|
|
|
|
|
|
def get_content(self, fname=None):
|
|
|
|
assert self._start_if is None
|
|
|
|
return QAPIGen.get_content(self, fname)
|
|
|
|
|
|
|
|
|
|
|
|
class QAPIGenC(QAPIGenCCode):
|
2018-03-09 12:53:43 +00:00
|
|
|
|
|
|
|
def __init__(self, blurb, pydoc):
|
2018-07-05 16:07:12 +00:00
|
|
|
QAPIGenCCode.__init__(self)
|
2018-03-09 12:53:43 +00:00
|
|
|
QAPIGen.__init__(self)
|
|
|
|
self._blurb = blurb
|
|
|
|
self._copyright = '\n * '.join(re.findall(r'^Copyright .*', pydoc,
|
|
|
|
re.MULTILINE))
|
|
|
|
|
|
|
|
def _top(self, fname):
|
|
|
|
return mcgen('''
|
|
|
|
/* AUTOMATICALLY GENERATED, DO NOT MODIFY */
|
2018-03-09 12:40:43 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
%(blurb)s
|
2018-03-09 12:42:58 +00:00
|
|
|
*
|
|
|
|
* %(copyright)s
|
2018-03-09 12:40:43 +00:00
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
|
|
*/
|
|
|
|
|
|
|
|
''',
|
2018-03-09 12:53:43 +00:00
|
|
|
blurb=self._blurb, copyright=self._copyright)
|
2018-02-19 20:31:47 +00:00
|
|
|
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
def _bottom(self, fname):
|
|
|
|
return mcgen('''
|
2018-09-26 01:12:13 +00:00
|
|
|
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
/* Dummy declaration to prevent empty .o file */
|
|
|
|
char dummy_%(name)s;
|
|
|
|
''',
|
|
|
|
name=c_name(fname))
|
|
|
|
|
2018-02-19 20:31:47 +00:00
|
|
|
|
2018-03-09 12:53:43 +00:00
|
|
|
class QAPIGenH(QAPIGenC):
|
|
|
|
def _top(self, fname):
|
|
|
|
return QAPIGenC._top(self, fname) + guardstart(fname)
|
2018-02-19 20:31:47 +00:00
|
|
|
|
2018-03-09 12:53:43 +00:00
|
|
|
def _bottom(self, fname):
|
|
|
|
return guardend(fname)
|
2018-02-19 20:31:47 +00:00
|
|
|
|
2018-02-19 23:07:49 +00:00
|
|
|
|
2018-03-09 12:53:43 +00:00
|
|
|
class QAPIGenDoc(QAPIGen):
|
|
|
|
def _top(self, fname):
|
|
|
|
return (QAPIGen._top(self, fname)
|
|
|
|
+ '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n\n')
|
2018-03-09 12:40:43 +00:00
|
|
|
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
|
2018-03-09 14:10:38 +00:00
|
|
|
class QAPISchemaMonolithicCVisitor(QAPISchemaVisitor):
|
|
|
|
|
|
|
|
def __init__(self, prefix, what, blurb, pydoc):
|
|
|
|
self._prefix = prefix
|
|
|
|
self._what = what
|
|
|
|
self._genc = QAPIGenC(blurb, pydoc)
|
|
|
|
self._genh = QAPIGenH(blurb, pydoc)
|
|
|
|
|
|
|
|
def write(self, output_dir):
|
|
|
|
self._genc.write(output_dir, self._prefix + self._what + '.c')
|
|
|
|
self._genh.write(output_dir, self._prefix + self._what + '.h')
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaModularCVisitor(QAPISchemaVisitor):
|
|
|
|
|
|
|
|
def __init__(self, prefix, what, blurb, pydoc):
|
|
|
|
self._prefix = prefix
|
|
|
|
self._what = what
|
|
|
|
self._blurb = blurb
|
|
|
|
self._pydoc = pydoc
|
|
|
|
self._module = {}
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
self._main_module = None
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
|
|
|
|
def _module_basename(self, what, name):
|
|
|
|
if name is None:
|
|
|
|
return re.sub(r'-', '-builtin-', what)
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
basename = os.path.join(os.path.dirname(name),
|
|
|
|
self._prefix + what)
|
|
|
|
if name == self._main_module:
|
|
|
|
return basename
|
|
|
|
return basename + '-' + os.path.splitext(os.path.basename(name))[0]
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
|
|
|
|
def _add_module(self, name, blurb):
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
if self._main_module is None and name is not None:
|
|
|
|
self._main_module = name
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
genc = QAPIGenC(blurb, self._pydoc)
|
|
|
|
genh = QAPIGenH(blurb, self._pydoc)
|
|
|
|
self._module[name] = (genc, genh)
|
|
|
|
self._set_module(name)
|
|
|
|
|
|
|
|
def _set_module(self, name):
|
|
|
|
self._genc, self._genh = self._module[name]
|
|
|
|
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
def write(self, output_dir, opt_builtins=False):
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
for name in self._module:
|
|
|
|
if name is None and not opt_builtins:
|
|
|
|
continue
|
|
|
|
basename = self._module_basename(self._what, name)
|
|
|
|
(genc, genh) = self._module[name]
|
|
|
|
genc.write(output_dir, basename + '.c')
|
|
|
|
genh.write(output_dir, basename + '.h')
|
|
|
|
|
|
|
|
def _begin_module(self, name):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_module(self, name):
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
if name in self._module:
|
|
|
|
self._set_module(name)
|
qapi/types qapi/visit: Generate built-in stuff into separate files
Linking code from multiple separate QAPI schemata into the same
program is possible, but involves some weirdness around built-in
types:
* We generate code for built-in types into .c only with option
--builtins. The user is responsible for generating code for exactly
one QAPI schema per program with --builtins.
* We generate code for built-in types into .h regardless of
--builtins, but guarded by #ifndef QAPI_VISIT_BUILTIN. Because all
copies of this code are exactly the same, including any combination
of these headers works.
Replace this contraption by something more conventional: generate code
for built-in types into their very own files: qapi-builtin-types.c,
qapi-builtin-visit.c, qapi-builtin-types.h, qapi-builtin-visit.h, but
only with --builtins. Obey --output-dir, but ignore --prefix for
them.
Make qapi-types.h include qapi-builtin-types.h. With multiple
schemata you now have multiple qapi-types.[ch], but only one
qapi-builtin-types.[ch]. Same for qapi-visit.[ch] and
qapi-builtin-visit.[ch].
Bonus: if all you need is built-in stuff, you can include a much
smaller header. To be exploited shortly.
Backports commit cdb6610ae4283720037bae2af1f78bd40eb5fe71 from qemu
2018-03-09 14:17:54 +00:00
|
|
|
return
|
|
|
|
self._add_module(name, self._blurb)
|
|
|
|
self._begin_module(name)
|
qapi: Generate separate .h, .c for each module
Our qapi-schema.json is composed of modules connected by include
directives, but the generated code is monolithic all the same: one
qapi-types.h with all the types, one qapi-visit.h with all the
visitors, and so forth. These monolithic headers get included all
over the place. In my "build everything" tree, adding a QAPI type
recompiles about 4800 out of 5100 objects.
We wouldn't write such monolithic headers by hand. It stands to
reason that we shouldn't generate them, either.
Split up generated qapi-types.h to mirror the schema's modular
structure: one header per module. Name the main module's header
qapi-types.h, and sub-module D/B.json's header D/qapi-types-B.h.
Mirror the schema's includes in the headers, so that qapi-types.h gets
you everything exactly as before. If you need less, you can include
one or more of the sub-module headers. To be exploited shortly.
Split up qapi-types.c, qapi-visit.h, qapi-visit.c, qmp-commands.h,
qmp-commands.c, qapi-event.h, qapi-event.c the same way.
qmp-introspect.h, qmp-introspect.c and qapi.texi remain monolithic.
The split of qmp-commands.c duplicates static helper function
qmp_marshal_output_str() in qapi-commands-char.c and
qapi-commands-misc.c. This happens when commands returning the same
type occur in multiple modules. Not worth avoiding.
Since I'm going to rename qapi-event.[ch] to qapi-events.[ch], and
qmp-commands.[ch] to qapi-commands.[ch], name the shards that way
already, to reduce churn. This requires temporary hacks in
commands.py and events.py. Similarly, c_name() must temporarily
be taught to munge '/' in common.py. They'll go away with the rename.
Backports commit 252dc3105fc494182e236e97fe20f2d6b1d652cb from qemu
2018-03-09 14:38:13 +00:00
|
|
|
|
|
|
|
def visit_include(self, name, info):
|
|
|
|
basename = self._module_basename(self._what, name)
|
|
|
|
self._genh.preamble_add(mcgen('''
|
|
|
|
#include "%(basename)s.h"
|
|
|
|
''',
|
|
|
|
basename=basename))
|