mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-01 23:03:54 -06:00
qapi: rework qapi Exception
Use a base class QAPIError, and QAPIParseError for parser errors and QAPISemError for semantic errors, suggested by Markus Armbruster. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Message-Id: <20170113144135.5150-12-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
parent
91d00e57a1
commit
4148c298d8
1 changed files with 157 additions and 179 deletions
336
scripts/qapi.py
336
scripts/qapi.py
|
@ -91,35 +91,38 @@ def error_path(parent):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class QAPISchemaError(Exception):
|
class QAPIError(Exception):
|
||||||
def __init__(self, schema, msg):
|
def __init__(self, fname, line, col, incl_info, msg):
|
||||||
Exception.__init__(self)
|
Exception.__init__(self)
|
||||||
self.fname = schema.fname
|
self.fname = fname
|
||||||
|
self.line = line
|
||||||
|
self.col = col
|
||||||
|
self.info = incl_info
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.col = 1
|
|
||||||
self.line = schema.line
|
def __str__(self):
|
||||||
for ch in schema.src[schema.line_pos:schema.pos]:
|
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]:
|
||||||
if ch == '\t':
|
if ch == '\t':
|
||||||
self.col = (self.col + 7) % 8 + 1
|
col = (col + 7) % 8 + 1
|
||||||
else:
|
else:
|
||||||
self.col += 1
|
col += 1
|
||||||
self.info = schema.incl_info
|
QAPIError.__init__(self, parser.fname, parser.line, col,
|
||||||
|
parser.incl_info, msg)
|
||||||
def __str__(self):
|
|
||||||
return error_path(self.info) + \
|
|
||||||
"%s:%d:%d: %s" % (self.fname, self.line, self.col, self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
class QAPIExprError(Exception):
|
class QAPISemError(QAPIError):
|
||||||
def __init__(self, expr_info, msg):
|
def __init__(self, info, msg):
|
||||||
Exception.__init__(self)
|
QAPIError.__init__(self, info['file'], info['line'], None,
|
||||||
assert expr_info
|
info['parent'], msg)
|
||||||
self.info = expr_info
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return error_path(self.info['parent']) + \
|
|
||||||
"%s:%d: %s" % (self.info['file'], self.info['line'], self.msg)
|
|
||||||
|
|
||||||
|
|
||||||
class QAPISchemaParser(object):
|
class QAPISchemaParser(object):
|
||||||
|
@ -140,25 +143,24 @@ class QAPISchemaParser(object):
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
while self.tok is not None:
|
while self.tok is not None:
|
||||||
expr_info = {'file': fname, 'line': self.line,
|
info = {'file': fname, 'line': self.line,
|
||||||
'parent': self.incl_info}
|
'parent': self.incl_info}
|
||||||
expr = self.get_expr(False)
|
expr = self.get_expr(False)
|
||||||
if isinstance(expr, dict) and "include" in expr:
|
if isinstance(expr, dict) and "include" in expr:
|
||||||
if len(expr) != 1:
|
if len(expr) != 1:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Invalid 'include' directive")
|
||||||
"Invalid 'include' directive")
|
|
||||||
include = expr["include"]
|
include = expr["include"]
|
||||||
if not isinstance(include, str):
|
if not isinstance(include, str):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"Value of 'include' must be a string")
|
"Value of 'include' must be a string")
|
||||||
incl_abs_fname = os.path.join(os.path.dirname(abs_fname),
|
incl_abs_fname = os.path.join(os.path.dirname(abs_fname),
|
||||||
include)
|
include)
|
||||||
# catch inclusion cycle
|
# catch inclusion cycle
|
||||||
inf = expr_info
|
inf = info
|
||||||
while inf:
|
while inf:
|
||||||
if incl_abs_fname == os.path.abspath(inf['file']):
|
if incl_abs_fname == os.path.abspath(inf['file']):
|
||||||
raise QAPIExprError(expr_info, "Inclusion loop for %s"
|
raise QAPISemError(info, "Inclusion loop for %s"
|
||||||
% include)
|
% include)
|
||||||
inf = inf['parent']
|
inf = inf['parent']
|
||||||
# skip multiple include of the same file
|
# skip multiple include of the same file
|
||||||
if incl_abs_fname in previously_included:
|
if incl_abs_fname in previously_included:
|
||||||
|
@ -166,14 +168,13 @@ class QAPISchemaParser(object):
|
||||||
try:
|
try:
|
||||||
fobj = open(incl_abs_fname, 'r')
|
fobj = open(incl_abs_fname, 'r')
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, '%s: %s' % (e.strerror, include))
|
||||||
'%s: %s' % (e.strerror, include))
|
|
||||||
exprs_include = QAPISchemaParser(fobj, previously_included,
|
exprs_include = QAPISchemaParser(fobj, previously_included,
|
||||||
expr_info)
|
info)
|
||||||
self.exprs.extend(exprs_include.exprs)
|
self.exprs.extend(exprs_include.exprs)
|
||||||
else:
|
else:
|
||||||
expr_elem = {'expr': expr,
|
expr_elem = {'expr': expr,
|
||||||
'info': expr_info}
|
'info': info}
|
||||||
self.exprs.append(expr_elem)
|
self.exprs.append(expr_elem)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
|
@ -194,8 +195,7 @@ class QAPISchemaParser(object):
|
||||||
ch = self.src[self.cursor]
|
ch = self.src[self.cursor]
|
||||||
self.cursor += 1
|
self.cursor += 1
|
||||||
if ch == '\n':
|
if ch == '\n':
|
||||||
raise QAPISchemaError(self,
|
raise QAPIParseError(self, 'Missing terminating "\'"')
|
||||||
'Missing terminating "\'"')
|
|
||||||
if esc:
|
if esc:
|
||||||
if ch == 'b':
|
if ch == 'b':
|
||||||
string += '\b'
|
string += '\b'
|
||||||
|
@ -213,25 +213,25 @@ class QAPISchemaParser(object):
|
||||||
ch = self.src[self.cursor]
|
ch = self.src[self.cursor]
|
||||||
self.cursor += 1
|
self.cursor += 1
|
||||||
if ch not in "0123456789abcdefABCDEF":
|
if ch not in "0123456789abcdefABCDEF":
|
||||||
raise QAPISchemaError(self,
|
raise QAPIParseError(self,
|
||||||
'\\u escape needs 4 '
|
'\\u escape needs 4 '
|
||||||
'hex digits')
|
'hex digits')
|
||||||
value = (value << 4) + int(ch, 16)
|
value = (value << 4) + int(ch, 16)
|
||||||
# If Python 2 and 3 didn't disagree so much on
|
# If Python 2 and 3 didn't disagree so much on
|
||||||
# how to handle Unicode, then we could allow
|
# how to handle Unicode, then we could allow
|
||||||
# Unicode string defaults. But most of QAPI is
|
# Unicode string defaults. But most of QAPI is
|
||||||
# ASCII-only, so we aren't losing much for now.
|
# ASCII-only, so we aren't losing much for now.
|
||||||
if not value or value > 0x7f:
|
if not value or value > 0x7f:
|
||||||
raise QAPISchemaError(self,
|
raise QAPIParseError(self,
|
||||||
'For now, \\u escape '
|
'For now, \\u escape '
|
||||||
'only supports non-zero '
|
'only supports non-zero '
|
||||||
'values up to \\u007f')
|
'values up to \\u007f')
|
||||||
string += chr(value)
|
string += chr(value)
|
||||||
elif ch in "\\/'\"":
|
elif ch in "\\/'\"":
|
||||||
string += ch
|
string += ch
|
||||||
else:
|
else:
|
||||||
raise QAPISchemaError(self,
|
raise QAPIParseError(self,
|
||||||
"Unknown escape \\%s" % ch)
|
"Unknown escape \\%s" % ch)
|
||||||
esc = False
|
esc = False
|
||||||
elif ch == "\\":
|
elif ch == "\\":
|
||||||
esc = True
|
esc = True
|
||||||
|
@ -259,7 +259,7 @@ class QAPISchemaParser(object):
|
||||||
self.line += 1
|
self.line += 1
|
||||||
self.line_pos = self.cursor
|
self.line_pos = self.cursor
|
||||||
elif not self.tok.isspace():
|
elif not self.tok.isspace():
|
||||||
raise QAPISchemaError(self, 'Stray "%s"' % self.tok)
|
raise QAPIParseError(self, 'Stray "%s"' % self.tok)
|
||||||
|
|
||||||
def get_members(self):
|
def get_members(self):
|
||||||
expr = OrderedDict()
|
expr = OrderedDict()
|
||||||
|
@ -267,24 +267,24 @@ class QAPISchemaParser(object):
|
||||||
self.accept()
|
self.accept()
|
||||||
return expr
|
return expr
|
||||||
if self.tok != "'":
|
if self.tok != "'":
|
||||||
raise QAPISchemaError(self, 'Expected string or "}"')
|
raise QAPIParseError(self, 'Expected string or "}"')
|
||||||
while True:
|
while True:
|
||||||
key = self.val
|
key = self.val
|
||||||
self.accept()
|
self.accept()
|
||||||
if self.tok != ':':
|
if self.tok != ':':
|
||||||
raise QAPISchemaError(self, 'Expected ":"')
|
raise QAPIParseError(self, 'Expected ":"')
|
||||||
self.accept()
|
self.accept()
|
||||||
if key in expr:
|
if key in expr:
|
||||||
raise QAPISchemaError(self, 'Duplicate key "%s"' % key)
|
raise QAPIParseError(self, 'Duplicate key "%s"' % key)
|
||||||
expr[key] = self.get_expr(True)
|
expr[key] = self.get_expr(True)
|
||||||
if self.tok == '}':
|
if self.tok == '}':
|
||||||
self.accept()
|
self.accept()
|
||||||
return expr
|
return expr
|
||||||
if self.tok != ',':
|
if self.tok != ',':
|
||||||
raise QAPISchemaError(self, 'Expected "," or "}"')
|
raise QAPIParseError(self, 'Expected "," or "}"')
|
||||||
self.accept()
|
self.accept()
|
||||||
if self.tok != "'":
|
if self.tok != "'":
|
||||||
raise QAPISchemaError(self, 'Expected string')
|
raise QAPIParseError(self, 'Expected string')
|
||||||
|
|
||||||
def get_values(self):
|
def get_values(self):
|
||||||
expr = []
|
expr = []
|
||||||
|
@ -292,20 +292,20 @@ class QAPISchemaParser(object):
|
||||||
self.accept()
|
self.accept()
|
||||||
return expr
|
return expr
|
||||||
if self.tok not in "{['tfn":
|
if self.tok not in "{['tfn":
|
||||||
raise QAPISchemaError(self, 'Expected "{", "[", "]", string, '
|
raise QAPIParseError(self, 'Expected "{", "[", "]", string, '
|
||||||
'boolean or "null"')
|
'boolean or "null"')
|
||||||
while True:
|
while True:
|
||||||
expr.append(self.get_expr(True))
|
expr.append(self.get_expr(True))
|
||||||
if self.tok == ']':
|
if self.tok == ']':
|
||||||
self.accept()
|
self.accept()
|
||||||
return expr
|
return expr
|
||||||
if self.tok != ',':
|
if self.tok != ',':
|
||||||
raise QAPISchemaError(self, 'Expected "," or "]"')
|
raise QAPIParseError(self, 'Expected "," or "]"')
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
def get_expr(self, nested):
|
def get_expr(self, nested):
|
||||||
if self.tok != '{' and not nested:
|
if self.tok != '{' and not nested:
|
||||||
raise QAPISchemaError(self, 'Expected "{"')
|
raise QAPIParseError(self, 'Expected "{"')
|
||||||
if self.tok == '{':
|
if self.tok == '{':
|
||||||
self.accept()
|
self.accept()
|
||||||
expr = self.get_members()
|
expr = self.get_members()
|
||||||
|
@ -316,7 +316,7 @@ class QAPISchemaParser(object):
|
||||||
expr = self.val
|
expr = self.val
|
||||||
self.accept()
|
self.accept()
|
||||||
else:
|
else:
|
||||||
raise QAPISchemaError(self, 'Expected "{", "[" or string')
|
raise QAPIParseError(self, 'Expected "{", "[" or string')
|
||||||
return expr
|
return expr
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -375,20 +375,18 @@ valid_name = re.compile('^(__[a-zA-Z0-9.-]+_)?'
|
||||||
'[a-zA-Z][a-zA-Z0-9_-]*$')
|
'[a-zA-Z][a-zA-Z0-9_-]*$')
|
||||||
|
|
||||||
|
|
||||||
def check_name(expr_info, source, name, allow_optional=False,
|
def check_name(info, source, name, allow_optional=False,
|
||||||
enum_member=False):
|
enum_member=False):
|
||||||
global valid_name
|
global valid_name
|
||||||
membername = name
|
membername = name
|
||||||
|
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "%s requires a string name" % source)
|
||||||
"%s requires a string name" % source)
|
|
||||||
if name.startswith('*'):
|
if name.startswith('*'):
|
||||||
membername = name[1:]
|
membername = name[1:]
|
||||||
if not allow_optional:
|
if not allow_optional:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "%s does not allow optional name '%s'"
|
||||||
"%s does not allow optional name '%s'"
|
% (source, name))
|
||||||
% (source, name))
|
|
||||||
# Enum members can start with a digit, because the generated C
|
# Enum members can start with a digit, because the generated C
|
||||||
# code always prefixes it with the enum name
|
# code always prefixes it with the enum name
|
||||||
if enum_member and membername[0].isdigit():
|
if enum_member and membername[0].isdigit():
|
||||||
|
@ -397,8 +395,7 @@ def check_name(expr_info, source, name, allow_optional=False,
|
||||||
# and 'q_obj_*' implicit type names.
|
# and 'q_obj_*' implicit type names.
|
||||||
if not valid_name.match(membername) or \
|
if not valid_name.match(membername) or \
|
||||||
c_name(membername, False).startswith('q_'):
|
c_name(membername, False).startswith('q_'):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
|
||||||
"%s uses invalid name '%s'" % (source, name))
|
|
||||||
|
|
||||||
|
|
||||||
def add_name(name, info, meta, implicit=False):
|
def add_name(name, info, meta, implicit=False):
|
||||||
|
@ -407,13 +404,11 @@ def add_name(name, info, meta, implicit=False):
|
||||||
# FIXME should reject names that differ only in '_' vs. '.'
|
# FIXME should reject names that differ only in '_' vs. '.'
|
||||||
# vs. '-', because they're liable to clash in generated C.
|
# vs. '-', because they're liable to clash in generated C.
|
||||||
if name in all_names:
|
if name in all_names:
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info, "%s '%s' is already defined"
|
||||||
"%s '%s' is already defined"
|
% (all_names[name], name))
|
||||||
% (all_names[name], name))
|
|
||||||
if not implicit and (name.endswith('Kind') or name.endswith('List')):
|
if not implicit and (name.endswith('Kind') or name.endswith('List')):
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info, "%s '%s' should not end in '%s'"
|
||||||
"%s '%s' should not end in '%s'"
|
% (meta, name, name[-4:]))
|
||||||
% (meta, name, name[-4:]))
|
|
||||||
all_names[name] = meta
|
all_names[name] = meta
|
||||||
|
|
||||||
|
|
||||||
|
@ -465,7 +460,7 @@ def is_enum(name):
|
||||||
return find_enum(name) is not None
|
return find_enum(name) is not None
|
||||||
|
|
||||||
|
|
||||||
def check_type(expr_info, source, value, allow_array=False,
|
def check_type(info, source, value, allow_array=False,
|
||||||
allow_dict=False, allow_optional=False,
|
allow_dict=False, allow_optional=False,
|
||||||
allow_metas=[]):
|
allow_metas=[]):
|
||||||
global all_names
|
global all_names
|
||||||
|
@ -476,69 +471,64 @@ def check_type(expr_info, source, value, allow_array=False,
|
||||||
# Check if array type for value is okay
|
# Check if array type for value is okay
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
if not allow_array:
|
if not allow_array:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "%s cannot be an array" % source)
|
||||||
"%s cannot be an array" % source)
|
|
||||||
if len(value) != 1 or not isinstance(value[0], str):
|
if len(value) != 1 or not isinstance(value[0], str):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"%s: array type must contain single type name"
|
"%s: array type must contain single type name" %
|
||||||
% source)
|
source)
|
||||||
value = value[0]
|
value = value[0]
|
||||||
|
|
||||||
# Check if type name for value is okay
|
# Check if type name for value is okay
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
if value not in all_names:
|
if value not in all_names:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "%s uses unknown type '%s'"
|
||||||
"%s uses unknown type '%s'"
|
% (source, value))
|
||||||
% (source, value))
|
|
||||||
if not all_names[value] in allow_metas:
|
if not all_names[value] in allow_metas:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "%s cannot use %s type '%s'" %
|
||||||
"%s cannot use %s type '%s'"
|
(source, all_names[value], value))
|
||||||
% (source, all_names[value], value))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if not allow_dict:
|
if not allow_dict:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "%s should be a type name" % source)
|
||||||
"%s should be a type name" % source)
|
|
||||||
|
|
||||||
if not isinstance(value, OrderedDict):
|
if not isinstance(value, OrderedDict):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"%s should be a dictionary or type name" % source)
|
"%s should be a dictionary or type name" % source)
|
||||||
|
|
||||||
# value is a dictionary, check that each member is okay
|
# value is a dictionary, check that each member is okay
|
||||||
for (key, arg) in value.items():
|
for (key, arg) in value.items():
|
||||||
check_name(expr_info, "Member of %s" % source, key,
|
check_name(info, "Member of %s" % source, key,
|
||||||
allow_optional=allow_optional)
|
allow_optional=allow_optional)
|
||||||
if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
|
if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Member of %s uses reserved name '%s'"
|
||||||
"Member of %s uses reserved name '%s'"
|
% (source, key))
|
||||||
% (source, key))
|
|
||||||
# Todo: allow dictionaries to represent default values of
|
# Todo: allow dictionaries to represent default values of
|
||||||
# an optional argument.
|
# an optional argument.
|
||||||
check_type(expr_info, "Member '%s' of %s" % (key, source), arg,
|
check_type(info, "Member '%s' of %s" % (key, source), arg,
|
||||||
allow_array=True,
|
allow_array=True,
|
||||||
allow_metas=['built-in', 'union', 'alternate', 'struct',
|
allow_metas=['built-in', 'union', 'alternate', 'struct',
|
||||||
'enum'])
|
'enum'])
|
||||||
|
|
||||||
|
|
||||||
def check_command(expr, expr_info):
|
def check_command(expr, info):
|
||||||
name = expr['command']
|
name = expr['command']
|
||||||
boxed = expr.get('boxed', False)
|
boxed = expr.get('boxed', False)
|
||||||
|
|
||||||
args_meta = ['struct']
|
args_meta = ['struct']
|
||||||
if boxed:
|
if boxed:
|
||||||
args_meta += ['union', 'alternate']
|
args_meta += ['union', 'alternate']
|
||||||
check_type(expr_info, "'data' for command '%s'" % name,
|
check_type(info, "'data' for command '%s'" % name,
|
||||||
expr.get('data'), allow_dict=not boxed, allow_optional=True,
|
expr.get('data'), allow_dict=not boxed, allow_optional=True,
|
||||||
allow_metas=args_meta)
|
allow_metas=args_meta)
|
||||||
returns_meta = ['union', 'struct']
|
returns_meta = ['union', 'struct']
|
||||||
if name in returns_whitelist:
|
if name in returns_whitelist:
|
||||||
returns_meta += ['built-in', 'alternate', 'enum']
|
returns_meta += ['built-in', 'alternate', 'enum']
|
||||||
check_type(expr_info, "'returns' for command '%s'" % name,
|
check_type(info, "'returns' for command '%s'" % name,
|
||||||
expr.get('returns'), allow_array=True,
|
expr.get('returns'), allow_array=True,
|
||||||
allow_optional=True, allow_metas=returns_meta)
|
allow_optional=True, allow_metas=returns_meta)
|
||||||
|
|
||||||
|
|
||||||
def check_event(expr, expr_info):
|
def check_event(expr, info):
|
||||||
global events
|
global events
|
||||||
name = expr['event']
|
name = expr['event']
|
||||||
boxed = expr.get('boxed', False)
|
boxed = expr.get('boxed', False)
|
||||||
|
@ -547,12 +537,12 @@ def check_event(expr, expr_info):
|
||||||
if boxed:
|
if boxed:
|
||||||
meta += ['union', 'alternate']
|
meta += ['union', 'alternate']
|
||||||
events.append(name)
|
events.append(name)
|
||||||
check_type(expr_info, "'data' for event '%s'" % name,
|
check_type(info, "'data' for event '%s'" % name,
|
||||||
expr.get('data'), allow_dict=not boxed, allow_optional=True,
|
expr.get('data'), allow_dict=not boxed, allow_optional=True,
|
||||||
allow_metas=meta)
|
allow_metas=meta)
|
||||||
|
|
||||||
|
|
||||||
def check_union(expr, expr_info):
|
def check_union(expr, info):
|
||||||
name = expr['union']
|
name = expr['union']
|
||||||
base = expr.get('base')
|
base = expr.get('base')
|
||||||
discriminator = expr.get('discriminator')
|
discriminator = expr.get('discriminator')
|
||||||
|
@ -565,123 +555,117 @@ def check_union(expr, expr_info):
|
||||||
enum_define = None
|
enum_define = None
|
||||||
allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
|
allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
|
||||||
if base is not None:
|
if base is not None:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Simple union '%s' must not have a base" %
|
||||||
"Simple union '%s' must not have a base"
|
name)
|
||||||
% name)
|
|
||||||
|
|
||||||
# Else, it's a flat union.
|
# Else, it's a flat union.
|
||||||
else:
|
else:
|
||||||
# The object must have a string or dictionary 'base'.
|
# The object must have a string or dictionary 'base'.
|
||||||
check_type(expr_info, "'base' for union '%s'" % name,
|
check_type(info, "'base' for union '%s'" % name,
|
||||||
base, allow_dict=True, allow_optional=True,
|
base, allow_dict=True, allow_optional=True,
|
||||||
allow_metas=['struct'])
|
allow_metas=['struct'])
|
||||||
if not base:
|
if not base:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Flat union '%s' must have a base"
|
||||||
"Flat union '%s' must have a base"
|
% name)
|
||||||
% name)
|
|
||||||
base_members = find_base_members(base)
|
base_members = find_base_members(base)
|
||||||
assert base_members
|
assert base_members
|
||||||
|
|
||||||
# The value of member 'discriminator' must name a non-optional
|
# The value of member 'discriminator' must name a non-optional
|
||||||
# member of the base struct.
|
# member of the base struct.
|
||||||
check_name(expr_info, "Discriminator of flat union '%s'" % name,
|
check_name(info, "Discriminator of flat union '%s'" % name,
|
||||||
discriminator)
|
discriminator)
|
||||||
discriminator_type = base_members.get(discriminator)
|
discriminator_type = base_members.get(discriminator)
|
||||||
if not discriminator_type:
|
if not discriminator_type:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"Discriminator '%s' is not a member of base "
|
"Discriminator '%s' is not a member of base "
|
||||||
"struct '%s'"
|
"struct '%s'"
|
||||||
% (discriminator, base))
|
% (discriminator, base))
|
||||||
enum_define = find_enum(discriminator_type)
|
enum_define = find_enum(discriminator_type)
|
||||||
allow_metas = ['struct']
|
allow_metas = ['struct']
|
||||||
# Do not allow string discriminator
|
# Do not allow string discriminator
|
||||||
if not enum_define:
|
if not enum_define:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"Discriminator '%s' must be of enumeration "
|
"Discriminator '%s' must be of enumeration "
|
||||||
"type" % discriminator)
|
"type" % discriminator)
|
||||||
|
|
||||||
# Check every branch; don't allow an empty union
|
# Check every branch; don't allow an empty union
|
||||||
if len(members) == 0:
|
if len(members) == 0:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
|
||||||
"Union '%s' cannot have empty 'data'" % name)
|
|
||||||
for (key, value) in members.items():
|
for (key, value) in members.items():
|
||||||
check_name(expr_info, "Member of union '%s'" % name, key)
|
check_name(info, "Member of union '%s'" % name, key)
|
||||||
|
|
||||||
# Each value must name a known type
|
# Each value must name a known type
|
||||||
check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
|
check_type(info, "Member '%s' of union '%s'" % (key, name),
|
||||||
value, allow_array=not base, allow_metas=allow_metas)
|
value, allow_array=not base, allow_metas=allow_metas)
|
||||||
|
|
||||||
# If the discriminator names an enum type, then all members
|
# If the discriminator names an enum type, then all members
|
||||||
# of 'data' must also be members of the enum type.
|
# of 'data' must also be members of the enum type.
|
||||||
if enum_define:
|
if enum_define:
|
||||||
if key not in enum_define['enum_values']:
|
if key not in enum_define['enum_values']:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"Discriminator value '%s' is not found in "
|
"Discriminator value '%s' is not found in "
|
||||||
"enum '%s'" %
|
"enum '%s'"
|
||||||
(key, enum_define["enum_name"]))
|
% (key, enum_define["enum_name"]))
|
||||||
|
|
||||||
# If discriminator is user-defined, ensure all values are covered
|
# If discriminator is user-defined, ensure all values are covered
|
||||||
if enum_define:
|
if enum_define:
|
||||||
for value in enum_define['enum_values']:
|
for value in enum_define['enum_values']:
|
||||||
if value not in members.keys():
|
if value not in members.keys():
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Union '%s' data missing '%s' branch"
|
||||||
"Union '%s' data missing '%s' branch"
|
% (name, value))
|
||||||
% (name, value))
|
|
||||||
|
|
||||||
|
|
||||||
def check_alternate(expr, expr_info):
|
def check_alternate(expr, info):
|
||||||
name = expr['alternate']
|
name = expr['alternate']
|
||||||
members = expr['data']
|
members = expr['data']
|
||||||
types_seen = {}
|
types_seen = {}
|
||||||
|
|
||||||
# Check every branch; require at least two branches
|
# Check every branch; require at least two branches
|
||||||
if len(members) < 2:
|
if len(members) < 2:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"Alternate '%s' should have at least two branches "
|
"Alternate '%s' should have at least two branches "
|
||||||
"in 'data'" % name)
|
"in 'data'" % name)
|
||||||
for (key, value) in members.items():
|
for (key, value) in members.items():
|
||||||
check_name(expr_info, "Member of alternate '%s'" % name, key)
|
check_name(info, "Member of alternate '%s'" % name, key)
|
||||||
|
|
||||||
# Ensure alternates have no type conflicts.
|
# Ensure alternates have no type conflicts.
|
||||||
check_type(expr_info, "Member '%s' of alternate '%s'" % (key, name),
|
check_type(info, "Member '%s' of alternate '%s'" % (key, name),
|
||||||
value,
|
value,
|
||||||
allow_metas=['built-in', 'union', 'struct', 'enum'])
|
allow_metas=['built-in', 'union', 'struct', 'enum'])
|
||||||
qtype = find_alternate_member_qtype(value)
|
qtype = find_alternate_member_qtype(value)
|
||||||
if not qtype:
|
if not qtype:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
|
||||||
"Alternate '%s' member '%s' cannot use "
|
"type '%s'" % (name, key, value))
|
||||||
"type '%s'" % (name, key, value))
|
|
||||||
if qtype in types_seen:
|
if qtype in types_seen:
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info, "Alternate '%s' member '%s' can't "
|
||||||
"Alternate '%s' member '%s' can't "
|
"be distinguished from member '%s'"
|
||||||
"be distinguished from member '%s'"
|
% (name, key, types_seen[qtype]))
|
||||||
% (name, key, types_seen[qtype]))
|
|
||||||
types_seen[qtype] = key
|
types_seen[qtype] = key
|
||||||
|
|
||||||
|
|
||||||
def check_enum(expr, expr_info):
|
def check_enum(expr, info):
|
||||||
name = expr['enum']
|
name = expr['enum']
|
||||||
members = expr.get('data')
|
members = expr.get('data')
|
||||||
prefix = expr.get('prefix')
|
prefix = expr.get('prefix')
|
||||||
|
|
||||||
if not isinstance(members, list):
|
if not isinstance(members, list):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"Enum '%s' requires an array for 'data'" % name)
|
"Enum '%s' requires an array for 'data'" % name)
|
||||||
if prefix is not None and not isinstance(prefix, str):
|
if prefix is not None and not isinstance(prefix, str):
|
||||||
raise QAPIExprError(expr_info,
|
raise QAPISemError(info,
|
||||||
"Enum '%s' requires a string for 'prefix'" % name)
|
"Enum '%s' requires a string for 'prefix'" % name)
|
||||||
for member in members:
|
for member in members:
|
||||||
check_name(expr_info, "Member of enum '%s'" % name, member,
|
check_name(info, "Member of enum '%s'" % name, member,
|
||||||
enum_member=True)
|
enum_member=True)
|
||||||
|
|
||||||
|
|
||||||
def check_struct(expr, expr_info):
|
def check_struct(expr, info):
|
||||||
name = expr['struct']
|
name = expr['struct']
|
||||||
members = expr['data']
|
members = expr['data']
|
||||||
|
|
||||||
check_type(expr_info, "'data' for struct '%s'" % name, members,
|
check_type(info, "'data' for struct '%s'" % name, members,
|
||||||
allow_dict=True, allow_optional=True)
|
allow_dict=True, allow_optional=True)
|
||||||
check_type(expr_info, "'base' for struct '%s'" % name, expr.get('base'),
|
check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
|
||||||
allow_metas=['struct'])
|
allow_metas=['struct'])
|
||||||
|
|
||||||
|
|
||||||
|
@ -690,27 +674,24 @@ def check_keys(expr_elem, meta, required, optional=[]):
|
||||||
info = expr_elem['info']
|
info = expr_elem['info']
|
||||||
name = expr[meta]
|
name = expr[meta]
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info, "'%s' key must have a string value" % meta)
|
||||||
"'%s' key must have a string value" % meta)
|
|
||||||
required = required + [meta]
|
required = required + [meta]
|
||||||
for (key, value) in expr.items():
|
for (key, value) in expr.items():
|
||||||
if key not in required and key not in optional:
|
if key not in required and key not in optional:
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info, "Unknown key '%s' in %s '%s'"
|
||||||
"Unknown key '%s' in %s '%s'"
|
% (key, meta, name))
|
||||||
% (key, meta, name))
|
|
||||||
if (key == 'gen' or key == 'success-response') and value is not False:
|
if (key == 'gen' or key == 'success-response') and value is not False:
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info,
|
||||||
"'%s' of %s '%s' should only use false value"
|
"'%s' of %s '%s' should only use false value"
|
||||||
% (key, meta, name))
|
% (key, meta, name))
|
||||||
if key == 'boxed' and value is not True:
|
if key == 'boxed' and value is not True:
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info,
|
||||||
"'%s' of %s '%s' should only use true value"
|
"'%s' of %s '%s' should only use true value"
|
||||||
% (key, meta, name))
|
% (key, meta, name))
|
||||||
for key in required:
|
for key in required:
|
||||||
if key not in expr:
|
if key not in expr:
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info, "Key '%s' is missing from %s '%s'"
|
||||||
"Key '%s' is missing from %s '%s'"
|
% (key, meta, name))
|
||||||
% (key, meta, name))
|
|
||||||
|
|
||||||
|
|
||||||
def check_exprs(exprs):
|
def check_exprs(exprs):
|
||||||
|
@ -743,8 +724,8 @@ def check_exprs(exprs):
|
||||||
check_keys(expr_elem, 'event', [], ['data', 'boxed'])
|
check_keys(expr_elem, 'event', [], ['data', 'boxed'])
|
||||||
add_name(expr['event'], info, 'event')
|
add_name(expr['event'], info, 'event')
|
||||||
else:
|
else:
|
||||||
raise QAPIExprError(expr_elem['info'],
|
raise QAPISemError(expr_elem['info'],
|
||||||
"Expression is missing metatype")
|
"Expression is missing metatype")
|
||||||
|
|
||||||
# Try again for hidden UnionKind enum
|
# Try again for hidden UnionKind enum
|
||||||
for expr_elem in exprs:
|
for expr_elem in exprs:
|
||||||
|
@ -978,8 +959,8 @@ class QAPISchemaObjectType(QAPISchemaType):
|
||||||
|
|
||||||
def check(self, schema):
|
def check(self, schema):
|
||||||
if self.members is False: # check for cycles
|
if self.members is False: # check for cycles
|
||||||
raise QAPIExprError(self.info,
|
raise QAPISemError(self.info,
|
||||||
"Object %s contains itself" % self.name)
|
"Object %s contains itself" % self.name)
|
||||||
if self.members:
|
if self.members:
|
||||||
return
|
return
|
||||||
self.members = False # mark as being checked
|
self.members = False # mark as being checked
|
||||||
|
@ -1051,12 +1032,11 @@ class QAPISchemaMember(object):
|
||||||
def check_clash(self, info, seen):
|
def check_clash(self, info, seen):
|
||||||
cname = c_name(self.name)
|
cname = c_name(self.name)
|
||||||
if cname.lower() != cname and self.owner not in case_whitelist:
|
if cname.lower() != cname and self.owner not in case_whitelist:
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info,
|
||||||
"%s should not use uppercase" % self.describe())
|
"%s should not use uppercase" % self.describe())
|
||||||
if cname in seen:
|
if cname in seen:
|
||||||
raise QAPIExprError(info,
|
raise QAPISemError(info, "%s collides with %s" %
|
||||||
"%s collides with %s"
|
(self.describe(), seen[cname].describe()))
|
||||||
% (self.describe(), seen[cname].describe()))
|
|
||||||
seen[cname] = self
|
seen[cname] = self
|
||||||
|
|
||||||
def _pretty_owner(self):
|
def _pretty_owner(self):
|
||||||
|
@ -1201,14 +1181,13 @@ class QAPISchemaCommand(QAPISchemaEntity):
|
||||||
self.arg_type.check(schema)
|
self.arg_type.check(schema)
|
||||||
if self.boxed:
|
if self.boxed:
|
||||||
if self.arg_type.is_empty():
|
if self.arg_type.is_empty():
|
||||||
raise QAPIExprError(self.info,
|
raise QAPISemError(self.info,
|
||||||
"Cannot use 'boxed' with empty type")
|
"Cannot use 'boxed' with empty type")
|
||||||
else:
|
else:
|
||||||
assert not isinstance(self.arg_type, QAPISchemaAlternateType)
|
assert not isinstance(self.arg_type, QAPISchemaAlternateType)
|
||||||
assert not self.arg_type.variants
|
assert not self.arg_type.variants
|
||||||
elif self.boxed:
|
elif self.boxed:
|
||||||
raise QAPIExprError(self.info,
|
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
|
||||||
"Use of 'boxed' requires 'data'")
|
|
||||||
if self._ret_type_name:
|
if self._ret_type_name:
|
||||||
self.ret_type = schema.lookup_type(self._ret_type_name)
|
self.ret_type = schema.lookup_type(self._ret_type_name)
|
||||||
assert isinstance(self.ret_type, QAPISchemaType)
|
assert isinstance(self.ret_type, QAPISchemaType)
|
||||||
|
@ -1235,14 +1214,13 @@ class QAPISchemaEvent(QAPISchemaEntity):
|
||||||
self.arg_type.check(schema)
|
self.arg_type.check(schema)
|
||||||
if self.boxed:
|
if self.boxed:
|
||||||
if self.arg_type.is_empty():
|
if self.arg_type.is_empty():
|
||||||
raise QAPIExprError(self.info,
|
raise QAPISemError(self.info,
|
||||||
"Cannot use 'boxed' with empty type")
|
"Cannot use 'boxed' with empty type")
|
||||||
else:
|
else:
|
||||||
assert not isinstance(self.arg_type, QAPISchemaAlternateType)
|
assert not isinstance(self.arg_type, QAPISchemaAlternateType)
|
||||||
assert not self.arg_type.variants
|
assert not self.arg_type.variants
|
||||||
elif self.boxed:
|
elif self.boxed:
|
||||||
raise QAPIExprError(self.info,
|
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
|
||||||
"Use of 'boxed' requires 'data'")
|
|
||||||
|
|
||||||
def visit(self, visitor):
|
def visit(self, visitor):
|
||||||
visitor.visit_event(self.name, self.info, self.arg_type, self.boxed)
|
visitor.visit_event(self.name, self.info, self.arg_type, self.boxed)
|
||||||
|
@ -1258,7 +1236,7 @@ class QAPISchema(object):
|
||||||
self._predefining = False
|
self._predefining = False
|
||||||
self._def_exprs()
|
self._def_exprs()
|
||||||
self.check()
|
self.check()
|
||||||
except (QAPISchemaError, QAPIExprError) as err:
|
except QAPIError as err:
|
||||||
print >>sys.stderr, err
|
print >>sys.stderr, err
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue