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:
Marc-André Lureau 2017-01-13 15:41:25 +01:00 committed by Markus Armbruster
parent 91d00e57a1
commit 4148c298d8

View file

@ -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)